KekikStream 1.0.5__tar.gz → 2.4.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/CLI/pypi_kontrol.py +6 -6
  2. kekikstream-2.4.6/KekikStream/Core/Extractor/ExtractorBase.py +55 -0
  3. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/Extractor/ExtractorLoader.py +34 -25
  4. kekikstream-2.4.6/KekikStream/Core/Extractor/ExtractorManager.py +75 -0
  5. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/Extractor/ExtractorModels.py +5 -7
  6. kekikstream-2.4.6/KekikStream/Core/Extractor/YTDLPCache.py +35 -0
  7. kekikstream-2.4.6/KekikStream/Core/HTMLHelper.py +205 -0
  8. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/Media/MediaHandler.py +56 -35
  9. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/Media/MediaManager.py +0 -3
  10. kekikstream-2.4.6/KekikStream/Core/Plugin/PluginBase.py +189 -0
  11. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/Plugin/PluginLoader.py +20 -15
  12. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/Plugin/PluginManager.py +2 -2
  13. kekikstream-2.4.6/KekikStream/Core/Plugin/PluginModels.py +76 -0
  14. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/__init__.py +6 -1
  15. kekikstream-2.4.6/KekikStream/Extractors/CloseLoad.py +52 -0
  16. kekikstream-2.4.6/KekikStream/Extractors/ContentX.py +54 -0
  17. kekikstream-2.4.6/KekikStream/Extractors/DonilasPlay.py +42 -0
  18. kekikstream-2.4.6/KekikStream/Extractors/DzenRu.py +24 -0
  19. kekikstream-2.4.6/KekikStream/Extractors/ExPlay.py +35 -0
  20. kekikstream-2.4.6/KekikStream/Extractors/Filemoon.py +63 -0
  21. kekikstream-2.4.6/KekikStream/Extractors/HDMomPlayer.py +30 -0
  22. kekikstream-2.4.6/KekikStream/Extractors/HDPlayerSystem.py +23 -0
  23. kekikstream-2.4.6/KekikStream/Extractors/HotStream.py +27 -0
  24. kekikstream-2.4.6/KekikStream/Extractors/JFVid.py +19 -0
  25. kekikstream-2.4.6/KekikStream/Extractors/JetTv.py +32 -0
  26. kekikstream-2.4.6/KekikStream/Extractors/MailRu.py +20 -0
  27. kekikstream-2.4.6/KekikStream/Extractors/MixPlayHD.py +28 -0
  28. kekikstream-2.4.6/KekikStream/Extractors/MixTiger.py +34 -0
  29. kekikstream-2.4.6/KekikStream/Extractors/MolyStream.py +38 -0
  30. kekikstream-2.4.6/KekikStream/Extractors/Odnoklassniki.py +41 -0
  31. kekikstream-2.4.6/KekikStream/Extractors/PeaceMakerst.py +36 -0
  32. kekikstream-2.4.6/KekikStream/Extractors/PixelDrain.py +20 -0
  33. kekikstream-2.4.6/KekikStream/Extractors/PlayerFilmIzle.py +46 -0
  34. kekikstream-2.4.6/KekikStream/Extractors/RapidVid.py +80 -0
  35. kekikstream-2.4.6/KekikStream/Extractors/SetPlay.py +41 -0
  36. kekikstream-2.4.6/KekikStream/Extractors/SetPrime.py +42 -0
  37. kekikstream-2.4.6/KekikStream/Extractors/SibNet.py +17 -0
  38. kekikstream-2.4.6/KekikStream/Extractors/Sobreatsesuyp.py +37 -0
  39. kekikstream-2.4.6/KekikStream/Extractors/TRsTX.py +37 -0
  40. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Extractors/TauVideo.py +2 -2
  41. kekikstream-2.4.6/KekikStream/Extractors/TurboImgz.py +17 -0
  42. kekikstream-2.4.6/KekikStream/Extractors/TurkeyPlayer.py +34 -0
  43. kekikstream-2.4.6/KekikStream/Extractors/VCTPlay.py +23 -0
  44. kekikstream-2.4.6/KekikStream/Extractors/VidHide.py +56 -0
  45. kekikstream-2.4.6/KekikStream/Extractors/VidMoly.py +98 -0
  46. kekikstream-2.4.6/KekikStream/Extractors/VidMoxy.py +37 -0
  47. kekikstream-2.4.6/KekikStream/Extractors/VidPapi.py +57 -0
  48. kekikstream-2.4.6/KekikStream/Extractors/VideoSeyred.py +32 -0
  49. kekikstream-2.4.6/KekikStream/Extractors/Videostr.py +58 -0
  50. kekikstream-2.4.6/KekikStream/Extractors/Vidoza.py +18 -0
  51. kekikstream-2.4.6/KekikStream/Extractors/YTDLP.py +211 -0
  52. kekikstream-2.4.6/KekikStream/Extractors/YildizKisaFilm.py +23 -0
  53. kekikstream-2.4.6/KekikStream/Plugins/BelgeselX.py +217 -0
  54. kekikstream-2.4.6/KekikStream/Plugins/DiziBox.py +216 -0
  55. kekikstream-2.4.6/KekikStream/Plugins/DiziMom.py +155 -0
  56. kekikstream-2.4.6/KekikStream/Plugins/DiziPal.py +180 -0
  57. kekikstream-2.4.6/KekikStream/Plugins/DiziYou.py +135 -0
  58. kekikstream-2.4.6/KekikStream/Plugins/Dizilla.py +218 -0
  59. kekikstream-2.4.6/KekikStream/Plugins/FilmBip.py +136 -0
  60. kekikstream-2.4.6/KekikStream/Plugins/FilmEkseni.py +120 -0
  61. kekikstream-2.4.6/KekikStream/Plugins/FilmMakinesi.py +140 -0
  62. kekikstream-2.4.6/KekikStream/Plugins/FilmModu.py +150 -0
  63. kekikstream-2.4.6/KekikStream/Plugins/Filmatek.py +179 -0
  64. kekikstream-2.4.6/KekikStream/Plugins/Full4kizle.py +174 -0
  65. kekikstream-2.4.6/KekikStream/Plugins/FullHDFilm.py +179 -0
  66. kekikstream-2.4.6/KekikStream/Plugins/FullHDFilmizlesene.py +137 -0
  67. kekikstream-2.4.6/KekikStream/Plugins/HDFilmCehennemi.py +265 -0
  68. kekikstream-2.4.6/KekikStream/Plugins/JetFilmizle.py +178 -0
  69. kekikstream-2.4.6/KekikStream/Plugins/KultFilmler.py +189 -0
  70. kekikstream-2.4.6/KekikStream/Plugins/RecTV.py +150 -0
  71. kekikstream-2.4.6/KekikStream/Plugins/RoketDizi.py +217 -0
  72. kekikstream-2.4.6/KekikStream/Plugins/SelcukFlix.py +332 -0
  73. kekikstream-2.4.6/KekikStream/Plugins/SetFilmIzle.py +220 -0
  74. kekikstream-2.4.6/KekikStream/Plugins/SezonlukDizi.py +182 -0
  75. kekikstream-2.4.6/KekikStream/Plugins/SineWix.py +158 -0
  76. kekikstream-2.4.6/KekikStream/Plugins/Sinefy.py +207 -0
  77. kekikstream-2.4.6/KekikStream/Plugins/SinemaCX.py +169 -0
  78. kekikstream-2.4.6/KekikStream/Plugins/Sinezy.py +129 -0
  79. kekikstream-2.4.6/KekikStream/Plugins/SuperFilmGeldi.py +142 -0
  80. kekikstream-2.4.6/KekikStream/Plugins/UgurFilm.py +120 -0
  81. kekikstream-2.4.6/KekikStream/Plugins/Watch32.py +175 -0
  82. kekikstream-2.4.6/KekikStream/Plugins/YabanciDizi.py +231 -0
  83. kekikstream-2.4.6/KekikStream/__init__.py +338 -0
  84. kekikstream-1.0.5/KekikStream.egg-info/requires.txt → kekikstream-2.4.6/KekikStream/requirements.txt +2 -2
  85. kekikstream-2.4.6/KekikStream.egg-info/PKG-INFO +319 -0
  86. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream.egg-info/SOURCES.txt +44 -12
  87. kekikstream-2.4.6/KekikStream.egg-info/requires.txt +9 -0
  88. kekikstream-2.4.6/PKG-INFO +319 -0
  89. kekikstream-2.4.6/README.md +282 -0
  90. {kekikstream-1.0.5 → kekikstream-2.4.6}/setup.py +6 -6
  91. kekikstream-1.0.5/KekikStream/Core/Extractor/ExtractorBase.py +0 -48
  92. kekikstream-1.0.5/KekikStream/Core/Extractor/ExtractorManager.py +0 -31
  93. kekikstream-1.0.5/KekikStream/Core/Plugin/PluginBase.py +0 -80
  94. kekikstream-1.0.5/KekikStream/Core/Plugin/PluginModels.py +0 -63
  95. kekikstream-1.0.5/KekikStream/Extractors/CloseLoad.py +0 -31
  96. kekikstream-1.0.5/KekikStream/Extractors/ContentX.py +0 -80
  97. kekikstream-1.0.5/KekikStream/Extractors/FourCX.py +0 -7
  98. kekikstream-1.0.5/KekikStream/Extractors/FourPichive.py +0 -7
  99. kekikstream-1.0.5/KekikStream/Extractors/FourPlayRu.py +0 -7
  100. kekikstream-1.0.5/KekikStream/Extractors/HDStreamAble.py +0 -7
  101. kekikstream-1.0.5/KekikStream/Extractors/Hotlinger.py +0 -7
  102. kekikstream-1.0.5/KekikStream/Extractors/MailRu.py +0 -40
  103. kekikstream-1.0.5/KekikStream/Extractors/MixPlayHD.py +0 -42
  104. kekikstream-1.0.5/KekikStream/Extractors/MolyStream.py +0 -16
  105. kekikstream-1.0.5/KekikStream/Extractors/Odnoklassniki.py +0 -106
  106. kekikstream-1.0.5/KekikStream/Extractors/OkRuHTTP.py +0 -7
  107. kekikstream-1.0.5/KekikStream/Extractors/OkRuSSL.py +0 -7
  108. kekikstream-1.0.5/KekikStream/Extractors/PeaceMakerst.py +0 -57
  109. kekikstream-1.0.5/KekikStream/Extractors/Pichive.py +0 -7
  110. kekikstream-1.0.5/KekikStream/Extractors/PixelDrain.py +0 -28
  111. kekikstream-1.0.5/KekikStream/Extractors/PlayRu.py +0 -7
  112. kekikstream-1.0.5/KekikStream/Extractors/RapidVid.py +0 -60
  113. kekikstream-1.0.5/KekikStream/Extractors/SibNet.py +0 -29
  114. kekikstream-1.0.5/KekikStream/Extractors/Sobreatsesuyp.py +0 -59
  115. kekikstream-1.0.5/KekikStream/Extractors/TRsTX.py +0 -67
  116. kekikstream-1.0.5/KekikStream/Extractors/TurboImgz.py +0 -25
  117. kekikstream-1.0.5/KekikStream/Extractors/VidMoly.py +0 -89
  118. kekikstream-1.0.5/KekikStream/Extractors/VidMolyMe.py +0 -7
  119. kekikstream-1.0.5/KekikStream/Extractors/VidMoxy.py +0 -50
  120. kekikstream-1.0.5/KekikStream/Extractors/VideoSeyred.py +0 -47
  121. kekikstream-1.0.5/KekikStream/Plugins/DiziBox.py +0 -147
  122. kekikstream-1.0.5/KekikStream/Plugins/DiziYou.py +0 -127
  123. kekikstream-1.0.5/KekikStream/Plugins/Dizilla.py +0 -106
  124. kekikstream-1.0.5/KekikStream/Plugins/FilmMakinesi.py +0 -65
  125. kekikstream-1.0.5/KekikStream/Plugins/FullHDFilmizlesene.py +0 -78
  126. kekikstream-1.0.5/KekikStream/Plugins/JetFilmizle.py +0 -92
  127. kekikstream-1.0.5/KekikStream/Plugins/RecTV.py +0 -118
  128. kekikstream-1.0.5/KekikStream/Plugins/SezonlukDizi.py +0 -108
  129. kekikstream-1.0.5/KekikStream/Plugins/Shorten.py +0 -223
  130. kekikstream-1.0.5/KekikStream/Plugins/SineWix.py +0 -111
  131. kekikstream-1.0.5/KekikStream/Plugins/UgurFilm.py +0 -75
  132. kekikstream-1.0.5/KekikStream/__init__.py +0 -355
  133. kekikstream-1.0.5/KekikStream/requirements.txt +0 -10
  134. kekikstream-1.0.5/KekikStream.egg-info/PKG-INFO +0 -102
  135. kekikstream-1.0.5/PKG-INFO +0 -102
  136. kekikstream-1.0.5/README.md +0 -66
  137. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/CLI/__init__.py +0 -0
  138. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/UI/UIManager.py +0 -0
  139. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/__main__.py +0 -0
  140. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream.egg-info/dependency_links.txt +0 -0
  141. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream.egg-info/entry_points.txt +0 -0
  142. {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream.egg-info/top_level.txt +0 -0
  143. {kekikstream-1.0.5 → kekikstream-2.4.6}/LICENSE +0 -0
  144. {kekikstream-1.0.5 → kekikstream-2.4.6}/MANIFEST.in +0 -0
  145. {kekikstream-1.0.5 → kekikstream-2.4.6}/setup.cfg +0 -0
@@ -1,16 +1,16 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from . import konsol
4
- from rich.panel import Panel
5
- from pkg_resources import get_distribution
6
- from requests import get
7
- from subprocess import check_call
3
+ from . import konsol
4
+ from rich.panel import Panel
5
+ from importlib import metadata
6
+ from requests import get
7
+ from subprocess import check_call
8
8
  import sys
9
9
 
10
10
  def pypi_kontrol_guncelle(paket_adi: str):
11
11
  try:
12
12
  konsol.print(f"[bold cyan] {paket_adi} Güncellemesi kontrol ediliyor...[/bold cyan]")
13
- mevcut_surum = get_distribution(paket_adi).version
13
+ mevcut_surum = metadata.version(paket_adi)
14
14
  konsol.print(Panel(f"[cyan]Yüklü sürüm:[/cyan] [bold yellow]{mevcut_surum}[/bold yellow]"))
15
15
 
16
16
  istek = get(f"https://pypi.org/pypi/{paket_adi}/json")
@@ -0,0 +1,55 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from abc import ABC, abstractmethod
4
+ from cloudscraper import CloudScraper
5
+ from httpx import AsyncClient
6
+ from typing import Optional
7
+ from .ExtractorModels import ExtractResult
8
+ from urllib.parse import urljoin, urlparse
9
+
10
+ class ExtractorBase(ABC):
11
+ # Çıkarıcının temel özellikleri
12
+ name = "Extractor"
13
+ main_url = ""
14
+
15
+ def __init__(self):
16
+ # cloudscraper - for bypassing Cloudflare
17
+ self.cloudscraper = CloudScraper()
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
+ })
27
+
28
+ def can_handle_url(self, url: str) -> bool:
29
+ # URL'nin bu çıkarıcı tarafından işlenip işlenemeyeceğini kontrol et
30
+ return self.main_url in url
31
+
32
+ def get_base_url(self, url: str) -> str:
33
+ """URL'den base URL'i çıkar (scheme + netloc)"""
34
+ parsed = urlparse(url)
35
+ return f"{parsed.scheme}://{parsed.netloc}"
36
+
37
+ @abstractmethod
38
+ async def extract(self, url: str, referer: Optional[str] = None) -> ExtractResult:
39
+ # Alt sınıflar tarafından uygulanacak medya çıkarma fonksiyonu
40
+ pass
41
+
42
+ async def close(self):
43
+ """Close HTTP client."""
44
+ await self.httpx.aclose()
45
+
46
+ def fix_url(self, url: str) -> str:
47
+ # Eksik URL'leri düzelt ve tam URL formatına çevir
48
+ if not url:
49
+ return ""
50
+
51
+ if url.startswith("http") or url.startswith("{\""):
52
+ return url.replace("\\", "")
53
+
54
+ url = f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
55
+ return url.replace("\\", "")
@@ -3,7 +3,7 @@
3
3
  from ...CLI import konsol, cikis_yap
