KekikStream 1.7.1__py3-none-any.whl → 2.0.2__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 (86) hide show
  1. KekikStream/Core/Extractor/ExtractorBase.py +13 -7
  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 +21 -9
  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 +4 -5
  13. KekikStream/Extractors/ContentX.py +4 -6
  14. KekikStream/Extractors/ContentX_.py +40 -0
  15. KekikStream/Extractors/DzenRu.py +38 -0
  16. KekikStream/Extractors/ExPlay.py +53 -0
  17. KekikStream/Extractors/FirePlayer.py +60 -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 +7 -7
  25. KekikStream/Extractors/{OkRuHTTP.py → Odnoklassniki_.py} +5 -1
  26. KekikStream/Extractors/PeaceMakerst.py +4 -5
  27. KekikStream/Extractors/{HDStreamAble.py → PeaceMakerst_.py} +1 -1
  28. KekikStream/Extractors/PixelDrain.py +1 -2
  29. KekikStream/Extractors/PlayerFilmIzle.py +62 -0
  30. KekikStream/Extractors/RapidVid.py +2 -3
  31. KekikStream/Extractors/RapidVid_.py +7 -0
  32. KekikStream/Extractors/SetPlay.py +57 -0
  33. KekikStream/Extractors/SetPrime.py +45 -0
  34. KekikStream/Extractors/SibNet.py +2 -3
  35. KekikStream/Extractors/Sobreatsesuyp.py +4 -5
  36. KekikStream/Extractors/TRsTX.py +4 -5
  37. KekikStream/Extractors/TauVideo.py +2 -3
  38. KekikStream/Extractors/TurboImgz.py +2 -3
  39. KekikStream/Extractors/TurkeyPlayer.py +34 -0
  40. KekikStream/Extractors/VidHide.py +72 -0
  41. KekikStream/Extractors/VidMoly.py +4 -5
  42. KekikStream/Extractors/{VidMolyMe.py → VidMoly_.py} +1 -1
  43. KekikStream/Extractors/VidMoxy.py +2 -3
  44. KekikStream/Extractors/VidPapi.py +89 -0
  45. KekikStream/Extractors/VideoSeyred.py +3 -4
  46. KekikStream/Extractors/YTDLP.py +177 -0
  47. KekikStream/Extractors/YildizKisaFilm.py +41 -0
  48. KekikStream/Plugins/DiziBox.py +18 -23
  49. KekikStream/Plugins/DiziPal.py +16 -16
  50. KekikStream/Plugins/DiziYou.py +48 -23
  51. KekikStream/Plugins/Dizilla.py +47 -32
  52. KekikStream/Plugins/FilmBip.py +145 -0
  53. KekikStream/Plugins/FilmMakinesi.py +6 -8
  54. KekikStream/Plugins/FilmModu.py +9 -9
  55. KekikStream/Plugins/FullHDFilm.py +164 -0
  56. KekikStream/Plugins/FullHDFilmizlesene.py +4 -8
  57. KekikStream/Plugins/HDFilmCehennemi.py +15 -19
  58. KekikStream/Plugins/JetFilmizle.py +67 -49
  59. KekikStream/Plugins/KultFilmler.py +219 -0
  60. KekikStream/Plugins/RecTV.py +18 -22
  61. KekikStream/Plugins/RoketDizi.py +232 -0
  62. KekikStream/Plugins/SelcukFlix.py +309 -0
  63. KekikStream/Plugins/SezonlukDizi.py +12 -13
  64. KekikStream/Plugins/SineWix.py +8 -12
  65. KekikStream/Plugins/Sinefy.py +238 -0
  66. KekikStream/Plugins/SinemaCX.py +157 -0
  67. KekikStream/Plugins/Sinezy.py +146 -0
  68. KekikStream/Plugins/SuperFilmGeldi.py +121 -0
  69. KekikStream/Plugins/UgurFilm.py +7 -11
  70. KekikStream/__init__.py +34 -24
  71. KekikStream/requirements.txt +3 -4
  72. kekikstream-2.0.2.dist-info/METADATA +309 -0
  73. kekikstream-2.0.2.dist-info/RECORD +82 -0
  74. KekikStream/Extractors/FourCX.py +0 -7
  75. KekikStream/Extractors/FourPichive.py +0 -7
  76. KekikStream/Extractors/FourPlayRu.py +0 -7
  77. KekikStream/Extractors/Hotlinger.py +0 -7
  78. KekikStream/Extractors/OkRuSSL.py +0 -7
  79. KekikStream/Extractors/Pichive.py +0 -7
  80. KekikStream/Extractors/PlayRu.py +0 -7
  81. kekikstream-1.7.1.dist-info/METADATA +0 -109
  82. kekikstream-1.7.1.dist-info/RECORD +0 -63
  83. {kekikstream-1.7.1.dist-info → kekikstream-2.0.2.dist-info}/WHEEL +0 -0
  84. {kekikstream-1.7.1.dist-info → kekikstream-2.0.2.dist-info}/entry_points.txt +0 -0
  85. {kekikstream-1.7.1.dist-info → kekikstream-2.0.2.dist-info}/licenses/LICENSE +0 -0
  86. {kekikstream-1.7.1.dist-info → kekikstream-2.0.2.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,8 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
3
  from abc import ABC, abstractmethod
4
- from curl_cffi import AsyncSession
5
4
  from cloudscraper import CloudScraper
5
+ from httpx import AsyncClient
6
6
  from typing import Optional
7
7
  from .ExtractorModels import ExtractResult
8
8
  from urllib.parse import urljoin
@@ -13,11 +13,17 @@ class ExtractorBase(ABC):
13
13
  main_url = ""
14
14
 
15
15
  def __init__(self):
16
- # HTTP istekleri için oturum oluştur
17
- self.cffi = AsyncSession(impersonate="firefox135")
16
+ # cloudscraper - for bypassing Cloudflare
18
17
  self.cloudscraper = CloudScraper()
19
- self.cffi.cookies.update(self.cloudscraper.cookies)
20
- self.cffi.headers.update({"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 15.7; rv:135.0) Gecko/20100101 Firefox/135.0"})
18
+
19
+ # httpx - lightweight and safe for most HTTP requests
20
+ self.httpx = AsyncClient(timeout = 10)
21
+ self.httpx.headers.update(self.cloudscraper.headers)
22
+ self.httpx.cookies.update(self.cloudscraper.cookies)
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
+ })
21
27
 
