KekikStream 2.2.9__py3-none-any.whl → 2.5.3__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 (88) hide show
  1. KekikStream/Core/Extractor/ExtractorBase.py +3 -2
  2. KekikStream/Core/Extractor/ExtractorLoader.py +8 -14
  3. KekikStream/Core/HTMLHelper.py +205 -0
  4. KekikStream/Core/Plugin/PluginBase.py +48 -12
  5. KekikStream/Core/Plugin/PluginLoader.py +13 -14
  6. KekikStream/Core/Plugin/PluginManager.py +2 -2
  7. KekikStream/Core/Plugin/PluginModels.py +0 -3
  8. KekikStream/Core/__init__.py +2 -0
  9. KekikStream/Extractors/Abstream.py +27 -0
  10. KekikStream/Extractors/CloseLoad.py +31 -56
  11. KekikStream/Extractors/ContentX.py +28 -71
  12. KekikStream/Extractors/DonilasPlay.py +34 -78
  13. KekikStream/Extractors/DzenRu.py +11 -25
  14. KekikStream/Extractors/ExPlay.py +20 -38
  15. KekikStream/Extractors/Filemoon.py +23 -53
  16. KekikStream/Extractors/HDMomPlayer.py +30 -0
  17. KekikStream/Extractors/HDPlayerSystem.py +13 -31
  18. KekikStream/Extractors/HotStream.py +27 -0
  19. KekikStream/Extractors/JFVid.py +3 -24
  20. KekikStream/Extractors/JetTv.py +21 -34
  21. KekikStream/Extractors/JetV.py +55 -0
  22. KekikStream/Extractors/MailRu.py +11 -29
  23. KekikStream/Extractors/MixPlayHD.py +17 -31
  24. KekikStream/Extractors/MixTiger.py +17 -40
  25. KekikStream/Extractors/MolyStream.py +25 -22
  26. KekikStream/Extractors/Odnoklassniki.py +41 -105
  27. KekikStream/Extractors/PeaceMakerst.py +20 -47
  28. KekikStream/Extractors/PixelDrain.py +9 -16
  29. KekikStream/Extractors/PlayerFilmIzle.py +23 -46
  30. KekikStream/Extractors/RapidVid.py +23 -36
  31. KekikStream/Extractors/SetPlay.py +19 -44
  32. KekikStream/Extractors/SetPrime.py +3 -6
  33. KekikStream/Extractors/SibNet.py +8 -19
  34. KekikStream/Extractors/Sobreatsesuyp.py +25 -47
  35. KekikStream/Extractors/TRsTX.py +25 -55
  36. KekikStream/Extractors/TurboImgz.py +8 -16
  37. KekikStream/Extractors/TurkeyPlayer.py +5 -5
  38. KekikStream/Extractors/VCTPlay.py +10 -28
  39. KekikStream/Extractors/Veev.py +145 -0
  40. KekikStream/Extractors/VidBiz.py +62 -0
  41. KekikStream/Extractors/VidHide.py +59 -34
  42. KekikStream/Extractors/VidMoly.py +67 -89
  43. KekikStream/Extractors/VidMoxy.py +17 -29
  44. KekikStream/Extractors/VidPapi.py +26 -58
  45. KekikStream/Extractors/VideoSeyred.py +21 -42
  46. KekikStream/Extractors/Videostr.py +58 -0
  47. KekikStream/Extractors/Vidoza.py +18 -0
  48. KekikStream/Extractors/Vtbe.py +38 -0
  49. KekikStream/Extractors/YTDLP.py +2 -2
  50. KekikStream/Extractors/YildizKisaFilm.py +13 -31
  51. KekikStream/Extractors/Zeus.py +61 -0
  52. KekikStream/Plugins/BelgeselX.py +108 -99
  53. KekikStream/Plugins/DiziBox.py +61 -106
  54. KekikStream/Plugins/DiziMom.py +179 -0
  55. KekikStream/Plugins/DiziPal.py +104 -192
  56. KekikStream/Plugins/DiziYou.py +66 -149
  57. KekikStream/Plugins/Dizilla.py +93 -126
  58. KekikStream/Plugins/FilmBip.py +102 -72
  59. KekikStream/Plugins/FilmEkseni.py +199 -0
  60. KekikStream/Plugins/FilmMakinesi.py +101 -64
  61. KekikStream/Plugins/FilmModu.py +35 -59
  62. KekikStream/Plugins/Filmatek.py +184 -0
  63. KekikStream/Plugins/FilmciBaba.py +155 -0
  64. KekikStream/Plugins/FullHDFilmizlesene.py +32 -78
  65. KekikStream/Plugins/HDFilm.py +243 -0
  66. KekikStream/Plugins/HDFilmCehennemi.py +261 -222
  67. KekikStream/Plugins/JetFilmizle.py +117 -98
  68. KekikStream/Plugins/KultFilmler.py +153 -143
  69. KekikStream/Plugins/RecTV.py +53 -49
  70. KekikStream/Plugins/RoketDizi.py +92 -123
  71. KekikStream/Plugins/SelcukFlix.py +86 -95
  72. KekikStream/Plugins/SetFilmIzle.py +105 -143
  73. KekikStream/Plugins/SezonlukDizi.py +106 -128
  74. KekikStream/Plugins/Sinefy.py +194 -166
  75. KekikStream/Plugins/SinemaCX.py +159 -113
  76. KekikStream/Plugins/Sinezy.py +44 -73
  77. KekikStream/Plugins/SuperFilmGeldi.py +28 -52
  78. KekikStream/Plugins/UgurFilm.py +94 -72
  79. KekikStream/Plugins/Watch32.py +160 -0
  80. KekikStream/Plugins/YabanciDizi.py +250 -0
  81. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/METADATA +1 -1
  82. kekikstream-2.5.3.dist-info/RECORD +99 -0
  83. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/WHEEL +1 -1
  84. KekikStream/Plugins/FullHDFilm.py +0 -254
  85. kekikstream-2.2.9.dist-info/RECORD +0 -82
  86. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/entry_points.txt +0 -0
  87. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/licenses/LICENSE +0 -0
  88. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.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,125 +29,69 @@ 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(
47
42
  category = category,
48
43
  title = title,
49
44
  url = self.fix_url(href),
50
- poster = self.fix_url(poster) if poster else None,
45
+ poster = self.fix_url(poster),
51
46
  ))
