KekikStream 2.3.9__py3-none-any.whl → 2.4.0__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.
@@ -0,0 +1,188 @@
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
+
7
+ class Filmatek(PluginBase):
8
+ name = "Filmatek"
9
+ language = "tr"
10
+ main_url = "https://filmatek.net"
11
+ favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
12
+ description = "Sosyalizmin Sineması Veritabanı"
13
+
14
+ # Main page categories
15
+ main_page = {
16
+ f"{main_url}/tur/aile/page" : "Aile",
17
+ f"{main_url}/tur/aksiyon/page" : "Aksiyon",
18
+ f"{main_url}/tur/animasyon/page" : "Animasyon",
19
+ f"{main_url}/tur/bilim-kurgu/page" : "Bilim Kurgu",
20
+ f"{main_url}/tur/komedi/page" : "Komedi",
21
+ f"{main_url}/tur/korku/page" : "Korku",
22
+ f"{main_url}/tur/macera/page" : "Macera",
23
+ f"{main_url}/tur/romantik/page" : "Romantik",
24
+ f"{main_url}/tur/suc/page" : "Suç",
25
+ f"{main_url}/tur/yerli-filmler/page" : "Yerli Filmler",
26
+ f"{main_url}/film-arsivi/page" : "Tüm Filmler",
27
+ }
28
+
29
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
30
+ target_url = f"{url}/{page}/"
31
+ istek = await self.httpx.get(target_url)
32
+ helper = HTMLHelper(istek.text)
33
+
34
+ items = helper.select("div.items article, #archive-content article")
35
+ results = []
36
+
37
+ for item in items:
38
+ title_el = helper.select_first("div.data h3 a, h3 a", item)
39
+ if not title_el: continue
40
+
41
+ title = title_el.text(strip=True)
42
+ href = self.fix_url(title_el.attrs.get("href"))
43
+
44
+ img_el = helper.select_first("img", item)
45
+ poster = self.fix_url(img_el.attrs.get("data-src") or img_el.attrs.get("src")) if img_el else None
46
+
47
+ results.append(MainPageResult(
48
+ category = category,
49
+ title = title,
50
+ url = href,
51
+ poster = poster
52
+ ))
53
+
54
+ return results
55
+
56
+ async def search(self, query: str) -> list[SearchResult]:
57
+ url = f"{self.main_url}/?s={query}"
58
+ istek = await self.httpx.get(url)
59
+ helper = HTMLHelper(istek.text)
60
+
61
+ items = helper.select("div.result-item")
62
+ results = []
63
+
64
+ for item in items:
65
+ title_el = helper.select_first("div.title a", item)
66
+ if not title_el: continue
67
+
68
+ title = title_el.text(strip=True)
69
+ href = self.fix_url(title_el.attrs.get("href"))
70
+
71
+ img_el = helper.select_first("div.image img", item)
72
+ poster = self.fix_url(img_el.attrs.get("src")) if img_el else None
73
+
74
+ results.append(SearchResult(
75
+ title = title,
76
+ url = href,
77
+ poster = poster
78
+ ))
79
+
80
+ return results
81
+
82
+ async def load_item(self, url: str) -> MovieInfo:
83
+ istek = await self.httpx.get(url)
84
+ helper = HTMLHelper(istek.text)
85
+
86
+ title = helper.select_text("div.data h1, h1") or "Bilinmiyor"
87
+
88
+ poster_el = helper.select_first("div.poster img")
89
+ poster = self.fix_url(poster_el.attrs.get("src")) if poster_el else None
90
+ if not poster:
91
+ poster = helper.select_attr("meta[property='og:image']", "content")
92
+
93
+ description = helper.select_text("div.wp-content p")
94
+ if not description:
95
+ description = helper.select_attr("meta[property='og:description']", "content")
96
+
97
+ year_text = helper.select_text("span.date")
98
+ year = year_text.strip()[-4:] if year_text else None
99
+
100
+ score_text = helper.select_text("span.dt_rating_vmanual")
101
+ rating = score_text.strip() if score_text else None
102
+
103
+ tags = helper.select_all_text("div.sgeneros a")
104
+
105
+ # Actors
106
+ actors_list = []
107
+ actor_els = helper.select("div.person")
108
+ for el in actor_els:
109
+ name = helper.select_text("div.name a", el)
110
+ if name:
111
+ actors_list.append(name.strip())
112
+ actors = ", ".join(actors_list) if actors_list else None
113
+
114
+ return MovieInfo(
115
+ url = url,
116
+ title = title,
117
+ description = description,
118
+ poster = poster,
119
+ year = year,
120
+ rating = rating,
121
+ tags = tags,
122
+ actors = actors
123
+ )
124
+
125
+ async def load_links(self, url: str) -> list[ExtractResult]:
126
+ istek = await self.httpx.get(url)
127
+ html = istek.text
128
+ helper = HTMLHelper(html)
129
+
130
+ # Get Post ID from body class usually "postid-123"
131
+ body_class = helper.select_attr("body", "class") or ""
132
+ post_id_match = re.search(r"postid-(\d+)", body_class)
133
+
134
+ results = []
135
+
136
+ if post_id_match:
137
+ post_id = post_id_match.group(1)
138
+
139
+ # AJAX request for player
140
+ ajax_url = f"{self.main_url}/wp-admin/admin-ajax.php"
141
+ data = {
142
+ "action": "doo_player_ajax",
143
+ "post": post_id,
144
+ "nume": "1", # Usually implies source number 1? Kotlin uses "1" hardcoded.
145
+ "type": "movie"
146
+ }
147
+
148
+ headers = {
149
+ "X-Requested-With": "XMLHttpRequest",
150
+ "Referer": url,
151
+ "Content-Type": "application/x-www-form-urlencoded"
152
+ }
153
+
154
+ try:
155
+ # Need to use post with data
156
+ player_resp = await self.httpx.post(ajax_url, data=data, headers=headers)
157
+
158
+ # Kotlin parses it as text and cleans slashes
159
+ content = player_resp.text.replace(r"\/", "/")
160
+
161
+ # Regex for URL
162
+ # Kotlin: (?:src|url)["']?\s*[:=]\s*["']([^"']+)["']
163
+ src_match = re.search(r'(?:src|url)["\']?\s*[:=]\s*["\']([^"\']+)["\']', content)
164
+
165
+ if src_match:
166
+ iframe_url = src_match.group(1)
167
+ if iframe_url.startswith("/"):
168
+ iframe_url = self.main_url + iframe_url
169
+
170
+ iframe_url = self.fix_url(iframe_url)
171
+
172
+ extracted = await self.extract(iframe_url)
173
+ if extracted:
174
+ if isinstance(extracted, list):
175
+ results.extend(extracted)
176
+ else:
177
+ results.append(extracted)
178
+ else:
179
+ results.append(ExtractResult(
180
+ name = "Filmatek | External",
181
+ url = iframe_url,
182
+ referer = url
183
+ ))
184
+ except Exception as e:
185
+ # print(f"Filmatek Error: {e}")
186
+ pass
187
+
188
+ return results
@@ -0,0 +1,190 @@
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/vizyondaki-filmler-izle/page" : "Vizyondaki Filmler",
16
+ f"{main_url}/Kategori/yerli-filmler-izle/page" : "Yerli Filmler",
17
+ f"{main_url}/Kategori/yabanci-diziler/page" : "Yabancı Diziler",
18
+ f"{main_url}/Kategori/netflix-filmleri-izle/page" : "Netflix Filmleri",
19
+ f"{main_url}/Kategori/netflix-dizileri/page" : "Netflix Dizileri",
20
+ f"{main_url}/Kategori/anime-izle/page" : "Anime İzle",
21
+ f"{main_url}/Kategori/cizgi-filmler/page" : "Çizgi Filmler",
22
+ }
23
+
24
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
25
+ target_url = f"{url}/{page}/"
26
+ istek = await self.httpx.get(target_url)
27
+ helper = HTMLHelper(istek.text)
28
+
29
+ items = helper.select("div.movie-preview")
30
+ results = []
31
+
32
+ for item in items:
33
+ title_el = helper.select_first(".movie-title a", item)
34
+ if not title_el: continue
35
+
36
+ title = title_el.text(strip=True)
37
+ # Remove " izle" case insensitive
38
+ title = re.sub(r"(?i) izle", "", title).strip()
39
+
40
+ href = self.fix_url(title_el.attrs.get("href"))
41
+
42
+ poster_el = helper.select_first(".movie-poster img", item)
43
+ poster = self.fix_url(poster_el.attrs.get("src")) if poster_el else None
44
+
45
+ results.append(MainPageResult(
46
+ category = category,
47
+ title = title,
48
+ url = href,
49
+ poster = poster
50
+ ))
51
+
52
+ return results
53
+
54
+ async def search(self, query: str) -> list[SearchResult]:
55
+ url = f"{self.main_url}/?s={query}"
56
+ istek = await self.httpx.get(url)
57
+ helper = HTMLHelper(istek.text)
58
+
59
+ items = helper.select("div.movie-preview")
60
+ results = []
61
+
62
+ for item in items:
63
+ title_el = helper.select_first(".movie-title a", item)
64
+ if not title_el: continue
65
+
66
+ title = title_el.text(strip=True)
67
+ # Remove " izle" case insensitive
68
+ title = re.sub(r"(?i) izle", "", title).strip()
69
+
70
+ href = self.fix_url(title_el.attrs.get("href"))
71
+
72
+ poster_el = helper.select_first(".movie-poster img", item)
73
+ poster = self.fix_url(poster_el.attrs.get("src")) if poster_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 | SeriesInfo:
84
+ istek = await self.httpx.get(url)
85
+ helper = HTMLHelper(istek.text)
86
+
87
+ title_raw = helper.select_text("h1") or "Bilinmiyor"
88
+ title = re.sub(r"(?i)izle", "", title_raw).strip()
89
+
90
+ poster_el = helper.select_first(".poster img")
91
+ poster = self.fix_url(poster_el.attrs.get("src")) if poster_el else None
92
+
93
+ description = helper.select_text(".excerpt p")
94
+
95
+ year_text = helper.select_text(".release a")
96
+ year = year_text.strip() if year_text else None
97
+
98
+ rating_text = helper.select_text(".imdb-rating")
99
+ rating = None
100
+ if rating_text:
101
+ rating_text = rating_text.replace("IMDB Puanı", "").strip()
102
+ rating = rating_text
103
+
104
+ # Check for Episodes to decide if Series or Movie
105
+ ep_elements = helper.select(".parts-middle a, .parts-middle .part.active")
106
+
107
+ if not ep_elements:
108
+ # Movie
109
+ return MovieInfo(
110
+ url = url,
111
+ title = title,
112
+ description = description,
113
+ poster = poster,
114
+ year = year,
115
+ rating = rating,
116
+ tags = None, # Tags usually in genres list, implementation skipped for now or add if easy
117
+ actors = None # Actors not extracted in Kotlin reference provided
118
+ )
119
+ else:
120
+ # Series
121
+ episodes = []
122
+ for i, el in enumerate(ep_elements):
123
+ ep_name = helper.select_text(".part-name", el) or f"Bölüm {i+1}"
124
+ ep_href = el.attrs.get("href")
125
+ if not ep_href:
126
+ ep_href = url # Current page if href is empty/active?
127
+ ep_href = self.fix_url(ep_href)
128
+
129
+ # Parse season/episode from name if possible
130
+ # Kotlin: find digit for season, substringAfter("Sezon") digit for episode
131
+ season = 1
132
+ episode = i + 1
133
+
134
+ # Simple heuristic similar to Kotlin
135
+ # "1. Sezon 5. Bölüm"
136
+ s_match = re.search(r"(\d+)\.\s*Sezon", ep_name)
137
+ e_match = re.search(r"(\d+)\.\s*Bölüm", ep_name)
138
+
139
+ if s_match:
140
+ season = int(s_match.group(1))
141
+ if e_match:
142
+ episode = int(e_match.group(1))
143
+
144
+ episodes.append(Episode(
145
+ season = season,
146
+ episode = episode,
147
+ title = ep_name,
148
+ url = ep_href
149
+ ))
150
+
151
+ return SeriesInfo(
152
+ url = url,
153
+ title = title,
154
+ description = description,
155
+ poster = poster,
156
+ year = year,
157
+ rating = rating,
158
+ tags = None,
159
+ actors = None,
160
+ episodes = episodes
161
+ )
162
+
163
+ async def load_links(self, url: str) -> list[ExtractResult]:
164
+ istek = await self.httpx.get(url)
165
+ helper = HTMLHelper(istek.text)
166
+
167
+ iframe = helper.select_attr(".center-container iframe", "src")
168
+ if not iframe:
169
+ iframe = helper.select_attr("iframe[src*='hotstream.club']", "src")
170
+
171
+ results = []
172
+
173
+ if iframe:
174
+ iframe = self.fix_url(iframe)
175
+
176
+ # Use general extract method
177
+ extracted = await self.extract(iframe)
178
+ if extracted:
179
+ if isinstance(extracted, list):
180
+ results.extend(extracted)
181
+ else:
182
+ results.append(extracted)
183
+ else:
184
+ results.append(ExtractResult(
185
+ name = "Full4kizle | External",
186
+ url = iframe,
187
+ referer = url
188
+ ))
189
+
190
+ return results
@@ -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
- # Bölüm için gerekli bilgileri JSON olarak sakla
80
- ep_data = {
81
- "url" : self.fix_url(episode.get("sources")[0].get("url")),
82
- "title" : f"{veri.get('title')} | {season.get('title', '1. Sezon')} {episode.get('title', '1. Bölüm')}",
83
- "is_episode" : True
84
- }
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
-
90
- ep_model = Episode(
91
- season = s1 or 1,
92
- episode = e2 or 1,
93
- title = episode.get("title"),
94
- url = dumps(ep_data),
95
- )
96
-
97
- episodes.append(ep_model)
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")
@@ -0,0 +1,185 @@
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
+ from json import dumps, loads
5
+ import re
6
+
7
+ class Watch32(PluginBase):
8
+ name = "Watch32"
9
+ language = "en"
10
+ main_url = "https://watch32.sx"
11
+ favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
12
+ description = "Watch Your Favorite Movies & TV Shows Online - Streaming For Free. With Movies & TV Shows Full HD. Find Your Movies & Watch NOW!"
13
+
14
+ main_page = {
15
+ f"{main_url}/movie?page=" : "Popular Movies",
16
+ f"{main_url}/tv-show?page=" : "Popular TV Shows",
17
+ f"{main_url}/coming-soon?page=" : "Coming Soon",
18
+ f"{main_url}/top-imdb?page=" : "Top IMDB Rating",
19
+ }
20
+
21
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
22
+ istek = await self.httpx.get(f"{url}{page}")
23
+ helper = HTMLHelper(istek.text)
24
+ items = helper.select("div.flw-item")
25
+
26
+ return [
27
+ MainPageResult(
28
+ category = category,
29
+ title = helper.select_attr("h2.film-name a", "title", veri),
30
+ url = self.fix_url(helper.select_attr("h2.film-name a", "href", veri)),
31
+ poster = helper.select_attr("img.film-poster-img", "data-src", veri)
32
+ )
33
+ for veri in items
34
+ ]
35
+
36
+ async def search(self, query: str) -> list[SearchResult]:
37
+ slug = query.replace(" ", "-")
38
+ url = f"{self.main_url}/search/{slug}"
39
+
40
+ istek = await self.httpx.get(url)
41
+ helper = HTMLHelper(istek.text)
42
+ items = helper.select("div.flw-item")
43
+
44
+ return [
45
+ SearchResult(
46
+ title = helper.select_attr("h2.film-name a", "title", veri),
47
+ url = self.fix_url(helper.select_attr("h2.film-name a", "href", veri)),
48
+ poster = helper.select_attr("img.film-poster-img", "data-src", veri)
49
+ )
50
+ for veri in items
51
+ ]
52
+
53
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
54
+ istek = await self.httpx.get(url)
55
+ helper = HTMLHelper(istek.text)
56
+
57
+ content_id = helper.select_attr("div.detail_page-watch", "data-id")
58
+ details = helper.select_first("div.detail_page-infor")
59
+ name = helper.select_text("h2.heading-name > a", details)
60
+
61
+ poster = helper.select_attr("div.film-poster > img", "src", details)
62
+ description = helper.select_text("div.description", details)
63
+
64
+ # Release year extraction
65
+ year_text = helper.regex_first(r"Released:\s*(\d{4})")
66
+ if not year_text:
67
+ # Fallback for series
68
+ year_text = helper.regex_first(r"Released:.+?(\d{4})")
69
+
70
+ # Tags/Genres
71
+ tags = helper.select_all_text("div.row-line:has(> span.type > strong:contains(Genre)) a")
72
+
73
+ # Rating
74
+ rating = helper.select_text("button.btn-imdb")
75
+ if rating:
76
+ rating = rating.replace("N/A", "").split(":")[-1].strip()
77
+
78
+ # Actors
79
+ actors = helper.select_all_text("div.row-line:has(> span.type > strong:contains(Casts)) a")
80
+
81
+ if "movie" in url:
82
+ return MovieInfo(
83
+ url = url,
84
+ poster = self.fix_url(poster),
85
+ title = name,
86
+ description = description,
87
+ tags = tags,
88
+ rating = rating,
89
+ year = year_text,
90
+ actors = actors
91
+ )
92
+ else:
93
+ episodes = []
94
+ seasons_resp = await self.httpx.get(f"{self.main_url}/ajax/season/list/{content_id}")
95
+ sh = HTMLHelper(seasons_resp.text)
96
+ seasons = sh.select("a.dropdown-item") # Relaxed selector from a.ss-item
97
+
98
+ for season in seasons:
99
+ season_id = season.attrs.get("data-id")
100
+ season_num_text = season.text().replace("Season ", "").replace("Series", "").strip()
101
+ season_num = int(season_num_text) if season_num_text.isdigit() else 1
102
+
103
+ episodes_resp = await self.httpx.get(f"{self.main_url}/ajax/season/episodes/{season_id}")
104
+ eh = HTMLHelper(episodes_resp.text)
105
+ eps = eh.select("a.eps-item")
106
+
107
+ for ep in eps:
108
+ ep_id = ep.attrs.get("data-id")
109
+ ep_title_raw = ep.attrs.get("title", "")
110
+ # Eps 1: Name
111
+ m = re.search(r"Eps (\d+): (.+)", ep_title_raw)
112
+ if m:
113
+ ep_num = int(m.group(1))
114
+ ep_name = m.group(2)
115
+ else:
116
+ ep_num = 1
117
+ ep_name = ep_title_raw
118
+
119
+ episodes.append(Episode(
120
+ season = season_num,
121
+ episode = ep_num,
122
+ title = ep_name,
123
+ url = f"servers/{ep_id}"
124
+ ))
125
+
126
+ return SeriesInfo(
127
+ url = url,
128
+ poster = self.fix_url(poster),
129
+ title = name,
130
+ description = description,
131
+ tags = tags,
132
+ rating = rating,
133
+ year = year_text,
134
+ actors = actors,
135
+ episodes = episodes
136
+ )
137
+
138
+ async def load_links(self, url: str) -> list[ExtractResult]:
139
+ # url in load_links might be the full page URL for movies or "servers/epId" for episodes
140
+ if "servers/" in url:
141
+ data = url.split("/")[-1]
142
+ if "servers/" in url:
143
+ data = url.split("/")[-1]
144
+ servers_url = f"servers/{data}"
145
+ elif "list/" in url:
146
+ data = url.split("/")[-1]
147
+ servers_url = f"list/{data}"
148
+ else:
149
+ # Re-fetch page to get contentId only if we don't have list/ or servers/
150
+ istek = await self.httpx.get(url)
151
+ helper = HTMLHelper(istek.text)
152
+ content_id = helper.select_attr("div.detail_page-watch", "data-id")
153
+ if not content_id:
154
+ # Try to get id from url if direct parse fails, similar to Kotlin logic
155
+ # But Kotlin parses search first. Here we assume we are at the page.
156
+ # If no content_id found, maybe it's not a valid page or structure changed.
157
+ return []
158
+ servers_url = f"list/{content_id}"
159
+
160
+ servers_resp = await self.httpx.get(f"{self.main_url}/ajax/episode/{servers_url}")
161
+ sh = HTMLHelper(servers_resp.text)
162
+ servers = sh.select("a.link-item")
163
+
164
+ results = []
165
+ for server in servers:
166
+ link_id = server.attrs.get("data-linkid") or server.attrs.get("data-id")
167
+ source_resp = await self.httpx.get(f"{self.main_url}/ajax/episode/sources/{link_id}")
168
+ source_data = source_resp.json()
169
+ video_url = source_data.get("link")
170
+
171
+ if video_url:
172
+ # Use extractors if possible
173
+ extract_result = await self.extract(video_url)
174
+ if extract_result:
175
+ if isinstance(extract_result, list):
176
+ results.extend(extract_result)
177
+ else:
178
+ results.append(extract_result)
179
+ else:
180
+ results.append(ExtractResult(
181
+ url = video_url,
182
+ name = f"{self.name} | {server.text()}"
183
+ ))
184
+
185
+ return results
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: KekikStream
3
- Version: 2.3.9
3
+ Version: 2.4.0
4
4
  Summary: terminal üzerinden medya içeriği aramanızı ve VLC/MPV gibi popüler medya oynatıcılar aracılığıyla doğrudan izlemenizi sağlayan modüler ve genişletilebilir bir bıdı bıdı