22
28
  def can_handle_url(self, url: str) -> bool:
23
29
  # URL'nin bu çıkarıcı tarafından işlenip işlenemeyeceğini kontrol et
@@ -29,8 +35,8 @@ class ExtractorBase(ABC):
29
35
  pass
30
36
 
31
37
  async def close(self):
32
- # HTTP oturumunu güvenli bir şekilde kapat
33
- await self.cffi.close()
38
+ """Close HTTP client."""
39
+ await self.httpx.aclose()
34
40
 
35
41
  def fix_url(self, url: str) -> str:
36
42
  # Eksik URL'leri düzelt ve tam URL formatına çevir
@@ -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 []
@@ -7,25 +7,69 @@ class ExtractorManager:
7
7
  def __init__(self, extractor_dir="Extractors"):
8
8
  # Çıkarıcı yükleyiciyi başlat ve tüm çıkarıcıları yükle
9
9
  self.extractor_loader = ExtractorLoader(extractor_dir)
10
- self.extractors = self.extractor_loader.load_all()
10
+ self.extractors = self.extractor_loader.load_all() # Sadece class'lar
11
11
 
12
- def find_extractor(self, link) -> ExtractorBase:
13
- # Verilen bağlantıyı işleyebilecek çıkarıcıyı bul
12
+ # Lazy loading: Instance'lar ilk kullanımda oluşturulacak
13
+ self._extractor_instances = None # None = henüz oluşturulmadı
14
+ self._ytdlp_extractor = None
15
+ self._initialized = False
16
+
17
+ def _ensure_initialized(self):
18
+ """
19
+ Lazy initialization: İlk kullanımda TÜM extractorları initialize et
20
+
21
+ Startup'ta sadece class'ları yükledik (hızlı).
22
+ Şimdi instance'ları oluştur ve cache'le (bir kere).
23
+ """
24
+ if self._initialized:
25
+ return
26
+
27
+ # Instance listesi oluştur
28
+ self._extractor_instances = []
29
+
30
+ # TÜM extractorları instance'la
14
31
  for extractor_cls in self.extractors:
15
- extractor:ExtractorBase = extractor_cls()
32
+ instance = extractor_cls()
33
+
34
+ # YTDLP'yi ayrı tut
35
+ if instance.name == "yt-dlp":
36
+ self._ytdlp_extractor = instance
37
+ else:
38
+ self._extractor_instances.append(instance)
39
+
40
+ # YTDLP'yi EN BAŞA ekle
41
+ if self._ytdlp_extractor:
42
+ self._extractor_instances.insert(0, self._ytdlp_extractor)
43
+
44
+ self._initialized = True
45
+
46
+ def find_extractor(self, link) -> ExtractorBase:
47
+ """
48
+ Verilen bağlantıyı işleyebilecek çıkarıcıyı bul
49
+ """
50
+ # Lazy loading: İlk kullanımda extractorları initialize et
51
+ self._ensure_initialized()
52
+
53
+ # Cached instance'ları kullan
54
+ for extractor in self._extractor_instances:
16
55
  if extractor.can_handle_url(link):
17
56
  return extractor
18
57
 
19
58
  return None
20
59
 
21
60
  def map_links_to_extractors(self, links) -> dict:
22
- # Bağlantıları uygun çıkarıcılarla eşleştir
61
+ """
62
+ Bağlantıları uygun çıkarıcılarla eşleştir
63
+ """
64
+ # Lazy loading: İlk kullanımda extractorları initialize et
65
+ self._ensure_initialized()
66
+
23
67
  mapping = {}
24
68
  for link in links:
25
- for extractor_cls in self.extractors:
26
- extractor:ExtractorBase = extractor_cls()
69
+ # Cached instance'ları kullan
70
+ for extractor in self._extractor_instances:
27
71
  if extractor.can_handle_url(link):
28
72
  mapping[link] = f"{extractor.name:<30} » {link.replace(extractor.main_url, '')}"
29
- break
73
+ break # İlk eşleşmede dur
30
74
 
31
- return mapping
75
+ return mapping
@@ -1,8 +1,6 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
3
  from pydantic import BaseModel
4
- from typing import List, Optional
5
-
6
4
 
7
5
  class Subtitle(BaseModel):
8
6
  """Altyazı modeli."""
@@ -12,8 +10,8 @@ class Subtitle(BaseModel):
12
10
 
13
11
  class ExtractResult(BaseModel):
14
12
  """Extractor'ın döndürmesi gereken sonuç modeli."""
15
- name : str
16
- url : str
17
- referer : str
18
- headers : Optional[dict] = {}
19
- subtitles : List[Subtitle] = []
13
+ name : str
14
+ url : str
15
+ referer : str | None = None
16
+ user_agent : str | None = None
17
+ subtitles : list[Subtitle] = []
@@ -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 []
@@ -5,36 +5,47 @@ from ..Extractor.ExtractorModels import ExtractResult
5
5
  import subprocess, os
6
6
 
7
7
  class MediaHandler:
8
- def __init__(self, title: str = "KekikStream", headers: dict = None):
9
- # Varsayılan HTTP başlıklarını ayarla
10
- if headers is None:
11
- headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5)"}
12
-
13
- self.headers = headers
8
+ def __init__(self, title: str = "KekikStream"):
14
9
  self.title = title
10
+ self.headers = {}
15
11
 
16
12
  def play_media(self, extract_data: ExtractResult):
17
- # Referer varsa headers'a ekle
18
- if extract_data.referer:
19
- self.headers.update({"Referer": extract_data.referer})
13
+ # user-agent ekle (varsayılan veya extract_data'dan)
14
+ user_agent = extract_data.user_agent or "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5)"
15
+ self.headers["user-agent"] = user_agent
20
16
 
21
- # ExtractResult'tan gelen headers'ları ekle
22
- if extract_data.headers:
23
- self.headers.update(extract_data.headers)
17
+ # referer ekle
18
+ if extract_data.referer:
19
+ self.headers["referer"] = extract_data.referer
24
20
 
25
- # Google Drive gibi özel durumlar için yt-dlp kullan
26
- if self.headers.get("User-Agent") in ["googleusercontent", "Mozilla/5.0 (X11; Linux x86_64; rv:101.0) Gecko/20100101 Firefox/101.0"]:
21
+ # Özel Durumlar (RecTV vs. Googleusercontent)
22
+ if user_agent in ["googleusercontent", "Mozilla/5.0 (X11; Linux x86_64; rv:101.0) Gecko/20100101 Firefox/101.0"]:
27
23
  return self.play_with_ytdlp(extract_data)
28
24
 
29
- # İşletim sistemine göre oynatıcı seç
25
+ # İşletim sistemine göre oynatıcı seç (Android durumu)
30
26
  if subprocess.check_output(['uname', '-o']).strip() == b'Android':
31
27
  return self.play_with_android_mxplayer(extract_data)
32
28
 