52
47
 
53
48
  return results
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(
70
62
  title = title,
71
63
  url = self.fix_url(href),
72
- poster = self.fix_url(poster) if poster else None,
64
+ poster = self.fix_url(poster),
73
65
  ))
74
66
 
75
67
  return results
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)
80
- html_text = istek.text
81
-
82
- # 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
-
86
- # Fallback: Eğer title boşsa URL'den çıkar (telif kısıtlaması olan sayfalar için)
87
- if not title:
88
- # URL'den slug'ı al: https://www.diziyou.one/jasmine/ -> jasmine -> Jasmine
89
- slug = url.rstrip('/').split('/')[-1]
90
- title = slug.replace('-', ' ').title()
91
-
92
- # 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 ""
95
-
96
- # 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)
101
-
102
- desc_el = secici.css_first("div.diziyou_desc")
103
- description = desc_el.text(strip=True) if desc_el else None
104
-
105
- tags = [a.text(strip=True) for a in secici.css("div.genres a") if a.text(strip=True)]
106
-
107
- # Rating - regex ile
108
- rating = None
109
- rating_match = re.search(r"IMDB.*?([0-9.]+)", html_text, re.DOTALL | re.IGNORECASE)
110
- if rating_match:
111
- rating = rating_match.group(1)
112
-
113
- # Actors - regex ile
114
- actors = []
115
- actors_match = re.search(r"Oyuncular.*?</span>([^<]+)", html_text, re.DOTALL | re.IGNORECASE)
116
- if actors_match:
117
- actors = [actor.strip() for actor in actors_match.group(1).split(",") if actor.strip()]
71
+ secici = HTMLHelper(istek.text)
72
+
73
+ poster = secici.select_poster("div.category_image img")
74
+ title = secici.select_text("h1.title-border")
75
+ description = secici.select_direct_text("div#icerikcatright")
76
+ tags = secici.select_texts("div.genres a")
77
+ rating = secici.regex_first(r"(?is)IMDB\s*:\s*</span>([0-9.]+)", secici.html)
78
+ year = secici.extract_year("div#icerikcat2")
79
+ raw_actors = secici.meta_value("Oyuncular", container_selector="div#icerikcat2")
80
+ actors = [x.strip() for x in raw_actors.split(",")] if raw_actors else None
118
81
 
