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.
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,9 +1,8 @@
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
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult, HTMLHelper
5
4
  from Kekik.Sifreleme import StringCodec
6
- import json, re
5
+ import json
7
6
 
8
7
  class FullHDFilmizlesene(PluginBase):
9
8
  name = "FullHDFilmizlesene"
@@ -42,17 +41,13 @@ class FullHDFilmizlesene(PluginBase):
42
41
 
43
42
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
44
43
  istek = self.cloudscraper.get(f"{url}{page}")
45
- secici = HTMLParser(istek.text)
44
+ secici = HTMLHelper(istek.text)
46
45
 
47
46
  results = []
48
- for veri in secici.css("li.film"):
49
- title_el = veri.css_first("span.film-title")
50
- link_el = veri.css_first("a")
51
- img_el = veri.css_first("img")
52
-
53
- title = title_el.text(strip=True) if title_el else None
54
- href = link_el.attrs.get("href") if link_el else None
55
- poster = img_el.attrs.get("data-src") if img_el else None
47
+ for veri in secici.select("li.film"):
48
+ title = secici.select_text("span.film-title", veri)
49
+ href = secici.select_attr("a", "href", veri)
50
+ poster = secici.select_attr("img", "data-src", veri)
56
51
 
57
52
  if title and href:
58
53
  results.append(MainPageResult(
@@ -66,17 +61,13 @@ class FullHDFilmizlesene(PluginBase):
66
61
 
67
62
  async def search(self, query: str) -> list[SearchResult]:
68
63
  istek = await self.httpx.get(f"{self.main_url}/arama/{query}")
69
- secici = HTMLParser(istek.text)
64
+ secici = HTMLHelper(istek.text)
70
65
 
71
66
  results = []
72
- for film in secici.css("li.film"):
73
- title_el = film.css_first("span.film-title")
74
- link_el = film.css_first("a")
75
- img_el = film.css_first("img")
76
-
77
- title = title_el.text(strip=True) if title_el else None
78
- href = link_el.attrs.get("href") if link_el else None
79
- poster = img_el.attrs.get("data-src") if img_el else None
67
+ for film in secici.select("li.film"):
68
+ title = secici.select_text("span.film-title", film)
69
+ href = secici.select_attr("a", "href", film)
70
+ poster = secici.select_attr("img", "data-src", film)
80
71
 
81
72
  if title and href:
82
73
  results.append(SearchResult(
@@ -89,51 +80,35 @@ class FullHDFilmizlesene(PluginBase):
89
80
 
90
81
  async def load_item(self, url: str) -> MovieInfo:
91
82
  istek = await self.httpx.get(url)
92
- secici = HTMLParser(istek.text)
83
+ secici = HTMLHelper(istek.text)
93
84
  html_text = istek.text
94
85
 
95
86
  # Title: normalize-space yerine doğrudan class ile
96
- title_el = secici.css_first("div.izle-titles")
97
- title = title_el.text(strip=True) if title_el else ""
87
+ title = secici.select_text("div.izle-titles") or ""
98
88
 
99
- img_el = secici.css_first("div img[data-src]")
100
- poster = img_el.attrs.get("data-src", "").strip() if img_el else ""
89
+ poster = secici.select_attr("div img[data-src]", "data-src") or ""
90
+ poster = poster.strip()
101
91
 
102
- desc_el = secici.css_first("div.ozet-ic p")
103
- description = desc_el.text(strip=True) if desc_el else ""
92
+ description = secici.select_text("div.ozet-ic p") or ""
104
93
 
105
- tags = [a.text(strip=True) for a in secici.css("a[rel='category tag']") if a.text(strip=True)]
94
+ tags = secici.select_all_text("a[rel='category tag']")
106
95
 
107
- # Rating: normalize-space yerine doğrudan class ile ve son kelimeyi al
108
- rating_el = secici.css_first("div.puanx-puan")
109
- rating = None
110
- if rating_el:
111
- rating_text = rating_el.text(strip=True)
112
- if rating_text:
113
- parts = rating_text.split()
114
- rating = parts[-1] if parts else None
96
+ # Rating: regex ile sayısal değeri yakala
97
+ rating_text = secici.select_text("div.puanx-puan") or ""
98
+ rating = secici.regex_first(r"(\d+\.\d+|\d+)", rating_text)
115
99
 
116
100
  # Year: ilk yıl formatında değer
117
- year_el = secici.css_first("div.dd a.category")
118
- year = None
119
- if year_el:
120
- year_text = year_el.text(strip=True)
121
- if year_text:
122
- parts = year_text.split()
123
- year = parts[0] if parts else None
101
+ year_text = secici.select_text("div.dd a.category") or ""
102
+ year = secici.regex_first(r"(\d{4})", year_text)
124
103
 
125
104
  # Actors: nth-child yerine tüm li'leri alıp 2. index
126
- lis = secici.css("div.film-info ul li")
105
+ lis = secici.select("div.film-info ul li")
127
106
  actors = []
128
107
  if len(lis) >= 2:
129
- actors = [a.text(strip=True) for a in lis[1].css("a > span") if a.text(strip=True)]
108
+ actors = secici.select_all_text("a > span", lis[1])
130
109
 
131
- duration_el = secici.css_first("span.sure")
132
- duration = "0"
133
- if duration_el:
134
- duration_text = duration_el.text(strip=True)
135
- duration_parts = duration_text.split()
136
- duration = duration_parts[0] if duration_parts else "0"
110
+ # Duration: regex ile yakala (örn: 201 dk)
111
+ duration = secici.regex_first(r"(\d+)\s*(?:dk|dakika)", html_text)
137
112
 
138
113
  return MovieInfo(
139
114
  url = url,
@@ -149,18 +124,18 @@ class FullHDFilmizlesene(PluginBase):
149
124
 
150
125
  async def load_links(self, url: str) -> list[ExtractResult]:
151
126
  istek = await self.httpx.get(url)
152
- secici = HTMLParser(istek.text)
127
+ secici = HTMLHelper(istek.text)
153
128
  html_text = istek.text
154
129
 
155
130
  # İlk script'i al (xpath (//script)[1] yerine)
156
- scripts = secici.css("script")
131
+ scripts = secici.select("script")
157
132
  script_content = scripts[0].text() if scripts else ""
158
133
 
159
- scx_match = re.search(r"scx = (.*?);", script_content)
160
- if not scx_match:
134
+ scx_json = HTMLHelper(script_content).regex_first(r"scx = (.*?);")
135
+ if not scx_json:
161
136
  return []
162
137
 
163
- scx_data = json.loads(scx_match.group(1))
138
+ scx_data = json.loads(scx_json)
164
139
  scx_keys = list(scx_data.keys())
165
140
 
166
141
  link_list = []
@@ -1,14 +1,13 @@
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, Subtitle, ExtractResult
4
- from selectolax.parser import HTMLParser
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, Subtitle, ExtractResult, HTMLHelper
5
4
  from Kekik.Sifreleme import Packer, StreamDecoder
6
- import random, string, re
5
+ import random, string
7
6
 
8
7
  class HDFilmCehennemi(PluginBase):
9
8
  name = "HDFilmCehennemi"
10
9
  language = "tr"
11
- main_url = "https://www.hdfilmcehennemi.ws"
10
+ main_url = "https://www.hdfilmcehennemi.nl"
12
11
  favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
13
12
  description = "Türkiye'nin en hızlı hd film izleme sitesi. Tek ve gerçek hdfilmcehennemi sitesi."
14
13
 
@@ -31,16 +30,13 @@ class HDFilmCehennemi(PluginBase):
31
30
 
32
31
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
33
32
  istek = await self.httpx.get(f"{url}", follow_redirects=True)
34
- secici = HTMLParser(istek.text)
33
+ secici = HTMLHelper(istek.text)
35
34
 
36
35
  results = []
37
- for veri in secici.css("div.section-content a.poster"):
38
- title_el = veri.css_first("strong.poster-title")
39
- img_el = veri.css_first("img")
40
-
41
- title = title_el.text(strip=True) if title_el else None
42
- href = veri.attrs.get("href")
43
- poster = img_el.attrs.get("data-src") if img_el else None
36
+ for veri in secici.select("div.section-content a.poster"):
37
+ title = secici.select_text("strong.poster-title", veri)
38
+ href = veri.attrs.get("href")
39
+ poster = secici.select_attr("img", "data-src", veri)
44
40
 
45
41
  if title and href:
46
42
  results.append(MainPageResult(
@@ -64,73 +60,63 @@ class HDFilmCehennemi(PluginBase):
64
60
 
65
61
  results = []
66
62
  for veri in istek.json().get("results", []):
67
- secici = HTMLParser(veri)
68
- title_el = secici.css_first("h4.title")
69
- link_el = secici.css_first("a")
70
- img_el = secici.css_first("img")
71
-
72
- title = title_el.text(strip=True) if title_el else None
73
- href = link_el.attrs.get("href") if link_el else None
74
- poster = (img_el.attrs.get("data-src") or img_el.attrs.get("src")) if img_el else None
75
-
63
+ secici = HTMLHelper(veri)
64
+ title = secici.select_text("h4.title")
65
+ href = secici.select_attr("a", "href")
66
+ poster = secici.select_attr("img", "data-src") or secici.select_attr("img", "src")
67
+
76
68
  if title and href:
77
69
  results.append(SearchResult(
78
70
  title = title,
79
71
  url = self.fix_url(href),
80
- poster = self.fix_url(poster) if poster else None,
72
+ poster = self.fix_url(poster).replace("/thumb/", "/list/") if poster else None,
81
73
  ))
82
-
74
+
83
75
  return results
84
76
 
85
77
  async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
86
78
  istek = await self.httpx.get(url, headers = {"Referer": f"{self.main_url}/"})
87
- secici = HTMLParser(istek.text)
79
+ secici = HTMLHelper(istek.text)
88
80
 
89
- title_el = secici.css_first("h1.section-title")
90
- title = title_el.text(strip=True) if title_el else ""
81
+ title = secici.select_text("h1.section-title") or ""
91
82
 
92
- poster_el = secici.css_first("aside.post-info-poster img.lazyload")
93
- poster = poster_el.attrs.get("data-src", "").strip() if poster_el else ""
83
+ poster = secici.select_attr("aside.post-info-poster img.lazyload", "data-src") or ""
84
+ poster = poster.strip()
94
85
 
95
- desc_el = secici.css_first("article.post-info-content > p")
96
- description = desc_el.text(strip=True) if desc_el else ""
86
+ description = secici.select_text("article.post-info-content > p") or ""
97
87
 
98
- tags = [a.text(strip=True) for a in secici.css("div.post-info-genres a") if a.text(strip=True)]
88
+ tags = secici.select_all_text("div.post-info-genres a")
99
89
 
100
- rating_el = secici.css_first("div.post-info-imdb-rating span")
101
- rating = rating_el.text(strip=True) if rating_el else ""
90
+ rating = secici.select_text("div.post-info-imdb-rating span") or ""
102
91
 
103
- year_el = secici.css_first("div.post-info-year-country a")
104
- year = year_el.text(strip=True) if year_el else ""
92
+ year = secici.select_text("div.post-info-year-country a") or ""
105
93
 
106
- actors = [a.text(strip=True) for a in secici.css("div.post-info-cast a > strong") if a.text(strip=True)]
94
+ actors = secici.select_all_text("div.post-info-cast a > strong")
107
95
 
108
- duration_el = secici.css_first("div.post-info-duration")
109
- duration_str = duration_el.text(strip=True) if duration_el else "0"
96
+ duration_str = secici.select_text("div.post-info-duration") or "0"
110
97
  duration_str = duration_str.replace("dakika", "").strip()
111
98
 
112
99
  try:
113
- duration_match = re.search(r'\d+', duration_str)
114
- duration_minutes = int(duration_match.group()) if duration_match else 0
100
+ duration_val = HTMLHelper(duration_str).regex_first(r'\d+')
101
+ duration_minutes = int(duration_val) if duration_val else 0
115
102
  except Exception:
116
103
  duration_minutes = 0
117
104
 
118
105
  # Dizi mi film mi kontrol et (Kotlin referansı: div.seasons kontrolü)
119
- is_series = len(secici.css("div.seasons")) > 0
106
+ is_series = len(secici.select("div.seasons")) > 0
120
107
 
121
108
  if is_series:
122
109
  episodes = []
123
- for ep in secici.css("div.seasons-tab-content a"):
124
- ep_name_el = ep.css_first("h4")
125
- ep_name = ep_name_el.text(strip=True) if ep_name_el else None
110
+ for ep in secici.select("div.seasons-tab-content a"):
111
+ ep_name = secici.select_text("h4", ep)
126
112
  ep_href = ep.attrs.get("href")
127
113
 
128
114
  if ep_name and ep_href:
129
115
  # Regex ile sezon ve bölüm numarası çıkar
130
- ep_match = re.search(r'(\d+)\.\s*Bölüm', ep_name)
131
- sz_match = re.search(r'(\d+)\.\s*Sezon', ep_name)
132
- ep_num = int(ep_match.group(1)) if ep_match else 1
133
- sz_num = int(sz_match.group(1)) if sz_match else 1
116
+ ep_val = HTMLHelper(ep_name).regex_first(r'(\d+)\.\s*Bölüm')
117
+ sz_val = HTMLHelper(ep_name).regex_first(r'(\d+)\.\s*Sezon')
118
+ ep_num = int(ep_val) if ep_val and ep_val.isdigit() else 1
119
+ sz_num = int(sz_val) if sz_val and sz_val.isdigit() else 1
134
120
 
135
121
  episodes.append(Episode(
136
122
  season = sz_num,
@@ -208,19 +194,19 @@ class HDFilmCehennemi(PluginBase):
208
194
  def _extract_from_json_ld(self, html: str) -> str | None:
209
195
  """JSON-LD script tag'inden contentUrl'i çıkar (Kotlin versiyonundaki gibi)"""
210
196
  # Önce JSON-LD'den dene
211
- json_ld_match = re.search(r'<script[^>]+type=["\']application/ld\+json["\'][^>]*>(.*?)</script>', html, re.DOTALL)
212
- if json_ld_match:
197
+ json_ld = HTMLHelper(html).regex_first(r'(?s)<script[^>]+type=["\']application/ld\+json["\'][^>]*>(.*?)</script>')
198
+ if json_ld:
213
199
  try:
214
200
  import json
215
- data = json.loads(json_ld_match.group(1).strip())
201
+ data = json.loads(json_ld.strip())
216
202
  if content_url := data.get("contentUrl"):
217
203
  if content_url.startswith("http"):
218
204
  return content_url
219
205
  except Exception:
220
206
  # Regex ile contentUrl'i çıkarmayı dene
221
- match = re.search(r'"contentUrl"\s*:\s*"([^"]+)"', html)
222
- if match and match.group(1).startswith("http"):
223
- return match.group(1)
207
+ content_url = HTMLHelper(html).regex_first(r'"contentUrl"\s*:\s*"([^"]+)"')
208
+ if content_url and content_url.startswith("http"):
209
+ return content_url
224
210
  return None
225
211
 
226
212
  async def invoke_local_source(self, iframe: str, source: str, url: str):
@@ -239,12 +225,12 @@ class HDFilmCehennemi(PluginBase):
239
225
  # Fallback: Packed JavaScript'ten çıkar
240
226
  if not video_url:
241
227
  # eval(function...) içeren packed script bul
242
- eval_match = re.search(r'(eval\(function[\s\S]+)', istek.text)
243
- if not eval_match:
228
+ eval_script = HTMLHelper(istek.text).regex_first(r'(eval\(function[\s\S]+)')
229
+ if not eval_script:
244
230
  return await self.cehennempass(iframe.split("/")[-1])
245
231
 
246
232
  try:
247
- unpacked = Packer.unpack(eval_match.group(1))
233
+ unpacked = Packer.unpack(eval_script)
248
234
  video_url = StreamDecoder.extract_stream_url(unpacked)
249
235
  except Exception:
250
236
  return await self.cehennempass(iframe.split("/")[-1])
@@ -255,10 +241,10 @@ class HDFilmCehennemi(PluginBase):
255
241
  subtitles = []
256
242
  try:
257
243
  sub_data = istek.text.split("tracks: [")[1].split("]")[0]
258
- for sub in re.findall(r'file":"([^"]+)".*?"language":"([^"]+)"', sub_data, flags=re.DOTALL):
244
+ for file_url, lang in HTMLHelper(sub_data).regex_all(r'(?s)file":"([^\\"]+)".*?"language":"([^\\"]+)"'):
259
245
  subtitles.append(Subtitle(
260
- name = sub[1].upper(),
261
- url = self.fix_url(sub[0].replace("\\", "")),
246
+ name = lang.upper(),
247
+ url = self.fix_url(file_url.replace("\\", "")),
262
248
  ))
263
249
  except Exception:
264
250
  pass
@@ -272,13 +258,13 @@ class HDFilmCehennemi(PluginBase):
272
258
 
273
259
  async def load_links(self, url: str) -> list[ExtractResult]:
274
260
  istek = await self.httpx.get(url)
275
- secici = HTMLParser(istek.text)
261
+ secici = HTMLHelper(istek.text)
276
262
 
277
263
  results = []
278
- for alternatif in secici.css("div.alternative-links"):
264
+ for alternatif in secici.select("div.alternative-links"):
279
265
  lang_code = alternatif.attrs.get("data-lang", "").upper()
280
266
 
281
- for link in alternatif.css("button.alternative-link"):
267
+ for link in secici.select("button.alternative-link", alternatif):
282
268
  source_text = link.text(strip=True).replace('(HDrip Xbet)', '').strip()
283
269
  source = f"{source_text} {lang_code}"
284
270
  video_id = link.attrs.get("data-video")
@@ -295,8 +281,8 @@ class HDFilmCehennemi(PluginBase):
295
281
  },
296
282
  )
297
283
 
298
- match = re.search(r'data-src=\\\"([^"]+)', api_get.text)
299
- iframe = match[1].replace("\\", "") if match else None
284
+ iframe = HTMLHelper(api_get.text).regex_first(r'data-src=\\\"([^\"]+)')
285
+ iframe = iframe.replace("\\", "") if iframe else None
300
286
 
301
287
  if not iframe:
302
288
  continue
@@ -1,8 +1,6 @@
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
6
4
 
7
5
  class JetFilmizle(PluginBase):
8
6
  name = "JetFilmizle"
@@ -40,23 +38,21 @@ class JetFilmizle(PluginBase):
40
38
 
41
39
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
42
40
  istek = await self.httpx.get(f"{url}{page}", follow_redirects=True)
43
- secici = HTMLParser(istek.text)
41
+ secici = HTMLHelper(istek.text)
44
42
 
45
43
  results = []
46
- for veri in secici.css("article.movie"):
44
+ for veri in secici.select("article.movie"):
47
45
  # h2-h6 içindeki a linki
48
- title_link = None
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")
52
+ href = secici.select_attr("a", "href", veri)
53
+ poster = secici.select_poster("img", veri)
56
54
 
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
55
+ title = self.clean_title(title_text) if title_text else None
60
56
 
61
57
  if title and href:
62
58
  results.append(MainPageResult(
@@ -74,23 +70,21 @@ class JetFilmizle(PluginBase):
74
70
  data = {"s": query},
75
71
  headers = {"Referer": f"{self.main_url}/"}
76
72
  )
77
- secici = HTMLParser(istek.text)
73
+ secici = HTMLHelper(istek.text)
78
74
 
79
75
  results = []
80
- for article in secici.css("article.movie"):
76
+ for article in secici.select("article.movie"):
81
77
  # h2-h6 içindeki a linki
82
- title_link = None
78
+ title_text = None
83
79
  for h_tag in ["h2", "h3", "h4", "h5", "h6"]:
84
- title_link = article.css_first(f"{h_tag} a")
85
- if title_link:
80
+ title_text = secici.select_text(f"{h_tag} a", article)
81
+ if title_text:
86
82
  break
87
83
 
88
- link_el = article.css_first("a")
89
- img_el = article.css_first("img")
84
+ href = secici.select_attr("a", "href", article)
85
+ poster = secici.select_poster("img", article)
90
86
 
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
87
+ title = self.clean_title(title_text) if title_text else None
94
88
 
95
89
  if title and href:
96
90
  results.append(SearchResult(
@@ -103,33 +97,26 @@ class JetFilmizle(PluginBase):
103
97
 
104
98
  async def load_item(self, url: str) -> MovieInfo:
105
99
  istek = await self.httpx.get(url)
106
- secici = HTMLParser(istek.text)
107
- html_text = istek.text
100
+ secici = HTMLHelper(istek.text)
108
101
 
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
102
+ title = self.clean_title(secici.select_text("div.movie-exp-title")) if secici.select_text("div.movie-exp-title") else None
111
103
 
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
104
+ poster = secici.select_poster("section.movie-exp img")
105
+ poster = poster.strip() if poster else None
115
106
 
116
- desc_el = secici.css_first("section.movie-exp p.aciklama")
117
- description = desc_el.text(strip=True) if desc_el else None
107
+ description = secici.select_text("section.movie-exp p.aciklama")
118
108
 
119
- tags = [a.text(strip=True) for a in secici.css("section.movie-exp div.catss a") if a.text(strip=True)]
109
+ tags = secici.select_all_text("section.movie-exp div.catss a")
120
110
 
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
111
+ rating = secici.select_text("section.movie-exp div.imdb_puan span")
123
112
 
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)
113
+ year = secici.extract_year("div.yap")
131
114
 
132
- actors = [a.text(strip=True) for a in secici.css("div[itemprop='actor'] a span") if a.text(strip=True)]
115
+ actors = secici.select_all_text("div[itemprop='actor'] a span")
116
+ if not actors: # Fallback to img alt
117
+ actors = [img.attrs.get("alt") for img in secici.select("div.oyuncular div.oyuncu img") if img.attrs.get("alt")]
118
+
119
+ duration = secici.regex_first(r"(\d+)\s*dk", istek.text)
133
120
 
134
121
  return MovieInfo(
135
122
  url = url,
@@ -139,17 +126,18 @@ class JetFilmizle(PluginBase):
139
126
  tags = tags,
140
127
  rating = rating,
141
128
  year = year,
142
- actors = actors
129
+ actors = actors,
130
+ duration = int(duration) if duration else None
143
131
  )
144
132
 
145
133
  async def load_links(self, url: str) -> list[ExtractResult]:
146
134
  istek = await self.httpx.get(url)
147
- secici = HTMLParser(istek.text)
135
+ secici = HTMLHelper(istek.text)
148
136
 
149
137
  results = []
150
138
 
151
139
  # 1) Ana iframe'leri kontrol et
152
- for iframe in secici.css("iframe"):
140
+ for iframe in secici.select("iframe"):
153
141
  src = (iframe.attrs.get("src") or
154
142
  iframe.attrs.get("data-src") or
155
143
  iframe.attrs.get("data-lazy-src"))
@@ -162,9 +150,8 @@ class JetFilmizle(PluginBase):
162
150
 
163
151
  # 2) Sayfa numaralarından linkleri topla (Fragman hariç)
164
152
  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 ""
153
+ for link in secici.select("a.post-page-numbers"):
154
+ isim = secici.select_text("span", link) or ""
168
155
  if isim != "Fragman":
169
156
  href = link.attrs.get("href")
170
157
  if href:
@@ -174,9 +161,9 @@ class JetFilmizle(PluginBase):
174
161
  for page_url, isim in page_links:
175
162
  try:
176
163
  page_resp = await self.httpx.get(page_url)
177
- page_sel = HTMLParser(page_resp.text)
164
+ page_sel = HTMLHelper(page_resp.text)
178
165
 
179
- for iframe in page_sel.css("div#movie iframe"):
166
+ for iframe in page_sel.select("div#movie iframe"):
180
167
  src = (iframe.attrs.get("src") or
181
168
  iframe.attrs.get("data-src") or
182
169
  iframe.attrs.get("data-lazy-src"))