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
@@ -17,13 +17,13 @@ class ExtractorBase(ABC):
17
17
  self.cloudscraper = CloudScraper()
18
18
 
19
19
  # httpx - lightweight and safe for most HTTP requests
20
- self.httpx = AsyncClient(
21
- timeout = 3,
22
- follow_redirects = True
23
- )
20
+ self.httpx = AsyncClient(timeout = 10)
24
21
  self.httpx.headers.update(self.cloudscraper.headers)
25
22
  self.httpx.cookies.update(self.cloudscraper.cookies)
26
- self.httpx.headers.update({"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 15.7; rv:135.0) Gecko/20100101 Firefox/135.0"})
23
+ self.httpx.headers.update({
24
+ "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 15.7; rv:135.0) Gecko/20100101 Firefox/135.0",
25
+ "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
26
+ })
27
27
 
28
28
  def can_handle_url(self, url: str) -> bool:
29
29
  # URL'nin bu çıkarıcı tarafından işlenip işlenemeyeceğini kontrol et
@@ -19,19 +19,22 @@ class ExtractorLoader:
19
19
  def load_all(self) -> list[ExtractorBase]:
20
20
  extractors = []
21
21
 
22
- # Global çıkarıcıları yükle
23
- if self.global_extractors_dir.exists():
24
- # konsol.log(f"[green][*] Global Extractor dizininden yükleniyor: {self.global_extractors_dir}[/green]")
25
- global_extractors = self._load_from_directory(self.global_extractors_dir)
26
- # konsol.log(f"[green]Global Extractor'lar: {[e.__name__ for e in global_extractors]}[/green]")
27
- extractors.extend(global_extractors)
28
-
29
- # Yerel çıkarıcıları yükle
22
+ # Eğer yerel dizinde Extractor varsa, sadece onları yükle (eklenti geliştirme modu)
30
23
  if self.local_extractors_dir.exists():
31
24
  # konsol.log(f"[green][*] Yerel Extractor dizininden yükleniyor: {self.local_extractors_dir}[/green]")
32
25
  local_extractors = self._load_from_directory(self.local_extractors_dir)
33
26
  # konsol.log(f"[green]Yerel Extractor'lar: {[e.__name__ for e in local_extractors]}[/green]")
34
- extractors.extend(local_extractors)
27
+
28
+ if local_extractors:
29
+ # konsol.log("[cyan][*] Yerel Extractor bulundu, global Extractor'lar atlanıyor (eklenti geliştirme modu)[/cyan]")
30
+ extractors.extend(local_extractors)
31
+
32
+ # Yerel dizinde Extractor yoksa, global'leri yükle
33
+ if not extractors and self.global_extractors_dir.exists():
34
+ # konsol.log(f"[green][*] Global Extractor dizininden yükleniyor: {self.global_extractors_dir}[/green]")
35
+ global_extractors = self._load_from_directory(self.global_extractors_dir)
36
+ # konsol.log(f"[green]Global Extractor'lar: {[e.__name__ for e in global_extractors]}[/green]")
37
+ extractors.extend(global_extractors)
35
38
 
36
39
  # Benzersizliği sağlama (modül adı + sınıf adı bazında)
37
40
  unique_extractors = []
@@ -57,9 +60,10 @@ class ExtractorLoader:
57
60
  if file.endswith(".py") and not file.startswith("__"):
58
61
  module_name = file[:-3] # .py uzantısını kaldır
59
62
  # konsol.log(f"[cyan]Okunan Dosya\t\t: {module_name}[/cyan]")
60
- if extractor := self._load_extractor(directory, module_name):
61
- # konsol.log(f"[magenta]Extractor Yüklendi\t: {extractor.__name__}[/magenta]")
62
- extractors.append(extractor)
63
+ module_extractors = self._load_extractor(directory, module_name)
64
+ if module_extractors:
65
+ # konsol.log(f"[magenta]Extractor Yüklendi\t: {[e.__name__ for e in module_extractors]}[/magenta]")
66
+ extractors.extend(module_extractors)
63
67
 
