KekikStream 1.9.9__py3-none-any.whl → 2.0.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. KekikStream/Core/Extractor/ExtractorBase.py +5 -5
  2. KekikStream/Core/Extractor/ExtractorLoader.py +25 -17
  3. KekikStream/Core/Extractor/ExtractorManager.py +1 -1
  4. KekikStream/Core/Extractor/YTDLPCache.py +35 -0
  5. KekikStream/Core/Plugin/PluginBase.py +4 -1
  6. KekikStream/Core/Plugin/PluginLoader.py +11 -7
  7. KekikStream/Core/__init__.py +1 -0
  8. KekikStream/Extractors/CloseLoad.py +1 -1
  9. KekikStream/Extractors/ContentX.py +13 -0
  10. KekikStream/Extractors/DonilasPlay.py +86 -0
  11. KekikStream/Extractors/Odnoklassniki.py +6 -0
  12. KekikStream/Extractors/PeaceMakerst.py +6 -0
  13. KekikStream/Extractors/RapidVid.py +6 -0
  14. KekikStream/Extractors/SetPlay.py +7 -1
  15. KekikStream/Extractors/VCTPlay.py +41 -0
  16. KekikStream/Extractors/VidMoly.py +52 -30
  17. KekikStream/Extractors/YTDLP.py +97 -58
  18. KekikStream/Plugins/BelgeselX.py +204 -0
  19. KekikStream/Plugins/Dizilla.py +22 -14
  20. KekikStream/Plugins/FilmMakinesi.py +3 -1
  21. KekikStream/Plugins/FilmModu.py +6 -2
  22. KekikStream/Plugins/FullHDFilmizlesene.py +1 -1
  23. KekikStream/Plugins/HDFilmCehennemi.py +83 -8
  24. KekikStream/Plugins/JetFilmizle.py +53 -38
  25. KekikStream/Plugins/KultFilmler.py +1 -1
  26. KekikStream/Plugins/RoketDizi.py +17 -24
  27. KekikStream/Plugins/SelcukFlix.py +51 -52
  28. KekikStream/Plugins/SetFilmIzle.py +259 -0
  29. KekikStream/Plugins/SezonlukDizi.py +28 -7
  30. KekikStream/Plugins/Sinefy.py +15 -9
  31. KekikStream/Plugins/SinemaCX.py +3 -7
  32. KekikStream/Plugins/Sinezy.py +16 -1
  33. KekikStream/Plugins/SuperFilmGeldi.py +1 -1
  34. KekikStream/Plugins/UgurFilm.py +1 -1
  35. {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/METADATA +27 -1
  36. kekikstream-2.0.8.dist-info/RECORD +81 -0
  37. KekikStream/Extractors/FourCX.py +0 -7
  38. KekikStream/Extractors/FourPichive.py +0 -7
  39. KekikStream/Extractors/FourPlayRu.py +0 -7
  40. KekikStream/Extractors/HDStreamAble.py +0 -7
  41. KekikStream/Extractors/Hotlinger.py +0 -7
  42. KekikStream/Extractors/OkRuHTTP.py +0 -7
  43. KekikStream/Extractors/OkRuSSL.py +0 -7
  44. KekikStream/Extractors/Pichive.py +0 -7
  45. KekikStream/Extractors/PlayRu.py +0 -7
  46. KekikStream/Extractors/VidMolyMe.py +0 -7
  47. kekikstream-1.9.9.dist-info/RECORD +0 -86
  48. {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/WHEEL +0 -0
  49. {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/entry_points.txt +0 -0
  50. {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/licenses/LICENSE +0 -0
  51. {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,7 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
3
+ from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, get_ytdlp_extractors
4
4
  from urllib.parse import urlparse
5
- from yt_dlp.extractor import gen_extractors
6
5
  import yt_dlp, re, sys, os
7
6
 
8
7
  class YTDLP(ExtractorBase):
@@ -11,51 +10,120 @@ class YTDLP(ExtractorBase):
11
10
 
12
11
  _FAST_DOMAIN_RE = None # compiled mega-regex (host üstünden)
13
12
 
13
+ _POPULAR_TLDS = {
14
+ "com", "net", "org", "tv", "io", "co", "me", "ly", "ru", "fr", "de", "es", "it",
15
+ "nl", "be", "ch", "at", "uk", "ca", "au", "jp", "kr", "cn", "in", "br", "mx",
16
+ "ar", "tr", "gov", "edu", "mil", "int", "info", "biz", "name", "pro", "aero",
17
+ "coop", "museum", "onion"
18
+ }
19
+
20
+ # 1. Literal TLD Regex: youtube\.com, vimeo\.com
21
+ # sorted by reverse length to prevent partial matches (e.g. 'co' matching 'com')
22
+ _LITERAL_TLD_RE = re.compile(
23
+ rf"([a-z0-9][-a-z0-9]*(?:\\\.[-a-z0-9]+)*\\\.(?:{'|'.join(sorted(_POPULAR_TLDS, key=len, reverse=True))}))",
24
+ re.IGNORECASE
25
+ )
26
+
27
+ # 2. Regex TLD Regex: dailymotion\.[a-z]{2,3}
28
+ _REGEX_TLD_RE = re.compile(
29
+ r"([a-z0-9][-a-z0-9]*)\\\.\[a-z\]\{?\d*,?\d*\}?",
30
+ re.IGNORECASE
31
+ )
32
+
33
+ # 3. Alternation TLD Regex: \.(?:com|net|org)
34
+ _ALT_TLD_RE = re.compile(
35
+ r"\\\.\(\?:([a-z|]+)\)",
36
+ re.IGNORECASE
37
+ )
38
+
39
+ # Kelime yakalayıcı (domain bulmak için)
40
+ _DOMAIN_WORD_RE = re.compile(
41
+ r"([a-z0-9][-a-z0-9]*)",
42
+ re.IGNORECASE
43
+ )
44
+
14
45
  @classmethod
15
- def _init_fast_domain_regex(cls):
16
- if cls._FAST_DOMAIN_RE is not None:
17
- return
46
+ def _extract_literal_domains(cls, valid_url: str) -> set[str]:
47
+ """Pattern 1: Literal TLD domainlerini (youtube.com) çıkarır."""
48
+ return {
49
+ m.replace(r"\.", ".").lower()
50
+ for m in cls._LITERAL_TLD_RE.findall(valid_url)
51
+ }
52
+
53
+ @classmethod
54
+ def _extract_regex_tld_domains(cls, valid_url: str) -> set[str]:
55
+ """Pattern 2: Regex TLD domainlerini (dailymotion.[...]) çıkarır ve popüler TLD'lerle birleştirir."""
56
+ domains = set()
57
+ for base in cls._REGEX_TLD_RE.findall(valid_url):
58
+ base_domain = base.lower()
59
+ for tld in cls._POPULAR_TLDS:
60
+ domains.add(f"{base_domain}.{tld}")
61
+ return domains
18
62
 
63
+ @classmethod
64
+ def _extract_alternation_domains(cls, valid_url: str) -> set[str]:
65
+ """Pattern 3: Alternation TLD domainlerini (pornhub.(?:com|net)) çıkarır."""
19
66
  domains = set()
67
+ for m in cls._ALT_TLD_RE.finditer(valid_url):
68
+ tlds = m.group(1).split("|")
69
+ start = m.start()
70
+
71
+ # Geriye doğru git ve domain'i bul
72
+ before = valid_url[:start]
73
+
74
+ # 1. Named Groups (?P<name> temizle
75
+ before = re.sub(r"\(\?P<[^>]+>", "", before)
20
76
 
21
- # yt-dlp extractor'larının _VALID_URL regex'lerinden domain yakala
22
- # Regex metinlerinde domainler genelde "\." şeklinde geçer.
23
- domain_pat = re.compile(r"(?:[a-z0-9-]+\\\.)+[a-z]{2,}", re.IGNORECASE)
77
+ # 2. Simple Non-Capturing Groups (?:xxx)? temizle (sadece alphanumeric ve escape)
78
+ before = re.sub(r"\(\?:[a-z0-9-]+\)\??", "", before)
24
79
 
25
- for ie in gen_extractors():
26
- # Generic'i fast-path'e dahil etmiyoruz
27
- if getattr(ie, "IE_NAME", "").lower() == "generic":
80
+ # Son domain-like kelimeyi al
81
+ words = cls._DOMAIN_WORD_RE.findall(before)
82
+ if not words:
28
83
  continue
29
84
 
85
+ base = words[-1].lower()
86
+ for tld in tlds:
87
+ tld = tld.strip().lower()
88
+ if tld and len(tld) <= 6:
89
+ domains.add(f"{base}.{tld}")
90
+
91
+ return domains
92
+
93
+ @classmethod
94
+ def _init_fast_domain_regex(cls):
95
+ """
96
+ Fast domain regex'i initialize et
97
+ """
98
+ if cls._FAST_DOMAIN_RE is not None:
99
+ return
100
+
101
+ domains = set()
102
+ extractors = get_ytdlp_extractors()
103
+
104
+ for ie in extractors:
30
105
  valid = getattr(ie, "_VALID_URL", None)
31
106
  if not valid or not isinstance(valid, str):
32
107
  continue
33
108
 
34
- for m in domain_pat.findall(valid):
35
- d = m.replace(r"\.", ".").lower()
36
-
37
- # Çok agresif/şüpheli şeyleri elemek istersen burada filtre koyabilirsin
38
- # (genelde gerek kalmıyor)
39
- domains.add(d)
109
+ domains |= cls._extract_literal_domains(valid)
110
+ domains |= cls._extract_regex_tld_domains(valid)
111
+ domains |= cls._extract_alternation_domains(valid)
40
112
 
41
113
  # Hiç domain çıkmazsa (çok uç durum) fallback: boş regex
42
114
  if not domains:
43
115
  cls._FAST_DOMAIN_RE = re.compile(r"$^") # hiçbir şeye match etmez
44
116
  return
45
117
 
46
- # Host eşleştirmesi: subdomain destekli (m.youtube.com, player.vimeo.com vs.)
47
- # (?:^|.*\.) (domain1|domain2|...) $
48
- joined = "|".join(sorted(re.escape(d) for d in domains))
49
- pattern = rf"(?:^|.*\.)(?:{joined})$"
50
- cls._FAST_DOMAIN_RE = re.compile(pattern, re.IGNORECASE)
118
+ joined = "|".join(re.escape(d) for d in sorted(domains))
119
+ cls._FAST_DOMAIN_RE = re.compile(rf"(?:^|.*\.)(?:{joined})$", re.IGNORECASE)
51
120
 
52
121
  def __init__(self):
53
122
  self.__class__._init_fast_domain_regex()
54
123
 
55
124
  def can_handle_url(self, url: str) -> bool:
56
125
  """
57
- Fast-path: URL host'unu tek mega-regex ile kontrol et (loop yok)
58
- Slow-path: gerekirse mevcut extract_info tabanlı kontrolün
126
+ Fast-path: URL host'unu tek mega-regex ile kontrol et
59
127
  """
60
128
  # URL parse + host al
61
129
  try:
@@ -76,39 +144,8 @@ class YTDLP(ExtractorBase):
76
144
  if host and self.__class__._FAST_DOMAIN_RE.search(host):
77
145
  return True
78
146
 
79
- # SLOW PATH: Diğer siteler için yt-dlp'nin native kontrolü
80
- try:
81
- # stderr'ı geçici olarak kapat (hata mesajlarını gizle)
82
- old_stderr = sys.stderr
83
- sys.stderr = open(os.devnull, "w")
84
-
85
- try:
86
- ydl_opts = {
87
- "simulate" : True, # Download yok, sadece tespit
88
- "quiet" : True, # Log kirliliği yok
89
- "no_warnings" : True, # Uyarı mesajları yok
90
- "extract_flat" : True, # Minimal işlem
91
- "no_check_certificates" : True,
92
- "ignoreerrors" : True # Hataları yoksay
93
- }
94
-
95
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
96
- # URL'yi işleyebiliyor mu kontrol et
97
- info = ydl.extract_info(url, download=False, process=False)
98
-
99
- # Generic extractor ise atla
100
- if info and info.get("extractor_key") != "Generic":
101
- return True
102
-
103
- return False
104
- finally:
105
- # stderr'ı geri yükle
106
- sys.stderr.close()
107
- sys.stderr = old_stderr
108
-
109
- except Exception:
110
- # yt-dlp işleyemezse False döndür
111
- return False
147
+ # yt-dlp işleyemezse False döndür
148
+ return False
112
149
 
113
150
  async def extract(self, url: str, referer: str | None = None) -> ExtractResult:
114
151
  ydl_opts = {
@@ -116,7 +153,9 @@ class YTDLP(ExtractorBase):
116
153
  "no_warnings" : True,
117
154
  "extract_flat" : False, # Tam bilgi al
118
155
  "format" : "best", # En iyi kalite
119
- "no_check_certificates" : True
156
+ "no_check_certificates" : True,
157
+ "socket_timeout" : 3,
158
+ "retries" : 1
120
159
  }
121
160
 
122
161
  # Referer varsa header olarak ekle
@@ -0,0 +1,204 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode
4
+ from parsel import Selector
5
+ import re
6
+
7
+ class BelgeselX(PluginBase):
8
+ name = "BelgeselX"
9
+ language = "tr"
10
+ main_url = "https://belgeselx.com"
11
+ favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
12
+ description = "2022 yılında son çıkan belgeselleri belgeselx.com'da izle. En yeni belgeseller, türkçe altyazılı yada dublaj olarak 1080p kalitesinde hd belgesel izle."
13
+
14
+ main_page = {
15
+ f"{main_url}/konu/turk-tarihi-belgeselleri&page=" : "Türk Tarihi",
16
+ f"{main_url}/konu/tarih-belgeselleri&page=" : "Tarih",
17
+ f"{main_url}/konu/seyehat-belgeselleri&page=" : "Seyahat",
18
+ f"{main_url}/konu/seri-belgeseller&page=" : "Seri",
19
+ f"{main_url}/konu/savas-belgeselleri&page=" : "Savaş",
20
+ f"{main_url}/konu/sanat-belgeselleri&page=" : "Sanat",
21
+ f"{main_url}/konu/psikoloji-belgeselleri&page=" : "Psikoloji",
22
+ f"{main_url}/konu/polisiye-belgeselleri&page=" : "Polisiye",
23
+ f"{main_url}/konu/otomobil-belgeselleri&page=" : "Otomobil",
24
+ f"{main_url}/konu/nazi-belgeselleri&page=" : "Nazi",
25
+ f"{main_url}/konu/muhendislik-belgeselleri&page=" : "Mühendislik",
26
+ f"{main_url}/konu/kultur-din-belgeselleri&page=" : "Kültür Din",
27
+ f"{main_url}/konu/kozmik-belgeseller&page=" : "Kozmik",
28
+ f"{main_url}/konu/hayvan-belgeselleri&page=" : "Hayvan",
29
+ f"{main_url}/konu/eski-tarih-belgeselleri&page=" : "Eski Tarih",
30
+ f"{main_url}/konu/egitim-belgeselleri&page=" : "Eğitim",
31
+ f"{main_url}/konu/dunya-belgeselleri&page=" : "Dünya",
32
+ f"{main_url}/konu/doga-belgeselleri&page=" : "Doğa",
33
+ f"{main_url}/konu/bilim-belgeselleri&page=" : "Bilim"
34
+ }
35
+
36
+ @staticmethod
37
+ def _to_title_case(text: str) -> str:
38
+ """Türkçe için title case dönüşümü."""
39
+ return " ".join(
40
+ word.lower().replace("i", "İ").capitalize() if word.lower().startswith("i") else word.capitalize()
41
+ for word in text.split()
42
+ )
43
+
44
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
45
+ istek = self.cloudscraper.get(f"{url}{page}")
46
+ secici = Selector(istek.text)
47
+
48
+ results = []
49
+ for item in secici.css("div.gen-movie-contain > div.gen-info-contain > div.gen-movie-info"):
50
+ title = item.css("div.gen-movie-info > h3 a::text").get()
51
+ href = item.css("div.gen-movie-info > h3 a::attr(href)").get()
52
+ parent = item.xpath("../../..")
53
+ poster = parent.css("div.gen-movie-img > img::attr(src)").get()
54
+
55
+ if title and href:
56
+ results.append(MainPageResult(
57
+ category = category,
58
+ title = self._to_title_case(title.strip()),
59
+ url = self.fix_url(href),
60
+ poster = self.fix_url(poster) if poster else None
61
+ ))
62
+
63
+ return results
64
+
65
+ async def search(self, query: str) -> list[SearchResult]:
66
+ # Google Custom Search API kullanıyor
67
+ cx = "016376594590146270301:iwmy65ijgrm"
68
+
69
+ token_resp = self.cloudscraper.get(f"https://cse.google.com/cse.js?cx={cx}")
70
+ token_text = token_resp.text
71
+
72
+ cse_lib_match = re.search(r'cselibVersion": "(.*)"', token_text)
73
+ cse_tok_match = re.search(r'cse_token": "(.*)"', token_text)
74
+
75
+ if not cse_lib_match or not cse_tok_match:
76
+ return []
77
+
78
+ cse_lib = cse_lib_match.group(1)
79
+ cse_tok = cse_tok_match.group(1)
80
+
81
+ search_url = (
82
+ f"https://cse.google.com/cse/element/v1?"
83
+ f"rsz=filtered_cse&num=100&hl=tr&source=gcsc&cselibv={cse_lib}&cx={cx}"
84
+ f"&q={query}&safe=off&cse_tok={cse_tok}&sort=&exp=cc%2Capo&oq={query}"
85
+ f"&callback=google.search.cse.api9969&rurl=https%3A%2F%2Fbelgeselx.com%2F"
86
+ )
87
+
88
+ resp = self.cloudscraper.get(search_url)
89
+ resp_text = resp.text
90
+
91
+ titles = re.findall(r'"titleNoFormatting": "(.*?)"', resp_text)
92
+ urls = re.findall(r'"url": "(.*?)"', resp_text)
93
+ images = re.findall(r'"ogImage": "(.*?)"', resp_text)
94
+
95
+ results = []
96
+ for i, title in enumerate(titles):
97
+ url_val = urls[i] if i < len(urls) else None
98
+ poster = images[i] if i < len(images) else None
99
+
100
+ if not url_val or "diziresimleri" not in url_val:
101
+ # URL'den belgesel linkini oluştur
102
+ if poster and "diziresimleri" in poster:
103
+ file_name = poster.rsplit("/", 1)[-1]
104
+ file_name = re.sub(r"\.(jpe?g|png|webp)$", "", file_name)
105
+ url_val = f"{self.main_url}/belgeseldizi/{file_name}"
106
+ else:
107
+ continue
108
+
109
+ clean_title = title.split("İzle")[0].strip()
110
+ results.append(SearchResult(
111
+ title = self._to_title_case(clean_title),
112
+ url = url_val,
113
+ poster = poster
114
+ ))
115
+
116
+ return results
117
+
118
+ async def load_item(self, url: str) -> SeriesInfo:
119
+ istek = await self.httpx.get(url)
120
+ secici = Selector(istek.text)
121
+
122
+ title = secici.css("h2.gen-title::text").get()
123
+ poster = secici.css("div.gen-tv-show-top img::attr(src)").get()
124
+ description = secici.css("div.gen-single-tv-show-info p::text").get()
125
+
126
+ tags = []
127
+ for tag_link in secici.css("div.gen-socail-share a[href*='belgeselkanali']"):
128
+ tag_href = tag_link.css("::attr(href)").get()
129
+ if tag_href:
130
+ tag_name = tag_href.rsplit("/", 1)[-1].replace("-", " ")
131
+ tags.append(self._to_title_case(tag_name))
132
+
133
+ episodes = []
134
+ counter = 0
135
+ for ep_item in secici.css("div.gen-movie-contain"):
136
+ ep_name = ep_item.css("div.gen-movie-info h3 a::text").get()
137
+ ep_href = ep_item.css("div.gen-movie-info h3 a::attr(href)").get()
138
+
139
+ if not ep_name or not ep_href:
140
+ continue
141
+
142
+ season_text = ep_item.css("div.gen-single-meta-holder ul li::text").get() or ""
143
+ episode_match = re.search(r"Bölüm (\d+)", season_text)
144
+ season_match = re.search(r"Sezon (\d+)", season_text)
145
+
146
+ ep_episode = int(episode_match.group(1)) if episode_match else counter
147
+ ep_season = int(season_match.group(1)) if season_match else 1
148
+
149
+ counter += 1
150
+
151
+ episodes.append(Episode(
152
+ season = ep_season,
153
+ episode = ep_episode,
154
+ title = ep_name.strip(),
155
+ url = self.fix_url(ep_href)
156
+ ))
157
+
158
+ return SeriesInfo(
159
+ url = url,
160
+ poster = self.fix_url(poster) if poster else None,
161
+ title = self._to_title_case(title.strip()) if title else None,
162
+ description = description.strip() if description else None,
163
+ tags = tags,
164
+ episodes = episodes
165
+ )
166
+
167
+ async def load_links(self, url: str) -> list[dict]:
168
+ istek = await self.httpx.get(url)
169
+ text = istek.text
170
+
171
+ # fnc_addWatch div'inden data-episode ID'sini al
172
+ ep_match = re.search(r'<div[^>]*class=["\'][^"\']*fnc_addWatch[^"\']*["\'][^>]*data-episode=["\'](\d+)["\']', text)
173
+ if not ep_match:
174
+ return []
175
+
176
+ episode_id = ep_match.group(1)
177
+ iframe_url = f"{self.main_url}/video/data/new4.php?id={episode_id}"
178
+
179
+ iframe_resp = await self.httpx.get(iframe_url, headers={"Referer": url})
180
+ iframe_text = iframe_resp.text
181
+
182
+ links = []
183
+ for match in re.finditer(r'file:"([^"]+)", label: "([^"]+)"', iframe_text):
184
+ video_url = match.group(1)
185
+ quality = match.group(2)
186
+
187
+ source_name = "Google" if quality == "FULL" else self.name
188
+ quality_str = "1080p" if quality == "FULL" else quality
189
+
190
+ links.append({
191
+ "url" : video_url,
192
+ "name" : f"{source_name} | {quality_str}",
193
+ "referer" : url
194
+ })
195
+
196
+ return links
197
+
198
+ async def play(self, **kwargs):
199
+ extract_result = ExtractResult(**kwargs)
200
+ self.media_handler.title = kwargs.get("name")
201
+ if self.name not in self.media_handler.title:
202
+ self.media_handler.title = f"{self.name} | {self.media_handler.title}"
203
+
204
+ self.media_handler.play_media(extract_result)
@@ -137,7 +137,7 @@ class Dizilla(PluginBase):
137
137
  year = veri.get("datePublished").split("-")[0]
138
138
 
139
139
  # Tags extraction from page content (h3 tag)
140
- tags_raw = secici.css("h3.text-white.opacity-60::text").get()
140
+ tags_raw = secici.css("div.poster.poster h3::text").get()
141
141
  tags = [t.strip() for t in tags_raw.split(",")] if tags_raw else []
142
142
 
143
143
  rating = veri.get("aggregateRating", {}).get("ratingValue")
@@ -179,17 +179,25 @@ class Dizilla(PluginBase):
179
179
  decrypted = await self.decrypt_response(secure_data)
180
180
  results = decrypted.get("RelatedResults", {}).get("getEpisodeSources", {}).get("result", [])
181
181
 
182
- links = []
183
- for result in results:
184
- iframe_src = Selector(result.get("source_content")).css("iframe::attr(src)").get()
185
- iframe_url = self.fix_url(iframe_src)
186
- if not iframe_url:
187
- continue
182
+ if not results:
183
+ return []
188
184
 
189
- extractor = self.ex_manager.find_extractor(iframe_url)
190
- links.append({
191
- "url" : iframe_url,
192
- "name" : f"{extractor.name if extractor else 'Main Player'} | {result.get('language_name')}",
193
- })
194
-
195
- return links
185
+ # Get first source (matching Kotlin)
186
+ first_result = results[0]
187
+ source_content = first_result.get("source_content", "")
188
+
189
+ # Clean the source_content string (matching Kotlin: .replace("\"", "").replace("\\", ""))
190
+ cleaned_source = source_content.replace('"', '').replace('\\', '')
191
+
192
+ # Parse cleaned HTML
193
+ iframe_src = Selector(cleaned_source).css("iframe::attr(src)").get()
194
+ iframe_url = self.fix_url(iframe_src)
195
+
196
+ if not iframe_url:
197
+ return []
198
+
199
+ extractor = self.ex_manager.find_extractor(iframe_url)
200
+ return [{
201
+ "url" : iframe_url,
202
+ "name" : f"{extractor.name if extractor else 'Main Player'} | {first_result.get('language_name', 'Unknown')}",
203
+ }]
@@ -8,7 +8,7 @@ class FilmMakinesi(PluginBase):
8
8
  language = "tr"
9
9
  main_url = "https://filmmakinesi.to"
10
10
  favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
11
- description = "Film Makinesi, en yeni ve en güncel filmleri sitemizde full HD kalite farkı ile izleyebilirsiniz. HD film izle denildiğinde akla gelen en kaliteli film izleme sitesi."
11
+ description = "Film Makinesi ile en yeni ve güncel filmleri Full HD kalite farkı ile izleyebilirsiniz. Film izle denildiğinde akla gelen en kaliteli film sitesi."
12
12
 
13
13
  main_page = {
14
14
  f"{main_url}/filmler-1/" : "Son Filmler",
@@ -83,6 +83,7 @@ class FilmMakinesi(PluginBase):
83
83
  rating = rating.strip().split()[0]
84
84
  year = secici.css("span.date a::text").get().strip()
85
85
  actors = secici.css("div.cast-name::text").getall()
86
+ tags = secici.css("div.genre a::text").getall()
86
87
  duration = secici.css("div.time::text").get()
87
88
  if duration:
88
89
  duration = duration.split()[1].strip()
@@ -92,6 +93,7 @@ class FilmMakinesi(PluginBase):
92
93
  poster = self.fix_url(poster),
93
94
  title = self.clean_title(title),
94
95
  description = description,
96
+ tags = tags,
95
97
  rating = rating,
96
98
  year = year,
97
99
  actors = actors,
@@ -9,7 +9,7 @@ class FilmModu(PluginBase):
9
9
  language = "tr"
10
10
  main_url = "https://www.filmmodu.ws"
11
11
  favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
12
- description = "HD Film izle, Türkçe Dublaj ve Altyazılı filmler."
12
+ description = "Film modun geldiyse yüksek kalitede yeni filmleri izle, 1080p izleyebileceğiniz reklamsız tek film sitesi."
13
13
 
14
14
  main_page = {
15
15
  f"{main_url}/hd-film-kategori/4k-film-izle?page=SAYFA" : "4K",
@@ -91,9 +91,13 @@ class FilmModu(PluginBase):
91
91
  istek = await self.httpx.get(url)
92
92
  secici = Selector(istek.text)
93
93
 
94
+ alternates = secici.css("div.alternates a")
95
+ if not alternates:
96
+ return [] # No alternates available
97
+
94
98
  results = []
95
99
 
96
- for alternatif in secici.css("div.alternates a"):
100
+ for alternatif in alternates:
97
101
  alt_link = self.fix_url(alternatif.css("::attr(href)").get())
98
102
  alt_name = alternatif.css("::text").get()
99
103
 
@@ -10,7 +10,7 @@ class FullHDFilmizlesene(PluginBase):
10
10
  language = "tr"
11
11
  main_url = "https://www.fullhdfilmizlesene.tv"
12
12
  favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
13
- description = "Sinema zevkini evinize kadar getirdik. Türkiye'nin lider Film sitesinde, en yeni filmleri Full HD izleyin."
13
+ description = "Türkiye'nin ilk ve lider HD film izleme platformu, kaliteli ve sorunsuz hizmetiyle sinema keyfini zirveye taşır."
14
14
 
15
15
  main_page = {
16
16
  f"{main_url}/en-cok-izlenen-hd-filmler/" : "En Çok izlenen Filmler",
@@ -3,14 +3,14 @@
3
3
  from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult, Subtitle
4
4
  from parsel import Selector
5
5
  from Kekik.Sifreleme import Packer, StreamDecoder
6
- import random, string, re
6
+ import random, string, re, base64
7
7
 
8
8
  class HDFilmCehennemi(PluginBase):
9
9
  name = "HDFilmCehennemi"
10
10
  language = "tr"
11
11
  main_url = "https://www.hdfilmcehennemi.ws"
12
12
  favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
13
- description = "Türkiye'nin en hızlı hd film izleme sitesi"
13
+ description = "Türkiye'nin en hızlı hd film izleme sitesi. Tek ve gerçek hdfilmcehennemi sitesi."
14
14
 
15
15
 
16
16
 
@@ -146,17 +146,85 @@ class HDFilmCehennemi(PluginBase):
146
146
 
147
147
  return results
148
148
 
149
+ def hdch_decode(self, value_parts: list[str]) -> str:
150
+ """
151
+ HDFilmCehennemi için özel decoder.
152
+ JavaScript sırası: join -> reverse -> atob -> atob -> shift_back
153
+ """
154
+ try:
155
+ # 1. Parçaları birleştir
156
+ joined = ''.join(value_parts)
157
+
158
+ # 2. Ters çevir (REV)
159
+ result = StreamDecoder._reverse(joined)
160
+
161
+ # 3. İlk base64 decode (B64D)
162
+ result = StreamDecoder._base64_decode(result)
163
+ if result is None:
164
+ return ""
165
+
166
+ # 4. İkinci base64 decode (B64D) - JavaScript'te iki kez atob yapılıyor!
167
+ result = StreamDecoder._base64_decode(result)
168
+ if result is None:
169
+ return ""
170
+
171
+ # 5. Shift back (SHF)
172
+ result = StreamDecoder._shift_back(result)
173
+
174
+ return result
175
+ except Exception:
176
+ return ""
177
+
178
+ def extract_hdch_url(self, unpacked: str) -> str:
179
+ """HDFilmCehennemi unpacked script'ten video URL'sini çıkar"""
180
+ # 1) Decode fonksiyonunun adını bul: function <NAME>(value_parts)
181
+ match_fn = re.search(r'function\s+(\w+)\s*\(\s*value_parts\s*\)', unpacked)
182
+ if not match_fn:
183
+ return ""
184
+
185
+ fn_name = match_fn.group(1)
186
+
187
+ # 2) Bu fonksiyonun array ile çağrıldığı yeri bul: <NAME>([ ... ])
188
+ array_call_regex = re.compile(rf'{re.escape(fn_name)}\(\s*\[(.*?)\]\s*\)', re.DOTALL)
189
+ match_call = array_call_regex.search(unpacked)
190
+ if not match_call:
191
+ return ""
192
+
193
+ array_body = match_call.group(1)
194
+
195
+ # 3) Array içindeki string parçalarını topla
196
+ parts = re.findall(r'["\']([^"\']+)["\']', array_body)
197
+ if not parts:
198
+ return ""
199
+
200
+ # 4) Özel decoder ile çöz
201
+ return self.hdch_decode(parts)
202
+
149
203
  async def invoke_local_source(self, iframe: str, source: str, url: str):
150
- self.httpx.headers.update({"Referer": f"{self.main_url}/"})
204
+ self.httpx.headers.update({
205
+ "Referer": f"{self.main_url}/",
206
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0"
207
+ })
151
208
  istek = await self.httpx.get(iframe)
152
209
 
210
+ if not istek.text:
211
+ return await self.cehennempass(iframe.split("/")[-1])
212
+
213
+ # eval(function...) içeren packed script bul
214
+ eval_match = re.search(r'(eval\(function[\s\S]+)', istek.text)
215
+ if not eval_match:
216
+ return await self.cehennempass(iframe.split("/")[-1])
217
+
153
218
  try:
154
- eval_func = re.compile(r'\s*(eval\(function[\s\S].*)\s*').findall(istek.text)[0]
219
+ unpacked = Packer.unpack(eval_match.group(1))
155
220
  except Exception:
156
221
  return await self.cehennempass(iframe.split("/")[-1])
157
222
 
158
- unpacked = Packer.unpack(eval_func)
159
- video_url = StreamDecoder.extract_stream_url(unpacked)
223
+ # HDFilmCehennemi özel decoder ile video URL'sini çıkar
224
+ video_url = self.extract_hdch_url(unpacked)
225
+
226
+ if not video_url:
227
+ return await self.cehennempass(iframe.split("/")[-1])
160
228
 
161
229
  subtitles = []
162
230
  try:
@@ -200,8 +268,15 @@ class HDFilmCehennemi(PluginBase):
200
268
  match = re.search(r'data-src=\\\"([^"]+)', api_get.text)
201
269
  iframe = match[1].replace("\\", "") if match else None
202
270
 
203
- if iframe and "?rapidrame_id=" in iframe:
204
- iframe = f"{self.main_url}/playerr/{iframe.split('?rapidrame_id=')[1]}"
271
+ if not iframe:
272
+ continue
273
+
274
+ # mobi URL'si varsa direkt kullan (query string'i kaldır)
275
+ if "mobi" in iframe:
276
+ iframe = iframe.split("?")[0] # rapidrame_id query param'ı kaldır
277
+ # mobi değilse ve rapidrame varsa rplayer kullan
278
+ elif "rapidrame" in iframe and "?rapidrame_id=" in iframe:
279
+ iframe = f"{self.main_url}/rplayer/{iframe.split('?rapidrame_id=')[1]}"
205
280
 
206
281
  video_data_list = await self.invoke_local_source(iframe, source, url)
207
282
  if not video_data_list: