KekikStream 1.7.1__py3-none-any.whl → 2.2.0__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 +20 -9
  2. KekikStream/Core/Extractor/ExtractorLoader.py +25 -17
  3. KekikStream/Core/Extractor/ExtractorManager.py +53 -9
  4. KekikStream/Core/Extractor/ExtractorModels.py +5 -7
  5. KekikStream/Core/Extractor/YTDLPCache.py +35 -0
  6. KekikStream/Core/Media/MediaHandler.py +44 -26
  7. KekikStream/Core/Media/MediaManager.py +0 -3
  8. KekikStream/Core/Plugin/PluginBase.py +82 -22
  9. KekikStream/Core/Plugin/PluginLoader.py +11 -7
  10. KekikStream/Core/Plugin/PluginModels.py +25 -26
  11. KekikStream/Core/__init__.py +1 -0
  12. KekikStream/Extractors/CloseLoad.py +21 -7
  13. KekikStream/Extractors/ContentX.py +21 -6
  14. KekikStream/Extractors/DonilasPlay.py +86 -0
  15. KekikStream/Extractors/DzenRu.py +38 -0
  16. KekikStream/Extractors/ExPlay.py +53 -0
  17. KekikStream/Extractors/Filemoon.py +78 -0
  18. KekikStream/Extractors/HDPlayerSystem.py +41 -0
  19. KekikStream/Extractors/JetTv.py +45 -0
  20. KekikStream/Extractors/MailRu.py +3 -4
  21. KekikStream/Extractors/MixPlayHD.py +2 -3
  22. KekikStream/Extractors/MixTiger.py +57 -0
  23. KekikStream/Extractors/MolyStream.py +5 -5
  24. KekikStream/Extractors/Odnoklassniki.py +13 -7
  25. KekikStream/Extractors/PeaceMakerst.py +10 -5
  26. KekikStream/Extractors/PixelDrain.py +1 -2
  27. KekikStream/Extractors/PlayerFilmIzle.py +65 -0
  28. KekikStream/Extractors/RapidVid.py +23 -8
  29. KekikStream/Extractors/SetPlay.py +66 -0
  30. KekikStream/Extractors/SetPrime.py +45 -0
  31. KekikStream/Extractors/SibNet.py +2 -3
  32. KekikStream/Extractors/Sobreatsesuyp.py +4 -5
  33. KekikStream/Extractors/TRsTX.py +4 -5
  34. KekikStream/Extractors/TauVideo.py +2 -3
  35. KekikStream/Extractors/TurboImgz.py +2 -3
  36. KekikStream/Extractors/TurkeyPlayer.py +34 -0
  37. KekikStream/Extractors/VCTPlay.py +41 -0
  38. KekikStream/Extractors/VidHide.py +81 -0
  39. KekikStream/Extractors/VidMoly.py +55 -34
  40. KekikStream/Extractors/VidMoxy.py +2 -3
  41. KekikStream/Extractors/VidPapi.py +89 -0
  42. KekikStream/Extractors/VideoSeyred.py +3 -4
  43. KekikStream/Extractors/YTDLP.py +211 -0
  44. KekikStream/Extractors/YildizKisaFilm.py +41 -0
  45. KekikStream/Plugins/BelgeselX.py +196 -0
  46. KekikStream/Plugins/DiziBox.py +25 -34
  47. KekikStream/Plugins/DiziPal.py +24 -35
  48. KekikStream/Plugins/DiziYou.py +54 -37
  49. KekikStream/Plugins/Dizilla.py +66 -46
  50. KekikStream/Plugins/FilmBip.py +142 -0
  51. KekikStream/Plugins/FilmMakinesi.py +36 -28
  52. KekikStream/Plugins/FilmModu.py +20 -24
  53. KekikStream/Plugins/FullHDFilm.py +220 -0
  54. KekikStream/Plugins/FullHDFilmizlesene.py +9 -15
  55. KekikStream/Plugins/HDFilmCehennemi.py +141 -69
  56. KekikStream/Plugins/JetFilmizle.py +85 -52
  57. KekikStream/Plugins/KultFilmler.py +217 -0
  58. KekikStream/Plugins/RecTV.py +22 -34
  59. KekikStream/Plugins/RoketDizi.py +222 -0
  60. KekikStream/Plugins/SelcukFlix.py +328 -0
  61. KekikStream/Plugins/SetFilmIzle.py +252 -0
  62. KekikStream/Plugins/SezonlukDizi.py +54 -21
  63. KekikStream/Plugins/SineWix.py +17 -29
  64. KekikStream/Plugins/Sinefy.py +241 -0
  65. KekikStream/Plugins/SinemaCX.py +154 -0
  66. KekikStream/Plugins/Sinezy.py +143 -0
  67. KekikStream/Plugins/SuperFilmGeldi.py +130 -0
  68. KekikStream/Plugins/UgurFilm.py +13 -19
  69. KekikStream/__init__.py +47 -56
  70. KekikStream/requirements.txt +3 -4
  71. kekikstream-2.2.0.dist-info/METADATA +312 -0
  72. kekikstream-2.2.0.dist-info/RECORD +81 -0
  73. KekikStream/Extractors/FourCX.py +0 -7
  74. KekikStream/Extractors/FourPichive.py +0 -7
  75. KekikStream/Extractors/FourPlayRu.py +0 -7
  76. KekikStream/Extractors/HDStreamAble.py +0 -7
  77. KekikStream/Extractors/Hotlinger.py +0 -7
  78. KekikStream/Extractors/OkRuHTTP.py +0 -7
  79. KekikStream/Extractors/OkRuSSL.py +0 -7
  80. KekikStream/Extractors/Pichive.py +0 -7
  81. KekikStream/Extractors/PlayRu.py +0 -7
  82. KekikStream/Extractors/VidMolyMe.py +0 -7
  83. kekikstream-1.7.1.dist-info/METADATA +0 -109
  84. kekikstream-1.7.1.dist-info/RECORD +0 -63
  85. {kekikstream-1.7.1.dist-info → kekikstream-2.2.0.dist-info}/WHEEL +0 -0
  86. {kekikstream-1.7.1.dist-info → kekikstream-2.2.0.dist-info}/entry_points.txt +0 -0
  87. {kekikstream-1.7.1.dist-info → kekikstream-2.2.0.dist-info}/licenses/LICENSE +0 -0
  88. {kekikstream-1.7.1.dist-info → kekikstream-2.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,220 @@
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 FullHDFilm(PluginBase):
8
+ name = "FullHDFilm"
9
+ language = "tr"
10
+ main_url = "https://hdfilm.us"
11
+ favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
12
+ description = "Full HD Film izle, Türkçe Dublaj ve Altyazılı filmler."
13
+
14
+ main_page = {
15
+ f"{main_url}/tur/turkce-altyazili-film-izle" : "Altyazılı Filmler",
16
+ f"{main_url}/tur/netflix-filmleri-izle" : "Netflix",
17
+ f"{main_url}/tur/yerli-film-izle" : "Yerli Film",
18
+ f"{main_url}/category/aile-filmleri-izle" : "Aile",
19
+ f"{main_url}/category/aksiyon-filmleri-izle" : "Aksiyon",
20
+ f"{main_url}/category/animasyon-filmleri-izle" : "Animasyon",
21
+ f"{main_url}/category/belgesel-filmleri-izle" : "Belgesel",
22
+ f"{main_url}/category/bilim-kurgu-filmleri-izle" : "Bilim Kurgu",
23
+ f"{main_url}/category/biyografi-filmleri-izle" : "Biyografi",
24
+ f"{main_url}/category/dram-filmleri-izle" : "Dram",
25
+ f"{main_url}/category/fantastik-filmler-izle" : "Fantastik",
26
+ f"{main_url}/category/gerilim-filmleri-izle" : "Gerilim",
27
+ f"{main_url}/category/gizem-filmleri-izle" : "Gizem",
28
+ f"{main_url}/category/kisa" : "Kısa",
29
+ f"{main_url}/category/komedi-filmleri-izle" : "Komedi",
30
+ f"{main_url}/category/korku-filmleri-izle" : "Korku",
31
+ f"{main_url}/category/macera-filmleri-izle" : "Macera",
32
+ f"{main_url}/category/muzik" : "Müzik",
33
+ f"{main_url}/category/muzikal-filmleri-izle" : "Müzikal",
34
+ f"{main_url}/category/romantik-filmler-izle" : "Romantik",
35
+ f"{main_url}/category/savas-filmleri-izle" : "Savaş",
36
+ f"{main_url}/category/spor-filmleri-izle" : "Spor",
37
+ f"{main_url}/category/suc-filmleri-izle" : "Suç",
38
+ f"{main_url}/category/tarih-filmleri-izle" : "Tarih",
39
+ f"{main_url}/category/western-filmleri-izle" : "Western",
40
+ }
41
+
42
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
43
+ page_url = url if page == 1 else f"{url}/page/{page}"
44
+
45
+ self.httpx.headers.update({
46
+ "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
47
+ "Referer" : f"{self.main_url}/"
48
+ })
49
+
50
+ istek = await self.httpx.get(page_url)
51
+ secici = Selector(istek.text)
52
+
53
+ return [
54
+ MainPageResult(
55
+ category = category,
56
+ title = veri.css("img::attr(alt)").get(),
57
+ url = self.fix_url(veri.css("a::attr(href)").get()),
58
+ poster = self.fix_url(veri.css("img::attr(src)").get()),
59
+ )
60
+ for veri in secici.css("div.movie-poster")
61
+ if veri.css("img::attr(alt)").get()
62
+ ]
63
+
64
+ async def search(self, query: str) -> list[SearchResult]:
65
+ istek = await self.httpx.get(f"{self.main_url}/?s={query}")
66
+ secici = Selector(istek.text)
67
+
68
+ return [
69
+ SearchResult(
70
+ title = veri.css("img::attr(alt)").get(),
71
+ url = self.fix_url(veri.css("a::attr(href)").get()),
72
+ poster = self.fix_url(veri.css("img::attr(src)").get()),
73
+ )
74
+ for veri in secici.css("div.movie-poster")
75
+ if veri.css("img::attr(alt)").get()
76
+ ]
77
+
78
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
79
+ istek = await self.httpx.get(url)
80
+ secici = Selector(istek.text)
81
+
82
+ title = secici.css("h1::text").get() or ""
83
+ title = title.strip() if title else ""
84
+ poster = self.fix_url(secici.css("div.poster img::attr(src)").get() or "")
85
+
86
+ actors_text = secici.css("div.oyuncular.info::text").get()
87
+ if actors_text:
88
+ actors_text = actors_text.replace("Oyuncular:", "").strip()
89
+ actors = [a.strip() for a in actors_text.split(",")]
90
+ else:
91
+ actors = []
92
+
93
+ year = secici.css("div.yayin-tarihi.info::text").re_first(r"(\d{4})")
94
+ tags = secici.css("div.tur.info a::text").getall()
95
+ rating = secici.css("div.imdb::text").re_first(r"IMDb\s*([\d\.]+)")
96
+
97
+ # Description
98
+ description = secici.xpath("//div[contains(@class, 'others')]/preceding-sibling::div[1]//text()").getall()
99
+ description = "".join(description).strip() if description else None
100
+
101
+ # Kotlin referansı: URL'de -dizi kontrolü veya tags içinde "dizi" kontrolü
102
+ is_series = "-dizi" in url.lower() or any("dizi" in tag.lower() for tag in tags)
103
+
104
+ if is_series:
105
+ episodes = []
106
+ part_elements = secici.css("li.psec")
107
+ part_names = secici.css("li.psec a::text").getall()
108
+
109
+ # pdata değerlerini çıkar
110
+ pdata_matches = re.findall(r"pdata\['([^']+)'\]\s*=\s*'([^']+)'", istek.text)
111
+
112
+ for idx, (part_id, part_name) in enumerate(zip([el.css("::attr(id)").get() for el in part_elements], part_names)):
113
+ if not part_name:
114
+ continue
115
+
116
+ part_name = part_name.strip()
117
+
118
+ # Fragman'ları atla
119
+ if "fragman" in part_name.lower() or (part_id and "fragman" in part_id.lower()):
120
+ continue
121
+
122
+ # Sezon ve bölüm numarası çıkar
123
+ sz_match = re.search(r'(\d+)\s*sezon', part_id.lower() if part_id else "")
124
+ ep_match = re.search(r'^(\d+)\.', part_name)
125
+
126
+ sz_num = int(sz_match.group(1)) if sz_match else 1
127
+ ep_num = int(ep_match.group(1)) if ep_match else idx + 1
128
+
129
+ # pdata'dan video URL'si çık (varsa)
130
+ video_url = url # Varsayılan olarak ana URL kullan
131
+ if idx < len(pdata_matches):
132
+ video_url = pdata_matches[idx][1] if pdata_matches[idx][1] else url
133
+
134
+ episodes.append(Episode(
135
+ season = sz_num,
136
+ episode = ep_num,
137
+ title = f"{sz_num}. Sezon {ep_num}. Bölüm",
138
+ url = url # Bölüm URL'leri load_links'te işlenecek
139
+ ))
140
+
141
+ return SeriesInfo(
142
+ url = url,
143
+ poster = poster,
144
+ title = self.clean_title(title) if title else "",
145
+ description = description,
146
+ tags = tags,
147
+ year = year,
148
+ actors = actors,
149
+ rating = rating.strip() if rating else None,
150
+ episodes = episodes
151
+ )
152
+ else:
153
+ return MovieInfo(
154
+ url = url,
155
+ poster = poster,
156
+ title = self.clean_title(title) if title else "",
157
+ description = description,
158
+ tags = tags,
159
+ year = year,
160
+ actors = actors,
161
+ rating = rating.strip() if rating else None,
162
+ )
163
+
164
+ def _get_iframe(self, source_code: str) -> str:
165
+ """Base64 kodlu iframe'i çözümle"""
166
+ match = re.search(r'<script[^>]*>(PCEtLWJhc2xpazp[^<]*)</script>', source_code)
167
+ if not match:
168
+ return ""
169
+
170
+ try:
171
+ decoded_html = base64.b64decode(match[1]).decode("utf-8")
172
+ iframe_match = re.search(r'<iframe[^>]+src=["\']([^"\']+)["\']', decoded_html)
173
+ return self.fix_url(iframe_match[1]) if iframe_match else ""
174
+ except Exception:
175
+ return ""
176
+
177
+ def _extract_subtitle_url(self, source_code: str) -> str | None:
178
+ """playerjsSubtitle değişkeninden .srt URL çıkar"""
179
+ patterns = [
180
+ r'var playerjsSubtitle = "\[Türkçe\](https?://[^\s"]+?\.srt)";',
181
+ r'var playerjsSubtitle = "(https?://[^\s"]+?\.srt)";',
182
+ r'subtitle:\s*"(https?://[^\s"]+?\.srt)"',
183
+ ]
184
+
185
+ for pattern in patterns:
186
+ if match := re.search(pattern, source_code):
187
+ return match[1]
188
+
189
+ return None
190
+
191
+ async def load_links(self, url: str) -> list[ExtractResult]:
192
+ self.httpx.headers.update({
193
+ "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
194
+ "Referer" : self.main_url
195
+ })
196
+
197
+ istek = await self.httpx.get(url)
198
+ source_code = istek.text
199
+
200
+ # Ana sayfadan altyazı URL'sini çek
201
+ subtitle_url = self._extract_subtitle_url(source_code)
202
+
203
+ # Iframe'den altyazı URL'sini çek
204
+ iframe_src = self._get_iframe(source_code)
205
+
206
+ if not subtitle_url and iframe_src:
207
+ iframe_istek = await self.httpx.get(iframe_src)
208
+ subtitle_url = self._extract_subtitle_url(iframe_istek.text)
209
+
210
+ results = []
211
+
212
+ if iframe_src:
213
+ data = await self.extract(iframe_src)
214
+ if data:
215
+ # ExtractResult objesi immutable, yeni bir kopya oluştur
216
+ subtitles = [Subtitle(name="Türkçe", url=subtitle_url)] if subtitle_url else []
217
+ updated_data = data.model_copy(update={"subtitles": subtitles}) if subtitles else data
218
+ results.append(updated_data)
219
+
220
+ return results
@@ -1,6 +1,6 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import kekik_cache, PluginBase, MainPageResult, SearchResult, MovieInfo
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult
4
4
  from parsel import Selector
5
5
  from Kekik.Sifreleme import StringCodec
6
6
  import json, re
@@ -10,7 +10,7 @@ class FullHDFilmizlesene(PluginBase):
10
10
  language = "tr"
11
11
  main_url = "https://www.fullhdfilmizlesene.tv"
12
12
  favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
13
- description = "Sinema zevkini evinize kadar getirdik. Türkiye'nin lider Film sitesinde, en yeni filmleri Full HD izleyin."
13
+ description = "Türkiye'nin ilk ve lider HD film izleme platformu, kaliteli ve sorunsuz hizmetiyle sinema keyfini zirveye taşır."
14
14
 
15
15
  main_page = {
16
16
  f"{main_url}/en-cok-izlenen-hd-filmler/" : "En Çok izlenen Filmler",
@@ -40,7 +40,6 @@ class FullHDFilmizlesene(PluginBase):
40
40
  f"{main_url}/filmizle/yerli-filmler-hd-izle/" : "Yerli Filmler"
41
41
  }
42
42
 
43
- #@kekik_cache(ttl=60*60)
44
43
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
45
44
  istek = self.cloudscraper.get(f"{url}{page}")
46
45
  secici = Selector(istek.text)
@@ -55,9 +54,8 @@ class FullHDFilmizlesene(PluginBase):
55
54
  for veri in secici.css("li.film")
56
55
  ]
