KekikStream 2.2.8__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 +47 -37
  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 +101 -86
  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 +110 -117
  49. KekikStream/Plugins/SezonlukDizi.py +88 -106
  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.8.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.8.dist-info/RECORD +0 -82
  59. {kekikstream-2.2.8.dist-info → kekikstream-2.3.9.dist-info}/WHEEL +0 -0
  60. {kekikstream-2.2.8.dist-info → kekikstream-2.3.9.dist-info}/entry_points.txt +0 -0
  61. {kekikstream-2.2.8.dist-info → kekikstream-2.3.9.dist-info}/licenses/LICENSE +0 -0
  62. {kekikstream-2.2.8.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, SeriesInfo, Episode, ExtractResult
4
- from selectolax.parser import HTMLParser
5
- import re
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult, HTMLHelper
4
+ import asyncio
6
5
 
7
6
  class SezonlukDizi(PluginBase):
8
7
  name = "SezonlukDizi"
@@ -39,18 +38,26 @@ class SezonlukDizi(PluginBase):
39
38
  f"{main_url}/diziler.asp?siralama_tipi=id&tur=western&s=" : "Western"
40
39
  }
41
40
 
41
+ async def _get_asp_data(self) -> dict:
42
+ js_req = await self.httpx.get(f"{self.main_url}/js/site.min.js")
43
+ js = HTMLHelper(js_req.text)
44
+ alt = js.regex_first(r"dataAlternatif(.*?)\.asp")
45
+ emb = js.regex_first(r"dataEmbed(.*?)\.asp")
46
+
47
+ return {
48
+ "alternatif": alt or "",
49
+ "embed": emb or ""
50
+ }
51
+
42
52
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
43
53
  istek = await self.httpx.get(f"{url}{page}")
44
- secici = HTMLParser(istek.text)
54
+ secici = HTMLHelper(istek.text)
45
55
 
46
56
  results = []
47
- for veri in secici.css("div.afis a"):
48
- desc_el = veri.css_first("div.description")
49
- img_el = veri.css_first("img")
50
-
51
- title = desc_el.text(strip=True) if desc_el else None
52
- href = veri.attrs.get("href")
53
- poster = img_el.attrs.get("data-src") if img_el else None
57
+ for veri in secici.select("div.afis a"):
58
+ title = secici.select_text("div.description", veri)
59
+ href = secici.select_attr("a", "href", veri)
60
+ poster = secici.select_attr("img", "data-src", veri)
54
61
 
55
62
  if title and href:
