KekikStream 0.2.0__tar.gz → 0.5.3__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.2.0 → kekikstream-0.5.3}/KekikStream/CLI/__init__.py +1 -1
- kekikstream-0.5.3/KekikStream/CLI/pypi_kontrol.py +30 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Core/ExtractorBase.py +13 -2
- kekikstream-0.5.3/KekikStream/Core/ExtractorLoader.py +82 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Core/ExtractorModels.py +2 -0
- kekikstream-0.5.3/KekikStream/Core/MediaHandler.py +108 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Core/PluginBase.py +6 -5
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Core/PluginLoader.py +3 -5
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Core/PluginModels.py +6 -16
- kekikstream-0.5.3/KekikStream/Extractors/ContentX.py +80 -0
- kekikstream-0.5.3/KekikStream/Extractors/FourCX.py +7 -0
- kekikstream-0.5.3/KekikStream/Extractors/FourPichive.py +7 -0
- kekikstream-0.5.3/KekikStream/Extractors/FourPlayRu.py +7 -0
- kekikstream-0.5.3/KekikStream/Extractors/HDStreamAble.py +7 -0
- kekikstream-0.5.3/KekikStream/Extractors/Hotlinger.py +7 -0
- kekikstream-0.5.3/KekikStream/Extractors/MixPlayHD.py +42 -0
- kekikstream-0.5.3/KekikStream/Extractors/Odnoklassniki.py +106 -0
- kekikstream-0.5.3/KekikStream/Extractors/OkRuHTTP.py +7 -0
- kekikstream-0.5.3/KekikStream/Extractors/OkRuSSL.py +7 -0
- kekikstream-0.5.3/KekikStream/Extractors/PeaceMakerst.py +57 -0
- kekikstream-0.5.3/KekikStream/Extractors/Pichive.py +7 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/PixelDrain.py +1 -1
- kekikstream-0.5.3/KekikStream/Extractors/PlayRu.py +7 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/RapidVid.py +9 -10
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/SibNet.py +1 -1
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/Sobreatsesuyp.py +5 -6
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/TRsTX.py +5 -6
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/TauVideo.py +11 -10
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/TurboImgz.py +9 -12
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/VidMoly.py +25 -33
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/VidMoxy.py +1 -1
- kekikstream-0.5.3/KekikStream/Extractors/VideoSeyred.py +47 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Managers/MediaManager.py +1 -1
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Managers/UIManager.py +4 -0
- kekikstream-0.5.3/KekikStream/Plugins/DiziBox.py +138 -0
- kekikstream-0.5.3/KekikStream/Plugins/Dizilla.py +95 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Plugins/FilmMakinesi.py +8 -8
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Plugins/FullHDFilmizlesene.py +5 -4
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Plugins/JetFilmizle.py +4 -8
- kekikstream-0.5.3/KekikStream/Plugins/RecTV.py +111 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Plugins/SezonlukDizi.py +5 -5
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Plugins/SineWix.py +4 -1
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Plugins/UgurFilm.py +3 -3
- kekikstream-0.5.3/KekikStream/__init__.py +255 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/__main__.py +1 -1
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/requirements.txt +2 -1
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream.egg-info/PKG-INFO +4 -5
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream.egg-info/SOURCES.txt +18 -1
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream.egg-info/requires.txt +2 -1
- {kekikstream-0.2.0 → kekikstream-0.5.3}/PKG-INFO +4 -5
- {kekikstream-0.2.0 → kekikstream-0.5.3}/README.md +1 -3
- {kekikstream-0.2.0 → kekikstream-0.5.3}/setup.py +3 -2
- kekikstream-0.2.0/KekikStream/CLI/check_update.py +0 -33
- kekikstream-0.2.0/KekikStream/Core/ExtractorLoader.py +0 -61
- kekikstream-0.2.0/KekikStream/Core/MediaHandler.py +0 -68
- kekikstream-0.2.0/KekikStream/__init__.py +0 -236
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Core/__init__.py +0 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/CloseLoad.py +0 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Extractors/MailRu.py +0 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Managers/ExtractorManager.py +0 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Managers/PluginManager.py +0 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream/Managers/__init__.py +0 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream.egg-info/dependency_links.txt +0 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream.egg-info/entry_points.txt +0 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/KekikStream.egg-info/top_level.txt +0 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/LICENSE +0 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/MANIFEST.in +0 -0
- {kekikstream-0.2.0 → kekikstream-0.5.3}/setup.cfg +0 -0
@@ -1,4 +1,4 @@
|
|
1
1
|
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
2
2
|
|
3
3
|
from Kekik.cli import konsol, cikis_yap, hata_salla, log_salla, hata_yakala, bellek_temizle, temizle
|
4
|
-
from .
|
4
|
+
from .pypi_kontrol import pypi_kontrol_guncelle
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
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
|
8
|
+
import sys
|
9
|
+
|
10
|
+
def pypi_kontrol_guncelle(paket_adi: str):
|
11
|
+
try:
|
12
|
+
konsol.print(f"[bold cyan] {paket_adi} Güncellemesi kontrol ediliyor...[/bold cyan]")
|
13
|
+
mevcut_surum = get_distribution(paket_adi).version
|
14
|
+
konsol.print(Panel(f"[cyan]Yüklü sürüm:[/cyan] [bold yellow]{mevcut_surum}[/bold yellow]"))
|
15
|
+
|
16
|
+
istek = get(f"https://pypi.org/pypi/{paket_adi}/json")
|
17
|
+
if istek.status_code == 200:
|
18
|
+
son_surum = istek.json()["info"]["version"]
|
19
|
+
konsol.print(Panel(f"[cyan]En son sürüm:[/cyan] [bold green]{son_surum}[/bold green]"))
|
20
|
+
|
21
|
+
if mevcut_surum != son_surum:
|
22
|
+
konsol.print(f"[bold red]{paket_adi} güncelleniyor...[/bold red]")
|
23
|
+
check_call([sys.executable, "-m", "pip", "install", "--upgrade", paket_adi, "--break-system-packages"])
|
24
|
+
konsol.print(f"[bold green]{paket_adi} güncellendi![/bold green]")
|
25
|
+
else:
|
26
|
+
konsol.print(f"[bold green]{paket_adi} zaten güncel.[/bold green]")
|
27
|
+
else:
|
28
|
+
konsol.print("[bold red]PyPI'ye erişilemiyor. Güncelleme kontrolü atlanıyor.[/bold red]")
|
29
|
+
except Exception as hata:
|
30
|
+
konsol.print(f"[bold red]Güncelleme kontrolü sırasında hata oluştu: {hata}[/bold red]")
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
from abc import ABC, abstractmethod
|
4
4
|
from httpx import AsyncClient, Timeout
|
5
|
+
from cloudscraper import CloudScraper
|
5
6
|
from typing import Optional
|
6
7
|
from .ExtractorModels import ExtractResult
|
7
8
|
|
@@ -15,8 +16,9 @@ class ExtractorBase(ABC):
|
|
15
16
|
"User-Agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5)",
|
16
17
|
"Accept" : "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
17
18
|
},
|
18
|
-
timeout = Timeout(10.0)
|
19
|
+
timeout = Timeout(10.0)
|
19
20
|
)
|
21
|
+
self.cloudscraper = CloudScraper()
|
20
22
|
|
21
23
|
def can_handle_url(self, url: str) -> bool:
|
22
24
|
"""URL'nin bu extractor tarafından işlenip işlenemeyeceğini kontrol eder."""
|
@@ -28,4 +30,13 @@ class ExtractorBase(ABC):
|
|
28
30
|
pass
|
29
31
|
|
30
32
|
async def close(self):
|
31
|
-
await self.oturum.aclose()
|
33
|
+
await self.oturum.aclose()
|
34
|
+
|
35
|
+
def fix_url(self, url: str) -> str:
|
36
|
+
if not url:
|
37
|
+
return ""
|
38
|
+
|
39
|
+
if url.startswith("http") or url.startswith("{\""):
|
40
|
+
return url
|
41
|
+
|
42
|
+
return f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
|
@@ -0,0 +1,82 @@
|
|
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
|
7
|
+
|
8
|
+
class ExtractorLoader:
|
9
|
+
def __init__(self, extractors_dir: str):
|
10
|
+
self.local_extractors_dir = Path(extractors_dir)
|
11
|
+
self.global_extractors_dir = Path(__file__).parent.parent / extractors_dir
|
12
|
+
if not self.local_extractors_dir.exists() and not self.global_extractors_dir.exists():
|
13
|
+
konsol.log(f"[red][!] Extractor dizini bulunamadı: {self.global_extractors_dir}[/red]")
|
14
|
+
cikis_yap(False)
|
15
|
+
|
16
|
+
def load_all(self) -> list[ExtractorBase]:
|
17
|
+
extractors = []
|
18
|
+
|
19
|
+
# Global Extractor'ları yükle
|
20
|
+
if self.global_extractors_dir.exists():
|
21
|
+
konsol.log(f"[green][*] Global Extractor dizininden yükleniyor: {self.global_extractors_dir}[/green]")
|
22
|
+
global_extractors = self._load_from_directory(self.global_extractors_dir)
|
23
|
+
konsol.log(f"[green]Global Extractor'lar: {[e.__name__ for e in global_extractors]}[/green]")
|
24
|
+
extractors.extend(global_extractors)
|
25
|
+
|
26
|
+
# Yerel Extractor'ları yükle
|
27
|
+
if self.local_extractors_dir.exists():
|
28
|
+
konsol.log(f"[green][*] Yerel Extractor dizininden yükleniyor: {self.local_extractors_dir}[/green]")
|
29
|
+
local_extractors = self._load_from_directory(self.local_extractors_dir)
|
30
|
+
konsol.log(f"[green]Yerel Extractor'lar: {[e.__name__ for e in local_extractors]}[/green]")
|
31
|
+
extractors.extend(local_extractors)
|
32
|
+
|
33
|
+
# Benzersizliği sağlama (modül adı + sınıf adı bazında)
|
34
|
+
unique_extractors = []
|
35
|
+
seen = set()
|
36
|
+
for ext in extractors:
|
37
|
+
identifier = f"{ext.__module__}.{ext.__name__}"
|
38
|
+
if identifier not in seen:
|
39
|
+
unique_extractors.append(ext)
|
40
|
+
seen.add(identifier)
|
41
|
+
|
42
|
+
konsol.log(f"[blue]Sonuç Extractor'lar: {[e.__name__ for e in unique_extractors]}[/blue]")
|
43
|
+
|
44
|
+
if not unique_extractors:
|
45
|
+
konsol.log("[yellow][!] Yüklenecek bir Extractor bulunamadı![/yellow]")
|
46
|
+
|
47
|
+
return unique_extractors
|
48
|
+
|
49
|
+
def _load_from_directory(self, directory: Path) -> list[ExtractorBase]:
|
50
|
+
extractors = []
|
51
|
+
for file in os.listdir(directory):
|
52
|
+
if file.endswith(".py") and not file.startswith("__"):
|
53
|
+
module_name = file[:-3]
|
54
|
+
konsol.log(f"[cyan]Modül yükleniyor: {module_name}[/cyan]")
|
55
|
+
if extractor := self._load_extractor(directory, module_name):
|
56
|
+
konsol.log(f"[magenta]Extractor bulundu: {extractor.__name__}[/magenta]")
|
57
|
+
extractors.append(extractor)
|
58
|
+
|
59
|
+
konsol.log(f"[yellow]{directory} dizininden yüklenen Extractor'lar: {[e.__name__ for e in extractors]}[/yellow]")
|
60
|
+
return extractors
|
61
|
+
|
62
|
+
def _load_extractor(self, directory: Path, module_name: str):
|
63
|
+
try:
|
64
|
+
path = directory / f"{module_name}.py"
|
65
|
+
spec = importlib.util.spec_from_file_location(module_name, path)
|
66
|
+
if not spec or not spec.loader:
|
67
|
+
return None
|
68
|
+
|
69
|
+
module = importlib.util.module_from_spec(spec)
|
70
|
+
spec.loader.exec_module(module)
|
71
|
+
|
72
|
+
# Yalnızca doğru modülden gelen ExtractorBase sınıflarını yükle
|
73
|
+
for attr in dir(module):
|
74
|
+
obj = getattr(module, attr)
|
75
|
+
if obj.__module__ == module_name and isinstance(obj, type) and issubclass(obj, ExtractorBase) and obj is not ExtractorBase:
|
76
|
+
konsol.log(f"[green]Yüklenen sınıf: {module_name}.{obj.__name__} ({obj.__module__}.{obj.__name__})[/green]")
|
77
|
+
return obj
|
78
|
+
|
79
|
+
except Exception as hata:
|
80
|
+
konsol.log(f"[red][!] Extractor yüklenirken hata oluştu: {module_name}\nHata: {hata}")
|
81
|
+
|
82
|
+
return None
|
@@ -3,11 +3,13 @@
|
|
3
3
|
from pydantic import BaseModel
|
4
4
|
from typing import List, Optional
|
5
5
|
|
6
|
+
|
6
7
|
class Subtitle(BaseModel):
|
7
8
|
"""Altyazı modeli."""
|
8
9
|
name : str
|
9
10
|
url : str
|
10
11
|
|
12
|
+
|
11
13
|
class ExtractResult(BaseModel):
|
12
14
|
"""Extractor'ın döndürmesi gereken sonuç modeli."""
|
13
15
|
name : str
|
@@ -0,0 +1,108 @@
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
2
|
+
|
3
|
+
from ..CLI import konsol
|
4
|
+
from .ExtractorModels import ExtractResult
|
5
|
+
import subprocess, os
|
6
|
+
|
7
|
+
class MediaHandler:
|
8
|
+
def __init__(self, title: str = "KekikStream", headers: dict = None):
|
9
|
+
if headers is None:
|
10
|
+
headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5)"}
|
11
|
+
|
12
|
+
self.headers = headers
|
13
|
+
self.title = title
|
14
|
+
|
15
|
+
def play_media(self, extract_data: ExtractResult):
|
16
|
+
if subprocess.check_output(['uname', '-o']).strip() == b'Android':
|
17
|
+
return self.play_with_android_mxplayer(extract_data)
|
18
|
+
|
19
|
+
if "Cookie" in self.headers or extract_data.subtitles:
|
20
|
+
return self.play_with_mpv(extract_data)
|
21
|
+
|
22
|
+
return self.play_with_vlc(extract_data)
|
23
|
+
|
24
|
+
def play_with_vlc(self, extract_data: ExtractResult):
|
25
|
+
try:
|
26
|
+
vlc_command = ["vlc", "--quiet"]
|
27
|
+
|
28
|
+
if self.title:
|
29
|
+
vlc_command.extend([
|
30
|
+
f"--meta-title={self.title}",
|
31
|
+
f"--input-title-format={self.title}"
|
32
|
+
])
|
33
|
+
|
34
|
+
if "User-Agent" in self.headers:
|
35
|
+
vlc_command.append(f"--http-user-agent={self.headers.get('User-Agent')}")
|
36
|
+
|
37
|
+
if "Referer" in self.headers:
|
38
|
+
vlc_command.append(f"--http-referrer={self.headers.get('Referer')}")
|
39
|
+
|
40
|
+
vlc_command.extend(
|
41
|
+
f"--sub-file={subtitle.url}" for subtitle in extract_data.subtitles
|
42
|
+
)
|
43
|
+
vlc_command.append(extract_data.url)
|
44
|
+
|
45
|
+
with open(os.devnull, "w") as devnull:
|
46
|
+
subprocess.run(vlc_command, stdout=devnull, stderr=devnull, check=True)
|
47
|
+
|
48
|
+
except subprocess.CalledProcessError as hata:
|
49
|
+
konsol.print(f"[red]VLC oynatma hatası: {hata}[/red]")
|
50
|
+
konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
51
|
+
except FileNotFoundError:
|
52
|
+
konsol.print("[red]VLC bulunamadı! VLC kurulu olduğundan emin olun.[/red]")
|
53
|
+
konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
54
|
+
|
55
|
+
def play_with_mpv(self, extract_data: ExtractResult):
|
56
|
+
try:
|
57
|
+
mpv_command = ["mpv", "--really-quiet"]
|
58
|
+
|
59
|
+
if self.title:
|
60
|
+
mpv_command.append(f"--force-media-title={self.title}")
|
61
|
+
|
62
|
+
for key, value in self.headers.items():
|
63
|
+
mpv_command.append(f"--http-header-fields={key}: {value}")
|
64
|
+
|
65
|
+
mpv_command.extend(
|
66
|
+
f"--sub-file={subtitle.url}" for subtitle in extract_data.subtitles
|
67
|
+
)
|
68
|
+
mpv_command.append(extract_data.url)
|
69
|
+
|
70
|
+
with open(os.devnull, "w") as devnull:
|
71
|
+
subprocess.run(mpv_command, stdout=devnull, stderr=devnull, check=True)
|
72
|
+
|
73
|
+
except subprocess.CalledProcessError as hata:
|
74
|
+
konsol.print(f"[red]mpv oynatma hatası: {hata}[/red]")
|
75
|
+
konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
76
|
+
except FileNotFoundError:
|
77
|
+
konsol.print("[red]mpv bulunamadı! mpv kurulu olduğundan emin olun.[/red]")
|
78
|
+
konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
79
|
+
|
80
|
+
def play_with_android_mxplayer(self, extract_data: ExtractResult):
|
81
|
+
paketler = [
|
82
|
+
"com.mxtech.videoplayer.ad/.ActivityScreen", # Free sürüm
|
83
|
+
"com.mxtech.videoplayer.pro/.ActivityScreen" # Pro sürüm
|
84
|
+
]
|
85
|
+
|
86
|
+
for paket in paketler:
|
87
|
+
try:
|
88
|
+
android_command = [
|
89
|
+
"am", "start",
|
90
|
+
"-a", "android.intent.action.VIEW",
|
91
|
+
"-d", extract_data.url,
|
92
|
+
"-n", paket
|
93
|
+
]
|
94
|
+
|
95
|
+
if self.title:
|
96
|
+
android_command.extend(["--es", "title", self.title])
|
97
|
+
|
98
|
+
with open(os.devnull, "w") as devnull:
|
99
|
+
subprocess.run(android_command, stdout=devnull, stderr=devnull, check=True)
|
100
|
+
|
101
|
+
return
|
102
|
+
|
103
|
+
except subprocess.CalledProcessError as hata:
|
104
|
+
konsol.print(f"[red]{paket} oynatma hatası: {hata}[/red]")
|
105
|
+
konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
106
|
+
except FileNotFoundError:
|
107
|
+
konsol.print(f"Paket: {paket}, Hata: MX Player kurulu değil")
|
108
|
+
konsol.print({"title": self.title, "url": extract_data.url, "headers": self.headers})
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
from abc import ABC, abstractmethod
|
4
4
|
from httpx import AsyncClient, Timeout
|
5
|
+
from cloudscraper import CloudScraper
|
5
6
|
from .PluginModels import SearchResult, MovieInfo
|
6
7
|
from .MediaHandler import MediaHandler
|
7
8
|
from urllib.parse import urljoin
|
@@ -21,6 +22,9 @@ class PluginBase(ABC):
|
|
21
22
|
timeout = Timeout(10.0),
|
22
23
|
)
|
23
24
|
self.media_handler = MediaHandler()
|
25
|
+
self.cloudscraper = CloudScraper()
|
26
|
+
self.oturum.headers.update(self.cloudscraper.headers)
|
27
|
+
self.oturum.cookies.update(self.cloudscraper.cookies)
|
24
28
|
|
25
29
|
@abstractmethod
|
26
30
|
async def search(self, query: str) -> list[SearchResult]:
|
@@ -43,14 +47,11 @@ class PluginBase(ABC):
|
|
43
47
|
def fix_url(self, url: str) -> str:
|
44
48
|
if not url:
|
45
49
|
return ""
|
46
|
-
|
50
|
+
|
47
51
|
if url.startswith("http") or url.startswith("{\""):
|
48
52
|
return url
|
49
53
|
|
50
|
-
if url.startswith("//")
|
51
|
-
return f"https:{url}"
|
52
|
-
|
53
|
-
return urljoin(self.main_url, url)
|
54
|
+
return f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
|
54
55
|
|
55
56
|
@staticmethod
|
56
57
|
def clean_title(title: str) -> str:
|
@@ -3,9 +3,7 @@
|
|
3
3
|
from ..CLI import konsol, cikis_yap
|
4
4
|
from .PluginBase import PluginBase
|
5
5
|
from pathlib import Path
|
6
|
-
import importlib.util
|
7
|
-
import os
|
8
|
-
import traceback
|
6
|
+
import os, importlib.util, traceback
|
9
7
|
|
10
8
|
class PluginLoader:
|
11
9
|
def __init__(self, plugins_dir: str):
|
@@ -20,11 +18,11 @@ class PluginLoader:
|
|
20
18
|
|
21
19
|
if self.global_plugins_dir.exists():
|
22
20
|
konsol.log(f"[green][*] Global Plugin dizininden yükleniyor: {self.global_plugins_dir}[/green]")
|
23
|
-
plugins
|
21
|
+
plugins |= self._load_from_directory(self.global_plugins_dir)
|
24
22
|
|
25
23
|
if self.local_plugins_dir.exists():
|
26
24
|
konsol.log(f"[green][*] Yerel Plugin dizininden yükleniyor: {self.local_plugins_dir}[/green]")
|
27
|
-
plugins
|
25
|
+
plugins |= self._load_from_directory(self.local_plugins_dir)
|
28
26
|
|
29
27
|
if not plugins:
|
30
28
|
konsol.print("[yellow][!] Yüklenecek bir Plugin bulunamadı![/yellow]")
|
@@ -23,17 +23,12 @@ class MovieInfo(BaseModel):
|
|
23
23
|
actors : Optional[str] = None
|
24
24
|
duration : Optional[int] = None
|
25
25
|
|
26
|
-
@field_validator("tags", mode="before")
|
26
|
+
@field_validator("tags", "actors", mode="before")
|
27
27
|
@classmethod
|
28
|
-
def
|
28
|
+
def convert_lists(cls, value):
|
29
29
|
return ", ".join(value) if isinstance(value, list) else value
|
30
30
|
|
31
|
-
@field_validator("
|
32
|
-
@classmethod
|
33
|
-
def convert_actors(cls, value):
|
34
|
-
return ", ".join(value) if isinstance(value, list) else value
|
35
|
-
|
36
|
-
@field_validator("rating", mode="before")
|
31
|
+
@field_validator("rating", "year", mode="before")
|
37
32
|
@classmethod
|
38
33
|
def ensure_string(cls, value):
|
39
34
|
return str(value) if value is not None else value
|
@@ -57,17 +52,12 @@ class SeriesInfo(BaseModel):
|
|
57
52
|
actors : Optional[str] = None
|
58
53
|
episodes : Optional[List[Episode]] = None
|
59
54
|
|
60
|
-
@field_validator("tags", mode="before")
|
61
|
-
@classmethod
|
62
|
-
def convert_tags(cls, value):
|
63
|
-
return ", ".join(value) if isinstance(value, list) else value
|
64
|
-
|
65
|
-
@field_validator("actors", mode="before")
|
55
|
+
@field_validator("tags", "actors", mode="before")
|
66
56
|
@classmethod
|
67
|
-
def
|
57
|
+
def convert_lists(cls, value):
|
68
58
|
return ", ".join(value) if isinstance(value, list) else value
|
69
59
|
|
70
|
-
@field_validator("rating", mode="before")
|
60
|
+
@field_validator("rating", "year", mode="before")
|
71
61
|
@classmethod
|
72
62
|
def ensure_string(cls, value):
|
73
63
|
return str(value) if value is not None else value
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
2
|
+
|
3
|
+
from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
|
4
|
+
import re
|
5
|
+
|
6
|
+
class ContentX(ExtractorBase):
|
7
|
+
name = "ContentX"
|
8
|
+
main_url = "https://contentx.me"
|
9
|
+
|
10
|
+
async def extract(self, url, referer=None) -> list[ExtractResult]:
|
11
|
+
if referer:
|
12
|
+
self.oturum.headers.update({"Referer": referer})
|
13
|
+
|
14
|
+
istek = await self.oturum.get(url)
|
15
|
+
istek.raise_for_status()
|
16
|
+
i_source = istek.text
|
17
|
+
|
18
|
+
i_extract = re.search(r"window\.openPlayer\('([^']+)'", i_source)
|
19
|
+
if not i_extract:
|
20
|
+
raise ValueError("i_extract is null")
|
21
|
+
i_extract_value = i_extract[1]
|
22
|
+
|
23
|
+
subtitles = []
|
24
|
+
sub_urls = set()
|
25
|
+
for match in re.finditer(r'"file":"([^"]+)","label":"([^"]+)"', i_source):
|
26
|
+
sub_url, sub_lang = match.groups()
|
27
|
+
|
28
|
+
if sub_url in sub_urls:
|
29
|
+
continue
|
30
|
+
|
31
|
+
sub_urls.add(sub_url)
|
32
|
+
subtitles.append(
|
33
|
+
Subtitle(
|
34
|
+
name = sub_lang.replace("\\u0131", "ı")
|
35
|
+
.replace("\\u0130", "İ")
|
36
|
+
.replace("\\u00fc", "ü")
|
37
|
+
.replace("\\u00e7", "ç"),
|
38
|
+
url = self.fix_url(sub_url.replace("\\", ""))
|
39
|
+
)
|
40
|
+
)
|
41
|
+
|
42
|
+
vid_source_request = await self.oturum.get(f"{self.main_url}/source2.php?v={i_extract_value}", headers={"Referer": referer or self.main_url})
|
43
|
+
vid_source_request.raise_for_status()
|
44
|
+
|
45
|
+
vid_source = vid_source_request.text
|
46
|
+
vid_extract = re.search(r'file":"([^"]+)"', vid_source)
|
47
|
+
if not vid_extract:
|
48
|
+
raise ValueError("vidExtract is null")
|
49
|
+
|
50
|
+
m3u_link = vid_extract[1].replace("\\", "")
|
51
|
+
results = [
|
52
|
+
ExtractResult(
|
53
|
+
name = self.name,
|
54
|
+
url = m3u_link,
|
55
|
+
referer = url,
|
56
|
+
subtitles = subtitles
|
57
|
+
)
|
58
|
+
]
|
59
|
+
|
60
|
+
if i_dublaj := re.search(r',\"([^"]+)\",\"Türkçe"', i_source):
|
61
|
+
dublaj_value = i_dublaj[1]
|
62
|
+
dublaj_source_request = await self.oturum.get(f"{self.main_url}/source2.php?v={dublaj_value}", headers={"Referer": referer or self.main_url})
|
63
|
+
dublaj_source_request.raise_for_status()
|
64
|
+
|
65
|
+
dublaj_source = dublaj_source_request.text
|
66
|
+
dublaj_extract = re.search(r'file":"([^"]+)"', dublaj_source)
|
67
|
+
if not dublaj_extract:
|
68
|
+
raise ValueError("dublajExtract is null")
|
69
|
+
|
70
|
+
dublaj_link = dublaj_extract[1].replace("\\", "")
|
71
|
+
results.append(
|
72
|
+
ExtractResult(
|
73
|
+
name = f"{self.name} Türkçe Dublaj",
|
74
|
+
url = dublaj_link,
|
75
|
+
referer = url,
|
76
|
+
subtitles = []
|
77
|
+
)
|
78
|
+
)
|
79
|
+
|
80
|
+
return results[0] if len(results) == 1 else results
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
2
|
+
|
3
|
+
from KekikStream.Core import ExtractorBase, ExtractResult
|
4
|
+
from Kekik.Sifreleme import AESManager
|
5
|
+
import re, json
|
6
|
+
|
7
|
+
class MixPlayHD(ExtractorBase):
|
8
|
+
name = "MixPlayHD"
|
9
|
+
main_url = "https://mixplayhd.com"
|
10
|
+
|
11
|
+
async def extract(self, url, referer=None) -> ExtractResult:
|
12
|
+
if referer:
|
13
|
+
self.oturum.headers.update({"Referer": referer})
|
14
|
+
|
15
|
+
istek = await self.oturum.get(url)
|
16
|
+
istek.raise_for_status()
|
17
|
+
|
18
|
+
be_player_match = re.search(r"bePlayer\('([^']+)',\s*'(\{[^\}]+\})'\);", istek.text)
|
19
|
+
if not be_player_match:
|
20
|
+
raise ValueError("bePlayer not found in the response.")
|
21
|
+
|
22
|
+
be_player_pass = be_player_match[1]
|
23
|
+
be_player_data = be_player_match[2]
|
24
|
+
|
25
|
+
try:
|
26
|
+
decrypted_data = AESManager.decrypt(be_player_data, be_player_pass).replace("\\", "")
|
27
|
+
decrypted_json = json.loads(decrypted_data)
|
28
|
+
except Exception as hata:
|
29
|
+
raise RuntimeError(f"Decryption failed: {hata}") from hata
|
30
|
+
|
31
|
+
if video_url_match := re.search(
|
32
|
+
pattern = r'"video_location":"([^"]+)"',
|
33
|
+
string = decrypted_json.get("schedule", {}).get("client", ""),
|
34
|
+
):
|
35
|
+
return ExtractResult(
|
36
|
+
name = self.name,
|
37
|
+
url = video_url_match[1],
|
38
|
+
referer = self.main_url,
|
39
|
+
subtitles = []
|
40
|
+
)
|
41
|
+
else:
|
42
|
+
raise ValueError("M3U8 video URL not found in the decrypted data.")
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
2
|
+
|
3
|
+
from KekikStream.Core import ExtractorBase, ExtractResult
|
4
|
+
import re, json
|
5
|
+
|
6
|
+
class Odnoklassniki(ExtractorBase):
|
7
|
+
name = "Odnoklassniki"
|
8
|
+
main_url = "https://odnoklassniki.ru"
|
9
|
+
|
10
|
+
async def extract(self, url, referer=None) -> ExtractResult:
|
11
|
+
if referer:
|
12
|
+
self.oturum.headers.update({"Referer": referer})
|
13
|
+
|
14
|
+
self.oturum.headers.update({
|
15
|
+
"User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36"
|
16
|
+
})
|
17
|
+
|
18
|
+
if "/video/" in url:
|
19
|
+
url = url.replace("/video/", "/videoembed/")
|
20
|
+
|
21
|
+
try:
|
22
|
+
istek = await self.fetch_with_redirects(url)
|
23
|
+
istek.raise_for_status()
|
24
|
+
except Exception as hata:
|
25
|
+
raise RuntimeError(f"Failed to fetch the URL: {url}, Error: {hata}") from hata
|
26
|
+
|
27
|
+
response_text = (
|
28
|
+
istek.text.replace("\\"", "\"")
|
29
|
+
.replace("\\\\", "\\")
|
30
|
+
.replace(r"\\u", "\\u")
|
31
|
+
)
|
32
|
+
response_text = re.sub(
|
33
|
+
r"\\u([0-9A-Fa-f]{4})",
|
34
|
+
lambda match: chr(int(match[1], 16)),
|
35
|
+
response_text
|
36
|
+
)
|
37
|
+
|
38
|
+
videos_match = re.search(r'"videos":(\[.*?\])', response_text)
|
39
|
+
if not videos_match:
|
40
|
+
raise ValueError("No video data found in the response.")
|
41
|
+
|
42
|
+
try:
|
43
|
+
videos = json.loads(videos_match[1])
|
44
|
+
except json.JSONDecodeError as hata:
|
45
|
+
raise ValueError("Failed to parse video data.") from hata
|
46
|
+
|
47
|
+
quality_order = {
|
48
|
+
"ULTRA": 6, # 4K veya daha yüksek
|
49
|
+
"QUAD": 5, # 1440p
|
50
|
+
"FULL": 4, # 1080p
|
51
|
+
"HD": 3, # 720p
|
52
|
+
"SD": 2, # 480p
|
53
|
+
"LOW": 1, # 360p
|
54
|
+
"MOBILE": 0 # 144p
|
55
|
+
}
|
56
|
+
|
57
|
+
# Kaliteye göre en iyi videoyu seçme
|
58
|
+
best_video = None
|
59
|
+
best_quality_score = -1
|
60
|
+
|
61
|
+
for video in videos:
|
62
|
+
video_url = video.get("url")
|
63
|
+
quality_name = video.get("name", "").upper()
|
64
|
+
|
65
|
+
if not video_url or not quality_name:
|
66
|
+
continue
|
67
|
+
|
68
|
+
# Kalite sıralamasına göre puanla
|
69
|
+
quality_score = quality_order.get(quality_name, -1)
|
70
|
+
if quality_score > best_quality_score:
|
71
|
+
best_quality_score = quality_score
|
72
|
+
best_video = video_url
|
73
|
+
|
74
|
+
if not best_video:
|
75
|
+
raise ValueError("No valid video URLs found.")
|
76
|
+
|
77
|
+
if best_video.startswith("//"):
|
78
|
+
best_video = f"https:{best_video}"
|
79
|
+
|
80
|
+
return ExtractResult(
|
81
|
+
name = self.name,
|
82
|
+
url = best_video,
|
83
|
+
referer = self.main_url,
|
84
|
+
subtitles = []
|
85
|
+
)
|
86
|
+
|
87
|
+
async def fetch_with_redirects(self, url, max_redirects=5):
|
88
|
+
"""Yönlendirmeleri takip eden bir fonksiyon"""
|
89
|
+
redirects = 0
|
90
|
+
while redirects < max_redirects:
|
91
|
+
istek = await self.oturum.get(url, follow_redirects=False)
|
92
|
+
|
93
|
+
if istek.status_code not in [301, 302]:
|
94
|
+
break # Yönlendirme yoksa çık
|
95
|
+
|
96
|
+
redirected_url = istek.headers.get("Location")
|
97
|
+
if not redirected_url:
|
98
|
+
raise ValueError("Redirect location not found.")
|
99
|
+
|
100
|
+
url = redirected_url if redirected_url.startswith("http") else f"https://{redirected_url}"
|
101
|
+
redirects += 1
|
102
|
+
|
103
|
+
if redirects == max_redirects:
|
104
|
+
raise RuntimeError(f"Max redirects ({max_redirects}) reached.")
|
105
|
+
|
106
|
+
return istek
|