57
56
 
58
- #@kekik_cache(ttl=60*60)
59
57
  async def search(self, query: str) -> list[SearchResult]:
60
- istek = await self.cffi.get(f"{self.main_url}/arama/{query}")
58
+ istek = await self.httpx.get(f"{self.main_url}/arama/{query}")
61
59
  secici = Selector(istek.text)
62
60
 
63
61
  results = []
@@ -77,9 +75,8 @@ class FullHDFilmizlesene(PluginBase):
77
75
 
78
76
  return results
79
77
 
80
- #@kekik_cache(ttl=60*60)
81
78
  async def load_item(self, url: str) -> MovieInfo:
82
- istek = await self.cffi.get(url)
79
+ istek = await self.httpx.get(url)
83
80
  secici = Selector(istek.text)
84
81
 
85
82
  title = secici.xpath("normalize-space(//div[@class='izle-titles'])").get().strip()
@@ -103,9 +100,8 @@ class FullHDFilmizlesene(PluginBase):
103
100
  duration = duration
104
101
  )
105
102
 
106
- #@kekik_cache(ttl=15*60)
107
- async def load_links(self, url: str) -> list[dict]:
108
- istek = await self.cffi.get(url)
103
+ async def load_links(self, url: str) -> list[ExtractResult]:
104
+ istek = await self.httpx.get(url)
109
105
  secici = Selector(istek.text)