119
82
  episodes = []
120
- # Episodes - bolumust div içeren a linklerini bul
121
- for link in secici.css("a"):
122
- bolumust = link.css_first("div.bolumust")
123
- if not bolumust:
124
- continue
125
-
126
- baslik_el = link.css_first("div.baslik")
127
- if not baslik_el:
128
- continue
129
-
130
- ep_name = baslik_el.text(strip=True)
131
- ep_href = link.attrs.get("href")
132
- if not ep_href:
133
- continue
134
-
135
- # Bölüm ismi varsa al
136
- bolumismi_el = link.css_first("div.bolumismi")
137
- ep_name_clean = bolumismi_el.text(strip=True).replace("(", "").replace(")", "").strip() if bolumismi_el else ep_name
138
-
139
- ep_episode_match = re.search(r"(\d+)\. Bölüm", ep_name)
140
- ep_season_match = re.search(r"(\d+)\. Sezon", ep_name)
141
-
142
- ep_episode = ep_episode_match.group(1) if ep_episode_match else None
143
- ep_season = ep_season_match.group(1) if ep_season_match else None
144
-
145
- if ep_episode and ep_season:
146
- episode = Episode(
147
- season = ep_season,
148
- episode = ep_episode,
149
- title = ep_name_clean,
150
- url = self.fix_url(ep_href),
151
- )
152
- episodes.append(episode)
83
+ for link in secici.select("div#scrollbar-container a"):
84
+ href = secici.select_attr(None, "href", link)
85
+ if href:
86
+ name = secici.select_text("div.bolumismi", link).strip("()")
87
+ s, e = secici.extract_season_episode(f"{name} {href}")
88
+ if e:
89
+ episodes.append(Episode(
90
+ season = s or 1,
91
+ episode = e,
92
+ title = name,
93
+ url = self.fix_url(href)
94
+ ))
153
95
 
