KekikStream 2.2.9__py3-none-any.whl → 2.3.9__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 (62) hide show
  1. KekikStream/Core/HTMLHelper.py +134 -0
  2. KekikStream/Core/Plugin/PluginBase.py +22 -4
  3. KekikStream/Core/Plugin/PluginLoader.py +3 -2
  4. KekikStream/Core/Plugin/PluginManager.py +2 -2
  5. KekikStream/Core/__init__.py +2 -0
  6. KekikStream/Extractors/CloseLoad.py +12 -13
  7. KekikStream/Extractors/ContentX.py +33 -31
  8. KekikStream/Extractors/DonilasPlay.py +10 -10
  9. KekikStream/Extractors/DzenRu.py +3 -3
  10. KekikStream/Extractors/ExPlay.py +10 -10
  11. KekikStream/Extractors/Filemoon.py +11 -16
  12. KekikStream/Extractors/JetTv.py +4 -4
  13. KekikStream/Extractors/MixPlayHD.py +10 -11
  14. KekikStream/Extractors/MolyStream.py +16 -9
  15. KekikStream/Extractors/Odnoklassniki.py +4 -4
  16. KekikStream/Extractors/PeaceMakerst.py +3 -3
  17. KekikStream/Extractors/PixelDrain.py +6 -5
  18. KekikStream/Extractors/PlayerFilmIzle.py +6 -10
  19. KekikStream/Extractors/RapidVid.py +8 -7
  20. KekikStream/Extractors/SetPlay.py +10 -10
  21. KekikStream/Extractors/SetPrime.py +3 -6
  22. KekikStream/Extractors/SibNet.py +4 -5
  23. KekikStream/Extractors/Sobreatsesuyp.py +5 -5
  24. KekikStream/Extractors/TRsTX.py +5 -5
  25. KekikStream/Extractors/TurboImgz.py +3 -4
  26. KekikStream/Extractors/TurkeyPlayer.py +5 -5
  27. KekikStream/Extractors/VidHide.py +4 -7
  28. KekikStream/Extractors/VidMoly.py +37 -25
  29. KekikStream/Extractors/VidMoxy.py +8 -9
  30. KekikStream/Extractors/VidPapi.py +5 -7
  31. KekikStream/Extractors/VideoSeyred.py +3 -3
  32. KekikStream/Plugins/BelgeselX.py +40 -51
  33. KekikStream/Plugins/DiziBox.py +53 -81
  34. KekikStream/Plugins/DiziPal.py +50 -72
  35. KekikStream/Plugins/DiziYou.py +96 -83
  36. KekikStream/Plugins/Dizilla.py +95 -107
  37. KekikStream/Plugins/FilmBip.py +29 -50
  38. KekikStream/Plugins/FilmMakinesi.py +84 -46
  39. KekikStream/Plugins/FilmModu.py +27 -41
  40. KekikStream/Plugins/FullHDFilm.py +57 -62
  41. KekikStream/Plugins/FullHDFilmizlesene.py +32 -57
  42. KekikStream/Plugins/HDFilmCehennemi.py +51 -65
  43. KekikStream/Plugins/JetFilmizle.py +38 -51
  44. KekikStream/Plugins/KultFilmler.py +43 -67
  45. KekikStream/Plugins/RecTV.py +34 -9
  46. KekikStream/Plugins/RoketDizi.py +89 -111
  47. KekikStream/Plugins/SelcukFlix.py +102 -93
  48. KekikStream/Plugins/SetFilmIzle.py +65 -75
  49. KekikStream/Plugins/SezonlukDizi.py +47 -65
  50. KekikStream/Plugins/Sinefy.py +70 -70
  51. KekikStream/Plugins/SinemaCX.py +31 -55
  52. KekikStream/Plugins/Sinezy.py +27 -54
  53. KekikStream/Plugins/SuperFilmGeldi.py +25 -44
  54. KekikStream/Plugins/UgurFilm.py +23 -48
  55. KekikStream/Plugins/YabanciDizi.py +285 -0
  56. {kekikstream-2.2.9.dist-info → kekikstream-2.3.9.dist-info}/METADATA +1 -1
  57. kekikstream-2.3.9.dist-info/RECORD +84 -0
  58. kekikstream-2.2.9.dist-info/RECORD +0 -82
  59. {kekikstream-2.2.9.dist-info → kekikstream-2.3.9.dist-info}/WHEEL +0 -0
  60. {kekikstream-2.2.9.dist-info → kekikstream-2.3.9.dist-info}/entry_points.txt +0 -0
  61. {kekikstream-2.2.9.dist-info → kekikstream-2.3.9.dist-info}/licenses/LICENSE +0 -0
  62. {kekikstream-2.2.9.dist-info → kekikstream-2.3.9.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, 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
6
5
 
7
6
  class KultFilmler(PluginBase):
8
7
  name = "KultFilmler"
@@ -38,16 +37,13 @@ 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(
@@ -61,16 +57,13 @@ class KultFilmler(PluginBase):
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(
@@ -83,58 +76,44 @@ class KultFilmler(PluginBase):
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']")
79
+ secici = HTMLHelper(istek.text)
91
80
 
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
81
+ 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")) if secici.select_attr("[property='og:image']", "content") else None
94
83
 
95
- desc_el = secici.css_first("div.description")
96
- description = desc_el.text(strip=True) if desc_el else None
84
+ description = secici.select_text("div.description")
97
85
 
98
- tags = [a.text(strip=True) for a in secici.css("ul.post-categories a") if a.text(strip=True)]
86
+ tags = [a.text(strip=True) for a in secici.select("ul.post-categories a") if a.text(strip=True)]
99
87
 
100
88
  # 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
89
+ year = secici.select_text("li.release span a")
103
90
 
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
91
+ time_text = secici.select_text("li.time span")
92
+ duration = secici.regex_first(r"(\d+)", time_text) if time_text else None
110
93
 
111
- rating_el = secici.css_first("div.imdb-count")
112
- rating = rating_el.text(strip=True) if rating_el else None
94
+ rating_text = secici.select_text("div.imdb-count")
95
+ rating = secici.regex_first(r"(\d+\.\d+|\d+)", rating_text) if rating_text else None
113
96
 
114
- actors = [a.text(strip=True) for a in secici.css("div.actors a") if a.text(strip=True)]
97
+ actors = [a.text(strip=True) for a in secici.select("div.actors a") if a.text(strip=True)]
115
98
 
116
99
  # Dizi mi kontrol et
117
100
  if "/dizi/" in url:
118
101
  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
102
+ for bolum in secici.select("div.episode-box"):
103
+ ep_href = secici.select_attr("div.name a", "href", bolum)
122
104
 
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 ""
105
+ ssn_detail = secici.select_text("span.episodetitle", bolum) or ""
106
+ ep_detail = secici.select_text("span.episodetitle b", bolum) or ""
128
107
 
129
108
  ep_name = f"{ssn_detail} - {ep_detail}"
130
109
 
131
110
  if ep_href:
132
- ep_season = re.search(r"(\d+)\.", ssn_detail)
133
- ep_episode = re.search(r"(\d+)\.", ep_detail)
111
+ ep_season = secici.regex_first(r"(\d+)\.", ssn_detail)
112
+ ep_episode = secici.regex_first(r"(\d+)\.", ep_detail)
134
113
 
135
114
  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,
115
+ season = int(ep_season) if ep_season else 1,
116
+ episode = int(ep_episode) if ep_episode else 1,
138
117
  title = ep_name.strip(" -"),
139
118
  url = self.fix_url(ep_href),
140
119
  ))
@@ -165,12 +144,10 @@ class KultFilmler(PluginBase):
165
144
 
166
145
  def _get_iframe(self, source_code: str) -> str:
167
146
  """Base64 kodlu iframe'i çözümle"""
168
- atob_match = re.search(r"PHA\+[0-9a-zA-Z+/=]*", source_code)
169
- if not atob_match:
147
+ atob = HTMLHelper(source_code).regex_first(r"PHA\+[0-9a-zA-Z+/=]*")
148
+ if not atob:
170
149
  return ""
171
150
 
172
- atob = atob_match.group()
173
-
174
151
  # Padding düzelt
175
152
  padding = 4 - len(atob) % 4
176
153
  if padding < 4:
@@ -178,20 +155,19 @@ class KultFilmler(PluginBase):
178
155
 
179
156
  try:
180
157
  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 ""
158
+ secici = HTMLHelper(decoded)
159
+ iframe_src = secici.select_attr("iframe", "src")
160
+ return self.fix_url(iframe_src) if iframe_src else ""
184
161
  except Exception:
185
162
  return ""
186
163
 
187
164
  def _extract_subtitle_url(self, source_code: str) -> str | None:
188
165
  """Altyazı URL'sini çıkar"""
189
- match = re.search(r"(https?://[^\s\"]+\.srt)", source_code)
190
- return match[1] if match else None
166
+ return HTMLHelper(source_code).regex_first(r"(https?://[^\s\"]+\.srt)")
191
167
 
192
168
  async def load_links(self, url: str) -> list[ExtractResult]:
193
169
  istek = await self.httpx.get(url)
194
- secici = HTMLParser(istek.text)
170
+ secici = HTMLHelper(istek.text)
195
171
 
196
172
  iframes = set()
197
173
 
@@ -201,9 +177,9 @@ class KultFilmler(PluginBase):
201
177
  iframes.add(main_frame)
202
178
 
203
179
  # 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
180
+ for player in secici.select("div.container#player"):
181
+ iframe_src = secici.select_attr("iframe", "src", player)
182
+ alt_iframe = self.fix_url(iframe_src) if iframe_src else None
207
183
  if alt_iframe:
208
184
  alt_istek = await self.httpx.get(alt_iframe)
209
185
  alt_frame = self._get_iframe(alt_istek.text)
@@ -222,12 +198,12 @@ class KultFilmler(PluginBase):
222
198
  "Sec-Fetch-Dest" : "iframe"
223
199
  }
