KekikStream 2.2.8__tar.gz → 2.3.0__tar.gz

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-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/Filemoon.py +41 -26
  2. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/Dizilla.py +64 -37
  3. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/FilmMakinesi.py +57 -2
  4. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/SetFilmIzle.py +47 -44
  5. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/SezonlukDizi.py +51 -51
  6. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/Sinefy.py +44 -33
  7. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream.egg-info/PKG-INFO +1 -1
  8. {kekikstream-2.2.8 → kekikstream-2.3.0}/PKG-INFO +1 -1
  9. {kekikstream-2.2.8 → kekikstream-2.3.0}/setup.py +1 -1
  10. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/CLI/__init__.py +0 -0
  11. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/CLI/pypi_kontrol.py +0 -0
  12. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Core/Extractor/ExtractorBase.py +0 -0
  13. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Core/Extractor/ExtractorLoader.py +0 -0
  14. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Core/Extractor/ExtractorManager.py +0 -0
  15. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Core/Extractor/ExtractorModels.py +0 -0
  16. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Core/Extractor/YTDLPCache.py +0 -0
  17. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Core/Media/MediaHandler.py +0 -0
  18. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Core/Media/MediaManager.py +0 -0
  19. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Core/Plugin/PluginBase.py +0 -0
  20. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Core/Plugin/PluginLoader.py +0 -0
  21. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Core/Plugin/PluginManager.py +0 -0
  22. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Core/Plugin/PluginModels.py +0 -0
  23. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Core/UI/UIManager.py +0 -0
  24. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Core/__init__.py +0 -0
  25. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/CloseLoad.py +0 -0
  26. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/ContentX.py +0 -0
  27. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/DonilasPlay.py +0 -0
  28. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/DzenRu.py +0 -0
  29. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/ExPlay.py +0 -0
  30. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/HDPlayerSystem.py +0 -0
  31. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/JFVid.py +0 -0
  32. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/JetTv.py +0 -0
  33. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/MailRu.py +0 -0
  34. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/MixPlayHD.py +0 -0
  35. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/MixTiger.py +0 -0
  36. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/MolyStream.py +0 -0
  37. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/Odnoklassniki.py +0 -0
  38. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/PeaceMakerst.py +0 -0
  39. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/PixelDrain.py +0 -0
  40. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/PlayerFilmIzle.py +0 -0
  41. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/RapidVid.py +0 -0
  42. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/SetPlay.py +0 -0
  43. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/SetPrime.py +0 -0
  44. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/SibNet.py +0 -0
  45. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/Sobreatsesuyp.py +0 -0
  46. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/TRsTX.py +0 -0
  47. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/TauVideo.py +0 -0
  48. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/TurboImgz.py +0 -0
  49. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/TurkeyPlayer.py +0 -0
  50. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/VCTPlay.py +0 -0
  51. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/VidHide.py +0 -0
  52. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/VidMoly.py +0 -0
  53. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/VidMoxy.py +0 -0
  54. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/VidPapi.py +0 -0
  55. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/VideoSeyred.py +0 -0
  56. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/YTDLP.py +0 -0
  57. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Extractors/YildizKisaFilm.py +0 -0
  58. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/BelgeselX.py +0 -0
  59. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/DiziBox.py +0 -0
  60. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/DiziPal.py +0 -0
  61. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/DiziYou.py +0 -0
  62. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/FilmBip.py +0 -0
  63. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/FilmModu.py +0 -0
  64. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/FullHDFilm.py +0 -0
  65. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/FullHDFilmizlesene.py +0 -0
  66. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/HDFilmCehennemi.py +0 -0
  67. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/JetFilmizle.py +0 -0
  68. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/KultFilmler.py +0 -0
  69. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/RecTV.py +0 -0
  70. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/RoketDizi.py +0 -0
  71. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/SelcukFlix.py +0 -0
  72. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/SineWix.py +0 -0
  73. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/SinemaCX.py +0 -0
  74. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/Sinezy.py +0 -0
  75. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/SuperFilmGeldi.py +0 -0
  76. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/Plugins/UgurFilm.py +0 -0
  77. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/__init__.py +0 -0
  78. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/__main__.py +0 -0
  79. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream/requirements.txt +0 -0
  80. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream.egg-info/SOURCES.txt +0 -0
  81. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream.egg-info/dependency_links.txt +0 -0
  82. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream.egg-info/entry_points.txt +0 -0
  83. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream.egg-info/requires.txt +0 -0
  84. {kekikstream-2.2.8 → kekikstream-2.3.0}/KekikStream.egg-info/top_level.txt +0 -0
  85. {kekikstream-2.2.8 → kekikstream-2.3.0}/LICENSE +0 -0
  86. {kekikstream-2.2.8 → kekikstream-2.3.0}/MANIFEST.in +0 -0
  87. {kekikstream-2.2.8 → kekikstream-2.3.0}/README.md +0 -0
  88. {kekikstream-2.2.8 → kekikstream-2.3.0}/setup.cfg +0 -0