154
96
  return SeriesInfo(
155
97
  url = url,
@@ -165,66 +107,41 @@ class DiziYou(PluginBase):
165
107
 
166
108
  async def load_links(self, url: str) -> list[ExtractResult]:
167
109
  istek = await self.httpx.get(url)
168
- secici = HTMLParser(istek.text)
169
-
170
- # Title ve episode name - None kontrolü ekle
171
- title_el = secici.css_first("div.title h1")
172
- item_title = title_el.text(strip=True) if title_el else ""
173
-
174
- ep_name_el = secici.css_first("div#bolum-ismi")
175
- ep_name = ep_name_el.text(strip=True) if ep_name_el else ""
176
-
177
- # Player src'den item_id çıkar
178
- player_el = secici.css_first("iframe#diziyouPlayer")
179
- player_src = player_el.attrs.get("src") if player_el else None
180
- if not player_src:
181
- return [] # Player bulunamadıysa boş liste döndür
182
-
183
- item_id = player_src.split("/")[-1].replace(".html", "")
184
-
185
- subtitles = []
186
- stream_urls = []
187
-
188
- for secenek in secici.css("span.diziyouOption"):
189
- opt_id = secenek.attrs.get("id")
190
- op_name = secenek.text(strip=True)
191
-
192
- match opt_id:
193
- case "turkceAltyazili":
194
- subtitles.append(Subtitle(
195
- name = op_name,
196
- url = self.fix_url(f"{self.main_url.replace('www', 'storage')}/subtitles/{item_id}/tr.vtt"),
197
- ))
198
- veri = {
199
- "dil": "Orjinal Dil (TR Altyazı)",
200
- "url": f"{self.main_url.replace('www', 'storage')}/episodes/{item_id}/play.m3u8"
201
- }
202
- if veri not in stream_urls:
203
- stream_urls.append(veri)
204
- case "ingilizceAltyazili":
205
- subtitles.append(Subtitle(
206
- name = op_name,
207
- url = self.fix_url(f"{self.main_url.replace('www', 'storage')}/subtitles/{item_id}/en.vtt"),
208
- ))
209
- veri = {
210
- "dil": "Orjinal Dil (EN Altyazı)",
211
- "url": f"{self.main_url.replace('www', 'storage')}/episodes/{item_id}/play.m3u8"
212
- }
213
- if veri not in stream_urls:
214
- stream_urls.append(veri)
215
- case "turkceDublaj":
216
- stream_urls.append({
217
- "dil": "Türkçe Dublaj",
218
- "url": f"{self.main_url.replace('www', 'storage')}/episodes/{item_id}_tr/play.m3u8"
219
- })
110
+ secici = HTMLHelper(istek.text)
111
+
112
+ # Player iframe'inden ID'yi yakala
113
+ iframe_src = secici.select_attr("iframe#diziyouPlayer", "src") or secici.select_attr("iframe[src*='/player/']", "src")
114
+ if not iframe_src:
115
+ return []
116
+
117
+ item_id = iframe_src.split("/")[-1].replace(".html", "")
118
+ base_storage = self.main_url.replace("www", "storage")
119
+
120
+ subtitles = []
121
+ for sub in [("turkceAltyazili", "tr", "Türkçe"), ("ingilizceAltyazili", "en", "İngilizce")]:
122
+ if secici.select_first(f"span#{sub[0]}"):
123
+ subtitles.append(Subtitle(
124
+ name = f"{sub[2]} Altyazı",
125
+ url = f"{base_storage}/subtitles/{item_id}/{sub[1]}.vtt"
126
+ ))
220
127
 
221
128
  results = []
222
- for stream in stream_urls:
129
+ # Altyazılı Seçenek (Eğer varsa)
130
+ if secici.select_first("span#turkceAltyazili") or secici.select_first("span#ingilizceAltyazili"):
223
131
  results.append(ExtractResult(
224
- url = stream.get("url"),
225
- name = f"{stream.get('dil')}",
132
+ name = "Altyazılı",
133
+ url = f"{base_storage}/episodes/{item_id}/play.m3u8",
226
134
  referer = url,
227
135
  subtitles = subtitles
228
136
  ))
229
137
 
230
- return results
138
+ # Dublaj Seçeneği (Eğer varsa)
139
+ if secici.select_first("span#turkceDublaj"):
140
+ results.append(ExtractResult(
141
+ name = "Türkçe Dublaj",
142
+ url = f"{base_storage}/episodes/{item_id}_tr/play.m3u8",
143
+ referer = url,
144
+ subtitles = subtitles
145
+ ))
146
+
147
+ return results
@@ -1,11 +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
5
- from json import loads
6
- from urllib.parse import urlparse, urlunparse
7
- from Crypto.Cipher import AES
8
- from base64 import b64decode
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult, HTMLHelper
4
+ from json import loads
5
+ from urllib.parse import urlparse, urlunparse
6
+ from Crypto.Cipher import AES
7
+ from base64 import b64decode
9
8
 
10
9
  class Dizilla(PluginBase):
11
10
  name = "Dizilla"
@@ -45,51 +44,32 @@ class Dizilla(PluginBase):
45
44
  category = category,
46
45
  title = veri.get("original_title"),
47
46
  url = self.fix_url(f"{self.main_url}/{veri.get('used_slug')}"),
48
- poster = self.fix_url(veri.get("object_poster_url")),
47
+ poster = self.fix_poster_url(self.fix_url(veri.get("poster_url"))),
49
48
  )
