KekikStream 0.4.5__tar.gz → 2.0.9__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.
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/CLI/pypi_kontrol.py +6 -6
- kekikstream-2.0.9/KekikStream/Core/Extractor/ExtractorBase.py +49 -0
- kekikstream-2.0.9/KekikStream/Core/Extractor/ExtractorLoader.py +98 -0
- kekikstream-2.0.9/KekikStream/Core/Extractor/ExtractorManager.py +75 -0
- {kekikstream-0.4.5/KekikStream/Core → kekikstream-2.0.9/KekikStream/Core/Extractor}/ExtractorModels.py +5 -7
- kekikstream-2.0.9/KekikStream/Core/Extractor/YTDLPCache.py +35 -0
- kekikstream-2.0.9/KekikStream/Core/Media/MediaHandler.py +183 -0
- {kekikstream-0.4.5/KekikStream/Managers → kekikstream-2.0.9/KekikStream/Core/Media}/MediaManager.py +1 -4
- kekikstream-2.0.9/KekikStream/Core/Plugin/PluginBase.py +122 -0
- kekikstream-2.0.9/KekikStream/Core/Plugin/PluginLoader.py +79 -0
- {kekikstream-0.4.5/KekikStream/Managers → kekikstream-2.0.9/KekikStream/Core/Plugin}/PluginManager.py +6 -1
- kekikstream-2.0.9/KekikStream/Core/Plugin/PluginModels.py +79 -0
- {kekikstream-0.4.5/KekikStream/Managers → kekikstream-2.0.9/KekikStream/Core/UI}/UIManager.py +5 -2
- kekikstream-2.0.9/KekikStream/Core/__init__.py +19 -0
- kekikstream-2.0.9/KekikStream/Extractors/CloseLoad.py +26 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/ContentX.py +17 -4
- kekikstream-2.0.9/KekikStream/Extractors/DonilasPlay.py +86 -0
- kekikstream-2.0.9/KekikStream/Extractors/DzenRu.py +38 -0
- kekikstream-2.0.9/KekikStream/Extractors/ExPlay.py +53 -0
- kekikstream-2.0.9/KekikStream/Extractors/HDPlayerSystem.py +41 -0
- kekikstream-2.0.9/KekikStream/Extractors/JetTv.py +45 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/MailRu.py +4 -6
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/MixPlayHD.py +2 -2
- kekikstream-2.0.9/KekikStream/Extractors/MixTiger.py +57 -0
- kekikstream-2.0.9/KekikStream/Extractors/MolyStream.py +34 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/Odnoklassniki.py +22 -11
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/PeaceMakerst.py +10 -4
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/PixelDrain.py +1 -2
- kekikstream-2.0.9/KekikStream/Extractors/PlayerFilmIzle.py +65 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/RapidVid.py +38 -15
- kekikstream-2.0.9/KekikStream/Extractors/SetPlay.py +63 -0
- kekikstream-2.0.9/KekikStream/Extractors/SetPrime.py +45 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/SibNet.py +2 -3
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/Sobreatsesuyp.py +4 -4
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/TRsTX.py +4 -4
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/TauVideo.py +2 -2
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/TurboImgz.py +2 -2
- kekikstream-2.0.9/KekikStream/Extractors/TurkeyPlayer.py +34 -0
- kekikstream-2.0.9/KekikStream/Extractors/VCTPlay.py +41 -0
- kekikstream-2.0.9/KekikStream/Extractors/VidHide.py +72 -0
- kekikstream-2.0.9/KekikStream/Extractors/VidMoly.py +112 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/VidMoxy.py +2 -3
- kekikstream-2.0.9/KekikStream/Extractors/VidPapi.py +89 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/Extractors/VideoSeyred.py +9 -3
- kekikstream-2.0.9/KekikStream/Extractors/YTDLP.py +211 -0
- kekikstream-2.0.9/KekikStream/Extractors/YildizKisaFilm.py +41 -0
- kekikstream-2.0.9/KekikStream/Plugins/BelgeselX.py +204 -0
- kekikstream-2.0.9/KekikStream/Plugins/DiziBox.py +212 -0
- kekikstream-2.0.9/KekikStream/Plugins/DiziPal.py +246 -0
- kekikstream-2.0.9/KekikStream/Plugins/DiziYou.py +197 -0
- kekikstream-2.0.9/KekikStream/Plugins/Dizilla.py +203 -0
- kekikstream-2.0.9/KekikStream/Plugins/FilmBip.py +145 -0
- kekikstream-2.0.9/KekikStream/Plugins/FilmMakinesi.py +121 -0
- kekikstream-2.0.9/KekikStream/Plugins/FilmModu.py +142 -0
- kekikstream-2.0.9/KekikStream/Plugins/FullHDFilm.py +164 -0
- kekikstream-2.0.9/KekikStream/Plugins/FullHDFilmizlesene.py +128 -0
- kekikstream-2.0.9/KekikStream/Plugins/HDFilmCehennemi.py +296 -0
- kekikstream-2.0.9/KekikStream/Plugins/JetFilmizle.py +149 -0
- kekikstream-2.0.9/KekikStream/Plugins/KultFilmler.py +219 -0
- kekikstream-2.0.9/KekikStream/Plugins/RecTV.py +158 -0
- kekikstream-2.0.9/KekikStream/Plugins/RoketDizi.py +225 -0
- kekikstream-2.0.9/KekikStream/Plugins/SelcukFlix.py +308 -0
- kekikstream-2.0.9/KekikStream/Plugins/SetFilmIzle.py +259 -0
- kekikstream-2.0.9/KekikStream/Plugins/SezonlukDizi.py +163 -0
- kekikstream-2.0.9/KekikStream/Plugins/SineWix.py +166 -0
- kekikstream-2.0.9/KekikStream/Plugins/Sinefy.py +241 -0
- kekikstream-2.0.9/KekikStream/Plugins/SinemaCX.py +153 -0
- kekikstream-2.0.9/KekikStream/Plugins/Sinezy.py +146 -0
- kekikstream-2.0.9/KekikStream/Plugins/SuperFilmGeldi.py +127 -0
- kekikstream-2.0.9/KekikStream/Plugins/UgurFilm.py +109 -0
- kekikstream-2.0.9/KekikStream/__init__.py +357 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/requirements.txt +2 -3
- kekikstream-2.0.9/KekikStream.egg-info/PKG-INFO +309 -0
- kekikstream-2.0.9/KekikStream.egg-info/SOURCES.txt +84 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream.egg-info/requires.txt +1 -0
- kekikstream-2.0.9/PKG-INFO +309 -0
- kekikstream-2.0.9/README.md +272 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/setup.py +4 -3
- kekikstream-0.4.5/KekikStream/Core/ExtractorBase.py +0 -42
- kekikstream-0.4.5/KekikStream/Core/ExtractorLoader.py +0 -82
- kekikstream-0.4.5/KekikStream/Core/MediaHandler.py +0 -122
- kekikstream-0.4.5/KekikStream/Core/PluginBase.py +0 -80
- kekikstream-0.4.5/KekikStream/Core/PluginLoader.py +0 -61
- kekikstream-0.4.5/KekikStream/Core/PluginModels.py +0 -73
- kekikstream-0.4.5/KekikStream/Core/__init__.py +0 -9
- kekikstream-0.4.5/KekikStream/Extractors/CloseLoad.py +0 -31
- kekikstream-0.4.5/KekikStream/Extractors/FourCX.py +0 -7
- kekikstream-0.4.5/KekikStream/Extractors/FourPichive.py +0 -7
- kekikstream-0.4.5/KekikStream/Extractors/FourPlayRu.py +0 -7
- kekikstream-0.4.5/KekikStream/Extractors/HDStreamAble.py +0 -7
- kekikstream-0.4.5/KekikStream/Extractors/Hotlinger.py +0 -7
- kekikstream-0.4.5/KekikStream/Extractors/OkRuHTTP.py +0 -7
- kekikstream-0.4.5/KekikStream/Extractors/OkRuSSL.py +0 -7
- kekikstream-0.4.5/KekikStream/Extractors/Pichive.py +0 -7
- kekikstream-0.4.5/KekikStream/Extractors/PlayRu.py +0 -7
- kekikstream-0.4.5/KekikStream/Extractors/VidMoly.py +0 -85
- kekikstream-0.4.5/KekikStream/Managers/ExtractorManager.py +0 -27
- kekikstream-0.4.5/KekikStream/Managers/__init__.py +0 -6
- kekikstream-0.4.5/KekikStream/Plugins/DiziBox.py +0 -138
- kekikstream-0.4.5/KekikStream/Plugins/Dizilla.py +0 -95
- kekikstream-0.4.5/KekikStream/Plugins/FilmMakinesi.py +0 -65
- kekikstream-0.4.5/KekikStream/Plugins/FullHDFilmizlesene.py +0 -78
- kekikstream-0.4.5/KekikStream/Plugins/JetFilmizle.py +0 -92
- kekikstream-0.4.5/KekikStream/Plugins/SezonlukDizi.py +0 -108
- kekikstream-0.4.5/KekikStream/Plugins/SineWix.py +0 -108
- kekikstream-0.4.5/KekikStream/Plugins/UgurFilm.py +0 -75
- kekikstream-0.4.5/KekikStream/__init__.py +0 -255
- kekikstream-0.4.5/KekikStream.egg-info/PKG-INFO +0 -83
- kekikstream-0.4.5/KekikStream.egg-info/SOURCES.txt +0 -61
- kekikstream-0.4.5/PKG-INFO +0 -83
- kekikstream-0.4.5/README.md +0 -59
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/CLI/__init__.py +0 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream/__main__.py +0 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream.egg-info/dependency_links.txt +0 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream.egg-info/entry_points.txt +0 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/KekikStream.egg-info/top_level.txt +0 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/LICENSE +0 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/MANIFEST.in +0 -0
- {kekikstream-0.4.5 → kekikstream-2.0.9}/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 .
|
|
4
|
-
from rich.panel
|
|
5
|
-
from
|
|
6
|
-
from requests
|
|
7
|
-
from subprocess
|
|
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 =
|
|
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,49 @@
|
|
|
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
|
|
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
|
+
@abstractmethod
|
|
33
|
+
async def extract(self, url: str, referer: Optional[str] = None) -> ExtractResult:
|
|
34
|
+
# Alt sınıflar tarafından uygulanacak medya çıkarma fonksiyonu
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
async def close(self):
|
|
38
|
+
"""Close HTTP client."""
|
|
39
|
+
await self.httpx.aclose()
|
|
40
|
+
|
|
41
|
+
def fix_url(self, url: str) -> str:
|
|
42
|
+
# Eksik URL'leri düzelt ve tam URL formatına çevir
|
|
43
|
+
if not url:
|
|
44
|
+
return ""
|
|
45
|
+
|
|
46
|
+
if url.startswith("http") or url.startswith("{\""):
|
|
47
|
+
return url
|
|
48
|
+
|
|
49
|
+
return f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
|
+
|
|
3
|
+
from ...CLI import konsol, cikis_yap
|
|
4
|
+
from .ExtractorBase import ExtractorBase
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import os, importlib.util, traceback
|
|
7
|
+
|
|
8
|
+
class ExtractorLoader:
|
|
9
|
+
def __init__(self, extractors_dir: str):
|
|
10
|
+
# Yerel ve global çıkarıcı dizinlerini ayarla
|
|
11
|
+
self.local_extractors_dir = Path(extractors_dir)
|
|
12
|
+
self.global_extractors_dir = Path(__file__).parent.parent.parent / extractors_dir
|
|
13
|
+
|
|
14
|
+
# Dizin kontrolü
|
|
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]")
|
|
17
|
+
cikis_yap(False)
|
|
18
|
+
|
|
19
|
+
def load_all(self) -> list[ExtractorBase]:
|
|
20
|
+
extractors = []
|
|
21
|
+
|
|
22
|
+
# Eğer yerel dizinde Extractor varsa, sadece onları yükle (eklenti geliştirme modu)
|
|
23
|
+
if self.local_extractors_dir.exists():
|
|
24
|
+
# konsol.log(f"[green][*] Yerel Extractor dizininden yükleniyor: {self.local_extractors_dir}[/green]")
|
|
25
|
+
local_extractors = self._load_from_directory(self.local_extractors_dir)
|
|
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)
|
|
38
|
+
|
|
39
|
+
# Benzersizliği sağlama (modül adı + sınıf adı bazında)
|
|
40
|
+
unique_extractors = []
|
|
41
|
+
seen_names = set()
|
|
42
|
+
for ext in extractors:
|
|
43
|
+
identifier = f"{ext.__module__}.{ext.__name__}"
|
|
44
|
+
if identifier not in seen_names:
|
|
45
|
+
unique_extractors.append(ext)
|
|
46
|
+
seen_names.add(identifier)
|
|
47
|
+
|
|
48
|
+
# konsol.log(f"[blue]Sonuç Extractor'lar: {[e.__name__ for e in unique_extractors]}[/blue]")
|
|
49
|
+
|
|
50
|
+
if not unique_extractors:
|
|
51
|
+
konsol.log("[yellow][!] Yüklenecek bir Extractor bulunamadı![/yellow]")
|
|
52
|
+
|
|
53
|
+
return unique_extractors
|
|
54
|
+
|
|
55
|
+
def _load_from_directory(self, directory: Path) -> list[ExtractorBase]:
|
|
56
|
+
extractors = []
|
|
57
|
+
|
|
58
|
+
# Dizindeki tüm .py dosyalarını tara
|
|
59
|
+
for file in os.listdir(directory):
|
|
60
|
+
if file.endswith(".py") and not file.startswith("__"):
|
|
61
|
+
module_name = file[:-3] # .py uzantısını kaldır
|
|
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)
|
|
67
|
+
|
|
68
|
+
# konsol.log(f"[yellow]{directory} dizininden yüklenen Extractor'lar: {[e.__name__ for e in extractors]}[/yellow]")
|
|
69
|
+
return extractors
|
|
70
|
+
|
|
71
|
+
def _load_extractor(self, directory: Path, module_name: str):
|
|
72
|
+
try:
|
|
73
|
+
# Modül dosyasını bul ve yükle
|
|
74
|
+
path = directory / f"{module_name}.py"
|
|
75
|
+
spec = importlib.util.spec_from_file_location(module_name, path)
|
|
76
|
+
if not spec or not spec.loader:
|
|
77
|
+
return []
|
|
78
|
+
|
|
79
|
+
# Modülü içe aktar
|
|
80
|
+
module = importlib.util.module_from_spec(spec)
|
|
81
|
+
spec.loader.exec_module(module)
|
|
82
|
+
|
|
83
|
+
# Yalnızca doğru modülden gelen ExtractorBase sınıflarını yükle (TÜM CLASS'LAR)
|
|
84
|
+
extractors = []
|
|
85
|
+
for attr in dir(module):
|
|
86
|
+
obj = getattr(module, attr)
|
|
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
|
|
93
|
+
|
|
94
|
+
except Exception as hata:
|
|
95
|
+
konsol.log(f"[red][!] Extractor yüklenirken hata oluştu: {module_name}\nHata: {hata}")
|
|
96
|
+
konsol.print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
97
|
+
|
|
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
|
|
16
|
-
url
|
|
17
|
-
referer
|
|
18
|
-
|
|
19
|
-
|
|
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,183 @@
|
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
|
+
|
|
3
|
+
from ...CLI import konsol
|
|
4
|
+
from ..Extractor.ExtractorModels import ExtractResult
|
|
5
|
+
import subprocess, os
|
|
6
|
+
|
|
7
|
+
class MediaHandler:
|
|
8
|
+
def __init__(self, title: str = "KekikStream"):
|
|
9
|
+
self.title = title
|
|
10
|
+
self.headers = {}
|
|
11
|
+
|
|
12
|
+
def play_media(self, extract_data: ExtractResult):
|
|
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
|
|
16
|
+
|
|
17
|
+
# referer ekle
|
|
18
|
+
if extract_data.referer:
|
|
19
|
+
self.headers["referer"] = extract_data.referer
|
|
20
|
+
|
|
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"]:
|
|
23
|
+
return self.play_with_ytdlp(extract_data)
|
|
24
|
+
|
|
25
|
+
# İşletim sistemine göre oynatıcı seç (Android durumu)
|
|
26
|
+
if subprocess.check_output(['uname', '-o']).strip() == b'Android':
|
|
27
|
+
return self.play_with_android_mxplayer(extract_data)
|
|
28
|
+
|
|
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
|
+
]
|
|
35
|
+
|
|
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
|
|
49
|
+
|
|
50
|
+
def play_with_vlc(self, extract_data: ExtractResult):
|
|
51
|
+
konsol.log(f"[yellow][»] VLC ile Oynatılıyor : {extract_data.url}")
|
|
52
|
+
# konsol.print(self.headers)
|
|
53
|
+
try:
|
|
54
|
+
vlc_command = ["vlc", "--quiet"]
|
|
55
|
+
|
|
56
|
+
if self.title:
|
|
57
|
+
vlc_command.extend([
|
|
58
|
+
f"--meta-title={self.title}",
|
|
59
|
+
f"--input-title-format={self.title}"
|
|
60
|
+
])
|
|
61
|
+
|
|
62
|
+
if "user-agent" in self.headers:
|
|
63
|
+
vlc_command.append(f"--http-user-agent={self.headers.get('user-agent')}")
|
|
64
|
+
|
|
65
|
+
if "referer" in self.headers:
|
|
66
|
+
vlc_command.append(f"--http-referrer={self.headers.get('referer')}")
|
|
67
|
+
|
|
68
|
+
vlc_command.extend(
|
|
69
|
+
f"--sub-file={subtitle.url}" for subtitle in extract_data.subtitles
|
|
70
|
+
)
|
|
71
|
+
vlc_command.append(extract_data.url)
|
|
72
|
+
|
|
73
|
+
with open(os.devnull, "w") as devnull:
|
|
74
|
+
subprocess.run(vlc_command, stdout=devnull, stderr=devnull, check=True)
|
|
75
|
+
|
|
76
|
+
return True
|
|
77
|
+
except subprocess.CalledProcessError as hata:
|
|
78
|
+
konsol.print(f"[red]VLC oynatma hatası: {hata}[/red]")
|
|
79
|
+
konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
|
80
|
+
return False
|
|
81
|
+
except FileNotFoundError:
|
|
82
|
+
konsol.print("[red]VLC bulunamadı! VLC kurulu olduğundan emin olun.[/red]")
|
|
83
|
+
# konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
def play_with_mpv(self, extract_data: ExtractResult):
|
|
87
|
+
konsol.log(f"[yellow][»] MPV ile Oynatılıyor : {extract_data.url}")
|
|
88
|
+
# konsol.print(self.headers)
|
|
89
|
+
try:
|
|
90
|
+
mpv_command = ["mpv"]
|
|
91
|
+
|
|
92
|
+
if self.title:
|
|
93
|
+
mpv_command.append(f"--force-media-title={self.title}")
|
|
94
|
+
|
|
95
|
+
for key, value in self.headers.items():
|
|
96
|
+
mpv_command.append(f"--http-header-fields={key}: {value}")
|
|
97
|
+
|
|
98
|
+
mpv_command.extend(
|
|
99
|
+
f"--sub-file={subtitle.url}" for subtitle in extract_data.subtitles
|
|
100
|
+
)
|
|
101
|
+
mpv_command.append(extract_data.url)
|
|
102
|
+
|
|
103
|
+
with open(os.devnull, "w") as devnull:
|
|
104
|
+
subprocess.run(mpv_command, stdout=devnull, stderr=devnull, check=True)
|
|
105
|
+
|
|
106
|
+
return True
|
|
107
|
+
except subprocess.CalledProcessError as hata:
|
|
108
|
+
konsol.print(f"[red]mpv oynatma hatası: {hata}[/red]")
|
|
109
|
+
konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
|
110
|
+
return False
|
|
111
|
+
except FileNotFoundError:
|
|
112
|
+
konsol.print("[red]mpv bulunamadı! mpv kurulu olduğundan emin olun.[/red]")
|
|
113
|
+
konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
|
114
|
+
return False
|
|
115
|
+
|
|
116
|
+
def play_with_ytdlp(self, extract_data: ExtractResult):
|
|
117
|
+
konsol.log(f"[yellow][»] yt-dlp ile Oynatılıyor : {extract_data.url}")
|
|
118
|
+
# konsol.print(self.headers)
|
|
119
|
+
try:
|
|
120
|
+
ytdlp_command = ["yt-dlp", "--quiet", "--no-warnings"]
|
|
121
|
+
|
|
122
|
+
for key, value in self.headers.items():
|
|
123
|
+
ytdlp_command.extend(["--add-header", f"{key}: {value}"])
|
|
124
|
+
|
|
125
|
+
ytdlp_command.extend([
|
|
126
|
+
"-o", "-",
|
|
127
|
+
extract_data.url
|
|
128
|
+
])
|
|
129
|
+
|
|
130
|
+
mpv_command = ["mpv", "--really-quiet", "-"]
|
|
131
|
+
|
|
132
|
+
if self.title:
|
|
133
|
+
mpv_command.append(f"--force-media-title={self.title}")
|
|
134
|
+
|
|
135
|
+
mpv_command.extend(
|
|
136
|
+
f"--sub-file={subtitle.url}" for subtitle in extract_data.subtitles
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
with subprocess.Popen(ytdlp_command, stdout=subprocess.PIPE) as ytdlp_proc:
|
|
140
|
+
subprocess.run(mpv_command, stdin=ytdlp_proc.stdout, check=True)
|
|
141
|
+
|
|
142
|
+
return True
|
|
143
|
+
except subprocess.CalledProcessError as hata:
|
|
144
|
+
konsol.print(f"[red]Oynatma hatası: {hata}[/red]")
|
|
145
|
+
konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
|
146
|
+
return False
|
|
147
|
+
except FileNotFoundError:
|
|
148
|
+
konsol.print("[red]yt-dlp veya mpv bulunamadı! Kurulumlarından emin olun.[/red]")
|
|
149
|
+
konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
def play_with_android_mxplayer(self, extract_data: ExtractResult):
|
|
153
|
+
konsol.log(f"[yellow][»] MxPlayer ile Oynatılıyor : {extract_data.url}")
|
|
154
|
+
# konsol.print(self.headers)
|
|
155
|
+
paketler = [
|
|
156
|
+
"com.mxtech.videoplayer.ad/.ActivityScreen", # Free sürüm
|
|
157
|
+
"com.mxtech.videoplayer.pro/.ActivityScreen" # Pro sürüm
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
for paket in paketler:
|
|
161
|
+
try:
|
|
162
|
+
android_command = [
|
|
163
|
+
"am", "start",
|
|
164
|
+
"-a", "android.intent.action.VIEW",
|
|
165
|
+
"-d", extract_data.url,
|
|
166
|
+
"-n", paket
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
if self.title:
|
|
170
|
+
android_command.extend(["--es", "title", self.title])
|
|
171
|
+
|
|
172
|
+
with open(os.devnull, "w") as devnull:
|
|
173
|
+
subprocess.run(android_command, stdout=devnull, stderr=devnull, check=True)
|
|
174
|
+
|
|
175
|
+
return True
|
|
176
|
+
except subprocess.CalledProcessError as hata:
|
|
177
|
+
konsol.print(f"[red]{paket} oynatma hatası: {hata}[/red]")
|
|
178
|
+
konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
|
179
|
+
return False
|
|
180
|
+
except FileNotFoundError:
|
|
181
|
+
konsol.print(f"Paket: {paket}, Hata: MX Player kurulu değil")
|
|
182
|
+
konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
|
183
|
+
return False
|
{kekikstream-0.4.5/KekikStream/Managers → kekikstream-2.0.9/KekikStream/Core/Media}/MediaManager.py
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from .MediaHandler import MediaHandler
|
|
4
4
|
|
|
5
5
|
class MediaManager:
|
|
6
6
|
def __init__(self):
|
|
@@ -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)
|
|
@@ -0,0 +1,122 @@
|
|
|
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 .PluginModels import MainPageResult, SearchResult, MovieInfo
|
|
7
|
+
from ..Media.MediaHandler import MediaHandler
|
|
8
|
+
from ..Extractor.ExtractorManager import ExtractorManager
|
|
9
|
+
from urllib.parse import urljoin
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
class PluginBase(ABC):
|
|
13
|
+
name = "Plugin"
|
|
14
|
+
language = "tr"
|
|
15
|
+
main_url = "https://example.com"
|
|
16
|
+
favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
|
|
17
|
+
description = "No description provided."
|
|
18
|
+
|
|
19
|
+
main_page = {}
|
|
20
|
+
|
|
21
|
+
async def url_update(self, new_url: str):
|
|
22
|
+
self.favicon = self.favicon.replace(self.main_url, new_url)
|
|
23
|
+
self.main_page = {url.replace(self.main_url, new_url): category for url, category in self.main_page.items()}
|
|
24
|
+
self.main_url = new_url
|
|
25
|
+
|
|
26
|
+
def __init__(self):
|
|
27
|
+
# cloudscraper - for bypassing Cloudflare
|
|
28
|
+
self.cloudscraper = CloudScraper()
|
|
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
|
+
|
|
42
|
+
self.media_handler = MediaHandler()
|
|
43
|
+
self.ex_manager = ExtractorManager()
|
|
44
|
+
|
|
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
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
async def search(self, query: str) -> list[SearchResult]:
|
|
52
|
+
"""Kullanıcı arama sorgusuna göre sonuç döndürür."""
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
async def load_item(self, url: str) -> MovieInfo:
|
|
57
|
+
"""Bir medya öğesi hakkında detaylı bilgi döndürür."""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
@abstractmethod
|
|
61
|
+
async def load_links(self, url: str) -> list[dict]:
|
|
62
|
+
"""
|
|
63
|
+
Bir medya öğesi için oynatma bağlantılarını döndürür.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
url: Medya URL'si
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Dictionary listesi, her biri şu alanları içerir:
|
|
70
|
+
- url (str, zorunlu): Video URL'si
|
|
71
|
+
- name (str, zorunlu): Gösterim adı (tüm bilgileri içerir)
|
|
72
|
+
- referer (str, opsiyonel): Referer header
|
|
73
|
+
- subtitles (list, opsiyonel): Altyazı listesi
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
[
|
|
77
|
+
{
|
|
78
|
+
"url": "https://example.com/video.m3u8",
|
|
79
|
+
"name": "HDFilmCehennemi | 1080p TR Dublaj"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
"""
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
async def close(self):
|
|
86
|
+
"""Close HTTP client."""
|
|
87
|
+
await self.httpx.aclose()
|
|
88
|
+
|
|
89
|
+
def fix_url(self, url: str) -> str:
|
|
90
|
+
if not url:
|
|
91
|
+
return ""
|
|
92
|
+
|
|
93
|
+
if url.startswith("http") or url.startswith("{\""):
|
|
94
|
+
return url
|
|
95
|
+
|
|
96
|
+
return f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
|
|
97
|
+
|
|
98
|
+
@staticmethod
|
|
99
|
+
def clean_title(title: str) -> str:
|
|
100
|
+
suffixes = [
|
|
101
|
+
" izle",
|
|
102
|
+
" full film",
|
|
103
|
+
" filmini full",
|
|
104
|
+
" full türkçe",
|
|
105
|
+
" alt yazılı",
|
|
106
|
+
" altyazılı",
|
|
107
|
+
" tr dublaj",
|
|
108
|
+
" hd türkçe",
|
|
109
|
+
" türkçe dublaj",
|
|
110
|
+
" yeşilçam ",
|
|
111
|
+
" erotik fil",
|
|
112
|
+
" türkçe",
|
|
113
|
+
" yerli",
|
|
114
|
+
" tüekçe dublaj",
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
cleaned_title = title.strip()
|
|
118
|
+
|
|
119
|
+
for suffix in suffixes:
|
|
120
|
+
cleaned_title = re.sub(f"{re.escape(suffix)}.*$", "", cleaned_title, flags=re.IGNORECASE).strip()
|
|
121
|
+
|
|
122
|
+
return cleaned_title
|