@@ -22,13 +22,14 @@ class Filemoon(ExtractorBase):
22
22
  return any(domain in url for domain in self.supported_domains)
23
23
 
24
24
  async def extract(self, url: str, referer: str = None) -> ExtractResult:
25
- headers = {
26
- "Referer" : url,
27
- "Sec-Fetch-Dest" : "iframe",
28
- "Sec-Fetch-Mode" : "navigate",
29
- "Sec-Fetch-Site" : "cross-site",
25
+ default_headers = {
26
+ "Referer" : url,
27
+ "Sec-Fetch-Dest" : "iframe",
28
+ "Sec-Fetch-Mode" : "navigate",
29
+ "Sec-Fetch-Site" : "cross-site",
30
+ "User-Agent" : "Mozilla/5.0 (X11; Linux x86_64; rv:137.0) Gecko/20100101 Firefox/137.0"
30
31
  }
31
- self.httpx.headers.update(headers)
32
+ self.httpx.headers.update(default_headers)
32
33
 
33
34
  # İlk sayfayı al
34
35
  istek = await self.httpx.get(url)
@@ -38,31 +39,44 @@ class Filemoon(ExtractorBase):
38
39
  # Eğer iframe varsa, iframe'e git
39
40
  iframe_el = secici.css_first("iframe")
40
41
  iframe_src = iframe_el.attrs.get("src") if iframe_el else None
41
- if iframe_src:
42
- iframe_url = self.fix_url(iframe_src)
43
- self.httpx.headers.update({
44
- "Accept-Language" : "en-US,en;q=0.5",
45
- "Sec-Fetch-Dest" : "iframe"
46
- })
47
- istek = await self.httpx.get(iframe_url)
48
- response = istek.text
49
-
50
- # Packed script'i bul ve unpack et
42
+
51
43
  m3u8_url = None
52
-
53
- if Packer.detect_packed(response):
54
- try:
55
- unpacked = Packer.unpack(response)
56
- # sources:[{file:"...» pattern'ını ara
57
- if match := re.search(r'sources:\s*\[\s*\{\s*file:\s*"([^"]+)"', unpacked):
44
+
45
+ if not iframe_src:
46
+ # Fallback: Script içinde ara (Kotlin: selectFirst("script:containsData(function(p,a,c,k,e,d))"))
47
+ script_data = ""
48
+ for script in secici.css("script"):
49
+ if "function(p,a,c,k,e,d)" in script.text():
50
+ script_data = script.text()
51
+ break
52
+
53
+ if script_data:
54
+ unpacked = Packer.unpack(script_data)
55
+ if match := re.search(r'sources:\[\{file:"(.*?)"', unpacked):
58
56
  m3u8_url = match.group(1)
59
- elif match := re.search(r'file:\s*"([^"]*?\.m3u8[^"]*)"', unpacked):
57
+ else:
58
+ # Iframe varsa devam et
59
+ iframe_url = self.fix_url(iframe_src)
60
+ iframe_headers = default_headers.copy()
61
+ iframe_headers["Accept-Language"] = "en-US,en;q=0.5"
62
+
63
+ istek = await self.httpx.get(iframe_url, headers=iframe_headers)
64
+ response = istek.text
65
+ secici = HTMLParser(response)
66
+
67
+ script_data = ""
68
+ for script in secici.css("script"):
69
+ if "function(p,a,c,k,e,d)" in script.text():
70
+ script_data = script.text()
71
+ break
72
+
73
+ if script_data:
74
+ unpacked = Packer.unpack(script_data)
75
+ if match := re.search(r'sources:\[\{file:"(.*?)"', unpacked):
60
76
  m3u8_url = match.group(1)
61
- except Exception:
62
- pass
63
77
 
64
- # Fallback: Doğrudan response'ta ara
65
78
  if not m3u8_url:
79
+ # Son çare: Normal response içinde ara
66
80
  if match := re.search(r'sources:\s*\[\s*\{\s*file:\s*"([^"]+)"', response):
67
81
  m3u8_url = match.group(1)
68
82
  elif match := re.search(r'file:\s*"([^"]*?\.m3u8[^"]*)"', response):
@@ -75,5 +89,6 @@ class Filemoon(ExtractorBase):
75
89
  name = self.name,
76
90
  url = self.fix_url(m3u8_url),
77
91
  referer = f"{self.main_url}/",
92
+ user_agent = default_headers["User-Agent"],
78
93
  subtitles = []
79
94
  )