4
4
  from .ExtractorBase import ExtractorBase
5
5
  from pathlib import Path
6
- import os, importlib.util
6
+ import os, importlib.util, traceback
7
7
 
8
8
  class ExtractorLoader:
9
9
  def __init__(self, extractors_dir: str):
@@ -13,25 +13,28 @@ class ExtractorLoader:
13
13
 
14
14
  # Dizin kontrolü
15
15
  if not self.local_extractors_dir.exists() and not self.global_extractors_dir.exists():
16
- konsol.log(f"[red][!] Extractor dizini bulunamadı: {self.global_extractors_dir}[/red]")
16
+ # konsol.log(f"[red][!] Extractor dizini bulunamadı: {self.global_extractors_dir}[/red]")
17
17
  cikis_yap(False)
18
18
 
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
- konsol.log(f"[green][*] Yerel Extractor dizininden yükleniyor: {self.local_extractors_dir}[/green]")
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
- konsol.log(f"[green]Yerel Extractor'lar: {[e.__name__ for e in local_extractors]}[/green]")
34
- extractors.extend(local_extractors)
26
+ # konsol.log(f"[green]Yerel Extractor'lar: {[e.__name__ for e in local_extractors]}[/green]")
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 = []
@@ -42,7 +45,7 @@ class ExtractorLoader:
42
45
  unique_extractors.append(ext)
