KekikStream 1.3.3__py3-none-any.whl → 1.7.1__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 (47) hide show
  1. KekikStream/CLI/pypi_kontrol.py +6 -6
  2. KekikStream/Core/Extractor/ExtractorBase.py +6 -11
  3. KekikStream/Core/Extractor/ExtractorModels.py +2 -2
  4. KekikStream/Core/Media/MediaHandler.py +9 -6
  5. KekikStream/Core/Plugin/PluginBase.py +35 -21
  6. KekikStream/Core/Plugin/PluginModels.py +1 -0
  7. KekikStream/Extractors/CloseLoad.py +8 -27
  8. KekikStream/Extractors/ContentX.py +6 -4
  9. KekikStream/Extractors/MailRu.py +4 -5
  10. KekikStream/Extractors/MixPlayHD.py +3 -2
  11. KekikStream/Extractors/MolyStream.py +23 -5
  12. KekikStream/Extractors/Odnoklassniki.py +14 -9
  13. KekikStream/Extractors/PeaceMakerst.py +5 -4
  14. KekikStream/Extractors/PixelDrain.py +2 -2
  15. KekikStream/Extractors/RapidVid.py +33 -15
  16. KekikStream/Extractors/SibNet.py +3 -3
  17. KekikStream/Extractors/Sobreatsesuyp.py +5 -4
  18. KekikStream/Extractors/TRsTX.py +5 -4
  19. KekikStream/Extractors/TauVideo.py +3 -2
  20. KekikStream/Extractors/TurboImgz.py +3 -2
  21. KekikStream/Extractors/VidMoly.py +23 -21
  22. KekikStream/Extractors/VidMoxy.py +3 -3
  23. KekikStream/Extractors/VideoSeyred.py +4 -3
  24. KekikStream/Plugins/DiziBox.py +46 -29
  25. KekikStream/Plugins/DiziPal.py +246 -0
  26. KekikStream/Plugins/DiziYou.py +22 -20
  27. KekikStream/Plugins/Dizilla.py +62 -48
  28. KekikStream/Plugins/FilmMakinesi.py +65 -54
  29. KekikStream/Plugins/FilmModu.py +138 -0
  30. KekikStream/Plugins/FullHDFilmizlesene.py +44 -39
  31. KekikStream/Plugins/HDFilmCehennemi.py +98 -74
  32. KekikStream/Plugins/JetFilmizle.py +22 -14
  33. KekikStream/Plugins/RecTV.py +43 -35
  34. KekikStream/Plugins/SezonlukDizi.py +52 -20
  35. KekikStream/Plugins/SineWix.py +43 -30
  36. KekikStream/Plugins/UgurFilm.py +50 -15
  37. KekikStream/__init__.py +285 -318
  38. KekikStream/requirements.txt +1 -1
  39. {kekikstream-1.3.3.dist-info → kekikstream-1.7.1.dist-info}/METADATA +11 -4
  40. kekikstream-1.7.1.dist-info/RECORD +63 -0
  41. {kekikstream-1.3.3.dist-info → kekikstream-1.7.1.dist-info}/WHEEL +1 -1
  42. KekikStream/Helpers/Unpack.py +0 -75
  43. KekikStream/Plugins/Shorten.py +0 -225
  44. kekikstream-1.3.3.dist-info/RECORD +0 -63
  45. {kekikstream-1.3.3.dist-info → kekikstream-1.7.1.dist-info}/entry_points.txt +0 -0
  46. {kekikstream-1.3.3.dist-info → kekikstream-1.7.1.dist-info/licenses}/LICENSE +0 -0
  47. {kekikstream-1.3.3.dist-info → kekikstream-1.7.1.dist-info}/top_level.txt +0 -0
@@ -9,9 +9,9 @@ class TRsTX(ExtractorBase):
9
9
 
10
10
  async def extract(self, url, referer=None) -> list[ExtractResult]:
11
11
  if referer:
12
- self.httpx.headers.update({"Referer": referer})
12
+ self.cffi.headers.update({"Referer": referer})
13
13
 
14
- istek = await self.httpx.get(url)
14
+ istek = await self.cffi.get(url)
15
15
  istek.raise_for_status()
16
16
 
17
17
  file_match = re.search(r'file\":\"([^\"]+)', istek.text)
@@ -21,7 +21,7 @@ class TRsTX(ExtractorBase):
21
21
  file_path = file_match[1].replace("\\", "")