@@ -142,44 +142,70 @@ class Dizilla(PluginBase):
142
142
  istek = await self.httpx.get(url)
143
143
  secici = HTMLParser(istek.text)
144
144
 
145
- # application/ld+json script'lerini al
146
- ld_json_scripts = secici.css("script[type='application/ld+json']")
147
- if not ld_json_scripts:
145
+ title = secici.css_first("div.poster.poster h2")
146
+ title = title.text(strip=True) if title else None
147
+ if not title:
148
148
  return None
149
149
 
150
- # Son script'i al
151
- last_script = ld_json_scripts[-1]
152
- veri = loads(last_script.text(strip=True))
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
153
152
 
154
- title = veri.get("name")
155
- if alt_title := veri.get("alternateName"):
156
- title += f" - ({alt_title})"
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
157
164
 
158
- poster = self.fix_url(veri.get("image"))
159
- description = veri.get("description")
160
- year = veri.get("datePublished").split("-")[0]
161
-
162
- # Tags extraction from page content (h3 tag)
163
165
  tags_el = secici.css_first("div.poster.poster h3")
164
- tags_raw = tags_el.text(strip=True) if tags_el else ""
165
- tags = [t.strip() for t in tags_raw.split(",")] if tags_raw else []
166
-
167
- rating = veri.get("aggregateRating", {}).get("ratingValue")
168
- actors = [actor.get("name") for actor in veri.get("actor", []) if actor.get("name")]
169
-
170
- bolumler = []
171
- sezonlar = veri.get("containsSeason") if isinstance(veri.get("containsSeason"), list) else [veri.get("containsSeason")]
172
- for sezon in sezonlar:
173
- episodes = sezon.get("episode")
174
- if isinstance(episodes, dict):
175
- episodes = [episodes]
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
176
 
177
- for bolum in episodes:
178
- bolumler.append(Episode(
179
- season = sezon.get("seasonNumber"),
180
- episode = bolum.get("episodeNumber"),
181
- title = bolum.get("name"),
182
- url = await self.url_base_degis(bolum.get("url"), self.main_url),
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
183
209
  ))
184
210
 
185
211
  return SeriesInfo(
@@ -188,9 +214,8 @@ class Dizilla(PluginBase):
188
214
  title = title,
189
215
  description = description,
190
216
  tags = tags,
191
- rating = rating,
192
- year = year,
193
- episodes = bolumler,
217
+ year = str(year) if year else None,
218
+ episodes = episodeses,
194
219
  actors = actors
195
220
  )
196
221
 
