KekikStream 2.3.9__py3-none-any.whl → 2.4.1__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 +2 -2
- KekikStream/Core/Plugin/PluginModels.py +0 -3
- KekikStream/Extractors/HDMomPlayer.py +62 -0
- KekikStream/Extractors/HotStream.py +45 -0
- KekikStream/Extractors/Videostr.py +115 -0
- KekikStream/Extractors/Vidoza.py +25 -0
- KekikStream/Plugins/DiziMom.py +247 -0
- KekikStream/Plugins/FilmEkseni.py +140 -0
- KekikStream/Plugins/Filmatek.py +205 -0
- KekikStream/Plugins/Full4kizle.py +274 -0
- KekikStream/Plugins/RecTV.py +32 -19
- KekikStream/Plugins/Watch32.py +207 -0
- {kekikstream-2.3.9.dist-info → kekikstream-2.4.1.dist-info}/METADATA +3 -3
- {kekikstream-2.3.9.dist-info → kekikstream-2.4.1.dist-info}/RECORD +18 -9
- {kekikstream-2.3.9.dist-info → kekikstream-2.4.1.dist-info}/WHEEL +1 -1
- {kekikstream-2.3.9.dist-info → kekikstream-2.4.1.dist-info}/entry_points.txt +0 -0
- {kekikstream-2.3.9.dist-info → kekikstream-2.4.1.dist-info}/licenses/LICENSE +0 -0
- {kekikstream-2.3.9.dist-info → kekikstream-2.4.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,205 @@
|
|
|
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, HTMLHelper
|
|
4
|
+
import re
|
|
5
|
+
from json import loads
|
|
6
|
+
from urllib.parse import unquote
|
|
7
|
+
|
|
8
|
+
class Filmatek(PluginBase):
|
|
9
|
+
name = "Filmatek"
|
|
10
|
+
language = "tr"
|
|
11
|
+
main_url = "https://filmatek.net"
|
|
12
|
+
favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
|
|
13
|
+
description = "Sosyalizmin Sineması Veritabanı"
|
|
14
|
+
|
|
15
|
+
# Main page categories
|
|
16
|
+
main_page = {
|
|
17
|
+
f"{main_url}/tur/aile/page" : "Aile",
|
|
18
|
+
f"{main_url}/tur/aksiyon/page" : "Aksiyon",
|
|
19
|
+
f"{main_url}/tur/animasyon/page" : "Animasyon",
|
|
20
|
+
f"{main_url}/tur/bilim-kurgu/page" : "Bilim Kurgu",
|
|
21
|
+
f"{main_url}/tur/komedi/page" : "Komedi",
|
|
22
|
+
f"{main_url}/tur/korku/page" : "Korku",
|
|
23
|
+
f"{main_url}/tur/macera/page" : "Macera",
|
|
24
|
+
f"{main_url}/tur/romantik/page" : "Romantik",
|
|
25
|
+
f"{main_url}/tur/suc/page" : "Suç",
|
|
26
|
+
f"{main_url}/tur/yerli-filmler/page" : "Yerli Filmler",
|
|
27
|
+
f"{main_url}/film-arsivi/page" : "Tüm Filmler",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
|
|
31
|
+
target_url = f"{url}/{page}/"
|
|
32
|
+
istek = await self.httpx.get(target_url)
|
|
33
|
+
helper = HTMLHelper(istek.text)
|
|
34
|
+
|
|
35
|
+
items = helper.select("div.items article, #archive-content article")
|
|
36
|
+
results = []
|
|
37
|
+
|
|
38
|
+
for item in items:
|
|
39
|
+
title_el = helper.select_first("div.data h3 a, h3 a", item)
|
|
40
|
+
if not title_el: continue
|
|
41
|
+
|
|
42
|
+
title = title_el.text(strip=True)
|
|
43
|
+
href = self.fix_url(title_el.attrs.get("href"))
|
|
44
|
+
|
|
45
|
+
img_el = helper.select_first("img", item)
|
|
46
|
+
poster = self.fix_url(img_el.attrs.get("data-src") or img_el.attrs.get("src")) if img_el else None
|
|
47
|
+
|
|
48
|
+
results.append(MainPageResult(
|
|
49
|
+
category = category,
|
|
50
|
+
title = title,
|
|
51
|
+
url = href,
|
|
52
|
+
poster = poster
|
|
53
|
+
))
|
|
54
|
+
|
|
55
|
+
return results
|
|
56
|
+
|
|
57
|
+
async def search(self, query: str) -> list[SearchResult]:
|
|
58
|
+
url = f"{self.main_url}/?s={query}"
|
|
59
|
+
istek = await self.httpx.get(url)
|
|
60
|
+
helper = HTMLHelper(istek.text)
|
|
61
|
+
|
|
62
|
+
items = helper.select("div.result-item")
|
|
63
|
+
results = []
|
|
64
|
+
|
|
65
|
+
for item in items:
|
|
66
|
+
title_el = helper.select_first("div.title a", item)
|
|
67
|
+
if not title_el: continue
|
|
68
|
+
|
|
69
|
+
title = title_el.text(strip=True)
|
|
70
|
+
href = self.fix_url(title_el.attrs.get("href"))
|
|
71
|
+
|
|
72
|
+
img_el = helper.select_first("div.image img", item)
|
|
73
|
+
poster = self.fix_url(img_el.attrs.get("src")) if img_el else None
|
|
74
|
+
|
|
75
|
+
results.append(SearchResult(
|
|
76
|
+
title = title,
|
|
77
|
+
url = href,
|
|
78
|
+
poster = poster
|
|
79
|
+
))
|
|
80
|
+
|
|
81
|
+
return results
|
|
82
|
+
|
|
83
|
+
async def load_item(self, url: str) -> MovieInfo:
|
|
84
|
+
istek = await self.httpx.get(url)
|
|
85
|
+
helper = HTMLHelper(istek.text)
|
|
86
|
+
|
|
87
|
+
title = helper.select_text("div.data h1, h1") or "Bilinmiyor"
|
|
88
|
+
|
|
89
|
+
poster_el = helper.select_first("div.poster img")
|
|
90
|
+
poster = self.fix_url(poster_el.attrs.get("src")) if poster_el else None
|
|
91
|
+
if not poster:
|
|
92
|
+
poster = helper.select_attr("meta[property='og:image']", "content")
|
|
93
|
+
|
|
94
|
+
description = helper.select_text("div.wp-content p")
|
|
95
|
+
if not description:
|
|
96
|
+
description = helper.select_attr("meta[property='og:description']", "content")
|
|
97
|
+
|
|
98
|
+
year_text = helper.select_text("span.date")
|
|
99
|
+
year = year_text.strip()[-4:] if year_text else None
|
|
100
|
+
|
|
101
|
+
# Rating extraction updated
|
|
102
|
+
rating = helper.select_text("span.dt_rating_vgs") or helper.select_text("span.dt_rating_vmanual")
|
|
103
|
+
|
|
104
|
+
# Duration extraction
|
|
105
|
+
duration = None
|
|
106
|
+
duration_text = helper.select_text("span.runtime")
|
|
107
|
+
if duration_text:
|
|
108
|
+
# "80 Dak." -> "80"
|
|
109
|
+
duration = duration_text.split()[0]
|
|
110
|
+
|
|
111
|
+
tags = helper.select_all_text("div.sgeneros a")
|
|
112
|
+
|
|
113
|
+
# Actors
|
|
114
|
+
actors_list = []
|
|
115
|
+
actor_els = helper.select("div.person")
|
|
116
|
+
for el in actor_els:
|
|
117
|
+
name = helper.select_text("div.name a", el)
|
|
118
|
+
if name:
|
|
119
|
+
actors_list.append(name.strip())
|
|
120
|
+
actors = ", ".join(actors_list) if actors_list else None
|
|
121
|
+
|
|
122
|
+
return MovieInfo(
|
|
123
|
+
url = url,
|
|
124
|
+
title = title,
|
|
125
|
+
description = description,
|
|
126
|
+
poster = poster,
|
|
127
|
+
year = year,
|
|
128
|
+
rating = rating,
|
|
129
|
+
duration = duration,
|
|
130
|
+
tags = tags,
|
|
131
|
+
actors = actors
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
async def load_links(self, url: str) -> list[ExtractResult]:
|
|
135
|
+
istek = await self.httpx.get(url)
|
|
136
|
+
html = istek.text
|
|
137
|
+
helper = HTMLHelper(html)
|
|
138
|
+
|
|
139
|
+
# Get Post ID from body class usually "postid-123"
|
|
140
|
+
body_class = helper.select_attr("body", "class") or ""
|
|
141
|
+
post_id_match = re.search(r"postid-(\d+)", body_class)
|
|
142
|
+
|
|
143
|
+
results = []
|
|
144
|
+
|
|
145
|
+
if post_id_match:
|
|
146
|
+
post_id = post_id_match.group(1)
|
|
147
|
+
|
|
148
|
+
# AJAX request for player
|
|
149
|
+
ajax_url = f"{self.main_url}/wp-admin/admin-ajax.php"
|
|
150
|
+
data = {
|
|
151
|
+
"action": "doo_player_ajax",
|
|
152
|
+
"post": post_id,
|
|
153
|
+
"nume": "1", # Usually implies source number 1? Kotlin uses "1" hardcoded.
|
|
154
|
+
"type": "movie"
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
headers = {
|
|
158
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
159
|
+
"Referer": url,
|
|
160
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
# Need to use post with data
|
|
165
|
+
player_resp = await self.httpx.post(ajax_url, data=data, headers=headers)
|
|
166
|
+
|
|
167
|
+
# Kotlin parses it as text and cleans slashes
|
|
168
|
+
content = player_resp.text.replace(r"\/", "/")
|
|
169
|
+
|
|
170
|
+
# Regex for URL
|
|
171
|
+
# Kotlin: (?:src|url)["']?\s*[:=]\s*["']([^"']+)["']
|
|
172
|
+
src_match = re.search(r'(?:src|url)["\']?\s*[:=]\s*["\']([^"\']+)["\']', content)
|
|
173
|
+
|
|
174
|
+
if src_match:
|
|
175
|
+
iframe_url = src_match.group(1)
|
|
176
|
+
if iframe_url.startswith("/"):
|
|
177
|
+
iframe_url = self.main_url + iframe_url
|
|
178
|
+
|
|
179
|
+
iframe_url = self.fix_url(iframe_url)
|
|
180
|
+
|
|
181
|
+
# Unwrap internal JWPlayer
|
|
182
|
+
if "jwplayer/?source=" in iframe_url:
|
|
183
|
+
try:
|
|
184
|
+
raw_source = iframe_url.split("source=")[1].split("&")[0]
|
|
185
|
+
iframe_url = unquote(raw_source)
|
|
186
|
+
except:
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
extracted = await self.extract(iframe_url)
|
|
190
|
+
if extracted:
|
|
191
|
+
if isinstance(extracted, list):
|
|
192
|
+
results.extend(extracted)
|
|
193
|
+
else:
|
|
194
|
+
results.append(extracted)
|
|
195
|
+
else:
|
|
196
|
+
results.append(ExtractResult(
|
|
197
|
+
name = "Filmatek | External",
|
|
198
|
+
url = iframe_url,
|
|
199
|
+
referer = url
|
|
200
|
+
))
|
|
201
|
+
except Exception as e:
|
|
202
|
+
# print(f"Filmatek Error: {e}")
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
return results
|
|
@@ -0,0 +1,274 @@
|
|
|
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, HTMLHelper
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
class Full4kizle(PluginBase):
|
|
7
|
+
name = "Full4kizle"
|
|
8
|
+
language = "tr"
|
|
9
|
+
main_url = "https://izlehdfilm.cc"
|
|
10
|
+
favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
|
|
11
|
+
description = "Filmci Baba, film izleme sitesi 4k Full film izle, 1080p ve 4k kalite de sinema filmleri ve dizileri, tek parça hd kalitede türkçe dublajlı filmler seyret."
|
|
12
|
+
|
|
13
|
+
main_page = {
|
|
14
|
+
f"{main_url}/Kategori/en-populer-filmler/page" : "En Popüler Filmler",
|
|
15
|
+
f"{main_url}/Kategori/tur/aksiyon-filmleri/page" : "Aksiyon",
|
|
16
|
+
f"{main_url}/Kategori/tur/macera-filmleri/page" : "Macera",
|
|
17
|
+
f"{main_url}/Kategori/tur/bilim-kurgu-filmleri/page" : "Bilim Kurgu",
|
|
18
|
+
f"{main_url}/Kategori/tur/fantastik-filmler/page" : "Fantastik",
|
|
19
|
+
f"{main_url}/Kategori/tur/korku-filmleri/page" : "Korku",
|
|
20
|
+
f"{main_url}/Kategori/tur/gerilim-filmleri-hd/page" : "Gerilim",
|
|
21
|
+
f"{main_url}/Kategori/tur/gizem-filmleri/page" : "Gizem",
|
|
22
|
+
f"{main_url}/Kategori/tur/dram-filmleri-hd/page" : "Dram",
|
|
23
|
+
f"{main_url}/Kategori/tur/komedi-filmleri-hd/page" : "Komedi",
|
|
24
|
+
f"{main_url}/Kategori/tur/romantik-filmler/page" : "Romantik",
|
|
25
|
+
f"{main_url}/Kategori/tur/aile-filmleri/page" : "Aile",
|
|
26
|
+
f"{main_url}/Kategori/tur/animasyon-filmleri/page" : "Animasyon",
|
|
27
|
+
f"{main_url}/Kategori/tur/biyografi-filmleri/page" : "Biyografi",
|
|
28
|
+
f"{main_url}/Kategori/tur/polisiye-suc-filmleri/page" : "Polisiye / Suç",
|
|
29
|
+
f"{main_url}/Kategori/tur/savas-filmleri/page" : "Savaş",
|
|
30
|
+
f"{main_url}/Kategori/tur/western-filmler/page" : "Western",
|
|
31
|
+
f"{main_url}/Kategori/tur/hint-filmleri/page" : "Hint Filmleri",
|
|
32
|
+
f"{main_url}/Kategori/tur/kore-filmleri/page" : "Kore Filmleri",
|
|
33
|
+
f"{main_url}/Kategori/tur/yerli-filmler-izle/page" : "Yerli Filmler",
|
|
34
|
+
f"{main_url}/Kategori/tur/yerli-diziler/page" : "Yerli Diziler",
|
|
35
|
+
f"{main_url}/Kategori/tur/18-erotik-filmler/page" : "+18 Erotik Filmler",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
|
|
40
|
+
target_url = f"{url}/{page}/"
|
|
41
|
+
istek = await self.httpx.get(target_url)
|
|
42
|
+
helper = HTMLHelper(istek.text)
|
|
43
|
+
|
|
44
|
+
items = helper.select("div.movie-preview")
|
|
45
|
+
results = []
|
|
46
|
+
|
|
47
|
+
for item in items:
|
|
48
|
+
title_el = helper.select_first(".movie-title a", item)
|
|
49
|
+
if not title_el: continue
|
|
50
|
+
|
|
51
|
+
title = title_el.text(strip=True)
|
|
52
|
+
# Remove " izle" case insensitive
|
|
53
|
+
title = re.sub(r"(?i) izle", "", title).strip()
|
|
54
|
+
|
|
55
|
+
href = self.fix_url(title_el.attrs.get("href"))
|
|
56
|
+
|
|
57
|
+
poster_el = helper.select_first(".movie-poster img", item)
|
|
58
|
+
poster = self.fix_url(poster_el.attrs.get("src")) if poster_el else None
|
|
59
|
+
|
|
60
|
+
results.append(MainPageResult(
|
|
61
|
+
category = category,
|
|
62
|
+
title = title,
|
|
63
|
+
url = href,
|
|
64
|
+
poster = poster
|
|
65
|
+
))
|
|
66
|
+
|
|
67
|
+
return results
|
|
68
|
+
|
|
69
|
+
async def search(self, query: str) -> list[SearchResult]:
|
|
70
|
+
url = f"{self.main_url}/?s={query}"
|
|
71
|
+
istek = await self.httpx.get(url)
|
|
72
|
+
helper = HTMLHelper(istek.text)
|
|
73
|
+
|
|
74
|
+
items = helper.select("div.movie-preview")
|
|
75
|
+
results = []
|
|
76
|
+
|
|
77
|
+
for item in items:
|
|
78
|
+
title_el = helper.select_first(".movie-title a", item)
|
|
79
|
+
if not title_el: continue
|
|
80
|
+
|
|
81
|
+
title = title_el.text(strip=True)
|
|
82
|
+
# Remove " izle" case insensitive
|
|
83
|
+
title = re.sub(r"(?i) izle", "", title).strip()
|
|
84
|
+
|
|
85
|
+
href = self.fix_url(title_el.attrs.get("href"))
|
|
86
|
+
|
|
87
|
+
poster_el = helper.select_first(".movie-poster img", item)
|
|
88
|
+
poster = self.fix_url(poster_el.attrs.get("src")) if poster_el else None
|
|
89
|
+
|
|
90
|
+
results.append(SearchResult(
|
|
91
|
+
title = title,
|
|
92
|
+
url = href,
|
|
93
|
+
poster = poster
|
|
94
|
+
))
|
|
95
|
+
|
|
96
|
+
return results
|
|
97
|
+
|
|
98
|
+
async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
|
|
99
|
+
istek = await self.httpx.get(url)
|
|
100
|
+
helper = HTMLHelper(istek.text)
|
|
101
|
+
|
|
102
|
+
title_raw = helper.select_text("h1") or "Bilinmiyor"
|
|
103
|
+
title = re.sub(r"(?i)izle", "", title_raw).strip()
|
|
104
|
+
|
|
105
|
+
poster_el = helper.select_first(".poster img")
|
|
106
|
+
poster = self.fix_url(poster_el.attrs.get("src")) if poster_el else None
|
|
107
|
+
|
|
108
|
+
description = helper.select_text(".excerpt p")
|
|
109
|
+
|
|
110
|
+
# Robust metadata extraction using Regex
|
|
111
|
+
|
|
112
|
+
# Initialize year first
|
|
113
|
+
year = None
|
|
114
|
+
|
|
115
|
+
# Try .release first (legacy) or directly regex
|
|
116
|
+
rel_text = helper.select_text(".release")
|
|
117
|
+
if rel_text:
|
|
118
|
+
m_y = re.search(r"(\d{4})", rel_text)
|
|
119
|
+
if m_y: year = m_y.group(1)
|
|
120
|
+
|
|
121
|
+
# Year fallbacks
|
|
122
|
+
if not year:
|
|
123
|
+
# Try finding year in text like "Yapım: 2024" or just isolated year in release date
|
|
124
|
+
m_year = helper.regex_first(r"Yapım:\s*(\d{4})") or helper.regex_first(r"Yıl:\s*(\d{4})")
|
|
125
|
+
if m_year:
|
|
126
|
+
year = m_year
|
|
127
|
+
|
|
128
|
+
# Rating
|
|
129
|
+
rating_text = helper.select_text(".imdb-rating")
|
|
130
|
+
if rating_text:
|
|
131
|
+
rating = rating_text.replace("IMDB Puanı", "").strip()
|
|
132
|
+
else:
|
|
133
|
+
rating = helper.regex_first(r"IMDB\s*:\s*([\d\.]+)")
|
|
134
|
+
|
|
135
|
+
# Duration
|
|
136
|
+
duration = None
|
|
137
|
+
duration_val = helper.regex_first(r"Süre:\s*(\d+)")
|
|
138
|
+
if duration_val:
|
|
139
|
+
duration = int(duration_val)
|
|
140
|
+
|
|
141
|
+
# Actors - Extract from actor links
|
|
142
|
+
actors = None
|
|
143
|
+
actors_list = []
|
|
144
|
+
|
|
145
|
+
# Site uses: <a href=".../oyuncular/...">Actor Name</a>
|
|
146
|
+
actor_els = helper.select("a[href*='/oyuncular/']")
|
|
147
|
+
if actor_els:
|
|
148
|
+
actors_list = [el.text(strip=True) for el in actor_els if el.text(strip=True)]
|
|
149
|
+
|
|
150
|
+
# Fallback: Try .cast-list selector
|
|
151
|
+
if not actors_list:
|
|
152
|
+
actor_els = helper.select(".cast-list .actor-name, .cast-list a")
|
|
153
|
+
if actor_els:
|
|
154
|
+
actors_list = [el.text(strip=True) for el in actor_els if el.text(strip=True)]
|
|
155
|
+
|
|
156
|
+
if actors_list:
|
|
157
|
+
actors = ", ".join(actors_list)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# Tags (Genres) - Extract from genre links
|
|
161
|
+
tags = None
|
|
162
|
+
tags_list = []
|
|
163
|
+
|
|
164
|
+
# Site uses: <a href=".../tur/...">Genre Name</a> or <a href=".../Kategori/tur/...">
|
|
165
|
+
tag_els = helper.select("a[href*='/tur/'], a[href*='/Kategori/tur/']")
|
|
166
|
+
if tag_els:
|
|
167
|
+
tags_list = [el.text(strip=True) for el in tag_els if el.text(strip=True)]
|
|
168
|
+
|
|
169
|
+
# Fallback: Try .genres selector
|
|
170
|
+
if not tags_list:
|
|
171
|
+
tag_els = helper.select(".genres a, .genre a")
|
|
172
|
+
if tag_els:
|
|
173
|
+
tags_list = [el.text(strip=True) for el in tag_els if el.text(strip=True)]
|
|
174
|
+
|
|
175
|
+
# Remove duplicates while preserving order
|
|
176
|
+
if tags_list:
|
|
177
|
+
seen = set()
|
|
178
|
+
unique_tags = []
|
|
179
|
+
for tag in tags_list:
|
|
180
|
+
if tag not in seen:
|
|
181
|
+
seen.add(tag)
|
|
182
|
+
unique_tags.append(tag)
|
|
183
|
+
tags = unique_tags if unique_tags else None
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# Check for Episodes to decide if Series or Movie
|
|
187
|
+
ep_elements = helper.select(".parts-middle a, .parts-middle .part.active")
|
|
188
|
+
|
|
189
|
+
if not ep_elements:
|
|
190
|
+
# Movie
|
|
191
|
+
return MovieInfo(
|
|
192
|
+
url = url,
|
|
193
|
+
title = title,
|
|
194
|
+
description = description,
|
|
195
|
+
poster = poster,
|
|
196
|
+
year = year,
|
|
197
|
+
rating = rating,
|
|
198
|
+
duration = duration,
|
|
199
|
+
tags = tags,
|
|
200
|
+
actors = actors
|
|
201
|
+
)
|
|
202
|
+
else:
|
|
203
|
+
# Series
|
|
204
|
+
episodes = []
|
|
205
|
+
for i, el in enumerate(ep_elements):
|
|
206
|
+
ep_name = helper.select_text(".part-name", el) or f"Bölüm {i+1}"
|
|
207
|
+
ep_href = el.attrs.get("href")
|
|
208
|
+
if not ep_href:
|
|
209
|
+
ep_href = url # Current page if href is empty/active?
|
|
210
|
+
ep_href = self.fix_url(ep_href)
|
|
211
|
+
|
|
212
|
+
# Parse season/episode from name if possible
|
|
213
|
+
# Kotlin: find digit for season, substringAfter("Sezon") digit for episode
|
|
214
|
+
season = 1
|
|
215
|
+
episode = i + 1
|
|
216
|
+
|
|
217
|
+
# Simple heuristic similar to Kotlin
|
|
218
|
+
# "1. Sezon 5. Bölüm"
|
|
219
|
+
s_match = re.search(r"(\d+)\.\s*Sezon", ep_name)
|
|
220
|
+
e_match = re.search(r"(\d+)\.\s*Bölüm", ep_name)
|
|
221
|
+
|
|
222
|
+
if s_match:
|
|
223
|
+
season = int(s_match.group(1))
|
|
224
|
+
if e_match:
|
|
225
|
+
episode = int(e_match.group(1))
|
|
226
|
+
|
|
227
|
+
episodes.append(Episode(
|
|
228
|
+
season = season,
|
|
229
|
+
episode = episode,
|
|
230
|
+
title = ep_name,
|
|
231
|
+
url = ep_href
|
|
232
|
+
))
|
|
233
|
+
|
|
234
|
+
return SeriesInfo(
|
|
235
|
+
url = url,
|
|
236
|
+
title = title,
|
|
237
|
+
description = description,
|
|
238
|
+
poster = poster,
|
|
239
|
+
year = year,
|
|
240
|
+
rating = rating,
|
|
241
|
+
duration = duration,
|
|
242
|
+
tags = tags,
|
|
243
|
+
actors = actors,
|
|
244
|
+
episodes = episodes
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
async def load_links(self, url: str) -> list[ExtractResult]:
|
|
248
|
+
istek = await self.httpx.get(url)
|
|
249
|
+
helper = HTMLHelper(istek.text)
|
|
250
|
+
|
|
251
|
+
iframe = helper.select_attr(".center-container iframe", "src")
|
|
252
|
+
if not iframe:
|
|
253
|
+
iframe = helper.select_attr("iframe[src*='hotstream.club']", "src")
|
|
254
|
+
|
|
255
|
+
results = []
|
|
256
|
+
|
|
257
|
+
if iframe:
|
|
258
|
+
iframe = self.fix_url(iframe)
|
|
259
|
+
|
|
260
|
+
# Use general extract method
|
|
261
|
+
extracted = await self.extract(iframe)
|
|
262
|
+
if extracted:
|
|
263
|
+
if isinstance(extracted, list):
|
|
264
|
+
results.extend(extracted)
|
|
265
|
+
else:
|
|
266
|
+
results.append(extracted)
|
|
267
|
+
else:
|
|
268
|
+
results.append(ExtractResult(
|
|
269
|
+
name = "Full4kizle | External",
|
|
270
|
+
url = iframe,
|
|
271
|
+
referer = url
|
|
272
|
+
))
|
|
273
|
+
|
|
274
|
+
return results
|
KekikStream/Plugins/RecTV.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, Episode, SeriesInfo, ExtractResult, HTMLHelper
|
|
4
4
|
from json import dumps, loads
|
|
5
|
+
import re
|
|
5
6
|
|
|
6
7
|
class RecTV(PluginBase):
|
|
7
8
|
name = "RecTV"
|
|
@@ -75,26 +76,38 @@ class RecTV(PluginBase):
|
|
|
75
76
|
|
|
76
77
|
episodes = []
|
|
77
78
|
for season in dizi_veri:
|
|
79
|
+
season_title = season.get("title", "").strip()
|
|
78
80
|
for episode in season.get("episodes"):
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
81
|
+
ep_label = episode.get("title", "").strip()
|
|
82
|
+
for source in episode.get("sources"):
|
|
83
|
+
# Bölüm için gerekli bilgileri JSON olarak sakla
|
|
84
|
+
ep_data = {
|
|
85
|
+
"url" : self.fix_url(source.get("url")),
|
|
86
|
+
"title" : f"{veri.get('title')} | {season_title} {ep_label} - {source.get('title')}",
|
|
87
|
+
"is_episode" : True
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
# Extract season/episode numbers using helper
|
|
91
|
+
s1, _ = HTMLHelper.extract_season_episode(season_title or "")
|
|
92
|
+
_, e2 = HTMLHelper.extract_season_episode(ep_label or "")
|
|
93
|
+
|
|
94
|
+
tag = ""
|
|
95
|
+
clean_season = season_title
|
|
96
|
+
if "dublaj" in season_title.lower():
|
|
97
|
+
tag = " (Dublaj)"
|
|
98
|
+
clean_season = re.sub(r"\s*dublaj\s*", "", season_title, flags=re.I).strip()
|
|
99
|
+
elif any(x in season_title.lower() for x in ["altyazı", "altyazi"]):
|
|
100
|
+
tag = " (Altyazı)"
|
|
101
|
+
clean_season = re.sub(r"\s*altyaz[ıi]\s*", "", season_title, flags=re.I).strip()
|
|
102
|
+
|
|
103
|
+
ep_model = Episode(
|
|
104
|
+
season = s1 or 1,
|
|
105
|
+
episode = e2 or 1,
|
|
106
|
+
title = f"{clean_season} {ep_label}{tag} - {source.get('title')}",
|
|
107
|
+
url = dumps(ep_data),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
episodes.append(ep_model)
|
|
98
111
|
|
|
99
112
|
# Süreyi dakikaya çevir (Örn: "1h 59min")
|
|
100
113
|
duration_raw = veri.get("duration")
|