22
22
  post_link = f"{self.main_url}/{file_path}"
23
23
 
24
- post_istek = await self.httpx.post(post_link)
24
+ post_istek = await self.cffi.post(post_link)
25
25
  post_istek.raise_for_status()
26
26
 
27
27
  try:
@@ -42,7 +42,7 @@ class TRsTX(ExtractorBase):
42
42
  continue
43
43
 
44
44
  playlist_url = f"{self.main_url}/playlist/{file.lstrip('/')}.txt"
45
- playlist_request = await self.httpx.post(playlist_url, headers={"Referer": referer or self.main_url})
45
+ playlist_request = await self.cffi.post(playlist_url, headers={"Referer": referer or self.main_url})
46
46
  playlist_request.raise_for_status()
47
47
 
48
48
  video_data = playlist_request.text
@@ -57,6 +57,7 @@ class TRsTX(ExtractorBase):
57
57
  name = f"{self.name} - {title}",
58
58
  url = video_data,
59
59
  referer = self.main_url,
60
+ headers = {},
60
61
  subtitles = []
61
62
  )
62
63
  )
@@ -8,12 +8,12 @@ class TauVideo(ExtractorBase):
8
8
 
9
9
  async def extract(self, url, referer=None) -> list[ExtractResult]:
10
10
  if referer:
11
- self.httpx.headers.update({"Referer": referer})
11
+ self.cffi.headers.update({"Referer": referer})
12
12
 
13
13
  video_key = url.split("/")[-1]
14
14
  api_url = f"{self.main_url}/api/video/{video_key}"
15
15
 
16
- response = await self.httpx.get(api_url)
16
+ response = await self.cffi.get(api_url)
17
17
  response.raise_for_status()
18
18
 
19
19
  api_data = response.json()
@@ -26,6 +26,7 @@ class TauVideo(ExtractorBase):
26
26
  name = f"{self.name} - {video['label']}",
27
27
  url = video["url"],
28
28
  referer = referer or self.main_url,
29
+ headers = {},
29
30
  subtitles = []
30
31
  )
31
32
  for video in api_data["urls"]
@@ -9,9 +9,9 @@ class TurboImgz(ExtractorBase):
9
9
 
10
10
  async def extract(self, url, referer=None) -> ExtractResult:
11
11
  if referer:
12
- self.httpx.headers.update({"Referer": referer})
12
+ self.cffi.headers.update({"Referer": referer})
13
13
 
14
- istek = await self.httpx.get(url)
14
+ istek = await self.cffi.get(url)
15
15
  istek.raise_for_status()
16
16
 
17
17
  if video_match := re.search(r'file: "(.*)",', istek.text):
@@ -19,6 +19,7 @@ class TurboImgz(ExtractorBase):
19
19
  name = self.name,
20
20
  url = video_match[1],
21
21
  referer = referer or self.main_url,
22
+ headers = {},
22
23
  subtitles = []
23
24
  )
24
25
  else:
@@ -2,7 +2,8 @@
2
2
  # ! https://github.com/recloudstream/cloudstream/blob/master/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidmoly.kt
3
3
 
4
4
  from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
5
- import re, asyncio, contextlib, json
5
+ from parsel import Selector
6
+ import re, contextlib, json
6
7
 
7
8
  class VidMoly(ExtractorBase):
8
9
  name = "VidMoly"
@@ -10,32 +11,33 @@ class VidMoly(ExtractorBase):
10
11
 
11
12
  async def extract(self, url: str, referer: str = None) -> ExtractResult:
12
13
  if referer:
13
- self.httpx.headers.update({"Referer": referer})
14
+ self.cffi.headers.update({"Referer": referer})
14
15
 
