KekikStream 2.2.9__py3-none-any.whl → 2.5.3__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.

Potentially problematic release.


This version of KekikStream might be problematic. Click here for more details.

Files changed (88) hide show
  1. KekikStream/Core/Extractor/ExtractorBase.py +3 -2
  2. KekikStream/Core/Extractor/ExtractorLoader.py +8 -14
  3. KekikStream/Core/HTMLHelper.py +205 -0
  4. KekikStream/Core/Plugin/PluginBase.py +48 -12
  5. KekikStream/Core/Plugin/PluginLoader.py +13 -14
  6. KekikStream/Core/Plugin/PluginManager.py +2 -2
  7. KekikStream/Core/Plugin/PluginModels.py +0 -3
  8. KekikStream/Core/__init__.py +2 -0
  9. KekikStream/Extractors/Abstream.py +27 -0
  10. KekikStream/Extractors/CloseLoad.py +31 -56
  11. KekikStream/Extractors/ContentX.py +28 -71
  12. KekikStream/Extractors/DonilasPlay.py +34 -78
  13. KekikStream/Extractors/DzenRu.py +11 -25
  14. KekikStream/Extractors/ExPlay.py +20 -38
  15. KekikStream/Extractors/Filemoon.py +23 -53
  16. KekikStream/Extractors/HDMomPlayer.py +30 -0
  17. KekikStream/Extractors/HDPlayerSystem.py +13 -31
  18. KekikStream/Extractors/HotStream.py +27 -0
  19. KekikStream/Extractors/JFVid.py +3 -24
  20. KekikStream/Extractors/JetTv.py +21 -34
  21. KekikStream/Extractors/JetV.py +55 -0
  22. KekikStream/Extractors/MailRu.py +11 -29
  23. KekikStream/Extractors/MixPlayHD.py +17 -31
  24. KekikStream/Extractors/MixTiger.py +17 -40
  25. KekikStream/Extractors/MolyStream.py +25 -22
  26. KekikStream/Extractors/Odnoklassniki.py +41 -105
  27. KekikStream/Extractors/PeaceMakerst.py +20 -47
  28. KekikStream/Extractors/PixelDrain.py +9 -16
  29. KekikStream/Extractors/PlayerFilmIzle.py +23 -46
  30. KekikStream/Extractors/RapidVid.py +23 -36
  31. KekikStream/Extractors/SetPlay.py +19 -44
  32. KekikStream/Extractors/SetPrime.py +3 -6
  33. KekikStream/Extractors/SibNet.py +8 -19
  34. KekikStream/Extractors/Sobreatsesuyp.py +25 -47
  35. KekikStream/Extractors/TRsTX.py +25 -55
  36. KekikStream/Extractors/TurboImgz.py +8 -16
  37. KekikStream/Extractors/TurkeyPlayer.py +5 -5
  38. KekikStream/Extractors/VCTPlay.py +10 -28
  39. KekikStream/Extractors/Veev.py +145 -0
  40. KekikStream/Extractors/VidBiz.py +62 -0
  41. KekikStream/Extractors/VidHide.py +59 -34
  42. KekikStream/Extractors/VidMoly.py +67 -89
  43. KekikStream/Extractors/VidMoxy.py +17 -29
  44. KekikStream/Extractors/VidPapi.py +26 -58
  45. KekikStream/Extractors/VideoSeyred.py +21 -42
  46. KekikStream/Extractors/Videostr.py +58 -0
  47. KekikStream/Extractors/Vidoza.py +18 -0
  48. KekikStream/Extractors/Vtbe.py +38 -0
  49. KekikStream/Extractors/YTDLP.py +2 -2
  50. KekikStream/Extractors/YildizKisaFilm.py +13 -31
  51. KekikStream/Extractors/Zeus.py +61 -0
  52. KekikStream/Plugins/BelgeselX.py +108 -99
  53. KekikStream/Plugins/DiziBox.py +61 -106
  54. KekikStream/Plugins/DiziMom.py +179 -0
  55. KekikStream/Plugins/DiziPal.py +104 -192
  56. KekikStream/Plugins/DiziYou.py +66 -149
  57. KekikStream/Plugins/Dizilla.py +93 -126
  58. KekikStream/Plugins/FilmBip.py +102 -72
  59. KekikStream/Plugins/FilmEkseni.py +199 -0
  60. KekikStream/Plugins/FilmMakinesi.py +101 -64
  61. KekikStream/Plugins/FilmModu.py +35 -59
  62. KekikStream/Plugins/Filmatek.py +184 -0
  63. KekikStream/Plugins/FilmciBaba.py +155 -0
  64. KekikStream/Plugins/FullHDFilmizlesene.py +32 -78
  65. KekikStream/Plugins/HDFilm.py +243 -0
  66. KekikStream/Plugins/HDFilmCehennemi.py +261 -222
  67. KekikStream/Plugins/JetFilmizle.py +117 -98
  68. KekikStream/Plugins/KultFilmler.py +153 -143
  69. KekikStream/Plugins/RecTV.py +53 -49
  70. KekikStream/Plugins/RoketDizi.py +92 -123
  71. KekikStream/Plugins/SelcukFlix.py +86 -95
  72. KekikStream/Plugins/SetFilmIzle.py +105 -143
  73. KekikStream/Plugins/SezonlukDizi.py +106 -128
  74. KekikStream/Plugins/Sinefy.py +194 -166
  75. KekikStream/Plugins/SinemaCX.py +159 -113
  76. KekikStream/Plugins/Sinezy.py +44 -73
  77. KekikStream/Plugins/SuperFilmGeldi.py +28 -52
  78. KekikStream/Plugins/UgurFilm.py +94 -72
  79. KekikStream/Plugins/Watch32.py +160 -0
  80. KekikStream/Plugins/YabanciDizi.py +250 -0
  81. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/METADATA +1 -1
  82. kekikstream-2.5.3.dist-info/RECORD +99 -0
  83. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/WHEEL +1 -1
  84. KekikStream/Plugins/FullHDFilm.py +0 -254
  85. kekikstream-2.2.9.dist-info/RECORD +0 -82
  86. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/entry_points.txt +0 -0
  87. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/licenses/LICENSE +0 -0
  88. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.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 PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult
4
- from selectolax.parser import HTMLParser
5
- import re
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult, HTMLHelper
4
+ import asyncio
6
5
 
7
6
  class JetFilmizle(PluginBase):
8
7
  name = "JetFilmizle"
@@ -40,30 +39,26 @@ class JetFilmizle(PluginBase):
40
39
 
41
40
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
42
41
  istek = await self.httpx.get(f"{url}{page}", follow_redirects=True)
43
- secici = HTMLParser(istek.text)
42
+ secici = HTMLHelper(istek.text)
44
43
 
45
44
  results = []
46
- for veri in secici.css("article.movie"):
47
- # h2-h6 içindeki a linki
48
- title_link = None
45
+ for veri in secici.select("article.movie"):
46
+ title_text = None
49
47
  for h_tag in ["h2", "h3", "h4", "h5", "h6"]:
50
- title_link = veri.css_first(f"{h_tag} a")
51
- if title_link:
48
+ title_text = secici.select_text(f"{h_tag} a", veri)
49
+ if title_text:
52
50
  break
53
51
 
54
- link_el = veri.css_first("a")
55
- img_el = veri.css_first("img")
56
-
57
- title = self.clean_title(title_link.text(strip=True)) if title_link else None
58
- href = link_el.attrs.get("href") if link_el else None
59
- poster = (img_el.attrs.get("data-src") or img_el.attrs.get("src")) if img_el else None
52
+ title = self.clean_title(title_text) if title_text else None
53
+ href = secici.select_attr("a", "href", veri)
54
+ poster = secici.select_poster("img", veri)
60
55
 
61
56
  if title and href:
62
57
  results.append(MainPageResult(
63
58
  category = category,
64
59
  title = title,
65
60
  url = self.fix_url(href),
66
- poster = self.fix_url(poster) if poster else None,
61
+ poster = self.fix_url(poster),
67
62
  ))
68
63
 
69
64
  return results
@@ -74,119 +69,143 @@ class JetFilmizle(PluginBase):
74
69
  data = {"s": query},
75
70
  headers = {"Referer": f"{self.main_url}/"}
76
71
  )
77
- secici = HTMLParser(istek.text)
72
+ secici = HTMLHelper(istek.text)
78
73
 
79
74
  results = []
