KekikStream 2.3.3__py3-none-any.whl → 2.3.5__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 (61) hide show
  1. KekikStream/Core/HTMLHelper.py +134 -0
  2. KekikStream/Core/Plugin/PluginBase.py +12 -2
  3. KekikStream/Core/__init__.py +2 -0
  4. KekikStream/Extractors/CloseLoad.py +12 -13
  5. KekikStream/Extractors/ContentX.py +33 -31
  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 +15 -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/DiziWatch.py +217 -0
  34. KekikStream/Plugins/DiziYou.py +95 -88
  35. KekikStream/Plugins/Dizilla.py +54 -72
  36. KekikStream/Plugins/FilmBip.py +24 -49
  37. KekikStream/Plugins/FilmMakinesi.py +35 -52
  38. KekikStream/Plugins/FilmModu.py +27 -41
  39. KekikStream/Plugins/FullHDFilm.py +50 -72
  40. KekikStream/Plugins/FullHDFilmizlesene.py +35 -51
  41. KekikStream/Plugins/HDFilmCehennemi.py +48 -62
  42. KekikStream/Plugins/JetFilmizle.py +32 -50
  43. KekikStream/Plugins/KultFilmler.py +42 -67
  44. KekikStream/Plugins/RecTV.py +7 -4
  45. KekikStream/Plugins/RoketDizi.py +30 -50
  46. KekikStream/Plugins/SelcukFlix.py +15 -29
  47. KekikStream/Plugins/SetFilmIzle.py +41 -70
  48. KekikStream/Plugins/SezonlukDizi.py +47 -65
  49. KekikStream/Plugins/Sinefy.py +39 -50
  50. KekikStream/Plugins/SinemaCX.py +31 -55
  51. KekikStream/Plugins/Sinezy.py +27 -54
  52. KekikStream/Plugins/SuperFilmGeldi.py +25 -44
  53. KekikStream/Plugins/UgurFilm.py +23 -48
  54. KekikStream/Plugins/YabanciDizi.py +274 -0
  55. {kekikstream-2.3.3.dist-info → kekikstream-2.3.5.dist-info}/METADATA +1 -1
  56. kekikstream-2.3.5.dist-info/RECORD +85 -0
  57. kekikstream-2.3.3.dist-info/RECORD +0 -82
  58. {kekikstream-2.3.3.dist-info → kekikstream-2.3.5.dist-info}/WHEEL +0 -0
  59. {kekikstream-2.3.3.dist-info → kekikstream-2.3.5.dist-info}/entry_points.txt +0 -0
  60. {kekikstream-2.3.3.dist-info → kekikstream-2.3.5.dist-info}/licenses/LICENSE +0 -0
  61. {kekikstream-2.3.3.dist-info → kekikstream-2.3.5.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,217 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult, HTMLHelper
4
+ import urllib.parse
5
+
6
+ class DiziWatch(PluginBase):
7
+ name = "DiziWatch"
8
+ language = "tr"
9
+ main_url = "https://diziwatch.to"
10
+ favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
11
+ description = "Diziwatch; en güncel yabancı dizileri ve animeleri, Türkçe altyazılı ve dublaj seçenekleriyle izleyebileceğiniz platform."
12
+
13
+ main_page = {
14
+ f"{main_url}/episodes" : "Yeni Bölümler",
15
+ "9" : "Aksiyon",
16
+ "17" : "Animasyon",
17
+ "5" : "Bilim Kurgu",
18
+ "2" : "Dram",
19
+ "12" : "Fantastik",
20
+ "3" : "Gizem",
21
+ "4" : "Komedi",
22
+ "8" : "Korku",
23
+ "24" : "Macera",
24
+ "14" : "Müzik",
25
+ "7" : "Romantik",
26
+ "23" : "Spor",
27
+ "1" : "Suç",
28
+ }
29
+
30
+ def __init__(self):
31
+ super().__init__()
32
+ self.c_key = None
33
+ self.c_value = None
34
+
35
+ async def _init_session(self):
36
+ if self.c_key and self.c_value:
37
+ return
38
+
39
+ # Fetch anime-arsivi to get CSRF tokens
40
+ resp = await self.httpx.get(f"{self.main_url}/anime-arsivi")
41
+ sel = HTMLHelper(resp.text)
42
+
43
+ # form.bg-[rgba(255,255,255,.15)] > input
44
+ # We can just look for the first two inputs in that specific form
45
+ inputs = sel.select("form.bg-\\[rgba\\(255\\,255\\,255\\,\\.15\\)\\] input")
46
+ if len(inputs) >= 2:
47
+ self.c_key = inputs[0].attrs.get("value")
48
+ self.c_value = inputs[1].attrs.get("value")
49
+
50
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
51
+ await self._init_session()
52
+
53
+ if url.startswith("https://"):
54
+ full_url = f"{url}?page={page}"
55
+ resp = await self.httpx.get(full_url, headers={"Referer": f"{self.main_url}/"})
56
+ sel = HTMLHelper(resp.text)
57
+ items = sel.select("div.swiper-slide a")
58
+ else:
59
+ # Category ID based
60
+ full_url = f"{self.main_url}/anime-arsivi?category={url}&minImdb=&name=&release_year=&sort=date_desc&page={page}"
61
+ resp = await self.httpx.get(full_url, headers={"Referer": f"{self.main_url}/"})
62
+ sel = HTMLHelper(resp.text)
63
+ items = sel.select("div.content-inner a")
64
+
65
+ results = []
66
+ for item in items:
67
+ title = sel.select_text("h2", item)
68
+ href = item.attrs.get("href") if item.tag == "a" else sel.select_attr("a", "href", item)
69
+ poster = sel.select_attr("img", "src", item) or sel.select_attr("img", "data-src", item)
70
+
71
+ if title and href:
72
+ # If it's an episode link, clean it to get show link
73
+ # Regex in Kotlin: /sezon-\d+/bolum-\d+/?$
74
+ clean_href = HTMLHelper(href).regex_replace(r"/sezon-\d+/bolum-\d+/?$", "")
75
+
76
+ # If cleaning changed something, it was an episode link, maybe add it to title
77
+ if clean_href != href:
78
+ se_info = sel.select_text("div.flex.gap-1.items-center", item)
79
+ if se_info:
80
+ title = f"{title} - {se_info}"
81
+
82
+ results.append(MainPageResult(
83
+ category = category,
84
+ title = title,
85
+ url = self.fix_url(clean_href),
86
+ poster = self.fix_url(poster) if poster else None
87
+ ))
88
+
89
+ return results
90
+
91
+ async def search(self, query: str) -> list[SearchResult]:
92
+ await self._init_session()
93
+
94
+ post_url = f"{self.main_url}/bg/searchcontent"
95
+ data = {
96
+ "cKey" : self.c_key,
97
+ "cValue" : self.c_value,
98
+ "searchterm" : query
99
+ }
100
+
101
+ headers = {
102
+ "X-Requested-With" : "XMLHttpRequest",
103
+ "Accept" : "application/json, text/javascript, */*; q=0.01",
104
+ "Referer" : f"{self.main_url}/"
105
+ }
106
+
107
+ resp = await self.httpx.post(post_url, data=data, headers=headers)
108
+
109
+ try:
110
+ raw = resp.json()
111
+ # Kotlin maps this to ApiResponse -> DataWrapper -> Icerikler
112
+ res_array = raw.get("data", {}).get("result", [])
113
+
114
+ results = []
115
+ for item in res_array:
116
+ title = item.get("object_name", "").replace("\\", "")
117
+ slug = item.get("used_slug", "").replace("\\", "")
118
+ poster = item.get("object_poster_url", "")
119
+
120
+ # Cleanup poster URL as in Kotlin
121
+ if poster:
122
+ poster = poster.replace("images-macellan-online.cdn.ampproject.org/i/s/", "") \
123
+ .replace("file.dizilla.club", "file.macellan.online") \
124
+ .replace("images.dizilla.club", "images.macellan.online") \
125
+ .replace("images.dizimia4.com", "images.macellan.online") \
126
+ .replace("file.dizimia4.com", "file.macellan.online")
127
+ poster = HTMLHelper(poster).regex_replace(r"(file\.)[\w\.]+\/?", r"\1macellan.online/")
128
+ poster = HTMLHelper(poster).regex_replace(r"(images\.)[\w\.]+\/?", r"\1macellan.online/")
129
+ poster = poster.replace("/f/f/", "/630/910/")
130
+
131
+ if title and slug:
132
+ results.append(SearchResult(
133
+ title = title,
134
+ url = self.fix_url(slug),
135
+ poster = self.fix_url(poster) if poster else None
136
+ ))
137
+ return results
138
+ except Exception:
139
+ return []
140
+
141
+ async def load_item(self, url: str) -> SeriesInfo:
142
+ resp = await self.httpx.get(url)
143
+ sel = HTMLHelper(resp.text)
144
+
145
+ title = sel.select_text("h2")
146
+ poster = sel.select_attr("img.rounded-md", "src")
147
+ description = sel.select_text("div.text-sm")
148
+
149
+ year = sel.regex_first(r"Yap\u0131m Y\u0131l\u0131\s*:\s*(\d+)", resp.text)
150
+
151
+ tags = []
152
+ tags_raw = sel.regex_first(r"T\u00fcr\s*:\s*([^<]+)", resp.text)
153
+ if tags_raw:
154
+ tags = [t.strip() for t in tags_raw.split(",")]
155
+
156
+ rating = sel.select_text(".font-semibold.text-white")
157
+ if rating:
158
+ rating = rating.replace(",", ".").strip()
159
+
160
+ actors = [a.text(strip=True) for a in sel.select("span.valor a")]
161
+
162
+ trailer_match = sel.regex_first(r"embed\/(.*)\?rel", resp.text)
163
+ trailer = f"https://www.youtube.com/embed/{trailer_match}" if trailer_match else None
164
+
165
+ duration_text = sel.select_text("span.runtime")
166
+ duration = duration_text.split(" ")[0] if duration_text else None
167
+
168
+ episodes = []
169
+ # ul a handles episodes
170
+ for ep_link in sel.select("ul a"):
171
+ href = ep_link.attrs.get("href")
172
+ if not href or "/sezon-" not in href:
173
+ continue
174
+
175
+ ep_name = sel.select_text("span.hidden.sm\\:block", ep_link)
176
+
177
+ season_match = sel.regex_first(r"sezon-(\d+)", href)
178
+ episode_match = sel.regex_first(r"bolum-(\d+)", href)
179
+
180
+ season = season_match if season_match else None
181
+ episode_num = episode_match if episode_match else None
182
+
183
+ episodes.append(Episode(
184
+ season = int(season) if season and season.isdigit() else None,
185
+ episode = int(episode_num) if episode_num and episode_num.isdigit() else None,
186
+ title = ep_name if ep_name else f"{season}x{episode_num}",
187
+ url = self.fix_url(href)
188
+ ))
189
+
190
+ return SeriesInfo(
191
+ title = title,
192
+ url = url,
193
+ poster = self.fix_url(poster) if poster else None,
194
+ description = description,
195
+ rating = rating,
196
+ tags = tags,
197
+ actors = actors,
198
+ year = year,
199
+ episodes = episodes,
200
+ duration = int(duration) if duration and str(duration).isdigit() else None
201
+ )
202
+
203
+ async def load_links(self, url: str) -> list[ExtractResult]:
204
+ resp = await self.httpx.get(url)
205
+ sel = HTMLHelper(resp.text)
206
+
207
+ iframe = sel.select_attr("iframe", "src")
208
+ if not iframe:
209
+ return []
210
+
211
+ iframe_url = self.fix_url(iframe)
212
+ data = await self.extract(iframe_url, referer=f"{self.main_url}/")
213
+
214
+ if not data:
215
+ return []
216
+
217
+ return data if isinstance(data, list) else [data]
@@ -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":