224
200
  iframe_istek = await self.httpx.get(iframe, headers=headers)
225
- m3u_match = re.search(r'file:"([^"]+)"', iframe_istek.text)
201
+ m3u_match = HTMLHelper(iframe_istek.text).regex_first(r'file:"([^"]+)"')
226
202
 
227
203
  if m3u_match:
228
204
  results.append(ExtractResult(
229
205
  name = "VidMoly",
230
- url = m3u_match[1],
206
+ url = m3u_match,
231
207
  referer = self.main_url,
232
208
  subtitles = []
233
209
  ))
@@ -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, Episode, SeriesInfo, ExtractResult
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, Episode, SeriesInfo, ExtractResult, HTMLHelper
4
4
  from json import dumps, loads
5
- import re
6
5
 
7
6
  class RecTV(PluginBase):
8
7
  name = "RecTV"
@@ -84,36 +83,62 @@ class RecTV(PluginBase):
84
83
  "is_episode" : True
85
84
  }
86
85
 
86
+ # Extract season/episode numbers using helper
87
+ s1, _ = HTMLHelper.extract_season_episode(season.get("title") or "")
88
+ _, e2 = HTMLHelper.extract_season_episode(episode.get("title") or "")
89
+
87
90
  ep_model = Episode(
88
- season = int(re.search(r"(\d+)\.S", season.get("title")).group(1)) if re.search(r"(\d+)\.S", season.get("title")) else 1,
89
- episode = int(re.search(r"Bölüm (\d+)", episode.get("title")).group(1)) if re.search(r"Bölüm (\d+)", episode.get("title")) else 1,
91
+ season = s1 or 1,
92
+ episode = e2 or 1,
90
93
  title = episode.get("title"),
91
94
  url = dumps(ep_data),
92
95
  )