50
49
  for veri in veriler
51
50
  ])
52
51
  else:
53
52
  istek = await self.httpx.get(url.replace("SAYFA", str(page)))
54
- secici = HTMLParser(istek.text)
53
+ secici = HTMLHelper(istek.text)
55
54
 
56
- for veri in secici.css("div.tab-content > div.grid a"):
57
- h2_el = veri.css_first("h2")
58
- name = h2_el.text(strip=True) if h2_el else None
59
-
60
- # opacity-80 div'den episode bilgisi - normalize-space yerine doğrudan text
61
- opacity_el = veri.css_first("div[class*='opacity-80']")
62
- ep_name = opacity_el.text(strip=True) if opacity_el else None
63
- 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:
64
60
  continue
65
61
 
66
- ep_name = ep_name.replace(". Sezon", "x").replace(". Bölüm", "").replace("x ", "x")
67
- title = f"{name} - {ep_name}"
68
-
69
- href = veri.attrs.get("href")
62
+ # Detay sayfasından poster vb. bilgileri al
70
63
  ep_req = await self.httpx.get(self.fix_url(href))
71
- ep_secici = HTMLParser(ep_req.text)
72
-
73
- # nav li'leri alıp 3. elemana erişme (nth-of-type yerine)
74
- nav_lis = ep_secici.css("nav li")
75
- if len(nav_lis) >= 3:
76
- link_el = nav_lis[2].css_first("a")
77
- href = link_el.attrs.get("href") if link_el else None
78
- else:
79
- href = None
80
-
81
- poster_el = ep_secici.css_first("img.imgt")
82
- poster = poster_el.attrs.get("src") if poster_el else None
83
-
84
- if href:
85
- ana_sayfa.append(
86
- MainPageResult(
87
- category = category,
88
- title = title,
89
- url = self.fix_url(href),
90
- poster = self.fix_url(poster) if poster else None
91
- )
92
- )
64
+ ep_secici = HTMLHelper(ep_req.text)
65
+ poster = ep_secici.select_poster('img.imgt') or ep_secici.select_poster('img')
66
+
67
+ ana_sayfa.append(MainPageResult(
68
+ category = category,
69
+ title = title,
70
+ url = self.fix_url(href),
71
+ poster = self.fix_url(poster)
72
+ ))
93
73
 
94
74
  return ana_sayfa
95
75
 
@@ -114,6 +94,21 @@ class Dizilla(PluginBase):
114
94
  # JSON decode
115
95
  return loads(decrypted.decode("utf-8"))
116
96
 
97
+ def fix_poster_url(self, url: str) -> str:
98
+ """AMP CDN URL'lerini düzelt."""
99
+ if not url:
100
+ return url
101
+ # AMP CDN URL'lerini orijinal URL'ye çevir
102
+ # https://images-macellan-online.cdn.ampproject.org/i/s/images.macellan.online/...
103
+ # -> https://images.macellan.online/...
104
+ if "cdn.ampproject.org" in url:
105
+ # /i/s/ veya /ii/s/ gibi AMP prefix'lerinden sonraki kısmı al
106
+ helper = HTMLHelper(url)
107
+ match = helper.regex_first(r"cdn\.ampproject\.org/[^/]+/s/(.+)$")
108
+ if match:
109
+ return f"https://{match}"
110
+ return url
111
+
117
112
  async def search(self, query: str) -> list[SearchResult]:
118
113
  arama_istek = await self.httpx.post(f"{self.main_url}/api/bg/searchcontent?searchterm={query}")
119
114
  decrypted = await self.decrypt_response(arama_istek.json().get("response"))