@@ -212,7 +237,7 @@ class Dizilla(PluginBase):
212
237
 
213
238
  # Get first source (matching Kotlin)
214
239
  first_result = results[0]
215
- source_content = first_result.get("source_content", "")
240
+ source_content = str(first_result.get("source_content", ""))
216
241
 
217
242
  # Clean the source_content string (matching Kotlin: .replace("\"", "").replace("\\", ""))
218
243
  cleaned_source = source_content.replace('"', '').replace('\\', '')
@@ -220,10 +245,12 @@ class Dizilla(PluginBase):
220
245
  # Parse cleaned HTML
221
246
  iframe_el = HTMLParser(cleaned_source).css_first("iframe")
222
247
  iframe_src = iframe_el.attrs.get("src") if iframe_el else None
248
+
249
+ # Referer check (matching Kotlin: loadExtractor(iframe, "${mainUrl}/", ...))
223
250
  iframe_url = self.fix_url(iframe_src) if iframe_src else None
224
251
 
225
252
  if not iframe_url:
226
253
  return []
227
254
 
228
- data = await self.extract(iframe_url, prefix=first_result.get('language_name', 'Unknown'))
255
+ data = await self.extract(iframe_url, referer=f"{self.main_url}/", prefix=first_result.get('language_name', 'Unknown'))
229
256
  return [data] if data else []
@@ -1,7 +1,8 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult
4
4
  from selectolax.parser import HTMLParser
5
+ import re
5
6
 
6
7
  class FilmMakinesi(PluginBase):
7
8
  name = "FilmMakinesi"
@@ -81,7 +82,7 @@ class FilmMakinesi(PluginBase):
81
82
 
82
83
  return results
83
84
 
84
- async def load_item(self, url: str) -> MovieInfo:
85
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
85
86
  istek = await self.httpx.get(url)
86
87
  secici = HTMLParser(istek.text)
87
88
 
@@ -115,6 +116,60 @@ class FilmMakinesi(PluginBase):
115
116
  if len(parts) > 1:
116
117
  duration = parts[1].strip()
117
118
 