80
- for article in secici.css("article.movie"):
81
- # h2-h6 içindeki a linki
82
- title_link = None
75
+ for article in secici.select("article.movie"):
76
+ title_text = None
83
77
  for h_tag in ["h2", "h3", "h4", "h5", "h6"]:
84
- title_link = article.css_first(f"{h_tag} a")
85
- if title_link:
78
+ title_text = secici.select_text(f"{h_tag} a", article)
79
+ if title_text:
86
80
  break
87
81
 
88
- link_el = article.css_first("a")
89
- img_el = article.css_first("img")
90
-
91
- title = self.clean_title(title_link.text(strip=True)) if title_link else None
92
- href = link_el.attrs.get("href") if link_el else None
93
- poster = (img_el.attrs.get("data-src") or img_el.attrs.get("src")) if img_el else None
82
+ title = self.clean_title(title_text) if title_text else None
83
+ href = secici.select_attr("a", "href", article)
84
+ poster = secici.select_poster("img", article)
94
85
 
95
86
  if title and href:
96
87
  results.append(SearchResult(
97
88
  title = title,
98
89
  url = self.fix_url(href),
99
- poster = self.fix_url(poster) if poster else None,
90
+ poster = self.fix_url(poster),
100
91
  ))
101
92
 
102
93
  return results
103
94
 
104
95
  async def load_item(self, url: str) -> MovieInfo:
105
96
  istek = await self.httpx.get(url)
106
- secici = HTMLParser(istek.text)
107
- html_text = istek.text
108
-
109
- title_el = secici.css_first("div.movie-exp-title")
110
- title = self.clean_title(title_el.text(strip=True)) if title_el else None
111
-
112
- img_el = secici.css_first("section.movie-exp img")
113
- poster_raw = (img_el.attrs.get("data-src") or img_el.attrs.get("src")) if img_el else None
114
- poster = poster_raw.strip() if poster_raw else None
115
-
116
- desc_el = secici.css_first("section.movie-exp p.aciklama")
117
- description = desc_el.text(strip=True) if desc_el else None
118
-
119
- tags = [a.text(strip=True) for a in secici.css("section.movie-exp div.catss a") if a.text(strip=True)]
120
-
121
- rating_el = secici.css_first("section.movie-exp div.imdb_puan span")
122
- rating = rating_el.text(strip=True) if rating_el else None
123
-
124
- # Year - div.yap içinde 4 haneli sayı ara (xpath yerine regex)
125
- year = None
126
- yap_match = re.search(r'<div class="yap"[^>]*>([^<]*(?:Vizyon|Yapım)[^<]*)</div>', html_text, re.IGNORECASE)
127
- if yap_match:
128
- year_match = re.search(r'(\d{4})', yap_match.group(1))
129
- if year_match:
130
- year = year_match.group(1)
131
-
132
- actors = [a.text(strip=True) for a in secici.css("div[itemprop='actor'] a span") if a.text(strip=True)]
97
+ secici = HTMLHelper(istek.text)
98
+
99
+ title = self.clean_title(secici.select_text("div.movie-exp-title"))
100
+ poster = secici.select_poster("section.movie-exp img")
101
+ description = secici.select_text("section.movie-exp p.aciklama")
102
+ tags = secici.select_texts("section.movie-exp div.catss a")
103
+ rating = secici.select_text("section.movie-exp div.imdb_puan span")
104
+ year = secici.meta_value("Yayın Yılı")
105
+ actors = secici.select_texts("div[itemprop='actor'] a span") or [img.attrs.get("alt") for img in secici.select("div.oyuncular div.oyuncu img") if img.attrs.get("alt")]
106
+ duration = secici.meta_value("Süre")
107
+ duration = duration.split() if duration else None
108
+
109
+ total_minutes = 0
110
+ if duration:
111
+ for i, p in enumerate(duration):
112
+ if p == "saat":
113
+ total_minutes += int(duration[i-1]) * 60
114
+ elif p == "dakika":
115
+ total_minutes += int(duration[i-1])
133
116
 
134
117
  return MovieInfo(
135
118
  url = url,
136
- poster = self.fix_url(poster) if poster else None,
119
+ poster = self.fix_url(poster),
137
120
  title = title,
138
121
  description = description,
139
122
  tags = tags,
140
123
  rating = rating,
141
124
  year = year,
142
- actors = actors
125
+ actors = actors,
126
+ duration = total_minutes if total_minutes else None
143
127
  )