43
46
  seen_names.add(identifier)
44
47
 
45
- konsol.log(f"[blue]Sonuç Extractor'lar: {[e.__name__ for e in unique_extractors]}[/blue]")
48
+ # konsol.log(f"[blue]Sonuç Extractor'lar: {[e.__name__ for e in unique_extractors]}[/blue]")
46
49
 
47
50
  if not unique_extractors:
48
51
  konsol.log("[yellow][!] Yüklenecek bir Extractor bulunamadı![/yellow]")
@@ -56,12 +59,13 @@ class ExtractorLoader:
56
59
  for file in os.listdir(directory):
57
60
  if file.endswith(".py") and not file.startswith("__"):
58
61
  module_name = file[:-3] # .py uzantısını kaldır
59
- 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)
62
+ # konsol.log(f"[cyan]Okunan Dosya\t\t: {module_name}[/cyan]")
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
- konsol.log(f"[yellow]{directory} dizininden yüklenen Extractor'lar: {[e.__name__ for e in extractors]}[/yellow]")
68
+ # konsol.log(f"[yellow]{directory} dizininden yüklenen Extractor'lar: {[e.__name__ for e in extractors]}[/yellow]")
65
69
  return extractors
66
70
 
67
71
  def _load_extractor(self, directory: Path, module_name: str):
