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
4
- from selectolax.parser import HTMLParser
5
- import re, base64, json, urllib.parse
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult, HTMLHelper
4
+ import base64, json, urllib.parse
6
5
 
7
6
  class SelcukFlix(PluginBase):
8
7
  name = "SelcukFlix"
@@ -34,17 +33,13 @@ class SelcukFlix(PluginBase):
34
33
  if "tum-bolumler" in url:
35
34
  try:
36
35
  resp = await self.httpx.get(url)
37
- sel = HTMLParser(resp.text)
36
+ sel = HTMLHelper(resp.text)
38
37
 
39
- for item in sel.css("div.col-span-3 a"):
40
- name_el = item.css_first("h2")
41
- ep_el = item.css_first("div.opacity-80")
42
- img_el = item.css_first("div.image img")
43
-
44
- name = name_el.text(strip=True) if name_el else None
45
- ep_info = ep_el.text(strip=True) if ep_el else None
46
- href = item.attrs.get("href")
47
- poster = img_el.attrs.get("src") if img_el else None
38
+ for item in sel.select("div.col-span-3 a"):
39
+ name = sel.select_text("h2", item)
40
+ ep_info = sel.select_text("div.opacity-80", item)
41
+ href = sel.select_attr("a", "href", item)
42
+ poster = sel.select_attr("div.image img", "src", item)
48
43
 
49
44
  if name and href:
50
45
  title = f"{name} - {ep_info}" if ep_info else name
@@ -188,91 +183,106 @@ class SelcukFlix(PluginBase):
188
183
 
189
184
  async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
190
185
  resp = await self.httpx.get(url)
191
- sel = HTMLParser(resp.text)
186
+ sel = HTMLHelper(resp.text)
187
+
188
+ next_data_text = sel.select_text("script#__NEXT_DATA__")
189
+ if not next_data_text:
190
+ return SeriesInfo(url=url, title=sel.select_text("h1") or "Bilinmeyen")
192
191
 
193
- next_data_el = sel.css_first("script#__NEXT_DATA__")
194
- if not next_data_el:
195
- return None
192
+ try:
193
+ next_data = json.loads(next_data_text)
194
+ secure_data_raw = next_data["props"]["pageProps"].get("secureData")
195
+ if not secure_data_raw:
196
+ return SeriesInfo(url=url, title=sel.select_text("h1") or "Bilinmeyen")
197
+
198
+ # Clean possible quotes from string before decoding
199
+ if isinstance(secure_data_raw, str):
200
+ secure_data_raw = secure_data_raw.strip('"')
196
201
 
197
- next_data = next_data_el.text(strip=True)
198
- if not next_data:
199
- return None
202
+ decoded_str = base64.b64decode(secure_data_raw).decode('utf-8')
203
+ content_details = json.loads(decoded_str)
204
+
205
+ # Sometimes content_details might be a string (double encoded)
206
+ if isinstance(content_details, str):
207
+ content_details = json.loads(content_details)
200
208
 