119
+ # Dizi mi kontrol et - sezon/bölüm linkleri var mı?
120
+ episodes = []
121
+ all_links = secici.css("a[href]")
122
+ for link in all_links:
123
+ href = link.attrs.get("href", "")
124
+ match = re.search(r"/sezon-(\d+)/bolum-(\d+)", href)
125
+ if match:
126
+ season_no = int(match.group(1))
127
+ ep_no = int(match.group(2))
128
+
129
+ # Bölüm başlığını çıkar - text'ten gerçek ismi al
130
+ # Format: "22 Eylül 2014 / 44 dk /1. Sezon / 1. BölümPilot"
131
+ full_text = link.text(strip=True)
132
+ # "Bölüm" kelimesinden sonraki kısmı al
133
+ ep_title = ""
134
+ if "Bölüm" in full_text:
135
+ parts = full_text.split("Bölüm")
136
+ if len(parts) > 1:
137
+ ep_title = parts[-1].strip()
138
+
139
+ episodes.append(Episode(
140
+ season = season_no,
141
+ episode = ep_no,
142
+ title = ep_title,
143
+ url = self.fix_url(href)
144
+ ))
145
+
146
+ # Bölümler varsa SeriesInfo döndür
147
+ if episodes:
148
+ # Tekrar eden bölümleri kaldır
149
+ seen = set()
150
+ unique_episodes = []
151
+ for ep in episodes:
152
+ key = (ep.season, ep.episode)
153
+ if key not in seen:
154
+ seen.add(key)
155
+ unique_episodes.append(ep)
156
+
157
+ # Sırala
158
+ unique_episodes.sort(key=lambda x: (x.season or 0, x.episode or 0))
159
+
160
+ return SeriesInfo(
161
+ url = url,
162
+ poster = self.fix_url(poster) if poster else None,
163
+ title = self.clean_title(title),
164
+ description = description,
165
+ tags = tags,
166
+ rating = rating,
167
+ year = year,
168
+ actors = actors,
169
+ duration = duration,
170
+ episodes = unique_episodes
171
+ )
172
+
118
173
  return MovieInfo(
119
174
  url = url,
120
175
  poster = self.fix_url(poster) if poster else None,
@@ -2,7 +2,7 @@
2
2
 
3
3
  from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult
4
4
  from selectolax.parser import HTMLParser
5
- import re, json
5
+ import re, json, asyncio
6
6
 
7
7
  class SetFilmIzle(PluginBase):
8
8
  name = "SetFilmIzle"
@@ -229,7 +229,7 @@ class SetFilmIzle(PluginBase):
229
229
  istek = await self.httpx.get(url)
230
230
  secici = HTMLParser(istek.text)
231
231
 
232
- nonce = self._get_nonce("video_nonce", referer=url)
232
+ nonce = secici.css_first("div#playex").attrs.get("data-nonce") if secici.css_first("div#playex") else ""
233
233
 
234
234
  # partKey to dil label mapping
235
235
  part_key_labels = {
@@ -238,46 +238,49 @@ class SetFilmIzle(PluginBase):
238
238
  "orijinal" : "Orijinal"
239
239
  }
240
240
 
241
- links = []
241
+ semaphore = asyncio.Semaphore(5)
242
+ tasks = []
243
+
244
+ async def fetch_and_extract(player):
245
+ async with semaphore:
246
+ source_id = player.attrs.get("data-post-id")
247
+ player_name = player.attrs.get("data-player-name")
248
+ part_key = player.attrs.get("data-part-key")
249
+
250
+ if not source_id or "event" in source_id or source_id == "":
251
+ return None
252
+
253
+ try:
254
+ resp = self.cloudscraper.post(
255
+ f"{self.main_url}/wp-admin/admin-ajax.php",
256
+ headers = {"Referer": url},
257
+ data = {
258
+ "action" : "get_video_url",
259
+ "nonce" : nonce,
260
+ "post_id" : source_id,
261
+ "player_name" : player_name or "",
262
+ "part_key" : part_key or ""
263
+ }
264
+ )
265
+ data = resp.json()
266
+ except:
267
+ return None
268
+
269
+ iframe_url = data.get("data", {}).get("url")
270
+ if not iframe_url:
271
+ return None
272
+
273
+ if "setplay" not in iframe_url and part_key:
274
+ iframe_url = f"{iframe_url}?partKey={part_key}"
275
+
276
+ label = part_key_labels.get(part_key, "")
277
+ if not label and part_key:
278
+ label = part_key.replace("_", " ").title()
279
+
280
+ return await self.extract(iframe_url, prefix=label if label else None)
281
+
242
282
  for player in secici.css("nav.player a"):
243
- source_id = player.attrs.get("data-post-id")
244
- player_name = player.attrs.get("data-player-name")
245
- part_key = player.attrs.get("data-part-key")
246
-
247
- if not source_id or "event" in source_id or source_id == "":
248
- continue
249
-
250
- try:
251
- resp = self.cloudscraper.post(
252
- f"{self.main_url}/wp-admin/admin-ajax.php",
253
- headers = {"Referer": url},
254
- data = {
255
- "action" : "get_video_url",
256
- "nonce" : nonce,
257
- "post_id" : source_id,
258
- "player_name" : player_name or "",
259
- "part_key" : part_key or ""
260
- }
261
- )
262
- data = resp.json()
263
- except:
264
- continue
265
-
266
- iframe_url = data.get("data", {}).get("url")
267
- if not iframe_url:
268
- continue
269
-
270
- # SetPlay URL'si için part_key ekleme
271
- if "setplay" not in iframe_url and part_key:
272
- iframe_url = f"{iframe_url}?partKey={part_key}"
273
-
274
- # Dil etiketi oluştur
275
- label = part_key_labels.get(part_key, "")
276
- if not label and part_key:
277
- label = part_key.replace("_", " ").title()
278
-
279
- data = await self.extract(iframe_url, prefix=label if label else None)
280
- if data:
281
- links.append(data)
282
-
283
- return links
283
+ tasks.append(fetch_and_extract(player))
284
+
285
+ results = await asyncio.gather(*tasks)
286
+ return [r for r in results if r]
@@ -2,7 +2,7 @@
2
2
 
3
3
  from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult
4
4
  from selectolax.parser import HTMLParser
5
- import re
5
+ import re, asyncio
6
6
 
7
7
  class SezonlukDizi(PluginBase):
8
8
  name = "SezonlukDizi"
@@ -39,6 +39,16 @@ class SezonlukDizi(PluginBase):
39
39
  f"{main_url}/diziler.asp?siralama_tipi=id&tur=western&s=" : "Western"
40
40
  }
