KekikStream 2.2.9__py3-none-any.whl → 2.5.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.
Potentially problematic release.
This version of KekikStream might be problematic. Click here for more details.
- KekikStream/Core/Extractor/ExtractorBase.py +3 -2
- KekikStream/Core/Extractor/ExtractorLoader.py +8 -14
- KekikStream/Core/HTMLHelper.py +205 -0
- KekikStream/Core/Plugin/PluginBase.py +48 -12
- KekikStream/Core/Plugin/PluginLoader.py +13 -14
- KekikStream/Core/Plugin/PluginManager.py +2 -2
- KekikStream/Core/Plugin/PluginModels.py +0 -3
- KekikStream/Core/__init__.py +2 -0
- KekikStream/Extractors/Abstream.py +27 -0
- KekikStream/Extractors/CloseLoad.py +31 -56
- KekikStream/Extractors/ContentX.py +28 -71
- KekikStream/Extractors/DonilasPlay.py +34 -78
- KekikStream/Extractors/DzenRu.py +11 -25
- KekikStream/Extractors/ExPlay.py +20 -38
- KekikStream/Extractors/Filemoon.py +23 -53
- KekikStream/Extractors/HDMomPlayer.py +30 -0
- KekikStream/Extractors/HDPlayerSystem.py +13 -31
- KekikStream/Extractors/HotStream.py +27 -0
- KekikStream/Extractors/JFVid.py +3 -24
- KekikStream/Extractors/JetTv.py +21 -34
- KekikStream/Extractors/JetV.py +55 -0
- KekikStream/Extractors/MailRu.py +11 -29
- KekikStream/Extractors/MixPlayHD.py +17 -31
- KekikStream/Extractors/MixTiger.py +17 -40
- KekikStream/Extractors/MolyStream.py +25 -22
- KekikStream/Extractors/Odnoklassniki.py +41 -105
- KekikStream/Extractors/PeaceMakerst.py +20 -47
- KekikStream/Extractors/PixelDrain.py +9 -16
- KekikStream/Extractors/PlayerFilmIzle.py +23 -46
- KekikStream/Extractors/RapidVid.py +23 -36
- KekikStream/Extractors/SetPlay.py +19 -44
- KekikStream/Extractors/SetPrime.py +3 -6
- KekikStream/Extractors/SibNet.py +8 -19
- KekikStream/Extractors/Sobreatsesuyp.py +25 -47
- KekikStream/Extractors/TRsTX.py +25 -55
- KekikStream/Extractors/TurboImgz.py +8 -16
- KekikStream/Extractors/TurkeyPlayer.py +5 -5
- KekikStream/Extractors/VCTPlay.py +10 -28
- KekikStream/Extractors/Veev.py +145 -0
- KekikStream/Extractors/VidBiz.py +62 -0
- KekikStream/Extractors/VidHide.py +59 -34
- KekikStream/Extractors/VidMoly.py +67 -89
- KekikStream/Extractors/VidMoxy.py +17 -29
- KekikStream/Extractors/VidPapi.py +26 -58
- KekikStream/Extractors/VideoSeyred.py +21 -42
- KekikStream/Extractors/Videostr.py +58 -0
- KekikStream/Extractors/Vidoza.py +18 -0
- KekikStream/Extractors/Vtbe.py +38 -0
- KekikStream/Extractors/YTDLP.py +2 -2
- KekikStream/Extractors/YildizKisaFilm.py +13 -31
- KekikStream/Extractors/Zeus.py +61 -0
- KekikStream/Plugins/BelgeselX.py +108 -99
- KekikStream/Plugins/DiziBox.py +61 -106
- KekikStream/Plugins/DiziMom.py +179 -0
- KekikStream/Plugins/DiziPal.py +104 -192
- KekikStream/Plugins/DiziYou.py +66 -149
- KekikStream/Plugins/Dizilla.py +93 -126
- KekikStream/Plugins/FilmBip.py +102 -72
- KekikStream/Plugins/FilmEkseni.py +199 -0
- KekikStream/Plugins/FilmMakinesi.py +101 -64
- KekikStream/Plugins/FilmModu.py +35 -59
- KekikStream/Plugins/Filmatek.py +184 -0
- KekikStream/Plugins/FilmciBaba.py +155 -0
- KekikStream/Plugins/FullHDFilmizlesene.py +32 -78
- KekikStream/Plugins/HDFilm.py +243 -0
- KekikStream/Plugins/HDFilmCehennemi.py +261 -222
- KekikStream/Plugins/JetFilmizle.py +117 -98
- KekikStream/Plugins/KultFilmler.py +153 -143
- KekikStream/Plugins/RecTV.py +53 -49
- KekikStream/Plugins/RoketDizi.py +92 -123
- KekikStream/Plugins/SelcukFlix.py +86 -95
- KekikStream/Plugins/SetFilmIzle.py +105 -143
- KekikStream/Plugins/SezonlukDizi.py +106 -128
- KekikStream/Plugins/Sinefy.py +194 -166
- KekikStream/Plugins/SinemaCX.py +159 -113
- KekikStream/Plugins/Sinezy.py +44 -73
- KekikStream/Plugins/SuperFilmGeldi.py +28 -52
- KekikStream/Plugins/UgurFilm.py +94 -72
- KekikStream/Plugins/Watch32.py +160 -0
- KekikStream/Plugins/YabanciDizi.py +250 -0
- {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/METADATA +1 -1
- kekikstream-2.5.3.dist-info/RECORD +99 -0
- {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/WHEEL +1 -1
- KekikStream/Plugins/FullHDFilm.py +0 -254
- kekikstream-2.2.9.dist-info/RECORD +0 -82
- {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/entry_points.txt +0 -0
- {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/licenses/LICENSE +0 -0
- {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/top_level.txt +0 -0
KekikStream/Plugins/Sinefy.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
|
|
4
|
-
|
|
5
|
-
import re, json, urllib.parse
|
|
3
|
+
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, MovieInfo, ExtractResult, HTMLHelper
|
|
4
|
+
import json, contextlib, asyncio
|
|
6
5
|
|
|
7
6
|
class Sinefy(PluginBase):
|
|
8
7
|
name = "Sinefy"
|
|
@@ -43,18 +42,14 @@ class Sinefy(PluginBase):
|
|
|
43
42
|
else:
|
|
44
43
|
full_url = f"{url}&page={page}"
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
istek = await self.httpx.get(full_url)
|
|
46
|
+
secici = HTMLHelper(istek.text)
|
|
48
47
|
|
|
49
48
|
results = []
|
|
50
|
-
for item in
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
title = h2_el.text(strip=True) if h2_el else None
|
|
56
|
-
href = link_el.attrs.get("href") if link_el else None
|
|
57
|
-
poster = img_el.attrs.get("data-srcset") if img_el else None
|
|
49
|
+
for item in secici.select("div.poster-with-subject, div.dark-segment div.poster-md.poster"):
|
|
50
|
+
title = secici.select_text("h2", item)
|
|
51
|
+
href = secici.select_attr("a", "href", item)
|
|
52
|
+
poster = secici.select_attr("img", "data-srcset", item)
|
|
58
53
|
|
|
59
54
|
if poster:
|
|
60
55
|
poster = poster.split(",")[0].split(" ")[0]
|
|
@@ -64,7 +59,7 @@ class Sinefy(PluginBase):
|
|
|
64
59
|
category = category,
|
|
65
60
|
title = title,
|
|
66
61
|
url = self.fix_url(href),
|
|
67
|
-
poster = self.fix_url(poster)
|
|
62
|
+
poster = self.fix_url(poster)
|
|
68
63
|
))
|
|
69
64
|
|
|
70
65
|
return results
|
|
@@ -74,197 +69,230 @@ class Sinefy(PluginBase):
|
|
|
74
69
|
c_key = "ca1d4a53d0f4761a949b85e51e18f096"
|
|
75
70
|
c_value = "MTc0NzI2OTAwMDU3ZTEwYmZjMDViNWFmOWIwZDViODg0MjU4MjA1ZmYxOThmZTYwMDdjMWQzMzliNzY5NzFlZmViMzRhMGVmNjgwODU3MGIyZA=="
|
|
76
71
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
cke_el = sel.css_first("input[name='cKey']")
|
|
82
|
-
cval_el = sel.css_first("input[name='cValue']")
|
|
72
|
+
with contextlib.suppress(Exception):
|
|
73
|
+
istek = await self.httpx.get(self.main_url)
|
|
74
|
+
secici = HTMLHelper(istek.text)
|
|
83
75
|
|
|
84
|
-
cke
|
|
85
|
-
cval =
|
|
76
|
+
cke = secici.select_attr("input[name='cKey']", "value")
|
|
77
|
+
cval = secici.select_attr("input[name='cValue']", "value")
|
|
86
78
|
|
|
87
79
|
if cke and cval:
|
|
88
80
|
c_key = cke
|
|
89
81
|
c_value = cval
|
|
90
82
|
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
response = await self.httpx.post(
|
|
84
|
+
url = f"{self.main_url}/bg/searchcontent",
|
|
85
|
+
headers = {
|
|
86
|
+
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
|
|
87
|
+
"Accept" : "application/json, text/javascript, */*; q=0.01",
|
|
88
|
+
"X-Requested-With" : "XMLHttpRequest",
|
|
89
|
+
"Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8"
|
|
90
|
+
},
|
|
91
|
+
data = {
|
|
92
|
+
"cKey" : c_key,
|
|
93
|
+
"cValue" : c_value,
|
|
94
|
+
"searchTerm" : query
|
|
95
|
+
}
|
|
96
|
+
)
|
|
93
97
|
|
|
94
|
-
|
|
95
|
-
data = {
|
|
96
|
-
"cKey" : c_key,
|
|
97
|
-
"cValue" : c_value,
|
|
98
|
-
"searchTerm" : query
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
headers = {
|
|
102
|
-
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
|
|
103
|
-
"Accept" : "application/json, text/javascript, */*; q=0.01",
|
|
104
|
-
"X-Requested-With" : "XMLHttpRequest",
|
|
105
|
-
"Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8"
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
response = await self.httpx.post(post_url, data=data, headers=headers)
|
|
109
|
-
|
|
110
|
-
try:
|
|
98
|
+
with contextlib.suppress(Exception):
|
|
111
99
|
# Extract JSON data from response (might contain garbage chars at start)
|
|
112
100
|
raw = response.text
|
|
113
101
|
json_start = raw.find('{')
|
|
114
102
|
if json_start != -1:
|
|
115
103
|
clean_json = raw[json_start:]
|
|
116
|
-
data
|
|
117
|
-
|
|
104
|
+
data = json.loads(clean_json)
|
|
105
|
+
|
|
118
106
|
results = []
|
|
119
107
|
# Result array is in data['data']['result']
|
|
120
108
|
res_array = data.get("data", {}).get("result", [])
|
|
121
|
-
|
|
109
|
+
|
|
122
110
|
if not res_array:
|
|
123
111
|
# Fallback manual parsing ?
|
|
124
112
|
pass
|
|
125
113
|
|
|
126
114
|
for item in res_array:
|
|
127
|
-
name
|
|
128
|
-
slug
|
|
115
|
+
name = item.get("object_name")
|
|
116
|
+
slug = item.get("used_slug")
|
|
129
117
|
poster = item.get("object_poster_url")
|
|
130
|
-
|
|
118
|
+
|
|
131
119
|
if name and slug:
|
|
132
120
|
if "cdn.ampproject.org" in poster:
|
|
133
121
|
poster = "https://images.macellan.online/images/movie/poster/180/275/80/" + poster.split("/")[-1]
|
|
134
|
-
|
|
122
|
+
|
|
135
123
|
results.append(SearchResult(
|
|
136
|
-
title=name,
|
|
137
|
-
url=self.fix_url(slug),
|
|
138
|
-
poster=self.fix_url(poster)
|
|
124
|
+
title = name,
|
|
125
|
+
url = self.fix_url(slug),
|
|
126
|
+
poster = self.fix_url(poster)
|
|
139
127
|
))
|
|
140
128
|
return results
|
|
141
129
|
|
|
142
|
-
except Exception:
|
|
143
|
-
pass
|
|
144
130
|
return []
|
|
145
131
|
|
|
146
|
-
async def load_item(self, url: str) -> SeriesInfo:
|
|
147
|
-
|
|
148
|
-
|
|
132
|
+
async def load_item(self, url: str) -> SeriesInfo | MovieInfo:
|
|
133
|
+
istek = await self.httpx.get(url)
|
|
134
|
+
secici = HTMLHelper(istek.text)
|
|
149
135
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
# take 1x
|
|
158
|
-
parts = str(poster_info).split(",")
|
|
159
|
-
for p in parts:
|
|
160
|
-
if "1x" in p:
|
|
161
|
-
poster = p.strip().split(" ")[0]
|
|
162
|
-
break
|
|
163
|
-
|
|
164
|
-
desc_el = sel.css_first("p#tv-series-desc")
|
|
165
|
-
description = desc_el.text(strip=True) if desc_el else None
|
|
166
|
-
|
|
167
|
-
tags = [a.text(strip=True) for a in sel.css("div.item.categories a") if a.text(strip=True)]
|
|
136
|
+
title = secici.select_direct_text("h1")
|
|
137
|
+
poster_attr = secici.select_attr("img.series-profile-thumb", "data-srcset") or secici.select_attr("img.series-profile-thumb", "srcset")
|
|
138
|
+
if poster_attr:
|
|
139
|
+
# "url 1x, url 2x" -> en sondakini (en yüksek kalite) al
|
|
140
|
+
poster = poster_attr.split(",")[-1].strip().split(" ")[0]
|
|
141
|
+
else:
|
|
142
|
+
poster = secici.select_poster("img.series-profile-thumb")
|
|
168
143
|
|
|
169
|
-
|
|
170
|
-
|
|
144
|
+
description = secici.select_text("p#tv-series-desc")
|
|
145
|
+
tags = secici.select_texts("div.item.categories a")
|
|
146
|
+
rating = secici.select_text("span.color-imdb")
|
|
147
|
+
actors = secici.select_texts("div.content h5")
|
|
148
|
+
year = secici.extract_year("div.truncate")
|
|
149
|
+
duration = secici.regex_first(r"(\d+)", secici.select_text(".media-meta td:last-child"))
|
|
150
|
+
if duration == year or int(duration) < 40:
|
|
151
|
+
duration = None
|
|
171
152
|
|
|
172
|
-
|
|
153
|
+
common_info = {
|
|
154
|
+
"url" : url,
|
|
155
|
+
"poster" : self.fix_url(poster),
|
|
156
|
+
"title" : title,
|
|
157
|
+
"description" : description,
|
|
158
|
+
"tags" : tags,
|
|
159
|
+
"rating" : rating,
|
|
160
|
+
"year" : year,
|
|
161
|
+
"actors" : actors,
|
|
162
|
+
"duration" : duration
|
|
163
|
+
}
|
|
173
164
|
|
|
174
|
-
year_el = sel.css_first("span.item.year")
|
|
175
|
-
year = year_el.text(strip=True) if year_el else None
|
|
176
|
-
|
|
177
165
|
episodes = []
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if season_elements:
|
|
181
|
-
# Get season links
|
|
182
|
-
season_links = []
|
|
183
|
-
menu = sel.css("div.ui.vertical.fluid.tabular.menu a")
|
|
184
|
-
for link in menu:
|
|
166
|
+
for tab in secici.select("div.ui.tab"):
|
|
167
|
+
for link in secici.select("a[href*='bolum']", tab):
|
|
185
168
|
href = link.attrs.get("href")
|
|
186
169
|
if href:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
try:
|
|
193
|
-
s_resp = await self.httpx.get(target_url)
|
|
194
|
-
s_sel = HTMLParser(s_resp.text)
|
|
195
|
-
ep_links = s_sel.css("div.ui.list.celled a.item")
|
|
196
|
-
|
|
197
|
-
current_season_no = 1
|
|
198
|
-
match = re.search(r"sezon-(\d+)", target_url)
|
|
199
|
-
if match:
|
|
200
|
-
current_season_no = int(match.group(1))
|
|
201
|
-
|
|
202
|
-
for ep_link in ep_links:
|
|
203
|
-
href = ep_link.attrs.get("href")
|
|
204
|
-
name_el = ep_link.css_first("div.content div.header")
|
|
205
|
-
name = name_el.text(strip=True) if name_el else ""
|
|
206
|
-
|
|
207
|
-
if href:
|
|
208
|
-
ep_no = 0
|
|
209
|
-
match_ep = re.search(r"bolum-(\d+)", href)
|
|
210
|
-
if match_ep:
|
|
211
|
-
ep_no = int(match_ep.group(1))
|
|
212
|
-
|
|
213
|
-
episodes.append(Episode(
|
|
214
|
-
season = current_season_no,
|
|
215
|
-
episode = ep_no,
|
|
216
|
-
title = name,
|
|
217
|
-
url = self.fix_url(href)
|
|
218
|
-
))
|
|
219
|
-
except Exception:
|
|
220
|
-
pass
|
|
221
|
-
|
|
170
|
+
s, e = secici.extract_season_episode(href)
|
|
171
|
+
name = secici.select_text("div.content div.header", link) or link.text(strip=True)
|
|
172
|
+
episodes.append(Episode(season=s or 1, episode=e or 1, title=name, url=self.fix_url(href)))
|
|
173
|
+
|
|
222
174
|
if episodes:
|
|
223
|
-
return SeriesInfo(
|
|
224
|
-
title = title,
|
|
225
|
-
url = url,
|
|
226
|
-
poster = self.fix_url(poster) if poster else None,
|
|
227
|
-
description = description,
|
|
228
|
-
rating = rating,
|
|
229
|
-
tags = tags,
|
|
230
|
-
actors = actors,
|
|
231
|
-
year = year,
|
|
232
|
-
episodes = episodes
|
|
233
|
-
)
|
|
234
|
-
else:
|
|
235
|
-
return MovieInfo(
|
|
236
|
-
title = title,
|
|
237
|
-
url = url,
|
|
238
|
-
poster = self.fix_url(poster) if poster else None,
|
|
239
|
-
description = description,
|
|
240
|
-
rating = rating,
|
|
241
|
-
tags = tags,
|
|
242
|
-
actors = actors,
|
|
243
|
-
year = year
|
|
244
|
-
)
|
|
175
|
+
return SeriesInfo(**common_info, episodes=episodes)
|
|
245
176
|
|
|
246
|
-
|
|
247
|
-
resp = await self.httpx.get(url)
|
|
248
|
-
sel = HTMLParser(resp.text)
|
|
249
|
-
|
|
250
|
-
iframe_el = sel.css_first("iframe")
|
|
251
|
-
iframe = iframe_el.attrs.get("src") if iframe_el else None
|
|
177
|
+
return MovieInfo(**common_info)
|
|
252
178
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
179
|
+
def _find_iframe(self, secici: HTMLHelper) -> str | None:
|
|
180
|
+
"""Sayfa kaynağındaki video iframe adresini bulur."""
|
|
181
|
+
src = secici.select_attr("iframe", "src") or \
|
|
182
|
+
secici.select_attr("iframe", "data-src") or \
|
|
183
|
+
secici.regex_first(r'<iframe[^>]+src="([^"]+)"')
|
|
184
|
+
return self.fix_url(src) if src else None
|
|
185
|
+
|
|
186
|
+
async def _process_source(self, source: dict, subtitles: list) -> list[ExtractResult]:
|
|
187
|
+
"""Tekil bir kaynağı işleyip sonucu döndürür."""
|
|
188
|
+
target_url = source["url"]
|
|
189
|
+
name = source["name"]
|
|
190
|
+
|
|
191
|
+
# Eğer direkt iframe değilse (Sayfa linki ise), önce iframe'i bul
|
|
192
|
+
if not source.get("is_main"):
|
|
193
|
+
try:
|
|
194
|
+
resp = await self.httpx.get(target_url)
|
|
195
|
+
temp_sel = HTMLHelper(resp.text)
|
|
196
|
+
|
|
197
|
+
if not (iframe_url := self._find_iframe(temp_sel)):
|
|
198
|
+
return []
|
|
199
|
+
|
|
200
|
+
target_url = iframe_url
|
|
201
|
+
|
|
202
|
+
# Tab (Dil Seçeneği) ise, gittiğimiz sayfadaki aktif player ismini ekle
|
|
203
|
+
if source.get("is_tab"):
|
|
204
|
+
p_name = temp_sel.select_text("div.alternatives-for-this div.playeritems.active") or "PUB"
|
|
205
|
+
name = f"{name} | {p_name}"
|
|
206
|
+
except Exception:
|
|
207
|
+
return []
|
|
208
|
+
|
|
209
|
+
# Linki Extract Et
|
|
259
210
|
try:
|
|
260
|
-
|
|
261
|
-
if
|
|
262
|
-
return [
|
|
211
|
+
extracted = await self.extract(target_url, referer=self.main_url)
|
|
212
|
+
if not extracted:
|
|
213
|
+
return []
|
|
214
|
+
|
|
215
|
+
items = extracted if isinstance(extracted, list) else [extracted]
|
|
216
|
+
|
|
217
|
+
# Sonuçları işle (İsim ver, altyazı ekle)
|
|
218
|
+
copy_subtitles = list(subtitles) # Her item için kopyasını kullan
|
|
219
|
+
for item in items:
|
|
220
|
+
item.name = name
|
|
221
|
+
if copy_subtitles:
|
|
222
|
+
if not item.subtitles:
|
|
223
|
+
item.subtitles = copy_subtitles
|
|
224
|
+
else:
|
|
225
|
+
item.subtitles.extend(copy_subtitles)
|
|
226
|
+
|
|
227
|
+
return items
|
|
263
228
|
except Exception:
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
229
|
+
return []
|
|
230
|
+
|
|
231
|
+
async def load_links(self, url: str) -> list[ExtractResult]:
|
|
232
|
+
istek = await self.httpx.get(url)
|
|
233
|
+
secici = HTMLHelper(istek.text)
|
|
234
|
+
|
|
235
|
+
# 1. Altyazıları Topla
|
|
236
|
+
subtitles = []
|
|
237
|
+
for track in secici.select("track"):
|
|
238
|
+
if track.attrs.get("kind") in ("subtitles", "captions"):
|
|
239
|
+
if src := track.attrs.get("src"):
|
|
240
|
+
lang = track.attrs.get("label") or track.attrs.get("srclang") or "Altyazı"
|
|
241
|
+
subtitles.append(self.new_subtitle(src, lang))
|
|
242
|
+
|
|
243
|
+
sources = []
|
|
244
|
+
|
|
245
|
+
# Aktif Sayfa Bilgileri
|
|
246
|
+
active_tab_name = secici.select_text("div#series-tabs a.active") or "Sinefy"
|
|
247
|
+
active_player = secici.select_text("div.alternatives-for-this div.playeritems.active") or "PUB"
|
|
248
|
+
|
|
249
|
+
# A) Ana Video (Main Iframe)
|
|
250
|
+
if main_iframe := self._find_iframe(secici):
|
|
251
|
+
sources.append({
|
|
252
|
+
"url" : main_iframe,
|
|
253
|
+
"name" : f"{active_tab_name} | {active_player}",
|
|
254
|
+
"is_main" : True,
|
|
255
|
+
"is_tab" : False
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
# B) Alternatif Playerlar (Mevcut Sayfa Player Butonları)
|
|
259
|
+
for btn in secici.select("div.alternatives-for-this div.playeritems:not(.active) a"):
|
|
260
|
+
if href := btn.attrs.get("href"):
|
|
261
|
+
if "javascript" not in href:
|
|
262
|
+
sources.append({
|
|
263
|
+
"url" : self.fix_url(href),
|
|
264
|
+
"name" : f"{active_tab_name} | {btn.text(strip=True)}",
|
|
265
|
+
"is_main" : False,
|
|
266
|
+
"is_tab" : False
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
# C) Diğer Dil Seçenekleri (Tabs - Sekmeler)
|
|
270
|
+
for tab in secici.select("div#series-tabs a:not(.active)"):
|
|
271
|
+
if href := tab.attrs.get("href"):
|
|
272
|
+
sources.append({
|
|
273
|
+
"url" : self.fix_url(href),
|
|
274
|
+
"name" : tab.text(strip=True),
|
|
275
|
+
"is_main" : False,
|
|
276
|
+
"is_tab" : True
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
# 2. Kaynakları Paralel İşle
|
|
280
|
+
tasks = [self._process_source(src, subtitles) for src in sources]
|
|
281
|
+
results_groups = await asyncio.gather(*tasks)
|
|
282
|
+
|
|
283
|
+
# 3. Sonuçları Birleştir
|
|
284
|
+
final_results = []
|
|
285
|
+
for group in results_groups:
|
|
286
|
+
if group:
|
|
287
|
+
final_results.extend(group)
|
|
288
|
+
|
|
289
|
+
# 4. Duplicate Temizle (URL + İsim Kombinasyonu)
|
|
290
|
+
unique_results = []
|
|
291
|
+
seen = set()
|
|
292
|
+
for res in final_results:
|
|
293
|
+
key = (res.url, res.name)
|
|
294
|
+
if res.url and key not in seen:
|
|
295
|
+
unique_results.append(res)
|
|
296
|
+
seen.add(key)
|
|
297
|
+
|
|
298
|
+
return unique_results
|