@@ -123,7 +118,7 @@ class Dizilla(PluginBase):
123
118
  SearchResult(
124
119
  title = veri.get("object_name"),
125
120
  url = self.fix_url(f"{self.main_url}/{veri.get('used_slug')}"),
126
- poster = self.fix_url(veri.get("object_poster_url")),
121
+ poster = self.fix_poster_url(self.fix_url(veri.get("object_poster_url"))),
127
122
  )
128
123
  for veri in arama_veri
129
124
  ]
@@ -140,73 +135,45 @@ class Dizilla(PluginBase):
140
135
 
141
136
  async def load_item(self, url: str) -> SeriesInfo:
142
137
  istek = await self.httpx.get(url)
143
- secici = HTMLParser(istek.text)
138
+ secici = HTMLHelper(istek.text)
144
139
 
145
- title = secici.css_first("div.poster.poster h2")
146
- title = title.text(strip=True) if title else None
147
- if not title:
140
+ next_data_text = secici.select_text("script#__NEXT_DATA__")
141
+ if not next_data_text:
148
142
  return None
149
143
 
150
- poster_el = secici.css_first("div.w-full.page-top.relative img")
151
- poster = self.fix_url(poster_el.attrs.get("src")) if poster_el else None
152
-
153
- # Year extraction (Kotlin: [1] index for w-fit min-w-fit)
154
- info_boxes = secici.css("div.w-fit.min-w-fit")
155
- year = None
156
- if len(info_boxes) > 1:
157
- year_el = info_boxes[1].css_first("span.text-sm.opacity-60")
158
- if year_el:
159
- year_text = year_el.text(strip=True)
160
- year = year_text.split(" ")[-1] if " " in year_text else year_text
161
-
162
- description_el = secici.css_first("div.mt-2.text-sm")
163
- description = description_el.text(strip=True) if description_el else None
164
-
165
- tags_el = secici.css_first("div.poster.poster h3")
166
- tags = [t.strip() for t in tags_el.text(strip=True).split(",")] if tags_el else []
167
-
168
- actors = [h5.text(strip=True) for h5 in secici.css("div.global-box h5")]
169
-
170
- episodeses = []
171
- # Seasons links iteration
172
- season_links = secici.css("div.flex.items-center.flex-wrap.gap-2.mb-4 a")
173
- for sezon in season_links:
174
- sezon_href = self.fix_url(sezon.attrs.get("href"))
175
- sezon_req = await self.httpx.get(sezon_href)
176
-
177
- season_num = None
178
- try:
179
- # URL'den sezon numarasını çek: ...-sezon-X
180
- season_match = re.search(r"sezon-(\d+)", sezon_href)
181
- if season_match:
182
- season_num = int(season_match.group(1))
183
- except:
184
- pass
185
-
186
- sezon_secici = HTMLParser(sezon_req.text)
187
- for bolum in sezon_secici.css("div.episodes div.cursor-pointer"):
188
- # Kotlin: bolum.select("a").last()
189
- links = bolum.css("a")
190
- if not links:
191
- continue
192
-
193
- ep_link = links[-1]
194
- ep_name = ep_link.text(strip=True)
195
- ep_href = self.fix_url(ep_link.attrs.get("href"))
196
-
197
- # Episode number (first link's text usually)
198
- ep_num = None
199
- try:
200
- ep_num = int(links[0].text(strip=True))
201
- except:
202
- pass
203
-
204
- episodeses.append(Episode(
205
- season = season_num,
206
- episode = ep_num,
207
- title = ep_name,
208
- url = ep_href
209
- ))
144
+ next_data = loads(next_data_text)
145
+ secure_data = next_data.get("props", {}).get("pageProps", {}).get("secureData")
146
+ if not secure_data:
147
+ return None
148
+
149
+ decrypted = await self.decrypt_response(secure_data)
150
+ content = decrypted.get("contentItem", {})
151
+ if not content:
152
+ return None
153
+
154
+ title = content.get("original_title") or content.get("used_title")
155
+ description = content.get("description") or content.get("used_description")
156
+ rating = content.get("imdb_point") or content.get("local_vote_avg")
157
+ year = content.get("release_year")
158
+ poster = self.fix_poster_url(self.fix_url(content.get("back_url") or content.get("poster_url")))
159
+
160
+ tags = [cat.get("name") for cat in decrypted.get("RelatedResults", {}).get("getSerieCategoriesById", {}).get("result", [])]
161
+ actors = [cast.get("name") for cast in decrypted.get("RelatedResults", {}).get("getSerieCastsById", {}).get("result", [])]
162
+
163
+ episodes = []
164
+ for season in decrypted.get("RelatedResults", {}).get("getSerieSeasonAndEpisodes", {}).get("result", []):
165
+ s_no = season.get("season_no")
166
+ for ep in season.get("episodes", []):
167
+ e_no = ep.get("episode_no")
168
+ slug = ep.get("used_slug")
169
+ name = ep.get("episode_text") or ""
170
+ if not any(e.season == s_no and e.episode == e_no for e in episodes):
171
+ episodes.append(Episode(
172
+ season = s_no,
173
+ episode = e_no,
174
+ title = name,
175
+ url = self.fix_url(f"{self.main_url}/{slug}")
176
+ ))
210
177
 