110
106
 
111
107
  script = secici.xpath("(//script)[1]").get()
@@ -123,10 +119,8 @@ class FullHDFilmizlesene(PluginBase):
123
119
  response = []
124
120
  for link in link_list:
125
121
  link = f"https:{link}" if link.startswith("//") else link
126
- extractor = self.ex_manager.find_extractor(link)
127
- response.append({
128
- "url" : link,
129
- "name" : extractor.name if extractor else "Direct Link"
130
- })
122
+ data = await self.extract(link)
123
+ if data:
124
+ response.append(data)
131
125
 
132
126
  return response
@@ -1,6 +1,6 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import kekik_cache, PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult, Subtitle
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, Subtitle, ExtractResult
4
4
  from parsel import Selector
5
5
  from Kekik.Sifreleme import Packer, StreamDecoder
6
6
  import random, string, re
@@ -10,7 +10,7 @@ class HDFilmCehennemi(PluginBase):
10
10
  language = "tr"
11
11
  main_url = "https://www.hdfilmcehennemi.ws"
12
12
  favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
13
- description = "Türkiye'nin en hızlı hd film izleme sitesi"
13
+ description = "Türkiye'nin en hızlı hd film izleme sitesi. Tek ve gerçek hdfilmcehennemi sitesi."
14
14
 