41
41
 
42
+ async def _get_asp_data(self) -> dict:
43
+ js_req = await self.httpx.get(f"{self.main_url}/js/site.min.js")
44
+ alt_match = re.search(r"dataAlternatif(.*?)\.asp", js_req.text)
45
+ embed_match = re.search(r"dataEmbed(.*?)\.asp", js_req.text)
46
+
47
+ return {
48
+ "alternatif": alt_match.group(1) if alt_match else "",
49
+ "embed": embed_match.group(1) if embed_match else ""
50
+ }
51
+
42
52
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
43
53
  istek = await self.httpx.get(f"{url}{page}")
44
54
  secici = HTMLParser(istek.text)
@@ -67,7 +77,7 @@ class SezonlukDizi(PluginBase):
67
77
  secici = HTMLParser(istek.text)
68
78
 
69
79
  results = []
70
- for afis in secici.css("div.afis a.column"):
80
+ for afis in secici.css("div.afis a"):
71
81
  desc_el = afis.css_first("div.description")
72
82
  img_el = afis.css_first("img")
73
83
 
@@ -169,64 +179,54 @@ class SezonlukDizi(PluginBase):
169
179
  actors = actors
170
180
  )
171
181
 
172
- async def get_asp_data(self) -> tuple[str, str]:
173
- """Fetch dynamic ASP version numbers from site.min.js"""
174
- try:
175
- js_content = await self.httpx.get(f"{self.main_url}/js/site.min.js")
176
- alternatif_match = re.search(r'dataAlternatif(.*?)\.asp', js_content.text)
177
- embed_match = re.search(r'dataEmbed(.*?)\.asp', js_content.text)
178
-
179
- alternatif_ver = alternatif_match.group(1) if alternatif_match else "22"
180
- embed_ver = embed_match.group(1) if embed_match else "22"
181
-
182
- return (alternatif_ver, embed_ver)
183
- except Exception:
184
- return ("22", "22") # Fallback to default versions
185
-
186
182
  async def load_links(self, url: str) -> list[ExtractResult]:
187
183
  istek = await self.httpx.get(url)
188
184
  secici = HTMLParser(istek.text)
189
-
190
- dilsec_el = secici.css_first("div#dilsec")
191
- bid = dilsec_el.attrs.get("data-id") if dilsec_el else None
185
+ asp_data = await self._get_asp_data()
186
+
187
+ bid = secici.css_first("div#dilsec").attrs.get("data-id") if secici.css_first("div#dilsec") else None
192
188
  if not bid:
193
189
  return []
194
190
 
195
- # Get dynamic ASP versions
196
- alternatif_ver, embed_ver = await self.get_asp_data()
191
+ semaphore = asyncio.Semaphore(5)
192
+ tasks = []
197
193
 