5
5
  Home-page: https://github.com/keyiflerolsun/KekikStream
6
6
  Author: keyiflerolsun
@@ -63,13 +63,13 @@ Terminal üzerinden içerik arayın, VLC/MPV ile doğrudan izleyin veya kendi AP
63
63
 
64
64
  ## 🚦 Ne Sunar?
65
65
 
66
- KekikStream, Türkçe medya kaynaklarını tek CLI arayüzünde toplayarak hızlı arama ve oynatma sunar. Plugin mimarisi sayesinde yeni kaynaklar eklemek ve [KekikStreamAPI](https://github.com/keyiflerolsun/KekikStreamAPI) ile web/API üzerinden yayın yapmak kolaydır.
66
+ KekikStream, Türkçe medya kaynaklarını tek CLI arayüzünde toplayarak hızlı arama ve oynatma sunar. Plugin mimarisi sayesinde yeni kaynaklar eklemek ve [WatchBuddy](https://github.com/WatchBuddy-tv/Stream) ile web/API üzerinden yayın yapmak kolaydır.
67
67
 
68
68
  - 🎥 Çoklu kaynak desteği: Onlarca Türkçe medya sitesi
69
69
  - 🔌 Plugin mimarisi: Yeni kaynak eklemek dakikalar sürer
70
70
  - 🎬 Çoklu oynatıcı: VLC, MPV, MX Player
71
71
  - 🖥️ CLI & kütüphane: Terminalde veya kod içinde kullanın
72
- - 🌐 API/Web UI: KekikStreamAPI üzerinden uzak erişim
72
+ - 🌐 API/Web UI: WatchBuddy üzerinden uzak erişim
73
73
 
74
74
  ---
75
75