33
- # Cookie veya alt yazılar varsa mpv kullan
34
- if "Cookie" in self.headers or extract_data.subtitles:
35
- return self.play_with_mpv(extract_data)
29
+ # Oynatıcı öncelik sırası (fallback zincirleme)
30
+ players = [
31
+ ("MPV", self.play_with_mpv),
32
+ ("VLC", self.play_with_vlc),
33
+ ("yt-dlp", self.play_with_ytdlp)
34
+ ]
36
35
 
37
- return self.play_with_vlc(extract_data) or self.play_with_mpv(extract_data)
36
+ # Fallback zincirleme
37
+ for player_name, player_func in players:
38
+ try:
39
+ result = player_func(extract_data)
40
+ if result or result is None: # None = MPV (exception yok)
41
+ konsol.log(f"[green][✓] {player_name} ile başarılı[/green]")
42
+ return True
43
+ except Exception as e:
44
+ konsol.log(f"[yellow][⚠] {player_name} hatası: {e}[/yellow]")
45
+ continue
46
+
47
+ konsol.print("[red][✗] Hiçbir oynatıcı çalışmadı![/red]")
48
+ return False
38
49
 
39
50
  def play_with_vlc(self, extract_data: ExtractResult):
40
51
  konsol.log(f"[yellow][»] VLC ile Oynatılıyor : {extract_data.url}")
@@ -48,11 +59,11 @@ class MediaHandler:
48
59
  f"--input-title-format={self.title}"
49
60
  ])
50
61
 
51
- if "User-Agent" in self.headers:
52
- vlc_command.append(f"--http-user-agent={self.headers.get('User-Agent')}")
62
+ if "user-agent" in self.headers:
63
+ vlc_command.append(f"--http-user-agent={self.headers.get('user-agent')}")
53
64
 
54
- if "Referer" in self.headers:
55
- vlc_command.append(f"--http-referrer={self.headers.get('Referer')}")
65
+ if "referer" in self.headers:
66
+ vlc_command.append(f"--http-referrer={self.headers.get('referer')}")
56
67
 
57
68
  vlc_command.extend(
58
69
  f"--sub-file={subtitle.url}" for subtitle in extract_data.subtitles
@@ -92,12 +103,15 @@ class MediaHandler:
92
103
  with open(os.devnull, "w") as devnull:
93
104
  subprocess.run(mpv_command, stdout=devnull, stderr=devnull, check=True)
94
105
 
106
+ return True
95
107
  except subprocess.CalledProcessError as hata:
96
108
  konsol.print(f"[red]mpv oynatma hatası: {hata}[/red]")
97
109
  konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
110
+ return False
98
111
  except FileNotFoundError:
99
112
  konsol.print("[red]mpv bulunamadı! mpv kurulu olduğundan emin olun.[/red]")
100
113
  konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
114
+ return False
101
115
 
102
116
  def play_with_ytdlp(self, extract_data: ExtractResult):
103
117
  konsol.log(f"[yellow][»] yt-dlp ile Oynatılıyor : {extract_data.url}")
@@ -125,12 +139,15 @@ class MediaHandler:
125
139
  with subprocess.Popen(ytdlp_command, stdout=subprocess.PIPE) as ytdlp_proc:
126
140
  subprocess.run(mpv_command, stdin=ytdlp_proc.stdout, check=True)
127
141
 
142
+ return True
128
143
  except subprocess.CalledProcessError as hata:
129
144
  konsol.print(f"[red]Oynatma hatası: {hata}[/red]")
130
145
  konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
146
+ return False
131
147
  except FileNotFoundError:
132
148
  konsol.print("[red]yt-dlp veya mpv bulunamadı! Kurulumlarından emin olun.[/red]")
133
149
  konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
150
+ return False
134
151
 
135
152
  def play_with_android_mxplayer(self, extract_data: ExtractResult):
136
153
  konsol.log(f"[yellow][»] MxPlayer ile Oynatılıyor : {extract_data.url}")
@@ -155,11 +172,12 @@ class MediaHandler:
155
172
  with open(os.devnull, "w") as devnull:
156
173
  subprocess.run(android_command, stdout=devnull, stderr=devnull, check=True)
157
174
 
158
- return
159
-
175
+ return True
160
176
  except subprocess.CalledProcessError as hata:
161
177
  konsol.print(f"[red]{paket} oynatma hatası: {hata}[/red]")
162
178
  konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
179
+ return False
163
180
  except FileNotFoundError:
164
181
  konsol.print(f"Paket: {paket}, Hata: MX Player kurulu değil")
165
- konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
182
+ konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
183
+ return False
@@ -12,8 +12,5 @@ class MediaManager:
12
12
  def get_title(self):
13
13
  return self.media_handler.title
14
14
 
15
- def set_headers(self, headers):
16
- self.media_handler.headers.update(headers)
17
-
18
15
  def play_media(self, extract_data):
19
16
  self.media_handler.play_media(extract_data)
@@ -1,8 +1,8 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
3
  from abc import ABC, abstractmethod
4
- from curl_cffi import AsyncSession
5
4
  from cloudscraper import CloudScraper
5
+ from httpx import AsyncClient
6
6
  from .PluginModels import MainPageResult, SearchResult, MovieInfo
7
7
  from ..Media.MediaHandler import MediaHandler
8
8
  from ..Extractor.ExtractorManager import ExtractorManager
@@ -24,17 +24,28 @@ class PluginBase(ABC):
24
24
  self.main_url = new_url
25
25
 
26
26
  def __init__(self):
27
- self.cffi = AsyncSession(impersonate="firefox135")
27
+ # cloudscraper - for bypassing Cloudflare
28
28
  self.cloudscraper = CloudScraper()
29
- self.cffi.cookies.update(self.cloudscraper.cookies)
30
- self.cffi.headers.update({"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 15.7; rv:135.0) Gecko/20100101 Firefox/135.0"})
29
+
30
+ # httpx - lightweight and safe for most HTTP requests
31
+ self.httpx = AsyncClient(
32
+ timeout = 3,
33
+ follow_redirects = True
34
+ )
35
+ self.httpx.headers.update(self.cloudscraper.headers)
36
+ self.httpx.cookies.update(self.cloudscraper.cookies)
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
+ })
41
+
31
42
  self.media_handler = MediaHandler()