64
68
  # konsol.log(f"[yellow]{directory} dizininden yüklenen Extractor'lar: {[e.__name__ for e in extractors]}[/yellow]")
65
69
  return extractors
@@ -70,21 +74,25 @@ class ExtractorLoader:
70
74
  path = directory / f"{module_name}.py"
71
75
  spec = importlib.util.spec_from_file_location(module_name, path)
72
76
  if not spec or not spec.loader:
73
- return None
77
+ return []
74
78
 
75
79
  # Modülü içe aktar
76
80
  module = importlib.util.module_from_spec(spec)
77
81
  spec.loader.exec_module(module)
78
82
 
79
- # Yalnızca doğru modülden gelen ExtractorBase sınıflarını yükle
83
+ # Yalnızca doğru modülden gelen ExtractorBase sınıflarını yükle (TÜM CLASS'LAR)
84
+ extractors = []
80
85
  for attr in dir(module):
81
86
  obj = getattr(module, attr)
82
- if obj.__module__ == module_name and isinstance(obj, type) and issubclass(obj, ExtractorBase) and obj is not ExtractorBase:
87
+ # isinstance kontrolünü __module__ kontrolünden ÖNCE yap
88
+ if isinstance(obj, type) and issubclass(obj, ExtractorBase) and obj is not ExtractorBase and obj.__module__ == module_name:
83
89
  # konsol.log(f"[green]Yüklenen sınıf\t\t: {module_name}.{obj.__name__} ({obj.__module__}.{obj.__name__})[/green]")
84
- return obj
90
+ extractors.append(obj)
91
+
92
+ return extractors
85
93
 
86
94
  except Exception as hata:
87
95
  konsol.log(f"[red][!] Extractor yüklenirken hata oluştu: {module_name}\nHata: {hata}")
88
96
  konsol.print(f"[dim]{traceback.format_exc()}[/dim]")
89
97
 
