KekikStream 1.7.2__py3-none-any.whl → 1.7.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/Extractors/DzenRu.py +39 -0
- KekikStream/Extractors/ExPlay.py +54 -0
- KekikStream/Extractors/FirePlayer.py +61 -0
- KekikStream/Extractors/HDPlayerSystem.py +42 -0
- KekikStream/Extractors/JetTv.py +47 -0
- KekikStream/Extractors/MixTiger.py +61 -0
- KekikStream/Extractors/PlayerFilmIzle.py +63 -0
- KekikStream/Extractors/SetPlay.py +58 -0
- KekikStream/Extractors/SetPrime.py +46 -0
- KekikStream/Extractors/TurkeyPlayer.py +35 -0
- KekikStream/Extractors/VidHide.py +73 -0
- KekikStream/Extractors/VidPapi.py +90 -0
- KekikStream/Extractors/VidStack.py +75 -0
- KekikStream/Extractors/YildizKisaFilm.py +42 -0
- KekikStream/Plugins/Dizilla.py +5 -1
- KekikStream/Plugins/FilmBip.py +145 -0
- KekikStream/Plugins/FullHDFilm.py +164 -0
- KekikStream/Plugins/JetFilmizle.py +10 -3
- KekikStream/Plugins/KultFilmler.py +219 -0
- KekikStream/Plugins/RoketDizi.py +204 -0
- KekikStream/Plugins/SelcukFlix.py +216 -0
- KekikStream/Plugins/SezonlukDizi.py +3 -0
- KekikStream/Plugins/Sinefy.py +214 -0
- KekikStream/Plugins/Sinezy.py +99 -0
- KekikStream/Plugins/SuperFilmGeldi.py +121 -0
- {kekikstream-1.7.2.dist-info → kekikstream-1.7.3.dist-info}/METADATA +1 -1
- {kekikstream-1.7.2.dist-info → kekikstream-1.7.3.dist-info}/RECORD +31 -9
- {kekikstream-1.7.2.dist-info → kekikstream-1.7.3.dist-info}/WHEEL +0 -0
- {kekikstream-1.7.2.dist-info → kekikstream-1.7.3.dist-info}/entry_points.txt +0 -0
- {kekikstream-1.7.2.dist-info → kekikstream-1.7.3.dist-info}/licenses/LICENSE +0 -0
- {kekikstream-1.7.2.dist-info → kekikstream-1.7.3.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
|
+
|
|
3
|
+
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult, Subtitle
|
|
4
|
+
from parsel import Selector
|
|
5
|
+
import re, base64
|
|
6
|
+
|
|
7
|
+
class KultFilmler(PluginBase):
|
|
8
|
+
name = "KultFilmler"
|
|
9
|
+
language = "tr"
|
|
10
|
+
main_url = "https://kultfilmler.net"
|
|
11
|
+
favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
|
|
12
|
+
description = "Kült film ve dizi izleme sitesi."
|
|
13
|
+
|
|
14
|
+
main_page = {
|
|
15
|
+
f"{main_url}/category/aile-filmleri-izle" : "Aile",
|
|
16
|
+
f"{main_url}/category/aksiyon-filmleri-izle" : "Aksiyon",
|
|
17
|
+
f"{main_url}/category/animasyon-filmleri-izle" : "Animasyon",
|
|
18
|
+
f"{main_url}/category/belgesel-izle" : "Belgesel",
|
|
19
|
+
f"{main_url}/category/bilim-kurgu-filmleri-izle": "Bilim Kurgu",
|
|
20
|
+
f"{main_url}/category/biyografi-filmleri-izle" : "Biyografi",
|
|
21
|
+
f"{main_url}/category/dram-filmleri-izle" : "Dram",
|
|
22
|
+
f"{main_url}/category/fantastik-filmleri-izle" : "Fantastik",
|
|
23
|
+
f"{main_url}/category/gerilim-filmleri-izle" : "Gerilim",
|
|
24
|
+
f"{main_url}/category/gizem-filmleri-izle" : "Gizem",
|
|
25
|
+
f"{main_url}/category/kara-filmleri-izle" : "Kara Film",
|
|
26
|
+
f"{main_url}/category/kisa-film-izle" : "Kısa Metraj",
|
|
27
|
+
f"{main_url}/category/komedi-filmleri-izle" : "Komedi",
|
|
28
|
+
f"{main_url}/category/korku-filmleri-izle" : "Korku",
|
|
29
|
+
f"{main_url}/category/macera-filmleri-izle" : "Macera",
|
|
30
|
+
f"{main_url}/category/muzik-filmleri-izle" : "Müzik",
|
|
31
|
+
f"{main_url}/category/polisiye-filmleri-izle" : "Polisiye",
|
|
32
|
+
f"{main_url}/category/romantik-filmleri-izle" : "Romantik",
|
|
33
|
+
f"{main_url}/category/savas-filmleri-izle" : "Savaş",
|
|
34
|
+
f"{main_url}/category/suc-filmleri-izle" : "Suç",
|
|
35
|
+
f"{main_url}/category/tarih-filmleri-izle" : "Tarih",
|
|
36
|
+
f"{main_url}/category/yerli-filmleri-izle" : "Yerli",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
|
|
40
|
+
istek = await self.cffi.get(url)
|
|
41
|
+
secici = Selector(istek.text)
|
|
42
|
+
|
|
43
|
+
results = []
|
|
44
|
+
for veri in secici.css("div.col-md-12 div.movie-box"):
|
|
45
|
+
title = veri.css("div.img img::attr(alt)").get()
|
|
46
|
+
href = self.fix_url(veri.css("a::attr(href)").get())
|
|
47
|
+
poster = self.fix_url(veri.css("div.img img::attr(src)").get())
|
|
48
|
+
|
|
49
|
+
if title and href:
|
|
50
|
+
results.append(MainPageResult(
|
|
51
|
+
category = category,
|
|
52
|
+
title = title,
|
|
53
|
+
url = href,
|
|
54
|
+
poster = poster,
|
|
55
|
+
))
|
|
56
|
+
|
|
57
|
+
return results
|
|
58
|
+
|
|
59
|
+
async def search(self, query: str) -> list[SearchResult]:
|
|
60
|
+
istek = await self.cffi.get(f"{self.main_url}?s={query}")
|
|
61
|
+
secici = Selector(istek.text)
|
|
62
|
+
|
|
63
|
+
results = []
|
|
64
|
+
for veri in secici.css("div.movie-box"):
|
|
65
|
+
title = veri.css("div.img img::attr(alt)").get()
|
|
66
|
+
href = self.fix_url(veri.css("a::attr(href)").get())
|
|
67
|
+
poster = self.fix_url(veri.css("div.img img::attr(src)").get())
|
|
68
|
+
|
|
69
|
+
if title and href:
|
|
70
|
+
results.append(SearchResult(
|
|
71
|
+
title = title,
|
|
72
|
+
url = href,
|
|
73
|
+
poster = poster,
|
|
74
|
+
))
|
|
75
|
+
|
|
76
|
+
return results
|
|
77
|
+
|
|
78
|
+
async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
|
|
79
|
+
istek = await self.cffi.get(url)
|
|
80
|
+
secici = Selector(istek.text)
|
|
81
|
+
|
|
82
|
+
title = secici.css("div.film-bilgileri img::attr(alt)").get() or secici.css("[property='og:title']::attr(content)").get()
|
|
83
|
+
poster = self.fix_url(secici.css("[property='og:image']::attr(content)").get())
|
|
84
|
+
description = secici.css("div.description::text").get()
|
|
85
|
+
tags = secici.css("ul.post-categories a::text").getall()
|
|
86
|
+
# HTML analizine göre güncellenen alanlar
|
|
87
|
+
year = secici.css("li.release span a::text").get()
|
|
88
|
+
duration = secici.css("li.time span::text").re_first(r"(\d+)")
|
|
89
|
+
rating = secici.css("div.imdb-count::text").get()
|
|
90
|
+
actors = secici.css("div.actors a::text").getall()
|
|
91
|
+
if rating:
|
|
92
|
+
rating = rating.strip()
|
|
93
|
+
|
|
94
|
+
# Dizi mi kontrol et
|
|
95
|
+
if "/dizi/" in url:
|
|
96
|
+
episodes = []
|
|
97
|
+
for bolum in secici.css("div.episode-box"):
|
|
98
|
+
ep_href = self.fix_url(bolum.css("div.name a::attr(href)").get())
|
|
99
|
+
ssn_detail = bolum.css("span.episodetitle::text").get() or ""
|
|
100
|
+
ep_detail = bolum.css("span.episodetitle b::text").get() or ""
|
|
101
|
+
ep_name = f"{ssn_detail} - {ep_detail}"
|
|
102
|
+
|
|
103
|
+
if ep_href:
|
|
104
|
+
ep_season = re.search(r"(\d+)\.", ssn_detail)
|
|
105
|
+
ep_episode = re.search(r"(\d+)\.", ep_detail)
|
|
106
|
+
|
|
107
|
+
episodes.append(Episode(
|
|
108
|
+
season = int(ep_season[1]) if ep_season else 1,
|
|
109
|
+
episode = int(ep_episode[1]) if ep_episode else 1,
|
|
110
|
+
title = ep_name.strip(" -"),
|
|
111
|
+
url = ep_href,
|
|
112
|
+
))
|
|
113
|
+
|
|
114
|
+
return SeriesInfo(
|
|
115
|
+
url = url,
|
|
116
|
+
poster = poster,
|
|
117
|
+
title = self.clean_title(title) if title else "",
|
|
118
|
+
description = description,
|
|
119
|
+
tags = tags,
|
|
120
|
+
year = year,
|
|
121
|
+
actors = actors,
|
|
122
|
+
rating = rating,
|
|
123
|
+
episodes = episodes,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return MovieInfo(
|
|
127
|
+
url = url,
|
|
128
|
+
poster = poster,
|
|
129
|
+
title = self.clean_title(title) if title else "",
|
|
130
|
+
description = description,
|
|
131
|
+
tags = tags,
|
|
132
|
+
year = year,
|
|
133
|
+
rating = rating,
|
|
134
|
+
actors = actors,
|
|
135
|
+
duration = int(duration) if duration else None,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
def _get_iframe(self, source_code: str) -> str:
|
|
139
|
+
"""Base64 kodlu iframe'i çözümle"""
|
|
140
|
+
atob_match = re.search(r"PHA\+[0-9a-zA-Z+/=]*", source_code)
|
|
141
|
+
if not atob_match:
|
|
142
|
+
return ""
|
|
143
|
+
|
|
144
|
+
atob = atob_match.group()
|
|
145
|
+
|
|
146
|
+
# Padding düzelt
|
|
147
|
+
padding = 4 - len(atob) % 4
|
|
148
|
+
if padding < 4:
|
|
149
|
+
atob = atob + "=" * padding
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
decoded = base64.b64decode(atob).decode("utf-8")
|
|
153
|
+
secici = Selector(text=decoded)
|
|
154
|
+
return self.fix_url(secici.css("iframe::attr(src)").get()) or ""
|
|
155
|
+
except Exception:
|
|
156
|
+
return ""
|
|
157
|
+
|
|
158
|
+
def _extract_subtitle_url(self, source_code: str) -> str | None:
|
|
159
|
+
"""Altyazı URL'sini çıkar"""
|
|
160
|
+
match = re.search(r"(https?://[^\s\"]+\.srt)", source_code)
|
|
161
|
+
return match[1] if match else None
|
|
162
|
+
|
|
163
|
+
async def load_links(self, url: str) -> list[dict]:
|
|
164
|
+
istek = await self.cffi.get(url)
|
|
165
|
+
secici = Selector(istek.text)
|
|
166
|
+
|
|
167
|
+
iframes = set()
|
|
168
|
+
|
|
169
|
+
# Ana iframe
|
|
170
|
+
main_frame = self._get_iframe(istek.text)
|
|
171
|
+
if main_frame:
|
|
172
|
+
iframes.add(main_frame)
|
|
173
|
+
|
|
174
|
+
# Alternatif player'lar
|
|
175
|
+
for player in secici.css("div.container#player"):
|
|
176
|
+
alt_iframe = self.fix_url(player.css("iframe::attr(src)").get())
|
|
177
|
+
if alt_iframe:
|
|
178
|
+
alt_istek = await self.cffi.get(alt_iframe)
|
|
179
|
+
alt_frame = self._get_iframe(alt_istek.text)
|
|
180
|
+
if alt_frame:
|
|
181
|
+
iframes.add(alt_frame)
|
|
182
|
+
|
|
183
|
+
results = []
|
|
184
|
+
|
|
185
|
+
for iframe in iframes:
|
|
186
|
+
subtitles = []
|
|
187
|
+
|
|
188
|
+
# VidMoly özel işleme
|
|
189
|
+
if "vidmoly" in iframe:
|
|
190
|
+
headers = {
|
|
191
|
+
"User-Agent" : "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36",
|
|
192
|
+
"Sec-Fetch-Dest" : "iframe"
|
|
193
|
+
}
|
|
194
|
+
iframe_istek = await self.cffi.get(iframe, headers=headers)
|
|
195
|
+
m3u_match = re.search(r'file:"([^"]+)"', iframe_istek.text)
|
|
196
|
+
|
|
197
|
+
if m3u_match:
|
|
198
|
+
results.append({
|
|
199
|
+
"name" : "VidMoly",
|
|
200
|
+
"url" : m3u_match[1],
|
|
201
|
+
"referer" : self.main_url,
|
|
202
|
+
"subtitles" : []
|
|
203
|
+
})
|
|
204
|
+
continue
|
|
205
|
+
|
|
206
|
+
# Altyazı çıkar
|
|
207
|
+
subtitle_url = self._extract_subtitle_url(url)
|
|
208
|
+
if subtitle_url:
|
|
209
|
+
subtitles.append(Subtitle(name="Türkçe", url=subtitle_url))
|
|
210
|
+
|
|
211
|
+
extractor = self.ex_manager.find_extractor(iframe)
|
|
212
|
+
results.append({
|
|
213
|
+
"name" : extractor.name if extractor else "Player",
|
|
214
|
+
"url" : iframe,
|
|
215
|
+
"referer" : f"{self.main_url}/",
|
|
216
|
+
"subtitles" : subtitles
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
return results
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
|
+
|
|
3
|
+
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, MovieInfo
|
|
4
|
+
from parsel import Selector
|
|
5
|
+
import re, base64, json, urllib.parse
|
|
6
|
+
|
|
7
|
+
class RoketDizi(PluginBase):
|
|
8
|
+
name = "RoketDizi"
|
|
9
|
+
lang = "tr"
|
|
10
|
+
main_url = "https://flatscher.net"
|
|
11
|
+
|
|
12
|
+
main_page = {
|
|
13
|
+
"dizi/tur/aksiyon" : "Aksiyon",
|
|
14
|
+
"dizi/tur/bilim-kurgu" : "Bilim Kurgu",
|
|
15
|
+
"dizi/tur/gerilim" : "Gerilim",
|
|
16
|
+
"dizi/tur/fantastik" : "Fantastik",
|
|
17
|
+
"dizi/tur/komedi" : "Komedi",
|
|
18
|
+
"dizi/tur/korku" : "Korku",
|
|
19
|
+
"dizi/tur/macera" : "Macera",
|
|
20
|
+
"dizi/tur/suc" : "Suç"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
|
|
24
|
+
full_url = f"{self.main_url}/{url}?&page={page}"
|
|
25
|
+
resp = await self.cffi.get(full_url)
|
|
26
|
+
sel = Selector(resp.text)
|
|
27
|
+
|
|
28
|
+
results = []
|
|
29
|
+
|
|
30
|
+
for item in sel.css("div.w-full.p-4 span.bg-\\[\\#232323\\]"):
|
|
31
|
+
title = item.css("span.font-normal.line-clamp-1::text").get()
|
|
32
|
+
href = item.css("a::attr(href)").get()
|
|
33
|
+
poster= item.css("img::attr(src)").get()
|
|
34
|
+
|
|
35
|
+
if title and href:
|
|
36
|
+
results.append(MainPageResult(
|
|
37
|
+
category=category,
|
|
38
|
+
title=title,
|
|
39
|
+
url=self.fix_url(href),
|
|
40
|
+
poster=self.fix_url(poster)
|
|
41
|
+
))
|
|
42
|
+
return results
|
|
43
|
+
|
|
44
|
+
async def get_domain(self):
|
|
45
|
+
try:
|
|
46
|
+
domain_list = await self.cffi.get("https://raw.githubusercontent.com/Kraptor123/domainListesi/refs/heads/main/eklenti_domainleri.txt")
|
|
47
|
+
if domain_list.status_code == 200:
|
|
48
|
+
for line in domain_list.text.split("|"):
|
|
49
|
+
if line.strip().startswith("RoketDizi"):
|
|
50
|
+
domain = line.split(":")[-1].strip()
|
|
51
|
+
if "http" not in domain:
|
|
52
|
+
domain = f"https://{domain}"
|
|
53
|
+
return domain
|
|
54
|
+
except Exception:
|
|
55
|
+
pass
|
|
56
|
+
return self.main_url
|
|
57
|
+
|
|
58
|
+
async def search(self, query: str) -> list[SearchResult]:
|
|
59
|
+
current_domain = await self.get_domain()
|
|
60
|
+
|
|
61
|
+
# Get Cookies and Keys
|
|
62
|
+
main_req = await self.cffi.get(current_domain)
|
|
63
|
+
sel = Selector(main_req.text)
|
|
64
|
+
|
|
65
|
+
c_key = sel.css("input[name='cKey']::attr(value)").get()
|
|
66
|
+
c_value = sel.css("input[name='cValue']::attr(value)").get()
|
|
67
|
+
|
|
68
|
+
post_url = f"{current_domain}/api/bg/searchContent?searchterm={query}"
|
|
69
|
+
|
|
70
|
+
headers = {
|
|
71
|
+
"Accept": "application/json, text/javascript, */*; q=0.01",
|
|
72
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
73
|
+
"Referer": f"{current_domain}/",
|
|
74
|
+
"CNT": "vakTR"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
data = {}
|
|
78
|
+
if c_key and c_value:
|
|
79
|
+
data = {"cKey": c_key, "cValue": c_value}
|
|
80
|
+
|
|
81
|
+
search_req = await self.cffi.post(post_url, data=data, headers=headers)
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
resp_json = search_req.json()
|
|
85
|
+
if not resp_json.get("state"):
|
|
86
|
+
return []
|
|
87
|
+
|
|
88
|
+
html_content = resp_json.get("html", "").strip()
|
|
89
|
+
sel_results = Selector(html_content)
|
|
90
|
+
|
|
91
|
+
results = []
|
|
92
|
+
items = re.findall(r'<a href="([^"]+)".*?data-srcset="([^"]+).*?<span class="text-white">([^<]+)', html_content, re.DOTALL)
|
|
93
|
+
|
|
94
|
+
for href, poster, title in items:
|
|
95
|
+
results.append(SearchResult(
|
|
96
|
+
title=title.strip(),
|
|
97
|
+
url=self.fix_url(href.strip(), current_domain),
|
|
98
|
+
poster=self.fix_url(poster.strip(), current_domain)
|
|
99
|
+
))
|
|
100
|
+
|
|
101
|
+
return results
|
|
102
|
+
|
|
103
|
+
except Exception:
|
|
104
|
+
return []
|
|
105
|
+
|
|
106
|
+
async def load_item(self, url: str) -> SeriesInfo:
|
|
107
|
+
# Note: Handling both Movie and Series logic in one, returning SeriesInfo generally or MovieInfo
|
|
108
|
+
resp = await self.cffi.get(url)
|
|
109
|
+
sel = Selector(resp.text)
|
|
110
|
+
|
|
111
|
+
title = sel.css("h1.text-white::text").get()
|
|
112
|
+
poster = sel.css("div.w-full.page-top img::attr(src)").get()
|
|
113
|
+
description = sel.css("div.mt-2.text-sm::text").get()
|
|
114
|
+
|
|
115
|
+
year = None # Implement if critical
|
|
116
|
+
|
|
117
|
+
tags = sel.css("h3.text-white.opacity-60::text").get()
|
|
118
|
+
if tags:
|
|
119
|
+
tags = [t.strip() for t in tags.split(",")]
|
|
120
|
+
|
|
121
|
+
rating = sel.css("div.flex.items-center span.text-white.text-sm::text").get()
|
|
122
|
+
actors = sel.css("div.global-box h5::text").getall()
|
|
123
|
+
|
|
124
|
+
# Check urls for episodes
|
|
125
|
+
all_urls = re.findall(r'"url":"([^"]*)"', resp.text)
|
|
126
|
+
is_series = any("bolum-" in u for u in all_urls)
|
|
127
|
+
|
|
128
|
+
episodes = []
|
|
129
|
+
if is_series:
|
|
130
|
+
seen_eps = set()
|
|
131
|
+
for u in all_urls:
|
|
132
|
+
if "bolum" in u and u not in seen_eps:
|
|
133
|
+
seen_eps.add(u)
|
|
134
|
+
season_match = re.search(r'/sezon-(\d+)', u)
|
|
135
|
+
ep_match = re.search(r'/bolum-(\d+)', u)
|
|
136
|
+
|
|
137
|
+
season = int(season_match.group(1)) if season_match else 1
|
|
138
|
+
episode_num = int(ep_match.group(1)) if ep_match else 1
|
|
139
|
+
|
|
140
|
+
episodes.append(Episode(
|
|
141
|
+
season=season,
|
|
142
|
+
episode=episode_num,
|
|
143
|
+
title=f"{season}. Sezon {episode_num}. Bölüm", # Placeholder title
|
|
144
|
+
url=self.fix_url(u, self.get_domain_sync(url))
|
|
145
|
+
))
|
|
146
|
+
|
|
147
|
+
return SeriesInfo(
|
|
148
|
+
title=title,
|
|
149
|
+
url=url,
|
|
150
|
+
poster=self.fix_url(poster),
|
|
151
|
+
description=description,
|
|
152
|
+
tags=tags,
|
|
153
|
+
rating=rating,
|
|
154
|
+
actors=actors,
|
|
155
|
+
episodes=episodes,
|
|
156
|
+
year=year
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
async def load_links(self, url: str) -> list[dict]:
|
|
160
|
+
resp = await self.cffi.get(url)
|
|
161
|
+
sel = Selector(resp.text)
|
|
162
|
+
|
|
163
|
+
next_data = sel.css("script#__NEXT_DATA__::text").get()
|
|
164
|
+
if not next_data:
|
|
165
|
+
return []
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
data = json.loads(next_data)
|
|
169
|
+
secure_data = data["props"]["pageProps"]["secureData"]
|
|
170
|
+
decoded = base64.b64decode(secure_data).decode('utf-8')
|
|
171
|
+
|
|
172
|
+
results = []
|
|
173
|
+
matches = re.findall(r'iframe src=\\"([^"]*)\\"', decoded)
|
|
174
|
+
for m in matches:
|
|
175
|
+
iframe_url = m.replace('\\', '')
|
|
176
|
+
if "http" not in iframe_url:
|
|
177
|
+
if iframe_url.startswith("//"):
|
|
178
|
+
iframe_url = "https:" + iframe_url
|
|
179
|
+
else:
|
|
180
|
+
iframe_url = "https://" + iframe_url # fallback
|
|
181
|
+
|
|
182
|
+
# Check extractor
|
|
183
|
+
extractor = self.ex_manager.find_extractor(iframe_url)
|
|
184
|
+
name = extractor.name if extractor else "Iframe"
|
|
185
|
+
|
|
186
|
+
results.append({
|
|
187
|
+
"url": iframe_url,
|
|
188
|
+
"name": name
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
return results
|
|
192
|
+
|
|
193
|
+
except Exception:
|
|
194
|
+
return []
|
|
195
|
+
|
|
196
|
+
def fix_url(self, url: str, domain:str=None) -> str:
|
|
197
|
+
if not url: return ""
|
|
198
|
+
if url.startswith("http"): return url
|
|
199
|
+
base = domain or self.main_url
|
|
200
|
+
return f"https:{url}" if url.startswith("//") else urllib.parse.urljoin(base, url)
|
|
201
|
+
|
|
202
|
+
def get_domain_sync(self, url:str):
|
|
203
|
+
parsed = urllib.parse.urlparse(url)
|
|
204
|
+
return f"{parsed.scheme}://{parsed.netloc}"
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
|
+
|
|
3
|
+
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode
|
|
4
|
+
from parsel import Selector
|
|
5
|
+
import re, base64, json, urllib.parse
|
|
6
|
+
|
|
7
|
+
class SelcukFlix(PluginBase):
|
|
8
|
+
name = "SelcukFlix"
|
|
9
|
+
main_url = "https://selcukflix.net"
|
|
10
|
+
lang = "tr"
|
|
11
|
+
|
|
12
|
+
main_page = {
|
|
13
|
+
"tum-bolumler" : "Yeni Eklenen Bölümler"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
|
|
17
|
+
full_url = f"{self.main_url}/{url}"
|
|
18
|
+
resp = await self.cffi.get(full_url)
|
|
19
|
+
sel = Selector(resp.text)
|
|
20
|
+
|
|
21
|
+
results = []
|
|
22
|
+
if "tum-bolumler" in url:
|
|
23
|
+
for item in sel.css("div.col-span-3 a"):
|
|
24
|
+
name = item.css("h2::text").get()
|
|
25
|
+
ep_info = item.css("div.opacity-80::text").get()
|
|
26
|
+
href = item.css("::attr(href)").get()
|
|
27
|
+
poster = item.css("div.image img::attr(src)").get()
|
|
28
|
+
|
|
29
|
+
if name and href:
|
|
30
|
+
title = f"{name} - {ep_info}" if ep_info else name
|
|
31
|
+
final_url = self.fix_url(href)
|
|
32
|
+
if "/dizi/" in final_url and "/sezon-" in final_url:
|
|
33
|
+
final_url = final_url.split("/sezon-")[0]
|
|
34
|
+
|
|
35
|
+
results.append(MainPageResult(
|
|
36
|
+
category=category,
|
|
37
|
+
title=title,
|
|
38
|
+
url=final_url,
|
|
39
|
+
poster=self.fix_url(poster)
|
|
40
|
+
))
|
|
41
|
+
|
|
42
|
+
return results
|
|
43
|
+
|
|
44
|
+
async def search(self, query: str) -> list[SearchResult]:
|
|
45
|
+
search_url = f"{self.main_url}/api/bg/searchcontent?searchterm={query}"
|
|
46
|
+
|
|
47
|
+
headers = {
|
|
48
|
+
"Accept": "application/json, text/plain, */*",
|
|
49
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
50
|
+
"Referer": f"{self.main_url}/"
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
post_resp = await self.cffi.post(search_url, headers=headers)
|
|
54
|
+
|
|
55
|
+
try:
|
|
56
|
+
resp_json = post_resp.json()
|
|
57
|
+
response_data = resp_json.get("response")
|
|
58
|
+
|
|
59
|
+
raw_data = base64.b64decode(response_data)
|
|
60
|
+
try:
|
|
61
|
+
decoded_str = raw_data.decode('utf-8')
|
|
62
|
+
except UnicodeDecodeError:
|
|
63
|
+
decoded_str = raw_data.decode('iso-8859-1').encode('utf-8').decode('utf-8')
|
|
64
|
+
|
|
65
|
+
search_data = json.loads(decoded_str)
|
|
66
|
+
|
|
67
|
+
results = []
|
|
68
|
+
for item in search_data.get("result", []):
|
|
69
|
+
title = item.get("title")
|
|
70
|
+
slug = item.get("slug")
|
|
71
|
+
poster = item.get("poster")
|
|
72
|
+
if poster:
|
|
73
|
+
poster = self.clean_image_url(poster)
|
|
74
|
+
|
|
75
|
+
if slug and "/seri-filmler/" not in slug:
|
|
76
|
+
results.append(SearchResult(
|
|
77
|
+
title=title,
|
|
78
|
+
url=self.fix_url(slug),
|
|
79
|
+
poster=poster
|
|
80
|
+
))
|
|
81
|
+
return results
|
|
82
|
+
|
|
83
|
+
except Exception:
|
|
84
|
+
return []
|
|
85
|
+
|
|
86
|
+
async def load_item(self, url: str) -> SeriesInfo:
|
|
87
|
+
resp = await self.cffi.get(url)
|
|
88
|
+
sel = Selector(resp.text)
|
|
89
|
+
|
|
90
|
+
next_data = sel.css("script#__NEXT_DATA__::text").get()
|
|
91
|
+
if not next_data:
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
data = json.loads(next_data)
|
|
95
|
+
secure_data = data["props"]["pageProps"]["secureData"]
|
|
96
|
+
raw_data = base64.b64decode(secure_data.replace('"', ''))
|
|
97
|
+
try:
|
|
98
|
+
decoded_str = raw_data.decode('utf-8')
|
|
99
|
+
except UnicodeDecodeError:
|
|
100
|
+
decoded_str = raw_data.decode('iso-8859-1') # .encode('utf-8').decode('utf-8') implied
|
|
101
|
+
|
|
102
|
+
content_details = json.loads(decoded_str)
|
|
103
|
+
item = content_details.get("contentItem", {})
|
|
104
|
+
|
|
105
|
+
title = item.get("original_title") or item.get("originalTitle")
|
|
106
|
+
poster = self.clean_image_url(item.get("poster_url") or item.get("posterUrl"))
|
|
107
|
+
description = item.get("description") or item.get("used_description")
|
|
108
|
+
rating = str(item.get("imdb_point") or item.get("imdbPoint", ""))
|
|
109
|
+
|
|
110
|
+
series_data = content_details.get("relatedData", {}).get("seriesData")
|
|
111
|
+
if not series_data and "RelatedResults" in content_details:
|
|
112
|
+
series_data = content_details["RelatedResults"].get("getSerieSeasonAndEpisodes", {}).get("result")
|
|
113
|
+
if series_data and isinstance(series_data, list):
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
episodes = []
|
|
117
|
+
if series_data:
|
|
118
|
+
seasons_list = []
|
|
119
|
+
if isinstance(series_data, dict):
|
|
120
|
+
seasons_list = series_data.get("seasons", [])
|
|
121
|
+
elif isinstance(series_data, list):
|
|
122
|
+
seasons_list = series_data
|
|
123
|
+
|
|
124
|
+
for season in seasons_list:
|
|
125
|
+
if not isinstance(season, dict): continue
|
|
126
|
+
s_no = season.get("season_no") or season.get("seasonNo") # Try snake_case too
|
|
127
|
+
ep_list = season.get("episodes", [])
|
|
128
|
+
for ep in ep_list:
|
|
129
|
+
episodes.append(Episode(
|
|
130
|
+
season = s_no,
|
|
131
|
+
episode = ep.get("episode_no") or ep.get("episodeNo"),
|
|
132
|
+
title = ep.get("ep_text") or ep.get("epText"),
|
|
133
|
+
url = self.fix_url(ep.get("used_slug") or ep.get("usedSlug"))
|
|
134
|
+
))
|
|
135
|
+
|
|
136
|
+
return SeriesInfo(
|
|
137
|
+
title=title,
|
|
138
|
+
url=url,
|
|
139
|
+
poster=poster,
|
|
140
|
+
description=description,
|
|
141
|
+
rating=rating,
|
|
142
|
+
episodes=episodes
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
async def load_links(self, url: str) -> list[dict]:
|
|
146
|
+
resp = await self.cffi.get(url)
|
|
147
|
+
sel = Selector(resp.text)
|
|
148
|
+
|
|
149
|
+
next_data = sel.css("script#__NEXT_DATA__::text").get()
|
|
150
|
+
if not next_data: return []
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
data = json.loads(next_data)
|
|
154
|
+
secure_data = data["props"]["pageProps"]["secureData"]
|
|
155
|
+
raw_data = base64.b64decode(secure_data.replace('"', ''))
|
|
156
|
+
try:
|
|
157
|
+
decoded_str = raw_data.decode('utf-8')
|
|
158
|
+
except UnicodeDecodeError:
|
|
159
|
+
decoded_str = raw_data.decode('iso-8859-1')
|
|
160
|
+
|
|
161
|
+
content_details = json.loads(decoded_str)
|
|
162
|
+
related_data = content_details.get("relatedData", {})
|
|
163
|
+
|
|
164
|
+
source_content = None
|
|
165
|
+
|
|
166
|
+
# Check if Series (episode) or Movie
|
|
167
|
+
if "/dizi/" in url:
|
|
168
|
+
if related_data.get("episodeSources", {}).get("state"):
|
|
169
|
+
res = related_data["episodeSources"].get("result", [])
|
|
170
|
+
if res:
|
|
171
|
+
source_content = res[0].get("sourceContent")
|
|
172
|
+
else:
|
|
173
|
+
# Movie
|
|
174
|
+
if related_data.get("movieParts", {}).get("state"):
|
|
175
|
+
# Looking for first part source
|
|
176
|
+
movie_parts = related_data["movieParts"].get("result", [])
|
|
177
|
+
if movie_parts:
|
|
178
|
+
first_part_id = movie_parts[0].get("id")
|
|
179
|
+
# RelatedResults -> getMoviePartSourcesById_ID
|
|
180
|
+
rr = content_details.get("RelatedResults", {})
|
|
181
|
+
key = f"getMoviePartSourcesById_{first_part_id}"
|
|
182
|
+
if key in rr:
|
|
183
|
+
res = rr[key].get("result", [])
|
|
184
|
+
if res:
|
|
185
|
+
source_content = res[0].get("source_content")
|
|
186
|
+
|
|
187
|
+
results = []
|
|
188
|
+
if source_content:
|
|
189
|
+
iframe_sel = Selector(source_content)
|
|
190
|
+
iframe_src = iframe_sel.css("iframe::attr(src)").get()
|
|
191
|
+
if iframe_src:
|
|
192
|
+
iframe_src = self.fix_url(iframe_src)
|
|
193
|
+
# Domain replace
|
|
194
|
+
if "sn.dplayer74.site" in iframe_src:
|
|
195
|
+
iframe_src = iframe_src.replace("sn.dplayer74.site", "sn.hotlinger.com")
|
|
196
|
+
|
|
197
|
+
extractor = self.ex_manager.find_extractor(iframe_src)
|
|
198
|
+
results.append({
|
|
199
|
+
"url": iframe_src,
|
|
200
|
+
"name": extractor.name if extractor else "Iframe"
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
return results
|
|
204
|
+
|
|
205
|
+
except Exception:
|
|
206
|
+
return []
|
|
207
|
+
|
|
208
|
+
def clean_image_url(self, url: str) -> str:
|
|
209
|
+
if not url: return None
|
|
210
|
+
url = url.replace("images-macellan-online.cdn.ampproject.org/i/s/", "")
|
|
211
|
+
url = url.replace("file.dizilla.club", "file.macellan.online")
|
|
212
|
+
url = url.replace("images.dizilla.club", "images.macellan.online")
|
|
213
|
+
url = url.replace("images.dizimia4.com", "images.macellan.online")
|
|
214
|
+
url = url.replace("file.dizimia4.com", "file.macellan.online")
|
|
215
|
+
url = url.replace("/f/f/", "/630/910/")
|
|
216
|
+
return self.fix_url(url)
|
|
@@ -134,6 +134,9 @@ class SezonlukDizi(PluginBase):
|
|
|
134
134
|
secici = Selector(veri_response.text)
|
|
135
135
|
|
|
136
136
|
if iframe := secici.css("iframe::attr(src)").get():
|
|
137
|
+
if "link.asp" in iframe:
|
|
138
|
+
continue
|
|
139
|
+
|
|
137
140
|
extractor = self.ex_manager.find_extractor(self.fix_url(iframe))
|
|
138
141
|
results.append({
|
|
139
142
|
"url" : self.fix_url(iframe),
|