144
128
 
145
- async def load_links(self, url: str) -> list[ExtractResult]:
146
- istek = await self.httpx.get(url)
147
- secici = HTMLParser(istek.text)
148
-
129
+ async def _process_source(self, url: str, name: str, html: str | None) -> list[ExtractResult]:
149
130
  results = []
150
-
151
- # 1) Ana iframe'leri kontrol et
152
- for iframe in secici.css("iframe"):
153
- src = (iframe.attrs.get("src") or
154
- iframe.attrs.get("data-src") or
155
- iframe.attrs.get("data-lazy-src"))
156
-
157
- if src and src != "about:blank":
158
- iframe_url = self.fix_url(src)
159
- data = await self.extract(iframe_url)
160
- if data:
161
- results.append(data)
162
-
163
- # 2) Sayfa numaralarından linkleri topla (Fragman hariç)
164
- page_links = []
165
- for link in secici.css("a.post-page-numbers"):
166
- span_el = link.css_first("span")
167
- isim = span_el.text(strip=True) if span_el else ""
168
- if isim != "Fragman":
169
- href = link.attrs.get("href")
170
- if href:
171
- page_links.append((self.fix_url(href), isim))
172
-
173
- # 3) Her sayfa linkindeki iframe'leri bul
174
- for page_url, isim in page_links:
175
- try:
176
- page_resp = await self.httpx.get(page_url)
177
- page_sel = HTMLParser(page_resp.text)
178
-
179
- for iframe in page_sel.css("div#movie iframe"):
131
+ try:
132
+ if html:
133
+ secici = HTMLHelper(html)
134
+ else:
135
+ resp = await self.httpx.get(url)
136
+ secici = HTMLHelper(resp.text)
137
+
138
+ # Iframe'leri bul
139
+ container = secici.select_first("div#movie") or secici.select_first("div.film-content")
140
+
141
+ if container:
142
+ for iframe in secici.select("iframe", container):
180
143
  src = (iframe.attrs.get("src") or
181
144
  iframe.attrs.get("data-src") or
182
145
  iframe.attrs.get("data-lazy-src"))
183
-
146
+
184
147
  if src and src != "about:blank":
185
148
  iframe_url = self.fix_url(src)
186
- data = await self.extract(iframe_url, prefix=isim)
149
+ # name_override KULLANMA, extractor kendi ismini versin
150
+ # Sonra biz düzenleriz
151
+ data = await self.extract(iframe_url)
152
+
187
153
  if data:
188
- results.append(data)
189
- except Exception:
190
- continue
154
+ items = data if isinstance(data, list) else [data]
155
+
156
+ for item in items:
157
+ # Sadece kalite bilgisi içeriyorsa ekle, yoksa sadece buton adını kullan
158
+ # Özellikle Zeus için kalite önemli (1080p, 720p)
159
+ # Diğerlerinde plugin adı (Apollo, JetPlay vb.) önemsiz
160
+
161
+ # Kalite kontrolü (basitçe)
162
+ quality_indicators = ["1080p", "720p", "480p", "360p", "240p", "144p", "4k", "2k"]
163
+ has_quality = any(q in item.name.lower() for q in quality_indicators)
164
+
165
+ if has_quality:
166
+ # Buton Adı | Extractor Adı (Kalite içerdiği için)
167
+ # Örn: Zeus | 1080p
168
+ # Eğer Extractor adı zaten Buton adını içeriyorsa (Zeus | 1080p -> Zeus) tekrar ekleme
169
+ if name.lower() not in item.name.lower():
170
+ item.name = f"{name} | {item.name}"
171
+ else:
172
+ # Kalite yoksa sadece Buton adını kullan
173
+ # Örn: Apollo | JetTv -> JetTv
174
+ item.name = name
175
+
176
+ results.append(item)
177
+ return results
178
+ except Exception:
179
+ return []
191
180
 
