KekikStream 2.3.3__py3-none-any.whl → 2.3.4__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 (59) hide show
  1. KekikStream/Core/HTMLHelper.py +134 -0
  2. KekikStream/Core/Plugin/PluginBase.py +4 -1
  3. KekikStream/Core/__init__.py +2 -0
  4. KekikStream/Extractors/CloseLoad.py +12 -13
  5. KekikStream/Extractors/ContentX.py +12 -15
  6. KekikStream/Extractors/DonilasPlay.py +10 -10
  7. KekikStream/Extractors/DzenRu.py +3 -3
  8. KekikStream/Extractors/ExPlay.py +10 -10
  9. KekikStream/Extractors/Filemoon.py +11 -16
  10. KekikStream/Extractors/JetTv.py +4 -4
  11. KekikStream/Extractors/MixPlayHD.py +10 -11
  12. KekikStream/Extractors/MolyStream.py +5 -9
  13. KekikStream/Extractors/Odnoklassniki.py +4 -4
  14. KekikStream/Extractors/PeaceMakerst.py +3 -3
  15. KekikStream/Extractors/PixelDrain.py +6 -5
  16. KekikStream/Extractors/PlayerFilmIzle.py +6 -10
  17. KekikStream/Extractors/RapidVid.py +8 -7
  18. KekikStream/Extractors/SetPlay.py +10 -10
  19. KekikStream/Extractors/SetPrime.py +3 -6
  20. KekikStream/Extractors/SibNet.py +4 -5
  21. KekikStream/Extractors/Sobreatsesuyp.py +5 -5
  22. KekikStream/Extractors/TRsTX.py +5 -5
  23. KekikStream/Extractors/TurboImgz.py +3 -4
  24. KekikStream/Extractors/TurkeyPlayer.py +5 -5
  25. KekikStream/Extractors/VidHide.py +4 -7
  26. KekikStream/Extractors/VidMoly.py +24 -25
  27. KekikStream/Extractors/VidMoxy.py +8 -9
  28. KekikStream/Extractors/VidPapi.py +5 -7
  29. KekikStream/Extractors/VideoSeyred.py +3 -3
  30. KekikStream/Plugins/BelgeselX.py +40 -51
  31. KekikStream/Plugins/DiziBox.py +53 -81
  32. KekikStream/Plugins/DiziPal.py +41 -74
  33. KekikStream/Plugins/DiziYou.py +95 -88
  34. KekikStream/Plugins/Dizilla.py +51 -71
  35. KekikStream/Plugins/FilmBip.py +24 -49
  36. KekikStream/Plugins/FilmMakinesi.py +35 -52
  37. KekikStream/Plugins/FilmModu.py +27 -41
  38. KekikStream/Plugins/FullHDFilm.py +50 -72
  39. KekikStream/Plugins/FullHDFilmizlesene.py +35 -51
  40. KekikStream/Plugins/HDFilmCehennemi.py +48 -62
  41. KekikStream/Plugins/JetFilmizle.py +32 -50
  42. KekikStream/Plugins/KultFilmler.py +42 -67
  43. KekikStream/Plugins/RecTV.py +7 -4
  44. KekikStream/Plugins/RoketDizi.py +30 -50
  45. KekikStream/Plugins/SelcukFlix.py +15 -29
  46. KekikStream/Plugins/SetFilmIzle.py +41 -70
  47. KekikStream/Plugins/SezonlukDizi.py +47 -65
  48. KekikStream/Plugins/Sinefy.py +34 -50
  49. KekikStream/Plugins/SinemaCX.py +31 -55
  50. KekikStream/Plugins/Sinezy.py +27 -54
  51. KekikStream/Plugins/SuperFilmGeldi.py +25 -44
  52. KekikStream/Plugins/UgurFilm.py +23 -48
  53. {kekikstream-2.3.3.dist-info → kekikstream-2.3.4.dist-info}/METADATA +1 -1
  54. kekikstream-2.3.4.dist-info/RECORD +83 -0
  55. kekikstream-2.3.3.dist-info/RECORD +0 -82
  56. {kekikstream-2.3.3.dist-info → kekikstream-2.3.4.dist-info}/WHEEL +0 -0
  57. {kekikstream-2.3.3.dist-info → kekikstream-2.3.4.dist-info}/entry_points.txt +0 -0
  58. {kekikstream-2.3.3.dist-info → kekikstream-2.3.4.dist-info}/licenses/LICENSE +0 -0
  59. {kekikstream-2.3.3.dist-info → kekikstream-2.3.4.dist-info}/top_level.txt +0 -0