90
- return None
98
+ return []
@@ -63,7 +63,7 @@ class ExtractorManager:
63
63
  """
64
64
  # Lazy loading: İlk kullanımda extractorları initialize et
65
65
  self._ensure_initialized()
66
-
66
+
67
67
  mapping = {}
68
68
  for link in links:
69
69
  # Cached instance'ları kullan
@@ -0,0 +1,35 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from Kekik.cli import konsol
4
+ from yt_dlp.extractor import gen_extractors
5
+
6
+ # Global cache (module-level singleton)
7
+ _YTDLP_EXTRACTORS_CACHE = None
8
+ _CACHE_INITIALIZED = False
9
+
10
+ def get_ytdlp_extractors() -> list:
11
+ """
12
+ yt-dlp extractorlarını cache'le ve döndür
13
+
14
+ Returns:
15
+ list: yt-dlp extractor sınıfları
16
+ """
17
+ global _YTDLP_EXTRACTORS_CACHE, _CACHE_INITIALIZED
18
+
19
+ if _CACHE_INITIALIZED:
20
+ return _YTDLP_EXTRACTORS_CACHE
21
+
22
+ try:
23
+ extractors = list(gen_extractors())
24
+ extractors = [ie for ie in extractors if ie.ie_key() != 'Generic']
25
+
26
+ _YTDLP_EXTRACTORS_CACHE = extractors
27
+ _CACHE_INITIALIZED = True
28
+
29
+ return extractors
30
+
31
+ except Exception as e:
32
+ konsol.log(f"[red][⚠] yt-dlp extractor cache hatası: {e}[/red]")
33
+ _YTDLP_EXTRACTORS_CACHE = []
34
+ _CACHE_INITIALIZED = True
35
+ return []
@@ -34,7 +34,10 @@ class PluginBase(ABC):
34
34
  )
35
35
  self.httpx.headers.update(self.cloudscraper.headers)
36
36
  self.httpx.cookies.update(self.cloudscraper.cookies)
37
- self.httpx.headers.update({"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 15.7; rv:135.0) Gecko/20100101 Firefox/135.0"})
37
+ self.httpx.headers.update({
38
+ "User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 15.7; rv:135.0) Gecko/20100101 Firefox/135.0",
39
+ "Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
40
+ })
38
41
 
39
42
  self.media_handler = MediaHandler()
40
43
  self.ex_manager = ExtractorManager()
@@ -19,15 +19,19 @@ class PluginLoader:
19
19
  def load_all(self) -> dict[str, PluginBase]:
20
20
  plugins = {}
21
21
 
22
- # Global eklentileri yükle
23
- if self.global_plugins_dir.exists():
24
- # konsol.log(f"[green][*] Global Eklenti dizininden yükleniyor: {self.global_plugins_dir}[/green]")
25
- plugins |= self._load_from_directory(self.global_plugins_dir)
26
-
27
- # Yerel eklentileri yükle
22
+ # Eğer yerel dizinde Plugin varsa, sadece onları yükle (eklenti geliştirme modu)
28
23
  if self.local_plugins_dir.exists():
29
24
  # konsol.log(f"[green][*] Yerel Eklenti dizininden yükleniyor: {self.local_plugins_dir}[/green]")
30
- plugins |= self._load_from_directory(self.local_plugins_dir)
25
+ local_plugins = self._load_from_directory(self.local_plugins_dir)
26
+
27
+ if local_plugins:
28
+ # konsol.log("[cyan][*] Yerel Plugin bulundu, global Plugin'ler atlanıyor (eklenti geliştirme modu)[/cyan]")
29
+ plugins |= local_plugins
30
+
31
+ # Yerel dizinde Plugin yoksa, global'leri yükle
32
+ if not plugins and self.global_plugins_dir.exists():
33
+ # konsol.log(f"[green][*] Global Eklenti dizininden yükleniyor: {self.global_plugins_dir}[/green]")
34
+ plugins |= self._load_from_directory(self.global_plugins_dir)
31
35
 
32
36
  if not plugins:
33
37
  konsol.print("[yellow][!] Yüklenecek bir Eklenti bulunamadı![/yellow]")
@@ -13,6 +13,7 @@ from .Extractor.ExtractorManager import ExtractorManager
13
13
  from .Extractor.ExtractorBase import ExtractorBase
14
14
  from .Extractor.ExtractorLoader import ExtractorLoader
15
15
  from .Extractor.ExtractorModels import ExtractResult, Subtitle
16
+ from .Extractor.YTDLPCache import get_ytdlp_extractors
16
17
 
17
18
  from .Media.MediaManager import MediaManager
18
19
  from .Media.MediaHandler import MediaHandler
@@ -6,7 +6,7 @@ import re
6
6
 
7
7
  class CloseLoadExtractor(ExtractorBase):
8
8
  name = "CloseLoad"
9
- main_url = "https://closeload.filmmakinesi.sh"
9
+ main_url = "https://closeload.filmmakinesi.to"
10
10
 
11
11
  async def extract(self, url, referer=None) -> ExtractResult:
12
12
  if referer:
@@ -7,6 +7,19 @@ class ContentX(ExtractorBase):
7
7
  name = "ContentX"
8
8
  main_url = "https://contentx.me"
9
9
 
10
+ # Birden fazla domain destekle
11
+ supported_domains = [
12
+ "contentx.me", "four.contentx.me",
13
+ "dplayer82.site", "sn.dplayer82.site", "four.dplayer82.site", "org.dplayer82.site",
14
+ "dplayer74.site", "sn.dplayer74.site",
15
+ "hotlinger.com", "sn.hotlinger.com",
16
+ "playru.net", "four.playru.net",
17
+ "pichive.online", "four.pichive.online", "pichive.me", "four.pichive.me"
18
+ ]
19
+
20
+ def can_handle_url(self, url: str) -> bool:
21
+ return any(domain in url for domain in self.supported_domains)
22
+
10
23
  async def extract(self, url, referer=None) -> list[ExtractResult]:
11
24
  if referer:
12
25
  self.httpx.headers.update({"Referer": referer})
@@ -0,0 +1,86 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
4
+ from Kekik.Sifreleme import AESManager
5
+ import re, json
6
+
7
+ class DonilasPlay(ExtractorBase):
8
+ name = "DonilasPlay"
9
+ main_url = "https://donilasplay.com"
10
+
11
+ async def extract(self, url, referer=None) -> ExtractResult:
12
+ if referer:
13
+ self.httpx.headers.update({"Referer": referer})
14
+
15
+ istek = await self.httpx.get(url)
16
+ istek.raise_for_status()
17
+ i_source = istek.text
18
+
19
+ m3u_link = None
20
+ subtitles = []
21
+
22
+ # bePlayer pattern
23
+ be_player_match = re.search(r"bePlayer\('([^']+)',\s*'(\{[^}]+\})'\);", i_source)
24
+ if be_player_match:
25
+ be_player_pass = be_player_match.group(1)
26
+ be_player_data = be_player_match.group(2)
27
+
28
+ try:
29
+ # AES decrypt
30
+ decrypted = AESManager.decrypt(be_player_data, be_player_pass)
31
+ data = json.loads(decrypted)
32
+
33
+ m3u_link = data.get("video_location")
34
+
35
+ # Altyazıları işle
36
+ str_subtitles = data.get("strSubtitles", [])
37
+ if str_subtitles:
38
+ for sub in str_subtitles:
39
+ label = sub.get("label", "")
40
+ file = sub.get("file", "")
41
+ # Forced altyazıları hariç tut
42
+ if "Forced" in label:
43
+ continue
44
+ if file:
45
+ # Türkçe kontrolü
46
+ keywords = ["tur", "tr", "türkçe", "turkce"]
47
+ language = "Turkish" if any(k in label.lower() for k in keywords) else label
48
+ subtitles.append(Subtitle(
49
+ name = language,
50
+ url = self.fix_url(file)
51
+ ))
52
+ except Exception:
53
+ pass
54
+
55
+ # Fallback: file pattern
56
+ if not m3u_link:
57
+ file_match = re.search(r'file:"([^"]+)"', i_source)
58
+ if file_match:
59
+ m3u_link = file_match.group(1)
60
+
61
+ # tracks pattern for subtitles
62
+ tracks_match = re.search(r'tracks:\[([^\]]+)', i_source)
63
+ if tracks_match:
64
+ try:
65
+ tracks_str = f"[{tracks_match.group(1)}]"
66
+ tracks = json.loads(tracks_str)
67
+ for track in tracks:
68
+ file_url = track.get("file")
69
+ label = track.get("label", "")
70
+ if file_url and "Forced" not in label:
71
+ subtitles.append(Subtitle(
72
+ name = label,
73
+ url = self.fix_url(file_url)
74
+ ))
75
+ except Exception:
76
+ pass
77
+
78
+ if not m3u_link:
79
+ raise ValueError("m3u link not found")
80
+
81
+ return ExtractResult(
82
+ name = self.name,
83
+ url = m3u_link,
84
+ referer = url,
85
+ subtitles = subtitles
86
+ )
@@ -7,6 +7,12 @@ class Odnoklassniki(ExtractorBase):
7
7
  name = "Odnoklassniki"
8
8
  main_url = "https://odnoklassniki.ru"
9
9
 
10
+ # Birden fazla domain destekle
11
+ supported_domains = ["odnoklassniki.ru", "ok.ru"]
12
+
13
+ def can_handle_url(self, url: str) -> bool:
14
+ return any(domain in url for domain in self.supported_domains)
15
+
10
16
  async def extract(self, url, referer=None) -> ExtractResult:
11
17
  if "/video/" in url:
12
18
  url = url.replace("/video/", "/videoembed/")
@@ -7,6 +7,12 @@ class PeaceMakerst(ExtractorBase):
7
7
  name = "PeaceMakerst"
8
8
  main_url = "https://peacemakerst.com"
9
9
 
10
+ # Birden fazla domain destekle
11
+ supported_domains = ["peacemakerst.com", "hdstreamable.com"]
12
+
13
+ def can_handle_url(self, url: str) -> bool:
14
+ return any(domain in url for domain in self.supported_domains)
15
+
10
16
  async def extract(self, url, referer=None) -> ExtractResult:
11
17
  if referer:
12
18
  self.httpx.headers.update({"Referer": referer})
@@ -8,6 +8,12 @@ class RapidVid(ExtractorBase):
8
8
  name = "RapidVid"
9
9
  main_url = "https://rapidvid.net"
10
10
 
11
+ # Birden fazla domain destekle
12
+ supported_domains = ["rapidvid.net", "rapid.filmmakinesi.to"]
13
+
14
+ def can_handle_url(self, url: str) -> bool:
15
+ return any(domain in url for domain in self.supported_domains)
16
+
11
17
  async def extract(self, url, referer=None) -> ExtractResult:
12
18
  if referer:
13
19
  self.httpx.headers.update({"Referer": referer})
@@ -5,7 +5,13 @@ import re
5
5
 
6
6
  class SetPlay(ExtractorBase):
7
7
  name = "SetPlay"
8
- main_url = "https://setplay.cfd"
8
+ main_url = "https://setplay.shop"
9
+
10
+ # Birden fazla domain destekle
11
+ supported_domains = ["setplay.cfd", "setplay.shop", "setplay.site"]
12
+
13
+ def can_handle_url(self, url: str) -> bool:
14
+ return any(domain in url for domain in self.supported_domains)
9
15
 
10
16
  async def extract(self, url, referer=None) -> ExtractResult:
11
17
  ext_ref = referer or ""
@@ -0,0 +1,41 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import ExtractorBase, ExtractResult
4
+ from urllib.parse import urlparse, parse_qs
5
+
6
+ class VCTPlay(ExtractorBase):
7
+ name = "VCTPlay"
8
+ main_url = "https://vctplay.site"
9
+
10
+ async def extract(self, url, referer=None) -> ExtractResult:
11
+ if referer:
12
+ self.httpx.headers.update({"Referer": referer})
13
+
14
+ # URL'den video ID'sini çıkar
15
+ # https://vctplay.site/video/2hjDGco5exdv -> 2hjDGco5exdv
16
+ video_id = url.split("/")[-1]
17
+ if "?" in video_id:
18
+ video_id = video_id.split("?")[0]
19
+
20
+ # Manifests URL oluştur
21
+ master_url = f"{self.main_url}/manifests/{video_id}/master.txt"
22
+
23
+ # partKey'den isim belirle
24
+ parsed = urlparse(url)
25
+ params = parse_qs(parsed.query)
26
+ part_key = params.get("partKey", [""])[0]
27
+
28
+ name_suffix = ""
29
+ if "turkcedublaj" in part_key.lower():
30
+ name_suffix = "Dublaj"
31
+ elif "turkcealtyazi" in part_key.lower():
32
+ name_suffix = "Altyazı"
33
+
34
+ display_name = f"{self.name} - {name_suffix}" if name_suffix else self.name
35
+
36
+ return ExtractResult(
37
+ name = display_name,
38
+ url = master_url,
39
+ referer = f"{self.main_url}/",
40
+ subtitles = []
41
+ )
@@ -9,6 +9,12 @@ class VidMoly(ExtractorBase):
9
9
  name = "VidMoly"
10
10
  main_url = "https://vidmoly.to"
11
11
 
12
+ # Birden fazla domain destekle
13
+ supported_domains = ["vidmoly.to", "vidmoly.me", "vidmoly.net"]
14
+
15
+ def can_handle_url(self, url: str) -> bool:
16
+ return any(domain in url for domain in self.supported_domains)
17
+
12
18
  async def extract(self, url: str, referer: str = None) -> ExtractResult:
13
19
  if referer:
14
20
  self.httpx.headers.update({"Referer": referer})
@@ -17,11 +23,11 @@ class VidMoly(ExtractorBase):
17
23
  "Sec-Fetch-Dest" : "iframe",
18
24
  })
19
25
 
20
- if self.main_url.endswith(".me"):
21
- self.main_url = self.main_url.replace(".me", ".net")
26
+ if ".me" in url:
22
27
  url = url.replace(".me", ".net")
23
28
 
24
- response = await self.httpx.get(url)
29
+ # VidMoly bazen redirect ediyor, takip et
30
+ response = await self.httpx.get(url, follow_redirects=True)
25
31
  if "Select number" in response.text:
26
32
  secici = Selector(response.text)
27
33
  response = await self.httpx.post(
@@ -33,21 +39,10 @@ class VidMoly(ExtractorBase):
33
39
  "ts" : secici.css("input[name='ts']::attr(value)").get(),
34
40
  "nonce" : secici.css("input[name='nonce']::attr(value)").get(),
35
41
  "ctok" : secici.css("input[name='ctok']::attr(value)").get()
36
- }
42
+ },
43
+ follow_redirects=True
37
44
  )
38
45
 
39
- script_match = re.search(r"sources:\s*\[(.*?)\],", response.text, re.DOTALL)
40
- script_content = script_match[1] if script_match else None
41
-
42
- if not script_content:
43
- raise ValueError("Gerekli script bulunamadı.")
44
-
45
- # Video kaynaklarını ayrıştır
46
- video_data = self._add_marks(script_content, "file")
47
- try:
48
- video_sources = json.loads(f"[{video_data}]")
49
- except json.JSONDecodeError as hata:
50
- raise ValueError("Video kaynakları ayrıştırılamadı.") from hata
51
46
 
52
47
  # Altyazı kaynaklarını ayrıştır
53
48
  subtitles = []
@@ -66,22 +61,49 @@ class VidMoly(ExtractorBase):
66
61
  for sub in subtitle_sources
67
62
  if sub.get("kind") == "captions"
68
63
  ]
69
- # İlk video kaynağını al
70
- video_url = None
71
- for source in video_sources:
72
- if file_url := source.get("file"):
73
- video_url = file_url
74
- break
75
64
 
76
- if not video_url:
77
- raise ValueError("Video URL bulunamadı.")
65
+ script_match = re.search(r"sources:\s*\[(.*?)\],", response.text, re.DOTALL)
66
+ if script_match:
67
+ script_content = script_match[1]
68
+ # Video kaynaklarını ayrıştır
69
+ video_data = self._add_marks(script_content, "file")
70
+ try:
71
+ video_sources = json.loads(f"[{video_data}]")
72
+ # İlk video kaynağını al
73
+ for source in video_sources:
74
+ if file_url := source.get("file"):
75
+ return ExtractResult(
76
+ name = self.name,
77
+ url = file_url,
78
+ referer = self.main_url,
79
+ subtitles = subtitles
80
+ )
81
+ except json.JSONDecodeError:
82
+ pass
83
+
84
+ # Fallback: Doğrudan file regex ile ara (Kotlin mantığı)
85
+ # file:"..." veya file: "..."
86
+ if file_match := re.search(r'file\s*:\s*["\']([^"\']+\.m3u8[^"\']*)["\']', response.text):
87
+ return ExtractResult(
88
+ name = self.name,
89
+ url = file_match.group(1),
90
+ referer = self.main_url,
91
+ subtitles = subtitles
92
+ )
93
+
94
+ # Fallback 2: Herhangi bir file (m3u8 olma şartı olmadan ama tercihen)
95
+ if file_match := re.search(r'file\s*:\s*["\']([^"\']+)["\']', response.text):
96
+ url_candidate = file_match.group(1)
97
+ # Resim dosyalarını hariç tut
98
+ if not url_candidate.endswith(('.jpg', '.png', '.jpeg')):
99
+ return ExtractResult(
100
+ name = self.name,
101
+ url = url_candidate,
102
+ referer = self.main_url,
103
+ subtitles = subtitles
104
+ )
78
105
 
79
- return ExtractResult(
80
- name = self.name,
81
- url = video_url,
82
- referer = self.main_url,
83
- subtitles = subtitles
84
- )
106
+ raise ValueError("Video URL bulunamadı.")
85
107
 
86
108
  def _add_marks(self, text: str, field: str) -> str:
87
109
  """