93
96
 
94
97
  episodes.append(ep_model)
95
98
 
99
+ # Süreyi dakikaya çevir (Örn: "1h 59min")
100
+ duration_raw = veri.get("duration")
101
+ duration = None
102
+ if duration_raw:
103
+ try:
104
+ h = int(HTMLHelper(duration_raw).regex_first(r"(\d+)h") or 0)
105
+ m = int(HTMLHelper(duration_raw).regex_first(r"(\d+)min") or 0)
106
+ duration = h * 60 + m
107
+ except: pass
108
+
96
109
  return SeriesInfo(
97
110
  url = url,
98
111
  poster = self.fix_url(veri.get("image")),
99
112
  title = veri.get("title"),
100
113
  description = veri.get("description"),
101
114
  tags = [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
102
- rating = veri.get("imdb") or veri.get("rating"),
103
- year = veri.get("year"),
115
+ rating = str(veri.get("imdb") or veri.get("rating") or ""),
116
+ year = str(veri.get("year") or ""),
104
117
  actors = [],
118
+ duration = duration,
105
119
  episodes = episodes
106
120
  )
107
121
  case _:
122
+ # Süreyi dakikaya çevir
123
+ duration_raw = veri.get("duration")
124
+ duration = None
125
+ if duration_raw:
126
+ try:
127
+ h = int(HTMLHelper(duration_raw).regex_first(r"(\d+)h") or 0)
128
+ m = int(HTMLHelper(duration_raw).regex_first(r"(\d+)min") or 0)
129
+ duration = h * 60 + m
130
+ except: pass
131
+
108
132
  return MovieInfo(
109
133
  url = url,
110
134
  poster = self.fix_url(veri.get("image")),
111
135
  title = veri.get("title"),
112
136
  description = veri.get("description"),
113
137
  tags = [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
114
- rating = veri.get("imdb") or veri.get("rating"),
115
- year = veri.get("year"),
116
- actors = []
138
+ rating = str(veri.get("imdb") or veri.get("rating") or ""),
139
+ year = str(veri.get("year") or ""),
140
+ actors = [],
141
+ duration = duration
117
142
  )
118
143
 
119
144
  async def load_links(self, url: str) -> list[ExtractResult]:
@@ -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, SeriesInfo, Episode, ExtractResult, MovieInfo
4
- from selectolax.parser import HTMLParser
5
- import re, base64, json
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult, MovieInfo, HTMLHelper
4
+ import base64, json
6
5
 
7
6
  class RoketDizi(PluginBase):
8
7
  name = "RoketDizi"
@@ -24,19 +23,15 @@ class RoketDizi(PluginBase):
24
23
 
25
24
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
26
25
  istek = await self.httpx.get(f"{url}?&page={page}")
27
- secici = HTMLParser(istek.text)
26
+ secici = HTMLHelper(istek.text)
28
27
 
29
28
  results = []
30
29
 
31
30
  # Use div.new-added-list to find the container, then get items
32
- for item in secici.css("div.new-added-list > span"):
33
- title_el = item.css_first("span.line-clamp-1")
34
- link_el = item.css_first("a")
35
- img_el = item.css_first("img")
36
-
37
- title = title_el.text(strip=True) if title_el else None
38
- href = link_el.attrs.get("href") if link_el else None
39
- poster = img_el.attrs.get("src") if img_el else None
31
+ for item in secici.select("div.new-added-list > span"):
32
+ title = secici.select_text("span.line-clamp-1", item)
33
+ href = secici.select_attr("a", "href", item)
34
+ poster = secici.select_attr("img", "src", item)
40
35
 
41
36
  if title and href:
42
37
  results.append(MainPageResult(
@@ -89,108 +84,93 @@ class RoketDizi(PluginBase):
89
84
  except Exception:
90
85
  return []
91
86
 
92
- async def load_item(self, url: str) -> SeriesInfo:
93
- # Note: Handling both Movie and Series logic in one, returning SeriesInfo generally or MovieInfo
87
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
94
88
  resp = await self.httpx.get(url)
95
- sel = HTMLParser(resp.text)
96
- html_text = resp.text
97
-
98
- title_el = sel.css_first("h1.text-white")
99
- title = title_el.text(strip=True) if title_el else None
100
-
101
- poster_el = sel.css_first("div.w-full.page-top img")
102
- poster = poster_el.attrs.get("src") if poster_el else None
103
-
104
- desc_el = sel.css_first("div.mt-2.text-sm")
105
- description = desc_el.text(strip=True) if desc_el else None
106
-
107
- # Tags - genre bilgileri (Detaylar bölümünde)
108
- tags = []
109
- genre_el = sel.css_first("h3.text-white.opacity-90")
110
- if genre_el:
111
- genre_text = genre_el.text(strip=True)
112
- if genre_text:
113
- tags = [t.strip() for t in genre_text.split(",")]
114
-
115
- # Rating
116
- rating_el = sel.css_first("span.text-white.text-sm.font-bold")
117
- rating = rating_el.text(strip=True) if rating_el else None
118
-
119
- # Year ve Actors - Detaylar (Details) bölümünden
120
- year = None
121
- actors = []
122
-
123
- # Detaylar bölümündeki tüm flex-col div'leri al
124
- detail_items = sel.css("div.flex.flex-col")
125
- for item in detail_items:
126
- label_el = item.css_first("span.text-base")
127
- value_el = item.css_first("span.text-sm.opacity-90")
89
+ sel = HTMLHelper(resp.text)
90
+
91
+ next_data_text = sel.select_text("script#__NEXT_DATA__")
92
+ if not next_data_text:
93
+ return SeriesInfo(url=url, title=sel.select_text("h1") or "Bilinmeyen")
94
+
95
+ try:
96
+ next_data = json.loads(next_data_text)
97
+ secure_data_raw = next_data["props"]["pageProps"]["secureData"]
98
+ secure_data = json.loads(base64.b64decode(secure_data_raw).decode('utf-8'))
128
99
 
129
- label = label_el.text(strip=True) if label_el else None
130
- value = value_el.text(strip=True) if value_el else None
100
+ content_item = secure_data.get("contentItem", {})
101
+ content = secure_data.get("content", {}).get("result", {})
131
102
 
132
- if label and value:
133
- # Yayın tarihi (yıl)
134
- if label == "Yayın tarihi":
135
- # "16 Ekim 2018" formatından yılı çıkar
136
- year_match = re.search(r'\d{4}', value)
137
- if year_match:
138
- year = year_match.group()
139
-
140
- # Yaratıcılar veya Oyuncular
141
- elif label in ["Yaratıcılar", "Oyuncular"]:
142
- if value:
143
- actors.append(value)
144
-
145
- # Check urls for episodes
146
- all_urls = re.findall(r'"url":"([^"]*)"', html_text)
147
- is_series = any("bolum-" in u for u in all_urls)
148
-
149
- episodes = []
150
- if is_series:
151
- # Dict kullanarak duplicate'leri önle ama sıralı tut
152
- episodes_dict = {}
153
- for u in all_urls:
154
- if "bolum" in u and u not in episodes_dict:
155
- season_match = re.search(r'/sezon-(\d+)', u)
156
- ep_match = re.search(r'/bolum-(\d+)', u)
157
-
158
- season = int(season_match.group(1)) if season_match else 1
159
- episode_num = int(ep_match.group(1)) if ep_match else 1
160
-
161
- # Key olarak (season, episode) tuple kullan
162
- key = (season, episode_num)
163
- episodes_dict[key] = Episode(
164
- season = season,
165
- episode = episode_num,
166
- title = f"{season}. Sezon {episode_num}. Bölüm",
167
- url = self.fix_url(u)
168
- )
169
-
170
- # Sıralı liste oluştur
171
- episodes = [episodes_dict[key] for key in sorted(episodes_dict.keys())]
172
-
173
- return SeriesInfo(
174
- title = title,
175
- url = url,
176
- poster = self.fix_url(poster) if poster else None,
177
- description = description,
178
- tags = tags,
179
- rating = rating,
180
- actors = actors,
181
- episodes = episodes,
182
- year = year
183
- )
103
+ title = content_item.get("original_title") or content_item.get("culture_title")
104
+ poster = content_item.get("poster_url") or content_item.get("face_url")
105
+ description = content_item.get("description")
106
+ rating = str(content_item.get("imdb_point") or "")
107
+ year = str(content_item.get("release_year") or "")
108
+ tags = content_item.get("categories", "").split(",")
109
+
110
+ # Actors extraction from getSerieCastsById or getMovieCastsById
111
+ actors = []
112
+ casts_data = content.get("getSerieCastsById") or content.get("getMovieCastsById")
113
+ if casts_data and casts_data.get("result"):
114
+ actors = [cast.get("name") for cast in casts_data["result"] if cast.get("name")]
115
+
116
+ # Episodes extraction
117
+ episodes = []
118
+ if "Series" in str(content.get("FindedType")):
119
+ # Check for episodes in SecureData -> RelatedResults -> getEpisodeSources (this might be for the current episode)
120
+ # Usually full episode list isn't in secureData, but we can get it from HTML or another API
121
+ # However, many times Next.js pages have them in props
122
+ # Let's fallback to the previous regex method for episodes if not in JSON
123
+ all_urls = HTMLHelper(resp.text).regex_all(r'"url":"([^"]*)"')
124
+ episodes_dict = {}
125
+ for u in all_urls:
126
+ if "bolum" in u and u not in episodes_dict:
127
+ s_match = HTMLHelper(u).regex_first(r'/sezon-(\d+)')
128
+ e_match = HTMLHelper(u).regex_first(r'/bolum-(\d+)')
129
+ s_val = int(s_match) if s_match else 1
130
+ e_val = int(e_match) if e_match else 1
131
+ episodes_dict[(s_val, e_val)] = Episode(
132
+ season = s_val,
133
+ episode = e_val,
134
+ title = f"{s_val}. Sezon {e_val}. Bölüm",
135
+ url = self.fix_url(u)
136
+ )
137
+ episodes = [episodes_dict[key] for key in sorted(episodes_dict.keys())]
138
+
139
+ return SeriesInfo(
140
+ url = url,
141
+ poster = self.fix_url(poster) if poster else None,
142
+ title = self.clean_title(title),
143
+ description = description,
144
+ tags = tags,
145
+ rating = rating,
146
+ year = year,
147
+ actors = actors,
148
+ episodes = episodes
149
+ )
150
+ else:
151
+ return MovieInfo(
152
+ url = url,
153
+ poster = self.fix_url(poster) if poster else None,
154
+ title = self.clean_title(title),
155
+ description = description,
156
+ tags = tags,
157
+ rating = rating,
158
+ year = year,
159
+ actors = actors
160
+ )
161
+
162
+ except Exception:
163
+ # Fallback to simple extraction if JSON parsing fails
164
+ return SeriesInfo(
165
+ url = url,
166
+ title = self.clean_title(sel.select_text("h1")) or "Bilinmeyen"
167
+ )
184
168
 
185
169
  async def load_links(self, url: str) -> list[ExtractResult]:
186
170
  resp = await self.httpx.get(url)
187
- sel = HTMLParser(resp.text)
171
+ sel = HTMLHelper(resp.text)
188
172
 
189
- next_data_el = sel.css_first("script#__NEXT_DATA__")
190
- if not next_data_el:
191
- return []
192
-
193
- next_data = next_data_el.text(strip=True)
173
+ next_data = sel.select_text("script#__NEXT_DATA__")
194
174
  if not next_data:
195
175
  return []
196
176
 
@@ -208,11 +188,9 @@ class RoketDizi(PluginBase):
208
188
  source_content = source.get("source_content", "")
209
189
 
210
190
  # iframe URL'ini source_content'ten çıkar
211
- iframe_match = re.search(r'<iframe[^>]*src=["\']([^"\']*)["\']', source_content)
212
- if not iframe_match:
191
+ iframe_url = HTMLHelper(source_content).regex_first(r'<iframe[^>]*src=["\']([^"\']*)["\']')
192
+ if not iframe_url:
213
193
  continue
214
-
215
- iframe_url = iframe_match.group(1)
216
194
 
217
195
  # Fix URL protocol
218
196
  if not iframe_url.startswith("http"):