KekikStream 2.4.1__py3-none-any.whl → 2.4.3__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 +3 -2
- KekikStream/Core/HTMLHelper.py +134 -40
- KekikStream/Core/Plugin/PluginBase.py +3 -2
- KekikStream/Extractors/CloseLoad.py +30 -54
- KekikStream/Extractors/ContentX.py +27 -72
- KekikStream/Extractors/DonilasPlay.py +33 -77
- KekikStream/Extractors/DzenRu.py +10 -24
- KekikStream/Extractors/ExPlay.py +20 -38
- KekikStream/Extractors/Filemoon.py +19 -45
- KekikStream/Extractors/HDMomPlayer.py +24 -56
- KekikStream/Extractors/HDPlayerSystem.py +13 -31
- KekikStream/Extractors/HotStream.py +14 -32
- KekikStream/Extractors/JFVid.py +3 -24
- KekikStream/Extractors/JetTv.py +21 -34
- KekikStream/Extractors/MailRu.py +11 -29
- KekikStream/Extractors/MixPlayHD.py +15 -28
- KekikStream/Extractors/MixTiger.py +17 -40
- KekikStream/Extractors/MolyStream.py +17 -21
- KekikStream/Extractors/Odnoklassniki.py +28 -104
- KekikStream/Extractors/PeaceMakerst.py +18 -45
- KekikStream/Extractors/PixelDrain.py +8 -16
- KekikStream/Extractors/PlayerFilmIzle.py +22 -41
- KekikStream/Extractors/RapidVid.py +21 -35
- KekikStream/Extractors/SetPlay.py +18 -43
- KekikStream/Extractors/SibNet.py +7 -17
- KekikStream/Extractors/Sobreatsesuyp.py +23 -45
- KekikStream/Extractors/TRsTX.py +23 -53
- KekikStream/Extractors/TurboImgz.py +7 -14
- KekikStream/Extractors/VCTPlay.py +10 -28
- KekikStream/Extractors/VidHide.py +10 -31
- KekikStream/Extractors/VidMoly.py +65 -99
- KekikStream/Extractors/VidMoxy.py +16 -27
- KekikStream/Extractors/VidPapi.py +24 -54
- KekikStream/Extractors/VideoSeyred.py +19 -40
- KekikStream/Extractors/Videostr.py +42 -99
- KekikStream/Extractors/Vidoza.py +8 -15
- KekikStream/Extractors/YildizKisaFilm.py +13 -31
- KekikStream/Plugins/BelgeselX.py +63 -69
- KekikStream/Plugins/DiziBox.py +16 -36
- KekikStream/Plugins/DiziMom.py +37 -129
- KekikStream/Plugins/DiziPal.py +26 -75
- KekikStream/Plugins/DiziYou.py +44 -152
- KekikStream/Plugins/Dizilla.py +18 -44
- KekikStream/Plugins/FilmBip.py +10 -24
- KekikStream/Plugins/FilmEkseni.py +12 -32
- KekikStream/Plugins/FilmMakinesi.py +24 -77
- KekikStream/Plugins/FilmModu.py +11 -18
- KekikStream/Plugins/Filmatek.py +13 -39
- KekikStream/Plugins/Full4kizle.py +33 -133
- KekikStream/Plugins/FullHDFilm.py +23 -93
- KekikStream/Plugins/FullHDFilmizlesene.py +10 -29
- KekikStream/Plugins/HDFilmCehennemi.py +27 -66
- KekikStream/Plugins/JetFilmizle.py +19 -20
- KekikStream/Plugins/KultFilmler.py +16 -50
- KekikStream/Plugins/RecTV.py +47 -85
- KekikStream/Plugins/SelcukFlix.py +29 -47
- KekikStream/Plugins/SetFilmIzle.py +28 -84
- KekikStream/Plugins/SezonlukDizi.py +27 -59
- KekikStream/Plugins/Sinefy.py +37 -100
- KekikStream/Plugins/SinemaCX.py +12 -18
- KekikStream/Plugins/Sinezy.py +12 -13
- KekikStream/Plugins/SuperFilmGeldi.py +8 -13
- KekikStream/Plugins/UgurFilm.py +14 -14
- KekikStream/Plugins/Watch32.py +42 -74
- KekikStream/Plugins/YabanciDizi.py +33 -87
- {kekikstream-2.4.1.dist-info → kekikstream-2.4.3.dist-info}/METADATA +1 -1
- kekikstream-2.4.3.dist-info/RECORD +93 -0
- {kekikstream-2.4.1.dist-info → kekikstream-2.4.3.dist-info}/WHEEL +1 -1
- kekikstream-2.4.1.dist-info/RECORD +0 -93
- {kekikstream-2.4.1.dist-info → kekikstream-2.4.3.dist-info}/entry_points.txt +0 -0
- {kekikstream-2.4.1.dist-info → kekikstream-2.4.3.dist-info}/licenses/LICENSE +0 -0
- {kekikstream-2.4.1.dist-info → kekikstream-2.4.3.dist-info}/top_level.txt +0 -0
|
@@ -49,6 +49,7 @@ class ExtractorBase(ABC):
|
|
|
49
49
|
return ""
|
|
50
50
|
|
|
51
51
|
if url.startswith("http") or url.startswith("{\""):
|
|
52
|
-
return url
|
|
52
|
+
return url.replace("\\", "")
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
url = f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
|
|
55
|
+
return url.replace("\\", "")
|
KekikStream/Core/HTMLHelper.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
4
5
|
import re
|
|
6
|
+
from selectolax.parser import HTMLParser, Node
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
class HTMLHelper:
|
|
@@ -10,96 +12,192 @@ class HTMLHelper:
|
|
|
10
12
|
"""
|
|
11
13
|
|
|
12
14
|
def __init__(self, html: str):
|
|
13
|
-
self.parser = HTMLParser(html)
|
|
14
15
|
self.html = html
|
|
16
|
+
self.parser = HTMLParser(html)
|
|
15
17
|
|
|
16
18
|
# ========================
|
|
17
|
-
#
|
|
19
|
+
# SELECTOR (CSS) İŞLEMLERİ
|
|
18
20
|
# ========================
|
|
19
21
|
|
|
20
|
-
def
|
|
22
|
+
def _root(self, element: Node | None) -> Node | HTMLParser:
|
|
21
23
|
"""İşlem yapılacak temel elementi döndürür."""
|
|
22
24
|
return element if element is not None else self.parser
|
|
23
25
|
|
|
24
26
|
def select(self, selector: str, element: Node | None = None) -> list[Node]:
|
|
25
27
|
"""CSS selector ile tüm eşleşen elementleri döndür."""
|
|
26
|
-
return self.
|
|
28
|
+
return self._root(element).css(selector)
|
|
27
29
|
|
|
28
30
|
def select_first(self, selector: str | None, element: Node | None = None) -> Node | None:
|
|
29
31
|
"""CSS selector ile ilk eşleşen elementi döndür."""
|
|
30
32
|
if not selector:
|
|
31
33
|
return element
|
|
32
|
-
|
|
33
|
-
return self._target(element).css_first(selector)
|
|
34
|
+
return self._root(element).css_first(selector)
|
|
34
35
|
|
|
35
36
|
def select_text(self, selector: str | None = None, element: Node | None = None, strip: bool = True) -> str | None:
|
|
36
37
|
"""CSS selector ile element bul ve text içeriğini döndür."""
|
|
37
38
|
el = self.select_first(selector, element)
|
|
38
39
|
if not el:
|
|
39
40
|
return None
|
|
40
|
-
|
|
41
41
|
val = el.text(strip=strip)
|
|
42
|
-
return val
|
|
42
|
+
return val or None
|
|
43
|
+
|
|
44
|
+
def select_texts(self, selector: str, element: Node | None = None, strip: bool = True) -> 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=strip)
|
|
49
|
+
if txt:
|
|
50
|
+
out.append(txt)
|
|
51
|
+
return out
|
|
43
52
|
|
|
44
53
|
def select_attr(self, selector: str | None, attr: str, element: Node | None = None) -> str | None:
|
|
45
54
|
"""CSS selector ile element bul ve attribute değerini döndür."""
|
|
46
55
|
el = self.select_first(selector, element)
|
|
47
56
|
return el.attrs.get(attr) if el else None
|
|
48
57
|
|
|
49
|
-
def
|
|
50
|
-
"""CSS selector ile tüm eşleşen elementlerin text içeriklerini döndür."""
|
|
51
|
-
return [
|
|
52
|
-
txt for el in self.select(selector, element)
|
|
53
|
-
if (txt := el.text(strip=strip))
|
|
54
|
-
]
|
|
55
|
-
|
|
56
|
-
def select_all_attr(self, selector: str, attr: str, element: Node | None = None) -> list[str]:
|
|
58
|
+
def select_attrs(self, selector: str, attr: str, element: Node | None = None) -> list[str]:
|
|
57
59
|
"""CSS selector ile tüm eşleşen elementlerin attribute değerlerini döndür."""
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
|
64
66
|
|
|
65
67
|
def select_poster(self, selector: str = "img", element: Node | None = None) -> str | None:
|
|
66
68
|
"""Poster URL'sini çıkar. Önce data-src, sonra src dener."""
|
|
67
69
|
el = self.select_first(selector, element)
|
|
68
70
|
if not el:
|
|
69
71
|
return None
|
|
70
|
-
|
|
71
72
|
return el.attrs.get("data-src") or el.attrs.get("src")
|
|
72
73
|
|
|
74
|
+
def select_direct_text(self, selector: str, element: Node | None = None, strip: bool = True) -> str | None:
|
|
75
|
+
"""
|
|
76
|
+
Elementin yalnızca "kendi" düz metnini döndürür (child elementlerin text'ini katmadan).
|
|
77
|
+
Selectolax sürüm farklarına göre deep=False dene, yoksa node sibling-walk ile fallback yapar.
|
|
78
|
+
"""
|
|
79
|
+
el = self.select_first(selector, element)
|
|
80
|
+
if not el:
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
# 1) Bazı sürümlerde var: sadece direct text
|
|
84
|
+
try:
|
|
85
|
+
val = el.text(strip=strip, deep=False) # type: ignore[call-arg]
|
|
86
|
+
return val or None
|
|
87
|
+
except TypeError:
|
|
88
|
+
pass # deep parametresi yok, fallback'e geç
|
|
89
|
+
|
|
90
|
+
# 2) Fallback: direct children'ı el.child + next ile dolaş
|
|
91
|
+
parts: list[str] = []
|
|
92
|
+
ch = el.child
|
|
93
|
+
while ch is not None:
|
|
94
|
+
if ch.tag == "-text":
|
|
95
|
+
t = ch.text(strip=strip)
|
|
96
|
+
if t:
|
|
97
|
+
parts.append(t)
|
|
98
|
+
elif ch.tag == "br":
|
|
99
|
+
parts.append("\n")
|
|
100
|
+
ch = ch.next
|
|
101
|
+
|
|
102
|
+
out = "".join(parts).strip()
|
|
103
|
+
return out or None
|
|
104
|
+
|
|
105
|
+
# ========================
|
|
106
|
+
# META (LABEL -> VALUE) İŞLEMLERİ
|
|
107
|
+
# ========================
|
|
108
|
+
|
|
109
|
+
def meta_value(self, label: str, container_selector: str | None = None, strip: bool = True) -> str | None:
|
|
110
|
+
"""
|
|
111
|
+
Herhangi bir container içinde: LABEL metnini içeren bir elementten SONRA gelen metni döndürür.
|
|
112
|
+
label örn: "Oyuncular", "Yapım Yılı", "IMDB"
|
|
113
|
+
"""
|
|
114
|
+
needle = label.casefold()
|
|
115
|
+
|
|
116
|
+
# Belirli bir container varsa içinde ara, yoksa tüm dökümanda
|
|
117
|
+
targets = self.select(container_selector) if container_selector else [self.parser.body]
|
|
118
|
+
|
|
119
|
+
for root in targets:
|
|
120
|
+
if not root: continue
|
|
121
|
+
|
|
122
|
+
# Kalın/vurgulu elementlerde (span, strong, b, label, dt) label'ı ara
|
|
123
|
+
for label_el in self.select("span, strong, b, label, dt", root):
|
|
124
|
+
txt = (label_el.text(strip=True) or "").casefold()
|
|
125
|
+
if needle not in txt:
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
# 1) Elementin kendi içindeki text'te LABEL: VALUE formatı olabilir
|
|
129
|
+
# "Oyuncular: Brad Pitt" gibi. LABEL: sonrasını al.
|
|
130
|
+
full_txt = label_el.text(strip=strip)
|
|
131
|
+
if ":" in full_txt and needle in full_txt.split(":")[0].casefold():
|
|
132
|
+
val = full_txt.split(":", 1)[1].strip()
|
|
133
|
+
if val: return val
|
|
134
|
+
|
|
135
|
+
# 2) Label sonrası gelen ilk text node'u veya element'i al
|
|
136
|
+
curr = label_el.next
|
|
137
|
+
while curr:
|
|
138
|
+
if curr.tag == "-text":
|
|
139
|
+
val = curr.text(strip=strip).strip(" :")
|
|
140
|
+
if val: return val
|
|
141
|
+
elif curr.tag != "br":
|
|
142
|
+
val = curr.text(strip=strip).strip(" :")
|
|
143
|
+
if val: return val
|
|
144
|
+
else: # <br> gördüysek satır bitmiştir
|
|
145
|
+
break
|
|
146
|
+
curr = curr.next
|
|
147
|
+
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
def meta_list(self, label: str, container_selector: str | None = None, sep: str = ",") -> list[str]:
|
|
151
|
+
"""meta_value(...) çıktısını veya label'ın ebeveynindeki linkleri listeye döndürür."""
|
|
152
|
+
needle = label.casefold()
|
|
153
|
+
targets = self.select(container_selector) if container_selector else [self.parser.body]
|
|
154
|
+
|
|
155
|
+
for root in targets:
|
|
156
|
+
if not root: continue
|
|
157
|
+
for label_el in self.select("span, strong, b, label, dt", root):
|
|
158
|
+
if needle in (label_el.text(strip=True) or "").casefold():
|
|
159
|
+
# Eğer elementin ebeveyninde linkler varsa (Kutucuklu yapı), onları al
|
|
160
|
+
links = self.select_texts("a", label_el.parent)
|
|
161
|
+
if links: return links
|
|
162
|
+
|
|
163
|
+
# Yoksa düz metin olarak meta_value mantığıyla al
|
|
164
|
+
raw = self.meta_value(label, container_selector=container_selector)
|
|
165
|
+
if not raw: return []
|
|
166
|
+
return [x.strip() for x in raw.split(sep) if x.strip()]
|
|
167
|
+
|
|
168
|
+
return []
|
|
169
|
+
|
|
73
170
|
# ========================
|
|
74
171
|
# REGEX İŞLEMLERİ
|
|
75
172
|
# ========================
|
|
76
173
|
|
|
77
|
-
def
|
|
174
|
+
def _regex_source(self, target: str | int | None) -> str:
|
|
78
175
|
"""Regex için kaynak metni döndürür."""
|
|
79
176
|
return target if isinstance(target, str) else self.html
|
|
80
177
|
|
|
81
|
-
def
|
|
178
|
+
def _regex_flags(self, target: str | int | None, flags: int) -> int:
|
|
82
179
|
"""Regex flags değerini döndürür."""
|
|
83
180
|
return target if isinstance(target, int) else flags
|
|
84
181
|
|
|
85
|
-
def regex_first(self, pattern: str, target: str | int | None = None, flags: int = 0) -> str | None:
|
|
86
|
-
"""Regex ile arama yap,
|
|
87
|
-
match = re.search(pattern, self.
|
|
182
|
+
def regex_first(self, pattern: str, target: str | int | None = None, flags: int = 0, group: int | None = 1) -> str | tuple | None:
|
|
183
|
+
"""Regex ile arama yap, istenen grubu döndür (group=None ise tüm grupları tuple olarak döndür)."""
|
|
184
|
+
match = re.search(pattern, self._regex_source(target), self._regex_flags(target, flags))
|
|
88
185
|
if not match:
|
|
89
186
|
return None
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
return match.
|
|
93
|
-
|
|
94
|
-
|
|
187
|
+
|
|
188
|
+
if group is None:
|
|
189
|
+
return match.groups()
|
|
190
|
+
|
|
191
|
+
last_idx = match.lastindex or 0
|
|
192
|
+
return match.group(group) if last_idx >= group else match.group(0)
|
|
95
193
|
|
|
96
194
|
def regex_all(self, pattern: str, target: str | int | None = None, flags: int = 0) -> list[str]:
|
|
97
195
|
"""Regex ile tüm eşleşmeleri döndür."""
|
|
98
|
-
return re.findall(pattern, self.
|
|
196
|
+
return re.findall(pattern, self._regex_source(target), self._regex_flags(target, flags))
|
|
99
197
|
|
|
100
198
|
def regex_replace(self, pattern: str, repl: str, target: str | int | None = None, flags: int = 0) -> str:
|
|
101
199
|
"""Regex ile replace yap."""
|
|
102
|
-
return re.sub(pattern, repl, self.
|
|
200
|
+
return re.sub(pattern, repl, self._regex_source(target), flags=self._regex_flags(target, flags))
|
|
103
201
|
|
|
104
202
|
# ========================
|
|
105
203
|
# ÖZEL AYIKLAYICILAR
|
|
@@ -108,15 +206,12 @@ class HTMLHelper:
|
|
|
108
206
|
@staticmethod
|
|
109
207
|
def extract_season_episode(text: str) -> tuple[int | None, int | None]:
|
|
110
208
|
"""Metin içinden sezon ve bölüm numarasını çıkar."""
|
|
111
|
-
# S01E05 formatı
|
|
112
209
|
if m := re.search(r"[Ss](\d+)[Ee](\d+)", text):
|
|
113
210
|
return int(m.group(1)), int(m.group(2))
|
|
114
211
|
|
|
115
|
-
# Ayrı ayrı ara
|
|
116
212
|
s = re.search(r"(\d+)\.\s*[Ss]ezon|[Ss]ezon[- ]?(\d+)|-(\d+)-sezon|S(\d+)|(\d+)\.[Ss]", text, re.I)
|
|
117
213
|
e = re.search(r"(\d+)\.\s*[Bb][öo]l[üu]m|[Bb][öo]l[üu]m[- ]?(\d+)|-(\d+)-bolum|[Ee](\d+)", text, re.I)
|
|
118
214
|
|
|
119
|
-
# İlk bulunan grubu al (None değilse)
|
|
120
215
|
s_val = next((int(g) for g in s.groups() if g), None) if s else None
|
|
121
216
|
e_val = next((int(g) for g in e.groups() if g), None) if e else None
|
|
122
217
|
|
|
@@ -131,4 +226,3 @@ class HTMLHelper:
|
|
|
131
226
|
|
|
132
227
|
val = self.regex_first(pattern)
|
|
133
228
|
return int(val) if val and val.isdigit() else None
|
|
134
|
-
|
|
@@ -101,9 +101,10 @@ class PluginBase(ABC):
|
|
|
101
101
|
return ""
|
|
102
102
|
|
|
103
103
|
if url.startswith("http") or url.startswith("{\""):
|
|
104
|
-
return url
|
|
104
|
+
return url.replace("\\", "")
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
url = f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
|
|
107
|
+
return url.replace("\\", "")
|
|
107
108
|
|
|
108
109
|
async def extract(self, url: str, referer: str = None, prefix: str | None = None) -> ExtractResult | None:
|
|
109
110
|
"""
|
|
@@ -2,75 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, HTMLHelper
|
|
4
4
|
from Kekik.Sifreleme import Packer, StreamDecoder
|
|
5
|
-
import json
|
|
5
|
+
import json, contextlib
|
|
6
6
|
|
|
7
|
-
class
|
|
7
|
+
class CloseLoad(ExtractorBase):
|
|
8
8
|
name = "CloseLoad"
|
|
9
9
|
main_url = "https://closeload.filmmakinesi.to"
|
|
10
10
|
|
|
11
|
-
def
|
|
12
|
-
"""JSON-LD script tag'inden contentUrl'i çıkar (Kotlin versiyonundaki gibi)"""
|
|
13
|
-
secici = HTMLHelper(html)
|
|
14
|
-
for script in secici.select("script[type='application/ld+json']"):
|
|
15
|
-
try:
|
|
16
|
-
data = json.loads(script.text(strip=True))
|
|
17
|
-
if content_url := data.get("contentUrl"):
|
|
18
|
-
if content_url.startswith("http"):
|
|
19
|
-
return content_url
|
|
20
|
-
except (json.JSONDecodeError, TypeError):
|
|
21
|
-
# Regex ile contentUrl'i çıkarmayı dene
|
|
22
|
-
if content_url := secici.regex_first(r'"contentUrl"\s*:\s*"([^\"]+)"', script.text()):
|
|
23
|
-
if content_url.startswith("http"):
|
|
24
|
-
return content_url
|
|
25
|
-
return None
|
|
26
|
-
|
|
27
|
-
def _extract_from_packed(self, html: str) -> str | None:
|
|
28
|
-
"""Packed JavaScript'ten video URL'sini çıkar (fallback)"""
|
|
29
|
-
try:
|
|
30
|
-
packed = HTMLHelper(html).regex_all(r'\s*(eval\(function[\s\S].*)')
|
|
31
|
-
if packed:
|
|
32
|
-
return StreamDecoder.extract_stream_url(Packer.unpack(packed[0]))
|
|
33
|
-
except Exception:
|
|
34
|
-
pass
|
|
35
|
-
return None
|
|
36
|
-
|
|
37
|
-
async def extract(self, url, referer=None) -> ExtractResult:
|
|
38
|
-
if referer:
|
|
39
|
-
self.httpx.headers.update({"Referer": referer})
|
|
40
|
-
|
|
11
|
+
async def extract(self, url: str, referer: str = None) -> ExtractResult:
|
|
41
12
|
self.httpx.headers.update({
|
|
42
|
-
"
|
|
43
|
-
"Origin": self.main_url
|
|
13
|
+
"Referer" : referer or self.main_url,
|
|
14
|
+
"Origin" : self.main_url
|
|
44
15
|
})
|
|
45
16
|
|
|
46
|
-
|
|
47
|
-
|
|
17
|
+
resp = await self.httpx.get(url)
|
|
18
|
+
sel = HTMLHelper(resp.text)
|
|
48
19
|
|
|
49
|
-
#
|
|
50
|
-
|
|
20
|
+
# 1. JSON-LD'den Dene
|
|
21
|
+
m3u8_url = None
|
|
22
|
+
for script in sel.select("script[type='application/ld+json']"):
|
|
23
|
+
with contextlib.suppress(Exception):
|
|
24
|
+
data = json.loads(script.text(strip=True))
|
|
25
|
+
if content_url := data.get("contentUrl"):
|
|
26
|
+
if content_url.startswith("http"):
|
|
27
|
+
m3u8_url = content_url
|
|
28
|
+
break
|
|
51
29
|
|
|
52
|
-
#
|
|
53
|
-
if not
|
|
54
|
-
|
|
30
|
+
# 2. Packed Script Fallback
|
|
31
|
+
if not m3u8_url:
|
|
32
|
+
if packed := sel.regex_first(r"(eval\(function\(p,a,c,k,e,d\).+?)\s*</script>"):
|
|
33
|
+
m3u8_url = StreamDecoder.extract_stream_url(Packer.unpack(packed))
|
|
55
34
|
|
|
56
|
-
if not
|
|
57
|
-
raise
|
|
35
|
+
if not m3u8_url:
|
|
36
|
+
raise ValueError(f"CloseLoad: Video URL bulunamadı. {url}")
|
|
58
37
|
|
|
59
|
-
# Subtitle'ları parse et (Kotlin referansı: track elementleri)
|
|
60
38
|
subtitles = []
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
full_url = raw_src if raw_src.startswith("http") else f"{self.main_url}{raw_src}"
|
|
69
|
-
subtitles.append(Subtitle(name=label, url=full_url))
|
|
39
|
+
for track in sel.select("track"):
|
|
40
|
+
src = track.attrs.get("src")
|
|
41
|
+
if src:
|
|
42
|
+
subtitles.append(Subtitle(
|
|
43
|
+
name = track.attrs.get("label") or track.attrs.get("srclang") or "Altyazı",
|
|
44
|
+
url = self.fix_url(src)
|
|
45
|
+
))
|
|
70
46
|
|
|
71
47
|
return ExtractResult(
|
|
72
48
|
name = self.name,
|
|
73
|
-
url =
|
|
49
|
+
url = m3u8_url,
|
|
74
50
|
referer = self.main_url,
|
|
75
51
|
subtitles = subtitles
|
|
76
52
|
)
|
|
@@ -19,81 +19,36 @@ class ContentX(ExtractorBase):
|
|
|
19
19
|
def can_handle_url(self, url: str) -> bool:
|
|
20
20
|
return any(domain in url for domain in self.supported_domains)
|
|
21
21
|
|
|
22
|
-
async def extract(self, url, referer=None) -> list[ExtractResult]:
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
async def extract(self, url: str, referer: str = None) -> list[ExtractResult] | ExtractResult:
|
|
23
|
+
ref = referer or self.get_base_url(url)
|
|
24
|
+
self.httpx.headers.update({"Referer": ref})
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
resp = await self.httpx.get(url)
|
|
27
|
+
sel = HTMLHelper(resp.text)
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
i_extract_value = HTMLHelper(i_source).regex_first(r"window\.openPlayer\('([^']+)'")
|
|
34
|
-
if not i_extract_value:
|
|
35
|
-
raise ValueError("i_extract is null")
|
|
29
|
+
v_id = sel.regex_first(r"window\.openPlayer\('([^']+)'")
|
|
30
|
+
if not v_id:
|
|
31
|
+
raise ValueError(f"ContentX: ID bulunamadı. {url}")
|
|
36
32
|
|
|
37
33
|
subtitles = []
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
# base_url kullan (contentx.me yerine)
|
|
60
|
-
vid_source_request = await self.httpx.get(f"{base_url}/source2.php?v={i_extract_value}", headers={"Referer": referer or base_url})
|
|
61
|
-
vid_source_request.raise_for_status()
|
|
62
|
-
|
|
63
|
-
vid_source = vid_source_request.text
|
|
64
|
-
m3u_link = HTMLHelper(vid_source).regex_first(r'file":"([^\"]+)"')
|
|
65
|
-
if not m3u_link:
|
|
66
|
-
raise ValueError("vidExtract is null")
|
|
67
|
-
|
|
68
|
-
m3u_link = m3u_link.replace("\\", "").replace("/m.php", "/master.m3u8")
|
|
69
|
-
results = [
|
|
70
|
-
ExtractResult(
|
|
71
|
-
name = self.name,
|
|
72
|
-
url = m3u_link,
|
|
73
|
-
referer = url,
|
|
74
|
-
subtitles = subtitles
|
|
75
|
-
)
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
dublaj_value = HTMLHelper(i_source).regex_first(r'["\']([^"\']+)["\'],["\']Türkçe["\']')
|
|
79
|
-
if dublaj_value:
|
|
80
|
-
try:
|
|
81
|
-
dublaj_source_request = await self.httpx.get(f"{base_url}/source2.php?v={dublaj_value}", headers={"Referer": referer or base_url})
|
|
82
|
-
dublaj_source_request.raise_for_status()
|
|
83
|
-
|
|
84
|
-
dublaj_source = dublaj_source_request.text
|
|
85
|
-
dublaj_link = HTMLHelper(dublaj_source).regex_first(r'file":"([^\"]+)"')
|
|
86
|
-
if dublaj_link:
|
|
87
|
-
dublaj_link = dublaj_link.replace("\\", "")
|
|
88
|
-
results.append(
|
|
89
|
-
ExtractResult(
|
|
90
|
-
name = f"{self.name} Türkçe Dublaj",
|
|
91
|
-
url = dublaj_link,
|
|
92
|
-
referer = url,
|
|
93
|
-
subtitles = []
|
|
94
|
-
)
|
|
95
|
-
)
|
|
96
|
-
except Exception:
|
|
97
|
-
pass
|
|
34
|
+
for s_url, s_lang in sel.regex_all(r'"file":"([^\"]+)","label":"([^\"]+)"'):
|
|
35
|
+
decoded_lang = s_lang.encode().decode('unicode_escape')
|
|
36
|
+
subtitles.append(Subtitle(name=decoded_lang, url=self.fix_url(s_url.replace("\\", ""))))
|
|
37
|
+
|
|
38
|
+
results = []
|
|
39
|
+
# Base m3u8
|
|
40
|
+
vid_resp = await self.httpx.get(f"{self.get_base_url(url)}/source2.php?v={v_id}", headers={"Referer": url})
|
|
41
|
+
if m3u8_link := HTMLHelper(vid_resp.text).regex_first(r'file":"([^\"]+)"'):
|
|
42
|
+
m3u8_link = m3u8_link.replace("\\", "").replace("/m.php", "/master.m3u8")
|
|
43
|
+
results.append(ExtractResult(name=self.name, url=m3u8_link, referer=url, subtitles=subtitles))
|
|
44
|
+
|
|
45
|
+
# Dublaj Kontrolü
|
|
46
|
+
if dub_id := sel.regex_first(r'["\']([^"\']+)["\'],["\']Türkçe["\']'):
|
|
47
|
+
dub_resp = await self.httpx.get(f"{self.get_base_url(url)}/source2.php?v={dub_id}", headers={"Referer": url})
|
|
48
|
+
if dub_link := HTMLHelper(dub_resp.text).regex_first(r'file":"([^\"]+)"'):
|
|
49
|
+
results.append(ExtractResult(name=f"{self.name} Türkçe Dublaj", url=dub_link.replace("\\", ""), referer=url))
|
|
50
|
+
|
|
51
|
+
if not results:
|
|
52
|
+
raise ValueError(f"ContentX: Video linki bulunamadı. {url}")
|
|
98
53
|
|
|
99
54
|
return results[0] if len(results) == 1 else results
|
|
@@ -2,85 +2,41 @@
|
|
|
2
2
|
|
|
3
3
|
from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, HTMLHelper
|
|
4
4
|
from Kekik.Sifreleme import AESManager
|
|
5
|
-
import json
|
|
5
|
+
import json, contextlib
|
|
6
6
|
|
|
7
7
|
class DonilasPlay(ExtractorBase):
|
|
8
8
|
name = "DonilasPlay"
|
|
9
9
|
main_url = "https://donilasplay.com"
|
|
10
10
|
|
|
11
|
-
async def extract(self, url, referer=None) -> ExtractResult:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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 = hp.regex_first(r'file:"([^"]+)"')
|
|
58
|
-
if file_match:
|
|
59
|
-
m3u_link = file_match
|
|
60
|
-
|
|
61
|
-
# tracks pattern for subtitles
|
|
62
|
-
tracks_match = hp.regex_first(r'tracks:\[([^\]]+)')
|
|
63
|
-
if tracks_match:
|
|
64
|
-
try:
|
|
65
|
-
tracks_str = f"[{tracks_match}]"
|
|
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
|
-
)
|
|
11
|
+
async def extract(self, url: str, referer: str = None) -> ExtractResult:
|
|
12
|
+
self.httpx.headers.update({"Referer": referer or url})
|
|
13
|
+
|
|
14
|
+
resp = await self.httpx.get(url)
|
|
15
|
+
sel = HTMLHelper(resp.text)
|
|
16
|
+
|
|
17
|
+
m3u8_url = None
|
|
18
|
+
subtitles = []
|
|
19
|
+
|
|
20
|
+
# 1. bePlayer (AES)
|
|
21
|
+
if be_match := sel.regex_first(r"bePlayer\('([^']+)',\s*'(\{[^}]+\})'\);", group=None):
|
|
22
|
+
pass_val, data_val = be_match
|
|
23
|
+
with contextlib.suppress(Exception):
|
|
24
|
+
data = json.loads(AESManager.decrypt(data_val, pass_val))
|
|
25
|
+
m3u8_url = data.get("video_location")
|
|
26
|
+
for sub in data.get("strSubtitles", []):
|
|
27
|
+
if "Forced" not in sub.get("label", ""):
|
|
28
|
+
subtitles.append(Subtitle(name=sub.get("label"), url=self.fix_url(sub.get("file"))))
|
|
29
|
+
|
|
30
|
+
# 2. Fallback
|
|
31
|
+
if not m3u8_url:
|
|
32
|
+
m3u8_url = sel.regex_first(r'file:"([^"]+)"')
|
|
33
|
+
if tracks_match := sel.regex_first(r'tracks:\[([^\]]+)'):
|
|
34
|
+
with contextlib.suppress(Exception):
|
|
35
|
+
for track in json.loads(f"[{tracks_match}]"):
|
|
36
|
+
if "Forced" not in track.get("label", ""):
|
|
37
|
+
subtitles.append(Subtitle(name=track.get("label"), url=self.fix_url(track.get("file"))))
|
|
38
|
+
|
|
39
|
+
if not m3u8_url:
|
|
40
|
+
raise ValueError(f"DonilasPlay: Video linki bulunamadı. {url}")
|
|
41
|
+
|
|
42
|
+
return ExtractResult(name=self.name, url=m3u8_url, referer=url, subtitles=subtitles)
|