@@ -70,20 +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:
83
- konsol.log(f"[green]Yüklenen sınıf\t\t: {module_name}.{obj.__name__} ({obj.__module__}.{obj.__name__})[/green]")
84
- return obj
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:
89
+ # konsol.log(f"[green]Yüklenen sınıf\t\t: {module_name}.{obj.__name__} ({obj.__module__}.{obj.__name__})[/green]")
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}")
96
+ konsol.print(f"[dim]{traceback.format_exc()}[/dim]")
88
97
 
89
- return None
98
+ return []
@@ -0,0 +1,75 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from .ExtractorLoader import ExtractorLoader
4
+ from .ExtractorBase import ExtractorBase
5
+
6
+ class ExtractorManager:
7
+ def __init__(self, extractor_dir="Extractors"):
8
+ # Çıkarıcı yükleyiciyi başlat ve tüm çıkarıcıları yükle
9
+ self.extractor_loader = ExtractorLoader(extractor_dir)
10
+ self.extractors = self.extractor_loader.load_all() # Sadece class'lar
11
+
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
31
+ for extractor_cls in self.extractors:
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:
55
+ if extractor.can_handle_url(link):
56
+ return extractor
57
+
58
+ return None
59
+
60
+ def map_links_to_extractors(self, links) -> dict:
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
+
67
+ mapping = {}
68
+ for link in links:
69
+ # Cached instance'ları kullan
70
+ for extractor in self._extractor_instances:
71
+ if extractor.can_handle_url(link):
72
+ mapping[link] = f"{extractor.name:<30} » {link.replace(extractor.main_url, '')}"
73
+ break # İlk eşleşmede dur
74
+
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
- subtitles : List[Subtitle] = []
19
- headers : Optional[dict] = {}
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 []
@@ -0,0 +1,205 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from selectolax.parser import HTMLParser, Node
7
+
8
+
9
+ class HTMLHelper:
10
+ """
11
+ Selectolax ile HTML parsing işlemlerini temiz, kısa ve okunabilir hale getiren yardımcı sınıf.
12
+ """
13
+
14
+ def __init__(self, html: str):
15
+ self.html = html
16
+ self.parser = HTMLParser(html)
17
+
18
+ # ========================
19
+ # SELECTOR (CSS) İŞLEMLERİ
20
+ # ========================
21
+
22
+ def _root(self, element: Node | None) -> Node | HTMLParser:
23
+ """İşlem yapılacak temel elementi döndürür."""
24
+ return element if element is not None else self.parser
25
+
26
+ def select(self, selector: str, element: Node | None = None) -> list[Node]:
27
+ """CSS selector ile tüm eşleşen elementleri döndür."""
28
+ return self._root(element).css(selector)
29
+
30
+ def select_first(self, selector: str | None, element: Node | None = None) -> Node | None:
31
+ """CSS selector ile ilk eşleşen elementi döndür."""
32
+ if not selector:
33
+ return element
34
+ return self._root(element).css_first(selector)
35
+
36
+ def select_text(self, selector: str | None = None, element: Node | None = None) -> str | None:
37
+ """CSS selector ile element bul ve text içeriğini döndür."""
38
+ el = self.select_first(selector, element)
39
+ if not el:
40
+ return None
41
+ val = el.text(strip=True)
42
+ return val or None
43
+
44
+ def select_texts(self, selector: str, element: Node | None = None) -> list[str]:
45
+ """CSS selector ile tüm eşleşen elementlerin text içeriklerini döndür."""
46
+ out: list[str] = []
47
+ for el in self.select(selector, element):
48
+ txt = el.text(strip=True)
49
+ if txt:
50
+ out.append(txt)
51
+ return out
52
+
53
+ def select_attr(self, selector: str | None, attr: str, element: Node | None = None) -> str | None:
54
+ """CSS selector ile element bul ve attribute değerini döndür."""
55
+ el = self.select_first(selector, element)
56
+ return el.attrs.get(attr) if el else None
57
+
58
+ def select_attrs(self, selector: str, attr: str, element: Node | None = None) -> list[str]:
59
+ """CSS selector ile tüm eşleşen elementlerin attribute değerlerini döndür."""
60
+ out: list[str] = []
61
+ for el in self.select(selector, element):
62
+ val = el.attrs.get(attr)
63
+ if val:
64
+ out.append(val)
65
+ return out
66
+
67
+ def select_poster(self, selector: str = "img", element: Node | None = None) -> str | None:
68
+ """Poster URL'sini çıkar. Önce data-src, sonra src dener."""
69
+ el = self.select_first(selector, element)
70
+ if not el:
71
+ return None
72
+ return el.attrs.get("data-src") or el.attrs.get("src")
73
+
74
+ def select_direct_text(self, selector: str, element: Node | None = None) -> str | None:
75
+ """
76
+ Elementin yalnızca "kendi" düz metnini döndürür (child elementlerin text'ini katmadan).
77
+ """
78
+ el = self.select_first(selector, element)
79
+ if not el:
80
+ return None
81
+
82
+ # type: ignore[call-arg]
83
+ val = el.text(strip=True, deep=False)
84
+ return val or None
85
+
86
+ # ========================
87
+ # META (LABEL -> VALUE) İŞLEMLERİ
88
+ # ========================
89
+
90
+ def meta_value(self, label: str, container_selector: str | None = None) -> str | None:
91
+ """
92
+ Herhangi bir container içinde: LABEL metnini içeren bir elementten SONRA gelen metni döndürür.
93
+ label örn: "Oyuncular", "Yapım Yılı", "IMDB"
94
+ """
95
+ needle = label.casefold()
96
+
97
+ # Belirli bir container varsa içinde ara, yoksa tüm dökümanda
98
+ targets = self.select(container_selector) if container_selector else [self.parser.body]
99
+
100
+ for root in targets:
101
+ if not root: continue
102
+
103
+ # Kalın/vurgulu elementlerde (span, strong, b, label, dt) label'ı ara
104
+ for label_el in self.select("span, strong, b, label, dt", root):
105
+ txt = (label_el.text(strip=True) or "").casefold()
106
+ if needle not in txt:
107
+ continue
108
+
109
+ # 1) Elementin kendi içindeki text'te LABEL: VALUE formatı olabilir
110
+ # "Oyuncular: Brad Pitt" gibi. LABEL: sonrasını al.
111
+ full_txt = label_el.text(strip=True)
112
+ if ":" in full_txt and needle in full_txt.split(":")[0].casefold():
113
+ val = full_txt.split(":", 1)[1].strip()
114
+ if val: return val
115
+
116
+ # 2) Label sonrası gelen ilk text node'u veya element'i al
117
+ curr = label_el.next
118
+ while curr:
119
+ if curr.tag == "-text":
120
+ val = curr.text(strip=True).strip(" :")
121
+ if val: return val
122
+ elif curr.tag != "br":
123
+ val = curr.text(strip=True).strip(" :")
124
+ if val: return val
125
+ else: # <br> gördüysek satır bitmiştir
126
+ break
127
+ curr = curr.next
128
+
129
+ return None
130
+
131
+ def meta_list(self, label: str, container_selector: str | None = None, sep: str = ",") -> list[str]:
132
+ """meta_value(...) çıktısını veya label'ın ebeveynindeki linkleri listeye döndürür."""
133
+ needle = label.casefold()
134
+ targets = self.select(container_selector) if container_selector else [self.parser.body]
135
+
136
+ for root in targets:
137
+ if not root: continue
138
+ for label_el in self.select("span, strong, b, label, dt", root):
139
+ if needle in (label_el.text(strip=True) or "").casefold():
140
+ # Eğer elementin ebeveyninde linkler varsa (Kutucuklu yapı), onları al
141
+ links = self.select_texts("a", label_el.parent)
142
+ if links: return links
143
+
144
+ # Yoksa düz metin olarak meta_value mantığıyla al
145
+ raw = self.meta_value(label, container_selector=container_selector)
146
+ if not raw: return []
147
+ return [x.strip() for x in raw.split(sep) if x.strip()]
148
+
149
+ return []
150
+
151
+ # ========================
152
+ # REGEX İŞLEMLERİ
153
+ # ========================
154
+
155
+ def _regex_source(self, target: str | int | None) -> str:
156
+ """Regex için kaynak metni döndürür."""
157
+ return target if isinstance(target, str) else self.html
158
+
159
+ def regex_first(self, pattern: str, target: str | int | None = None, group: int | None = 1) -> str | tuple | None:
160
+ """Regex ile arama yap, istenen grubu döndür (group=None ise tüm grupları tuple olarak döndür)."""
161
+ match = re.search(pattern, self._regex_source(target))
162
+ if not match:
163
+ return None
164
+
165
+ if group is None:
166
+ return match.groups()
167
+
168
+ last_idx = match.lastindex or 0
169
+ return match.group(group) if last_idx >= group else match.group(0)
170
+
171
+ def regex_all(self, pattern: str, target: str | int | None = None) -> list[str] | list[tuple]:
172
+ """Regex ile tüm eşleşmeleri döndür."""
173
+ return re.findall(pattern, self._regex_source(target))
174
+
175
+ def regex_replace(self, pattern: str, repl: str, target: str | int | None = None) -> str:
176
+ """Regex ile replace yap."""
177
+ return re.sub(pattern, repl, self._regex_source(target))
178
+
179
+ # ========================
180
+ # ÖZEL AYIKLAYICILAR
181
+ # ========================
182
+
183
+ @staticmethod
184
+ def extract_season_episode(text: str) -> tuple[int | None, int | None]:
185
+ """Metin içinden sezon ve bölüm numarasını çıkar."""
186
+ if m := re.search(r"[Ss](\d+)[Ee](\d+)", text):
187
+ return int(m.group(1)), int(m.group(2))
188
+
189
+ s = re.search(r"(\d+)\.\s*[Ss]ezon|[Ss]ezon[- ]?(\d+)|-(\d+)-sezon|S(\d+)|(\d+)\.[Ss]", text, re.I)
190
+ e = re.search(r"(\d+)\.\s*[Bb][öo]l[üu]m|[Bb][öo]l[üu]m[- ]?(\d+)|-(\d+)-bolum|[Ee](\d+)", text, re.I)
191
+
192
+ s_val = next((int(g) for g in s.groups() if g), None) if s else None
193
+ e_val = next((int(g) for g in e.groups() if g), None) if e else None
194
+
195
+ return s_val, e_val
196
+
197
+ def extract_year(self, *selectors: str, pattern: str = r"(\d{4})") -> int | None:
198
+ """Birden fazla selector veya regex ile yıl bilgisini çıkar."""
199
+ for selector in selectors:
200
+ if text := self.select_text(selector):
201
+ if m := re.search(r"(\d{4})", text):
202
+ return int(m.group(1))
203
+
204
+ val = self.regex_first(pattern)
205
+ return int(val) if val and val.isdigit() else None