192
- return results
181
+ async def load_links(self, url: str) -> list[ExtractResult]:
182
+ istek = await self.httpx.get(url)
183
+ secici = HTMLHelper(istek.text)
184
+
185
+ sources = []
186
+ if film_part := secici.select_first("div.film_part"):
187
+ # Tüm spanları gez
188
+ for span in secici.select("span", film_part):
189
+ # Eğer bu span bir <a> etiketi içinde değilse, aktif kaynaktır
190
+ if span.parent.tag != "a":
191
+ name = span.text(strip=True)
192
+ if name:
193
+ sources.append((url, name, istek.text)) # html content var
194
+ break
195
+
196
+ # Diğer kaynak linkleri
197
+ for link in secici.select("a.post-page-numbers", film_part):
198
+ name = secici.select_text("span", link) or link.text(strip=True)
199
+ href = link.attrs.get("href")
200
+ if name != "Fragman" and href:
201
+ sources.append((self.fix_url(href), name, None)) # html yok, çekilecek
202
+
203
+ # Eğer film_part yoksa, sadece mevcut sayfayı tara (Tek part olabilir)
204
+ if not sources:
205
+ sources.append((url, "JetFilmizle", istek.text))
206
+
207
+ tasks = []
208
+ for page_url, source_name, html_content in sources:
209
+ tasks.append(self._process_source(page_url, source_name, html_content))
210
+
211
+ return [item for sublist in await asyncio.gather(*tasks) for item in sublist]
@@ -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 PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult, Subtitle
4
- from selectolax.parser import HTMLParser
5
- import re, base64
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult, Subtitle, HTMLHelper
4
+ import base64, asyncio, contextlib
6
5
 
7
6
  class KultFilmler(PluginBase):
8
7
  name = "KultFilmler"
@@ -38,210 +37,221 @@ class KultFilmler(PluginBase):
38
37
 
39
38
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
40
39
  istek = await self.httpx.get(url)
41
- secici = HTMLParser(istek.text)
40
+ secici = HTMLHelper(istek.text)
42
41
 
43
42
  results = []
44
- for veri in secici.css("div.col-md-12 div.movie-box"):
45
- img_el = veri.css_first("div.img img")
46
- link_el = veri.css_first("a")
47
-
48
- title = img_el.attrs.get("alt") if img_el else None
49
- href = link_el.attrs.get("href") if link_el else None
50
- poster = img_el.attrs.get("src") if img_el else None
43
+ for veri in secici.select("div.col-md-12 div.movie-box"):
44
+ title = secici.select_attr("div.img img", "alt", veri)
45
+ href = secici.select_attr("a", "href", veri)
46
+ poster = secici.select_attr("div.img img", "src", veri)
51
47
 
52
48
  if title and href:
53
49
  results.append(MainPageResult(
54
50
  category = category,
55
51
  title = title,
56
52
  url = self.fix_url(href),
57
- poster = self.fix_url(poster) if poster else None,
53
+ poster = self.fix_url(poster),
58
54
  ))
59
55
 
60
56
  return results
61
57
 
62
58
  async def search(self, query: str) -> list[SearchResult]:
63
59
  istek = await self.httpx.get(f"{self.main_url}?s={query}")
64
- secici = HTMLParser(istek.text)
60
+ secici = HTMLHelper(istek.text)
65
61
 
66
62
  results = []
67
- for veri in secici.css("div.movie-box"):
68
- img_el = veri.css_first("div.img img")
69
- link_el = veri.css_first("a")
70
-
71
- title = img_el.attrs.get("alt") if img_el else None
72
- href = link_el.attrs.get("href") if link_el else None
73
- poster = img_el.attrs.get("src") if img_el else None
63
+ for veri in secici.select("div.movie-box"):
64
+ title = secici.select_attr("div.img img", "alt", veri)
65
+ href = secici.select_attr("a", "href", veri)
66
+ poster = secici.select_attr("div.img img", "src", veri)
74
67
 
75
68
  if title and href:
76
69
  results.append(SearchResult(
77
70
  title = title,
78
71
  url = self.fix_url(href),
79
- poster = self.fix_url(poster) if poster else None,
72
+ poster = self.fix_url(poster),
80
73
  ))
81
74
 
82
75
  return results
83
76
 
84
77
  async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
85
78
  istek = await self.httpx.get(url)
