KekikStream 2.3.7__tar.gz → 2.3.9__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.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/MolyStream.py +1 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/VidMoly.py +13 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/DiziPal.py +20 -9
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/DiziYou.py +14 -18
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/Dizilla.py +50 -59
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/FilmBip.py +8 -4
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/FilmMakinesi.py +1 -1
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/FullHDFilmizlesene.py +7 -16
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/JetFilmizle.py +6 -1
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/KultFilmler.py +2 -1
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/RecTV.py +27 -5
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/RoketDizi.py +76 -78
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/SelcukFlix.py +90 -67
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/SetFilmIzle.py +27 -8
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/YabanciDizi.py +25 -14
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream.egg-info/PKG-INFO +1 -1
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream.egg-info/SOURCES.txt +0 -1
- {kekikstream-2.3.7 → kekikstream-2.3.9}/PKG-INFO +1 -1
- {kekikstream-2.3.7 → kekikstream-2.3.9}/setup.py +1 -1
- kekikstream-2.3.7/KekikStream/Plugins/DiziWatch.py +0 -212
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/CLI/__init__.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/CLI/pypi_kontrol.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Extractor/ExtractorBase.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Extractor/ExtractorLoader.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Extractor/ExtractorManager.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Extractor/ExtractorModels.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Extractor/YTDLPCache.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/HTMLHelper.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Media/MediaHandler.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Media/MediaManager.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Plugin/PluginBase.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Plugin/PluginLoader.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Plugin/PluginManager.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Plugin/PluginModels.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/UI/UIManager.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/__init__.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/CloseLoad.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/ContentX.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/DonilasPlay.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/DzenRu.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/ExPlay.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/Filemoon.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/HDPlayerSystem.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/JFVid.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/JetTv.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/MailRu.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/MixPlayHD.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/MixTiger.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/Odnoklassniki.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/PeaceMakerst.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/PixelDrain.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/PlayerFilmIzle.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/RapidVid.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/SetPlay.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/SetPrime.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/SibNet.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/Sobreatsesuyp.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/TRsTX.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/TauVideo.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/TurboImgz.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/TurkeyPlayer.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/VCTPlay.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/VidHide.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/VidMoxy.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/VidPapi.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/VideoSeyred.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/YTDLP.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/YildizKisaFilm.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/BelgeselX.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/DiziBox.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/FilmModu.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/FullHDFilm.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/HDFilmCehennemi.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/SezonlukDizi.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/SineWix.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/Sinefy.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/SinemaCX.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/Sinezy.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/SuperFilmGeldi.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/UgurFilm.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/__init__.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/__main__.py +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/requirements.txt +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream.egg-info/dependency_links.txt +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream.egg-info/entry_points.txt +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream.egg-info/requires.txt +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream.egg-info/top_level.txt +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/LICENSE +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/MANIFEST.in +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/README.md +0 -0
- {kekikstream-2.3.7 → kekikstream-2.3.9}/setup.cfg +0 -0
|
@@ -24,6 +24,8 @@ class VidMoly(ExtractorBase):
|
|
|
24
24
|
|
|
25
25
|
if ".me" in url:
|
|
26
26
|
url = url.replace(".me", ".net")
|
|
27
|
+
if ".to" in url:
|
|
28
|
+
url = url.replace(".to", ".net")
|
|
27
29
|
|
|
28
30
|
# VidMoly bazen redirect ediyor, takip et
|
|
29
31
|
response = await self.httpx.get(url, follow_redirects=True)
|
|
@@ -70,6 +72,17 @@ class VidMoly(ExtractorBase):
|
|
|
70
72
|
if sub.get("kind") == "captions"
|
|
71
73
|
]
|
|
72
74
|
|
|
75
|
+
if "#EXTM3U" in response.text:
|
|
76
|
+
for line in response.text.splitlines():
|
|
77
|
+
line = line.strip().replace('"', '').replace("'", "")
|
|
78
|
+
if line.startswith("http"):
|
|
79
|
+
return ExtractResult(
|
|
80
|
+
name = self.name,
|
|
81
|
+
url = line,
|
|
82
|
+
referer = self.main_url,
|
|
83
|
+
subtitles = subtitles
|
|
84
|
+
)
|
|
85
|
+
|
|
73
86
|
if script_str := resp_sec.regex_first(r"sources:\s*\[(.*?)\],", flags= re.DOTALL):
|
|
74
87
|
script_content = script_str
|
|
75
88
|
# Video kaynaklarını ayrıştır
|
|
@@ -127,18 +127,27 @@ class DiziPal(PluginBase):
|
|
|
127
127
|
|
|
128
128
|
poster = self.fix_url(secici.select_attr("meta[property='og:image']", "content")) if secici.select_attr("meta[property='og:image']", "content") else None
|
|
129
129
|
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
# Sidebar bilgilerini topla
|
|
131
|
+
info = {}
|
|
132
|
+
for li in secici.select("li"):
|
|
133
|
+
key = secici.select_text("div.key", li)
|
|
134
|
+
val = secici.select_text("div.value", li)
|
|
135
|
+
if key and val:
|
|
136
|
+
info[key.strip(":")] = val.strip()
|
|
137
|
+
|
|
138
|
+
year = info.get("Yapım Yılı")
|
|
139
|
+
rating = info.get("IMDB Puanı")
|
|
140
|
+
|
|
141
|
+
tags_raw = info.get("Türler", "")
|
|
142
|
+
tags = [t.strip() for t in tags_raw.split() if t.strip()] if tags_raw else None
|
|
134
143
|
|
|
135
|
-
|
|
144
|
+
actors_raw = info.get("Oyuncular")
|
|
145
|
+
actors = [a.strip() for a in actors_raw.split(",") if a.strip()] if actors_raw else None
|
|
136
146
|
|
|
137
|
-
|
|
138
|
-
tags = [t.strip() for t in tags_raw.split() if t.strip()] if tags_raw else None
|
|
147
|
+
description = secici.select_text("div.summary p")
|
|
139
148
|
|
|
140
|
-
duration_raw =
|
|
141
|
-
duration = int(duration_raw) if duration_raw else None
|
|
149
|
+
duration_raw = info.get("Ortalama Süre")
|
|
150
|
+
duration = int(secici.regex_first(r"(\d+)", duration_raw)) if duration_raw else None
|
|
142
151
|
|
|
143
152
|
if "/dizi/" in url:
|
|
144
153
|
title = secici.select_text("div.cover h5")
|
|
@@ -177,6 +186,7 @@ class DiziPal(PluginBase):
|
|
|
177
186
|
year = year,
|
|
178
187
|
duration = duration,
|
|
179
188
|
episodes = episodes if episodes else None,
|
|
189
|
+
actors = actors,
|
|
180
190
|
)
|
|
181
191
|
else:
|
|
182
192
|
# Film için title - g-title div'lerinin 2. olanı
|
|
@@ -192,6 +202,7 @@ class DiziPal(PluginBase):
|
|
|
192
202
|
rating = rating,
|
|
193
203
|
year = year,
|
|
194
204
|
duration = duration,
|
|
205
|
+
actors = actors,
|
|
195
206
|
)
|
|
196
207
|
|
|
197
208
|
async def load_links(self, url: str) -> list[ExtractResult]:
|
|
@@ -72,7 +72,7 @@ class DiziYou(PluginBase):
|
|
|
72
72
|
html_text = istek.text
|
|
73
73
|
|
|
74
74
|
# Title - div.title h1 içinde
|
|
75
|
-
title = secici.select_text("div.title h1")
|
|
75
|
+
title = (secici.select_text("div.title h1") or "").strip()
|
|
76
76
|
|
|
77
77
|
# Fallback: Eğer title boşsa URL'den çıkar (telif kısıtlaması olan sayfalar için)
|
|
78
78
|
if not title:
|
|
@@ -81,23 +81,19 @@ class DiziYou(PluginBase):
|
|
|
81
81
|
title = slug.replace('-', ' ').title()
|
|
82
82
|
|
|
83
83
|
# Poster
|
|
84
|
-
poster_src = secici.select_attr("div.category_image img", "src")
|
|
84
|
+
poster_src = secici.select_attr("div.category_image img", "src") or secici.select_attr("meta[property='og:image']", "content")
|
|
85
85
|
poster = self.fix_url(poster_src) if poster_src else ""
|
|
86
86
|
|
|
87
87
|
# Year - regex ile çıkarma (xpath yerine)
|
|
88
88
|
year = secici.regex_first(r"(?is)Yapım Yılı.*?(\d{4})", secici.html)
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
desc_html = HTMLHelper(desc_html).regex_replace(r"(?s)<div id=\"icerikcat2\".*", "")
|
|
98
|
-
# Kalan HTML'den text çıkar
|
|
99
|
-
clean_sel = HTMLHelper(desc_html)
|
|
100
|
-
description = clean_sel.select_text()
|
|
90
|
+
description_el = secici.select("div.diziyou_desc") or secici.select("div#icerikcat")
|
|
91
|
+
description = ""
|
|
92
|
+
if description_el:
|
|
93
|
+
# Scriptleri temizle
|
|
94
|
+
for script in secici.select("script", description_el[0]):
|
|
95
|
+
script.decompose()
|
|
96
|
+
description = secici.select_text(None, description_el[0])
|
|
101
97
|
|
|
102
98
|
tags = [secici.select_text(None, a) for a in secici.select("div.genres a") if secici.select_text(None, a)]
|
|
103
99
|
|
|
@@ -109,9 +105,9 @@ class DiziYou(PluginBase):
|
|
|
109
105
|
actors = [actor.strip() for actor in actors_raw.split(",") if actor.strip()] if actors_raw else []
|
|
110
106
|
|
|
111
107
|
episodes = []
|
|
112
|
-
# Episodes -
|
|
113
|
-
for link in secici.select("a"):
|
|
114
|
-
ep_href = secici.select_attr(
|
|
108
|
+
# Episodes - div#scrollbar-container a (kısıtlı alan)
|
|
109
|
+
for link in secici.select("div#scrollbar-container a"):
|
|
110
|
+
ep_href = secici.select_attr(None, "href", link)
|
|
115
111
|
if not ep_href:
|
|
116
112
|
continue
|
|
117
113
|
|
|
@@ -179,9 +175,9 @@ class DiziYou(PluginBase):
|
|
|
179
175
|
# Player src'den item_id çıkar - önce özel player seçicisini dene
|
|
180
176
|
player_src = None
|
|
181
177
|
# Yaygın locatorlar
|
|
182
|
-
for sel in ["iframe#diziyouPlayer", "div.player iframe", "iframe[src*='/episodes/']", "iframe"]:
|
|
178
|
+
for sel in ["iframe#diziyouPlayer", "div.player iframe", "iframe[src*='/player/']", "iframe[src*='/episodes/']", "iframe"]:
|
|
183
179
|
p = secici.select_attr(sel, "src")
|
|
184
|
-
if p and
|
|
180
|
+
if p and any(x in p.lower() for x in ["/player/", "/episodes/", "diziyou"]):
|
|
185
181
|
player_src = p
|
|
186
182
|
break
|
|
187
183
|
|
|
@@ -137,70 +137,60 @@ class Dizilla(PluginBase):
|
|
|
137
137
|
istek = await self.httpx.get(url)
|
|
138
138
|
secici = HTMLHelper(istek.text)
|
|
139
139
|
|
|
140
|
-
|
|
141
|
-
if not
|
|
140
|
+
next_data_text = secici.select_text("script#__NEXT_DATA__")
|
|
141
|
+
if not next_data_text:
|
|
142
142
|
return None
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
description =
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
for
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if not links:
|
|
184
|
-
continue
|
|
185
|
-
|
|
186
|
-
ep_link = links[-1]
|
|
187
|
-
ep_name = sezon_secici.select_text("a", ep_link)
|
|
188
|
-
ep_href = sezon_secici.select_attr("a", "href", ep_link)
|
|
189
|
-
ep_href = self.fix_url(ep_href)
|
|
144
|
+
next_data = loads(next_data_text)
|
|
145
|
+
secure_data = next_data.get("props", {}).get("pageProps", {}).get("secureData")
|
|
146
|
+
if not secure_data:
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
decrypted = await self.decrypt_response(secure_data)
|
|
150
|
+
content = decrypted.get("contentItem", {})
|
|
151
|
+
if not content:
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
title = content.get("original_title") or content.get("used_title")
|
|
155
|
+
description = content.get("description") or content.get("used_description")
|
|
156
|
+
rating = content.get("imdb_point") or content.get("local_vote_avg")
|
|
157
|
+
year = content.get("release_year")
|
|
158
|
+
|
|
159
|
+
# Poster and Backdrop - prefer backdrop if available for SeriesInfo
|
|
160
|
+
poster = self.fix_poster_url(self.fix_url(content.get("back_url") or content.get("poster_url")))
|
|
161
|
+
|
|
162
|
+
# Tags
|
|
163
|
+
tags = []
|
|
164
|
+
categories = decrypted.get("RelatedResults", {}).get("getSerieCategoriesById", {}).get("result", [])
|
|
165
|
+
for cat in categories:
|
|
166
|
+
tags.append(cat.get("name"))
|
|
167
|
+
|
|
168
|
+
# Actors
|
|
169
|
+
actors = []
|
|
170
|
+
casts = decrypted.get("RelatedResults", {}).get("getSerieCastsById", {}).get("result", [])
|
|
171
|
+
for cast in casts:
|
|
172
|
+
actors.append(cast.get("name"))
|
|
173
|
+
|
|
174
|
+
# Episodes
|
|
175
|
+
episodes = []
|
|
176
|
+
seasons_data = decrypted.get("RelatedResults", {}).get("getSerieSeasonAndEpisodes", {}).get("result", [])
|
|
177
|
+
for season_item in seasons_data:
|
|
178
|
+
season_num = season_item.get("season_no")
|
|
179
|
+
for ep_item in season_item.get("episodes", []):
|
|
180
|
+
ep_num = ep_item.get("episode_no")
|
|
181
|
+
ep_slug = ep_item.get("used_slug")
|
|
182
|
+
ep_name = ep_item.get("episode_text") or ""
|
|
190
183
|
|
|
191
|
-
#
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
pass
|
|
198
|
-
|
|
199
|
-
episodeses.append(Episode(
|
|
184
|
+
# Filter out duplicate language entries if any (we just need one link per episode)
|
|
185
|
+
# Usually they share the same slug for the episode page
|
|
186
|
+
if any(e.season == season_num and e.episode == ep_num for e in episodes):
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
episodes.append(Episode(
|
|
200
190
|
season = season_num,
|
|
201
191
|
episode = ep_num,
|
|
202
192
|
title = ep_name,
|
|
203
|
-
url =
|
|
193
|
+
url = self.fix_url(f"{self.main_url}/{ep_slug}")
|
|
204
194
|
))
|
|
205
195
|
|
|
206
196
|
return SeriesInfo(
|
|
@@ -209,8 +199,9 @@ class Dizilla(PluginBase):
|
|
|
209
199
|
title = title,
|
|
210
200
|
description = description,
|
|
211
201
|
tags = tags,
|
|
202
|
+
rating = str(rating) if rating else None,
|
|
212
203
|
year = str(year) if year else None,
|
|
213
|
-
episodes =
|
|
204
|
+
episodes = episodes,
|
|
214
205
|
actors = actors
|
|
215
206
|
)
|
|
216
207
|
|
|
@@ -108,18 +108,22 @@ class FilmBip(PluginBase):
|
|
|
108
108
|
tags = secici.select_all_text("div.series-profile-type.tv-show-profile-type a")
|
|
109
109
|
|
|
110
110
|
# XPath yerine regex kullanarak yıl, süre vs. çıkarma
|
|
111
|
-
year = secici.regex_first(r
|
|
111
|
+
year = secici.regex_first(r"(?is)Yap\u0131m y\u0131l\u0131.*?<p[^>]*>(.*?)<\/p>")
|
|
112
|
+
if not year:
|
|
113
|
+
# Fallback: Başlığın sonundaki parantezli yılı yakala
|
|
114
|
+
year = secici.regex_first(r"\((\d{4})\)", title)
|
|
112
115
|
|
|
113
|
-
|
|
116
|
+
duration_raw = secici.regex_first(r"(?is)S\u00fcre.*?<p[^>]*>(.*?)<\/p>")
|
|
117
|
+
duration = secici.regex_first(r"(\d+)", duration_raw) if duration_raw else None
|
|
114
118
|
|
|
115
|
-
rating = secici.regex_first(r
|
|
119
|
+
rating = secici.regex_first(r"(?is)IMDB Puan\u0131.*?<span[^>]*>(.*?)<\/span>")
|
|
116
120
|
|
|
117
121
|
actors = [img.attrs.get("alt") for img in secici.select("div.series-profile-cast ul li a img") if img.attrs.get("alt")]
|
|
118
122
|
|
|
119
123
|
return MovieInfo(
|
|
120
124
|
url = url,
|
|
121
125
|
poster = self.fix_url(poster) if poster else None,
|
|
122
|
-
title =
|
|
126
|
+
title = HTMLHelper(title).regex_replace(r"\(\d{4}\)", "").strip() if title else "",
|
|
123
127
|
description = description,
|
|
124
128
|
tags = tags,
|
|
125
129
|
year = year,
|
|
@@ -91,7 +91,7 @@ class FilmMakinesi(PluginBase):
|
|
|
91
91
|
year = secici.select_text("span.date a") or ""
|
|
92
92
|
|
|
93
93
|
actors = secici.select_all_text("div.cast-name")
|
|
94
|
-
tags = secici.
|
|
94
|
+
tags = [a.text(strip=True) for a in secici.select("div.type a") if "/tur/" in (a.attrs.get("href") or "")]
|
|
95
95
|
|
|
96
96
|
duration = None
|
|
97
97
|
duration_text = secici.select_text("div.time") or None
|
|
@@ -93,19 +93,13 @@ class FullHDFilmizlesene(PluginBase):
|
|
|
93
93
|
|
|
94
94
|
tags = secici.select_all_text("a[rel='category tag']")
|
|
95
95
|
|
|
96
|
-
# Rating:
|
|
97
|
-
rating_text = secici.select_text("div.puanx-puan") or
|
|
98
|
-
rating =
|
|
99
|
-
if rating_text:
|
|
100
|
-
parts = rating_text.split()
|
|
101
|
-
rating = parts[-1] if parts else None
|
|
96
|
+
# Rating: regex ile sayısal değeri yakala
|
|
97
|
+
rating_text = secici.select_text("div.puanx-puan") or ""
|
|
98
|
+
rating = secici.regex_first(r"(\d+\.\d+|\d+)", rating_text)
|
|
102
99
|
|
|
103
100
|
# Year: ilk yıl formatında değer
|
|
104
|
-
year_text = secici.select_text("div.dd a.category") or
|
|
105
|
-
year =
|
|
106
|
-
if year_text:
|
|
107
|
-
parts = year_text.split()
|
|
108
|
-
year = parts[0] if parts else None
|
|
101
|
+
year_text = secici.select_text("div.dd a.category") or ""
|
|
102
|
+
year = secici.regex_first(r"(\d{4})", year_text)
|
|
109
103
|
|
|
110
104
|
# Actors: nth-child yerine tüm li'leri alıp 2. index
|
|
111
105
|
lis = secici.select("div.film-info ul li")
|
|
@@ -113,11 +107,8 @@ class FullHDFilmizlesene(PluginBase):
|
|
|
113
107
|
if len(lis) >= 2:
|
|
114
108
|
actors = secici.select_all_text("a > span", lis[1])
|
|
115
109
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if duration_text:
|
|
119
|
-
duration_parts = duration_text.split()
|
|
120
|
-
duration = duration_parts[0] if duration_parts else "0"
|
|
110
|
+
# Duration: regex ile yakala (örn: 201 dk)
|
|
111
|
+
duration = secici.regex_first(r"(\d+)\s*(?:dk|dakika)", html_text)
|
|
121
112
|
|
|
122
113
|
return MovieInfo(
|
|
123
114
|
url = url,
|
|
@@ -113,6 +113,10 @@ class JetFilmizle(PluginBase):
|
|
|
113
113
|
year = secici.extract_year("div.yap")
|
|
114
114
|
|
|
115
115
|
actors = secici.select_all_text("div[itemprop='actor'] a span")
|
|
116
|
+
if not actors: # Fallback to img alt
|
|
117
|
+
actors = [img.attrs.get("alt") for img in secici.select("div.oyuncular div.oyuncu img") if img.attrs.get("alt")]
|
|
118
|
+
|
|
119
|
+
duration = secici.regex_first(r"(\d+)\s*dk", istek.text)
|
|
116
120
|
|
|
117
121
|
return MovieInfo(
|
|
118
122
|
url = url,
|
|
@@ -122,7 +126,8 @@ class JetFilmizle(PluginBase):
|
|
|
122
126
|
tags = tags,
|
|
123
127
|
rating = rating,
|
|
124
128
|
year = year,
|
|
125
|
-
actors = actors
|
|
129
|
+
actors = actors,
|
|
130
|
+
duration = int(duration) if duration else None
|
|
126
131
|
)
|
|
127
132
|
|
|
128
133
|
async def load_links(self, url: str) -> list[ExtractResult]:
|
|
@@ -91,7 +91,8 @@ class KultFilmler(PluginBase):
|
|
|
91
91
|
time_text = secici.select_text("li.time span")
|
|
92
92
|
duration = secici.regex_first(r"(\d+)", time_text) if time_text else None
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
rating_text = secici.select_text("div.imdb-count")
|
|
95
|
+
rating = secici.regex_first(r"(\d+\.\d+|\d+)", rating_text) if rating_text else None
|
|
95
96
|
|
|
96
97
|
actors = [a.text(strip=True) for a in secici.select("div.actors a") if a.text(strip=True)]
|
|
97
98
|
|
|
@@ -96,27 +96,49 @@ class RecTV(PluginBase):
|
|
|
96
96
|
|
|
97
97
|
episodes.append(ep_model)
|
|
98
98
|
|
|
99
|
+
# Süreyi dakikaya çevir (Örn: "1h 59min")
|
|
100
|
+
duration_raw = veri.get("duration")
|
|
101
|
+
duration = None
|
|
102
|
+
if duration_raw:
|
|
103
|
+
try:
|
|
104
|
+
h = int(HTMLHelper(duration_raw).regex_first(r"(\d+)h") or 0)
|
|
105
|
+
m = int(HTMLHelper(duration_raw).regex_first(r"(\d+)min") or 0)
|
|
106
|
+
duration = h * 60 + m
|
|
107
|
+
except: pass
|
|
108
|
+
|
|
99
109
|
return SeriesInfo(
|
|
100
110
|
url = url,
|
|
101
111
|
poster = self.fix_url(veri.get("image")),
|
|
102
112
|
title = veri.get("title"),
|
|
103
113
|
description = veri.get("description"),
|
|
104
114
|
tags = [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
|
|
105
|
-
rating = veri.get("imdb") or veri.get("rating"),
|
|
106
|
-
year = veri.get("year"),
|
|
115
|
+
rating = str(veri.get("imdb") or veri.get("rating") or ""),
|
|
116
|
+
year = str(veri.get("year") or ""),
|
|
107
117
|
actors = [],
|
|
118
|
+
duration = duration,
|
|
108
119
|
episodes = episodes
|
|
109
120
|
)
|
|
110
121
|
case _:
|
|
122
|
+
# Süreyi dakikaya çevir
|
|
123
|
+
duration_raw = veri.get("duration")
|
|
124
|
+
duration = None
|
|
125
|
+
if duration_raw:
|
|
126
|
+
try:
|
|
127
|
+
h = int(HTMLHelper(duration_raw).regex_first(r"(\d+)h") or 0)
|
|
128
|
+
m = int(HTMLHelper(duration_raw).regex_first(r"(\d+)min") or 0)
|
|
129
|
+
duration = h * 60 + m
|
|
130
|
+
except: pass
|
|
131
|
+
|
|
111
132
|
return MovieInfo(
|
|
112
133
|
url = url,
|
|
113
134
|
poster = self.fix_url(veri.get("image")),
|
|
114
135
|
title = veri.get("title"),
|
|
115
136
|
description = veri.get("description"),
|
|
116
137
|
tags = [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
|
|
117
|
-
rating = veri.get("imdb") or veri.get("rating"),
|
|
118
|
-
year = veri.get("year"),
|
|
119
|
-
actors = []
|
|
138
|
+
rating = str(veri.get("imdb") or veri.get("rating") or ""),
|
|
139
|
+
year = str(veri.get("year") or ""),
|
|
140
|
+
actors = [],
|
|
141
|
+
duration = duration
|
|
120
142
|
)
|
|
121
143
|
|
|
122
144
|
async def load_links(self, url: str) -> list[ExtractResult]:
|
|
@@ -84,89 +84,87 @@ class RoketDizi(PluginBase):
|
|
|
84
84
|
except Exception:
|
|
85
85
|
return []
|
|
86
86
|
|
|
87
|
-
async def load_item(self, url: str) -> SeriesInfo:
|
|
88
|
-
# Note: Handling both Movie and Series logic in one, returning SeriesInfo generally or MovieInfo
|
|
87
|
+
async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
|
|
89
88
|
resp = await self.httpx.get(url)
|
|
90
89
|
sel = HTMLHelper(resp.text)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
poster = sel.select_attr("div.w-full.page-top img", "src")
|
|
96
|
-
|
|
97
|
-
description = sel.select_text("div.mt-2.text-sm")
|
|
98
|
-
|
|
99
|
-
# Tags - genre bilgileri (Detaylar bölümünde)
|
|
100
|
-
tags = []
|
|
101
|
-
genre_text = sel.select_text("h3.text-white.opacity-90")
|
|
102
|
-
if genre_text:
|
|
103
|
-
tags = [t.strip() for t in genre_text.split(",")]
|
|
104
|
-
|
|
105
|
-
# Rating
|
|
106
|
-
rating = sel.select_text("span.text-white.text-sm.font-bold")
|
|
107
|
-
|
|
108
|
-
# Year ve Actors - Detaylar (Details) bölümünden
|
|
109
|
-
year = None
|
|
110
|
-
actors = []
|
|
90
|
+
|
|
91
|
+
next_data_text = sel.select_text("script#__NEXT_DATA__")
|
|
92
|
+
if not next_data_text:
|
|
93
|
+
return SeriesInfo(url=url, title=sel.select_text("h1") or "Bilinmeyen")
|
|
111
94
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
value = sel.select_text("span.text-sm.opacity-90", item)
|
|
95
|
+
try:
|
|
96
|
+
next_data = json.loads(next_data_text)
|
|
97
|
+
secure_data_raw = next_data["props"]["pageProps"]["secureData"]
|
|
98
|
+
secure_data = json.loads(base64.b64decode(secure_data_raw).decode('utf-8'))
|
|
117
99
|
|
|
118
|
-
|
|
119
|
-
|
|
100
|
+
content_item = secure_data.get("contentItem", {})
|
|
101
|
+
content = secure_data.get("content", {}).get("result", {})
|
|
120
102
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
103
|
+
title = content_item.get("original_title") or content_item.get("culture_title")
|
|
104
|
+
poster = content_item.get("poster_url") or content_item.get("face_url")
|
|
105
|
+
description = content_item.get("description")
|
|
106
|
+
rating = str(content_item.get("imdb_point") or "")
|
|
107
|
+
year = str(content_item.get("release_year") or "")
|
|
108
|
+
tags = content_item.get("categories", "").split(",")
|
|
109
|
+
|
|
110
|
+
# Actors extraction from getSerieCastsById or getMovieCastsById
|
|
111
|
+
actors = []
|
|
112
|
+
casts_data = content.get("getSerieCastsById") or content.get("getMovieCastsById")
|
|
113
|
+
if casts_data and casts_data.get("result"):
|
|
114
|
+
actors = [cast.get("name") for cast in casts_data["result"] if cast.get("name")]
|
|
115
|
+
|
|
116
|
+
# Episodes extraction
|
|
117
|
+
episodes = []
|
|
118
|
+
if "Series" in str(content.get("FindedType")):
|
|
119
|
+
# Check for episodes in SecureData -> RelatedResults -> getEpisodeSources (this might be for the current episode)
|
|
120
|
+
# Usually full episode list isn't in secureData, but we can get it from HTML or another API
|
|
121
|
+
# However, many times Next.js pages have them in props
|
|
122
|
+
# Let's fallback to the previous regex method for episodes if not in JSON
|
|
123
|
+
all_urls = HTMLHelper(resp.text).regex_all(r'"url":"([^"]*)"')
|
|
124
|
+
episodes_dict = {}
|
|
125
|
+
for u in all_urls:
|
|
126
|
+
if "bolum" in u and u not in episodes_dict:
|
|
127
|
+
s_match = HTMLHelper(u).regex_first(r'/sezon-(\d+)')
|
|
128
|
+
e_match = HTMLHelper(u).regex_first(r'/bolum-(\d+)')
|
|
129
|
+
s_val = int(s_match) if s_match else 1
|
|
130
|
+
e_val = int(e_match) if e_match else 1
|
|
131
|
+
episodes_dict[(s_val, e_val)] = Episode(
|
|
132
|
+
season = s_val,
|
|
133
|
+
episode = e_val,
|
|
134
|
+
title = f"{s_val}. Sezon {e_val}. Bölüm",
|
|
135
|
+
url = self.fix_url(u)
|
|
136
|
+
)
|
|
137
|
+
episodes = [episodes_dict[key] for key in sorted(episodes_dict.keys())]
|
|
138
|
+
|
|
139
|
+
return SeriesInfo(
|
|
140
|
+
url = url,
|
|
141
|
+
poster = self.fix_url(poster) if poster else None,
|
|
142
|
+
title = self.clean_title(title),
|
|
143
|
+
description = description,
|
|
144
|
+
tags = tags,
|
|
145
|
+
rating = rating,
|
|
146
|
+
year = year,
|
|
147
|
+
actors = actors,
|
|
148
|
+
episodes = episodes
|
|
149
|
+
)
|
|
150
|
+
else:
|
|
151
|
+
return MovieInfo(
|
|
152
|
+
url = url,
|
|
153
|
+
poster = self.fix_url(poster) if poster else None,
|
|
154
|
+
title = self.clean_title(title),
|
|
155
|
+
description = description,
|
|
156
|
+
tags = tags,
|
|
157
|
+
rating = rating,
|
|
158
|
+
year = year,
|
|
159
|
+
actors = actors
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
except Exception:
|
|
163
|
+
# Fallback to simple extraction if JSON parsing fails
|
|
164
|
+
return SeriesInfo(
|
|
165
|
+
url = url,
|
|
166
|
+
title = self.clean_title(sel.select_text("h1")) or "Bilinmeyen"
|
|
167
|
+
)
|
|
170
168
|
|
|
171
169
|
async def load_links(self, url: str) -> list[ExtractResult]:
|
|
172
170
|
resp = await self.httpx.get(url)
|