201
- data = json.loads(next_data)
202
- secure_data = data["props"]["pageProps"]["secureData"]
203
- raw_data = base64.b64decode(secure_data.replace('"', ''))
204
- try:
205
- decoded_str = raw_data.decode('utf-8')
206
- except UnicodeDecodeError:
207
- decoded_str = raw_data.decode('iso-8859-1')
208
-
209
- content_details = json.loads(decoded_str)
210
- item = content_details.get("contentItem", {})
211
-
212
- title = item.get("original_title") or item.get("originalTitle") or ""
213
- poster = self.clean_image_url(item.get("poster_url") or item.get("posterUrl"))
214
- description = item.get("description") or item.get("used_description")
215
- rating = str(item.get("imdb_point") or item.get("imdbPoint", ""))
216
- year = item.get("release_year") or item.get("releaseYear")
217
- duration = item.get("total_minutes") or item.get("totalMinutes")
218
-
219
- series_data = content_details.get("relatedData", {}).get("seriesData")
220
- if not series_data and "RelatedResults" in content_details:
221
- series_data = content_details["RelatedResults"].get("getSerieSeasonAndEpisodes", {}).get("result")
222
- if series_data and isinstance(series_data, list):
223
- pass
224
-
225
- # Dizi mi film mi kontrol et (Kotlin referansı)
226
- if series_data:
227
- episodes = []
228
- seasons_list = []
229
- if isinstance(series_data, dict):
230
- seasons_list = series_data.get("seasons", [])
231
- elif isinstance(series_data, list):
232
- seasons_list = series_data
233
-
234
- for season in seasons_list:
235
- if not isinstance(season, dict): continue
236
- s_no = season.get("season_no") or season.get("seasonNo")
237
- ep_list = season.get("episodes", [])
238
- for ep in ep_list:
239
- episodes.append(Episode(
240
- season = s_no,
241
- episode = ep.get("episode_no") or ep.get("episodeNo"),
242
- title = ep.get("ep_text") or ep.get("epText"),
243
- url = self.fix_url(ep.get("used_slug") or ep.get("usedSlug"))
244
- ))
209
+ print(f"DEBUG: type(content_details)={type(content_details)}")
210
+ item = content_details.get("contentItem", {})
211
+ print(f"DEBUG: type(item)={type(item)}")
212
+ related_results = content_details.get("RelatedResults", {})
213
+
214
+ title = item.get("original_title") or item.get("culture_title") or item.get("originalTitle") or ""
215
+ poster = self.clean_image_url(item.get("poster_url") or item.get("posterUrl") or item.get("face_url"))
216
+ description = item.get("description") or item.get("used_description")
217
+ rating = str(item.get("imdb_point") or item.get("imdbPoint") or "")
218
+ year = str(item.get("release_year") or item.get("releaseYear") or "")
219
+ duration = item.get("total_minutes") or item.get("totalMinutes")
220
+
221
+ tags = []
222
+ tags_raw = item.get("category_names") or item.get("categoryNames") or item.get("categories")
223
+ if isinstance(tags_raw, str):
224
+ tags = [t.strip() for t in tags_raw.split(",")]
225
+ elif isinstance(tags_raw, list):
226
+ tags = [c.get("title") if isinstance(c, dict) else str(c) for c in tags_raw]
227
+
228
+ actors = []
229
+ actors_raw = item.get("actor_names") or item.get("actorNames")
230
+ if isinstance(actors_raw, str):
231
+ actors = [a.strip() for a in actors_raw.split(",")]
245
232
 
246
- return SeriesInfo(
247
- title = title,
248
- url = url,
249
- poster = poster,
250
- description = description,
251
- rating = rating,
252
- year = year,
253
- episodes = episodes
254
- )
255
- else:
256
- # Film ise MovieInfo döndür
257
- return MovieInfo(
258
- title = title,
259
- url = url,
260
- poster = poster,
261
- description = description,
262
- rating = rating,
263
- year = year,
264
- duration = duration
265
- )
233
+ # Casts from RelatedResults
234
+ casts_data = related_results.get("getSerieCastsById") or related_results.get("getMovieCastsById")
235
+ if casts_data and isinstance(casts_data, dict) and casts_data.get("result"):
236
+ actors = [cast.get("name") for cast in casts_data["result"] if cast.get("name")]
237
+
238
+ series_data = related_results.get("getSerieSeasonAndEpisodes")
239
+ if series_data and isinstance(series_data, dict) and series_data.get("result"):
240
+ episodes = []
241
+ for season in series_data["result"]:
242
+ s_no = season.get("season_no") or season.get("seasonNo") or 1
243
+ for ep in season.get("episodes", []):
244
+ ep_slug = ep.get("used_slug") or ep.get("usedSlug")
245
+ if ep_slug:
246
+ episodes.append(Episode(
247
+ season = s_no,
248
+ episode = ep.get("episode_no") or ep.get("episodeNo") or 1,
249
+ title = ep.get("ep_text") or ep.get("epText") or "",
250
+ url = self.fix_url(ep_slug)
251
+ ))
252
+
253
+ return SeriesInfo(
254
+ url = url,
255
+ poster = poster,
256
+ title = self.clean_title(title),
257
+ description = description,
258
+ tags = tags,
259
+ rating = rating,
260
+ year = year,
261
+ actors = actors,
262
+ duration = duration,
263
+ episodes = episodes
264
+ )
265
+ else:
266
+ return MovieInfo(
267
+ url = url,
268
+ poster = poster,
269
+ title = self.clean_title(title),
270
+ description = description,
271
+ tags = tags,
272
+ rating = rating,
273
+ year = year,
274
+ actors = actors,
275
+ duration = duration
276
+ )
277
+
278
+ except Exception:
279
+ return SeriesInfo(url=url, title=self.clean_title(sel.select_text("h1")) or "Bilinmeyen")
266
280
 
267
281
  async def load_links(self, url: str) -> list[ExtractResult]:
268
282
  resp = await self.httpx.get(url)