32
43
  self.ex_manager = ExtractorManager()
33
44
 
34
- # @abstractmethod
35
- # async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
36
- # """Ana sayfadaki popüler içerikleri döndürür."""
37
- # pass
45
+ @abstractmethod
46
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
47
+ """Ana sayfadaki popüler içerikleri döndürür."""
48
+ pass
38
49
 
39
50
  @abstractmethod
40
51
  async def search(self, query: str) -> list[SearchResult]:
@@ -72,7 +83,8 @@ class PluginBase(ABC):
72
83
  pass
73
84
 
74
85
  async def close(self):
75
- await self.cffi.close()
86
+ """Close HTTP client."""
87
+ await self.httpx.aclose()
76
88
 
77
89
  def fix_url(self, url: str) -> str:
78
90
  if not url:
@@ -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]")
@@ -1,34 +1,33 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
3
  from pydantic import BaseModel, field_validator, model_validator
4
- from typing import List, Optional
5
4
 
6
5
  class MainPageResult(BaseModel):
7
6
  """Ana sayfa sonucunda dönecek veri modeli."""
8
7
  category : str
9
8
  title : str
10
9
  url : str
11
- poster : Optional[str] = None
10
+ poster : str | None = None
12
11
 
13
12
 
14
13
  class SearchResult(BaseModel):
15
14
  """Arama sonucunda dönecek veri modeli."""
16
15
  title : str
17
16
  url : str
18
- poster : Optional[str] = None
17
+ poster : str | None = None
19
18
 
20
19
 
21
20
  class MovieInfo(BaseModel):
22
21
  """Bir medya öğesinin bilgilerini tutan model."""
23
22
  url : str
24
- poster : Optional[str] = None
25
- title : Optional[str] = None
26
- description : Optional[str] = None
27
- tags : Optional[str] = None
28
- rating : Optional[str] = None
29
- year : Optional[str] = None
30
- actors : Optional[str] = None
31
- duration : Optional[int] = None
23
+ poster : str | None = None
24
+ title : str | None = None
25
+ description : str | None = None
26
+ tags : str | None = None
27
+ rating : str | None = None
28
+ year : str | None = None
29
+ actors : str | None = None
30
+ duration : int | None = None
32
31
 
33
32
  @field_validator("tags", "actors", mode="before")
34
33
  @classmethod
@@ -42,10 +41,10 @@ class MovieInfo(BaseModel):
42
41
 
43
42
 
44
43
  class Episode(BaseModel):
45
- season : Optional[int] = None
46
- episode : Optional[int] = None
47
- title : Optional[str] = None
48
- url : Optional[str] = None
44
+ season : int | None = None
45
+ episode : int | None = None
46
+ title : str | None = None
47
+ url : str | None = None
49
48
 
