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.
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/CLI/pypi_kontrol.py +6 -6
- kekikstream-2.4.6/KekikStream/Core/Extractor/ExtractorBase.py +55 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/Extractor/ExtractorLoader.py +34 -25
- kekikstream-2.4.6/KekikStream/Core/Extractor/ExtractorManager.py +75 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/Extractor/ExtractorModels.py +5 -7
- kekikstream-2.4.6/KekikStream/Core/Extractor/YTDLPCache.py +35 -0
- kekikstream-2.4.6/KekikStream/Core/HTMLHelper.py +205 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/Media/MediaHandler.py +56 -35
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/Media/MediaManager.py +0 -3
- kekikstream-2.4.6/KekikStream/Core/Plugin/PluginBase.py +189 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/Plugin/PluginLoader.py +20 -15
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/Plugin/PluginManager.py +2 -2
- kekikstream-2.4.6/KekikStream/Core/Plugin/PluginModels.py +76 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/__init__.py +6 -1
- kekikstream-2.4.6/KekikStream/Extractors/CloseLoad.py +52 -0
- kekikstream-2.4.6/KekikStream/Extractors/ContentX.py +54 -0
- kekikstream-2.4.6/KekikStream/Extractors/DonilasPlay.py +42 -0
- kekikstream-2.4.6/KekikStream/Extractors/DzenRu.py +24 -0
- kekikstream-2.4.6/KekikStream/Extractors/ExPlay.py +35 -0
- kekikstream-2.4.6/KekikStream/Extractors/Filemoon.py +63 -0
- kekikstream-2.4.6/KekikStream/Extractors/HDMomPlayer.py +30 -0
- kekikstream-2.4.6/KekikStream/Extractors/HDPlayerSystem.py +23 -0
- kekikstream-2.4.6/KekikStream/Extractors/HotStream.py +27 -0
- kekikstream-2.4.6/KekikStream/Extractors/JFVid.py +19 -0
- kekikstream-2.4.6/KekikStream/Extractors/JetTv.py +32 -0
- kekikstream-2.4.6/KekikStream/Extractors/MailRu.py +20 -0
- kekikstream-2.4.6/KekikStream/Extractors/MixPlayHD.py +28 -0
- kekikstream-2.4.6/KekikStream/Extractors/MixTiger.py +34 -0
- kekikstream-2.4.6/KekikStream/Extractors/MolyStream.py +38 -0
- kekikstream-2.4.6/KekikStream/Extractors/Odnoklassniki.py +41 -0
- kekikstream-2.4.6/KekikStream/Extractors/PeaceMakerst.py +36 -0
- kekikstream-2.4.6/KekikStream/Extractors/PixelDrain.py +20 -0
- kekikstream-2.4.6/KekikStream/Extractors/PlayerFilmIzle.py +46 -0
- kekikstream-2.4.6/KekikStream/Extractors/RapidVid.py +80 -0
- kekikstream-2.4.6/KekikStream/Extractors/SetPlay.py +41 -0
- kekikstream-2.4.6/KekikStream/Extractors/SetPrime.py +42 -0
- kekikstream-2.4.6/KekikStream/Extractors/SibNet.py +17 -0
- kekikstream-2.4.6/KekikStream/Extractors/Sobreatsesuyp.py +37 -0
- kekikstream-2.4.6/KekikStream/Extractors/TRsTX.py +37 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Extractors/TauVideo.py +2 -2
- kekikstream-2.4.6/KekikStream/Extractors/TurboImgz.py +17 -0
- kekikstream-2.4.6/KekikStream/Extractors/TurkeyPlayer.py +34 -0
- kekikstream-2.4.6/KekikStream/Extractors/VCTPlay.py +23 -0
- kekikstream-2.4.6/KekikStream/Extractors/VidHide.py +56 -0
- kekikstream-2.4.6/KekikStream/Extractors/VidMoly.py +98 -0
- kekikstream-2.4.6/KekikStream/Extractors/VidMoxy.py +37 -0
- kekikstream-2.4.6/KekikStream/Extractors/VidPapi.py +57 -0
- kekikstream-2.4.6/KekikStream/Extractors/VideoSeyred.py +32 -0
- kekikstream-2.4.6/KekikStream/Extractors/Videostr.py +58 -0
- kekikstream-2.4.6/KekikStream/Extractors/Vidoza.py +18 -0
- kekikstream-2.4.6/KekikStream/Extractors/YTDLP.py +211 -0
- kekikstream-2.4.6/KekikStream/Extractors/YildizKisaFilm.py +23 -0
- kekikstream-2.4.6/KekikStream/Plugins/BelgeselX.py +217 -0
- kekikstream-2.4.6/KekikStream/Plugins/DiziBox.py +216 -0
- kekikstream-2.4.6/KekikStream/Plugins/DiziMom.py +155 -0
- kekikstream-2.4.6/KekikStream/Plugins/DiziPal.py +180 -0
- kekikstream-2.4.6/KekikStream/Plugins/DiziYou.py +135 -0
- kekikstream-2.4.6/KekikStream/Plugins/Dizilla.py +218 -0
- kekikstream-2.4.6/KekikStream/Plugins/FilmBip.py +136 -0
- kekikstream-2.4.6/KekikStream/Plugins/FilmEkseni.py +120 -0
- kekikstream-2.4.6/KekikStream/Plugins/FilmMakinesi.py +140 -0
- kekikstream-2.4.6/KekikStream/Plugins/FilmModu.py +150 -0
- kekikstream-2.4.6/KekikStream/Plugins/Filmatek.py +179 -0
- kekikstream-2.4.6/KekikStream/Plugins/Full4kizle.py +174 -0
- kekikstream-2.4.6/KekikStream/Plugins/FullHDFilm.py +179 -0
- kekikstream-2.4.6/KekikStream/Plugins/FullHDFilmizlesene.py +137 -0
- kekikstream-2.4.6/KekikStream/Plugins/HDFilmCehennemi.py +265 -0
- kekikstream-2.4.6/KekikStream/Plugins/JetFilmizle.py +178 -0
- kekikstream-2.4.6/KekikStream/Plugins/KultFilmler.py +189 -0
- kekikstream-2.4.6/KekikStream/Plugins/RecTV.py +150 -0
- kekikstream-2.4.6/KekikStream/Plugins/RoketDizi.py +217 -0
- kekikstream-2.4.6/KekikStream/Plugins/SelcukFlix.py +332 -0
- kekikstream-2.4.6/KekikStream/Plugins/SetFilmIzle.py +220 -0
- kekikstream-2.4.6/KekikStream/Plugins/SezonlukDizi.py +182 -0
- kekikstream-2.4.6/KekikStream/Plugins/SineWix.py +158 -0
- kekikstream-2.4.6/KekikStream/Plugins/Sinefy.py +207 -0
- kekikstream-2.4.6/KekikStream/Plugins/SinemaCX.py +169 -0
- kekikstream-2.4.6/KekikStream/Plugins/Sinezy.py +129 -0
- kekikstream-2.4.6/KekikStream/Plugins/SuperFilmGeldi.py +142 -0
- kekikstream-2.4.6/KekikStream/Plugins/UgurFilm.py +120 -0
- kekikstream-2.4.6/KekikStream/Plugins/Watch32.py +175 -0
- kekikstream-2.4.6/KekikStream/Plugins/YabanciDizi.py +231 -0
- kekikstream-2.4.6/KekikStream/__init__.py +338 -0
- kekikstream-1.0.5/KekikStream.egg-info/requires.txt → kekikstream-2.4.6/KekikStream/requirements.txt +2 -2
- kekikstream-2.4.6/KekikStream.egg-info/PKG-INFO +319 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream.egg-info/SOURCES.txt +44 -12
- kekikstream-2.4.6/KekikStream.egg-info/requires.txt +9 -0
- kekikstream-2.4.6/PKG-INFO +319 -0
- kekikstream-2.4.6/README.md +282 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/setup.py +6 -6
- kekikstream-1.0.5/KekikStream/Core/Extractor/ExtractorBase.py +0 -48
- kekikstream-1.0.5/KekikStream/Core/Extractor/ExtractorManager.py +0 -31
- kekikstream-1.0.5/KekikStream/Core/Plugin/PluginBase.py +0 -80
- kekikstream-1.0.5/KekikStream/Core/Plugin/PluginModels.py +0 -63
- kekikstream-1.0.5/KekikStream/Extractors/CloseLoad.py +0 -31
- kekikstream-1.0.5/KekikStream/Extractors/ContentX.py +0 -80
- kekikstream-1.0.5/KekikStream/Extractors/FourCX.py +0 -7
- kekikstream-1.0.5/KekikStream/Extractors/FourPichive.py +0 -7
- kekikstream-1.0.5/KekikStream/Extractors/FourPlayRu.py +0 -7
- kekikstream-1.0.5/KekikStream/Extractors/HDStreamAble.py +0 -7
- kekikstream-1.0.5/KekikStream/Extractors/Hotlinger.py +0 -7
- kekikstream-1.0.5/KekikStream/Extractors/MailRu.py +0 -40
- kekikstream-1.0.5/KekikStream/Extractors/MixPlayHD.py +0 -42
- kekikstream-1.0.5/KekikStream/Extractors/MolyStream.py +0 -16
- kekikstream-1.0.5/KekikStream/Extractors/Odnoklassniki.py +0 -106
- kekikstream-1.0.5/KekikStream/Extractors/OkRuHTTP.py +0 -7
- kekikstream-1.0.5/KekikStream/Extractors/OkRuSSL.py +0 -7
- kekikstream-1.0.5/KekikStream/Extractors/PeaceMakerst.py +0 -57
- kekikstream-1.0.5/KekikStream/Extractors/Pichive.py +0 -7
- kekikstream-1.0.5/KekikStream/Extractors/PixelDrain.py +0 -28
- kekikstream-1.0.5/KekikStream/Extractors/PlayRu.py +0 -7
- kekikstream-1.0.5/KekikStream/Extractors/RapidVid.py +0 -60
- kekikstream-1.0.5/KekikStream/Extractors/SibNet.py +0 -29
- kekikstream-1.0.5/KekikStream/Extractors/Sobreatsesuyp.py +0 -59
- kekikstream-1.0.5/KekikStream/Extractors/TRsTX.py +0 -67
- kekikstream-1.0.5/KekikStream/Extractors/TurboImgz.py +0 -25
- kekikstream-1.0.5/KekikStream/Extractors/VidMoly.py +0 -89
- kekikstream-1.0.5/KekikStream/Extractors/VidMolyMe.py +0 -7
- kekikstream-1.0.5/KekikStream/Extractors/VidMoxy.py +0 -50
- kekikstream-1.0.5/KekikStream/Extractors/VideoSeyred.py +0 -47
- kekikstream-1.0.5/KekikStream/Plugins/DiziBox.py +0 -147
- kekikstream-1.0.5/KekikStream/Plugins/DiziYou.py +0 -127
- kekikstream-1.0.5/KekikStream/Plugins/Dizilla.py +0 -106
- kekikstream-1.0.5/KekikStream/Plugins/FilmMakinesi.py +0 -65
- kekikstream-1.0.5/KekikStream/Plugins/FullHDFilmizlesene.py +0 -78
- kekikstream-1.0.5/KekikStream/Plugins/JetFilmizle.py +0 -92
- kekikstream-1.0.5/KekikStream/Plugins/RecTV.py +0 -118
- kekikstream-1.0.5/KekikStream/Plugins/SezonlukDizi.py +0 -108
- kekikstream-1.0.5/KekikStream/Plugins/Shorten.py +0 -223
- kekikstream-1.0.5/KekikStream/Plugins/SineWix.py +0 -111
- kekikstream-1.0.5/KekikStream/Plugins/UgurFilm.py +0 -75
- kekikstream-1.0.5/KekikStream/__init__.py +0 -355
- kekikstream-1.0.5/KekikStream/requirements.txt +0 -10
- kekikstream-1.0.5/KekikStream.egg-info/PKG-INFO +0 -102
- kekikstream-1.0.5/PKG-INFO +0 -102
- kekikstream-1.0.5/README.md +0 -66
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/CLI/__init__.py +0 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/Core/UI/UIManager.py +0 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream/__main__.py +0 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream.egg-info/dependency_links.txt +0 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream.egg-info/entry_points.txt +0 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/KekikStream.egg-info/top_level.txt +0 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/LICENSE +0 -0
- {kekikstream-1.0.5 → kekikstream-2.4.6}/MANIFEST.in +0 -0
- {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 .
|
|
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,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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
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,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
|