KekikStream 1.7.2__py3-none-any.whl → 1.7.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 (31) hide show
  1. KekikStream/Extractors/DzenRu.py +39 -0
  2. KekikStream/Extractors/ExPlay.py +54 -0
  3. KekikStream/Extractors/FirePlayer.py +61 -0
  4. KekikStream/Extractors/HDPlayerSystem.py +42 -0
  5. KekikStream/Extractors/JetTv.py +47 -0
  6. KekikStream/Extractors/MixTiger.py +61 -0
  7. KekikStream/Extractors/PlayerFilmIzle.py +63 -0
  8. KekikStream/Extractors/SetPlay.py +58 -0
  9. KekikStream/Extractors/SetPrime.py +46 -0
  10. KekikStream/Extractors/TurkeyPlayer.py +35 -0
  11. KekikStream/Extractors/VidHide.py +73 -0
  12. KekikStream/Extractors/VidPapi.py +90 -0
  13. KekikStream/Extractors/VidStack.py +75 -0
  14. KekikStream/Extractors/YildizKisaFilm.py +42 -0
  15. KekikStream/Plugins/Dizilla.py +5 -1
  16. KekikStream/Plugins/FilmBip.py +145 -0
  17. KekikStream/Plugins/FullHDFilm.py +164 -0
  18. KekikStream/Plugins/JetFilmizle.py +10 -3
  19. KekikStream/Plugins/KultFilmler.py +219 -0
  20. KekikStream/Plugins/RoketDizi.py +204 -0
  21. KekikStream/Plugins/SelcukFlix.py +216 -0
  22. KekikStream/Plugins/SezonlukDizi.py +3 -0
  23. KekikStream/Plugins/Sinefy.py +214 -0
  24. KekikStream/Plugins/Sinezy.py +99 -0
  25. KekikStream/Plugins/SuperFilmGeldi.py +121 -0
  26. {kekikstream-1.7.2.dist-info → kekikstream-1.7.3.dist-info}/METADATA +1 -1
  27. {kekikstream-1.7.2.dist-info → kekikstream-1.7.3.dist-info}/RECORD +31 -9
  28. {kekikstream-1.7.2.dist-info → kekikstream-1.7.3.dist-info}/WHEEL +0 -0
  29. {kekikstream-1.7.2.dist-info → kekikstream-1.7.3.dist-info}/entry_points.txt +0 -0
  30. {kekikstream-1.7.2.dist-info → kekikstream-1.7.3.dist-info}/licenses/LICENSE +0 -0
  31. {kekikstream-1.7.2.dist-info → kekikstream-1.7.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,219 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult, Subtitle
4
+ from parsel import Selector
5
+ import re, base64
6
+
7
+ class KultFilmler(PluginBase):
8
+ name = "KultFilmler"
9
+ language = "tr"
10
+ main_url = "https://kultfilmler.net"
11
+ favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
12
+ description = "Kült film ve dizi izleme sitesi."
13
+
14
+ main_page = {
15
+ f"{main_url}/category/aile-filmleri-izle" : "Aile",
16
+ f"{main_url}/category/aksiyon-filmleri-izle" : "Aksiyon",
17
+ f"{main_url}/category/animasyon-filmleri-izle" : "Animasyon",
18
+ f"{main_url}/category/belgesel-izle" : "Belgesel",
19
+ f"{main_url}/category/bilim-kurgu-filmleri-izle": "Bilim Kurgu",
20
+ f"{main_url}/category/biyografi-filmleri-izle" : "Biyografi",
21
+ f"{main_url}/category/dram-filmleri-izle" : "Dram",
22
+ f"{main_url}/category/fantastik-filmleri-izle" : "Fantastik",
23
+ f"{main_url}/category/gerilim-filmleri-izle" : "Gerilim",
24
+ f"{main_url}/category/gizem-filmleri-izle" : "Gizem",
25
+ f"{main_url}/category/kara-filmleri-izle" : "Kara Film",
26
+ f"{main_url}/category/kisa-film-izle" : "Kısa Metraj",
27
+ f"{main_url}/category/komedi-filmleri-izle" : "Komedi",
28
+ f"{main_url}/category/korku-filmleri-izle" : "Korku",
29
+ f"{main_url}/category/macera-filmleri-izle" : "Macera",
30
+ f"{main_url}/category/muzik-filmleri-izle" : "Müzik",
31
+ f"{main_url}/category/polisiye-filmleri-izle" : "Polisiye",
32
+ f"{main_url}/category/romantik-filmleri-izle" : "Romantik",
33
+ f"{main_url}/category/savas-filmleri-izle" : "Savaş",
34
+ f"{main_url}/category/suc-filmleri-izle" : "Suç",
35
+ f"{main_url}/category/tarih-filmleri-izle" : "Tarih",
36
+ f"{main_url}/category/yerli-filmleri-izle" : "Yerli",
37
+ }
38
+
39
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
40
+ istek = await self.cffi.get(url)
41
+ secici = Selector(istek.text)
42
+
43
+ results = []
44
+ for veri in secici.css("div.col-md-12 div.movie-box"):
45
+ title = veri.css("div.img img::attr(alt)").get()
46
+ href = self.fix_url(veri.css("a::attr(href)").get())
47
+ poster = self.fix_url(veri.css("div.img img::attr(src)").get())
48
+
49
+ if title and href:
50
+ results.append(MainPageResult(
51
+ category = category,
52
+ title = title,
53
+ url = href,
54
+ poster = poster,
55
+ ))
56
+
57
+ return results
58
+
59
+ async def search(self, query: str) -> list[SearchResult]:
60
+ istek = await self.cffi.get(f"{self.main_url}?s={query}")
61
+ secici = Selector(istek.text)
62
+
63
+ results = []
64
+ for veri in secici.css("div.movie-box"):
65
+ title = veri.css("div.img img::attr(alt)").get()
66
+ href = self.fix_url(veri.css("a::attr(href)").get())
67
+ poster = self.fix_url(veri.css("div.img img::attr(src)").get())
68
+
69
+ if title and href:
70
+ results.append(SearchResult(
71
+ title = title,
72
+ url = href,
73
+ poster = poster,
74
+ ))
75
+
76
+ return results
77
+
78
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
79
+ istek = await self.cffi.get(url)
80
+ secici = Selector(istek.text)
81
+
82
+ title = secici.css("div.film-bilgileri img::attr(alt)").get() or secici.css("[property='og:title']::attr(content)").get()
83
+ poster = self.fix_url(secici.css("[property='og:image']::attr(content)").get())
84
+ description = secici.css("div.description::text").get()
85
+ tags = secici.css("ul.post-categories a::text").getall()
86
+ # HTML analizine göre güncellenen alanlar
87
+ year = secici.css("li.release span a::text").get()
88
+ duration = secici.css("li.time span::text").re_first(r"(\d+)")
89
+ rating = secici.css("div.imdb-count::text").get()
90
+ actors = secici.css("div.actors a::text").getall()
91
+ if rating:
92
+ rating = rating.strip()
93
+
94
+ # Dizi mi kontrol et
95
+ if "/dizi/" in url:
96
+ episodes = []
97
+ for bolum in secici.css("div.episode-box"):
98
+ ep_href = self.fix_url(bolum.css("div.name a::attr(href)").get())
99
+ ssn_detail = bolum.css("span.episodetitle::text").get() or ""
100
+ ep_detail = bolum.css("span.episodetitle b::text").get() or ""
101
+ ep_name = f"{ssn_detail} - {ep_detail}"
102
+
103
+ if ep_href:
104
+ ep_season = re.search(r"(\d+)\.", ssn_detail)
105
+ ep_episode = re.search(r"(\d+)\.", ep_detail)
106
+
107
+ episodes.append(Episode(
108
+ season = int(ep_season[1]) if ep_season else 1,
109
+ episode = int(ep_episode[1]) if ep_episode else 1,
110
+ title = ep_name.strip(" -"),
111
+ url = ep_href,
112
+ ))
113
+
114
+ return SeriesInfo(
115
+ url = url,
116
+ poster = poster,
117
+ title = self.clean_title(title) if title else "",
118
+ description = description,
119
+ tags = tags,
120
+ year = year,
121
+ actors = actors,
122
+ rating = rating,
123
+ episodes = episodes,
124
+ )
125
+
126
+ return MovieInfo(
127
+ url = url,
128
+ poster = poster,
129
+ title = self.clean_title(title) if title else "",
130
+ description = description,
131
+ tags = tags,
132
+ year = year,
133
+ rating = rating,
134
+ actors = actors,
135
+ duration = int(duration) if duration else None,
136
+ )
137
+
138
+ def _get_iframe(self, source_code: str) -> str:
139
+ """Base64 kodlu iframe'i çözümle"""
140
+ atob_match = re.search(r"PHA\+[0-9a-zA-Z+/=]*", source_code)
141
+ if not atob_match:
142
+ return ""
143
+
144
+ atob = atob_match.group()
145
+
146
+ # Padding düzelt
147
+ padding = 4 - len(atob) % 4
148
+ if padding < 4:
149
+ atob = atob + "=" * padding
150
+
151
+ try:
152
+ decoded = base64.b64decode(atob).decode("utf-8")
153
+ secici = Selector(text=decoded)
154
+ return self.fix_url(secici.css("iframe::attr(src)").get()) or ""
155
+ except Exception:
156
+ return ""
157
+
158
+ def _extract_subtitle_url(self, source_code: str) -> str | None:
159
+ """Altyazı URL'sini çıkar"""
160
+ match = re.search(r"(https?://[^\s\"]+\.srt)", source_code)
161
+ return match[1] if match else None
162
+
163
+ async def load_links(self, url: str) -> list[dict]:
164
+ istek = await self.cffi.get(url)
165
+ secici = Selector(istek.text)
166
+
167
+ iframes = set()
168
+
169
+ # Ana iframe
170
+ main_frame = self._get_iframe(istek.text)
171
+ if main_frame:
172
+ iframes.add(main_frame)
173
+
174
+ # Alternatif player'lar
175
+ for player in secici.css("div.container#player"):
176
+ alt_iframe = self.fix_url(player.css("iframe::attr(src)").get())
177
+ if alt_iframe:
178
+ alt_istek = await self.cffi.get(alt_iframe)
179
+ alt_frame = self._get_iframe(alt_istek.text)
180
+ if alt_frame:
181
+ iframes.add(alt_frame)
182
+
183
+ results = []
184
+
185
+ for iframe in iframes:
186
+ subtitles = []
187
+
188
+ # VidMoly özel işleme
189
+ if "vidmoly" in iframe:
190
+ headers = {
191
+ "User-Agent" : "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36",
192
+ "Sec-Fetch-Dest" : "iframe"
193
+ }
194
+ iframe_istek = await self.cffi.get(iframe, headers=headers)
195
+ m3u_match = re.search(r'file:"([^"]+)"', iframe_istek.text)
196
+
197
+ if m3u_match:
198
+ results.append({
199
+ "name" : "VidMoly",
200
+ "url" : m3u_match[1],
201
+ "referer" : self.main_url,
202
+ "subtitles" : []
203
+ })
204
+ continue
205
+
206
+ # Altyazı çıkar
207
+ subtitle_url = self._extract_subtitle_url(url)
208
+ if subtitle_url:
209
+ subtitles.append(Subtitle(name="Türkçe", url=subtitle_url))
210
+
211
+ extractor = self.ex_manager.find_extractor(iframe)
212
+ results.append({
213
+ "name" : extractor.name if extractor else "Player",
214
+ "url" : iframe,
215
+ "referer" : f"{self.main_url}/",
216
+ "subtitles" : subtitles
217
+ })
218
+
219
+ return results
@@ -0,0 +1,204 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, MovieInfo
4
+ from parsel import Selector
5
+ import re, base64, json, urllib.parse
6
+
7
+ class RoketDizi(PluginBase):
8
+ name = "RoketDizi"
9
+ lang = "tr"
10
+ main_url = "https://flatscher.net"
11
+
12
+ main_page = {
13
+ "dizi/tur/aksiyon" : "Aksiyon",
14
+ "dizi/tur/bilim-kurgu" : "Bilim Kurgu",
15
+ "dizi/tur/gerilim" : "Gerilim",
16
+ "dizi/tur/fantastik" : "Fantastik",
17
+ "dizi/tur/komedi" : "Komedi",
18
+ "dizi/tur/korku" : "Korku",
19
+ "dizi/tur/macera" : "Macera",
20
+ "dizi/tur/suc" : "Suç"
21
+ }
22
+
23
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
24
+ full_url = f"{self.main_url}/{url}?&page={page}"
25
+ resp = await self.cffi.get(full_url)
26
+ sel = Selector(resp.text)
27
+
28
+ results = []
29
+
30
+ for item in sel.css("div.w-full.p-4 span.bg-\\[\\#232323\\]"):
31
+ title = item.css("span.font-normal.line-clamp-1::text").get()
32
+ href = item.css("a::attr(href)").get()
33
+ poster= item.css("img::attr(src)").get()
34
+
35
+ if title and href:
36
+ results.append(MainPageResult(
37
+ category=category,
38
+ title=title,
39
+ url=self.fix_url(href),
40
+ poster=self.fix_url(poster)
41
+ ))
42
+ return results
43
+
44
+ async def get_domain(self):
45
+ try:
46
+ domain_list = await self.cffi.get("https://raw.githubusercontent.com/Kraptor123/domainListesi/refs/heads/main/eklenti_domainleri.txt")
47
+ if domain_list.status_code == 200:
48
+ for line in domain_list.text.split("|"):
49
+ if line.strip().startswith("RoketDizi"):
50
+ domain = line.split(":")[-1].strip()
51
+ if "http" not in domain:
52
+ domain = f"https://{domain}"
53
+ return domain
54
+ except Exception:
55
+ pass
56
+ return self.main_url
57
+
58
+ async def search(self, query: str) -> list[SearchResult]:
59
+ current_domain = await self.get_domain()
60
+
61
+ # Get Cookies and Keys
62
+ main_req = await self.cffi.get(current_domain)
63
+ sel = Selector(main_req.text)
64
+
65
+ c_key = sel.css("input[name='cKey']::attr(value)").get()
66
+ c_value = sel.css("input[name='cValue']::attr(value)").get()
67
+
68
+ post_url = f"{current_domain}/api/bg/searchContent?searchterm={query}"
69
+
70
+ headers = {
71
+ "Accept": "application/json, text/javascript, */*; q=0.01",
72
+ "X-Requested-With": "XMLHttpRequest",
73
+ "Referer": f"{current_domain}/",
74
+ "CNT": "vakTR"
75
+ }
76
+
77
+ data = {}
78
+ if c_key and c_value:
79
+ data = {"cKey": c_key, "cValue": c_value}
80
+
81
+ search_req = await self.cffi.post(post_url, data=data, headers=headers)
82
+
83
+ try:
84
+ resp_json = search_req.json()
85
+ if not resp_json.get("state"):
86
+ return []
87
+
88
+ html_content = resp_json.get("html", "").strip()
89
+ sel_results = Selector(html_content)
90
+
91
+ results = []
92
+ items = re.findall(r'<a href="([^"]+)".*?data-srcset="([^"]+).*?<span class="text-white">([^<]+)', html_content, re.DOTALL)
93
+
94
+ for href, poster, title in items:
95
+ results.append(SearchResult(
96
+ title=title.strip(),
97
+ url=self.fix_url(href.strip(), current_domain),
98
+ poster=self.fix_url(poster.strip(), current_domain)
99
+ ))
100
+
101
+ return results
102
+
103
+ except Exception:
104
+ return []
105
+
106
+ async def load_item(self, url: str) -> SeriesInfo:
107
+ # Note: Handling both Movie and Series logic in one, returning SeriesInfo generally or MovieInfo
108
+ resp = await self.cffi.get(url)
109
+ sel = Selector(resp.text)
110
+
111
+ title = sel.css("h1.text-white::text").get()
112
+ poster = sel.css("div.w-full.page-top img::attr(src)").get()
113
+ description = sel.css("div.mt-2.text-sm::text").get()
114
+
115
+ year = None # Implement if critical
116
+
117
+ tags = sel.css("h3.text-white.opacity-60::text").get()
118
+ if tags:
119
+ tags = [t.strip() for t in tags.split(",")]
120
+
121
+ rating = sel.css("div.flex.items-center span.text-white.text-sm::text").get()
122
+ actors = sel.css("div.global-box h5::text").getall()
123
+
124
+ # Check urls for episodes
125
+ all_urls = re.findall(r'"url":"([^"]*)"', resp.text)
126
+ is_series = any("bolum-" in u for u in all_urls)
127
+
128
+ episodes = []
129
+ if is_series:
130
+ seen_eps = set()
131
+ for u in all_urls:
132
+ if "bolum" in u and u not in seen_eps:
133
+ seen_eps.add(u)
134
+ season_match = re.search(r'/sezon-(\d+)', u)
135
+ ep_match = re.search(r'/bolum-(\d+)', u)
136
+
137
+ season = int(season_match.group(1)) if season_match else 1
138
+ episode_num = int(ep_match.group(1)) if ep_match else 1
139
+
140
+ episodes.append(Episode(
141
+ season=season,
142
+ episode=episode_num,
143
+ title=f"{season}. Sezon {episode_num}. Bölüm", # Placeholder title
144
+ url=self.fix_url(u, self.get_domain_sync(url))
145
+ ))
146
+
147
+ return SeriesInfo(
148
+ title=title,
149
+ url=url,
150
+ poster=self.fix_url(poster),
151
+ description=description,
152
+ tags=tags,
153
+ rating=rating,
154
+ actors=actors,
155
+ episodes=episodes,
156
+ year=year
157
+ )
158
+
159
+ async def load_links(self, url: str) -> list[dict]:
160
+ resp = await self.cffi.get(url)
161
+ sel = Selector(resp.text)
162
+
163
+ next_data = sel.css("script#__NEXT_DATA__::text").get()
164
+ if not next_data:
165
+ return []
166
+
167
+ try:
168
+ data = json.loads(next_data)
169
+ secure_data = data["props"]["pageProps"]["secureData"]
170
+ decoded = base64.b64decode(secure_data).decode('utf-8')
171
+
172
+ results = []
173
+ matches = re.findall(r'iframe src=\\"([^"]*)\\"', decoded)
174
+ for m in matches:
175
+ iframe_url = m.replace('\\', '')
176
+ if "http" not in iframe_url:
177
+ if iframe_url.startswith("//"):
178
+ iframe_url = "https:" + iframe_url
179
+ else:
180
+ iframe_url = "https://" + iframe_url # fallback
181
+
182
+ # Check extractor
183
+ extractor = self.ex_manager.find_extractor(iframe_url)
184
+ name = extractor.name if extractor else "Iframe"
185
+
186
+ results.append({
187
+ "url": iframe_url,
188
+ "name": name
189
+ })
190
+
191
+ return results
192
+
193
+ except Exception:
194
+ return []
195
+
196
+ def fix_url(self, url: str, domain:str=None) -> str:
197
+ if not url: return ""
198
+ if url.startswith("http"): return url
199
+ base = domain or self.main_url
200
+ return f"https:{url}" if url.startswith("//") else urllib.parse.urljoin(base, url)
201
+
202
+ def get_domain_sync(self, url:str):
203
+ parsed = urllib.parse.urlparse(url)
204
+ return f"{parsed.scheme}://{parsed.netloc}"
@@ -0,0 +1,216 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode
4
+ from parsel import Selector
5
+ import re, base64, json, urllib.parse
6
+
7
+ class SelcukFlix(PluginBase):
8
+ name = "SelcukFlix"
9
+ main_url = "https://selcukflix.net"
10
+ lang = "tr"
11
+
12
+ main_page = {
13
+ "tum-bolumler" : "Yeni Eklenen Bölümler"
14
+ }
15
+
16
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
17
+ full_url = f"{self.main_url}/{url}"
18
+ resp = await self.cffi.get(full_url)
19
+ sel = Selector(resp.text)
20
+
21
+ results = []
22
+ if "tum-bolumler" in url:
23
+ for item in sel.css("div.col-span-3 a"):
24
+ name = item.css("h2::text").get()
25
+ ep_info = item.css("div.opacity-80::text").get()
26
+ href = item.css("::attr(href)").get()
27
+ poster = item.css("div.image img::attr(src)").get()
28
+
29
+ if name and href:
30
+ title = f"{name} - {ep_info}" if ep_info else name
31
+ final_url = self.fix_url(href)
32
+ if "/dizi/" in final_url and "/sezon-" in final_url:
33
+ final_url = final_url.split("/sezon-")[0]
34
+
35
+ results.append(MainPageResult(
36
+ category=category,
37
+ title=title,
38
+ url=final_url,
39
+ poster=self.fix_url(poster)
40
+ ))
41
+
42
+ return results
43
+
44
+ async def search(self, query: str) -> list[SearchResult]:
45
+ search_url = f"{self.main_url}/api/bg/searchcontent?searchterm={query}"
46
+
47
+ headers = {
48
+ "Accept": "application/json, text/plain, */*",
49
+ "X-Requested-With": "XMLHttpRequest",
50
+ "Referer": f"{self.main_url}/"
51
+ }
52
+
53
+ post_resp = await self.cffi.post(search_url, headers=headers)
54
+
55
+ try:
56
+ resp_json = post_resp.json()
57
+ response_data = resp_json.get("response")
58
+
59
+ raw_data = base64.b64decode(response_data)
60
+ try:
61
+ decoded_str = raw_data.decode('utf-8')
62
+ except UnicodeDecodeError:
63
+ decoded_str = raw_data.decode('iso-8859-1').encode('utf-8').decode('utf-8')
64
+
65
+ search_data = json.loads(decoded_str)
66
+
67
+ results = []
68
+ for item in search_data.get("result", []):
69
+ title = item.get("title")
70
+ slug = item.get("slug")
71
+ poster = item.get("poster")
72
+ if poster:
73
+ poster = self.clean_image_url(poster)
74
+
75
+ if slug and "/seri-filmler/" not in slug:
76
+ results.append(SearchResult(
77
+ title=title,
78
+ url=self.fix_url(slug),
79
+ poster=poster
80
+ ))
81
+ return results
82
+
83
+ except Exception:
84
+ return []
85
+
86
+ async def load_item(self, url: str) -> SeriesInfo:
87
+ resp = await self.cffi.get(url)
88
+ sel = Selector(resp.text)
89
+
90
+ next_data = sel.css("script#__NEXT_DATA__::text").get()
91
+ if not next_data:
92
+ return None
93
+
94
+ data = json.loads(next_data)
95
+ secure_data = data["props"]["pageProps"]["secureData"]
96
+ raw_data = base64.b64decode(secure_data.replace('"', ''))
97
+ try:
98
+ decoded_str = raw_data.decode('utf-8')
99
+ except UnicodeDecodeError:
100
+ decoded_str = raw_data.decode('iso-8859-1') # .encode('utf-8').decode('utf-8') implied
101
+
102
+ content_details = json.loads(decoded_str)
103
+ item = content_details.get("contentItem", {})
104
+
105
+ title = item.get("original_title") or item.get("originalTitle")
106
+ poster = self.clean_image_url(item.get("poster_url") or item.get("posterUrl"))
107
+ description = item.get("description") or item.get("used_description")
108
+ rating = str(item.get("imdb_point") or item.get("imdbPoint", ""))
109
+
110
+ series_data = content_details.get("relatedData", {}).get("seriesData")
111
+ if not series_data and "RelatedResults" in content_details:
112
+ series_data = content_details["RelatedResults"].get("getSerieSeasonAndEpisodes", {}).get("result")
113
+ if series_data and isinstance(series_data, list):
114
+ pass
115
+
116
+ episodes = []
117
+ if series_data:
118
+ seasons_list = []
119
+ if isinstance(series_data, dict):
120
+ seasons_list = series_data.get("seasons", [])
121
+ elif isinstance(series_data, list):
122
+ seasons_list = series_data
123
+
124
+ for season in seasons_list:
125
+ if not isinstance(season, dict): continue
126
+ s_no = season.get("season_no") or season.get("seasonNo") # Try snake_case too
127
+ ep_list = season.get("episodes", [])
128
+ for ep in ep_list:
129
+ episodes.append(Episode(
130
+ season = s_no,
131
+ episode = ep.get("episode_no") or ep.get("episodeNo"),
132
+ title = ep.get("ep_text") or ep.get("epText"),
133
+ url = self.fix_url(ep.get("used_slug") or ep.get("usedSlug"))
134
+ ))
135
+
136
+ return SeriesInfo(
137
+ title=title,
138
+ url=url,
139
+ poster=poster,
140
+ description=description,
141
+ rating=rating,
142
+ episodes=episodes
143
+ )
144
+
145
+ async def load_links(self, url: str) -> list[dict]:
146
+ resp = await self.cffi.get(url)
147
+ sel = Selector(resp.text)
148
+
149
+ next_data = sel.css("script#__NEXT_DATA__::text").get()
150
+ if not next_data: return []
151
+
152
+ try:
153
+ data = json.loads(next_data)
154
+ secure_data = data["props"]["pageProps"]["secureData"]
155
+ raw_data = base64.b64decode(secure_data.replace('"', ''))
156
+ try:
157
+ decoded_str = raw_data.decode('utf-8')
158
+ except UnicodeDecodeError:
159
+ decoded_str = raw_data.decode('iso-8859-1')
160
+
161
+ content_details = json.loads(decoded_str)
162
+ related_data = content_details.get("relatedData", {})
163
+
164
+ source_content = None
165
+
166
+ # Check if Series (episode) or Movie
167
+ if "/dizi/" in url:
168
+ if related_data.get("episodeSources", {}).get("state"):
169
+ res = related_data["episodeSources"].get("result", [])
170
+ if res:
171
+ source_content = res[0].get("sourceContent")
172
+ else:
173
+ # Movie
174
+ if related_data.get("movieParts", {}).get("state"):
175
+ # Looking for first part source
176
+ movie_parts = related_data["movieParts"].get("result", [])
177
+ if movie_parts:
178
+ first_part_id = movie_parts[0].get("id")
179
+ # RelatedResults -> getMoviePartSourcesById_ID
180
+ rr = content_details.get("RelatedResults", {})
181
+ key = f"getMoviePartSourcesById_{first_part_id}"
182
+ if key in rr:
183
+ res = rr[key].get("result", [])
184
+ if res:
185
+ source_content = res[0].get("source_content")
186
+
187
+ results = []
188
+ if source_content:
189
+ iframe_sel = Selector(source_content)
190
+ iframe_src = iframe_sel.css("iframe::attr(src)").get()
191
+ if iframe_src:
192
+ iframe_src = self.fix_url(iframe_src)
193
+ # Domain replace
194
+ if "sn.dplayer74.site" in iframe_src:
195
+ iframe_src = iframe_src.replace("sn.dplayer74.site", "sn.hotlinger.com")
196
+
197
+ extractor = self.ex_manager.find_extractor(iframe_src)
198
+ results.append({
199
+ "url": iframe_src,
200
+ "name": extractor.name if extractor else "Iframe"
201
+ })
202
+
203
+ return results
204
+
205
+ except Exception:
206
+ return []
207
+
208
+ def clean_image_url(self, url: str) -> str:
209
+ if not url: return None
210
+ url = url.replace("images-macellan-online.cdn.ampproject.org/i/s/", "")
211
+ url = url.replace("file.dizilla.club", "file.macellan.online")
212
+ url = url.replace("images.dizilla.club", "images.macellan.online")
213
+ url = url.replace("images.dizimia4.com", "images.macellan.online")
214
+ url = url.replace("file.dizimia4.com", "file.macellan.online")
215
+ url = url.replace("/f/f/", "/630/910/")
216
+ return self.fix_url(url)
@@ -134,6 +134,9 @@ class SezonlukDizi(PluginBase):
134
134
  secici = Selector(veri_response.text)
135
135
 
136
136
  if iframe := secici.css("iframe::attr(src)").get():
137
+ if "link.asp" in iframe:
138
+ continue
139
+
137
140
  extractor = self.ex_manager.find_extractor(self.fix_url(iframe))
138
141
  results.append({
139
142
  "url" : self.fix_url(iframe),