86
- secici = HTMLParser(istek.text)
87
-
88
- film_img = secici.css_first("div.film-bilgileri img")
89
- og_title = secici.css_first("[property='og:title']")
90
- og_image = secici.css_first("[property='og:image']")
91
-
92
- title = (film_img.attrs.get("alt") if film_img else None) or (og_title.attrs.get("content") if og_title else None)
93
- poster = self.fix_url(og_image.attrs.get("content")) if og_image else None
94
-
95
- desc_el = secici.css_first("div.description")
96
- description = desc_el.text(strip=True) if desc_el else None
97
-
98
- tags = [a.text(strip=True) for a in secici.css("ul.post-categories a") if a.text(strip=True)]
99
-
100
- # HTML analizine göre güncellenen alanlar
101
- year_el = secici.css_first("li.release span a")
102
- year = year_el.text(strip=True) if year_el else None
103
-
104
- time_el = secici.css_first("li.time span")
105
- duration = None
106
- if time_el:
107
- time_text = time_el.text(strip=True)
108
- dur_match = re.search(r"(\d+)", time_text)
109
- duration = dur_match.group(1) if dur_match else None
79
+ secici = HTMLHelper(istek.text)
110
80
 
111
- rating_el = secici.css_first("div.imdb-count")
112
- rating = rating_el.text(strip=True) if rating_el else None
81
+ title = self.clean_title(secici.select_attr("div.film-bilgileri img", "alt") or secici.select_attr("[property='og:title']", "content"))
82
+ poster = self.fix_url(secici.select_attr("[property='og:image']", "content"))
83
+ description = secici.select_text("div.description")
84
+ tags = secici.select_texts("ul.post-categories a")
85
+ year = secici.extract_year("li.release span a")
86
+ duration = int(secici.regex_first(r"(\d+)", secici.select_text("li.time span")) or 0)
87
+ rating = secici.regex_first(r"(\d+\.\d+|\d+)", secici.select_text("div.imdb-count"))
88
+ actors = secici.select_texts("div.actors a")
113
89
 
114
- actors = [a.text(strip=True) for a in secici.css("div.actors a") if a.text(strip=True)]
115
-
116
- # Dizi mi kontrol et
117
90
  if "/dizi/" in url:
118
91
  episodes = []
119
- for bolum in secici.css("div.episode-box"):
120
- name_link = bolum.css_first("div.name a")
121
- ep_href = name_link.attrs.get("href") if name_link else None
122
-
123
- ssn_el = bolum.css_first("span.episodetitle")
124
- ssn_detail = ssn_el.text(strip=True) if ssn_el else ""
125
-
126
- ep_b_el = bolum.css_first("span.episodetitle b")
127
- ep_detail = ep_b_el.text(strip=True) if ep_b_el else ""
128
-
129
- ep_name = f"{ssn_detail} - {ep_detail}"
130
-
131
- if ep_href:
132
- ep_season = re.search(r"(\d+)\.", ssn_detail)
133
- ep_episode = re.search(r"(\d+)\.", ep_detail)
134
-
135
- episodes.append(Episode(
136
- season = int(ep_season[1]) if ep_season else 1,
137
- episode = int(ep_episode[1]) if ep_episode else 1,
138
- title = ep_name.strip(" -"),
139
- url = self.fix_url(ep_href),
140
- ))
92
+ for bolum in secici.select("div.episode-box"):
93
+ href = secici.select_attr("div.name a", "href", bolum)
94
+ ssn_detail = secici.select_text("span.episodetitle", bolum) or ""
95
+ ep_detail = secici.select_text("span.episodetitle b", bolum) or ""
96
+ if href:
97
+ s, e = secici.extract_season_episode(f"{ssn_detail} {ep_detail}")
98
+ name = f"{ssn_detail} - {ep_detail}".strip(" -")
99
+ episodes.append(Episode(season=s or 1, episode=e or 1, title=name, url=self.fix_url(href)))
141
100
 
142
101
  return SeriesInfo(
143
102
  url = url,
144
103
  poster = poster,
145
- title = self.clean_title(title) if title else "",
104
+ title = title,
146
105
  description = description,
147
106
  tags = tags,
148
107
  year = year,
149
108
  actors = actors,
150
109
  rating = rating,
151
- episodes = episodes,
110
+ episodes = episodes
152
111
  )
153
112
 
