KekikStream 1.7.1__py3-none-any.whl → 2.2.0__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 +20 -9
- KekikStream/Core/Extractor/ExtractorLoader.py +25 -17
- KekikStream/Core/Extractor/ExtractorManager.py +53 -9
- KekikStream/Core/Extractor/ExtractorModels.py +5 -7
- KekikStream/Core/Extractor/YTDLPCache.py +35 -0
- KekikStream/Core/Media/MediaHandler.py +44 -26
- KekikStream/Core/Media/MediaManager.py +0 -3
- KekikStream/Core/Plugin/PluginBase.py +82 -22
- KekikStream/Core/Plugin/PluginLoader.py +11 -7
- KekikStream/Core/Plugin/PluginModels.py +25 -26
- KekikStream/Core/__init__.py +1 -0
- KekikStream/Extractors/CloseLoad.py +21 -7
- KekikStream/Extractors/ContentX.py +21 -6
- KekikStream/Extractors/DonilasPlay.py +86 -0
- KekikStream/Extractors/DzenRu.py +38 -0
- KekikStream/Extractors/ExPlay.py +53 -0
- KekikStream/Extractors/Filemoon.py +78 -0
- KekikStream/Extractors/HDPlayerSystem.py +41 -0
- KekikStream/Extractors/JetTv.py +45 -0
- KekikStream/Extractors/MailRu.py +3 -4
- KekikStream/Extractors/MixPlayHD.py +2 -3
- KekikStream/Extractors/MixTiger.py +57 -0
- KekikStream/Extractors/MolyStream.py +5 -5
- KekikStream/Extractors/Odnoklassniki.py +13 -7
- KekikStream/Extractors/PeaceMakerst.py +10 -5
- KekikStream/Extractors/PixelDrain.py +1 -2
- KekikStream/Extractors/PlayerFilmIzle.py +65 -0
- KekikStream/Extractors/RapidVid.py +23 -8
- KekikStream/Extractors/SetPlay.py +66 -0
- KekikStream/Extractors/SetPrime.py +45 -0
- KekikStream/Extractors/SibNet.py +2 -3
- KekikStream/Extractors/Sobreatsesuyp.py +4 -5
- KekikStream/Extractors/TRsTX.py +4 -5
- KekikStream/Extractors/TauVideo.py +2 -3
- KekikStream/Extractors/TurboImgz.py +2 -3
- KekikStream/Extractors/TurkeyPlayer.py +34 -0
- KekikStream/Extractors/VCTPlay.py +41 -0
- KekikStream/Extractors/VidHide.py +81 -0
- KekikStream/Extractors/VidMoly.py +55 -34
- KekikStream/Extractors/VidMoxy.py +2 -3
- KekikStream/Extractors/VidPapi.py +89 -0
- KekikStream/Extractors/VideoSeyred.py +3 -4
- KekikStream/Extractors/YTDLP.py +211 -0
- KekikStream/Extractors/YildizKisaFilm.py +41 -0
- KekikStream/Plugins/BelgeselX.py +196 -0
- KekikStream/Plugins/DiziBox.py +25 -34
- KekikStream/Plugins/DiziPal.py +24 -35
- KekikStream/Plugins/DiziYou.py +54 -37
- KekikStream/Plugins/Dizilla.py +66 -46
- KekikStream/Plugins/FilmBip.py +142 -0
- KekikStream/Plugins/FilmMakinesi.py +36 -28
- KekikStream/Plugins/FilmModu.py +20 -24
- KekikStream/Plugins/FullHDFilm.py +220 -0
- KekikStream/Plugins/FullHDFilmizlesene.py +9 -15
- KekikStream/Plugins/HDFilmCehennemi.py +141 -69
- KekikStream/Plugins/JetFilmizle.py +85 -52
- KekikStream/Plugins/KultFilmler.py +217 -0
- KekikStream/Plugins/RecTV.py +22 -34
- KekikStream/Plugins/RoketDizi.py +222 -0
- KekikStream/Plugins/SelcukFlix.py +328 -0
- KekikStream/Plugins/SetFilmIzle.py +252 -0
- KekikStream/Plugins/SezonlukDizi.py +54 -21
- KekikStream/Plugins/SineWix.py +17 -29
- KekikStream/Plugins/Sinefy.py +241 -0
- KekikStream/Plugins/SinemaCX.py +154 -0
- KekikStream/Plugins/Sinezy.py +143 -0
- KekikStream/Plugins/SuperFilmGeldi.py +130 -0
- KekikStream/Plugins/UgurFilm.py +13 -19
- KekikStream/__init__.py +47 -56
- KekikStream/requirements.txt +3 -4
- kekikstream-2.2.0.dist-info/METADATA +312 -0
- kekikstream-2.2.0.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.7.1.dist-info/METADATA +0 -109
- kekikstream-1.7.1.dist-info/RECORD +0 -63
- {kekikstream-1.7.1.dist-info → kekikstream-2.2.0.dist-info}/WHEEL +0 -0
- {kekikstream-1.7.1.dist-info → kekikstream-2.2.0.dist-info}/entry_points.txt +0 -0
- {kekikstream-1.7.1.dist-info → kekikstream-2.2.0.dist-info}/licenses/LICENSE +0 -0
- {kekikstream-1.7.1.dist-info → kekikstream-2.2.0.dist-info}/top_level.txt +0 -0
|
@@ -9,22 +9,28 @@ 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
|
-
self.
|
|
20
|
+
self.httpx.headers.update({"Referer": referer})
|
|
15
21
|
|
|
16
|
-
self.
|
|
22
|
+
self.httpx.headers.update({
|
|
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
|
-
response = await self.
|
|
33
|
+
response = await self.httpx.post(
|
|
28
34
|
url = url,
|
|
29
35
|
data = {
|
|
30
36
|
"op" : secici.css("input[name='op']::attr(value)").get(),
|
|
@@ -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,23 +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
|
-
headers = {},
|
|
84
|
-
subtitles = subtitles
|
|
85
|
-
)
|
|
106
|
+
raise ValueError("Video URL bulunamadı.")
|
|
86
107
|
|
|
87
108
|
def _add_marks(self, text: str, field: str) -> str:
|
|
88
109
|
"""
|
|
@@ -10,9 +10,9 @@ class VidMoxy(ExtractorBase):
|
|
|
10
10
|
|
|
11
11
|
async def extract(self, url, referer=None) -> ExtractResult:
|
|
12
12
|
if referer:
|
|
13
|
-
self.
|
|
13
|
+
self.httpx.headers.update({"Referer": referer})
|
|
14
14
|
|
|
15
|
-
istek = await self.
|
|
15
|
+
istek = await self.httpx.get(url)
|
|
16
16
|
istek.raise_for_status()
|
|
17
17
|
|
|
18
18
|
subtitles = []
|
|
@@ -45,6 +45,5 @@ class VidMoxy(ExtractorBase):
|
|
|
45
45
|
name = self.name,
|
|
46
46
|
url = m3u_link,
|
|
47
47
|
referer = self.main_url,
|
|
48
|
-
headers = {},
|
|
49
48
|
subtitles = subtitles
|
|
50
49
|
)
|
|
@@ -0,0 +1,89 @@
|
|
|
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 VidPapi(ExtractorBase):
|
|
7
|
+
name = "VidApi"
|
|
8
|
+
main_url = "https://vidpapi.xyz"
|
|
9
|
+
|
|
10
|
+
async def extract(self, url, referer=None) -> ExtractResult:
|
|
11
|
+
ext_ref = referer or ""
|
|
12
|
+
|
|
13
|
+
# URL parsing
|
|
14
|
+
if "video/" in url:
|
|
15
|
+
vid_id = url.split("video/")[-1]
|
|
16
|
+
else:
|
|
17
|
+
vid_id = url.split("?data=")[-1]
|
|
18
|
+
|
|
19
|
+
# 1. Altyazıları çek
|
|
20
|
+
sub_url = f"{self.main_url}/player/index.php?data={vid_id}"
|
|
21
|
+
sub_headers = {
|
|
22
|
+
"Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8",
|
|
23
|
+
"X-Requested-With" : "XMLHttpRequest",
|
|
24
|
+
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0",
|
|
25
|
+
"Referer" : ext_ref or "https://kultfilmler.pro/"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
subtitles = []
|
|
29
|
+
try:
|
|
30
|
+
sub_istek = await self.httpx.post(
|
|
31
|
+
url = sub_url,
|
|
32
|
+
headers = sub_headers,
|
|
33
|
+
data = {"hash": vid_id, "r": "https://kultfilmler.pro/"}
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
subtitle_match = re.search(r'var playerjsSubtitle = "([^"]*)"', sub_istek.text, re.IGNORECASE)
|
|
37
|
+
if subtitle_match and subtitle_match.group(1):
|
|
38
|
+
raw_subs = subtitle_match.group(1)
|
|
39
|
+
|
|
40
|
+
found_subs = re.findall(r'\[(.*?)\](.*?)(?:,|$)', raw_subs)
|
|
41
|
+
for lang, sub_link in found_subs:
|
|
42
|
+
lang = lang.strip()
|
|
43
|
+
if "Türkçe" in lang:
|
|
44
|
+
lang_code = "tr"
|
|
45
|
+
lang_name = "Turkish"
|
|
46
|
+
elif "İngilizce" in lang:
|
|
47
|
+
lang_code = "en"
|
|
48
|
+
lang_name = "English"
|
|
49
|
+
else:
|
|
50
|
+
lang_code = lang[:2].lower()
|
|
51
|
+
lang_name = lang
|
|
52
|
+
|
|
53
|
+
subtitles.append(Subtitle(
|
|
54
|
+
name = lang_name,
|
|
55
|
+
url = sub_link.strip()
|
|
56
|
+
))
|
|
57
|
+
|
|
58
|
+
except Exception as e:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
# 2. Videoyu çek
|
|
62
|
+
video_url = f"{self.main_url}/player/index.php?data={vid_id}&do=getVideo"
|
|
63
|
+
video_headers = sub_headers.copy()
|
|
64
|
+
|
|
65
|
+
response = await self.httpx.post(
|
|
66
|
+
url = video_url,
|
|
67
|
+
headers = video_headers,
|
|
68
|
+
data = {"hash": vid_id, "r": "https://kultfilmler.pro/"}
|
|
69
|
+
)
|
|
70
|
+
response.raise_for_status()
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
video_data = response.json()
|
|
74
|
+
except Exception:
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
stream_url = video_data.get("securedLink")
|
|
78
|
+
if not stream_url or not stream_url.strip():
|
|
79
|
+
stream_url = video_data.get("videoSource")
|
|
80
|
+
|
|
81
|
+
if not stream_url:
|
|
82
|
+
raise ValueError("No video link found in VidPapi response")
|
|
83
|
+
|
|
84
|
+
return ExtractResult(
|
|
85
|
+
name = self.name,
|
|
86
|
+
url = stream_url,
|
|
87
|
+
referer = ext_ref or self.main_url,
|
|
88
|
+
subtitles = subtitles
|
|
89
|
+
)
|
|
@@ -9,18 +9,18 @@ class VideoSeyred(ExtractorBase):
|
|
|
9
9
|
|
|
10
10
|
async def extract(self, url, referer=None) -> ExtractResult:
|
|
11
11
|
if referer:
|
|
12
|
-
self.
|
|
12
|
+
self.httpx.headers.update({"Referer": referer})
|
|
13
13
|
|
|
14
14
|
video_id = url.split("embed/")[1].split("?")[0]
|
|
15
15
|
if len(video_id) > 10:
|
|
16
|
-
kontrol = await self.
|
|
16
|
+
kontrol = await self.httpx.get(url)
|
|
17
17
|
kontrol.raise_for_status()
|
|
18
18
|
|
|
19
19
|
video_id = re.search(r"playlist\/(.*)\.json", kontrol.text)[1]
|
|
20
20
|
|
|
21
21
|
video_url = f"{self.main_url}/playlist/{video_id}.json"
|
|
22
22
|
|
|
23
|
-
response = await self.
|
|
23
|
+
response = await self.httpx.get(video_url)
|
|
24
24
|
response.raise_for_status()
|
|
25
25
|
|
|
26
26
|
try:
|
|
@@ -43,7 +43,6 @@ class VideoSeyred(ExtractorBase):
|
|
|
43
43
|
name = self.name,
|
|
44
44
|
url = self.fix_url(source["file"]),
|
|
45
45
|
referer = self.main_url,
|
|
46
|
-
headers = {},
|
|
47
46
|
subtitles = subtitles,
|
|
48
47
|
)
|
|
49
48
|
for source in response_data.get("sources", [])
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
|
+
|
|
3
|
+
from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, get_ytdlp_extractors
|
|
4
|
+
from urllib.parse import urlparse
|
|
5
|
+
import yt_dlp, re, sys, os
|
|
6
|
+
|
|
7
|
+
class YTDLP(ExtractorBase):
|
|
8
|
+
name = "yt-dlp"
|
|
9
|
+
main_url = "" # Universal - tüm siteleri destekler
|
|
10
|
+
|
|
11
|
+
_FAST_DOMAIN_RE = None # compiled mega-regex (host üstünden)
|
|
12
|
+
|
|
13
|
+
_POPULAR_TLDS = {
|
|
14
|
+
"com", "net", "org", "tv", "io", "co", "me", "ly", "ru", "fr", "de", "es", "it",
|
|
15
|
+
"nl", "be", "ch", "at", "uk", "ca", "au", "jp", "kr", "cn", "in", "br", "mx",
|
|
16
|
+
"ar", "tr", "gov", "edu", "mil", "int", "info", "biz", "name", "pro", "aero",
|
|
17
|
+
"coop", "museum", "onion"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
# 1. Literal TLD Regex: youtube\.com, vimeo\.com
|
|
21
|
+
# sorted by reverse length to prevent partial matches (e.g. 'co' matching 'com')
|
|
22
|
+
_LITERAL_TLD_RE = re.compile(
|
|
23
|
+
rf"([a-z0-9][-a-z0-9]*(?:\\\.[-a-z0-9]+)*\\\.(?:{'|'.join(sorted(_POPULAR_TLDS, key=len, reverse=True))}))",
|
|
24
|
+
re.IGNORECASE
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
# 2. Regex TLD Regex: dailymotion\.[a-z]{2,3}
|
|
28
|
+
_REGEX_TLD_RE = re.compile(
|
|
29
|
+
r"([a-z0-9][-a-z0-9]*)\\\.\[a-z\]\{?\d*,?\d*\}?",
|
|
30
|
+
re.IGNORECASE
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# 3. Alternation TLD Regex: \.(?:com|net|org)
|
|
34
|
+
_ALT_TLD_RE = re.compile(
|
|
35
|
+
r"\\\.\(\?:([a-z|]+)\)",
|
|
36
|
+
re.IGNORECASE
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Kelime yakalayıcı (domain bulmak için)
|
|
40
|
+
_DOMAIN_WORD_RE = re.compile(
|
|
41
|
+
r"([a-z0-9][-a-z0-9]*)",
|
|
42
|
+
re.IGNORECASE
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def _extract_literal_domains(cls, valid_url: str) -> set[str]:
|
|
47
|
+
"""Pattern 1: Literal TLD domainlerini (youtube.com) çıkarır."""
|
|
48
|
+
return {
|
|
49
|
+
m.replace(r"\.", ".").lower()
|
|
50
|
+
for m in cls._LITERAL_TLD_RE.findall(valid_url)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def _extract_regex_tld_domains(cls, valid_url: str) -> set[str]:
|
|
55
|
+
"""Pattern 2: Regex TLD domainlerini (dailymotion.[...]) çıkarır ve popüler TLD'lerle birleştirir."""
|
|
56
|
+
domains = set()
|
|
57
|
+
for base in cls._REGEX_TLD_RE.findall(valid_url):
|
|
58
|
+
base_domain = base.lower()
|
|
59
|
+
for tld in cls._POPULAR_TLDS:
|
|
60
|
+
domains.add(f"{base_domain}.{tld}")
|
|
61
|
+
return domains
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def _extract_alternation_domains(cls, valid_url: str) -> set[str]:
|
|
65
|
+
"""Pattern 3: Alternation TLD domainlerini (pornhub.(?:com|net)) çıkarır."""
|
|
66
|
+
domains = set()
|
|
67
|
+
for m in cls._ALT_TLD_RE.finditer(valid_url):
|
|
68
|
+
tlds = m.group(1).split("|")
|
|
69
|
+
start = m.start()
|
|
70
|
+
|
|
71
|
+
# Geriye doğru git ve domain'i bul
|
|
72
|
+
before = valid_url[:start]
|
|
73
|
+
|
|
74
|
+
# 1. Named Groups (?P<name> temizle
|
|
75
|
+
before = re.sub(r"\(\?P<[^>]+>", "", before)
|
|
76
|
+
|
|
77
|
+
# 2. Simple Non-Capturing Groups (?:xxx)? temizle (sadece alphanumeric ve escape)
|
|
78
|
+
before = re.sub(r"\(\?:[a-z0-9-]+\)\??", "", before)
|
|
79
|
+
|
|
80
|
+
# Son domain-like kelimeyi al
|
|
81
|
+
words = cls._DOMAIN_WORD_RE.findall(before)
|
|
82
|
+
if not words:
|
|
83
|
+
continue
|
|
84
|
+
|
|
85
|
+
base = words[-1].lower()
|
|
86
|
+
for tld in tlds:
|
|
87
|
+
tld = tld.strip().lower()
|
|
88
|
+
if tld and len(tld) <= 6:
|
|
89
|
+
domains.add(f"{base}.{tld}")
|
|
90
|
+
|
|
91
|
+
return domains
|
|
92
|
+
|
|
93
|
+
@classmethod
|
|
94
|
+
def _init_fast_domain_regex(cls):
|
|
95
|
+
"""
|
|
96
|
+
Fast domain regex'i initialize et
|
|
97
|
+
"""
|
|
98
|
+
if cls._FAST_DOMAIN_RE is not None:
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
domains = set()
|
|
102
|
+
extractors = get_ytdlp_extractors()
|
|
103
|
+
|
|
104
|
+
for ie in extractors:
|
|
105
|
+
valid = getattr(ie, "_VALID_URL", None)
|
|
106
|
+
if not valid or not isinstance(valid, str):
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
domains |= cls._extract_literal_domains(valid)
|
|
110
|
+
domains |= cls._extract_regex_tld_domains(valid)
|
|
111
|
+
domains |= cls._extract_alternation_domains(valid)
|
|
112
|
+
|
|
113
|
+
# Hiç domain çıkmazsa (çok uç durum) fallback: boş regex
|
|
114
|
+
if not domains:
|
|
115
|
+
cls._FAST_DOMAIN_RE = re.compile(r"$^") # hiçbir şeye match etmez
|
|
116
|
+
return
|
|
117
|
+
|
|
118
|
+
joined = "|".join(re.escape(d) for d in sorted(domains))
|
|
119
|
+
cls._FAST_DOMAIN_RE = re.compile(rf"(?:^|.*\.)(?:{joined})$", re.IGNORECASE)
|
|
120
|
+
|
|
121
|
+
def __init__(self):
|
|
122
|
+
self.__class__._init_fast_domain_regex()
|
|
123
|
+
|
|
124
|
+
def can_handle_url(self, url: str) -> bool:
|
|
125
|
+
"""
|
|
126
|
+
Fast-path: URL host'unu tek mega-regex ile kontrol et
|
|
127
|
+
"""
|
|
128
|
+
# URL parse + host al
|
|
129
|
+
try:
|
|
130
|
+
parsed = urlparse(url)
|
|
131
|
+
host = (parsed.hostname or "").lower()
|
|
132
|
+
except Exception:
|
|
133
|
+
host = ""
|
|
134
|
+
|
|
135
|
+
# Şemasız URL desteği: "youtube.com/..." gibi
|
|
136
|
+
if not host and "://" not in url:
|
|
137
|
+
try:
|
|
138
|
+
parsed = urlparse("https://" + url)
|
|
139
|
+
host = (parsed.hostname or "").lower()
|
|
140
|
+
except Exception:
|
|
141
|
+
host = ""
|
|
142
|
+
|
|
143
|
+
# Fast-path
|
|
144
|
+
if host and self.__class__._FAST_DOMAIN_RE.search(host):
|
|
145
|
+
return True
|
|
146
|
+
|
|
147
|
+
# yt-dlp işleyemezse False döndür
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
async def extract(self, url: str, referer: str | None = None) -> ExtractResult:
|
|
151
|
+
ydl_opts = {
|
|
152
|
+
"quiet" : True,
|
|
153
|
+
"no_warnings" : True,
|
|
154
|
+
"extract_flat" : False, # Tam bilgi al
|
|
155
|
+
"format" : "best", # En iyi kalite
|
|
156
|
+
"no_check_certificates" : True,
|
|
157
|
+
"socket_timeout" : 3,
|
|
158
|
+
"retries" : 1
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# Referer varsa header olarak ekle
|
|
162
|
+
if referer:
|
|
163
|
+
ydl_opts["http_headers"] = {"Referer": referer}
|
|
164
|
+
|
|
165
|
+
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
166
|
+
info = ydl.extract_info(url, download=False)
|
|
167
|
+
|
|
168
|
+
if not info:
|
|
169
|
+
raise ValueError("yt-dlp video bilgisi döndürmedi")
|
|
170
|
+
|
|
171
|
+
# Video URL'sini al
|
|
172
|
+
video_url = info.get("url")
|
|
173
|
+
if not video_url:
|
|
174
|
+
# Bazen formatlar listesinde olabilir
|
|
175
|
+
formats = info.get("formats", [])
|
|
176
|
+
if formats:
|
|
177
|
+
video_url = formats[-1].get("url") # Son format (genellikle en iyi)
|
|
178
|
+
|
|
179
|
+
if not video_url:
|
|
180
|
+
raise ValueError("Video URL bulunamadı")
|
|
181
|
+
|
|
182
|
+
# Altyazıları çıkar
|
|
183
|
+
subtitles = []
|
|
184
|
+
if subtitle_data := info.get("subtitles"):
|
|
185
|
+
for lang, subs in subtitle_data.items():
|
|
186
|
+
for sub in subs:
|
|
187
|
+
if sub_url := sub.get("url"):
|
|
188
|
+
subtitles.append(
|
|
189
|
+
Subtitle(
|
|
190
|
+
name=f"{lang} ({sub.get('ext', 'unknown')})",
|
|
191
|
+
url=sub_url
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# User-Agent al
|
|
196
|
+
user_agent = None
|
|
197
|
+
http_headers = info.get("http_headers", {})
|
|
198
|
+
if http_headers:
|
|
199
|
+
user_agent = http_headers.get("User-Agent")
|
|
200
|
+
|
|
201
|
+
return ExtractResult(
|
|
202
|
+
name = self.name,
|
|
203
|
+
url = video_url,
|
|
204
|
+
referer = referer or info.get("webpage_url"),
|
|
205
|
+
user_agent = user_agent,
|
|
206
|
+
subtitles = subtitles
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
async def close(self):
|
|
210
|
+
"""yt-dlp için cleanup gerekmez"""
|
|
211
|
+
pass
|
|
@@ -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
|
+
|
|
5
|
+
class YildizKisaFilm(ExtractorBase):
|
|
6
|
+
name = "YildizKisaFilm"
|
|
7
|
+
main_url = "https://yildizkisafilm.org"
|
|
8
|
+
|
|
9
|
+
async def extract(self, url, referer=None) -> ExtractResult:
|
|
10
|
+
ext_ref = referer or ""
|
|
11
|
+
|
|
12
|
+
if "video/" in url:
|
|
13
|
+
vid_id = url.split("video/")[-1]
|
|
14
|
+
else:
|
|
15
|
+
vid_id = url.split("?data=")[-1]
|
|
16
|
+
|
|
17
|
+
post_url = f"{self.main_url}/player/index.php?data={vid_id}&do=getVideo"
|
|
18
|
+
|
|
19
|
+
response = await self.httpx.post(
|
|
20
|
+
url = post_url,
|
|
21
|
+
data = {"hash": vid_id, "r": ext_ref},
|
|
22
|
+
headers = {
|
|
23
|
+
"Referer" : ext_ref,
|
|
24
|
+
"Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8",
|
|
25
|
+
"X-Requested-With" : "XMLHttpRequest"
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
response.raise_for_status()
|
|
29
|
+
|
|
30
|
+
video_data = response.json()
|
|
31
|
+
m3u_link = video_data.get("securedLink")
|
|
32
|
+
|
|
33
|
+
if not m3u_link:
|
|
34
|
+
raise ValueError("securedLink not found in response")
|
|
35
|
+
|
|
36
|
+
return ExtractResult(
|
|
37
|
+
name = self.name,
|
|
38
|
+
url = m3u_link,
|
|
39
|
+
referer = ext_ref,
|
|
40
|
+
subtitles = []
|
|
41
|
+
)
|