15
15
  main_page = {
16
16
  f"{main_url}" : "Yeni Eklenen Filmler",
@@ -29,9 +29,8 @@ class HDFilmCehennemi(PluginBase):
29
29
  f"{main_url}/tur/romantik-filmleri-izle-1" : "Romantik Filmleri"
30
30
  }
31
31
 
32
- #@kekik_cache(ttl=60*60)
33
32
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
34
- istek = await self.cffi.get(f"{url}", allow_redirects=True)
33
+ istek = await self.httpx.get(f"{url}", follow_redirects=True)
35
34
  secici = Selector(istek.text)
36
35
 
37
36
  return [
@@ -44,9 +43,8 @@ class HDFilmCehennemi(PluginBase):
44
43
  for veri in secici.css("div.section-content a.poster")
45
44
  ]
46
45
 
47
- #@kekik_cache(ttl=60*60)
48
46
  async def search(self, query: str) -> list[SearchResult]:
49
- istek = await self.cffi.get(
47
+ istek = await self.httpx.get(
50
48
  url = f"{self.main_url}/search/?q={query}",
51
49
  headers = {
52
50
  "Referer" : f"{self.main_url}/",
@@ -73,46 +71,84 @@ class HDFilmCehennemi(PluginBase):
73
71
 
74
72
  return results
75
73
 
76
- #@kekik_cache(ttl=60*60)
77
- async def load_item(self, url: str) -> MovieInfo:
78
- istek = await self.cffi.get(url, headers = {"Referer": f"{self.main_url}/"})
74
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
75
+ istek = await self.httpx.get(url, headers = {"Referer": f"{self.main_url}/"})
79
76
  secici = Selector(istek.text)
80
77
 
81
- title = secici.css("h1.section-title::text").get().strip()
82
- poster = secici.css("aside.post-info-poster img.lazyload::attr(data-src)").get().strip()
83
- description = secici.css("article.post-info-content > p::text").get().strip()
78
+ title = secici.css("h1.section-title::text").get()
79
+ title = title.strip() if title else ""
80
+ poster = secici.css("aside.post-info-poster img.lazyload::attr(data-src)").get() or ""
81
+ poster = poster.strip() if poster else ""
82
+ description = secici.css("article.post-info-content > p::text").get() or ""
83
+ description = description.strip() if description else ""
84
84
  tags = secici.css("div.post-info-genres a::text").getall()
85
- rating = secici.css("div.post-info-imdb-rating span::text").get().strip()
86
- year = secici.css("div.post-info-year-country a::text").get().strip()
85
+ rating = secici.css("div.post-info-imdb-rating span::text").get() or ""
86
+ rating = rating.strip() if rating else ""
87
+ year = secici.css("div.post-info-year-country a::text").get() or ""
88
+ year = year.strip() if year else ""
87
89
  actors = secici.css("div.post-info-cast a > strong::text").getall()
88
- duration = secici.css("div.post-info-duration::text").get().replace("dakika", "").strip()
90
+ duration = secici.css("div.post-info-duration::text").get() or "0"
91
+ duration = duration.replace("dakika", "").strip()
89
92
 
90
-
91
93
  try:
92
- duration_minutes = int(duration[2:-1])
94
+ duration_minutes = int(re.search(r'\d+', duration).group()) if re.search(r'\d+', duration) else 0
93
95
  except Exception:
94
96
  duration_minutes = 0
95
97
 
96
- return MovieInfo(
97
- url = url,
98
- poster = self.fix_url(poster),
99
- title = self.clean_title(title),
100
- description = description,
101
- tags = tags,
102
- rating = rating,
103
- year = year,
104
- actors = actors,
105
- duration = duration_minutes
106
- )
98
+ # Dizi mi film mi kontrol et (Kotlin referansı: div.seasons kontrolü)
99
+ is_series = len(secici.css("div.seasons").getall()) > 0
100
+
101
+ if is_series:
102
+ episodes = []
103
+ for ep in secici.css("div.seasons-tab-content a"):
104
+ ep_name = ep.css("h4::text").get()
105
+ ep_href = ep.css("::attr(href)").get()
106
+ if ep_name and ep_href:
107
+ ep_name = ep_name.strip()
108
+ # Regex ile sezon ve bölüm numarası çıkar
109
+ ep_match = re.search(r'(\d+)\.\s*Bölüm', ep_name)
110
+ sz_match = re.search(r'(\d+)\.\s*Sezon', ep_name)
111
+ ep_num = int(ep_match.group(1)) if ep_match else 1
112
+ sz_num = int(sz_match.group(1)) if sz_match else 1
113
+
114
+ episodes.append(Episode(
115
+ season = sz_num,
116
+ episode = ep_num,
117
+ title = ep_name,
118
+ url = self.fix_url(ep_href)
119
+ ))
120
+
121
+ return SeriesInfo(
122
+ url = url,
123
+ poster = self.fix_url(poster),
124
+ title = self.clean_title(title),
125
+ description = description,
126
+ tags = tags,
127
+ rating = rating,
128
+ year = year,
129
+ actors = actors,
130
+ episodes = episodes
131
+ )
132
+ else:
133
+ return MovieInfo(
134
+ url = url,
135
+ poster = self.fix_url(poster),
136
+ title = self.clean_title(title),
137
+ description = description,
138
+ tags = tags,
139
+ rating = rating,
140
+ year = year,
141
+ actors = actors,
142
+ duration = duration_minutes
143
+ )
107
144
 
108
145
  def generate_random_cookie(self):
109
146
  return "".join(random.choices(string.ascii_letters + string.digits, k=16))
110
147
 
111
- #@kekik_cache(ttl=15*60)
112
- async def cehennempass(self, video_id: str) -> list[dict]:
148
+ async def cehennempass(self, video_id: str) -> list:
113
149
  results = []
114
150
 
115
- istek = await self.cffi.post(
151
+ istek = await self.httpx.post(
116
152
  url = "https://cehennempass.pw/process_quality_selection.php",
117
153
  headers = {
118
154
  "Referer" : f"https://cehennempass.pw/download/{video_id}",
@@ -123,13 +159,13 @@ class HDFilmCehennemi(PluginBase):
123
159
  data = {"video_id": video_id, "selected_quality": "low"},
124
160
  )
125
161
  if video_url := istek.json().get("download_link"):
126
- results.append({
127
- "url" : self.fix_url(video_url),
128
- "name" : "Düşük Kalite",
129
- "referer" : f"https://cehennempass.pw/download/{video_id}"
130
- })
162
+ results.append(ExtractResult(
163
+ url = self.fix_url(video_url),
164
+ name = "Düşük Kalite",
165
+ referer = f"https://cehennempass.pw/download/{video_id}"
166
+ ))
131
167
 
132
- istek = await self.cffi.post(
168
+ istek = await self.httpx.post(
133
169
  url = "https://cehennempass.pw/process_quality_selection.php",
134
170
  headers = {
135
171
  "Referer" : f"https://cehennempass.pw/download/{video_id}",
@@ -140,26 +176,64 @@ class HDFilmCehennemi(PluginBase):
140
176
  data = {"video_id": video_id, "selected_quality": "high"},
141
177
  )
142
178
  if video_url := istek.json().get("download_link"):
143
- results.append({
144
- "url" : self.fix_url(video_url),
145
- "name" : "Yüksek Kalite",
146
- "referer" : f"https://cehennempass.pw/download/{video_id}"
147
- })
179
+ results.append(ExtractResult(
180
+ url = self.fix_url(video_url),
181
+ name = "Yüksek Kalite",
182
+ referer = f"https://cehennempass.pw/download/{video_id}"
183
+ ))
148
184
 
149
185
  return results
150
186
 
151
- #@kekik_cache(ttl=15*60)
187
+ def extract_hdch_url(self, unpacked: str) -> str:
188
+ """HDFilmCehennemi unpacked script'ten video URL'sini çıkar"""
189
+ # 1) Decode fonksiyonunun adını bul: function <NAME>(value_parts)
190
+ match_fn = re.search(r'function\s+(\w+)\s*\(\s*value_parts\s*\)', unpacked)
191
+ if not match_fn:
192
+ return ""
193
+
194
+ fn_name = match_fn.group(1)
195
+
196
+ # 2) Bu fonksiyonun array ile çağrıldığı yeri bul: <NAME>([ ... ])
197
+ array_call_regex = re.compile(rf'{re.escape(fn_name)}\(\s*\[(.*?)\]\s*\)', re.DOTALL)
198
+ match_call = array_call_regex.search(unpacked)
199
+ if not match_call:
200
+ return ""
201
+
202
+ array_body = match_call.group(1)
203
+
204
+ # 3) Array içindeki string parçalarını topla
205
+ parts = re.findall(r'["\']([^"\']+)["\']', array_body)
206
+ if not parts:
207
+ return ""
208
+
209
+ # 4) Özel decoder ile çöz
210
+ return StreamDecoder._brute_force(parts)
211
+
152
212
  async def invoke_local_source(self, iframe: str, source: str, url: str):
153
- self.cffi.headers.update({"Referer": f"{self.main_url}/"})
154
- istek = await self.cffi.get(iframe)
213
+ self.httpx.headers.update({
214
+ "Referer": f"{self.main_url}/",
215
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0"
216
+ })
217
+ istek = await self.httpx.get(iframe)
218
+
219
+ if not istek.text:
220
+ return await self.cehennempass(iframe.split("/")[-1])
221
+
222
+ # eval(function...) içeren packed script bul
223
+ eval_match = re.search(r'(eval\(function[\s\S]+)', istek.text)
224
+ if not eval_match:
225
+ return await self.cehennempass(iframe.split("/")[-1])
155
226
 
156
227
  try:
157
- eval_func = re.compile(r'\s*(eval\(function[\s\S].*)\s*').findall(istek.text)[0]
228
+ unpacked = Packer.unpack(eval_match.group(1))
158
229
  except Exception:
159
230
  return await self.cehennempass(iframe.split("/")[-1])
160
231
 
161
- unpacked = Packer.unpack(eval_func)
162
- video_url = StreamDecoder.extract_stream_url(unpacked)
232
+ # HDFilmCehennemi özel decoder ile video URL'sini çıkar
233
+ video_url = self.extract_hdch_url(unpacked)
234
+
235
+ if not video_url:
236
+ return await self.cehennempass(iframe.split("/")[-1])
163
237
 
164
238
  subtitles = []
165
239
  try:
@@ -172,16 +246,15 @@ class HDFilmCehennemi(PluginBase):
172
246
  except Exception:
173
247
  pass
174
248
 
175
- return [{
176
- "url" : video_url,
177
- "name" : source,
178
- "referer" : url,
179
- "subtitles" : subtitles
180
- }]
249
+ return [ExtractResult(
250
+ url = video_url,
251
+ name = source,
252
+ referer = url,
253
+ subtitles = subtitles
254
+ )]
181
255
 
182
- #@kekik_cache(ttl=15*60)
183
- async def load_links(self, url: str) -> list[dict]:
184
- istek = await self.cffi.get(url)
256
+ async def load_links(self, url: str) -> list[ExtractResult]:
257
+ istek = await self.httpx.get(url)
185
258
  secici = Selector(istek.text)
186
259
 
187
260
  results = []
@@ -192,7 +265,7 @@ class HDFilmCehennemi(PluginBase):
192
265
  source = f"{link.css('::text').get().replace('(HDrip Xbet)', '').strip()} {lang_code}"
193
266
  video_id = link.css("::attr(data-video)").get()
194
267
 
195
- api_get = await self.cffi.get(
268
+ api_get = await self.httpx.get(
196
269
  url = f"{self.main_url}/video/{video_id}/",
197
270
  headers = {
198
271
  "Content-Type" : "application/json",
@@ -204,8 +277,15 @@ class HDFilmCehennemi(PluginBase):
204
277
  match = re.search(r'data-src=\\\"([^"]+)', api_get.text)
205
278
  iframe = match[1].replace("\\", "") if match else None
206
279
 
207
- if iframe and "?rapidrame_id=" in iframe:
208
- iframe = f"{self.main_url}/playerr/{iframe.split('?rapidrame_id=')[1]}"
280
+ if not iframe:
281
+ continue
282
+
283
+ # mobi URL'si varsa direkt kullan (query string'i kaldır)
284
+ if "mobi" in iframe:
285
+ iframe = iframe.split("?")[0] # rapidrame_id query param'ı kaldır
286
+ # mobi değilse ve rapidrame varsa rplayer kullan
287
+ elif "rapidrame" in iframe and "?rapidrame_id=" in iframe:
288
+ iframe = f"{self.main_url}/rplayer/{iframe.split('?rapidrame_id=')[1]}"
209
289
 
210
290
  video_data_list = await self.invoke_local_source(iframe, source, url)
211
291
  if not video_data_list:
@@ -214,12 +294,4 @@ class HDFilmCehennemi(PluginBase):
214
294
  for video_data in video_data_list:
215
295
  results.append(video_data)
216
296
 
217
- return results
218
-
219
- async def play(self, name: str, url: str, referer: str, subtitles: list[Subtitle]):
220
- extract_result = ExtractResult(name=name, url=url, referer=referer, subtitles=subtitles)
221
- self.media_handler.title = name
222
- if self.name not in self.media_handler.title:
223
- self.media_handler.title = f"{self.name} | {self.media_handler.title}"
224
-
225
- self.media_handler.play_media(extract_result)
297
+ return results