154
113
  return MovieInfo(
155
114
  url = url,
156
115
  poster = poster,
157
- title = self.clean_title(title) if title else "",
116
+ title = title,
158
117
  description = description,
159
118
  tags = tags,
160
119
  year = year,
161
120
  rating = rating,
162
121
  actors = actors,
163
- duration = int(duration) if duration else None,
122
+ duration = duration
164
123
  )
165
124
 
166
- def _get_iframe(self, source_code: str) -> str:
167
- """Base64 kodlu iframe'i çözümle"""
168
- atob_match = re.search(r"PHA\+[0-9a-zA-Z+/=]*", source_code)
169
- if not atob_match:
170
- return ""
171
-
172
- atob = atob_match.group()
125
+ def _decode_iframe(self, content: str) -> str | None:
126
+ """Base64 kodlanmış iframe verisini çözer"""
127
+ match = HTMLHelper(content).regex_first(r"PHA\+[0-9a-zA-Z+/=]*")
128
+ if not match:
129
+ return None
173
130
 
174
- # Padding düzelt
175
- padding = 4 - len(atob) % 4
176
- if padding < 4:
177
- atob = atob + "=" * padding
131
+ # Base64 Padding Fix
132
+ pad = len(match) % 4
133
+ if pad:
134
+ match += "=" * (4 - pad)
178
135
 
179
136
  try:
180
- decoded = base64.b64decode(atob).decode("utf-8")
181
- secici = HTMLParser(decoded)
182
- iframe_el = secici.css_first("iframe")
183
- return self.fix_url(iframe_el.attrs.get("src")) if iframe_el else ""
137
+ decoded = base64.b64decode(match).decode("utf-8")
138
+ src = HTMLHelper(decoded).select_attr("iframe", "src")
139
+ return self.fix_url(src) if src else None
184
140
  except Exception:
185
- return ""
141
+ return None
186
142
 
187
- def _extract_subtitle_url(self, source_code: str) -> str | None:
188
- """Altyazı URL'sini çıkar"""
189
- match = re.search(r"(https?://[^\s\"]+\.srt)", source_code)
190
- return match[1] if match else None
191
-
192
- async def load_links(self, url: str) -> list[ExtractResult]:
193
- istek = await self.httpx.get(url)
194
- secici = HTMLParser(istek.text)
195
-
196
- iframes = set()
197
-
198
- # Ana iframe
199
- main_frame = self._get_iframe(istek.text)
200
- if main_frame:
201
- iframes.add(main_frame)
202
-
203
- # Alternatif player'lar
204
- for player in secici.css("div.container#player"):
205
- iframe_el = player.css_first("iframe")
206
- alt_iframe = self.fix_url(iframe_el.attrs.get("src")) if iframe_el else None
207
- if alt_iframe:
208
- alt_istek = await self.httpx.get(alt_iframe)
209
- alt_frame = self._get_iframe(alt_istek.text)
210
- if alt_frame:
211
- iframes.add(alt_frame)
143
+ async def _resolve_alt_page(self, url: str, title: str) -> tuple[str | None, str]:
144
+ """Alternatif sayfa kaynak kodunu indirip iframe'i bulur"""
145
+ try:
146
+ res = await self.httpx.get(url)
147
+ return self._decode_iframe(res.text), title
148
+ except Exception:
149
+ return None, title
212
150
 
151
+ async def _extract_stream(self, iframe_url: str, title: str, subtitles: list[Subtitle]) -> list[ExtractResult]:
152
+ """Iframe üzerinden stream linklerini ayıklar"""
213
153
  results = []
214
154
 
215
- for iframe in iframes:
216
- subtitles = []
217
-
218
- # VidMoly özel işleme
219
- if "vidmoly" in iframe:
220
- headers = {
221
- "User-Agent" : "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36",
222
- "Sec-Fetch-Dest" : "iframe"
223
- }
224
- iframe_istek = await self.httpx.get(iframe, headers=headers)
225
- m3u_match = re.search(r'file:"([^"]+)"', iframe_istek.text)
226
-
227
- if m3u_match:
155
+ # 1. VidMoly Özel Çözümleme(M3U)
156
+ if "vidmoly" in iframe_url:
157
+ with contextlib.suppress(Exception):
158
+ res = await self.httpx.get(
159
+ url = iframe_url,
160
+ headers = {
161
+ "User-Agent" : "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36",
162
+ "Sec-Fetch-Dest" : "iframe"
163
+ }
164
+ )
165
+ m3u = HTMLHelper(res.text).regex_first(r'file:"([^"]+)"')
166
+
167
+ if m3u:
228
168
  results.append(ExtractResult(
229
- name = "VidMoly",
230
- url = m3u_match[1],
169
+ name = title or "VidMoly",
170
+ url = m3u,
231
171
  referer = self.main_url,
232
- subtitles = []
172
+ subtitles = subtitles
233
173
  ))
