KekikStream 2.2.9__py3-none-any.whl → 2.3.9__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.
Potentially problematic release.
This version of KekikStream might be problematic. Click here for more details.
- KekikStream/Core/HTMLHelper.py +134 -0
- KekikStream/Core/Plugin/PluginBase.py +22 -4
- KekikStream/Core/Plugin/PluginLoader.py +3 -2
- KekikStream/Core/Plugin/PluginManager.py +2 -2
- KekikStream/Core/__init__.py +2 -0
- KekikStream/Extractors/CloseLoad.py +12 -13
- KekikStream/Extractors/ContentX.py +33 -31
- KekikStream/Extractors/DonilasPlay.py +10 -10
- KekikStream/Extractors/DzenRu.py +3 -3
- KekikStream/Extractors/ExPlay.py +10 -10
- KekikStream/Extractors/Filemoon.py +11 -16
- KekikStream/Extractors/JetTv.py +4 -4
- KekikStream/Extractors/MixPlayHD.py +10 -11
- KekikStream/Extractors/MolyStream.py +16 -9
- KekikStream/Extractors/Odnoklassniki.py +4 -4
- KekikStream/Extractors/PeaceMakerst.py +3 -3
- KekikStream/Extractors/PixelDrain.py +6 -5
- KekikStream/Extractors/PlayerFilmIzle.py +6 -10
- KekikStream/Extractors/RapidVid.py +8 -7
- KekikStream/Extractors/SetPlay.py +10 -10
- KekikStream/Extractors/SetPrime.py +3 -6
- KekikStream/Extractors/SibNet.py +4 -5
- KekikStream/Extractors/Sobreatsesuyp.py +5 -5
- KekikStream/Extractors/TRsTX.py +5 -5
- KekikStream/Extractors/TurboImgz.py +3 -4
- KekikStream/Extractors/TurkeyPlayer.py +5 -5
- KekikStream/Extractors/VidHide.py +4 -7
- KekikStream/Extractors/VidMoly.py +37 -25
- KekikStream/Extractors/VidMoxy.py +8 -9
- KekikStream/Extractors/VidPapi.py +5 -7
- KekikStream/Extractors/VideoSeyred.py +3 -3
- KekikStream/Plugins/BelgeselX.py +40 -51
- KekikStream/Plugins/DiziBox.py +53 -81
- KekikStream/Plugins/DiziPal.py +50 -72
- KekikStream/Plugins/DiziYou.py +96 -83
- KekikStream/Plugins/Dizilla.py +95 -107
- KekikStream/Plugins/FilmBip.py +29 -50
- KekikStream/Plugins/FilmMakinesi.py +84 -46
- KekikStream/Plugins/FilmModu.py +27 -41
- KekikStream/Plugins/FullHDFilm.py +57 -62
- KekikStream/Plugins/FullHDFilmizlesene.py +32 -57
- KekikStream/Plugins/HDFilmCehennemi.py +51 -65
- KekikStream/Plugins/JetFilmizle.py +38 -51
- KekikStream/Plugins/KultFilmler.py +43 -67
- KekikStream/Plugins/RecTV.py +34 -9
- KekikStream/Plugins/RoketDizi.py +89 -111
- KekikStream/Plugins/SelcukFlix.py +102 -93
- KekikStream/Plugins/SetFilmIzle.py +65 -75
- KekikStream/Plugins/SezonlukDizi.py +47 -65
- KekikStream/Plugins/Sinefy.py +70 -70
- KekikStream/Plugins/SinemaCX.py +31 -55
- KekikStream/Plugins/Sinezy.py +27 -54
- KekikStream/Plugins/SuperFilmGeldi.py +25 -44
- KekikStream/Plugins/UgurFilm.py +23 -48
- KekikStream/Plugins/YabanciDizi.py +285 -0
- {kekikstream-2.2.9.dist-info → kekikstream-2.3.9.dist-info}/METADATA +1 -1
- kekikstream-2.3.9.dist-info/RECORD +84 -0
- kekikstream-2.2.9.dist-info/RECORD +0 -82
- {kekikstream-2.2.9.dist-info → kekikstream-2.3.9.dist-info}/WHEEL +0 -0
- {kekikstream-2.2.9.dist-info → kekikstream-2.3.9.dist-info}/entry_points.txt +0 -0
- {kekikstream-2.2.9.dist-info → kekikstream-2.3.9.dist-info}/licenses/LICENSE +0 -0
- {kekikstream-2.2.9.dist-info → kekikstream-2.3.9.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
2
|
|
|
3
|
-
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult, Subtitle
|
|
4
|
-
|
|
5
|
-
import re, base64
|
|
3
|
+
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult, Subtitle, HTMLHelper
|
|
4
|
+
import base64
|
|
6
5
|
|
|
7
6
|
class KultFilmler(PluginBase):
|
|
8
7
|
name = "KultFilmler"
|
|
@@ -38,16 +37,13 @@ class KultFilmler(PluginBase):
|
|
|
38
37
|
|
|
39
38
|
async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
|
|
40
39
|
istek = await self.httpx.get(url)
|
|
41
|
-
secici =
|
|
40
|
+
secici = HTMLHelper(istek.text)
|
|
42
41
|
|
|
43
42
|
results = []
|
|
44
|
-
for veri in secici.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
title = img_el.attrs.get("alt") if img_el else None
|
|
49
|
-
href = link_el.attrs.get("href") if link_el else None
|
|
50
|
-
poster = img_el.attrs.get("src") if img_el else None
|
|
43
|
+
for veri in secici.select("div.col-md-12 div.movie-box"):
|
|
44
|
+
title = secici.select_attr("div.img img", "alt", veri)
|
|
45
|
+
href = secici.select_attr("a", "href", veri)
|
|
46
|
+
poster = secici.select_attr("div.img img", "src", veri)
|
|
51
47
|
|
|
52
48
|
if title and href:
|
|
53
49
|
results.append(MainPageResult(
|
|
@@ -61,16 +57,13 @@ class KultFilmler(PluginBase):
|
|
|
61
57
|
|
|
62
58
|
async def search(self, query: str) -> list[SearchResult]:
|
|
63
59
|
istek = await self.httpx.get(f"{self.main_url}?s={query}")
|
|
64
|
-
secici =
|
|
60
|
+
secici = HTMLHelper(istek.text)
|
|
65
61
|
|
|
66
62
|
results = []
|
|
67
|
-
for veri in secici.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
title = img_el.attrs.get("alt") if img_el else None
|
|
72
|
-
href = link_el.attrs.get("href") if link_el else None
|
|
73
|
-
poster = img_el.attrs.get("src") if img_el else None
|
|
63
|
+
for veri in secici.select("div.movie-box"):
|
|
64
|
+
title = secici.select_attr("div.img img", "alt", veri)
|
|
65
|
+
href = secici.select_attr("a", "href", veri)
|
|
66
|
+
poster = secici.select_attr("div.img img", "src", veri)
|
|
74
67
|
|
|
75
68
|
if title and href:
|
|
76
69
|
results.append(SearchResult(
|
|
@@ -83,58 +76,44 @@ class KultFilmler(PluginBase):
|
|
|
83
76
|
|
|
84
77
|
async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
|
|
85
78
|
istek = await self.httpx.get(url)
|
|
86
|
-
secici =
|
|
87
|
-
|
|
88
|
-
film_img = secici.css_first("div.film-bilgileri img")
|
|
89
|
-
og_title = secici.css_first("[property='og:title']")
|
|
90
|
-
og_image = secici.css_first("[property='og:image']")
|
|
79
|
+
secici = HTMLHelper(istek.text)
|
|
91
80
|
|
|
92
|
-
title =
|
|
93
|
-
poster = self.fix_url(
|
|
81
|
+
title = secici.select_attr("div.film-bilgileri img", "alt") or secici.select_attr("[property='og:title']", "content")
|
|
82
|
+
poster = self.fix_url(secici.select_attr("[property='og:image']", "content")) if secici.select_attr("[property='og:image']", "content") else None
|
|
94
83
|
|
|
95
|
-
|
|
96
|
-
description = desc_el.text(strip=True) if desc_el else None
|
|
84
|
+
description = secici.select_text("div.description")
|
|
97
85
|
|
|
98
|
-
tags = [a.text(strip=True) for a in secici.
|
|
86
|
+
tags = [a.text(strip=True) for a in secici.select("ul.post-categories a") if a.text(strip=True)]
|
|
99
87
|
|
|
100
88
|
# HTML analizine göre güncellenen alanlar
|
|
101
|
-
|
|
102
|
-
year = year_el.text(strip=True) if year_el else None
|
|
89
|
+
year = secici.select_text("li.release span a")
|
|
103
90
|
|
|
104
|
-
|
|
105
|
-
duration = None
|
|
106
|
-
if time_el:
|
|
107
|
-
time_text = time_el.text(strip=True)
|
|
108
|
-
dur_match = re.search(r"(\d+)", time_text)
|
|
109
|
-
duration = dur_match.group(1) if dur_match else None
|
|
91
|
+
time_text = secici.select_text("li.time span")
|
|
92
|
+
duration = secici.regex_first(r"(\d+)", time_text) if time_text else None
|
|
110
93
|
|
|
111
|
-
|
|
112
|
-
rating
|
|
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
|
|
113
96
|
|
|
114
|
-
actors = [a.text(strip=True) for a in secici.
|
|
97
|
+
actors = [a.text(strip=True) for a in secici.select("div.actors a") if a.text(strip=True)]
|
|
115
98
|
|
|
116
99
|
# Dizi mi kontrol et
|
|
117
100
|
if "/dizi/" in url:
|
|
118
101
|
episodes = []
|
|
119
|
-
for bolum in secici.
|
|
120
|
-
|
|
121
|
-
ep_href = name_link.attrs.get("href") if name_link else None
|
|
102
|
+
for bolum in secici.select("div.episode-box"):
|
|
103
|
+
ep_href = secici.select_attr("div.name a", "href", bolum)
|
|
122
104
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
ep_b_el = bolum.css_first("span.episodetitle b")
|
|
127
|
-
ep_detail = ep_b_el.text(strip=True) if ep_b_el else ""
|
|
105
|
+
ssn_detail = secici.select_text("span.episodetitle", bolum) or ""
|
|
106
|
+
ep_detail = secici.select_text("span.episodetitle b", bolum) or ""
|
|
128
107
|
|
|
129
108
|
ep_name = f"{ssn_detail} - {ep_detail}"
|
|
130
109
|
|
|
131
110
|
if ep_href:
|
|
132
|
-
ep_season =
|
|
133
|
-
ep_episode =
|
|
111
|
+
ep_season = secici.regex_first(r"(\d+)\.", ssn_detail)
|
|
112
|
+
ep_episode = secici.regex_first(r"(\d+)\.", ep_detail)
|
|
134
113
|
|
|
135
114
|
episodes.append(Episode(
|
|
136
|
-
season = int(ep_season
|
|
137
|
-
episode = int(ep_episode
|
|
115
|
+
season = int(ep_season) if ep_season else 1,
|
|
116
|
+
episode = int(ep_episode) if ep_episode else 1,
|
|
138
117
|
title = ep_name.strip(" -"),
|
|
139
118
|
url = self.fix_url(ep_href),
|
|
140
119
|
))
|
|
@@ -165,12 +144,10 @@ class KultFilmler(PluginBase):
|
|
|
165
144
|
|
|
166
145
|
def _get_iframe(self, source_code: str) -> str:
|
|
167
146
|
"""Base64 kodlu iframe'i çözümle"""
|
|
168
|
-
|
|
169
|
-
if not
|
|
147
|
+
atob = HTMLHelper(source_code).regex_first(r"PHA\+[0-9a-zA-Z+/=]*")
|
|
148
|
+
if not atob:
|
|
170
149
|
return ""
|
|
171
150
|
|
|
172
|
-
atob = atob_match.group()
|
|
173
|
-
|
|
174
151
|
# Padding düzelt
|
|
175
152
|
padding = 4 - len(atob) % 4
|
|
176
153
|
if padding < 4:
|
|
@@ -178,20 +155,19 @@ class KultFilmler(PluginBase):
|
|
|
178
155
|
|
|
179
156
|
try:
|
|
180
157
|
decoded = base64.b64decode(atob).decode("utf-8")
|
|
181
|
-
secici =
|
|
182
|
-
|
|
183
|
-
return self.fix_url(
|
|
158
|
+
secici = HTMLHelper(decoded)
|
|
159
|
+
iframe_src = secici.select_attr("iframe", "src")
|
|
160
|
+
return self.fix_url(iframe_src) if iframe_src else ""
|
|
184
161
|
except Exception:
|
|
185
162
|
return ""
|
|
186
163
|
|
|
187
164
|
def _extract_subtitle_url(self, source_code: str) -> str | None:
|
|
188
165
|
"""Altyazı URL'sini çıkar"""
|
|
189
|
-
|
|
190
|
-
return match[1] if match else None
|
|
166
|
+
return HTMLHelper(source_code).regex_first(r"(https?://[^\s\"]+\.srt)")
|
|
191
167
|
|
|
192
168
|
async def load_links(self, url: str) -> list[ExtractResult]:
|
|
193
169
|
istek = await self.httpx.get(url)
|
|
194
|
-
secici =
|
|
170
|
+
secici = HTMLHelper(istek.text)
|
|
195
171
|
|
|
196
172
|
iframes = set()
|
|
197
173
|
|
|
@@ -201,9 +177,9 @@ class KultFilmler(PluginBase):
|
|
|
201
177
|
iframes.add(main_frame)
|
|
202
178
|
|
|
203
179
|
# Alternatif player'lar
|
|
204
|
-
for player in secici.
|
|
205
|
-
|
|
206
|
-
alt_iframe = self.fix_url(
|
|
180
|
+
for player in secici.select("div.container#player"):
|
|
181
|
+
iframe_src = secici.select_attr("iframe", "src", player)
|
|
182
|
+
alt_iframe = self.fix_url(iframe_src) if iframe_src else None
|
|
207
183
|
if alt_iframe:
|
|
208
184
|
alt_istek = await self.httpx.get(alt_iframe)
|
|
209
185
|
alt_frame = self._get_iframe(alt_istek.text)
|
|
@@ -222,12 +198,12 @@ class KultFilmler(PluginBase):
|
|
|
222
198
|
"Sec-Fetch-Dest" : "iframe"
|
|
223
199
|
}
|
|
224
200
|
iframe_istek = await self.httpx.get(iframe, headers=headers)
|
|
225
|
-
m3u_match =
|
|
201
|
+
m3u_match = HTMLHelper(iframe_istek.text).regex_first(r'file:"([^"]+)"')
|
|
226
202
|
|
|
227
203
|
if m3u_match:
|
|
228
204
|
results.append(ExtractResult(
|
|
229
205
|
name = "VidMoly",
|
|
230
|
-
url = m3u_match
|
|
206
|
+
url = m3u_match,
|
|
231
207
|
referer = self.main_url,
|
|
232
208
|
subtitles = []
|
|
233
209
|
))
|
KekikStream/Plugins/RecTV.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
2
|
|
|
3
|
-
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, Episode, SeriesInfo, ExtractResult
|
|
3
|
+
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, Episode, SeriesInfo, ExtractResult, HTMLHelper
|
|
4
4
|
from json import dumps, loads
|
|
5
|
-
import re
|
|
6
5
|
|
|
7
6
|
class RecTV(PluginBase):
|
|
8
7
|
name = "RecTV"
|
|
@@ -84,36 +83,62 @@ class RecTV(PluginBase):
|
|
|
84
83
|
"is_episode" : True
|
|
85
84
|
}
|
|
86
85
|
|
|
86
|
+
# Extract season/episode numbers using helper
|
|
87
|
+
s1, _ = HTMLHelper.extract_season_episode(season.get("title") or "")
|
|
88
|
+
_, e2 = HTMLHelper.extract_season_episode(episode.get("title") or "")
|
|
89
|
+
|
|
87
90
|
ep_model = Episode(
|
|
88
|
-
season =
|
|
89
|
-
episode =
|
|
91
|
+
season = s1 or 1,
|
|
92
|
+
episode = e2 or 1,
|
|
90
93
|
title = episode.get("title"),
|
|
91
94
|
url = dumps(ep_data),
|
|
92
95
|
)
|
|
93
96
|
|
|
94
97
|
episodes.append(ep_model)
|
|
95
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
|
+
|
|
96
109
|
return SeriesInfo(
|
|
97
110
|
url = url,
|
|
98
111
|
poster = self.fix_url(veri.get("image")),
|
|
99
112
|
title = veri.get("title"),
|
|
100
113
|
description = veri.get("description"),
|
|
101
114
|
tags = [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
|
|
102
|
-
rating = veri.get("imdb") or veri.get("rating"),
|
|
103
|
-
year = veri.get("year"),
|
|
115
|
+
rating = str(veri.get("imdb") or veri.get("rating") or ""),
|
|
116
|
+
year = str(veri.get("year") or ""),
|
|
104
117
|
actors = [],
|
|
118
|
+
duration = duration,
|
|
105
119
|
episodes = episodes
|
|
106
120
|
)
|
|
107
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
|
+
|
|
108
132
|
return MovieInfo(
|
|
109
133
|
url = url,
|
|
110
134
|
poster = self.fix_url(veri.get("image")),
|
|
111
135
|
title = veri.get("title"),
|
|
112
136
|
description = veri.get("description"),
|
|
113
137
|
tags = [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
|
|
114
|
-
rating = veri.get("imdb") or veri.get("rating"),
|
|
115
|
-
year = veri.get("year"),
|
|
116
|
-
actors = []
|
|
138
|
+
rating = str(veri.get("imdb") or veri.get("rating") or ""),
|
|
139
|
+
year = str(veri.get("year") or ""),
|
|
140
|
+
actors = [],
|
|
141
|
+
duration = duration
|
|
117
142
|
)
|
|
118
143
|
|
|
119
144
|
async def load_links(self, url: str) -> list[ExtractResult]:
|
KekikStream/Plugins/RoketDizi.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
2
|
|
|
3
|
-
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult, MovieInfo
|
|
4
|
-
|
|
5
|
-
import re, base64, json
|
|
3
|
+
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult, MovieInfo, HTMLHelper
|
|
4
|
+
import base64, json
|
|
6
5
|
|
|
7
6
|
class RoketDizi(PluginBase):
|
|
8
7
|
name = "RoketDizi"
|
|
@@ -24,19 +23,15 @@ class RoketDizi(PluginBase):
|
|
|
24
23
|
|
|
25
24
|
async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
|
|
26
25
|
istek = await self.httpx.get(f"{url}?&page={page}")
|
|
27
|
-
secici =
|
|
26
|
+
secici = HTMLHelper(istek.text)
|
|
28
27
|
|
|
29
28
|
results = []
|
|
30
29
|
|
|
31
30
|
# Use div.new-added-list to find the container, then get items
|
|
32
|
-
for item in secici.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
title = title_el.text(strip=True) if title_el else None
|
|
38
|
-
href = link_el.attrs.get("href") if link_el else None
|
|
39
|
-
poster = img_el.attrs.get("src") if img_el else None
|
|
31
|
+
for item in secici.select("div.new-added-list > span"):
|
|
32
|
+
title = secici.select_text("span.line-clamp-1", item)
|
|
33
|
+
href = secici.select_attr("a", "href", item)
|
|
34
|
+
poster = secici.select_attr("img", "src", item)
|
|
40
35
|
|
|
41
36
|
if title and href:
|
|
42
37
|
results.append(MainPageResult(
|
|
@@ -89,108 +84,93 @@ class RoketDizi(PluginBase):
|
|
|
89
84
|
except Exception:
|
|
90
85
|
return []
|
|
91
86
|
|
|
92
|
-
async def load_item(self, url: str) -> SeriesInfo:
|
|
93
|
-
# Note: Handling both Movie and Series logic in one, returning SeriesInfo generally or MovieInfo
|
|
87
|
+
async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
|
|
94
88
|
resp = await self.httpx.get(url)
|
|
95
|
-
sel =
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
description = desc_el.text(strip=True) if desc_el else None
|
|
106
|
-
|
|
107
|
-
# Tags - genre bilgileri (Detaylar bölümünde)
|
|
108
|
-
tags = []
|
|
109
|
-
genre_el = sel.css_first("h3.text-white.opacity-90")
|
|
110
|
-
if genre_el:
|
|
111
|
-
genre_text = genre_el.text(strip=True)
|
|
112
|
-
if genre_text:
|
|
113
|
-
tags = [t.strip() for t in genre_text.split(",")]
|
|
114
|
-
|
|
115
|
-
# Rating
|
|
116
|
-
rating_el = sel.css_first("span.text-white.text-sm.font-bold")
|
|
117
|
-
rating = rating_el.text(strip=True) if rating_el else None
|
|
118
|
-
|
|
119
|
-
# Year ve Actors - Detaylar (Details) bölümünden
|
|
120
|
-
year = None
|
|
121
|
-
actors = []
|
|
122
|
-
|
|
123
|
-
# Detaylar bölümündeki tüm flex-col div'leri al
|
|
124
|
-
detail_items = sel.css("div.flex.flex-col")
|
|
125
|
-
for item in detail_items:
|
|
126
|
-
label_el = item.css_first("span.text-base")
|
|
127
|
-
value_el = item.css_first("span.text-sm.opacity-90")
|
|
89
|
+
sel = HTMLHelper(resp.text)
|
|
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")
|
|
94
|
+
|
|
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'))
|
|
128
99
|
|
|
129
|
-
|
|
130
|
-
|
|
100
|
+
content_item = secure_data.get("contentItem", {})
|
|
101
|
+
content = secure_data.get("content", {}).get("result", {})
|
|
131
102
|
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
+
)
|
|
184
168
|
|
|
185
169
|
async def load_links(self, url: str) -> list[ExtractResult]:
|
|
186
170
|
resp = await self.httpx.get(url)
|
|
187
|
-
sel =
|
|
171
|
+
sel = HTMLHelper(resp.text)
|
|
188
172
|
|
|
189
|
-
|
|
190
|
-
if not next_data_el:
|
|
191
|
-
return []
|
|
192
|
-
|
|
193
|
-
next_data = next_data_el.text(strip=True)
|
|
173
|
+
next_data = sel.select_text("script#__NEXT_DATA__")
|
|
194
174
|
if not next_data:
|
|
195
175
|
return []
|
|
196
176
|
|
|
@@ -208,11 +188,9 @@ class RoketDizi(PluginBase):
|
|
|
208
188
|
source_content = source.get("source_content", "")
|
|
209
189
|
|
|
210
190
|
# iframe URL'ini source_content'ten çıkar
|
|
211
|
-
|
|
212
|
-
if not
|
|
191
|
+
iframe_url = HTMLHelper(source_content).regex_first(r'<iframe[^>]*src=["\']([^"\']*)["\']')
|
|
192
|
+
if not iframe_url:
|
|
213
193
|
continue
|
|
214
|
-
|
|
215
|
-
iframe_url = iframe_match.group(1)
|
|
216
194
|
|
|
217
195
|
# Fix URL protocol
|
|
218
196
|
if not iframe_url.startswith("http"):
|