KekikStream 2.0.3__tar.gz → 2.1.7__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-2.0.3 → kekikstream-2.1.7}/KekikStream/Core/Extractor/ExtractorBase.py +7 -2
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Core/Plugin/PluginBase.py +61 -13
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/CloseLoad.py +17 -2
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/ContentX.py +19 -2
- kekikstream-2.1.7/KekikStream/Extractors/DonilasPlay.py +86 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/MixTiger.py +5 -5
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/Odnoklassniki.py +6 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/PeaceMakerst.py +6 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/PlayerFilmIzle.py +8 -5
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/RapidVid.py +6 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/SetPlay.py +13 -4
- kekikstream-2.1.7/KekikStream/Extractors/VCTPlay.py +41 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/VidMoly.py +52 -30
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/YTDLP.py +87 -53
- kekikstream-2.1.7/KekikStream/Plugins/BelgeselX.py +196 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/DiziBox.py +8 -12
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/DiziPal.py +11 -22
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/DiziYou.py +9 -17
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/Dizilla.py +19 -14
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/FilmBip.py +5 -8
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/FilmMakinesi.py +31 -21
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/FilmModu.py +14 -18
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/FullHDFilm.py +83 -27
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/FullHDFilmizlesene.py +6 -8
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/HDFilmCehennemi.py +133 -57
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/JetFilmizle.py +9 -13
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/KultFilmler.py +13 -15
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/RecTV.py +16 -24
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/RoketDizi.py +22 -32
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/SelcukFlix.py +99 -80
- kekikstream-2.1.7/KekikStream/Plugins/SetFilmIzle.py +252 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/SezonlukDizi.py +26 -9
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/SineWix.py +13 -21
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/Sinefy.py +13 -10
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/SinemaCX.py +35 -38
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/Sinezy.py +5 -8
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/SuperFilmGeldi.py +36 -27
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Plugins/UgurFilm.py +7 -9
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/__init__.py +17 -36
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream.egg-info/PKG-INFO +5 -3
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream.egg-info/SOURCES.txt +4 -6
- {kekikstream-2.0.3 → kekikstream-2.1.7}/PKG-INFO +5 -3
- {kekikstream-2.0.3 → kekikstream-2.1.7}/README.md +4 -2
- {kekikstream-2.0.3 → kekikstream-2.1.7}/setup.py +1 -1
- kekikstream-2.0.3/KekikStream/Extractors/ContentX_.py +0 -40
- kekikstream-2.0.3/KekikStream/Extractors/FirePlayer.py +0 -60
- kekikstream-2.0.3/KekikStream/Extractors/Odnoklassniki_.py +0 -11
- kekikstream-2.0.3/KekikStream/Extractors/PeaceMakerst_.py +0 -7
- kekikstream-2.0.3/KekikStream/Extractors/RapidVid_.py +0 -7
- kekikstream-2.0.3/KekikStream/Extractors/VidMoly_.py +0 -7
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/CLI/__init__.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/CLI/pypi_kontrol.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Core/Extractor/ExtractorLoader.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Core/Extractor/ExtractorManager.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Core/Extractor/ExtractorModels.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Core/Extractor/YTDLPCache.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Core/Media/MediaHandler.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Core/Media/MediaManager.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Core/Plugin/PluginLoader.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Core/Plugin/PluginManager.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Core/Plugin/PluginModels.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Core/UI/UIManager.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Core/__init__.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/DzenRu.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/ExPlay.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/HDPlayerSystem.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/JetTv.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/MailRu.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/MixPlayHD.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/MolyStream.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/PixelDrain.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/SetPrime.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/SibNet.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/Sobreatsesuyp.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/TRsTX.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/TauVideo.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/TurboImgz.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/TurkeyPlayer.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/VidHide.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/VidMoxy.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/VidPapi.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/VideoSeyred.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/Extractors/YildizKisaFilm.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/__main__.py +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream/requirements.txt +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream.egg-info/dependency_links.txt +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream.egg-info/entry_points.txt +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream.egg-info/requires.txt +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/KekikStream.egg-info/top_level.txt +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/LICENSE +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/MANIFEST.in +0 -0
- {kekikstream-2.0.3 → kekikstream-2.1.7}/setup.cfg +0 -0
|
@@ -5,7 +5,7 @@ from cloudscraper import CloudScraper
|
|
|
5
5
|
from httpx import AsyncClient
|
|
6
6
|
from typing import Optional
|
|
7
7
|
from .ExtractorModels import ExtractResult
|
|
8
|
-
from urllib.parse import urljoin
|
|
8
|
+
from urllib.parse import urljoin, urlparse
|
|
9
9
|
|
|
10
10
|
class ExtractorBase(ABC):
|
|
11
11
|
# Çıkarıcının temel özellikleri
|
|
@@ -29,6 +29,11 @@ class ExtractorBase(ABC):
|
|
|
29
29
|
# URL'nin bu çıkarıcı tarafından işlenip işlenemeyeceğini kontrol et
|
|
30
30
|
return self.main_url in url
|
|
31
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
|
+
|
|
32
37
|
@abstractmethod
|
|
33
38
|
async def extract(self, url: str, referer: Optional[str] = None) -> ExtractResult:
|
|
34
39
|
# Alt sınıflar tarafından uygulanacak medya çıkarma fonksiyonu
|
|
@@ -46,4 +51,4 @@ class ExtractorBase(ABC):
|
|
|
46
51
|
if url.startswith("http") or url.startswith("{\""):
|
|
47
52
|
return url
|
|
48
53
|
|
|
49
|
-
return f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
|
|
54
|
+
return f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
2
|
|
|
3
|
+
from ...CLI import konsol
|
|
3
4
|
from abc import ABC, abstractmethod
|
|
4
5
|
from cloudscraper import CloudScraper
|
|
5
6
|
from httpx import AsyncClient
|
|
6
|
-
from .PluginModels import MainPageResult, SearchResult, MovieInfo
|
|
7
|
+
from .PluginModels import MainPageResult, SearchResult, MovieInfo, SeriesInfo
|
|
7
8
|
from ..Media.MediaHandler import MediaHandler
|
|
8
9
|
from ..Extractor.ExtractorManager import ExtractorManager
|
|
10
|
+
from ..Extractor.ExtractorModels import ExtractResult
|
|
9
11
|
from urllib.parse import urljoin
|
|
10
12
|
import re
|
|
11
13
|
|
|
@@ -53,31 +55,31 @@ class PluginBase(ABC):
|
|
|
53
55
|
pass
|
|
54
56
|
|
|
55
57
|
@abstractmethod
|
|
56
|
-
async def load_item(self, url: str) -> MovieInfo:
|
|
58
|
+
async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
|
|
57
59
|
"""Bir medya öğesi hakkında detaylı bilgi döndürür."""
|
|
58
60
|
pass
|
|
59
61
|
|
|
60
62
|
@abstractmethod
|
|
61
|
-
async def load_links(self, url: str) -> list[
|
|
63
|
+
async def load_links(self, url: str) -> list[ExtractResult]:
|
|
62
64
|
"""
|
|
63
65
|
Bir medya öğesi için oynatma bağlantılarını döndürür.
|
|
64
|
-
|
|
66
|
+
|
|
65
67
|
Args:
|
|
66
68
|
url: Medya URL'si
|
|
67
|
-
|
|
69
|
+
|
|
68
70
|
Returns:
|
|
69
|
-
|
|
71
|
+
ExtractResult listesi, her biri şu alanları içerir:
|
|
70
72
|
- url (str, zorunlu): Video URL'si
|
|
71
73
|
- name (str, zorunlu): Gösterim adı (tüm bilgileri içerir)
|
|
72
74
|
- referer (str, opsiyonel): Referer header
|
|
73
|
-
- subtitles (list, opsiyonel): Altyazı listesi
|
|
74
|
-
|
|
75
|
+
- subtitles (list[Subtitle], opsiyonel): Altyazı listesi
|
|
76
|
+
|
|
75
77
|
Example:
|
|
76
78
|
[
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
ExtractResult(
|
|
80
|
+
url="https://example.com/video.m3u8",
|
|
81
|
+
name="HDFilmCehennemi | 1080p TR Dublaj"
|
|
82
|
+
)
|
|
81
83
|
]
|
|
82
84
|
"""
|
|
83
85
|
pass
|
|
@@ -95,6 +97,40 @@ class PluginBase(ABC):
|
|
|
95
97
|
|
|
96
98
|
return f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
|
|
97
99
|
|
|
100
|
+
async def extract(self, url: str, referer: str = None, prefix: str | None = None) -> ExtractResult | None:
|
|
101
|
+
"""
|
|
102
|
+
Extractor ile video URL'sini çıkarır.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
url: Iframe veya video URL'si
|
|
106
|
+
referer: Referer header (varsayılan: plugin main_url)
|
|
107
|
+
prefix: İsmin başına eklenecek opsiyonel etiket (örn: "Türkçe Dublaj")
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
ExtractResult: Extractor sonucu (name prefix ile birleştirilmiş) veya None
|
|
111
|
+
|
|
112
|
+
Extractor bulunamadığında veya hata oluştuğunda uyarı verir.
|
|
113
|
+
"""
|
|
114
|
+
if referer is None:
|
|
115
|
+
referer = f"{self.main_url}/"
|
|
116
|
+
|
|
117
|
+
extractor = self.ex_manager.find_extractor(url)
|
|
118
|
+
if not extractor:
|
|
119
|
+
konsol.log(f"[magenta][?] {self.name} » Extractor bulunamadı: {url}")
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
try:
|
|
123
|
+
data = await extractor.extract(url, referer=referer)
|
|
124
|
+
|
|
125
|
+
# prefix varsa name'e ekle
|
|
126
|
+
if prefix and data.name:
|
|
127
|
+
data.name = f"{prefix} | {data.name}"
|
|
128
|
+
|
|
129
|
+
return data
|
|
130
|
+
except Exception as hata:
|
|
131
|
+
konsol.log(f"[red][!] {self.name} » Extractor hatası ({extractor.name}): {hata}")
|
|
132
|
+
return None
|
|
133
|
+
|
|
98
134
|
@staticmethod
|
|
99
135
|
def clean_title(title: str) -> str:
|
|
100
136
|
suffixes = [
|
|
@@ -119,4 +155,16 @@ class PluginBase(ABC):
|
|
|
119
155
|
for suffix in suffixes:
|
|
120
156
|
cleaned_title = re.sub(f"{re.escape(suffix)}.*$", "", cleaned_title, flags=re.IGNORECASE).strip()
|
|
121
157
|
|
|
122
|
-
return cleaned_title
|
|
158
|
+
return cleaned_title
|
|
159
|
+
|
|
160
|
+
async def play(self, **kwargs):
|
|
161
|
+
"""
|
|
162
|
+
Varsayılan oynatma metodu.
|
|
163
|
+
Tüm pluginlerde ortak kullanılır.
|
|
164
|
+
"""
|
|
165
|
+
extract_result = ExtractResult(**kwargs)
|
|
166
|
+
self.media_handler.title = kwargs.get("name")
|
|
167
|
+
if self.name not in self.media_handler.title:
|
|
168
|
+
self.media_handler.title = f"{self.name} | {self.media_handler.title}"
|
|
169
|
+
|
|
170
|
+
self.media_handler.play_media(extract_result)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
2
|
|
|
3
|
-
from KekikStream.Core import ExtractorBase, ExtractResult
|
|
3
|
+
from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
|
|
4
4
|
from Kekik.Sifreleme import Packer, StreamDecoder
|
|
5
|
+
from parsel import Selector
|
|
5
6
|
import re
|
|
6
7
|
|
|
7
8
|
class CloseLoadExtractor(ExtractorBase):
|
|
@@ -15,12 +16,26 @@ class CloseLoadExtractor(ExtractorBase):
|
|
|
15
16
|
istek = await self.httpx.get(url)
|
|
16
17
|
istek.raise_for_status()
|
|
17
18
|
|
|
19
|
+
# Video URL'sini çıkar
|
|
18
20
|
eval_func = re.compile(r'\s*(eval\(function[\s\S].*)\s*').findall(istek.text)[0]
|
|
19
21
|
m3u_link = StreamDecoder.extract_stream_url(Packer.unpack(eval_func))
|
|
20
22
|
|
|
23
|
+
# Subtitle'ları parse et (Kotlin referansı: track elementleri)
|
|
24
|
+
subtitles = []
|
|
25
|
+
secici = Selector(istek.text)
|
|
26
|
+
for track in secici.css("track"):
|
|
27
|
+
raw_src = track.css("::attr(src)").get() or ""
|
|
28
|
+
raw_src = raw_src.strip()
|
|
29
|
+
label = track.css("::attr(label)").get() or track.css("::attr(srclang)").get() or "Altyazı"
|
|
30
|
+
|
|
31
|
+
if raw_src:
|
|
32
|
+
full_url = raw_src if raw_src.startswith("http") else f"{self.main_url}{raw_src}"
|
|
33
|
+
subtitles.append(Subtitle(name=label, url=full_url))
|
|
34
|
+
|
|
21
35
|
return ExtractResult(
|
|
22
36
|
name = self.name,
|
|
23
37
|
url = m3u_link,
|
|
24
38
|
referer = self.main_url,
|
|
25
|
-
subtitles =
|
|
39
|
+
subtitles = subtitles
|
|
26
40
|
)
|
|
41
|
+
|
|
@@ -7,10 +7,26 @@ 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})
|
|
13
26
|
|
|
27
|
+
# Dinamik base URL kullan
|
|
28
|
+
base_url = self.get_base_url(url)
|
|
29
|
+
|
|
14
30
|
istek = await self.httpx.get(url)
|
|
15
31
|
istek.raise_for_status()
|
|
16
32
|
i_source = istek.text
|
|
@@ -39,7 +55,8 @@ class ContentX(ExtractorBase):
|
|
|
39
55
|
)
|
|
40
56
|
)
|
|
41
57
|
|
|
42
|
-
|
|
58
|
+
# base_url kullan (contentx.me yerine)
|
|
59
|
+
vid_source_request = await self.httpx.get(f"{base_url}/source2.php?v={i_extract_value}", headers={"Referer": referer or base_url})
|
|
43
60
|
vid_source_request.raise_for_status()
|
|
44
61
|
|
|
45
62
|
vid_source = vid_source_request.text
|
|
@@ -59,7 +76,7 @@ class ContentX(ExtractorBase):
|
|
|
59
76
|
|
|
60
77
|
if i_dublaj := re.search(r',\"([^"]+)\",\"Türkçe"', i_source):
|
|
61
78
|
dublaj_value = i_dublaj[1]
|
|
62
|
-
dublaj_source_request = await self.httpx.get(f"{
|
|
79
|
+
dublaj_source_request = await self.httpx.get(f"{base_url}/source2.php?v={dublaj_value}", headers={"Referer": referer or base_url})
|
|
63
80
|
dublaj_source_request.raise_for_status()
|
|
64
81
|
|
|
65
82
|
dublaj_source = dublaj_source_request.text
|
|
@@ -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
|
+
)
|
|
@@ -37,16 +37,15 @@ class MixTiger(ExtractorBase):
|
|
|
37
37
|
if not m3u_link:
|
|
38
38
|
raise ValueError("Video URL not found in response")
|
|
39
39
|
|
|
40
|
-
# Recursive extraction check
|
|
40
|
+
# Recursive extraction check - başka extractor kullanılabilir mi?
|
|
41
41
|
try:
|
|
42
42
|
from KekikStream.Core.Extractor.ExtractorManager import ExtractorManager
|
|
43
|
-
# Import inside method to avoid circular dependency
|
|
44
43
|
manager = ExtractorManager()
|
|
45
44
|
if nested_extractor := manager.find_extractor(m3u_link):
|
|
46
|
-
#
|
|
47
|
-
return await nested_extractor.extract(m3u_link, referer=
|
|
45
|
+
# Nested extractor ile çıkar
|
|
46
|
+
return await nested_extractor.extract(m3u_link, referer=ext_ref)
|
|
48
47
|
except Exception:
|
|
49
|
-
#
|
|
48
|
+
# Recursive extraction başarısız olursa standart sonucu döndür
|
|
50
49
|
pass
|
|
51
50
|
|
|
52
51
|
return ExtractResult(
|
|
@@ -55,3 +54,4 @@ class MixTiger(ExtractorBase):
|
|
|
55
54
|
referer = None if "disk.yandex" in m3u_link else ext_ref,
|
|
56
55
|
subtitles = []
|
|
57
56
|
)
|
|
57
|
+
|
|
@@ -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})
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
2
|
|
|
3
3
|
from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
|
|
4
|
-
import re
|
|
4
|
+
import re
|
|
5
5
|
|
|
6
6
|
class PlayerFilmIzle(ExtractorBase):
|
|
7
7
|
name = "PlayerFilmIzle"
|
|
8
8
|
main_url = "https://player.filmizle.in"
|
|
9
9
|
|
|
10
|
+
def can_handle_url(self, url: str) -> bool:
|
|
11
|
+
return "filmizle.in" in url or "fireplayer" in url.lower()
|
|
12
|
+
|
|
10
13
|
async def extract(self, url: str, referer: str = None) -> ExtractResult:
|
|
11
14
|
# Kotlin tarafında referer mainUrl olarak zorlanmış
|
|
12
15
|
ext_ref = self.main_url
|
|
@@ -29,20 +32,20 @@ class PlayerFilmIzle(ExtractorBase):
|
|
|
29
32
|
# Data yakalama: FirePlayer|DATA|...
|
|
30
33
|
data_match = re.search(r'FirePlayer\|([^|]+)\|', video_req, re.IGNORECASE)
|
|
31
34
|
data_val = data_match.group(1) if data_match else None
|
|
32
|
-
|
|
35
|
+
|
|
33
36
|
if not data_val:
|
|
34
37
|
raise ValueError("PlayerFilmIzle: Data bulunamadı")
|
|
35
38
|
|
|
36
39
|
url_post = f"{self.main_url}/player/index.php?data={data_val}&do=getVideo"
|
|
37
|
-
|
|
40
|
+
|
|
38
41
|
post_headers = {
|
|
39
42
|
"Referer": ext_ref,
|
|
40
43
|
"X-Requested-With": "XMLHttpRequest"
|
|
41
44
|
}
|
|
42
|
-
|
|
45
|
+
|
|
43
46
|
# Kotlin'de post data: "hash" -> data, "r" -> ""
|
|
44
47
|
post_data = {"hash": data_val, "r": ""}
|
|
45
|
-
|
|
48
|
+
|
|
46
49
|
response = await self.httpx.post(url_post, data=post_data, headers=post_headers)
|
|
47
50
|
get_url = response.text.replace("\\", "")
|
|
48
51
|
|
|
@@ -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})
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
2
|
|
|
3
3
|
from KekikStream.Core import ExtractorBase, ExtractResult
|
|
4
|
+
from urllib.parse import urlparse, parse_qs
|
|
4
5
|
import re
|
|
5
6
|
|
|
6
7
|
class SetPlay(ExtractorBase):
|
|
7
8
|
name = "SetPlay"
|
|
8
|
-
main_url = "https://setplay.
|
|
9
|
+
main_url = "https://setplay.shop"
|
|
10
|
+
|
|
11
|
+
# Birden fazla domain destekle
|
|
12
|
+
supported_domains = ["setplay.cfd", "setplay.shop", "setplay.site"]
|
|
13
|
+
|
|
14
|
+
def can_handle_url(self, url: str) -> bool:
|
|
15
|
+
return any(domain in url for domain in self.supported_domains)
|
|
9
16
|
|
|
10
17
|
async def extract(self, url, referer=None) -> ExtractResult:
|
|
11
18
|
ext_ref = referer or ""
|
|
@@ -13,6 +20,9 @@ class SetPlay(ExtractorBase):
|
|
|
13
20
|
if referer:
|
|
14
21
|
self.httpx.headers.update({"Referer": referer})
|
|
15
22
|
|
|
23
|
+
# Dinamik base URL kullan
|
|
24
|
+
base_url = self.get_base_url(url)
|
|
25
|
+
|
|
16
26
|
istek = await self.httpx.get(url)
|
|
17
27
|
istek.raise_for_status()
|
|
18
28
|
|
|
@@ -33,7 +43,6 @@ class SetPlay(ExtractorBase):
|
|
|
33
43
|
title_base = title_match[1].split(".")[-1] if title_match else "Unknown"
|
|
34
44
|
|
|
35
45
|
# partKey logic
|
|
36
|
-
from urllib.parse import urlparse, parse_qs
|
|
37
46
|
parsed = urlparse(url)
|
|
38
47
|
params = parse_qs(parsed.query)
|
|
39
48
|
part_key = params.get("partKey", [""])[0]
|
|
@@ -46,8 +55,8 @@ class SetPlay(ExtractorBase):
|
|
|
46
55
|
else:
|
|
47
56
|
name_suffix = title_base
|
|
48
57
|
|
|
49
|
-
# M3U8 link oluştur
|
|
50
|
-
m3u_link = f"{
|
|
58
|
+
# M3U8 link oluştur - base_url kullan (main_url yerine)
|
|
59
|
+
m3u_link = f"{base_url}{video_url}?s={video_server}"
|
|
51
60
|
|
|
52
61
|
return ExtractResult(
|
|
53
62
|
name = f"{self.name} - {name_suffix}",
|
|
@@ -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
|
"""
|