15
- self.httpx.headers.update({
16
- "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
16
+ self.cffi.headers.update({
17
17
  "Sec-Fetch-Dest" : "iframe",
18
18
  })
19
19
 
20
20
  if self.main_url.endswith(".me"):
21
- self.main_url = self.main_url.replace(".me", ".to")
22
- url = url.replace(".me", ".to")
21
+ self.main_url = self.main_url.replace(".me", ".net")
22
+ url = url.replace(".me", ".net")
23
23
 
24
- # Embed URL oluştur
25
- embed_url = url.replace("/w/", "/embed-") + "-920x360.html" if "/w/" in url else url
26
- script_content = None
27
- attempts = 0
24
+ response = await self.cffi.get(url)
25
+ if "Select number" in response.text:
26
+ secici = Selector(response.text)
27
+ response = await self.cffi.post(
28
+ url = url,
29
+ data = {
30
+ "op" : secici.css("input[name='op']::attr(value)").get(),
31
+ "file_code" : secici.css("input[name='file_code']::attr(value)").get(),
32
+ "answer" : secici.css("div.vhint b::text").get(),
33
+ "ts" : secici.css("input[name='ts']::attr(value)").get(),
34
+ "nonce" : secici.css("input[name='nonce']::attr(value)").get(),
35
+ "ctok" : secici.css("input[name='ctok']::attr(value)").get()
36
+ }
37
+ )
28
38
 
29
- # Script verisini almak için deneme yap
30
- while attempts < 10 and not script_content:
31
- attempts += 1
32
- response = await self.httpx.get(embed_url)
33
- response.raise_for_status()
34
-
35
- script_match = re.search(r"sources:\s*\[(.*?)\],", response.text, re.DOTALL)
36
- script_content = script_match[1] if script_match else None
37
- if not script_content:
38
- await asyncio.sleep(0.5)
39
+ script_match = re.search(r"sources:\s*\[(.*?)\],", response.text, re.DOTALL)
40
+ script_content = script_match[1] if script_match else None
39
41
 
40
42
  if not script_content:
41
43
  raise ValueError("Gerekli script bulunamadı.")
@@ -74,11 +76,11 @@ class VidMoly(ExtractorBase):
74
76
  if not video_url:
75
77
  raise ValueError("Video URL bulunamadı.")
76
78
 
77
- await self.close()
78
79
  return ExtractResult(
79
80
  name = self.name,
80
81
  url = video_url,
81
82
  referer = self.main_url,
83
+ headers = {},
82
84
  subtitles = subtitles
83
85
  )
84
86
 
@@ -10,9 +10,9 @@ class VidMoxy(ExtractorBase):
10
10
 
11
11
  async def extract(self, url, referer=None) -> ExtractResult:
12
12
  if referer:
13
- self.httpx.headers.update({"Referer": referer})
13
+ self.cffi.headers.update({"Referer": referer})
14
14
 
15
- istek = await self.httpx.get(url)
15
+ istek = await self.cffi.get(url)
16
16
  istek.raise_for_status()
17
17
 
18
18
  subtitles = []
@@ -41,10 +41,10 @@ class VidMoxy(ExtractorBase):
41
41
 
42
42
  m3u_link = HexCodec.decode(escaped_hex)
43
43
 
44
- await self.close()
45
44
  return ExtractResult(
46
45
  name = self.name,
47
46
  url = m3u_link,
48
47
  referer = self.main_url,
48
+ headers = {},
49
49
  subtitles = subtitles
50
50
  )
@@ -9,18 +9,18 @@ class VideoSeyred(ExtractorBase):
9
9
 
10
10
  async def extract(self, url, referer=None) -> ExtractResult:
11
11
  if referer:
12
- self.httpx.headers.update({"Referer": referer})
12
+ self.cffi.headers.update({"Referer": referer})
13
13
 
14
14
  video_id = url.split("embed/")[1].split("?")[0]
15
15
  if len(video_id) > 10:
16
- kontrol = await self.httpx.get(url)
16
+ kontrol = await self.cffi.get(url)
17
17
  kontrol.raise_for_status()
18
18
 
19
19
  video_id = re.search(r"playlist\/(.*)\.json", kontrol.text)[1]
20
20
 
21
21
  video_url = f"{self.main_url}/playlist/{video_id}.json"
22
22
 
23
- response = await self.httpx.get(video_url)
23
+ response = await self.cffi.get(video_url)
24
24
  response.raise_for_status()
25
25
 
26
26
  try:
@@ -43,6 +43,7 @@ class VideoSeyred(ExtractorBase):
43
43
  name = self.name,
44
44
  url = self.fix_url(source["file"]),
45
45
  referer = self.main_url,
46
+ headers = {},
46
47
  subtitles = subtitles,
47
48
  )
48
49
  for source in response_data.get("sources", [])
@@ -3,7 +3,7 @@
3
3
  from KekikStream.Core import kekik_cache, PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode
4
4
  from Kekik.Sifreleme import CryptoJS
5
5
  from parsel import Selector
6
- import re, urllib.parse, base64, contextlib, asyncio
6
+ import re, urllib.parse, base64, contextlib, asyncio, time
7
7
 
8
8
  class DiziBox(PluginBase):
9
9
  name = "DiziBox"
@@ -40,11 +40,15 @@ class DiziBox(PluginBase):
40
40
  f"{main_url}/dizi-arsivi/page/SAYFA/?tur[0]=yarisma&yil&imdb" : "Yarışma"
41
41
  }