50
49
  @model_validator(mode="after")
51
50
  def check_title(self) -> "Episode":
@@ -58,16 +57,16 @@ class Episode(BaseModel):
58
57
  return self
59
58
 
60
59
  class SeriesInfo(BaseModel):
61
- url : Optional[str] = None
62
- poster : Optional[str] = None
63
- title : Optional[str] = None
64
- description : Optional[str] = None
65
- tags : Optional[str] = None
66
- rating : Optional[str] = None
67
- year : Optional[str] = None
68
- actors : Optional[str] = None
69
- duration : Optional[int] = None
70
- episodes : Optional[List[Episode]] = None
60
+ url : str | None = None
61
+ poster : str | None = None
62
+ title : str | None = None
63
+ description : str | None = None
64
+ tags : str | None = None
65
+ rating : str | None = None
66
+ year : str | None = None
67
+ actors : str | None = None
68
+ duration : int | None = None
69
+ episodes : list[Episode] | None = None
71
70
 
72
71
  @field_validator("tags", "actors", mode="before")
73
72
  @classmethod
@@ -77,4 +76,4 @@ class SeriesInfo(BaseModel):
77
76
  @field_validator("rating", "year", mode="before")
78
77
  @classmethod
79
78
  def ensure_string(cls, value):
80
- return str(value) if value is not None else value
79
+ return str(value) if value is not None else value
@@ -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,13 +6,13 @@ 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:
13
- self.cffi.headers.update({"Referer": referer})
13
+ self.httpx.headers.update({"Referer": referer})
14
14
 
15
- istek = await self.cffi.get(url)
15
+ istek = await self.httpx.get(url)
16
16
  istek.raise_for_status()
17
17
 
18
18
  eval_func = re.compile(r'\s*(eval\(function[\s\S].*)\s*').findall(istek.text)[0]
@@ -22,6 +22,5 @@ class CloseLoadExtractor(ExtractorBase):
22
22
  name = self.name,
23
23
  url = m3u_link,
24
24
  referer = self.main_url,
25
- headers = {},
26
25
  subtitles = []
27
- )
26
+ )
@@ -9,9 +9,9 @@ class ContentX(ExtractorBase):
9
9
 
10
10
  async def extract(self, url, referer=None) -> list[ExtractResult]:
11
11
  if referer:
12
- self.cffi.headers.update({"Referer": referer})
12
+ self.httpx.headers.update({"Referer": referer})
13
13
 
14
- istek = await self.cffi.get(url)
14
+ istek = await self.httpx.get(url)
15
15
  istek.raise_for_status()
16
16
  i_source = istek.text
17
17
 
@@ -39,7 +39,7 @@ class ContentX(ExtractorBase):
39
39
  )
40
40
  )
41
41
 
42
- vid_source_request = await self.cffi.get(f"{self.main_url}/source2.php?v={i_extract_value}", headers={"Referer": referer or self.main_url})
42
+ vid_source_request = await self.httpx.get(f"{self.main_url}/source2.php?v={i_extract_value}", headers={"Referer": referer or self.main_url})
43
43
  vid_source_request.raise_for_status()
44
44
 
45
45
  vid_source = vid_source_request.text
@@ -53,14 +53,13 @@ class ContentX(ExtractorBase):
53
53
  name = self.name,
54
54
  url = m3u_link,
55
55
  referer = url,
56
- headers = {},
57
56
  subtitles = subtitles
58
57
  )
59
58
  ]
60
59
 
61
60
  if i_dublaj := re.search(r',\"([^"]+)\",\"Türkçe"', i_source):
62
61
  dublaj_value = i_dublaj[1]
63
- dublaj_source_request = await self.cffi.get(f"{self.main_url}/source2.php?v={dublaj_value}", headers={"Referer": referer or self.main_url})
62
+ dublaj_source_request = await self.httpx.get(f"{self.main_url}/source2.php?v={dublaj_value}", headers={"Referer": referer or self.main_url})
64
63
  dublaj_source_request.raise_for_status()
65
64
 
66
65
  dublaj_source = dublaj_source_request.text
@@ -74,7 +73,6 @@ class ContentX(ExtractorBase):
74
73
  name = f"{self.name} Türkçe Dublaj",
75
74
  url = dublaj_link,
76
75
  referer = url,
77
- headers = {},
78
76
  subtitles = []
79
77
  )
80
78
  )