KekikStream 1.9.9__py3-none-any.whl → 2.0.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- KekikStream/Core/Extractor/ExtractorBase.py +5 -5
- KekikStream/Core/Extractor/ExtractorLoader.py +25 -17
- KekikStream/Core/Extractor/ExtractorManager.py +1 -1
- KekikStream/Core/Extractor/YTDLPCache.py +35 -0
- KekikStream/Core/Plugin/PluginBase.py +4 -1
- KekikStream/Core/Plugin/PluginLoader.py +11 -7
- KekikStream/Core/__init__.py +1 -0
- KekikStream/Extractors/CloseLoad.py +1 -1
- KekikStream/Extractors/ContentX.py +13 -0
- KekikStream/Extractors/DonilasPlay.py +86 -0
- KekikStream/Extractors/Odnoklassniki.py +6 -0
- KekikStream/Extractors/PeaceMakerst.py +6 -0
- KekikStream/Extractors/RapidVid.py +6 -0
- KekikStream/Extractors/SetPlay.py +7 -1
- KekikStream/Extractors/VCTPlay.py +41 -0
- KekikStream/Extractors/VidMoly.py +52 -30
- KekikStream/Extractors/YTDLP.py +97 -58
- KekikStream/Plugins/BelgeselX.py +204 -0
- KekikStream/Plugins/Dizilla.py +22 -14
- KekikStream/Plugins/FilmMakinesi.py +3 -1
- KekikStream/Plugins/FilmModu.py +6 -2
- KekikStream/Plugins/FullHDFilmizlesene.py +1 -1
- KekikStream/Plugins/HDFilmCehennemi.py +83 -8
- KekikStream/Plugins/JetFilmizle.py +53 -38
- KekikStream/Plugins/KultFilmler.py +1 -1
- KekikStream/Plugins/RoketDizi.py +17 -24
- KekikStream/Plugins/SelcukFlix.py +51 -52
- KekikStream/Plugins/SetFilmIzle.py +259 -0
- KekikStream/Plugins/SezonlukDizi.py +28 -7
- KekikStream/Plugins/Sinefy.py +15 -9
- KekikStream/Plugins/SinemaCX.py +3 -7
- KekikStream/Plugins/Sinezy.py +16 -1
- KekikStream/Plugins/SuperFilmGeldi.py +1 -1
- KekikStream/Plugins/UgurFilm.py +1 -1
- {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/METADATA +27 -1
- kekikstream-2.0.8.dist-info/RECORD +81 -0
- KekikStream/Extractors/FourCX.py +0 -7
- KekikStream/Extractors/FourPichive.py +0 -7
- KekikStream/Extractors/FourPlayRu.py +0 -7
- KekikStream/Extractors/HDStreamAble.py +0 -7
- KekikStream/Extractors/Hotlinger.py +0 -7
- KekikStream/Extractors/OkRuHTTP.py +0 -7
- KekikStream/Extractors/OkRuSSL.py +0 -7
- KekikStream/Extractors/Pichive.py +0 -7
- KekikStream/Extractors/PlayRu.py +0 -7
- KekikStream/Extractors/VidMolyMe.py +0 -7
- kekikstream-1.9.9.dist-info/RECORD +0 -86
- {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/WHEEL +0 -0
- {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/entry_points.txt +0 -0
- {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/licenses/LICENSE +0 -0
- {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/top_level.txt +0 -0
|
@@ -17,13 +17,13 @@ class ExtractorBase(ABC):
|
|
|
17
17
|
self.cloudscraper = CloudScraper()
|
|
18
18
|
|
|
19
19
|
# httpx - lightweight and safe for most HTTP requests
|
|
20
|
-
self.httpx = AsyncClient(
|
|
21
|
-
timeout = 3,
|
|
22
|
-
follow_redirects = True
|
|
23
|
-
)
|
|
20
|
+
self.httpx = AsyncClient(timeout = 10)
|
|
24
21
|
self.httpx.headers.update(self.cloudscraper.headers)
|
|
25
22
|
self.httpx.cookies.update(self.cloudscraper.cookies)
|
|
26
|
-
self.httpx.headers.update({
|
|
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
27
|
|
|
28
28
|
def can_handle_url(self, url: str) -> bool:
|
|
29
29
|
# URL'nin bu çıkarıcı tarafından işlenip işlenemeyeceğini kontrol et
|
|
@@ -19,19 +19,22 @@ class ExtractorLoader:
|
|
|
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
24
|
# konsol.log(f"[green][*] Yerel Extractor dizininden yükleniyor: {self.local_extractors_dir}[/green]")
|
|
32
25
|
local_extractors = self._load_from_directory(self.local_extractors_dir)
|
|
33
26
|
# konsol.log(f"[green]Yerel Extractor'lar: {[e.__name__ for e in local_extractors]}[/green]")
|
|
34
|
-
|
|
27
|
+
|
|
28
|
+
if local_extractors:
|
|
29
|
+
# konsol.log("[cyan][*] Yerel Extractor bulundu, global Extractor'lar atlanıyor (eklenti geliştirme modu)[/cyan]")
|
|
30
|
+
extractors.extend(local_extractors)
|
|
31
|
+
|
|
32
|
+
# Yerel dizinde Extractor yoksa, global'leri yükle
|
|
33
|
+
if not extractors and self.global_extractors_dir.exists():
|
|
34
|
+
# konsol.log(f"[green][*] Global Extractor dizininden yükleniyor: {self.global_extractors_dir}[/green]")
|
|
35
|
+
global_extractors = self._load_from_directory(self.global_extractors_dir)
|
|
36
|
+
# konsol.log(f"[green]Global Extractor'lar: {[e.__name__ for e in global_extractors]}[/green]")
|
|
37
|
+
extractors.extend(global_extractors)
|
|
35
38
|
|
|
36
39
|
# Benzersizliği sağlama (modül adı + sınıf adı bazında)
|
|
37
40
|
unique_extractors = []
|
|
@@ -57,9 +60,10 @@ class ExtractorLoader:
|
|
|
57
60
|
if file.endswith(".py") and not file.startswith("__"):
|
|
58
61
|
module_name = file[:-3] # .py uzantısını kaldır
|
|
59
62
|
# konsol.log(f"[cyan]Okunan Dosya\t\t: {module_name}[/cyan]")
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
module_extractors = self._load_extractor(directory, module_name)
|
|
64
|
+
if module_extractors:
|
|
65
|
+
# konsol.log(f"[magenta]Extractor Yüklendi\t: {[e.__name__ for e in module_extractors]}[/magenta]")
|
|
66
|
+
extractors.extend(module_extractors)
|
|
63
67
|
|
|
64
68
|
# konsol.log(f"[yellow]{directory} dizininden yüklenen Extractor'lar: {[e.__name__ for e in extractors]}[/yellow]")
|
|
65
69
|
return extractors
|
|
@@ -70,21 +74,25 @@ class ExtractorLoader:
|
|
|
70
74
|
path = directory / f"{module_name}.py"
|
|
71
75
|
spec = importlib.util.spec_from_file_location(module_name, path)
|
|
72
76
|
if not spec or not spec.loader:
|
|
73
|
-
return
|
|
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
|
-
|
|
87
|
+
# isinstance kontrolünü __module__ kontrolünden ÖNCE yap
|
|
88
|
+
if isinstance(obj, type) and issubclass(obj, ExtractorBase) and obj is not ExtractorBase and obj.__module__ == module_name:
|
|
83
89
|
# konsol.log(f"[green]Yüklenen sınıf\t\t: {module_name}.{obj.__name__} ({obj.__module__}.{obj.__name__})[/green]")
|
|
84
|
-
|
|
90
|
+
extractors.append(obj)
|
|
91
|
+
|
|
92
|
+
return extractors
|
|
85
93
|
|
|
86
94
|
except Exception as hata:
|
|
87
95
|
konsol.log(f"[red][!] Extractor yüklenirken hata oluştu: {module_name}\nHata: {hata}")
|
|
88
96
|
konsol.print(f"[dim]{traceback.format_exc()}[/dim]")
|
|
89
97
|
|
|
90
|
-
return
|
|
98
|
+
return []
|
|
@@ -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 []
|
|
@@ -34,7 +34,10 @@ class PluginBase(ABC):
|
|
|
34
34
|
)
|
|
35
35
|
self.httpx.headers.update(self.cloudscraper.headers)
|
|
36
36
|
self.httpx.cookies.update(self.cloudscraper.cookies)
|
|
37
|
-
self.httpx.headers.update({
|
|
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
|
+
})
|
|
38
41
|
|
|
39
42
|
self.media_handler = MediaHandler()
|
|
40
43
|
self.ex_manager = ExtractorManager()
|
|
@@ -19,15 +19,19 @@ class PluginLoader:
|
|
|
19
19
|
def load_all(self) -> dict[str, PluginBase]:
|
|
20
20
|
plugins = {}
|
|
21
21
|
|
|
22
|
-
#
|
|
23
|
-
if self.global_plugins_dir.exists():
|
|
24
|
-
# konsol.log(f"[green][*] Global Eklenti dizininden yükleniyor: {self.global_plugins_dir}[/green]")
|
|
25
|
-
plugins |= self._load_from_directory(self.global_plugins_dir)
|
|
26
|
-
|
|
27
|
-
# Yerel eklentileri yükle
|
|
22
|
+
# Eğer yerel dizinde Plugin varsa, sadece onları yükle (eklenti geliştirme modu)
|
|
28
23
|
if self.local_plugins_dir.exists():
|
|
29
24
|
# konsol.log(f"[green][*] Yerel Eklenti dizininden yükleniyor: {self.local_plugins_dir}[/green]")
|
|
30
|
-
|
|
25
|
+
local_plugins = self._load_from_directory(self.local_plugins_dir)
|
|
26
|
+
|
|
27
|
+
if local_plugins:
|
|
28
|
+
# konsol.log("[cyan][*] Yerel Plugin bulundu, global Plugin'ler atlanıyor (eklenti geliştirme modu)[/cyan]")
|
|
29
|
+
plugins |= local_plugins
|
|
30
|
+
|
|
31
|
+
# Yerel dizinde Plugin yoksa, global'leri yükle
|
|
32
|
+
if not plugins and self.global_plugins_dir.exists():
|
|
33
|
+
# konsol.log(f"[green][*] Global Eklenti dizininden yükleniyor: {self.global_plugins_dir}[/green]")
|
|
34
|
+
plugins |= self._load_from_directory(self.global_plugins_dir)
|
|
31
35
|
|
|
32
36
|
if not plugins:
|
|
33
37
|
konsol.print("[yellow][!] Yüklenecek bir Eklenti bulunamadı![/yellow]")
|
KekikStream/Core/__init__.py
CHANGED
|
@@ -13,6 +13,7 @@ from .Extractor.ExtractorManager import ExtractorManager
|
|
|
13
13
|
from .Extractor.ExtractorBase import ExtractorBase
|
|
14
14
|
from .Extractor.ExtractorLoader import ExtractorLoader
|
|
15
15
|
from .Extractor.ExtractorModels import ExtractResult, Subtitle
|
|
16
|
+
from .Extractor.YTDLPCache import get_ytdlp_extractors
|
|
16
17
|
|
|
17
18
|
from .Media.MediaManager import MediaManager
|
|
18
19
|
from .Media.MediaHandler import MediaHandler
|
|
@@ -7,6 +7,19 @@ class ContentX(ExtractorBase):
|
|
|
7
7
|
name = "ContentX"
|
|
8
8
|
main_url = "https://contentx.me"
|
|
9
9
|
|
|
10
|
+
# Birden fazla domain destekle
|
|
11
|
+
supported_domains = [
|
|
12
|
+
"contentx.me", "four.contentx.me",
|
|
13
|
+
"dplayer82.site", "sn.dplayer82.site", "four.dplayer82.site", "org.dplayer82.site",
|
|
14
|
+
"dplayer74.site", "sn.dplayer74.site",
|
|
15
|
+
"hotlinger.com", "sn.hotlinger.com",
|
|
16
|
+
"playru.net", "four.playru.net",
|
|
17
|
+
"pichive.online", "four.pichive.online", "pichive.me", "four.pichive.me"
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
def can_handle_url(self, url: str) -> bool:
|
|
21
|
+
return any(domain in url for domain in self.supported_domains)
|
|
22
|
+
|
|
10
23
|
async def extract(self, url, referer=None) -> list[ExtractResult]:
|
|
11
24
|
if referer:
|
|
12
25
|
self.httpx.headers.update({"Referer": referer})
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
|
+
|
|
3
|
+
from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
|
|
4
|
+
from Kekik.Sifreleme import AESManager
|
|
5
|
+
import re, json
|
|
6
|
+
|
|
7
|
+
class DonilasPlay(ExtractorBase):
|
|
8
|
+
name = "DonilasPlay"
|
|
9
|
+
main_url = "https://donilasplay.com"
|
|
10
|
+
|
|
11
|
+
async def extract(self, url, referer=None) -> ExtractResult:
|
|
12
|
+
if referer:
|
|
13
|
+
self.httpx.headers.update({"Referer": referer})
|
|
14
|
+
|
|
15
|
+
istek = await self.httpx.get(url)
|
|
16
|
+
istek.raise_for_status()
|
|
17
|
+
i_source = istek.text
|
|
18
|
+
|
|
19
|
+
m3u_link = None
|
|
20
|
+
subtitles = []
|
|
21
|
+
|
|
22
|
+
# bePlayer pattern
|
|
23
|
+
be_player_match = re.search(r"bePlayer\('([^']+)',\s*'(\{[^}]+\})'\);", i_source)
|
|
24
|
+
if be_player_match:
|
|
25
|
+
be_player_pass = be_player_match.group(1)
|
|
26
|
+
be_player_data = be_player_match.group(2)
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
# AES decrypt
|
|
30
|
+
decrypted = AESManager.decrypt(be_player_data, be_player_pass)
|
|
31
|
+
data = json.loads(decrypted)
|
|
32
|
+
|
|
33
|
+
m3u_link = data.get("video_location")
|
|
34
|
+
|
|
35
|
+
# Altyazıları işle
|
|
36
|
+
str_subtitles = data.get("strSubtitles", [])
|
|
37
|
+
if str_subtitles:
|
|
38
|
+
for sub in str_subtitles:
|
|
39
|
+
label = sub.get("label", "")
|
|
40
|
+
file = sub.get("file", "")
|
|
41
|
+
# Forced altyazıları hariç tut
|
|
42
|
+
if "Forced" in label:
|
|
43
|
+
continue
|
|
44
|
+
if file:
|
|
45
|
+
# Türkçe kontrolü
|
|
46
|
+
keywords = ["tur", "tr", "türkçe", "turkce"]
|
|
47
|
+
language = "Turkish" if any(k in label.lower() for k in keywords) else label
|
|
48
|
+
subtitles.append(Subtitle(
|
|
49
|
+
name = language,
|
|
50
|
+
url = self.fix_url(file)
|
|
51
|
+
))
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
# Fallback: file pattern
|
|
56
|
+
if not m3u_link:
|
|
57
|
+
file_match = re.search(r'file:"([^"]+)"', i_source)
|
|
58
|
+
if file_match:
|
|
59
|
+
m3u_link = file_match.group(1)
|
|
60
|
+
|
|
61
|
+
# tracks pattern for subtitles
|
|
62
|
+
tracks_match = re.search(r'tracks:\[([^\]]+)', i_source)
|
|
63
|
+
if tracks_match:
|
|
64
|
+
try:
|
|
65
|
+
tracks_str = f"[{tracks_match.group(1)}]"
|
|
66
|
+
tracks = json.loads(tracks_str)
|
|
67
|
+
for track in tracks:
|
|
68
|
+
file_url = track.get("file")
|
|
69
|
+
label = track.get("label", "")
|
|
70
|
+
if file_url and "Forced" not in label:
|
|
71
|
+
subtitles.append(Subtitle(
|
|
72
|
+
name = label,
|
|
73
|
+
url = self.fix_url(file_url)
|
|
74
|
+
))
|
|
75
|
+
except Exception:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
if not m3u_link:
|
|
79
|
+
raise ValueError("m3u link not found")
|
|
80
|
+
|
|
81
|
+
return ExtractResult(
|
|
82
|
+
name = self.name,
|
|
83
|
+
url = m3u_link,
|
|
84
|
+
referer = url,
|
|
85
|
+
subtitles = subtitles
|
|
86
|
+
)
|
|
@@ -7,6 +7,12 @@ class Odnoklassniki(ExtractorBase):
|
|
|
7
7
|
name = "Odnoklassniki"
|
|
8
8
|
main_url = "https://odnoklassniki.ru"
|
|
9
9
|
|
|
10
|
+
# Birden fazla domain destekle
|
|
11
|
+
supported_domains = ["odnoklassniki.ru", "ok.ru"]
|
|
12
|
+
|
|
13
|
+
def can_handle_url(self, url: str) -> bool:
|
|
14
|
+
return any(domain in url for domain in self.supported_domains)
|
|
15
|
+
|
|
10
16
|
async def extract(self, url, referer=None) -> ExtractResult:
|
|
11
17
|
if "/video/" in url:
|
|
12
18
|
url = url.replace("/video/", "/videoembed/")
|
|
@@ -7,6 +7,12 @@ class PeaceMakerst(ExtractorBase):
|
|
|
7
7
|
name = "PeaceMakerst"
|
|
8
8
|
main_url = "https://peacemakerst.com"
|
|
9
9
|
|
|
10
|
+
# Birden fazla domain destekle
|
|
11
|
+
supported_domains = ["peacemakerst.com", "hdstreamable.com"]
|
|
12
|
+
|
|
13
|
+
def can_handle_url(self, url: str) -> bool:
|
|
14
|
+
return any(domain in url for domain in self.supported_domains)
|
|
15
|
+
|
|
10
16
|
async def extract(self, url, referer=None) -> ExtractResult:
|
|
11
17
|
if referer:
|
|
12
18
|
self.httpx.headers.update({"Referer": referer})
|
|
@@ -8,6 +8,12 @@ class RapidVid(ExtractorBase):
|
|
|
8
8
|
name = "RapidVid"
|
|
9
9
|
main_url = "https://rapidvid.net"
|
|
10
10
|
|
|
11
|
+
# Birden fazla domain destekle
|
|
12
|
+
supported_domains = ["rapidvid.net", "rapid.filmmakinesi.to"]
|
|
13
|
+
|
|
14
|
+
def can_handle_url(self, url: str) -> bool:
|
|
15
|
+
return any(domain in url for domain in self.supported_domains)
|
|
16
|
+
|
|
11
17
|
async def extract(self, url, referer=None) -> ExtractResult:
|
|
12
18
|
if referer:
|
|
13
19
|
self.httpx.headers.update({"Referer": referer})
|
|
@@ -5,7 +5,13 @@ import re
|
|
|
5
5
|
|
|
6
6
|
class SetPlay(ExtractorBase):
|
|
7
7
|
name = "SetPlay"
|
|
8
|
-
main_url = "https://setplay.
|
|
8
|
+
main_url = "https://setplay.shop"
|
|
9
|
+
|
|
10
|
+
# Birden fazla domain destekle
|
|
11
|
+
supported_domains = ["setplay.cfd", "setplay.shop", "setplay.site"]
|
|
12
|
+
|
|
13
|
+
def can_handle_url(self, url: str) -> bool:
|
|
14
|
+
return any(domain in url for domain in self.supported_domains)
|
|
9
15
|
|
|
10
16
|
async def extract(self, url, referer=None) -> ExtractResult:
|
|
11
17
|
ext_ref = referer or ""
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
|
+
|
|
3
|
+
from KekikStream.Core import ExtractorBase, ExtractResult
|
|
4
|
+
from urllib.parse import urlparse, parse_qs
|
|
5
|
+
|
|
6
|
+
class VCTPlay(ExtractorBase):
|
|
7
|
+
name = "VCTPlay"
|
|
8
|
+
main_url = "https://vctplay.site"
|
|
9
|
+
|
|
10
|
+
async def extract(self, url, referer=None) -> ExtractResult:
|
|
11
|
+
if referer:
|
|
12
|
+
self.httpx.headers.update({"Referer": referer})
|
|
13
|
+
|
|
14
|
+
# URL'den video ID'sini çıkar
|
|
15
|
+
# https://vctplay.site/video/2hjDGco5exdv -> 2hjDGco5exdv
|
|
16
|
+
video_id = url.split("/")[-1]
|
|
17
|
+
if "?" in video_id:
|
|
18
|
+
video_id = video_id.split("?")[0]
|
|
19
|
+
|
|
20
|
+
# Manifests URL oluştur
|
|
21
|
+
master_url = f"{self.main_url}/manifests/{video_id}/master.txt"
|
|
22
|
+
|
|
23
|
+
# partKey'den isim belirle
|
|
24
|
+
parsed = urlparse(url)
|
|
25
|
+
params = parse_qs(parsed.query)
|
|
26
|
+
part_key = params.get("partKey", [""])[0]
|
|
27
|
+
|
|
28
|
+
name_suffix = ""
|
|
29
|
+
if "turkcedublaj" in part_key.lower():
|
|
30
|
+
name_suffix = "Dublaj"
|
|
31
|
+
elif "turkcealtyazi" in part_key.lower():
|
|
32
|
+
name_suffix = "Altyazı"
|
|
33
|
+
|
|
34
|
+
display_name = f"{self.name} - {name_suffix}" if name_suffix else self.name
|
|
35
|
+
|
|
36
|
+
return ExtractResult(
|
|
37
|
+
name = display_name,
|
|
38
|
+
url = master_url,
|
|
39
|
+
referer = f"{self.main_url}/",
|
|
40
|
+
subtitles = []
|
|
41
|
+
)
|
|
@@ -9,6 +9,12 @@ class VidMoly(ExtractorBase):
|
|
|
9
9
|
name = "VidMoly"
|
|
10
10
|
main_url = "https://vidmoly.to"
|
|
11
11
|
|
|
12
|
+
# Birden fazla domain destekle
|
|
13
|
+
supported_domains = ["vidmoly.to", "vidmoly.me", "vidmoly.net"]
|
|
14
|
+
|
|
15
|
+
def can_handle_url(self, url: str) -> bool:
|
|
16
|
+
return any(domain in url for domain in self.supported_domains)
|
|
17
|
+
|
|
12
18
|
async def extract(self, url: str, referer: str = None) -> ExtractResult:
|
|
13
19
|
if referer:
|
|
14
20
|
self.httpx.headers.update({"Referer": referer})
|
|
@@ -17,11 +23,11 @@ class VidMoly(ExtractorBase):
|
|
|
17
23
|
"Sec-Fetch-Dest" : "iframe",
|
|
18
24
|
})
|
|
19
25
|
|
|
20
|
-
if
|
|
21
|
-
self.main_url = self.main_url.replace(".me", ".net")
|
|
26
|
+
if ".me" in url:
|
|
22
27
|
url = url.replace(".me", ".net")
|
|
23
28
|
|
|
24
|
-
|
|
29
|
+
# VidMoly bazen redirect ediyor, takip et
|
|
30
|
+
response = await self.httpx.get(url, follow_redirects=True)
|
|
25
31
|
if "Select number" in response.text:
|
|
26
32
|
secici = Selector(response.text)
|
|
27
33
|
response = await self.httpx.post(
|
|
@@ -33,21 +39,10 @@ class VidMoly(ExtractorBase):
|
|
|
33
39
|
"ts" : secici.css("input[name='ts']::attr(value)").get(),
|
|
34
40
|
"nonce" : secici.css("input[name='nonce']::attr(value)").get(),
|
|
35
41
|
"ctok" : secici.css("input[name='ctok']::attr(value)").get()
|
|
36
|
-
}
|
|
42
|
+
},
|
|
43
|
+
follow_redirects=True
|
|
37
44
|
)
|
|
38
45
|
|
|
39
|
-
script_match = re.search(r"sources:\s*\[(.*?)\],", response.text, re.DOTALL)
|
|
40
|
-
script_content = script_match[1] if script_match else None
|
|
41
|
-
|
|
42
|
-
if not script_content:
|
|
43
|
-
raise ValueError("Gerekli script bulunamadı.")
|
|
44
|
-
|
|
45
|
-
# Video kaynaklarını ayrıştır
|
|
46
|
-
video_data = self._add_marks(script_content, "file")
|
|
47
|
-
try:
|
|
48
|
-
video_sources = json.loads(f"[{video_data}]")
|
|
49
|
-
except json.JSONDecodeError as hata:
|
|
50
|
-
raise ValueError("Video kaynakları ayrıştırılamadı.") from hata
|
|
51
46
|
|
|
52
47
|
# Altyazı kaynaklarını ayrıştır
|
|
53
48
|
subtitles = []
|
|
@@ -66,22 +61,49 @@ class VidMoly(ExtractorBase):
|
|
|
66
61
|
for sub in subtitle_sources
|
|
67
62
|
if sub.get("kind") == "captions"
|
|
68
63
|
]
|
|
69
|
-
# İlk video kaynağını al
|
|
70
|
-
video_url = None
|
|
71
|
-
for source in video_sources:
|
|
72
|
-
if file_url := source.get("file"):
|
|
73
|
-
video_url = file_url
|
|
74
|
-
break
|
|
75
64
|
|
|
76
|
-
|
|
77
|
-
|
|
65
|
+
script_match = re.search(r"sources:\s*\[(.*?)\],", response.text, re.DOTALL)
|
|
66
|
+
if script_match:
|
|
67
|
+
script_content = script_match[1]
|
|
68
|
+
# Video kaynaklarını ayrıştır
|
|
69
|
+
video_data = self._add_marks(script_content, "file")
|
|
70
|
+
try:
|
|
71
|
+
video_sources = json.loads(f"[{video_data}]")
|
|
72
|
+
# İlk video kaynağını al
|
|
73
|
+
for source in video_sources:
|
|
74
|
+
if file_url := source.get("file"):
|
|
75
|
+
return ExtractResult(
|
|
76
|
+
name = self.name,
|
|
77
|
+
url = file_url,
|
|
78
|
+
referer = self.main_url,
|
|
79
|
+
subtitles = subtitles
|
|
80
|
+
)
|
|
81
|
+
except json.JSONDecodeError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
# Fallback: Doğrudan file regex ile ara (Kotlin mantığı)
|
|
85
|
+
# file:"..." veya file: "..."
|
|
86
|
+
if file_match := re.search(r'file\s*:\s*["\']([^"\']+\.m3u8[^"\']*)["\']', response.text):
|
|
87
|
+
return ExtractResult(
|
|
88
|
+
name = self.name,
|
|
89
|
+
url = file_match.group(1),
|
|
90
|
+
referer = self.main_url,
|
|
91
|
+
subtitles = subtitles
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Fallback 2: Herhangi bir file (m3u8 olma şartı olmadan ama tercihen)
|
|
95
|
+
if file_match := re.search(r'file\s*:\s*["\']([^"\']+)["\']', response.text):
|
|
96
|
+
url_candidate = file_match.group(1)
|
|
97
|
+
# Resim dosyalarını hariç tut
|
|
98
|
+
if not url_candidate.endswith(('.jpg', '.png', '.jpeg')):
|
|
99
|
+
return ExtractResult(
|
|
100
|
+
name = self.name,
|
|
101
|
+
url = url_candidate,
|
|
102
|
+
referer = self.main_url,
|
|
103
|
+
subtitles = subtitles
|
|
104
|
+
)
|
|
78
105
|
|
|
79
|
-
|
|
80
|
-
name = self.name,
|
|
81
|
-
url = video_url,
|
|
82
|
-
referer = self.main_url,
|
|
83
|
-
subtitles = subtitles
|
|
84
|
-
)
|
|
106
|
+
raise ValueError("Video URL bulunamadı.")
|
|
85
107
|
|
|
86
108
|
def _add_marks(self, text: str, field: str) -> str:
|
|
87
109
|
"""
|