42
42
 
43
- @kekik_cache(ttl=60*60)
43
+ #@kekik_cache(ttl=60*60)
44
44
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
45
- istek = await self.httpx.get(
45
+ self.cffi.cookies.update({
46
+ "isTrustedUser" : "true",
47
+ "dbxu" : str(time.time() * 1000).split(".")[0]
48
+ })
49
+ istek = await self.cffi.get(
46
50
  url = f"{url.replace('SAYFA', str(page))}",
47
- follow_redirects = True
51
+ allow_redirects = True
48
52
  )
49
53
  secici = Selector(istek.text)
50
54
 
@@ -58,14 +62,13 @@ class DiziBox(PluginBase):
58
62
  for veri in secici.css("article.detailed-article")
59
63
  ]
60
64
 
61
- @kekik_cache(ttl=60*60)
65
+ #@kekik_cache(ttl=60*60)
62
66
  async def search(self, query: str) -> list[SearchResult]:
63
- self.httpx.cookies.update({
64
- "LockUser" : "true",
67
+ self.cffi.cookies.update({
65
68
  "isTrustedUser" : "true",
66
- "dbxu" : "1722403730363"
69
+ "dbxu" : str(time.time() * 1000).split(".")[0]
67
70
  })
68
- istek = await self.httpx.get(f"{self.main_url}/?s={query}")
71
+ istek = await self.cffi.get(f"{self.main_url}/?s={query}")
69
72
  secici = Selector(istek.text)
70
73
 
71
74
  return [
@@ -77,9 +80,9 @@ class DiziBox(PluginBase):
77
80
  for item in secici.css("article.detailed-article")
78
81
  ]
79
82
 
80
- @kekik_cache(ttl=60*60)
83
+ #@kekik_cache(ttl=60*60)
81
84
  async def load_item(self, url: str) -> SeriesInfo:
82
- istek = await self.httpx.get(url)
85
+ istek = await self.cffi.get(url)
83
86
  secici = Selector(istek.text)
84
87
 
85
88
  title = secici.css("div.tv-overview h1 a::text").get()
@@ -93,7 +96,7 @@ class DiziBox(PluginBase):
93
96
  episodes = []
94
97
  for sezon_link in secici.css("div#seasons-list a::attr(href)").getall():
95
98
  sezon_url = self.fix_url(sezon_link)
96
- sezon_istek = await self.httpx.get(sezon_url)
99
+ sezon_istek = await self.cffi.get(sezon_url)
97
100
  sezon_secici = Selector(sezon_istek.text)
98
101
 
99
102
  for bolum in sezon_secici.css("article.grid-box"):
@@ -124,20 +127,25 @@ class DiziBox(PluginBase):
124
127
  actors = actors,
125
128
  )
126
129
 
127
- @kekik_cache(ttl=60*60)
130
+ #@kekik_cache(ttl=60*60)
128
131
  async def _iframe_decode(self, name:str, iframe_link:str, referer:str) -> list[str]:
129
132
  results = []
130
133
 
134
+ self.cffi.headers.update({"Referer": referer})
135
+ self.cffi.cookies.update({
136
+ "isTrustedUser" : "true",
137
+ "dbxu" : str(time.time() * 1000).split(".")[0]
138
+ })
139
+
131
140
  if "/player/king/king.php" in iframe_link:
132
141
  iframe_link = iframe_link.replace("king.php?v=", "king.php?wmode=opaque&v=")
133
- self.httpx.headers.update({"Referer": referer})
134
142
 
135
- istek = await self.httpx.get(iframe_link)
143
+ istek = await self.cffi.get(iframe_link)
136
144
  secici = Selector(istek.text)
137
145
  iframe = secici.css("div#Player iframe::attr(src)").get()
138
146
 
139
- self.httpx.headers.update({"Referer": self.main_url})
140
- istek = await self.httpx.get(iframe)
147
+ self.cffi.headers.update({"Referer": self.main_url})
148
+ istek = await self.cffi.get(iframe)
141
149
 
142
150
  crypt_data = re.search(r"CryptoJS\.AES\.decrypt\(\"(.*)\",\"", istek.text)[1]
143
151
  crypt_pass = re.search(r"\",\"(.*)\"\);", istek.text)[1]
@@ -150,11 +158,10 @@ class DiziBox(PluginBase):
150
158
 
151
159
  elif "/player/moly/moly.php" in iframe_link:
152
160
  iframe_link = iframe_link.replace("moly.php?h=", "moly.php?wmode=opaque&h=")
153
- self.httpx.headers.update({"Referer": referer})
154
161
  while True:
155
162
  await asyncio.sleep(.3)
156
163
  with contextlib.suppress(Exception):
157
- istek = await self.httpx.get(iframe_link)
164
+ istek = await self.cffi.get(iframe_link)
158
165
 
159
166
  if atob_data := re.search(r"unescape\(\"(.*)\"\)", istek.text):
160
167
  decoded_atob = urllib.parse.unquote(atob_data[1])
@@ -171,15 +178,20 @@ class DiziBox(PluginBase):
171
178
 
172
179
  return results
173
180
 
174
- @kekik_cache(ttl=15*60)
175
- async def load_links(self, url: str) -> list[str]:
176
- istek = await self.httpx.get(url)
181
+ #@kekik_cache(ttl=15*60)
182
+ async def load_links(self, url: str) -> list[dict]:
183
+ istek = await self.cffi.get(url)
177
184
  secici = Selector(istek.text)
178
185
 
179
- iframes = []
186
+ results = []
180
187
  if main_iframe := secici.css("div#video-area iframe::attr(src)").get():
181
188
  if decoded := await self._iframe_decode(self.name, main_iframe, url):
182
- iframes.extend(decoded)
189
+ for iframe in decoded:
190
+ extractor = self.ex_manager.find_extractor(iframe)
191
+ results.append({
192
+ "url" : iframe,
193
+ "name" : f"{extractor.name if extractor else 'Main Player'}"
194
+ })
183
195
 
184
196
  for alternatif in secici.css("div.video-toolbar option[value]"):
185
197
  alt_name = alternatif.css("::text").get()
@@ -188,13 +200,18 @@ class DiziBox(PluginBase):
188
200
  if not alt_link:
189
201
  continue
190
202
 
191
- self.httpx.headers.update({"Referer": url})
192
- alt_istek = await self.httpx.get(alt_link)
203
+ self.cffi.headers.update({"Referer": url})
204
+ alt_istek = await self.cffi.get(alt_link)
193
205
  alt_istek.raise_for_status()
194
206
 
195
207
  alt_secici = Selector(alt_istek.text)
196
208
  if alt_iframe := alt_secici.css("div#video-area iframe::attr(src)").get():
197
209
  if decoded := await self._iframe_decode(alt_name, alt_iframe, url):
198
- iframes.extend(decoded)
199
-
200
- return iframes
210
+ for iframe in decoded:
211
+ extractor = self.ex_manager.find_extractor(iframe)
212
+ results.append({
213
+ "url" : iframe,
214
+ "name" : f"{extractor.name if extractor else alt_name}"
215
+ })
216
+
217
+ return results
@@ -0,0 +1,246 @@
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
6
+
7
+ class DiziPal(PluginBase):
8
+ name = "DiziPal"
9
+ language = "tr"
10
+ main_url = "https://dizipal1222.com"
11
+ favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
12
+ description = "Yabancı Dizi ve Film izle."
13
+
14
+ main_page = {
15
+ f"{main_url}/diziler/son-bolumler" : "Son Bölümler",
16
+ f"{main_url}/diziler" : "Yeni Diziler",
17
+ f"{main_url}/filmler" : "Yeni Filmler",
18
+ f"{main_url}/koleksiyon/netflix" : "Netflix",
19
+ f"{main_url}/koleksiyon/exxen" : "Exxen",
20
+ f"{main_url}/koleksiyon/blutv" : "BluTV",
21
+ f"{main_url}/koleksiyon/disney" : "Disney+",
22
+ f"{main_url}/koleksiyon/amazon-prime" : "Amazon Prime",
23
+ f"{main_url}/koleksiyon/tod-bein" : "TOD (beIN)",
24
+ f"{main_url}/koleksiyon/gain" : "Gain",
25
+ f"{main_url}/tur/mubi" : "Mubi",
26
+ }
27
+
28
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
29
+ istek = await self.cffi.get(url)
30
+ secici = Selector(istek.text)
31
+
32
+ results = []
33
+
34
+ if "/son-bolumler" in url:
35
+ for veri in secici.css("div.episode-item"):
36
+ name = veri.css("div.name::text").get()
37
+ episode = veri.css("div.episode::text").get()
38
+ href = veri.css("a::attr(href)").get()
39
+ poster = veri.css("img::attr(src)").get()
40
+
41
+ if name and href:
42
+ ep_text = episode.strip().replace(". Sezon ", "x").replace(". Bölüm", "") if episode else ""
43
+ title = f"{name} {ep_text}"
44
+ # Son bölümler linkini dizi sayfasına çevir
45
+ dizi_url = href.split("/sezon")[0] if "/sezon" in href else href
46
+
47
+ results.append(MainPageResult(
48
+ category = category,
49
+ title = title,
50
+ url = self.fix_url(dizi_url),
51
+ poster = self.fix_url(poster) if poster else None,
52
+ ))
53
+ else:
54
+ for veri in secici.css("article.type2 ul li"):
55
+ title = veri.css("span.title::text").get()
56
+ href = veri.css("a::attr(href)").get()
57
+ poster = veri.css("img::attr(src)").get()
58
+
59
+ if title and href:
60
+ results.append(MainPageResult(
61
+ category = category,
62
+ title = title,
63
+ url = self.fix_url(href),
64
+ poster = self.fix_url(poster) if poster else None,
65
+ ))
66
+
67
+ return results
68
+
69
+ async def search(self, query: str) -> list[SearchResult]:
70
+ self.cffi.headers.update({
71
+ "Accept" : "application/json, text/javascript, */*; q=0.01",
72
+ "X-Requested-With" : "XMLHttpRequest"
73
+ })
74
+
75
+ istek = await self.cffi.post(
76
+ url = f"{self.main_url}/api/search-autocomplete",
77
+ data = {"query": query}
78
+ )
79
+
80
+ try:
81
+ data = istek.json()
82
+ except Exception:
83
+ return []
84
+
85
+ results = []
86
+
87
+ # API bazen dict, bazen list döner
88
+ items = data.values() if isinstance(data, dict) else data
89
+
90
+ for item in items:
91
+ if not isinstance(item, dict):
92
+ continue
93
+
94
+ title = item.get("title")
95
+ url = item.get("url")
96
+ poster = item.get("poster")
97
+
98
+ if title and url:
99
+ results.append(SearchResult(
100
+ title = title,
101
+ url = f"{self.main_url}{url}",
102
+ poster = self.fix_url(poster) if poster else None,
103
+ ))
104
+
105
+ return results
106
+
107
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
108
+ # Reset headers to get HTML response
109
+ self.cffi.headers.update({
110
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
111
+ })
112
+ self.cffi.headers.pop("X-Requested-With", None)
113
+
114
+ istek = await self.cffi.get(url)
115
+ secici = Selector(text=istek.text, type="html")
116
+
117
+ poster = self.fix_url(secici.css("meta[property='og:image']::attr(content)").get())
118
+ year = secici.xpath("//div[text()='Yapım Yılı']//following-sibling::div/text()").get()
119
+ description = secici.css("div.summary p::text").get()
120
+ rating = secici.xpath("//div[text()='IMDB Puanı']//following-sibling::div/text()").get()
121
+ tags_raw = secici.xpath("//div[text()='Türler']//following-sibling::div/text()").get()
122
+ tags = [t.strip() for t in tags_raw.split() if t.strip()] if tags_raw else None
123
+
124
+ dur_text = secici.xpath("//div[text()='Ortalama Süre']//following-sibling::div/text()").get()
125
+ dur_match = re.search(r"(\d+)", dur_text or "")
126
+ duration = int(dur_match[1]) if dur_match else None
127
+
128
+ if "/dizi/" in url:
129
+ title = secici.css("div.cover h5::text").get()
130
+
131
+ episodes = []
132
+ for ep in secici.css("div.episode-item"):
133
+ ep_name = ep.css("div.name::text").get()
134
+ ep_href = ep.css("a::attr(href)").get()
135
+ ep_text = ep.css("div.episode::text").get() or ""
136
+ ep_parts = ep_text.strip().split(" ")
137
+
138
+ ep_season = None
139
+ ep_episode = None
140
+ if len(ep_parts) >= 4:
141
+ try:
142
+ ep_season = int(ep_parts[0].replace(".", ""))
143
+ ep_episode = int(ep_parts[2].replace(".", ""))
144
+ except ValueError:
145
+ pass
146
+
147
+ if ep_name and ep_href:
148
+ episodes.append(Episode(
149
+ season = ep_season,
150
+ episode = ep_episode,
151
+ title = ep_name.strip(),
152
+ url = self.fix_url(ep_href),
153
+ ))
154
+
155
+ return SeriesInfo(
156
+ url = url,
157
+ poster = poster,
158
+ title = title,
159
+ description = description.strip() if description else None,
160
+ tags = tags,
161
+ rating = rating.strip() if rating else None,
162
+ year = year.strip() if year else None,
163
+ duration = duration,
164
+ episodes = episodes if episodes else None,
165
+ )
166
+ else:
167
+ title = secici.xpath("//div[@class='g-title'][2]/div/text()").get()
168
+
169
+ return MovieInfo(
170
+ url = url,
171
+ poster = poster,
172
+ title = title.strip() if title else None,
173
+ description = description.strip() if description else None,
174
+ tags = tags,
175
+ rating = rating.strip() if rating else None,
176
+ year = year.strip() if year else None,
177
+ duration = duration,
178
+ )
179
+
180
+ async def load_links(self, url: str) -> list[dict]:
181
+ # Reset headers to get HTML response
182
+ self.cffi.headers.update({
183
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
184
+ })
185
+ self.cffi.headers.pop("X-Requested-With", None)
186
+
187
+ istek = await self.cffi.get(url)
188
+ secici = Selector(istek.text)
189
+
190
+ iframe = secici.css(".series-player-container iframe::attr(src)").get()
191
+ if not iframe:
192
+ iframe = secici.css("div#vast_new iframe::attr(src)").get()
193
+
194
+ if not iframe:
195
+ return []
196
+
197
+ results = []
198
+
199
+ self.cffi.headers.update({"Referer": f"{self.main_url}/"})
200
+ i_istek = await self.cffi.get(iframe)
201
+ i_text = i_istek.text
202
+
203
+ # m3u link çıkar
204
+ m3u_match = re.search(r'file:"([^"]+)"', i_text)
205
+ if m3u_match:
206
+ m3u_link = m3u_match[1]
207
+
208
+ # Altyazıları çıkar
209
+ subtitles = []
210
+ sub_match = re.search(r'"subtitle":"([^"]+)"', i_text)
211
+ if sub_match:
212
+ sub_text = sub_match[1]
213
+ if "," in sub_text:
214
+ for sub in sub_text.split(","):
215
+ lang = sub.split("[")[1].split("]")[0] if "[" in sub else "Türkçe"
216
+ sub_url = sub.replace(f"[{lang}]", "")
217
+ subtitles.append(Subtitle(name=lang, url=self.fix_url(sub_url)))
218
+ else:
219
+ lang = sub_text.split("[")[1].split("]")[0] if "[" in sub_text else "Türkçe"
220
+ sub_url = sub_text.replace(f"[{lang}]", "")
221
+ subtitles.append(Subtitle(name=lang, url=self.fix_url(sub_url)))
222
+
223
+ results.append({
224
+ "name" : self.name,
225
+ "url" : m3u_link,
226
+ "referer" : f"{self.main_url}/",
227
+ "subtitles" : subtitles
228
+ })
229
+ else:
230
+ # Extractor'a yönlendir
231
+ extractor = self.ex_manager.find_extractor(iframe)
232
+ results.append({
233
+ "name" : f"{extractor.name if extractor else self.name}",
234
+ "url" : iframe,
235
+ "referer" : f"{self.main_url}/",
236
+ })
237
+
238
+ return results
239
+
240
+ async def play(self, name: str, url: str, referer: str, subtitles: list[Subtitle]):
241
+ extract_result = ExtractResult(name=name, url=url, referer=referer, subtitles=subtitles)
242
+ self.media_handler.title = name
243
+ if self.name not in self.media_handler.title:
244
+ self.media_handler.title = f"{self.name} | {self.media_handler.title}"
245
+
246
+ self.media_handler.play_media(extract_result)