@@ -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, SeriesInfo, Episode, Subtitle, ExtractResult
4
- from selectolax.parser import HTMLParser
5
- import re
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, Subtitle, ExtractResult, HTMLHelper
6
4
 
7
5
  class DiziYou(PluginBase):
8
6
  name = "DiziYou"
@@ -31,16 +29,13 @@ class DiziYou(PluginBase):
31
29
 
32
30
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
33
31
  istek = await self.httpx.get(f"{url.replace('SAYFA', str(page))}")
34
- secici = HTMLParser(istek.text)
32
+ secici = HTMLHelper(istek.text)
35
33
 
36
34
  results = []
37
- for veri in secici.css("div.single-item"):
38
- title_el = veri.css_first("div#categorytitle a")
39
- img_el = veri.css_first("img")
40
-
41
- title = title_el.text(strip=True) if title_el else None
42
- href = title_el.attrs.get("href") if title_el else None
43
- poster = img_el.attrs.get("src") if img_el else None
35
+ for veri in secici.select("div.single-item"):
36
+ title = secici.select_text("div#categorytitle a", veri)
37
+ href = secici.select_attr("div#categorytitle a", "href", veri)
38
+ poster = secici.select_attr("img", "src", veri)
44
39
 
45
40
  if title and href:
46
41
  results.append(MainPageResult(
@@ -54,16 +49,13 @@ class DiziYou(PluginBase):
54
49
 
55
50
  async def search(self, query: str) -> list[SearchResult]:
56
51
  istek = await self.httpx.get(f"{self.main_url}/?s={query}")
57
- secici = HTMLParser(istek.text)
52
+ secici = HTMLHelper(istek.text)
58
53
 
59
54
  results = []
60
- for afis in secici.css("div.incontent div#list-series"):
61
- title_el = afis.css_first("div#categorytitle a")
62
- img_el = afis.css_first("img")
63
-
64
- title = title_el.text(strip=True) if title_el else None
65
- href = title_el.attrs.get("href") if title_el else None
66
- poster = (img_el.attrs.get("src") or img_el.attrs.get("data-src")) if img_el else None
55
+ for afis in secici.select("div.incontent div#list-series"):
56
+ title = secici.select_text("div#categorytitle a", afis)
57
+ href = secici.select_attr("div#categorytitle a", "href", afis)
58
+ poster = (secici.select_attr("img", "src", afis) or secici.select_attr("img", "data-src", afis))
67
59
 
68
60
  if title and href:
69
61
  results.append(SearchResult(
@@ -76,90 +68,92 @@ class DiziYou(PluginBase):
76
68
 
77
69
  async def load_item(self, url: str) -> SeriesInfo:
78
70
  istek = await self.httpx.get(url)
79
- secici = HTMLParser(istek.text)
71
+ secici = HTMLHelper(istek.text)
80
72
  html_text = istek.text
81
73
 
82
74
  # Title - div.title h1 içinde
83
- title_el = secici.css_first("div.title h1")
84
- title = title_el.text(strip=True) if title_el else ""
85
-
75
+ title = secici.select_text("div.title h1")
76
+
86
77
  # Fallback: Eğer title boşsa URL'den çıkar (telif kısıtlaması olan sayfalar için)
87
78
  if not title:
88
79
  # URL'den slug'ı al: https://www.diziyou.one/jasmine/ -> jasmine -> Jasmine
89
80
  slug = url.rstrip('/').split('/')[-1]
90
81
  title = slug.replace('-', ' ').title()
91
-
82
+
92
83
  # Poster
93
- poster_el = secici.css_first("div.category_image img")
94
- poster = self.fix_url(poster_el.attrs.get("src")) if poster_el else ""
84
+ poster_src = secici.select_attr("div.category_image img", "src")
85
+ poster = self.fix_url(poster_src) if poster_src else ""
95
86
 
96
87
  # Year - regex ile çıkarma (xpath yerine)
97
- year = None
98
- year_match = re.search(r"Yapım Yılı.*?(\d{4})", html_text, re.DOTALL | re.IGNORECASE)
99
- if year_match:
100
- year = year_match.group(1)
88
+ year = secici.regex_first(r"(?is)Yapım Yılı.*?(\d{4})", secici.html)
101
89
 
102
- desc_el = secici.css_first("div.diziyou_desc")
103
90
  description = None
104
- if desc_el:
105
- # HTML'i al ve script + meta div'lerini temizle
106
- desc_html = desc_el.html
91
+ # Extract inner HTML via regex and clean
92
+ desc_html = secici.regex_first(r'(?s)<div class="diziyou_desc">(.*?)</div>', secici.html)
93
+ if desc_html:
107
94
  # Script taglarını kaldır
108
- desc_html = re.sub(r"<script.*?</script>", "", desc_html, flags=re.DOTALL)
95
+ desc_html = HTMLHelper(desc_html).regex_replace(r"(?s)<script.*?</script>", "")
109
96
  # div#icerikcat2 ve sonrasını kaldır (meta bilgileri içeriyor)
110
- desc_html = re.sub(r"<div id=\"icerikcat2\".*", "", desc_html, flags=re.DOTALL)
97
+ desc_html = HTMLHelper(desc_html).regex_replace(r"(?s)<div id=\"icerikcat2\".*", "")
111
98
  # Kalan HTML'den text çıkar
112
- clean_sel = HTMLParser(desc_html)
113
- description = clean_sel.text(strip=True)
99
+ clean_sel = HTMLHelper(desc_html)
100
+ description = clean_sel.select_text()
114
101
 
115
- tags = [a.text(strip=True) for a in secici.css("div.genres a") if a.text(strip=True)]
102
+ tags = [secici.select_text(None, a) for a in secici.select("div.genres a") if secici.select_text(None, a)]
116
103
 
117
104
  # Rating - daha spesifik regex ile
118
- rating = None
119
- rating_match = re.search(r"IMDB\s*:\s*</span>([0-9.]+)", html_text, re.DOTALL | re.IGNORECASE)
120
- if rating_match:
121
- rating = rating_match.group(1)
105
+ rating = secici.regex_first(r"(?is)IMDB\s*:\s*</span>([0-9.]+)", secici.html)
122
106
 
123
107
  # Actors - regex ile
124
- actors = []
125
- actors_match = re.search(r"Oyuncular.*?</span>([^<]+)", html_text, re.DOTALL | re.IGNORECASE)
126
- if actors_match:
127
- actors = [actor.strip() for actor in actors_match.group(1).split(",") if actor.strip()]
108
+ actors_raw = secici.regex_first(r"(?is)Oyuncular.*?</span>([^<]+)", secici.html)
109
+ actors = [actor.strip() for actor in actors_raw.split(",") if actor.strip()] if actors_raw else []
128
110
 
129
111
  episodes = []
130
- # Episodes - bolumust div içeren a linklerini bul
131
- for link in secici.css("a"):
132
- bolumust = link.css_first("div.bolumust")
133
- if not bolumust:
134
- continue
135
-
136
- baslik_el = link.css_first("div.baslik")
137
- if not baslik_el:
138
- continue
139
-
140
- ep_name = baslik_el.text(strip=True)
141
- ep_href = link.attrs.get("href")
112
+ # Episodes - daha fazla DOM/URL kalıbını destekle
113
+ for link in secici.select("a"):
114
+ ep_href = secici.select_attr("a", "href", link)
142
115
  if not ep_href:
143
116
  continue
144
117
 
145
- # Bölüm ismi varsa al
146
- bolumismi_el = link.css_first("div.bolumismi")
147
- ep_name_clean = bolumismi_el.text(strip=True).replace("(", "").replace(")", "").strip() if bolumismi_el else ep_name
148
-
149
- ep_episode_match = re.search(r"(\d+)\. Bölüm", ep_name)
150
- ep_season_match = re.search(r"(\d+)\. Sezon", ep_name)
151
-
152
- ep_episode = ep_episode_match.group(1) if ep_episode_match else None
153
- ep_season = ep_season_match.group(1) if ep_season_match else None
154
-
155
- if ep_episode and ep_season:
156
- episode = Episode(
157
- season = ep_season,
158
- episode = ep_episode,
159
- title = ep_name_clean,
118
+ # Link metni veya alt başlık al
119
+ ep_name = (secici.select_text(None, link) or "").strip()
120
+ title_child = secici.select_text("div.baslik", link) or secici.select_text("div.bolumismi", link)
121
+ if title_child:
122
+ ep_name = title_child
123
+
124
+ # Önce metin üzerinden sezon/bölüm çıkart
125
+ s_val, e_val = HTMLHelper.extract_season_episode(ep_name)
126
+
127
+ # URL bazlı kalıplar: -1-sezon-2-bolum gibi
128
+ if not (s_val or e_val):
129
+ pairs = HTMLHelper(ep_href).regex_all(r"-(\d+)-sezon-(\d+)-bolum")
130
+ if pairs:
131
+ s_val, e_val = int(pairs[0][0]), int(pairs[0][1])
132
+ else:
133
+ pairs = HTMLHelper(ep_href).regex_all(r"(\d+)-sezon-(\d+)-bolum")
134
+ if pairs:
135
+ s_val, e_val = int(pairs[0][0]), int(pairs[0][1])
136
+ else:
137
+ e_val_str = HTMLHelper(ep_href).regex_first(r"(\d+)-bolum")
138
+ if e_val_str:
139
+ e_val = int(e_val_str)
140
+ # Metin üzerinden son bir deneme
141
+ if not e_val:
142
+ e_str = HTMLHelper(ep_name).regex_first(r"(\d+)\s*\.\s*[Bb]ölüm")
143
+ if e_str:
144
+ e_val = int(e_str)
145
+ if not s_val:
146
+ s_str = HTMLHelper(ep_name).regex_first(r"(\d+)\s*\.\s*[Ss]ezon")
147
+ if s_str:
148
+ s_val = int(s_str)
149
+
150
+ if e_val or HTMLHelper(ep_href).regex_first(r"-\d+-sezon-\d+-bolum"):
151
+ episodes.append(Episode(
152
+ season = s_val,
153
+ episode = e_val,
154
+ title = ep_name if ep_name else None,
160
155
  url = self.fix_url(ep_href),
161
- )
162
- episodes.append(episode)
156
+ ))
163
157
 
164
158
  return SeriesInfo(
165
159
  url = url,
@@ -175,29 +169,42 @@ class DiziYou(PluginBase):
175
169
 
176
170
  async def load_links(self, url: str) -> list[ExtractResult]:
177
171
  istek = await self.httpx.get(url)
178
- secici = HTMLParser(istek.text)
172
+ secici = HTMLHelper(istek.text)
179
173
 
180
174
  # Title ve episode name - None kontrolü ekle
181
- title_el = secici.css_first("div.title h1")
182
- item_title = title_el.text(strip=True) if title_el else ""
183
-
184
- ep_name_el = secici.css_first("div#bolum-ismi")
185
- ep_name = ep_name_el.text(strip=True) if ep_name_el else ""
175
+ item_title = secici.select_text("div.title h1")
176
+ ep_name = secici.select_text("div#bolum-ismi")
186
177
 
187
178
  # Player src'den item_id çıkar
188
- player_el = secici.css_first("iframe#diziyouPlayer")
189
- player_src = player_el.attrs.get("src") if player_el else None
179
+ # Player src'den item_id çıkar - önce özel player seçicisini dene
180
+ player_src = None
181
+ # Yaygın locatorlar
182
+ for sel in ["iframe#diziyouPlayer", "div.player iframe", "iframe[src*='/episodes/']", "iframe"]:
183
+ p = secici.select_attr(sel, "src")
184
+ if p and "youtube.com" not in p.lower():
185
+ player_src = p
186
+ break
187
+
188
+ # Eğer hâlâ bulunamadıysa, varsa bir bölüm sayfasına git ve oradan player'ı çek
189
+ if not player_src:
190
+ for a in secici.select("a"):
191
+ href = secici.select_attr("a", "href", a)
192
+ if not href:
193
+ continue
194
+ if HTMLHelper(href).regex_first(r"(-\d+-sezon-\d+-bolum|/bolum|/episode|/episodes|/play)"):
195
+ break
196
+
190
197
  if not player_src:
191
198
  return [] # Player bulunamadıysa boş liste döndür
192
-
199
+
193
200
  item_id = player_src.split("/")[-1].replace(".html", "")
194
201
 
195
202
  subtitles = []
196
203
  stream_urls = []
197
204
 
198
- for secenek in secici.css("span.diziyouOption"):
199
- opt_id = secenek.attrs.get("id")
200
- op_name = secenek.text(strip=True)
205
+ for secenek in secici.select("span.diziyouOption"):
206
+ opt_id = secici.select_attr("span.diziyouOption", "id", secenek)
207
+ op_name = secici.select_text("span.diziyouOption", secenek)
201
208
 
202
209
  match opt_id:
203
210
  case "turkceAltyazili":
@@ -1,12 +1,10 @@
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
4
- from selectolax.parser import HTMLParser
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult, HTMLHelper
5
4
  from json import loads
6
5
  from urllib.parse import urlparse, urlunparse
7
6
  from Crypto.Cipher import AES
8
7
  from base64 import b64decode
9
- import re
10
8
 
11
9
  class Dizilla(PluginBase):
12
10
  name = "Dizilla"
@@ -52,45 +50,26 @@ class Dizilla(PluginBase):
52
50
  ])
53
51
  else:
54
52
  istek = await self.httpx.get(url.replace("SAYFA", str(page)))
55
- secici = HTMLParser(istek.text)
53
+ secici = HTMLHelper(istek.text)
56
54
 
57
- for veri in secici.css("div.tab-content > div.grid a"):
58
- h2_el = veri.css_first("h2")
59
- name = h2_el.text(strip=True) if h2_el else None
60
-
61
- # opacity-80 div'den episode bilgisi - normalize-space yerine doğrudan text
62
- opacity_el = veri.css_first("div[class*='opacity-80']")
63
- ep_name = opacity_el.text(strip=True) if opacity_el else None
64
- if not ep_name:
55
+ # Genel olarak dizi sayfalarına giden linkleri al
56
+ for veri in secici.select('a[href*="/dizi/"]'):
57
+ href = secici.select_attr('a', 'href', veri)
58
+ title = secici.select_text(None, veri)
59
+ if not href or not title:
65
60
  continue
66
61
 
67
- ep_name = ep_name.replace(". Sezon", "x").replace(". Bölüm", "").replace("x ", "x")
68
- title = f"{name} - {ep_name}"
69
-
70
- href = veri.attrs.get("href")
71
- ep_req = await self.httpx.get(self.fix_url(href))
72
- ep_secici = HTMLParser(ep_req.text)
73
-
74
- # nav li'leri alıp 3. elemana erişme (nth-of-type yerine)
75
- nav_lis = ep_secici.css("nav li")
76
- if len(nav_lis) >= 3:
77
- link_el = nav_lis[2].css_first("a")
78
- href = link_el.attrs.get("href") if link_el else None
79
- else:
80
- href = None
81
-
82
- poster_el = ep_secici.css_first("img.imgt")
83
- poster = poster_el.attrs.get("src") if poster_el else None
84
-
85
- if href:
86
- ana_sayfa.append(
87
- MainPageResult(
88
- category = category,
89
- title = title,
90
- url = self.fix_url(href),
91
- poster = self.fix_url(poster) if poster else None
92
- )
93
- )
62
+ # Detay sayfasından poster vb. bilgileri al
63
+ ep_req = await self.httpx.get(self.fix_url(href))
64
+ ep_secici = HTMLHelper(ep_req.text)
65
+ poster = ep_secici.select_attr('img.imgt', 'src') or ep_secici.select_attr('img', 'src')
66
+
67
+ ana_sayfa.append(MainPageResult(
68
+ category = category,
69
+ title = title,
70
+ url = self.fix_url(href),
71
+ poster = self.fix_url(poster) if poster else None
72
+ ))
94
73
 
95
74
  return ana_sayfa
96
75
 
@@ -124,9 +103,10 @@ class Dizilla(PluginBase):
124
103
  # -> https://images.macellan.online/...
125
104
  if "cdn.ampproject.org" in url:
126
105
  # /i/s/ veya /ii/s/ gibi AMP prefix'lerinden sonraki kısmı al
127
- match = re.search(r"cdn\.ampproject\.org/[^/]+/s/(.+)$", url)
106
+ helper = HTMLHelper(url)
107
+ match = helper.regex_first(r"cdn\.ampproject\.org/[^/]+/s/(.+)$")
128
108
  if match:
129
- return f"https://{match.group(1)}"
109
+ return f"https://{match}"
130
110
  return url
131
111
 
132
112
  async def search(self, query: str) -> list[SearchResult]:
@@ -155,64 +135,64 @@ class Dizilla(PluginBase):
155
135
 
156
136
  async def load_item(self, url: str) -> SeriesInfo:
157
137
  istek = await self.httpx.get(url)
158
- secici = HTMLParser(istek.text)
138
+ secici = HTMLHelper(istek.text)
159
139
 
160
- title = secici.css_first("div.poster.poster h2")
161
- title = title.text(strip=True) if title else None
140
+ title = secici.select_text("div.poster.poster h2")
162
141
  if not title:
163
142
  return None
164
143
 
165
- poster_el = secici.css_first("div.w-full.page-top.relative img")
166
- poster = self.fix_url(poster_el.attrs.get("src")) if poster_el else None
144
+ poster = secici.select_attr("div.w-full.page-top.relative img", "src")
145
+ poster = self.fix_url(poster) if poster else None
167
146
 
168
147
  # Year extraction (Kotlin: [1] index for w-fit min-w-fit)
169
- info_boxes = secici.css("div.w-fit.min-w-fit")
148
+ info_boxes = secici.select("div.w-fit.min-w-fit")
170
149
  year = None
171
150
  if len(info_boxes) > 1:
172
- year_el = info_boxes[1].css_first("span.text-sm.opacity-60")
173
- if year_el:
174
- year_text = year_el.text(strip=True)
151
+ year_text = secici.select_text("span.text-sm.opacity-60", info_boxes[1])
152
+ if year_text:
175
153
  year = year_text.split(" ")[-1] if " " in year_text else year_text
176
154
 
177
- description_el = secici.css_first("div.mt-2.text-sm")
178
- description = description_el.text(strip=True) if description_el else None
155
+ description = secici.select_text("div.mt-2.text-sm")
179
156
 
180
- tags_el = secici.css_first("div.poster.poster h3")
181
- tags = [t.strip() for t in tags_el.text(strip=True).split(",")] if tags_el else []
157
+ tags_text = secici.select_text("div.poster.poster h3")
158
+ tags = [t.strip() for t in tags_text.split(",")] if tags_text else []
182
159
 
183
- actors = [h5.text(strip=True) for h5 in secici.css("div.global-box h5")]
160
+ actors = secici.select_all_text("div.global-box h5")
184
161
 
185
162
  episodeses = []
186
163
  # Seasons links iteration
187
- season_links = secici.css("div.flex.items-center.flex-wrap.gap-2.mb-4 a")
164
+ season_links = secici.select("div.flex.items-center.flex-wrap.gap-2.mb-4 a")
188
165
  for sezon in season_links:
189
- sezon_href = self.fix_url(sezon.attrs.get("href"))
166
+ sezon_href = secici.select_attr("a", "href", sezon)
167
+ sezon_href = self.fix_url(sezon_href)
190
168
  sezon_req = await self.httpx.get(sezon_href)
191
169
 
192
170
  season_num = None
193
171
  try:
194
172
  # URL'den sezon numarasını çek: ...-N-sezon formatı
195
- season_match = re.search(r"-(\d+)-sezon", sezon_href)
173
+ season_match = secici.regex_first(r"-(\d+)-sezon", sezon_href)
196
174
  if season_match:
197
- season_num = int(season_match.group(1))
175
+ season_num = int(season_match)
198
176
  except:
199
177
  pass
200
178
 
201
- sezon_secici = HTMLParser(sezon_req.text)
202
- for bolum in sezon_secici.css("div.episodes div.cursor-pointer"):
179
+ sezon_secici = HTMLHelper(sezon_req.text)
180
+ for bolum in sezon_secici.select("div.episodes div.cursor-pointer"):
203
181
  # Kotlin: bolum.select("a").last()
204
- links = bolum.css("a")
182
+ links = sezon_secici.select("a", bolum)
205
183
  if not links:
206
184
  continue
207
185
 
208
186
  ep_link = links[-1]
209
- ep_name = ep_link.text(strip=True)
210
- ep_href = self.fix_url(ep_link.attrs.get("href"))
187
+ ep_name = sezon_secici.select_text("a", ep_link)
188
+ ep_href = sezon_secici.select_attr("a", "href", ep_link)
189
+ ep_href = self.fix_url(ep_href)
211
190
 
212
191
  # Episode number (first link's text usually)
213
192
  ep_num = None
214
193
  try:
215
- ep_num = int(links[0].text(strip=True))
194
+ ep_num_text = sezon_secici.select_text("a", links[0])
195
+ ep_num = int(ep_num_text) if ep_num_text else None
216
196
  except:
217
197
  pass
218
198
 
@@ -236,13 +216,13 @@ class Dizilla(PluginBase):
236
216
 
237
217
  async def load_links(self, url: str) -> list[ExtractResult]:
238
218
  istek = await self.httpx.get(url)
239
- secici = HTMLParser(istek.text)
219
+ secici = HTMLHelper(istek.text)
240
220
 
241
- next_data_el = secici.css_first("script#__NEXT_DATA__")
242
- if not next_data_el:
221
+ next_data_text = secici.select_text("script#__NEXT_DATA__")
222
+ if not next_data_text:
243
223
  return []
244
224
 
245
- next_data = loads(next_data_el.text(strip=True))
225
+ next_data = loads(next_data_text)
246
226
  secure_data = next_data.get("props", {}).get("pageProps", {}).get("secureData", {})
247
227
  decrypted = await self.decrypt_response(secure_data)
248
228
  results = decrypted.get("RelatedResults", {}).get("getEpisodeSources", {}).get("result", [])
@@ -258,8 +238,8 @@ class Dizilla(PluginBase):
258
238
  cleaned_source = source_content.replace('"', '').replace('\\', '')
259
239
 
260
240
  # Parse cleaned HTML
261
- iframe_el = HTMLParser(cleaned_source).css_first("iframe")
262
- iframe_src = iframe_el.attrs.get("src") if iframe_el else None
241
+ iframe_secici = HTMLHelper(cleaned_source)
242
+ iframe_src = iframe_secici.select_attr("iframe", "src")
263
243
 
264
244
  # Referer check (matching Kotlin: loadExtractor(iframe, "${mainUrl}/", ...))
265
245
  iframe_url = self.fix_url(iframe_src) if iframe_src else None
@@ -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 FilmBip(PluginBase):
8
6
  name = "FilmBip"
@@ -37,16 +35,13 @@ class FilmBip(PluginBase):
37
35
  page_url = page_url.rstrip("/")
38
36
 
39
37
  istek = await self.httpx.get(page_url)
40
- secici = HTMLParser(istek.text)
38
+ secici = HTMLHelper(istek.text)
41
39
 
42
40
  results = []
43
- for veri in secici.css("div.poster-long"):
44
- img = veri.css_first("a.block img.lazy")
45
- link_el = veri.css_first("a.block")
46
-
47
- title = img.attrs.get("alt") if img else None
48
- href = link_el.attrs.get("href") if link_el else None
49
- poster = (img.attrs.get("data-src") or img.attrs.get("src")) if img else None
41
+ for veri in secici.select("div.poster-long"):
42
+ title = secici.select_attr("a.block img.lazy", "alt", veri)
43
+ href = secici.select_attr("a.block", "href", veri)
44
+ poster = secici.select_poster("a.block img.lazy", veri)
50
45
 
51
46
  if title and href:
52
47
  results.append(MainPageResult(
@@ -80,17 +75,13 @@ class FilmBip(PluginBase):
80
75
  except Exception:
81
76
  return []
82
77
 
83
- secici = HTMLParser(html_content)
78
+ secici = HTMLHelper(html_content)
84
79
 
85
80
  results = []
86
- for veri in secici.css("li"):
87
- link_el = veri.css_first("a.block.truncate")
88
- href_el = veri.css_first("a")
89
- img_el = veri.css_first("img.lazy")
90
-
91
- title = link_el.text(strip=True) if link_el else None
92
- href = href_el.attrs.get("href") if href_el else None
93
- poster = img_el.attrs.get("data-src") if img_el else None
81
+ for veri in secici.select("li"):
82
+ title = secici.select_text("a.block.truncate", veri)
83
+ href = secici.select_attr("a", "href", veri)
84
+ poster = secici.select_attr("img.lazy", "data-src", veri)
94
85
 
95
86
  if title and href:
96
87
  results.append(SearchResult(
@@ -103,42 +94,27 @@ class FilmBip(PluginBase):
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)
97
+ secici = HTMLHelper(istek.text)
107
98
  html_text = istek.text
108
99
 
109
- title_el = secici.css_first("div.page-title h1")
110
- title = title_el.text(strip=True) if title_el else ""
100
+ title = secici.select_text("div.page-title h1") or ""
111
101
 
112
- og_image = secici.css_first("meta[property='og:image']")
113
- poster = og_image.attrs.get("content") if og_image else None
102
+ poster = secici.select_attr("meta[property='og:image']", "content")
114
103
 
115
- trailer_el = secici.css_first("div.series-profile-trailer")
116
- trailer = trailer_el.attrs.get("data-yt") if trailer_el else None
104
+ trailer = secici.select_attr("div.series-profile-trailer", "data-yt")
117
105
 
118
- desc_el = secici.css_first("div.series-profile-infos-in.article p")
119
- if not desc_el:
120
- desc_el = secici.css_first("div.series-profile-summary p")
121
- description = desc_el.text(strip=True) if desc_el else None
106
+ description = secici.select_text("div.series-profile-infos-in.article p") or secici.select_text("div.series-profile-summary p")
122
107
 
123
- tags = [a.text(strip=True) for a in secici.css("div.series-profile-type.tv-show-profile-type a") if a.text(strip=True)]
108
+ tags = secici.select_all_text("div.series-profile-type.tv-show-profile-type a")
124
109
 
125
110
  # XPath yerine regex kullanarak yıl, süre vs. çıkarma
126
- year = None
127
- year_match = re.search(r'Yapım yılı.*?<p[^>]*>(\d{4})</p>', html_text, re.IGNORECASE | re.DOTALL)
128
- if year_match:
129
- year = year_match.group(1)
111
+ year = secici.regex_first(r'(?i)Yapım yılı.*?<p[^>]*>(\d{4})</p>', secici.html)
130
112
 
131
- duration = None
132
- duration_match = re.search(r'Süre.*?<p[^>]*>(\d+)', html_text, re.IGNORECASE | re.DOTALL)
133
- if duration_match:
134
- duration = duration_match.group(1)
113
+ duration = secici.regex_first(r'(?i)Süre.*?<p[^>]*>(\d+)', secici.html)
135
114
 
136
- rating = None
137
- rating_match = re.search(r'IMDB Puanı.*?<span[^>]*>([0-9.]+)</span>', html_text, re.IGNORECASE | re.DOTALL)
138
- if rating_match:
139
- rating = rating_match.group(1)
115
+ rating = secici.regex_first(r'(?i)IMDB Puanı.*?<span[^>]*>([0-9.]+)</span>', secici.html)
140
116
 
141
- actors = [img.attrs.get("alt") for img in secici.css("div.series-profile-cast ul li a img") if img.attrs.get("alt")]
117
+ actors = [img.attrs.get("alt") for img in secici.select("div.series-profile-cast ul li a img") if img.attrs.get("alt")]
142
118
 
143
119
  return MovieInfo(
144
120
  url = url,
@@ -154,13 +130,12 @@ class FilmBip(PluginBase):
154
130
 
155
131
  async def load_links(self, url: str) -> list[ExtractResult]:
156
132
  istek = await self.httpx.get(url)
157
- secici = HTMLParser(istek.text)
133
+ secici = HTMLHelper(istek.text)
158
134
 
159
135
  results = []
160
136
 
161
- for player in secici.css("div#tv-spoox2"):
162
- iframe_el = player.css_first("iframe")
163
- iframe = iframe_el.attrs.get("src") if iframe_el else None
137
+ for player in secici.select("div#tv-spoox2"):
138
+ iframe = secici.select_attr("iframe", "src", player)
164
139
 
165
140
  if iframe:
166
141
  iframe = self.fix_url(iframe)