234
- continue
235
174
 
236
- # Altyazı çıkar
237
- subtitle_url = self._extract_subtitle_url(url)
238
- if subtitle_url:
239
- subtitles.append(Subtitle(name="Türkçe", url=subtitle_url))
175
+ return results
176
+
177
+ # 2. Genel Extractor Kullanımı
178
+ with contextlib.suppress(Exception):
179
+ extracted = await self.extract(iframe_url)
180
+ if not extracted:
181
+ return []
182
+
183
+ items = extracted if isinstance(extracted, list) else [extracted]
184
+ for item in items:
185
+ # İsim ve altyazı bilgilerini güncelle
186
+ # Orijinal extractor ismini ezmek için title kullan
187
+ if title:
188
+ item.name = title
240
189
 
241
- data = await self.extract(iframe)
242
- if data:
243
- # ExtractResult objesi immutable, yeni bir kopya oluştur
244
- updated_data = data.model_copy(update={"subtitles": subtitles}) if subtitles else data
245
- results.append(updated_data)
190
+ # Varsa altyazıları ekle
191
+ if subtitles:
192
+ # Copy update daha güvenli (Pydantic model)
193
+ if hasattr(item, "model_copy"):
194
+ item = item.model_copy(update={"subtitles": subtitles})
195
+ else:
196
+ item.subtitles = subtitles
197
+
198
+ results.append(item)
246
199
 
247
200
  return results
201
+
202
+ async def load_links(self, url: str) -> list[ExtractResult]:
203
+ response = await self.httpx.get(url)
204
+ source = response.text
205
+ helper = HTMLHelper(source)
206
+
207
+ # Altyazı Bul
208
+ sub_url = helper.regex_first(r"(https?://[^\s\"]+\.srt)")
209
+ subtitles = [Subtitle(name="Türkçe", url=sub_url)] if sub_url else []
210
+
211
+ # İşlenecek kaynakları topla: (Iframe_URL, Başlık)
212
+ sources = []
213
+
214
+ # A) Ana Player
215
+ main_iframe = self._decode_iframe(source)
216
+ if main_iframe:
217
+ p_name = helper.select_text("div.parts-middle div.part.active div.part-name") or None
218
+ p_lang = helper.select_attr("div.parts-middle div.part.active div.part-lang span", "title")
219
+ full_title = f"{p_name} | {p_lang}" if p_lang else p_name
220
+ sources.append((main_iframe, full_title))
221
+
222
+ # B) Alternatif Playerlar (Link Çözümleme Gerektirir)
223
+ alt_tasks = []
224
+ for link in helper.select("div.parts-middle a.post-page-numbers"):
225
+ href = link.attrs.get("href")
226
+ if not href:
227
+ continue
228
+
229
+ a_name = helper.select_text("div.part-name", link) or "Alternatif"
230
+ a_lang = helper.select_attr("div.part-lang span", "title", link)
231
+ full_title = f"{a_name} | {a_lang}" if a_lang else a_name
232
+
233
+ alt_tasks.append(self._resolve_alt_page(self.fix_url(href), full_title))
234
+
235
+ if alt_tasks:
236
+ resolved_alts = await asyncio.gather(*alt_tasks)
237
+ for iframe, title in resolved_alts:
238
+ if iframe:
239
+ sources.append((iframe, title))
240
+
241
+ # 3. Tüm kaynakları paralel işle (Extract)
242
+ if not sources:
243
+ return []
244
+
245
+ extract_tasks = [
246
+ self._extract_stream(iframe, title, subtitles)
247
+ for iframe, title in sources
248
+ ]
249
+
250
+ results_groups = await asyncio.gather(*extract_tasks)
251
+
252
+ # Sonuçları düzleştir
253
+ final_results = []
254
+ for group in results_groups:
255
+ final_results.extend(group)
256
+
257
+ return final_results