KekikStream 2.3.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.
- KekikStream/Core/Extractor/ExtractorBase.py +3 -2
- KekikStream/Core/Extractor/ExtractorLoader.py +8 -14
- KekikStream/Core/HTMLHelper.py +120 -49
- KekikStream/Core/Plugin/PluginBase.py +30 -12
- KekikStream/Core/Plugin/PluginLoader.py +12 -14
- KekikStream/Core/Plugin/PluginManager.py +2 -2
- KekikStream/Core/Plugin/PluginModels.py +0 -3
- KekikStream/Extractors/Abstream.py +27 -0
- 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 +21 -46
- 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 +15 -28
- KekikStream/Extractors/MixTiger.py +17 -40
- KekikStream/Extractors/MolyStream.py +17 -21
- KekikStream/Extractors/Odnoklassniki.py +40 -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/Veev.py +145 -0
- KekikStream/Extractors/VidBiz.py +62 -0
- KekikStream/Extractors/VidHide.py +58 -30
- 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 +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 +97 -77
- KekikStream/Plugins/DiziBox.py +28 -45
- KekikStream/Plugins/DiziMom.py +179 -0
- KekikStream/Plugins/DiziPal.py +95 -161
- KekikStream/Plugins/DiziYou.py +51 -147
- KekikStream/Plugins/Dizilla.py +40 -61
- KekikStream/Plugins/FilmBip.py +90 -39
- KekikStream/Plugins/FilmEkseni.py +199 -0
- KekikStream/Plugins/FilmMakinesi.py +72 -73
- KekikStream/Plugins/FilmModu.py +25 -35
- KekikStream/Plugins/Filmatek.py +184 -0
- KekikStream/Plugins/FilmciBaba.py +155 -0
- KekikStream/Plugins/FullHDFilmizlesene.py +16 -37
- KekikStream/Plugins/HDFilm.py +243 -0
- KekikStream/Plugins/HDFilmCehennemi.py +242 -189
- KekikStream/Plugins/JetFilmizle.py +101 -69
- KekikStream/Plugins/KultFilmler.py +138 -104
- KekikStream/Plugins/RecTV.py +52 -73
- KekikStream/Plugins/RoketDizi.py +18 -27
- KekikStream/Plugins/SelcukFlix.py +30 -48
- KekikStream/Plugins/SetFilmIzle.py +76 -104
- KekikStream/Plugins/SezonlukDizi.py +90 -94
- KekikStream/Plugins/Sinefy.py +195 -167
- KekikStream/Plugins/SinemaCX.py +148 -78
- KekikStream/Plugins/Sinezy.py +29 -31
- KekikStream/Plugins/SuperFilmGeldi.py +12 -17
- KekikStream/Plugins/UgurFilm.py +85 -38
- KekikStream/Plugins/Watch32.py +160 -0
- KekikStream/Plugins/YabanciDizi.py +176 -211
- {kekikstream-2.3.9.dist-info → kekikstream-2.5.3.dist-info}/METADATA +1 -1
- kekikstream-2.5.3.dist-info/RECORD +99 -0
- {kekikstream-2.3.9.dist-info → kekikstream-2.5.3.dist-info}/WHEEL +1 -1
- KekikStream/Plugins/FullHDFilm.py +0 -249
- kekikstream-2.3.9.dist-info/RECORD +0 -84
- {kekikstream-2.3.9.dist-info → kekikstream-2.5.3.dist-info}/entry_points.txt +0 -0
- {kekikstream-2.3.9.dist-info → kekikstream-2.5.3.dist-info}/licenses/LICENSE +0 -0
- {kekikstream-2.3.9.dist-info → kekikstream-2.5.3.dist-info}/top_level.txt +0 -0
KekikStream/Plugins/Sinefy.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
2
|
|
|
3
|
-
from KekikStream.Core
|
|
4
|
-
import json,
|
|
3
|
+
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, MovieInfo, ExtractResult, HTMLHelper
|
|
4
|
+
import json, contextlib, asyncio
|
|
5
5
|
|
|
6
6
|
class Sinefy(PluginBase):
|
|
7
7
|
name = "Sinefy"
|
|
@@ -42,14 +42,14 @@ class Sinefy(PluginBase):
|
|
|
42
42
|
else:
|
|
43
43
|
full_url = f"{url}&page={page}"
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
istek = await self.httpx.get(full_url)
|
|
46
|
+
secici = HTMLHelper(istek.text)
|
|
47
47
|
|
|
48
48
|
results = []
|
|
49
|
-
for item in
|
|
50
|
-
title =
|
|
51
|
-
href =
|
|
52
|
-
poster =
|
|
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)
|
|
53
53
|
|
|
54
54
|
if poster:
|
|
55
55
|
poster = poster.split(",")[0].split(" ")[0]
|
|
@@ -59,7 +59,7 @@ class Sinefy(PluginBase):
|
|
|
59
59
|
category = category,
|
|
60
60
|
title = title,
|
|
61
61
|
url = self.fix_url(href),
|
|
62
|
-
poster = self.fix_url(poster)
|
|
62
|
+
poster = self.fix_url(poster)
|
|
63
63
|
))
|
|
64
64
|
|
|
65
65
|
return results
|
|
@@ -69,202 +69,230 @@ class Sinefy(PluginBase):
|
|
|
69
69
|
c_key = "ca1d4a53d0f4761a949b85e51e18f096"
|
|
70
70
|
c_value = "MTc0NzI2OTAwMDU3ZTEwYmZjMDViNWFmOWIwZDViODg0MjU4MjA1ZmYxOThmZTYwMDdjMWQzMzliNzY5NzFlZmViMzRhMGVmNjgwODU3MGIyZA=="
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
with contextlib.suppress(Exception):
|
|
73
|
+
istek = await self.httpx.get(self.main_url)
|
|
74
|
+
secici = HTMLHelper(istek.text)
|
|
75
75
|
|
|
76
|
-
cke
|
|
77
|
-
cval =
|
|
76
|
+
cke = secici.select_attr("input[name='cKey']", "value")
|
|
77
|
+
cval = secici.select_attr("input[name='cValue']", "value")
|
|
78
78
|
|
|
79
79
|
if cke and cval:
|
|
80
80
|
c_key = cke
|
|
81
81
|
c_value = cval
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
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
|
+
)
|
|
85
97
|
|
|
86
|
-
|
|
87
|
-
data = {
|
|
88
|
-
"cKey" : c_key,
|
|
89
|
-
"cValue" : c_value,
|
|
90
|
-
"searchTerm" : query
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
headers = {
|
|
94
|
-
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0",
|
|
95
|
-
"Accept" : "application/json, text/javascript, */*; q=0.01",
|
|
96
|
-
"X-Requested-With" : "XMLHttpRequest",
|
|
97
|
-
"Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8"
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
response = await self.httpx.post(post_url, data=data, headers=headers)
|
|
101
|
-
|
|
102
|
-
try:
|
|
98
|
+
with contextlib.suppress(Exception):
|
|
103
99
|
# Extract JSON data from response (might contain garbage chars at start)
|
|
104
100
|
raw = response.text
|
|
105
101
|
json_start = raw.find('{')
|
|
106
102
|
if json_start != -1:
|
|
107
103
|
clean_json = raw[json_start:]
|
|
108
|
-
data
|
|
109
|
-
|
|
104
|
+
data = json.loads(clean_json)
|
|
105
|
+
|
|
110
106
|
results = []
|
|
111
107
|
# Result array is in data['data']['result']
|
|
112
108
|
res_array = data.get("data", {}).get("result", [])
|
|
113
|
-
|
|
109
|
+
|
|
114
110
|
if not res_array:
|
|
115
111
|
# Fallback manual parsing ?
|
|
116
112
|
pass
|
|
117
113
|
|
|
118
114
|
for item in res_array:
|
|
119
|
-
name
|
|
120
|
-
slug
|
|
115
|
+
name = item.get("object_name")
|
|
116
|
+
slug = item.get("used_slug")
|
|
121
117
|
poster = item.get("object_poster_url")
|
|
122
|
-
|
|
118
|
+
|
|
123
119
|
if name and slug:
|
|
124
120
|
if "cdn.ampproject.org" in poster:
|
|
125
121
|
poster = "https://images.macellan.online/images/movie/poster/180/275/80/" + poster.split("/")[-1]
|
|
126
|
-
|
|
122
|
+
|
|
127
123
|
results.append(SearchResult(
|
|
128
|
-
title=name,
|
|
129
|
-
url=self.fix_url(slug),
|
|
130
|
-
poster=self.fix_url(poster)
|
|
124
|
+
title = name,
|
|
125
|
+
url = self.fix_url(slug),
|
|
126
|
+
poster = self.fix_url(poster)
|
|
131
127
|
))
|
|
132
128
|
return results
|
|
133
129
|
|
|
134
|
-
except Exception:
|
|
135
|
-
pass
|
|
136
130
|
return []
|
|
137
131
|
|
|
138
|
-
async def load_item(self, url: str) -> SeriesInfo:
|
|
139
|
-
|
|
140
|
-
|
|
132
|
+
async def load_item(self, url: str) -> SeriesInfo | MovieInfo:
|
|
133
|
+
istek = await self.httpx.get(url)
|
|
134
|
+
secici = HTMLHelper(istek.text)
|
|
141
135
|
|
|
142
|
-
title
|
|
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")
|
|
143
143
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
|
152
152
|
|
|
153
|
-
|
|
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
|
+
}
|
|
154
164
|
|
|
155
|
-
|
|
165
|
+
episodes = []
|
|
166
|
+
for tab in secici.select("div.ui.tab"):
|
|
167
|
+
for link in secici.select("a[href*='bolum']", tab):
|
|
168
|
+
href = link.attrs.get("href")
|
|
169
|
+
if href:
|
|
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)))
|
|
156
173
|
|
|
157
|
-
|
|
174
|
+
if episodes:
|
|
175
|
+
return SeriesInfo(**common_info, episodes=episodes)
|
|
158
176
|
|
|
159
|
-
|
|
177
|
+
return MovieInfo(**common_info)
|
|
160
178
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
episodes = []
|
|
169
|
-
episodes_box_list = sel.select("section.episodes-box")
|
|
170
|
-
|
|
171
|
-
if episodes_box_list:
|
|
172
|
-
episodes_box = episodes_box_list[0]
|
|
173
|
-
# Sezon menüsünden sezon linklerini al
|
|
174
|
-
season_menu = sel.select("div.ui.vertical.fluid.tabular.menu a.item", episodes_box)
|
|
175
|
-
|
|
176
|
-
# Sezon tab içeriklerini al
|
|
177
|
-
season_tabs = sel.select("div.ui.tab", episodes_box)
|
|
178
|
-
|
|
179
|
-
# Eğer birden fazla sezon varsa, her sezon tab'ından bölümleri çek
|
|
180
|
-
if season_tabs:
|
|
181
|
-
for idx, season_tab in enumerate(season_tabs):
|
|
182
|
-
# Sezon numarasını belirle
|
|
183
|
-
current_season_no = idx + 1
|
|
184
|
-
|
|
185
|
-
# Menüden sezon numarasını almaya çalış
|
|
186
|
-
if idx < len(season_menu):
|
|
187
|
-
menu_href = season_menu[idx].attrs.get("href", "")
|
|
188
|
-
match = sel.regex_first(r"sezon-(\d+)", menu_href)
|
|
189
|
-
if match:
|
|
190
|
-
current_season_no = int(match)
|
|
191
|
-
|
|
192
|
-
# Bu sezon tab'ından bölüm linklerini çek
|
|
193
|
-
ep_links = sel.select("a[href*='bolum']", season_tab)
|
|
194
|
-
|
|
195
|
-
seen_urls = set()
|
|
196
|
-
for ep_link in ep_links:
|
|
197
|
-
href = ep_link.attrs.get("href")
|
|
198
|
-
if not href or href in seen_urls:
|
|
199
|
-
continue
|
|
200
|
-
seen_urls.add(href)
|
|
201
|
-
|
|
202
|
-
# Bölüm numarasını URL'den çıkar
|
|
203
|
-
ep_no = 0
|
|
204
|
-
match_ep = sel.regex_first(r"bolum-(\d+)", href)
|
|
205
|
-
if match_ep:
|
|
206
|
-
ep_no = int(match_ep)
|
|
207
|
-
|
|
208
|
-
# Bölüm başlığını çıkar (önce title attribute, sonra text)
|
|
209
|
-
name = ep_link.attrs.get("title", "")
|
|
210
|
-
if not name:
|
|
211
|
-
name = sel.select_text("div.content div.header", ep_link)
|
|
212
|
-
if not name:
|
|
213
|
-
name = ep_link.text(strip=True)
|
|
214
|
-
|
|
215
|
-
if href and ep_no > 0:
|
|
216
|
-
episodes.append(Episode(
|
|
217
|
-
season = current_season_no,
|
|
218
|
-
episode = ep_no,
|
|
219
|
-
title = name.strip() if name else f"{ep_no}. Bölüm",
|
|
220
|
-
url = self.fix_url(href)
|
|
221
|
-
))
|
|
222
|
-
|
|
223
|
-
if episodes:
|
|
224
|
-
return SeriesInfo(
|
|
225
|
-
title = title,
|
|
226
|
-
url = url,
|
|
227
|
-
poster = self.fix_url(poster) if poster else None,
|
|
228
|
-
description = description,
|
|
229
|
-
rating = rating,
|
|
230
|
-
tags = tags,
|
|
231
|
-
actors = actors,
|
|
232
|
-
year = year,
|
|
233
|
-
episodes = episodes
|
|
234
|
-
)
|
|
235
|
-
else:
|
|
236
|
-
return MovieInfo(
|
|
237
|
-
title = title,
|
|
238
|
-
url = url,
|
|
239
|
-
poster = self.fix_url(poster) if poster else None,
|
|
240
|
-
description = description,
|
|
241
|
-
rating = rating,
|
|
242
|
-
tags = tags,
|
|
243
|
-
actors = actors,
|
|
244
|
-
year = year
|
|
245
|
-
)
|
|
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
|
|
246
185
|
|
|
247
|
-
async def
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
iframe = sel.select_attr("iframe", "src")
|
|
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"]
|
|
252
190
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|