269
- sel = HTMLParser(resp.text)
283
+ sel = HTMLHelper(resp.text)
270
284
 
271
- next_data_el = sel.css_first("script#__NEXT_DATA__")
272
- if not next_data_el:
273
- return []
274
-
275
- next_data = next_data_el.text(strip=True)
285
+ next_data = sel.select_text("script#__NEXT_DATA__")
276
286
  if not next_data:
277
287
  return []
278
288
 
@@ -312,9 +322,8 @@ class SelcukFlix(PluginBase):
312
322
  source_content = res[0].get("source_content") or res[0].get("sourceContent")
313
323
 
314
324
  if source_content:
315
- iframe_sel = HTMLParser(source_content)
316
- iframe_el = iframe_sel.css_first("iframe")
317
- iframe_src = iframe_el.attrs.get("src") if iframe_el else None
325
+ iframe_sel = HTMLHelper(source_content)
326
+ iframe_src = iframe_sel.select_attr("iframe", "src")
318
327
  if iframe_src:
319
328
  iframe_src = self.fix_url(iframe_src)
320
329
  # Hotlinger domain değişimi (Kotlin referansı)
@@ -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
4
- from selectolax.parser import HTMLParser
5
- import re, json, asyncio
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult, HTMLHelper
4
+ import json, asyncio
6
5
 
7
6
  class SetFilmIzle(PluginBase):
8
7
  name = "SetFilmIzle"
@@ -34,8 +33,8 @@ class SetFilmIzle(PluginBase):
34
33
  f"{main_url}/tur/western/" : "Western"
35
34
  }
36
35
 
37
- def _get_nonce(self, nonce_type: str = "video_nonce", referer: str = None) -> str:
38
- """Site cache'lenmiş nonce'ları expire olabiliyor, fresh nonce al"""
36
+ def _get_nonce(self, nonce_type: str = "video", referer: str = None) -> str:
37
+ """Site cache'lenmiş nonce'ları expire olabiliyor, fresh nonce al veya sayfadan çek"""
39
38
  try:
40
39
  resp = self.cloudscraper.post(
41
40
  f"{self.main_url}/wp-admin/admin-ajax.php",
@@ -46,24 +45,31 @@ class SetFilmIzle(PluginBase):
46
45
  },
47
46
  data = "action=st_cache_refresh_nonces"
48
47
  )
49
- nonces = resp.json().get("data", {}).get("nonces", {})
50
- return nonces.get(nonce_type, "")
48
+ data = resp.json()
49
+ if data and data.get("success"):
50
+ nonces = data.get("data", {}).get("nonces", {})
51
+ return nonces.get(nonce_type if nonce_type != "search" else "dt_ajax_search", "")
52
+ except:
53
+ pass
54
+
55
+ # AJAX başarısızsa sayfadan çekmeyi dene
56
+ try:
57
+ main_resp = self.cloudscraper.get(referer or self.main_url)
58
+ # STMOVIE_AJAX = { ... nonces: { search: "...", ... } }
59
+ nonce = HTMLHelper(main_resp.text).regex_first(rf'"{nonce_type}":\s*"([^"]+)"')
60
+ return nonce or ""
51
61
  except:
52
62
  return ""
53
63
 
54
64
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
55
65
  istek = self.cloudscraper.get(url)
56
- secici = HTMLParser(istek.text)
66
+ secici = HTMLHelper(istek.text)
57
67
 
58
68
  results = []
59
- for item in secici.css("div.items article"):
60
- title_el = item.css_first("h2")
61
- link_el = item.css_first("a")
62
- img_el = item.css_first("img")
63
-
64
- title = title_el.text(strip=True) if title_el else None
65
- href = link_el.attrs.get("href") if link_el else None
66
- poster = img_el.attrs.get("data-src") if img_el else None
69
+ for item in secici.select("div.items article"):
70
+ title = secici.select_text("h2", item)
71
+ href = secici.select_attr("a", "href", item)
72
+ poster = secici.select_attr("img", "data-src", item)
67
73
 
68
74
  if title and href:
69
75
  results.append(MainPageResult(
@@ -99,17 +105,13 @@ class SetFilmIzle(PluginBase):
99
105
  except:
100
106
  return []
101
107
 
102
- secici = HTMLParser(html)
108
+ secici = HTMLHelper(html)
103
109
  results = []
104
110
 
105
- for item in secici.css("div.items article"):
106
- title_el = item.css_first("h2")
107
- link_el = item.css_first("a")
108
- img_el = item.css_first("img")
109
-
110
- title = title_el.text(strip=True) if title_el else None
111
- href = link_el.attrs.get("href") if link_el else None
112
- poster = img_el.attrs.get("data-src") if img_el else None
111
+ for item in secici.select("div.items article"):
112
+ title = secici.select_text("h2", item)
113
+ href = secici.select_attr("a", "href", item)
114
+ poster = secici.select_attr("img", "data-src", item)
113
115
 
114
116
  if title and href:
115
117
  results.append(SearchResult(
@@ -121,79 +123,66 @@ class SetFilmIzle(PluginBase):
121
123
  return results
122
124
 
123
125
  async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
124
- istek = await self.httpx.get(url)
125
- secici = HTMLParser(istek.text)
126
+ istek = self.cloudscraper.get(url)
127
+ secici = HTMLHelper(istek.text)
126
128
  html_text = istek.text
127
129
 
128
- title_el = secici.css_first("h1") or secici.css_first(".titles h1")
129
- raw_title = title_el.text(strip=True) if title_el else ""
130
- if not raw_title:
131
- # Alternatif title yeri
132
- title_meta = secici.css_first("meta[property='og:title']")
133
- raw_title = title_meta.attrs.get("content", "") if title_meta else ""
134
-
135
- title = re.sub(r"\s*izle.*$", "", raw_title, flags=re.IGNORECASE).strip()
130
+ raw_title = secici.select_text("h1") or secici.select_text(".titles h1") or secici.select_attr("meta[property='og:title']", "content") or ""
131
+ title = HTMLHelper(raw_title).regex_replace(r"(?i)\s*izle.*$", "", flags=0).strip()
136
132
 
137
- poster_el = secici.css_first("div.poster img")
138
- poster = poster_el.attrs.get("src") if poster_el else None
133
+ poster = secici.select_attr("div.poster img", "src")
139
134
 
140
- desc_el = secici.css_first("div.wp-content p")
141
- description = desc_el.text(strip=True) if desc_el else None
135
+ description = secici.select_text("div.wp-content p")
142
136
 
143
- year_el = secici.css_first("div.extra span.C a")
144
- year = None
145
- if year_el:
146
- year_text = year_el.text(strip=True)
147
- year_match = re.search(r"\d{4}", year_text)
148
- year = year_match.group() if year_match else None
137
+ rating = secici.select_text("b#repimdb strong") or secici.regex_first(r'repimdb"><strong>\s*([^<]+)</strong>', html_text)
138
+
139
+ # Yıl için info bölümünden veya regex ile yakala
140
+ year = secici.regex_first(r'(\d{4})', secici.select_text("div.extra span.valor") or secici.select_text("span.valor") or "")
141
+ if not year:
142
+ year = secici.regex_first(r'<span>(\d{4})</span>', html_text) or secici.regex_first(r'(\d{4})', html_text)
149
143
 
150
- tags = [a.text(strip=True) for a in secici.css("div.sgeneros a") if a.text(strip=True)]
144
+ tags = [a.text(strip=True) for a in secici.select("div.sgeneros a") if a.text(strip=True)]
151
145
 
152
- duration_el = secici.css_first("span.runtime")
153
- duration = None
154
- if duration_el:
155
- duration_text = duration_el.text(strip=True)
156
- dur_match = re.search(r"\d+", duration_text)
157
- duration = int(dur_match.group()) if dur_match else None
146
+ duration_text = secici.select_text("span.runtime")
147
+ duration = int(secici.regex_first(r"\d+", duration_text)) if duration_text and secici.regex_first(r"\d+", duration_text) else None
158
148
 
159
- actors = [span.text(strip=True) for span in secici.css("span.valor a > span") if span.text(strip=True)]
149
+ actors = [a.text(strip=True) for a in secici.select("span.valor a") if "/oyuncu/" in (a.attrs.get("href") or "")]
150
+ if not actors:
151
+ actors = secici.regex_all(r'href="[^"]*/oyuncu/[^"]*">([^<]+)</a>')
160
152
 
161
- trailer_match = re.search(r'embed/([^?]*)\?rel', html_text)
162
- trailer = f"https://www.youtube.com/embed/{trailer_match.group(1)}" if trailer_match else None
153
+ trailer = None
154
+ if trailer_id := secici.regex_first(r'embed/([^?]*)\?rel', html_text):
155
+ trailer = f"https://www.youtube.com/embed/{trailer_id}"
163
156
 
164
157
  # Dizi mi film mi kontrol et
165
158
  is_series = "/dizi/" in url
166
159
 
167
160
  if is_series:
168
- year_link_el = secici.css_first("a[href*='/yil/']")
169
- if year_link_el:
170
- year_elem = year_link_el.text(strip=True)
171
- year_match = re.search(r"\d{4}", year_elem)
172
- year = year_match.group() if year_match else year
161
+ year_elem = secici.select_text("a[href*='/yil/']")
162
+ if year_elem:
163
+ year = secici.regex_first(r"\d{4}", year_elem) or year
173
164
 
174
165
  # Duration from info section
175
- for span in secici.css("div#info span"):
166
+ for span in secici.select("div#info span"):
176
167
  span_text = span.text(strip=True) if span.text() else ""
177
168
  if "Dakika" in span_text:
178
- dur_match = re.search(r"\d+", span_text)
179
- duration = int(dur_match.group()) if dur_match else duration
169
+ duration = secici.regex_first(r"\d+", span_text) and int(secici.regex_first(r"\d+", span_text))
180
170
  break
181
171
 
182
172
  episodes = []
183
- for ep_item in secici.css("div#episodes ul.episodios li"):
184
- ep_title_el = ep_item.css_first("h4.episodiotitle a")
185
- ep_href = ep_title_el.attrs.get("href") if ep_title_el else None
186
- ep_name = ep_title_el.text(strip=True) if ep_title_el else None
173
+ for ep_item in secici.select("div#episodes ul.episodios li"):
174
+ ep_href = secici.select_attr("h4.episodiotitle a", "href", ep_item)
175
+ ep_name = secici.select_text("h4.episodiotitle a", ep_item)
187
176
 
188
177
  if not ep_href or not ep_name:
189
178
  continue
190
179
 
191
180
  ep_detail = ep_name
192
- season_match = re.search(r"(\d+)\.\s*Sezon", ep_detail)
193
- episode_match = re.search(r"Sezon\s+(\d+)\.\s*Bölüm", ep_detail)
181
+ ep_season = secici.regex_first(r"(\d+)\.\s*Sezon", ep_detail) or 1
182
+ ep_episode = secici.regex_first(r"Sezon\s+(\d+)\.\s*Bölüm", ep_detail)
194
183
 
195
- ep_season = int(season_match.group(1)) if season_match else 1
196
- ep_episode = int(episode_match.group(1)) if episode_match else None
184
+ ep_season = int(ep_season) if isinstance(ep_season, str) and ep_season.isdigit() else 1
185
+ ep_episode = int(ep_episode) if isinstance(ep_episode, str) and ep_episode.isdigit() else None
197
186
 
198
187
  episodes.append(Episode(
199
188
  season = ep_season,
@@ -201,13 +190,13 @@ class SetFilmIzle(PluginBase):
201
190
  title = ep_name,
202
191
  url = self.fix_url(ep_href)
203
192
  ))
204
-
205
193
  return SeriesInfo(
206
194
  url = url,
207
195
  poster = self.fix_url(poster) if poster else None,
208
196
  title = title,
209
197
  description = description,
210
198
  tags = tags,
199
+ rating = rating,
211
200
  year = year,
212
201
  duration = duration,
213
202
  actors = actors,
@@ -220,6 +209,7 @@ class SetFilmIzle(PluginBase):
220
209
  title = title,
221
210
  description = description,
222
211
  tags = tags,
212
+ rating = rating,
223
213
  year = year,
224
214
  duration = duration,
225
215
  actors = actors
@@ -227,9 +217,9 @@ class SetFilmIzle(PluginBase):
227
217
 
228
218
  async def load_links(self, url: str) -> list[ExtractResult]:
229
219
  istek = await self.httpx.get(url)
230
- secici = HTMLParser(istek.text)
220
+ secici = HTMLHelper(istek.text)
231
221
 
232
- nonce = secici.css_first("div#playex").attrs.get("data-nonce") if secici.css_first("div#playex") else ""
222
+ nonce = secici.select_attr("div#playex", "data-nonce") or ""
233
223
 
234
224
  # partKey to dil label mapping
235
225
  part_key_labels = {
@@ -279,7 +269,7 @@ class SetFilmIzle(PluginBase):
279
269
 
280
270
  return await self.extract(iframe_url, prefix=label if label else None)
281
271
 
282
- for player in secici.css("nav.player a"):
272
+ for player in secici.select("nav.player a"):
283
273
  tasks.append(fetch_and_extract(player))
284
274
 
285
275
  results = await asyncio.gather(*tasks)