56
63
  results.append(MainPageResult(
@@ -64,16 +71,13 @@ class SezonlukDizi(PluginBase):
64
71
 
65
72
  async def search(self, query: str) -> list[SearchResult]:
66
73
  istek = await self.httpx.get(f"{self.main_url}/diziler.asp?adi={query}")
67
- secici = HTMLParser(istek.text)
74
+ secici = HTMLHelper(istek.text)
68
75
 
69
76
  results = []
70
- for afis in secici.css("div.afis a.column"):
71
- desc_el = afis.css_first("div.description")
72
- img_el = afis.css_first("img")
73
-
74
- title = desc_el.text(strip=True) if desc_el else None
75
- href = afis.attrs.get("href")
76
- poster = img_el.attrs.get("data-src") if img_el else None
77
+ for afis in secici.select("div.afis a"):
78
+ title = secici.select_text("div.description", afis)
79
+ href = secici.select_attr("a", "href", afis)
80
+ poster = secici.select_attr("img", "data-src", afis)
77
81
 
78
82
  if title and href:
79
83
  results.append(SearchResult(
@@ -86,67 +90,56 @@ class SezonlukDizi(PluginBase):
86
90
 
87
91
  async def load_item(self, url: str) -> SeriesInfo:
88
92
  istek = await self.httpx.get(url)
89
- secici = HTMLParser(istek.text)
93
+ secici = HTMLHelper(istek.text)
90
94
 
91
- title_el = secici.css_first("div.header")
92
- title = title_el.text(strip=True) if title_el else ""
95
+ title = secici.select_text("div.header") or ""
93
96
 
94
- poster_el = secici.css_first("div.image img")
95
- poster = poster_el.attrs.get("data-src", "").strip() if poster_el else ""
97
+ poster = secici.select_attr("div.image img", "data-src") or ""
96
98
 
97
99
  # year: re_first yerine re.search
98
- year_el = secici.css_first("div.extra span")
99
- year_text = year_el.text(strip=True) if year_el else ""
100
- year_match = re.search(r"(\d{4})", year_text)
101
- year = year_match.group(1) if year_match else None
100
+ year_text = secici.select_text("div.extra span") or ""
101
+ year = secici.regex_first(r"(\d{4})", year_text)
102
102
 
103
103
  # xpath normalized-space yerine doğrudan ID ile element bulup text al
104
- desc_el = secici.css_first("span#tartismayorum-konu")
105
- description = desc_el.text(strip=True) if desc_el else ""
104
+ description = secici.select_text("span#tartismayorum-konu") or ""
106
105
 
107
- tags = [a.text(strip=True) for a in secici.css("div.labels a[href*='tur']") if a.text(strip=True)]
106
+ tags = [a.text(strip=True) for a in secici.select("div.labels a[href*='tur']") if a.text(strip=True)]
108
107
 
109
- # rating: re_first yerine re.search
110
- rating_el = secici.css_first("div.dizipuani a div")
111
- rating_text = rating_el.text(strip=True) if rating_el else ""
112
- rating_match = re.search(r"[\d.,]+", rating_text)
113
- rating = rating_match.group() if rating_match else None
108
+ # rating: regex ile çıkar
109
+ rating_text = secici.select_text("div.dizipuani a div") or ""
110
+ rating = secici.regex_first(r"[\d.,]+", rating_text)
114
111
 
115
112
  actors = []
116
113
 
117
114
  actors_istek = await self.httpx.get(f"{self.main_url}/oyuncular/{url.split('/')[-1]}")
118
- actors_secici = HTMLParser(actors_istek.text)
119
- for actor in actors_secici.css("div.doubling div.ui"):
120
- header_el = actor.css_first("div.header")
121
- if header_el and header_el.text(strip=True):
122
- actors.append(header_el.text(strip=True))
115
+ actors_secici = HTMLHelper(actors_istek.text)
116
+ for actor in actors_secici.select("div.doubling div.ui"):
117
+ header_text = actors_secici.select_text("div.header", actor)
118
+ if header_text:
119
+ actors.append(header_text)
123
120
 
124
121
  episodes_istek = await self.httpx.get(f"{self.main_url}/bolumler/{url.split('/')[-1]}")
125
- episodes_secici = HTMLParser(episodes_istek.text)
122
+ episodes_secici = HTMLHelper(episodes_istek.text)
126
123
  episodes = []
127
124
 
128
- for sezon in episodes_secici.css("table.unstackable"):
129
- for bolum in sezon.css("tbody tr"):
125
+ for sezon in episodes_secici.select("table.unstackable"):
126
+ for bolum in episodes_secici.select("tbody tr", sezon):
130
127
  # td:nth-of-type selectolax'ta desteklenmiyor, alternatif yol: tüm td'leri alıp indexle
131
- tds = bolum.css("td")
128
+ tds = episodes_secici.select("td", bolum)
132
129
  if len(tds) < 4:
133
130
  continue
134
131
 
135
132
  # 4. td'den isim ve href
136
- ep_name_el = tds[3].css_first("a")
137
- ep_name = ep_name_el.text(strip=True) if ep_name_el else None
138
- ep_href = ep_name_el.attrs.get("href") if ep_name_el else None
133
+ ep_name = episodes_secici.select_text("a", tds[3])
134
+ ep_href = episodes_secici.select_attr("a", "href", tds[3])
139
135
 
140
- # 3. td'den episode (re_first yerine re.search)
141
- ep_episode_el = tds[2].css_first("a")
142
- ep_episode_text = ep_episode_el.text(strip=True) if ep_episode_el else ""
143
- ep_episode_match = re.search(r"(\d+)", ep_episode_text)
144
- ep_episode = ep_episode_match.group(1) if ep_episode_match else None
136
+ # 3. td'den episode (re_first yerine regex)
137
+ ep_episode_text = episodes_secici.select_text("a", tds[2]) or ""
138
+ ep_episode = episodes_secici.regex_first(r"(\d+)", ep_episode_text)
145
139
 
146
- # 2. td'den season (re_first yerine re.search)
140
+ # 2. td'den season (re_first yerine regex)
147
141
  ep_season_text = tds[1].text(strip=True) if tds[1] else ""
148
- ep_season_match = re.search(r"(\d+)", ep_season_text)
149
- ep_season = ep_season_match.group(1) if ep_season_match else None
142
+ ep_season = secici.regex_first(r"(\d+)", ep_season_text)
150
143
 
151
144
  if ep_name and ep_href:
152
145
  episode = Episode(
@@ -169,64 +162,53 @@ class SezonlukDizi(PluginBase):
169
162
  actors = actors
170
163
  )
171
164
 
172
- async def get_asp_data(self) -> tuple[str, str]:
173
- """Fetch dynamic ASP version numbers from site.min.js"""
174
- try:
175
- js_content = await self.httpx.get(f"{self.main_url}/js/site.min.js")
176
- alternatif_match = re.search(r'dataAlternatif(.*?)\.asp', js_content.text)
177
- embed_match = re.search(r'dataEmbed(.*?)\.asp', js_content.text)
178
-
179
- alternatif_ver = alternatif_match.group(1) if alternatif_match else "22"
180
- embed_ver = embed_match.group(1) if embed_match else "22"
181
-
182
- return (alternatif_ver, embed_ver)
183
- except Exception:
184
- return ("22", "22") # Fallback to default versions
185
-
186
165
  async def load_links(self, url: str) -> list[ExtractResult]:
187
166
  istek = await self.httpx.get(url)
188
- secici = HTMLParser(istek.text)
189
-
190
- dilsec_el = secici.css_first("div#dilsec")
191
- bid = dilsec_el.attrs.get("data-id") if dilsec_el else None
167
+ secici = HTMLHelper(istek.text)
168
+ asp_data = await self._get_asp_data()
169
+
170
+ bid = secici.select_attr("div#dilsec", "data-id")
192
171
  if not bid:
193
172
  return []
194
173
 
195
- # Get dynamic ASP versions
196
- alternatif_ver, embed_ver = await self.get_asp_data()
174
+ semaphore = asyncio.Semaphore(5)
175
+ tasks = []
197
176
 
198
- results = []
199
- for dil, label in [("1", "Altyazı"), ("0", "Dublaj")]:
200
- dil_istek = await self.httpx.post(
201
- url = f"{self.main_url}/ajax/dataAlternatif{alternatif_ver}.asp",
177
+ async def fetch_and_extract(veri, dil_etiketi):
178
+ async with semaphore:
179
+ try:
180
+ embed_resp = await self.httpx.post(
181
+ f"{self.main_url}/ajax/dataEmbed{asp_data['embed']}.asp",
182
+ headers = {"X-Requested-With": "XMLHttpRequest"},
183
+ data = {"id": str(veri.get("id"))}
184
+ )
185
+ embed_secici = HTMLHelper(embed_resp.text)
186
+ iframe_src = embed_secici.select_attr("iframe", "src")
187
+
188
+ if iframe_src:
189
+ if "link.asp" in iframe_src:
190
+ return None
191
+
192
+ iframe_url = self.fix_url(iframe_src)
193
+ return await self.extract(iframe_url, referer=f"{self.main_url}/", prefix=f"{dil_etiketi} - {veri.get('baslik')}")
194
+ except:
195
+ pass
196
+ return None
197
+
198
+ for dil_kodu, dil_etiketi in [("1", "Altyazı"), ("0", "Dublaj")]:
199
+ altyazi_resp = await self.httpx.post(
200
+ f"{self.main_url}/ajax/dataAlternatif{asp_data['alternatif']}.asp",
202
201
  headers = {"X-Requested-With": "XMLHttpRequest"},
203
- data = {"bid": bid, "dil": dil},
202
+ data = {"bid": bid, "dil": dil_kodu}
204
203
  )
205
-
204
+
206
205
  try:
207
- dil_json = dil_istek.json()
208
- except Exception:
206
+ data_json = altyazi_resp.json()
207
+ if data_json.get("status") == "success" and data_json.get("data"):
208
+ for veri in data_json["data"]:
209
+ tasks.append(fetch_and_extract(veri, dil_etiketi))
210
+ except:
209
211
  continue
210
212
 
211
- if dil_json.get("status") == "success":
212
- for idx, veri in enumerate(dil_json.get("data", [])):
213
- veri_response = await self.httpx.post(
214
- url = f"{self.main_url}/ajax/dataEmbed{embed_ver}.asp",
215
- headers = {"X-Requested-With": "XMLHttpRequest"},
216
- data = {"id": veri.get("id")},
217
- )
218
- veri_secici = HTMLParser(veri_response.text)
219
-
220
- iframe_el = veri_secici.css_first("iframe")
221
- iframe = iframe_el.attrs.get("src") if iframe_el else None
222
-
223
- if iframe:
224
- if "link.asp" in iframe:
225
- continue
226
-
227
- iframe_url = self.fix_url(iframe)
228
- data = await self.extract(iframe_url, prefix=label)
229
- if data:
230
- results.append(data)
231
-
232
- return results
213
+ results = await asyncio.gather(*tasks)
214
+ return [r for r in results if r]
@@ -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, MovieInfo, ExtractResult
4
- from selectolax.parser import HTMLParser
5
- import re, json, urllib.parse
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, MovieInfo, ExtractResult, HTMLHelper
4
+ import json, urllib.parse
6
5
 
7
6
  class Sinefy(PluginBase):
8
7
  name = "Sinefy"
@@ -44,17 +43,13 @@ class Sinefy(PluginBase):
44
43
  full_url = f"{url}&page={page}"
45
44
 
46
45
  resp = await self.httpx.get(full_url)
47
- sel = HTMLParser(resp.text)
46
+ sel = HTMLHelper(resp.text)
48
47
 
49
48
  results = []
50
- for item in sel.css("div.poster-with-subject, div.dark-segment div.poster-md.poster"):
51
- h2_el = item.css_first("h2")
52
- link_el = item.css_first("a")
53
- img_el = item.css_first("img")
54
-
55
- title = h2_el.text(strip=True) if h2_el else None
56
- href = link_el.attrs.get("href") if link_el else None
57
- poster = img_el.attrs.get("data-srcset") if img_el else None
49
+ for item in sel.select("div.poster-with-subject, div.dark-segment div.poster-md.poster"):
50
+ title = sel.select_text("h2", item)
51
+ href = sel.select_attr("a", "href", item)
52
+ poster = sel.select_attr("img", "data-srcset", item)
58
53
 
59
54
  if poster:
60
55
  poster = poster.split(",")[0].split(" ")[0]
@@ -76,13 +71,10 @@ class Sinefy(PluginBase):
76
71
 
77
72
  try:
78
73
  resp = await self.httpx.get(self.main_url)
79
- sel = HTMLParser(resp.text)
80
-
81
- cke_el = sel.css_first("input[name='cKey']")
82
- cval_el = sel.css_first("input[name='cValue']")
74
+ sel = HTMLHelper(resp.text)
83
75
 
84
- cke = cke_el.attrs.get("value") if cke_el else None
85
- cval = cval_el.attrs.get("value") if cval_el else None
76
+ cke = sel.select_attr("input[name='cKey']", "value")
77
+ cval = sel.select_attr("input[name='cValue']", "value")
86
78
 
87
79
  if cke and cval:
88
80
  c_key = cke
@@ -145,79 +137,88 @@ class Sinefy(PluginBase):
145
137
 
146
138
  async def load_item(self, url: str) -> SeriesInfo:
147
139
  resp = await self.httpx.get(url)
148
- sel = HTMLParser(resp.text)
140
+ sel = HTMLHelper(resp.text)
149
141
 
150
- title_el = sel.css_first("h1")
151
- title = title_el.text(strip=True) if title_el else None
142
+ title = sel.select_text("h1")
152
143
 
153
- img_el = sel.css_first("div.ui.items img")
154
- poster_info = img_el.attrs.get("data-srcset") if img_el else None
155
- poster = None
144
+ poster_info = sel.select_attr("div.ui.items img", "data-srcset")
145
+ poster = None
156
146
  if poster_info:
157
- # take 1x
158
147
  parts = str(poster_info).split(",")
159
148
  for p in parts:
160
149
  if "1x" in p:
161
150
  poster = p.strip().split(" ")[0]
162
151
  break
163
-
164
- desc_el = sel.css_first("p#tv-series-desc")
165
- description = desc_el.text(strip=True) if desc_el else None
166
152
 
167
- tags = [a.text(strip=True) for a in sel.css("div.item.categories a") if a.text(strip=True)]
153
+ description = sel.select_text("p#tv-series-desc")
168
154
 
169
- rating_el = sel.css_first("span.color-imdb")
170
- rating = rating_el.text(strip=True) if rating_el else None
155
+ tags = [a.text(strip=True) for a in sel.select("div.item.categories a") if a.text(strip=True)]
171
156
 
172
- actors = [h5.text(strip=True) for h5 in sel.css("div.content h5") if h5.text(strip=True)]
157
+ rating = sel.select_text("span.color-imdb")
173
158
 
174
- year_el = sel.css_first("span.item.year")
175
- year = year_el.text(strip=True) if year_el else None
159
+ actors = [h5.text(strip=True) for h5 in sel.select("div.content h5") if h5.text(strip=True)]
160
+
161
+ year = sel.select_text("span.item.year")
162
+ if not year and title:
163
+ # Try to extract year from title like "Movie Name(2024)"
164
+ year_match = sel.regex_first(r"\((\d{4})\)", title)
165
+ if year_match:
166
+ year = year_match
176
167
 
177
168
  episodes = []
178
- season_elements = sel.css("section.episodes-box")
169
+ episodes_box_list = sel.select("section.episodes-box")
179
170
 
180
- if season_elements:
181
- # Get season links
182
- season_links = []
183
- menu = sel.css("div.ui.vertical.fluid.tabular.menu a")
184
- for link in menu:
185
- href = link.attrs.get("href")
186
- if href:
187
- season_links.append(self.fix_url(href))
171
+ if episodes_box_list:
172
+ episodes_box = episodes_box_list[0]
173
+ # Sezon menüsünden sezon linklerini al
174
+ season_menu = sel.select("div.ui.vertical.fluid.tabular.menu a.item", episodes_box)
188
175
 
189
- for s_url in season_links:
190
- target_url = s_url if "/bolum-" in s_url else f"{s_url}/bolum-1"
191
-
192
- try:
193
- s_resp = await self.httpx.get(target_url)
194
- s_sel = HTMLParser(s_resp.text)
195
- ep_links = s_sel.css("div.ui.list.celled a.item")
176
+ # Sezon tab içeriklerini al
177
+ season_tabs = sel.select("div.ui.tab", episodes_box)
178
+
179
+ # Eğer birden fazla sezon varsa, her sezon tab'ından bölümleri çek
180
+ if season_tabs:
181
+ for idx, season_tab in enumerate(season_tabs):
182
+ # Sezon numarasını belirle
183
+ current_season_no = idx + 1
184
+
185
+ # Menüden sezon numarasını almaya çalış
186
+ if idx < len(season_menu):
187
+ menu_href = season_menu[idx].attrs.get("href", "")
188
+ match = sel.regex_first(r"sezon-(\d+)", menu_href)
189
+ if match:
190
+ current_season_no = int(match)
196
191
 
197
- current_season_no = 1
198
- match = re.search(r"sezon-(\d+)", target_url)
199
- if match:
200
- current_season_no = int(match.group(1))
192
+ # Bu sezon tab'ından bölüm linklerini çek
193
+ ep_links = sel.select("a[href*='bolum']", season_tab)
201
194
 
195
+ seen_urls = set()
202
196
  for ep_link in ep_links:
203
197
  href = ep_link.attrs.get("href")
204
- name_el = ep_link.css_first("div.content div.header")
205
- name = name_el.text(strip=True) if name_el else ""
198
+ if not href or href in seen_urls:
199
+ continue
200
+ seen_urls.add(href)
201
+
202
+ # Bölüm numarasını URL'den çıkar
203
+ ep_no = 0
204
+ match_ep = sel.regex_first(r"bolum-(\d+)", href)
205
+ if match_ep:
206
+ ep_no = int(match_ep)
206
207
 
207
- if href:
208
- ep_no = 0
209
- match_ep = re.search(r"bolum-(\d+)", href)
210
- if match_ep:
211
- ep_no = int(match_ep.group(1))
212
-
208
+ # Bölüm başlığını çıkar (önce title attribute, sonra text)
209
+ name = ep_link.attrs.get("title", "")
210
+ if not name:
211
+ name = sel.select_text("div.content div.header", ep_link)
212
+ if not name:
213
+ name = ep_link.text(strip=True)
214
+
215
+ if href and ep_no > 0:
213
216
  episodes.append(Episode(
214
- season = current_season_no,
217
+ season = current_season_no,
215
218
  episode = ep_no,
216
- title = name,
217
- url = self.fix_url(href)
219
+ title = name.strip() if name else f"{ep_no}. Bölüm",
220
+ url = self.fix_url(href)
218
221
  ))
219
- except Exception:
220
- pass
221
222
 
222
223
  if episodes:
223
224
  return SeriesInfo(
@@ -245,10 +246,9 @@ class Sinefy(PluginBase):
245
246
 
246
247
  async def load_links(self, url: str) -> list[ExtractResult]:
247
248
  resp = await self.httpx.get(url)
248
- sel = HTMLParser(resp.text)
249
+ sel = HTMLHelper(resp.text)
249
250
 
250
- iframe_el = sel.css_first("iframe")
251
- iframe = iframe_el.attrs.get("src") if iframe_el else None
251
+ iframe = sel.select_attr("iframe", "src")
252
252
 
253
253
  if not iframe:
254
254
  return []
@@ -1,8 +1,7 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
3
  from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, Subtitle, ExtractResult
4
- from selectolax.parser import HTMLParser
5
- import re
4
+ from KekikStream.Core.HTMLHelper import HTMLHelper
6
5
 
7
6
  class SinemaCX(PluginBase):
8
7
  name = "SinemaCX"
@@ -39,23 +38,16 @@ class SinemaCX(PluginBase):
39
38
 
40
39
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
41
40
  istek = await self.httpx.get(url.replace("SAYFA", str(page)))
42
- secici = HTMLParser(istek.text)
41
+ secici = HTMLHelper(istek.text)
43
42
 
44
43
  results = []
45
- for veri in secici.css("div.son div.frag-k, div.icerik div.frag-k"):
46
- span_el = veri.css_first("div.yanac span")
47
- if not span_el:
48
- continue
49
-
50
- title = span_el.text(strip=True)
44
+ for veri in secici.select("div.son div.frag-k, div.icerik div.frag-k"):
45
+ title = secici.select_text("div.yanac span", veri)
51
46
  if not title:
52
47
  continue
53
48
 
54
- link_el = veri.css_first("div.yanac a")
55
- img_el = veri.css_first("a.resim img")
56
-
57
- href = link_el.attrs.get("href") if link_el else None
58
- poster = (img_el.attrs.get("data-src") or img_el.attrs.get("src")) if img_el else None
49
+ href = secici.select_attr("div.yanac a", "href", veri)
50
+ poster = secici.select_attr("a.resim img", "data-src", veri) or secici.select_attr("a.resim img", "src", veri)
59
51
 
60
52
  results.append(MainPageResult(
61
53
  category = category,
@@ -68,23 +60,16 @@ class SinemaCX(PluginBase):
68
60
 
69
61
  async def search(self, query: str) -> list[SearchResult]:
70
62
  istek = await self.httpx.get(f"{self.main_url}/?s={query}")
71
- secici = HTMLParser(istek.text)
63
+ secici = HTMLHelper(istek.text)
72
64
 
73
65
  results = []
74
- for veri in secici.css("div.icerik div.frag-k"):
75
- span_el = veri.css_first("div.yanac span")
76
- if not span_el:
77
- continue
78
-
79
- title = span_el.text(strip=True)
66
+ for veri in secici.select("div.icerik div.frag-k"):
67
+ title = secici.select_text("div.yanac span", veri)
80
68
  if not title:
81
69
  continue
82
70
 
83
- link_el = veri.css_first("div.yanac a")
84
- img_el = veri.css_first("a.resim img")
85
-
86
- href = link_el.attrs.get("href") if link_el else None
87
- poster = (img_el.attrs.get("data-src") or img_el.attrs.get("src")) if img_el else None
71
+ href = secici.select_attr("div.yanac a", "href", veri)
72
+ poster = secici.select_attr("a.resim img", "data-src", veri) or secici.select_attr("a.resim img", "src", veri)
88
73
 
89
74
  results.append(SearchResult(
90
75
  title = title,
@@ -96,29 +81,21 @@ class SinemaCX(PluginBase):
96
81
 
97
82
  async def load_item(self, url: str) -> MovieInfo:
98
83
  istek = await self.httpx.get(url)
99
- secici = HTMLParser(istek.text)
84
+ secici = HTMLHelper(istek.text)
100
85
 
101
- duration_match = re.search(r"Süre:.*?(\d+)\s*Dakika", istek.text)
86
+ duration_match = secici.regex_first(r"Süre:.*?(\d+)\s*Dakika")
102
87
 
103
- desc_el = secici.css_first("div.ackl div.scroll-liste")
104
- description = desc_el.text(strip=True) if desc_el else None
88
+ description = secici.select_text("div.ackl div.scroll-liste")
105
89
 
106
- link_el = secici.css_first("link[rel='image_src']")
107
- poster = link_el.attrs.get("href") if link_el else None
90
+ poster = secici.select_attr("link[rel='image_src']", "href")
108
91
 
109
- title_el = secici.css_first("div.f-bilgi h1")
110
- title = title_el.text(strip=True) if title_el else None
92
+ title = secici.select_text("div.f-bilgi h1")
111
93
 
112
- tags = [a.text(strip=True) for a in secici.css("div.f-bilgi div.tur a") if a.text(strip=True)]
94
+ tags = secici.select_all_text("div.f-bilgi div.tur a")
113
95
 
114
- year_el = secici.css_first("div.f-bilgi ul.detay a[href*='yapim']")
115
- year = year_el.text(strip=True) if year_el else None
96
+ year = secici.select_text("div.f-bilgi ul.detay a[href*='yapim']")
116
97
 
117
- actors = []
118
- for li in secici.css("li.oync li.oyuncu-k"):
119
- isim_el = li.css_first("span.isim")
120
- if isim_el and isim_el.text(strip=True):
121
- actors.append(isim_el.text(strip=True))
98
+ actors = secici.select_all_text("li.oync li.oyuncu-k span.isim")
122
99
 
123
100
  return MovieInfo(
124
101
  url = url,
@@ -128,14 +105,14 @@ class SinemaCX(PluginBase):
128
105
  tags = tags,
129
106
  year = year,
130
107
  actors = actors,
131
- duration = int(duration_match[1]) if duration_match else None,
108
+ duration = int(duration_match) if duration_match else None,
132
109
  )
133
110
 
134
111
  async def load_links(self, url: str) -> list[ExtractResult]:
135
112
  istek = await self.httpx.get(url)
136
- secici = HTMLParser(istek.text)
113
+ secici = HTMLHelper(istek.text)
137
114
 
138
- iframe_list = [iframe.attrs.get("data-vsrc") for iframe in secici.css("iframe") if iframe.attrs.get("data-vsrc")]
115
+ iframe_list = secici.select_all_attr("iframe", "data-vsrc")
139
116
 
140
117
  # Sadece fragman varsa /2/ sayfasından dene
141
118
  has_only_trailer = all(
@@ -146,8 +123,9 @@ class SinemaCX(PluginBase):
146
123
  if has_only_trailer:
147
124
  alt_url = url.rstrip("/") + "/2/"
148
125
  alt_istek = await self.httpx.get(alt_url)
149
- alt_sec = HTMLParser(alt_istek.text)
150
- iframe_list = [iframe.attrs.get("data-vsrc") for iframe in alt_sec.css("iframe") if iframe.attrs.get("data-vsrc")]
126
+ alt_istek = await self.httpx.get(alt_url)
127
+ alt_sec = HTMLHelper(alt_istek.text)
128
+ iframe_list = alt_sec.select_all_attr("iframe", "data-vsrc")
151
129
 
152
130
  if not iframe_list:
153
131
  return []
@@ -164,17 +142,15 @@ class SinemaCX(PluginBase):
164
142
  iframe_text = iframe_istek.text
165
143
 
166
144
  subtitles = []
167
- sub_match = re.search(r'playerjsSubtitle\s*=\s*"(.+?)"', iframe_text)
168
- if sub_match:
169
- sub_section = sub_match[1]
170
- for sub in re.finditer(r'\[(.*?)](https?://[^\s",]+)', sub_section):
171
- subtitles.append(Subtitle(name=sub[1], url=self.fix_url(sub[2])))
145
+ sub_section = HTMLHelper(iframe_text).regex_first(r'playerjsSubtitle\s*=\s*"(.+?)"')
146
+ if sub_section:
147
+ for lang, link in HTMLHelper(sub_section).regex_all(r'\[(.*?)](https?://[^\s\",]+)'):
148
+ subtitles.append(Subtitle(name=lang, url=self.fix_url(link)))
172
149
 
173
150
  # player.filmizle.in kontrolü
174
151
  if "player.filmizle.in" in iframe.lower():
175
- base_match = re.search(r"https?://([^/]+)", iframe)
176
- if base_match:
177
- base_url = base_match[1]
152
+ base_url = HTMLHelper(iframe).regex_first(r"https?://([^/]+)")
153
+ if base_url:
178
154
  vid_id = iframe.split("/")[-1]
179
155
 
180
156
  self.httpx.headers.update({"X-Requested-With": "XMLHttpRequest"})