211
178
  return SeriesInfo(
212
179
  url = url,
@@ -214,20 +181,21 @@ class Dizilla(PluginBase):
214
181
  title = title,
215
182
  description = description,
216
183
  tags = tags,
217
- year = str(year) if year else None,
218
- episodes = episodeses,
184
+ rating = rating,
185
+ year = year,
186
+ episodes = episodes,
219
187
  actors = actors
220
188
  )
221
189
 
222
190
  async def load_links(self, url: str) -> list[ExtractResult]:
223
- istek = await self.httpx.get(url)
224
- secici = HTMLParser(istek.text)
191
+ istek = await self.httpx.get(url)
192
+ secici = HTMLHelper(istek.text)
225
193
 
226
- next_data_el = secici.css_first("script#__NEXT_DATA__")
227
- if not next_data_el:
194
+ next_data_text = secici.select_text("script#__NEXT_DATA__")
195
+ if not next_data_text:
228
196
  return []
229
197
 
230
- next_data = loads(next_data_el.text(strip=True))
198
+ next_data = loads(next_data_text)
231
199
  secure_data = next_data.get("props", {}).get("pageProps", {}).get("secureData", {})
232
200
  decrypted = await self.decrypt_response(secure_data)
233
201
  results = decrypted.get("RelatedResults", {}).get("getEpisodeSources", {}).get("result", [])
@@ -235,22 +203,21 @@ class Dizilla(PluginBase):
235
203
  if not results:
236
204
  return []
237
205
 
238
- # Get first source (matching Kotlin)
239
- first_result = results[0]
206
+ first_result = results[0]
240
207
  source_content = str(first_result.get("source_content", ""))
241
-
242
- # Clean the source_content string (matching Kotlin: .replace("\"", "").replace("\\", ""))
208
+
243
209
  cleaned_source = source_content.replace('"', '').replace('\\', '')
244
-
245
- # Parse cleaned HTML
246
- iframe_el = HTMLParser(cleaned_source).css_first("iframe")
247
- iframe_src = iframe_el.attrs.get("src") if iframe_el else None
248
-
249
- # Referer check (matching Kotlin: loadExtractor(iframe, "${mainUrl}/", ...))
210
+
211
+ iframe_secici = HTMLHelper(cleaned_source)
212
+ iframe_src = iframe_secici.select_attr("iframe", "src")
213
+
250
214
  iframe_url = self.fix_url(iframe_src) if iframe_src else None
251
-
215
+
252
216
  if not iframe_url:
253
217
  return []
254
218
 
255
219
  data = await self.extract(iframe_url, referer=f"{self.main_url}/", prefix=first_result.get('language_name', 'Unknown'))
256
- return [data] if data else []
220
+ if not data:
221
+ return []
222
+
223
+ return data if isinstance(data, list) else [data]