KekikStream 1.9.9__py3-none-any.whl → 2.0.8__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 (51) hide show
  1. KekikStream/Core/Extractor/ExtractorBase.py +5 -5
  2. KekikStream/Core/Extractor/ExtractorLoader.py +25 -17
  3. KekikStream/Core/Extractor/ExtractorManager.py +1 -1
  4. KekikStream/Core/Extractor/YTDLPCache.py +35 -0
  5. KekikStream/Core/Plugin/PluginBase.py +4 -1
  6. KekikStream/Core/Plugin/PluginLoader.py +11 -7
  7. KekikStream/Core/__init__.py +1 -0
  8. KekikStream/Extractors/CloseLoad.py +1 -1
  9. KekikStream/Extractors/ContentX.py +13 -0
  10. KekikStream/Extractors/DonilasPlay.py +86 -0
  11. KekikStream/Extractors/Odnoklassniki.py +6 -0
  12. KekikStream/Extractors/PeaceMakerst.py +6 -0
  13. KekikStream/Extractors/RapidVid.py +6 -0
  14. KekikStream/Extractors/SetPlay.py +7 -1
  15. KekikStream/Extractors/VCTPlay.py +41 -0
  16. KekikStream/Extractors/VidMoly.py +52 -30
  17. KekikStream/Extractors/YTDLP.py +97 -58
  18. KekikStream/Plugins/BelgeselX.py +204 -0
  19. KekikStream/Plugins/Dizilla.py +22 -14
  20. KekikStream/Plugins/FilmMakinesi.py +3 -1
  21. KekikStream/Plugins/FilmModu.py +6 -2
  22. KekikStream/Plugins/FullHDFilmizlesene.py +1 -1
  23. KekikStream/Plugins/HDFilmCehennemi.py +83 -8
  24. KekikStream/Plugins/JetFilmizle.py +53 -38
  25. KekikStream/Plugins/KultFilmler.py +1 -1
  26. KekikStream/Plugins/RoketDizi.py +17 -24
  27. KekikStream/Plugins/SelcukFlix.py +51 -52
  28. KekikStream/Plugins/SetFilmIzle.py +259 -0
  29. KekikStream/Plugins/SezonlukDizi.py +28 -7
  30. KekikStream/Plugins/Sinefy.py +15 -9
  31. KekikStream/Plugins/SinemaCX.py +3 -7
  32. KekikStream/Plugins/Sinezy.py +16 -1
  33. KekikStream/Plugins/SuperFilmGeldi.py +1 -1
  34. KekikStream/Plugins/UgurFilm.py +1 -1
  35. {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/METADATA +27 -1
  36. kekikstream-2.0.8.dist-info/RECORD +81 -0
  37. KekikStream/Extractors/FourCX.py +0 -7
  38. KekikStream/Extractors/FourPichive.py +0 -7
  39. KekikStream/Extractors/FourPlayRu.py +0 -7
  40. KekikStream/Extractors/HDStreamAble.py +0 -7
  41. KekikStream/Extractors/Hotlinger.py +0 -7
  42. KekikStream/Extractors/OkRuHTTP.py +0 -7
  43. KekikStream/Extractors/OkRuSSL.py +0 -7
  44. KekikStream/Extractors/Pichive.py +0 -7
  45. KekikStream/Extractors/PlayRu.py +0 -7
  46. KekikStream/Extractors/VidMolyMe.py +0 -7
  47. kekikstream-1.9.9.dist-info/RECORD +0 -86
  48. {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/WHEEL +0 -0
  49. {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/entry_points.txt +0 -0
  50. {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/licenses/LICENSE +0 -0
  51. {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ class JetFilmizle(PluginBase):
8
8
  language = "tr"
9
9
  main_url = "https://jetfilmizle.website"
10
10
  favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
11
- description = "Binlerce Film İzleme Seçeneğiyle En İyi Film İzleme Sitesi"
11
+ description = "Film izle, Yerli, Yabancı film izle, Türkçe dublaj, alt yazılı seçenekleriyle ödül almış filmleri Full HD kalitesiyle ve jetfilmizle hızıyla donmadan ücretsizce izleyebilirsiniz."
12
12
 
13
13
  main_page = {
14
14
  f"{main_url}/page/" : "Son Filmler",
@@ -74,8 +74,15 @@ class JetFilmizle(PluginBase):
74
74
  rating_raw = secici.css("section.movie-exp div.imdb_puan span::text").get()
75
75
  rating = rating_raw.strip() if rating_raw else None
76
76
 
77
- year = secici.xpath("//div[@class='yap' and (contains(., 'Vizyon') or contains(., 'Yapım'))]/text()").get()
78
- year = year.strip() if year else None
77
+
78
+ # Year - div.yap içinde 4 haneli sayı ara
79
+ year_div = secici.xpath("//div[@class='yap' and (contains(., 'Vizyon') or contains(., 'Yapım'))]/text()").get()
80
+ year = None
81
+ if year_div:
82
+ year_match = re.search(r'(\d{4})', year_div.strip())
83
+ if year_match:
84
+ year = year_match.group(1)
85
+
79
86
  actors = secici.css("div[itemprop='actor'] a span::text").getall()
80
87
 
81
88
  return MovieInfo(
@@ -93,42 +100,50 @@ class JetFilmizle(PluginBase):
93
100
  istek = await self.httpx.get(url)
94
101
  secici = Selector(istek.text)
95
102
 
96
- iframes = []
97
- if main_iframe := secici.css("div#movie iframe::attr(data-src), div#movie iframe::attr(data), div#movie iframe::attr(src)").get():
98
- iframes.append(self.fix_url(main_iframe))
103
+ results = []
99
104
 
100
- for part in secici.css("div.film_part a"):
101
- part_href = part.attrib.get("href")
102
- if not part_href:
105
+ # 1) Ana iframe'leri kontrol et
106
+ for iframe in secici.css("iframe"):
107
+ src = (iframe.css("::attr(src)").get() or
108
+ iframe.css("::attr(data-src)").get() or
109
+ iframe.css("::attr(data-lazy-src)").get())
110
+
111
+ if src and src != "about:blank":
112
+ iframe_url = self.fix_url(src)
113
+ extractor = self.ex_manager.find_extractor(iframe_url)
114
+ results.append({
115
+ "url": iframe_url,
116
+ "name": extractor.name if extractor else "Ana Player"
117
+ })
118
+
119
+ # 2) Sayfa numaralarından linkleri topla (Fragman hariç)
120
+ page_links = []
121
+ for link in secici.css("a.post-page-numbers"):
122
+ isim = link.css("span::text").get() or ""
123
+ if isim != "Fragman":
124
+ href = link.css("::attr(href)").get()
125
+ if href:
126
+ page_links.append((self.fix_url(href), isim))
127
+
128
+ # 3) Her sayfa linkindeki iframe'leri bul
129
+ for page_url, isim in page_links:
130
+ try:
131
+ page_resp = await self.httpx.get(page_url)
132
+ page_sel = Selector(page_resp.text)
133
+
134
+ for iframe in page_sel.css("div#movie iframe"):
135
+ src = (iframe.css("::attr(src)").get() or
136
+ iframe.css("::attr(data-src)").get() or
137
+ iframe.css("::attr(data-lazy-src)").get())
138
+
139
+ if src and src != "about:blank":
140
+ iframe_url = self.fix_url(src)
141
+ extractor = self.ex_manager.find_extractor(iframe_url)
142
+ results.append({
143
+ "url": iframe_url,
144
+ "name": f"{extractor.name if extractor else 'Player'} | {isim}"
145
+ })
146
+ except Exception:
103
147
  continue
104
148
 
105
- part_istek = await self.httpx.get(part_href)
106
- part_secici = Selector(part_istek.text)
107
-
108
- if iframe := part_secici.css("div#movie iframe::attr(data-src), div#movie iframe::attr(data), div#movie iframe::attr(src)").get():
109
- iframes.append(self.fix_url(iframe))
110
- else:
111
- for link in part_secici.css("div#movie p a"):
112
- if download_link := link.attrib.get("href"):
113
- iframes.append(self.fix_url(download_link))
114
-
115
- processed_iframes = []
116
- for iframe in iframes:
117
- if "jetv.xyz" in iframe:
118
- jetv_istek = await self.httpx.get(iframe)
119
- jetv_secici = Selector(jetv_istek.text)
120
-
121
- if jetv_iframe := jetv_secici.css("iframe::attr(src)").get():
122
- processed_iframes.append(self.fix_url(jetv_iframe))
123
- else:
124
- processed_iframes.append(iframe)
125
-
126
- results = []
127
- for idx, iframe in enumerate(processed_iframes):
128
- extractor = self.ex_manager.find_extractor(iframe)
129
- results.append({
130
- "url" : iframe,
131
- "name" : f"{extractor.name if extractor else f'Player {idx + 1}'}"
132
- })
133
-
134
149
  return results
@@ -9,7 +9,7 @@ class KultFilmler(PluginBase):
9
9
  language = "tr"
10
10
  main_url = "https://kultfilmler.net"
11
11
  favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
12
- description = "Kült film ve dizi izleme sitesi."
12
+ description = "Kült Filmler özenle en iyi filmleri derler ve iyi bir altyazılı film izleme deneyimi sunmayı amaçlar. Reklamsız 1080P Altyazılı Film izle..."
13
13
 
14
14
  main_page = {
15
15
  f"{main_url}/category/aile-filmleri-izle" : "Aile",
@@ -185,6 +185,7 @@ class RoketDizi(PluginBase):
185
185
  # secureData içindeki RelatedResults -> getEpisodeSources -> result dizisini al
186
186
  sources = decoded_json.get("RelatedResults", {}).get("getEpisodeSources", {}).get("result", [])
187
187
 
188
+ seen_urls = set()
188
189
  results = []
189
190
  for source in sources:
190
191
  source_content = source.get("source_content", "")
@@ -195,35 +196,27 @@ class RoketDizi(PluginBase):
195
196
  continue
196
197
 
197
198
  iframe_url = iframe_match.group(1)
198
- if "http" not in iframe_url:
199
- if iframe_url.startswith("//"):
200
- iframe_url = "https:" + iframe_url
201
- else:
202
- iframe_url = "https://" + iframe_url
199
+
200
+ # Fix URL protocol
201
+ if not iframe_url.startswith("http"):
202
+ if iframe_url.startswith("//"):
203
+ iframe_url = "https:" + iframe_url
204
+ else:
205
+ iframe_url = "https://" + iframe_url
206
+
207
+ iframe_url = self.fix_url(iframe_url)
208
+
209
+ # Deduplicate
210
+ if iframe_url in seen_urls:
211
+ continue
212
+ seen_urls.add(iframe_url)
203
213
 
204
214
  # Check extractor
205
215
  extractor = self.ex_manager.find_extractor(iframe_url)
206
- ext_name = extractor.name if extractor else ""
207
-
208
- # Metadata'dan bilgileri al
209
- source_name = source.get("source_name", "")
210
- language_name = source.get("language_name", "")
211
- quality_name = source.get("quality_name", "")
212
-
213
- # İsmi oluştur
214
- name_parts = []
215
- if source_name:
216
- name_parts.append(source_name)
217
- if ext_name:
218
- name_parts.append(ext_name)
219
- if language_name:
220
- name_parts.append(language_name)
221
- if quality_name:
222
- name_parts.append(quality_name)
223
-
216
+
224
217
  results.append({
225
218
  "url" : iframe_url,
226
- "name" : " | ".join(name_parts)
219
+ "name" : extractor.name if extractor else "Player"
227
220
  })
228
221
 
229
222
  return results
@@ -9,7 +9,7 @@ class SelcukFlix(PluginBase):
9
9
  lang = "tr"
10
10
  main_url = "https://selcukflix.net"
11
11
  favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
12
- description = "Selcukflix'te her türden en yeni ve en popüler dizi ve filmleri izlemenin keyfini çıkarın. Aksiyondan romantiğe, bilim kurgudan dramaya, geniş kütüphanemizde herkes için bir şey var."
12
+ description = "Selcukflix'te her türden en yeni ve en popüler dizi ve filmleri izlemenin keyfini çıkarın. Aksiyondan romantiğe, bilim kurgudan dramaya, geniş kütüphanemizde herkes için bir şey var."
13
13
 
14
14
  main_page = {
15
15
  f"{main_url}/tum-bolumler" : "Yeni Eklenen Bölümler",
@@ -240,60 +240,59 @@ class SelcukFlix(PluginBase):
240
240
  sel = Selector(resp.text)
241
241
 
242
242
  next_data = sel.css("script#__NEXT_DATA__::text").get()
243
- if not next_data: return []
243
+ if not next_data:
244
+ return []
244
245
 
245
246
  try:
246
- data = json.loads(next_data)
247
- secure_data = data["props"]["pageProps"]["secureData"]
248
- raw_data = base64.b64decode(secure_data.replace('"', ''))
249
- try:
250
- decoded_str = raw_data.decode('utf-8')
251
- except UnicodeDecodeError:
252
- decoded_str = raw_data.decode('iso-8859-1')
247
+ data = json.loads(next_data)
248
+ secure_data = data["props"]["pageProps"]["secureData"]
249
+ raw_data = base64.b64decode(secure_data.replace('"', ''))
250
+
251
+ # Try UTF-8 first, fallback to ISO-8859-1 (matching Kotlin)
252
+ try:
253
+ decoded_str = raw_data.decode('utf-8')
254
+ except UnicodeDecodeError:
255
+ decoded_str = raw_data.decode('iso-8859-1')
253
256
 
254
- content_details = json.loads(decoded_str)
255
- related_data = content_details.get("relatedData", {})
256
-
257
- source_content = None
258
-
259
- # Check if Series (episode) or Movie
260
- if "/dizi/" in url:
261
- if related_data.get("episodeSources", {}).get("state"):
262
- res = related_data["episodeSources"].get("result", [])
263
- if res:
264
- source_content = res[0].get("sourceContent")
265
- else:
266
- # Movie
267
- if related_data.get("movieParts", {}).get("state"):
268
- # Looking for first part source
269
- movie_parts = related_data["movieParts"].get("result", [])
270
- if movie_parts:
271
- first_part_id = movie_parts[0].get("id")
272
- # RelatedResults -> getMoviePartSourcesById_ID
273
- rr = content_details.get("RelatedResults", {})
274
- key = f"getMoviePartSourcesById_{first_part_id}"
275
- if key in rr:
276
- res = rr[key].get("result", [])
277
- if res:
278
- source_content = res[0].get("source_content")
279
-
280
- results = []
281
- if source_content:
282
- iframe_sel = Selector(source_content)
283
- iframe_src = iframe_sel.css("iframe::attr(src)").get()
284
- if iframe_src:
285
- iframe_src = self.fix_url(iframe_src)
286
- # Domain replace
287
- if "sn.dplayer74.site" in iframe_src:
288
- iframe_src = iframe_src.replace("sn.dplayer74.site", "sn.hotlinger.com")
289
-
290
- extractor = self.ex_manager.find_extractor(iframe_src)
291
- results.append({
292
- "url": iframe_src,
293
- "name": extractor.name if extractor else "Iframe"
294
- })
295
-
296
- return results
257
+ content_details = json.loads(decoded_str)
258
+ related_data = content_details.get("relatedData", {})
259
+
260
+ source_content = None
261
+
262
+ # Check if Series (episode) or Movie
263
+ if "/dizi/" in url:
264
+ if related_data.get("episodeSources", {}).get("state"):
265
+ res = related_data["episodeSources"].get("result", [])
266
+ if res:
267
+ source_content = res[0].get("sourceContent")
268
+ else:
269
+ # Movie
270
+ if related_data.get("movieParts", {}).get("state"):
271
+ movie_parts = related_data["movieParts"].get("result", [])
272
+ if movie_parts:
273
+ first_part_id = movie_parts[0].get("id")
274
+ # RelatedResults -> getMoviePartSourcesById_ID
275
+ rr = content_details.get("RelatedResults", {})
276
+ key = f"getMoviePartSourcesById_{first_part_id}"
277
+ if key in rr:
278
+ res = rr[key].get("result", [])
279
+ if res:
280
+ source_content = res[0].get("source_content")
281
+
282
+ if source_content:
283
+ iframe_sel = Selector(source_content)
284
+ iframe_src = iframe_sel.css("iframe::attr(src)").get()
285
+ if iframe_src:
286
+ iframe_src = self.fix_url(iframe_src)
287
+ extractor = self.ex_manager.find_extractor(iframe_src)
288
+
289
+ if extractor: # Only return if extractor found
290
+ return [{
291
+ "url" : iframe_src,
292
+ "name" : extractor.name
293
+ }]
294
+
295
+ return []
297
296
 
298
297
  except Exception:
299
298
  return []
@@ -0,0 +1,259 @@
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
4
+ from parsel import Selector
5
+ import re, json
6
+
7
+ class SetFilmIzle(PluginBase):
8
+ name = "SetFilmIzle"
9
+ language = "tr"
10
+ main_url = "https://www.setfilmizle.uk"
11
+ favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
12
+ description = "Setfilmizle sitemizde, donma yaşamadan Türkçe dublaj ve altyazılı filmleri ile dizileri muhteşem 1080p full HD kalitesinde izleyebilirsiniz."
13
+
14
+ main_page = {
15
+ f"{main_url}/tur/aile/" : "Aile",
16
+ f"{main_url}/tur/aksiyon/" : "Aksiyon",
17
+ f"{main_url}/tur/animasyon/" : "Animasyon",
18
+ f"{main_url}/tur/belgesel/" : "Belgesel",
19
+ f"{main_url}/tur/bilim-kurgu/" : "Bilim-Kurgu",
20
+ f"{main_url}/tur/biyografi/" : "Biyografi",
21
+ f"{main_url}/tur/dini/" : "Dini",
22
+ f"{main_url}/tur/dram/" : "Dram",
23
+ f"{main_url}/tur/fantastik/" : "Fantastik",
24
+ f"{main_url}/tur/genclik/" : "Gençlik",
25
+ f"{main_url}/tur/gerilim/" : "Gerilim",
26
+ f"{main_url}/tur/gizem/" : "Gizem",
27
+ f"{main_url}/tur/komedi/" : "Komedi",
28
+ f"{main_url}/tur/korku/" : "Korku",
29
+ f"{main_url}/tur/macera/" : "Macera",
30
+ f"{main_url}/tur/romantik/" : "Romantik",
31
+ f"{main_url}/tur/savas/" : "Savaş",
32
+ f"{main_url}/tur/suc/" : "Suç",
33
+ f"{main_url}/tur/tarih/" : "Tarih",
34
+ f"{main_url}/tur/western/" : "Western"
35
+ }
36
+
37
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
38
+ istek = self.cloudscraper.get(url)
39
+ secici = Selector(istek.text)
40
+
41
+ results = []
42
+ for item in secici.css("div.items article"):
43
+ title = item.css("h2::text").get()
44
+ href = item.css("a::attr(href)").get()
45
+ poster = item.css("img::attr(data-src)").get()
46
+
47
+ if title and href:
48
+ results.append(MainPageResult(
49
+ category = category,
50
+ title = title.strip(),
51
+ url = self.fix_url(href),
52
+ poster = self.fix_url(poster) if poster else None
53
+ ))
54
+
55
+ return results
56
+
57
+ async def search(self, query: str) -> list[SearchResult]:
58
+ # Ana sayfadan nonce değerini al
59
+ main_resp = self.cloudscraper.get(self.main_url)
60
+
61
+ # Birden fazla nonce pattern dene
62
+ nonce = ""
63
+ nonce_patterns = [
64
+ r'nonces:\s*\{\s*search:\s*"([^"]+)"', # STMOVIE_AJAX.nonces.search
65
+ r'"search":\s*"([a-zA-Z0-9]+)"', # JSON format
66
+ r"nonce:\s*'([^']*)'",
67
+ r'"nonce":"([^"]+)"',
68
+ r'data-nonce="([^"]+)"',
69
+ ]
70
+ for pattern in nonce_patterns:
71
+ match = re.search(pattern, main_resp.text)
72
+ if match:
73
+ nonce = match.group(1)
74
+ break
75
+
76
+ search_resp = self.cloudscraper.post(
77
+ f"{self.main_url}/wp-admin/admin-ajax.php",
78
+ headers = {
79
+ "X-Requested-With" : "XMLHttpRequest",
80
+ "Content-Type" : "application/x-www-form-urlencoded",
81
+ "Referer" : f"{self.main_url}/"
82
+ },
83
+ data = {
84
+ "action" : "ajax_search",
85
+ "search" : query,
86
+ "original_search" : query,
87
+ "nonce" : nonce
88
+ }
89
+ )
90
+
91
+ try:
92
+ data = search_resp.json()
93
+ html = data.get("html", "")
94
+ except:
95
+ return []
96
+
97
+ secici = Selector(text=html)
98
+ results = []
99
+
100
+ for item in secici.css("div.items article"):
101
+ title = item.css("h2::text").get()
102
+ href = item.css("a::attr(href)").get()
103
+ poster = item.css("img::attr(data-src)").get()
104
+
105
+ if title and href:
106
+ results.append(SearchResult(
107
+ title = title.strip(),
108
+ url = self.fix_url(href),
109
+ poster = self.fix_url(poster) if poster else None
110
+ ))
111
+
112
+ return results
113
+
114
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
115
+ istek = await self.httpx.get(url)
116
+ secici = Selector(istek.text)
117
+
118
+ raw_title = secici.css("h1::text").get() or ""
119
+ title = re.sub(r"\s*izle.*$", "", raw_title, flags=re.IGNORECASE).strip()
120
+ poster = secici.css("div.poster img::attr(src)").get()
121
+ description = secici.css("div.wp-content p::text").get()
122
+ year = secici.css("div.extra span.C a::text").get()
123
+ if year:
124
+ year_match = re.search(r"\d{4}", year)
125
+ year = year_match.group() if year_match else None
126
+ tags = [a.css("::text").get().strip() for a in secici.css("div.sgeneros a") if a.css("::text").get()]
127
+ duration = secici.css("span.runtime::text").get()
128
+ if duration:
129
+ dur_match = re.search(r"\d+", duration)
130
+ duration = int(dur_match.group()) if dur_match else None
131
+
132
+ actors = [span.css("::text").get().strip() for span in secici.css("span.valor a > span") if span.css("::text").get()]
133
+
134
+ trailer_match = re.search(r'embed/([^?]*)\?rel', istek.text)
135
+ trailer = f"https://www.youtube.com/embed/{trailer_match.group(1)}" if trailer_match else None
136
+
137
+ # Dizi mi film mi kontrol et
138
+ is_series = "/dizi/" in url
139
+
140
+ if is_series:
141
+ year_elem = secici.css("a[href*='/yil/']::text").get()
142
+ if year_elem:
143
+ year_match = re.search(r"\d{4}", year_elem)
144
+ year = year_match.group() if year_match else year
145
+
146
+ dur_elem = secici.css("div#info span:contains('Dakika')::text").get()
147
+ if dur_elem:
148
+ dur_match = re.search(r"\d+", dur_elem)
149
+ duration = int(dur_match.group()) if dur_match else duration
150
+
151
+ episodes = []
152
+ for ep_item in secici.css("div#episodes ul.episodios li"):
153
+ ep_href = ep_item.css("h4.episodiotitle a::attr(href)").get()
154
+ ep_name = ep_item.css("h4.episodiotitle a::text").get()
155
+
156
+ if not ep_href or not ep_name:
157
+ continue
158
+
159
+ ep_detail = ep_name.strip()
160
+ season_match = re.search(r"(\d+)\.\s*Sezon", ep_detail)
161
+ episode_match = re.search(r"Sezon\s+(\d+)\.\s*Bölüm", ep_detail)
162
+
163
+ ep_season = int(season_match.group(1)) if season_match else 1
164
+ ep_episode = int(episode_match.group(1)) if episode_match else None
165
+
166
+ episodes.append(Episode(
167
+ season = ep_season,
168
+ episode = ep_episode,
169
+ title = ep_name.strip(),
170
+ url = self.fix_url(ep_href)
171
+ ))
172
+
173
+ return SeriesInfo(
174
+ url = url,
175
+ poster = self.fix_url(poster) if poster else None,
176
+ title = title,
177
+ description = description.strip() if description else None,
178
+ tags = tags,
179
+ year = year,
180
+ duration = duration,
181
+ actors = actors,
182
+ episodes = episodes
183
+ )
184
+
185
+ return MovieInfo(
186
+ url = url,
187
+ poster = self.fix_url(poster) if poster else None,
188
+ title = title,
189
+ description = description.strip() if description else None,
190
+ tags = tags,
191
+ year = year,
192
+ duration = duration,
193
+ actors = actors
194
+ )
195
+
196
+ async def load_links(self, url: str) -> list[dict]:
197
+ istek = await self.httpx.get(url)
198
+ secici = Selector(istek.text)
199
+
200
+ nonce = secici.css("div#playex::attr(data-nonce)").get() or ""
201
+
202
+ # partKey to dil label mapping
203
+ part_key_labels = {
204
+ "turkcedublaj" : "Türkçe Dublaj",
205
+ "turkcealtyazi" : "Türkçe Altyazı",
206
+ "orijinal" : "Orijinal"
207
+ }
208
+
209
+ links = []
210
+ for player in secici.css("nav.player a"):
211
+ source_id = player.css("::attr(data-post-id)").get()
212
+ player_name = player.css("::attr(data-player-name)").get()
213
+ part_key = player.css("::attr(data-part-key)").get()
214
+
215
+ if not source_id or "event" in source_id or source_id == "":
216
+ continue
217
+
218
+ # Multipart form request
219
+ try:
220
+ resp = self.cloudscraper.post(
221
+ f"{self.main_url}/wp-admin/admin-ajax.php",
222
+ headers = {"Referer": url},
223
+ data = {
224
+ "action" : "get_video_url",
225
+ "nonce" : nonce,
226
+ "post_id" : source_id,
227
+ "player_name" : player_name or "",
228
+ "part_key" : part_key or ""
229
+ }
230
+ )
231
+ data = resp.json()
232
+ except:
233
+ continue
234
+
235
+ iframe_url = data.get("data", {}).get("url")
236
+ if not iframe_url:
237
+ continue
238
+
239
+ # SetPlay URL'si için part_key ekleme
240
+ if "setplay" not in iframe_url and part_key:
241
+ iframe_url = f"{iframe_url}?partKey={part_key}"
242
+
243
+ # Dil etiketi oluştur
244
+ label = part_key_labels.get(part_key, "")
245
+ if not label and part_key:
246
+ label = part_key.replace("_", " ").title()
247
+
248
+ extractor = self.ex_manager.find_extractor(iframe_url)
249
+ name = extractor.name if extractor else player_name or "Direct Link"
250
+ if label:
251
+ name = f"{name} | {label}"
252
+
253
+ links.append({
254
+ "url" : iframe_url,
255
+ "name" : name,
256
+ "referer" : self.main_url
257
+ })
258
+
259
+ return links
@@ -2,6 +2,7 @@
2
2
 
3
3
  from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode
4
4
  from parsel import Selector
5
+ import re
5
6
 
6
7
  class SezonlukDizi(PluginBase):
7
8
  name = "SezonlukDizi"
@@ -99,6 +100,20 @@ class SezonlukDizi(PluginBase):
99
100
  actors = actors
100
101
  )
101
102
 
103
+ async def get_asp_data(self) -> tuple[str, str]:
104
+ """Fetch dynamic ASP version numbers from site.min.js"""
105
+ try:
106
+ js_content = await self.httpx.get(f"{self.main_url}/js/site.min.js")
107
+ alternatif_match = re.search(r'dataAlternatif(.*?)\.asp', js_content.text)
108
+ embed_match = re.search(r'dataEmbed(.*?)\.asp', js_content.text)
109
+
110
+ alternatif_ver = alternatif_match.group(1) if alternatif_match else "22"
111
+ embed_ver = embed_match.group(1) if embed_match else "22"
112
+
113
+ return (alternatif_ver, embed_ver)
114
+ except Exception:
115
+ return ("22", "22") # Fallback to default versions
116
+
102
117
  async def load_links(self, url: str) -> list[dict]:
103
118
  istek = await self.httpx.get(url)
104
119
  secici = Selector(istek.text)
@@ -107,10 +122,13 @@ class SezonlukDizi(PluginBase):
107
122
  if not bid:
108
123
  return []
109
124
 
125
+ # Get dynamic ASP versions
126
+ alternatif_ver, embed_ver = await self.get_asp_data()
127
+
110
128
  results = []
111
129
  for dil, label in [("1", "Altyazı"), ("0", "Dublaj")]:
112
130
  dil_istek = await self.httpx.post(
113
- url = f"{self.main_url}/ajax/dataAlternatif22.asp",
131
+ url = f"{self.main_url}/ajax/dataAlternatif{alternatif_ver}.asp",
114
132
  headers = {"X-Requested-With": "XMLHttpRequest"},
115
133
  data = {"bid": bid, "dil": dil},
116
134
  )
@@ -123,7 +141,7 @@ class SezonlukDizi(PluginBase):
123
141
  if dil_json.get("status") == "success":
124
142
  for idx, veri in enumerate(dil_json.get("data", [])):
125
143
  veri_response = await self.httpx.post(
126
- url = f"{self.main_url}/ajax/dataEmbed22.asp",
144
+ url = f"{self.main_url}/ajax/dataEmbed{embed_ver}.asp",
127
145
  headers = {"X-Requested-With": "XMLHttpRequest"},
128
146
  data = {"id": veri.get("id")},
129
147
  )
@@ -133,10 +151,13 @@ class SezonlukDizi(PluginBase):
133
151
  if "link.asp" in iframe:
134
152
  continue
135
153
 
136
- extractor = self.ex_manager.find_extractor(self.fix_url(iframe))
137
- results.append({
138
- "url" : self.fix_url(iframe),
139
- "name" : f"{extractor.name if extractor else f'{label} - Player {idx + 1}'}"
140
- })
154
+ iframe_url = self.fix_url(iframe)
155
+ extractor = self.ex_manager.find_extractor(iframe_url)
156
+
157
+ if extractor: # Only add if extractor found
158
+ results.append({
159
+ "url" : iframe_url,
160
+ "name" : f"{extractor.name} | {label}"
161
+ })
141
162
 
142
163
  return results