198
- results = []
199
- for dil, label in [("1", "Altyazı"), ("0", "Dublaj")]:
200
- dil_istek = await self.httpx.post(
201
- url = f"{self.main_url}/ajax/dataAlternatif{alternatif_ver}.asp",
194
+ async def fetch_and_extract(veri, dil_etiketi):
195
+ async with semaphore:
196
+ try:
197
+ embed_resp = await self.httpx.post(
198
+ f"{self.main_url}/ajax/dataEmbed{asp_data['embed']}.asp",
199
+ headers = {"X-Requested-With": "XMLHttpRequest"},
200
+ data = {"id": str(veri.get("id"))}
201
+ )
202
+ embed_secici = HTMLParser(embed_resp.text)
203
+ iframe_el = embed_secici.css_first("iframe")
204
+ iframe_src = iframe_el.attrs.get("src") if iframe_el else None
205
+
206
+ if iframe_src:
207
+ if "link.asp" in iframe_src:
208
+ return None
209
+
210
+ iframe_url = self.fix_url(iframe_src)
211
+ return await self.extract(iframe_url, referer=f"{self.main_url}/", prefix=f"{dil_etiketi} - {veri.get('baslik')}")
212
+ except:
213
+ pass
214
+ return None
215
+
216
+ for dil_kodu, dil_etiketi in [("1", "Altyazı"), ("0", "Dublaj")]:
217
+ altyazi_resp = await self.httpx.post(
218
+ f"{self.main_url}/ajax/dataAlternatif{asp_data['alternatif']}.asp",
202
219
  headers = {"X-Requested-With": "XMLHttpRequest"},
203
- data = {"bid": bid, "dil": dil},
220
+ data = {"bid": bid, "dil": dil_kodu}
204
221
  )
205
-
222
+
206
223
  try:
207
- dil_json = dil_istek.json()
208
- except Exception:
224
+ data_json = altyazi_resp.json()
225
+ if data_json.get("status") == "success" and data_json.get("data"):
226
+ for veri in data_json["data"]:
227
+ tasks.append(fetch_and_extract(veri, dil_etiketi))
228
+ except:
209
229
  continue
210
230
 
211
- if dil_json.get("status") == "success":
212
- for idx, veri in enumerate(dil_json.get("data", [])):
213
- veri_response = await self.httpx.post(
214
- url = f"{self.main_url}/ajax/dataEmbed{embed_ver}.asp",
215
- headers = {"X-Requested-With": "XMLHttpRequest"},
216
- data = {"id": veri.get("id")},
217
- )
218
- veri_secici = HTMLParser(veri_response.text)
219
-
220
- iframe_el = veri_secici.css_first("iframe")
221
- iframe = iframe_el.attrs.get("src") if iframe_el else None
222
-
223
- if iframe:
224
- if "link.asp" in iframe:
225
- continue
226
-
227
- iframe_url = self.fix_url(iframe)
228
- data = await self.extract(iframe_url, prefix=label)
229
- if data:
230
- results.append(data)
231
-
232
- return results
231
+ results = await asyncio.gather(*tasks)
232
+ return [r for r in results if r]
@@ -175,49 +175,60 @@ class Sinefy(PluginBase):
175
175
  year = year_el.text(strip=True) if year_el else None
176
176
 
177
177
  episodes = []
178
- season_elements = sel.css("section.episodes-box")
178
+ episodes_box = sel.css_first("section.episodes-box")
179
179
 
180
- if season_elements:
181
- # Get season links
182
- season_links = []
183
- menu = sel.css("div.ui.vertical.fluid.tabular.menu a")
184
- for link in menu:
185
- href = link.attrs.get("href")
186
- if href:
187
- season_links.append(self.fix_url(href))
180
+ if episodes_box:
181
+ # Sezon menüsünden sezon linklerini al
182
+ season_menu = episodes_box.css("div.ui.vertical.fluid.tabular.menu a.item")
188
183
 
189
- for s_url in season_links:
190
- target_url = s_url if "/bolum-" in s_url else f"{s_url}/bolum-1"
191
-
192
- try:
193
- s_resp = await self.httpx.get(target_url)
194
- s_sel = HTMLParser(s_resp.text)
195
- ep_links = s_sel.css("div.ui.list.celled a.item")
184
+ # Sezon tab içeriklerini al
185
+ season_tabs = episodes_box.css("div.ui.tab")
186
+
187
+ # Eğer birden fazla sezon varsa, her sezon tab'ından bölümleri çek
188
+ if season_tabs:
189
+ for idx, season_tab in enumerate(season_tabs):
190
+ # Sezon numarasını belirle
191
+ current_season_no = idx + 1
192
+
193
+ # Menüden sezon numarasını almaya çalış
194
+ if idx < len(season_menu):
195
+ menu_href = season_menu[idx].attrs.get("href", "")
196
+ match = re.search(r"sezon-(\d+)", menu_href)
197
+ if match:
198
+ current_season_no = int(match.group(1))
196
199
 
197
- current_season_no = 1
198
- match = re.search(r"sezon-(\d+)", target_url)
199
- if match:
200
- current_season_no = int(match.group(1))
200
+ # Bu sezon tab'ından bölüm linklerini çek
201
+ ep_links = season_tab.css("a[href*='bolum']")
201
202
 
203
+ seen_urls = set()
202
204
  for ep_link in ep_links:
203
205
  href = ep_link.attrs.get("href")
204
- name_el = ep_link.css_first("div.content div.header")
205
- name = name_el.text(strip=True) if name_el else ""
206
+ if not href or href in seen_urls:
207
+ continue
208
+ seen_urls.add(href)
206
209
 
207
- if href:
208
- ep_no = 0
209
- match_ep = re.search(r"bolum-(\d+)", href)
210
- if match_ep:
211
- ep_no = int(match_ep.group(1))
212
-
210
+ # Bölüm numarasını URL'den çıkar
211
+ ep_no = 0
212
+ match_ep = re.search(r"bolum-(\d+)", href)
213
+ if match_ep:
214
+ ep_no = int(match_ep.group(1))
215
+
216
+ # Bölüm başlığını çıkar (önce title attribute, sonra text)
217
+ name = ep_link.attrs.get("title", "")
218
+ if not name:
219
+ name_el = ep_link.css_first("div.content div.header")
220
+ if name_el:
221
+ name = name_el.text(strip=True)
222
+ else:
223
+ name = ep_link.text(strip=True)
224
+
225
+ if href and ep_no > 0:
213
226
  episodes.append(Episode(
214
- season = current_season_no,
227
+ season = current_season_no,
215
228
  episode = ep_no,
216
- title = name,
217
- url = self.fix_url(href)
229
+ title = name.strip() if name else f"{ep_no}. Bölüm",
230
+ url = self.fix_url(href)
218
231
  ))
219
- except Exception:
220
- pass
221
232
 
222
233
  if episodes:
223
234
  return SeriesInfo(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: KekikStream
3
- Version: 2.2.8
3
+ Version: 2.3.0
4
4
  Summary: terminal üzerinden medya içeriği aramanızı ve VLC/MPV gibi popüler medya oynatıcılar aracılığıyla doğrudan izlemenizi sağlayan modüler ve genişletilebilir bir bıdı bıdı
5
5
  Home-page: https://github.com/keyiflerolsun/KekikStream
6
6
  Author: keyiflerolsun
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: KekikStream
3
- Version: 2.2.8
3
+ Version: 2.3.0
4
4
  Summary: terminal üzerinden medya içeriği aramanızı ve VLC/MPV gibi popüler medya oynatıcılar aracılığıyla doğrudan izlemenizi sağlayan modüler ve genişletilebilir bir bıdı bıdı
5
5
  Home-page: https://github.com/keyiflerolsun/KekikStream
6
6
  Author: keyiflerolsun
@@ -6,7 +6,7 @@ from io import open
6
6
  setup(
7
7
  # ? Genel Bilgiler
8
8
  name = "KekikStream",
9
- version = "2.2.8",
9
+ version = "2.3.0",
10
10
  url = "https://github.com/keyiflerolsun/KekikStream",
11
11
  description = "terminal üzerinden medya içeriği aramanızı ve VLC/MPV gibi popüler medya oynatıcılar aracılığıyla doğrudan izlemenizi sağlayan modüler ve genişletilebilir bir bıdı bıdı",
12
12
  keywords = ["KekikStream", "KekikAkademi", "keyiflerolsun"],
File without changes
File without changes
File without changes
File without changes