KekikStream 2.2.9__py3-none-any.whl → 2.5.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of KekikStream might be problematic. Click here for more details.

Files changed (88) hide show
  1. KekikStream/Core/Extractor/ExtractorBase.py +3 -2
  2. KekikStream/Core/Extractor/ExtractorLoader.py +8 -14
  3. KekikStream/Core/HTMLHelper.py +205 -0
  4. KekikStream/Core/Plugin/PluginBase.py +48 -12
  5. KekikStream/Core/Plugin/PluginLoader.py +13 -14
  6. KekikStream/Core/Plugin/PluginManager.py +2 -2
  7. KekikStream/Core/Plugin/PluginModels.py +0 -3
  8. KekikStream/Core/__init__.py +2 -0
  9. KekikStream/Extractors/Abstream.py +27 -0
  10. KekikStream/Extractors/CloseLoad.py +31 -56
  11. KekikStream/Extractors/ContentX.py +28 -71
  12. KekikStream/Extractors/DonilasPlay.py +34 -78
  13. KekikStream/Extractors/DzenRu.py +11 -25
  14. KekikStream/Extractors/ExPlay.py +20 -38
  15. KekikStream/Extractors/Filemoon.py +23 -53
  16. KekikStream/Extractors/HDMomPlayer.py +30 -0
  17. KekikStream/Extractors/HDPlayerSystem.py +13 -31
  18. KekikStream/Extractors/HotStream.py +27 -0
  19. KekikStream/Extractors/JFVid.py +3 -24
  20. KekikStream/Extractors/JetTv.py +21 -34
  21. KekikStream/Extractors/JetV.py +55 -0
  22. KekikStream/Extractors/MailRu.py +11 -29
  23. KekikStream/Extractors/MixPlayHD.py +17 -31
  24. KekikStream/Extractors/MixTiger.py +17 -40
  25. KekikStream/Extractors/MolyStream.py +25 -22
  26. KekikStream/Extractors/Odnoklassniki.py +41 -105
  27. KekikStream/Extractors/PeaceMakerst.py +20 -47
  28. KekikStream/Extractors/PixelDrain.py +9 -16
  29. KekikStream/Extractors/PlayerFilmIzle.py +23 -46
  30. KekikStream/Extractors/RapidVid.py +23 -36
  31. KekikStream/Extractors/SetPlay.py +19 -44
  32. KekikStream/Extractors/SetPrime.py +3 -6
  33. KekikStream/Extractors/SibNet.py +8 -19
  34. KekikStream/Extractors/Sobreatsesuyp.py +25 -47
  35. KekikStream/Extractors/TRsTX.py +25 -55
  36. KekikStream/Extractors/TurboImgz.py +8 -16
  37. KekikStream/Extractors/TurkeyPlayer.py +5 -5
  38. KekikStream/Extractors/VCTPlay.py +10 -28
  39. KekikStream/Extractors/Veev.py +145 -0
  40. KekikStream/Extractors/VidBiz.py +62 -0
  41. KekikStream/Extractors/VidHide.py +59 -34
  42. KekikStream/Extractors/VidMoly.py +67 -89
  43. KekikStream/Extractors/VidMoxy.py +17 -29
  44. KekikStream/Extractors/VidPapi.py +26 -58
  45. KekikStream/Extractors/VideoSeyred.py +21 -42
  46. KekikStream/Extractors/Videostr.py +58 -0
  47. KekikStream/Extractors/Vidoza.py +18 -0
  48. KekikStream/Extractors/Vtbe.py +38 -0
  49. KekikStream/Extractors/YTDLP.py +2 -2
  50. KekikStream/Extractors/YildizKisaFilm.py +13 -31
  51. KekikStream/Extractors/Zeus.py +61 -0
  52. KekikStream/Plugins/BelgeselX.py +108 -99
  53. KekikStream/Plugins/DiziBox.py +61 -106
  54. KekikStream/Plugins/DiziMom.py +179 -0
  55. KekikStream/Plugins/DiziPal.py +104 -192
  56. KekikStream/Plugins/DiziYou.py +66 -149
  57. KekikStream/Plugins/Dizilla.py +93 -126
  58. KekikStream/Plugins/FilmBip.py +102 -72
  59. KekikStream/Plugins/FilmEkseni.py +199 -0
  60. KekikStream/Plugins/FilmMakinesi.py +101 -64
  61. KekikStream/Plugins/FilmModu.py +35 -59
  62. KekikStream/Plugins/Filmatek.py +184 -0
  63. KekikStream/Plugins/FilmciBaba.py +155 -0
  64. KekikStream/Plugins/FullHDFilmizlesene.py +32 -78
  65. KekikStream/Plugins/HDFilm.py +243 -0
  66. KekikStream/Plugins/HDFilmCehennemi.py +261 -222
  67. KekikStream/Plugins/JetFilmizle.py +117 -98
  68. KekikStream/Plugins/KultFilmler.py +153 -143
  69. KekikStream/Plugins/RecTV.py +53 -49
  70. KekikStream/Plugins/RoketDizi.py +92 -123
  71. KekikStream/Plugins/SelcukFlix.py +86 -95
  72. KekikStream/Plugins/SetFilmIzle.py +105 -143
  73. KekikStream/Plugins/SezonlukDizi.py +106 -128
  74. KekikStream/Plugins/Sinefy.py +194 -166
  75. KekikStream/Plugins/SinemaCX.py +159 -113
  76. KekikStream/Plugins/Sinezy.py +44 -73
  77. KekikStream/Plugins/SuperFilmGeldi.py +28 -52
  78. KekikStream/Plugins/UgurFilm.py +94 -72
  79. KekikStream/Plugins/Watch32.py +160 -0
  80. KekikStream/Plugins/YabanciDizi.py +250 -0
  81. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/METADATA +1 -1
  82. kekikstream-2.5.3.dist-info/RECORD +99 -0
  83. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/WHEEL +1 -1
  84. KekikStream/Plugins/FullHDFilm.py +0 -254
  85. kekikstream-2.2.9.dist-info/RECORD +0 -82
  86. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/entry_points.txt +0 -0
  87. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/licenses/LICENSE +0 -0
  88. {kekikstream-2.2.9.dist-info → kekikstream-2.5.3.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,7 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult
4
- from selectolax.parser import HTMLParser
5
- import re
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult, HTMLHelper
4
+ import asyncio, contextlib
6
5
 
7
6
  class UgurFilm(PluginBase):
8
7
  name = "UgurFilm"
@@ -26,46 +25,36 @@ class UgurFilm(PluginBase):
26
25
 
27
26
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
28
27
  istek = await self.httpx.get(f"{url}{page}", follow_redirects=True)
29
- secici = HTMLParser(istek.text)
28
+ secici = HTMLHelper(istek.text)
30
29
 
31
30
  results = []
32
- for veri in secici.css("div.icerik div"):
31
+ for veri in secici.select("div.icerik div"):
33
32
  # Title is in the second span (a.baslik > span), not the first span (class="sol" which is empty)
34
- title_el = veri.css_first("a.baslik span")
35
- title = title_el.text(strip=True) if title_el else None
33
+ title = secici.select_text("a.baslik span", veri)
36
34
  if not title:
37
35
  continue
38
36
 
39
- link_el = veri.css_first("a")
40
- img_el = veri.css_first("img")
41
-
42
- href = link_el.attrs.get("href") if link_el else None
43
- poster = img_el.attrs.get("src") if img_el else None
37
+ href = secici.select_attr("a", "href", veri)
38
+ poster = secici.select_attr("img", "src", veri)
44
39
 
45
40
  results.append(MainPageResult(
46
41
  category = category,
47
42
  title = title,
48
43
  url = self.fix_url(href) if href else "",
49
- poster = self.fix_url(poster) if poster else None,
44
+ poster = self.fix_url(poster),
50
45
  ))
51
46
 
52
47
  return results
53
48
 
54
49
  async def search(self, query: str) -> list[SearchResult]:
55
50
  istek = await self.httpx.get(f"{self.main_url}/?s={query}")
56
- secici = HTMLParser(istek.text)
51
+ secici = HTMLHelper(istek.text)
57
52
 
58
53
  results = []
59
- for film in secici.css("div.icerik div"):
60
- # Title is in a.baslik > span, not the first span
61
- title_el = film.css_first("a.baslik span")
62
- title = title_el.text(strip=True) if title_el else None
63
-
64
- link_el = film.css_first("a")
65
- img_el = film.css_first("img")
66
-
67
- href = link_el.attrs.get("href") if link_el else None
68
- poster = img_el.attrs.get("src") if img_el else None
54
+ for film in secici.select("div.icerik div"):
55
+ title = secici.select_text("a.baslik span", film)
56
+ href = secici.select_attr("a", "href", film)
57
+ poster = secici.select_attr("img", "src", film)
69
58
 
70
59
  if title and href:
71
60
  results.append(SearchResult(
@@ -78,68 +67,101 @@ class UgurFilm(PluginBase):
78
67
 
79
68
  async def load_item(self, url: str) -> MovieInfo:
80
69
  istek = await self.httpx.get(url)
81
- secici = HTMLParser(istek.text)
82
-
83
- title_el = secici.css_first("div.bilgi h2")
84
- title = title_el.text(strip=True) if title_el else ""
85
-
86
- poster_el = secici.css_first("div.resim img")
87
- poster = poster_el.attrs.get("src", "").strip() if poster_el else ""
88
-
89
- desc_el = secici.css_first("div.slayt-aciklama")
90
- description = desc_el.text(strip=True) if desc_el else ""
70
+ secici = HTMLHelper(istek.text)
91
71
 
92
- tags = [a.text(strip=True) for a in secici.css("p.tur a[href*='/category/']") if a.text(strip=True)]
93
-
94
- # re_first yerine re.search
95
- year_el = secici.css_first("a[href*='/yil/']")
96
- year_text = year_el.text(strip=True) if year_el else ""
97
- year_match = re.search(r"\d+", year_text)
98
- year = year_match.group() if year_match else None
99
-
100
- actors = []
101
- for actor in secici.css("li.oyuncu-k"):
102
- span_el = actor.css_first("span")
103
- if span_el and span_el.text(strip=True):
104
- actors.append(span_el.text(strip=True))
72
+ title = secici.select_text("div.bilgi h2")
73
+ poster = secici.select_poster("div.resim img")
74
+ description = secici.select_text("div.slayt-aciklama")
75
+ rating = secici.select_text("b#puandegistir")
76
+ tags = secici.select_texts("p.tur a[href*='/category/']")
77
+ year = secici.extract_year("a[href*='/yil/']")
78
+ actors = secici.select_texts("li.oyuncu-k span")
79
+ duration = secici.regex_first(r"(\d+) Dakika", secici.select_text("div.bilgi b"))
105
80
 
106
81
  return MovieInfo(
107
- url = self.fix_url(url),
108
- poster = self.fix_url(poster) if poster else None,
82
+ url = url,
83
+ poster = self.fix_url(poster),
109
84
  title = title,
110
85
  description = description,
86
+ rating = rating,
111
87
  tags = tags,
112
88
  year = year,
113
89
  actors = actors,
90
+ duration = duration
114
91
  )
115
92
 
116
93
  async def load_links(self, url: str) -> list[ExtractResult]:
117
94
  istek = await self.httpx.get(url)
118
- secici = HTMLParser(istek.text)
95
+ secici = HTMLHelper(istek.text)
119
96
  results = []
120
97
 
121
- part_links = [a.attrs.get("href") for a in secici.css("li.parttab a") if a.attrs.get("href")]
122
-
123
- for part_link in part_links:
124
- sub_response = await self.httpx.get(part_link)
125
- sub_selector = HTMLParser(sub_response.text)
98
+ part_links = secici.select_attrs("li.parttab a", "href")
99
+ if not part_links:
100
+ part_links = [url]
126
101
 
127
- iframe_el = sub_selector.css_first("div#vast iframe")
128
- iframe = iframe_el.attrs.get("src") if iframe_el else None
129
-
130
- if iframe and self.main_url in iframe:
131
- post_data = {
132
- "vid" : iframe.split("vid=")[-1],
133
- "alternative" : "vidmoly",
134
- "ord" : "0",
135
- }
136
- player_response = await self.httpx.post(
102
+ async def process_alt(vid: str, alt_name: str, ord_val: str) -> list[ExtractResult]:
103
+ """Alternatif player kaynağından video linkini çıkarır."""
104
+ with contextlib.suppress(Exception):
105
+ resp = await self.httpx.post(
137
106
  url = f"{self.main_url}/player/ajax_sources.php",
138
- data = post_data
107
+ data = {"vid": vid, "alternative": alt_name, "ord": ord_val}
139
108
  )
140
- iframe = self.fix_url(player_response.json().get("iframe"))
141
- data = await self.extract(iframe)
142
- if data:
143
- results.append(data)
144
-
145
- return results
109
+ if iframe_url := resp.json().get("iframe"):
110
+ data = await self.extract(self.fix_url(iframe_url))
111
+ if not data:
112
+ return []
113
+
114
+ return data if isinstance(data, list) else [data]
115
+
116
+ return []
117
+
118
+ async def process_part(part_url: str) -> list[ExtractResult]:
119
+ """Her bir part sayfasını ve alternatiflerini işler."""
120
+ try:
121
+ # Elimizde zaten olan ana sayfayı tekrar çekmemek için
122
+ if part_url == url:
123
+ sub_sec = secici
124
+ else:
125
+ sub_resp = await self.httpx.get(part_url)
126
+ sub_sec = HTMLHelper(sub_resp.text)
127
+
128
+ iframe = sub_sec.select_attr("div#vast iframe", "src")
129
+ if not iframe:
130
+ return []
131
+
132
+ if self.main_url not in iframe:
133
+ data = await self.extract(self.fix_url(iframe))
134
+ if not data:
135
+ return []
136
+
137
+ return data if isinstance(data, list) else [data]
138
+
139
+ # İç kaynaklı ise 3 alternatif için paralel istek at
140
+ vid = iframe.split("vid=")[-1]
141
+ tasks = [
142
+ process_alt(vid, "vidmoly", "0"),
143
+ process_alt(vid, "ok.ru", "1"),
144
+ process_alt(vid, "mailru", "2")
145
+ ]
146
+
147
+ alt_results = await asyncio.gather(*tasks)
148
+
149
+ return [item for sublist in alt_results for item in sublist]
150
+ except Exception:
151
+ return []
152
+
153
+ # Tüm partları paralel işle
154
+ groups = await asyncio.gather(*(process_part(p) for p in part_links))
155
+
156
+ for group in groups:
157
+ results.extend(group)
158
+
159
+ # Duplicate Temizliği
160
+ unique_results = []
161
+ seen = set()
162
+ for res in results:
163
+ if res.url and res.url not in seen:
164
+ unique_results.append(res)
165
+ seen.add(res.url)
166
+
167
+ return unique_results
@@ -0,0 +1,160 @@
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
+
5
+ class Watch32(PluginBase):
6
+ name = "Watch32"
7
+ language = "en"
8
+ main_url = "https://watch32.sx"
9
+ favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
10
+ description = "Watch Your Favorite Movies & TV Shows Online - Streaming For Free. With Movies & TV Shows Full HD. Find Your Movies & Watch NOW!"
11
+
12
+ main_page = {
13
+ # Main Categories
14
+ f"{main_url}/movie?page=" : "Popular Movies",
15
+ f"{main_url}/tv-show?page=" : "Popular TV Shows",
16
+ f"{main_url}/coming-soon?page=" : "Coming Soon",
17
+ f"{main_url}/top-imdb?page=" : "Top IMDB Rating",
18
+ # Genre Categories
19
+ f"{main_url}/genre/action?page=" : "Action",
20
+ f"{main_url}/genre/adventure?page=" : "Adventure",
21
+ f"{main_url}/genre/animation?page=" : "Animation",
22
+ f"{main_url}/genre/biography?page=" : "Biography",
23
+ f"{main_url}/genre/comedy?page=" : "Comedy",
24
+ f"{main_url}/genre/crime?page=" : "Crime",
25
+ f"{main_url}/genre/documentary?page=" : "Documentary",
26
+ f"{main_url}/genre/drama?page=" : "Drama",
27
+ f"{main_url}/genre/family?page=" : "Family",
28
+ f"{main_url}/genre/fantasy?page=" : "Fantasy",
29
+ f"{main_url}/genre/history?page=" : "History",
30
+ f"{main_url}/genre/horror?page=" : "Horror",
31
+ f"{main_url}/genre/music?page=" : "Music",
32
+ f"{main_url}/genre/mystery?page=" : "Mystery",
33
+ f"{main_url}/genre/romance?page=" : "Romance",
34
+ f"{main_url}/genre/science-fiction?page=" : "Science Fiction",
35
+ f"{main_url}/genre/thriller?page=" : "Thriller",
36
+ f"{main_url}/genre/war?page=" : "War",
37
+ f"{main_url}/genre/western?page=" : "Western",
38
+ }
39
+
40
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
41
+ istek = await self.httpx.get(f"{url}{page}")
42
+ helper = HTMLHelper(istek.text)
43
+ items = helper.select("div.flw-item")
44
+
45
+ return [
46
+ MainPageResult(
47
+ category = category,
48
+ title = helper.select_attr("h2.film-name a", "title", veri),
49
+ url = self.fix_url(helper.select_attr("h2.film-name a", "href", veri)),
50
+ poster = helper.select_attr("img.film-poster-img", "data-src", veri)
51
+ )
52
+ for veri in items
53
+ ]
54
+
55
+ async def search(self, query: str) -> list[SearchResult]:
56
+ istek = await self.httpx.get(f"{self.main_url}/search/{query.replace(' ', '-')}")
57
+ secici = HTMLHelper(istek.text)
58
+
59
+ return [
60
+ SearchResult(
61
+ title = secici.select_attr("h2.film-name a", "title", veri),
62
+ url = self.fix_url(secici.select_attr("h2.film-name a", "href", veri)),
63
+ poster = secici.select_attr("img.film-poster-img", "data-src", veri)
64
+ )
65
+ for veri in secici.select("div.flw-item")
66
+ ]
67
+
68
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
69
+ istek = await self.httpx.get(url)
70
+ secici = HTMLHelper(istek.text)
71
+
72
+ content_id = secici.select_attr("div.detail_page-watch", "data-id")
73
+ details = secici.select_first("div.detail_page-infor")
74
+ name = secici.select_text("h2.heading-name > a", details)
75
+ poster = secici.select_poster("div.film-poster > img", details)
76
+ description = secici.select_text("div.description", details)
77
+ year = str(secici.extract_year())
78
+ tags = secici.meta_list("Genre", container_selector="div.row-line")
79
+ rating = secici.select_text("button.btn-imdb").replace("N/A", "").split(":")[-1].strip() if secici.select_text("button.btn-imdb") else None
80
+ actors = secici.meta_list("Casts", container_selector="div.row-line")
81
+
82
+ common_info = {
83
+ "url" : url,
84
+ "poster" : self.fix_url(poster),
85
+ "title" : name,
86
+ "description" : description,
87
+ "tags" : tags,
88
+ "rating" : rating,
89
+ "year" : year,
90
+ "actors" : actors
91
+ }
92
+
93
+ if "movie" in url:
94
+ return MovieInfo(**common_info)
95
+
96
+ episodes = []
97
+ seasons_resp = await self.httpx.get(f"{self.main_url}/ajax/season/list/{content_id}")
98
+ sh = HTMLHelper(seasons_resp.text)
99
+
100
+ for season in sh.select("a.dropdown-item"):
101
+ season_id = season.attrs.get("data-id")
102
+ s_val, _ = sh.extract_season_episode(season.text())
103
+
104
+ e_resp = await self.httpx.get(f"{self.main_url}/ajax/season/episodes/{season_id}")
105
+ eh = HTMLHelper(e_resp.text)
106
+
107
+ for ep in eh.select("a.eps-item"):
108
+ ep_id = ep.attrs.get("data-id")
109
+ ep_title = ep.attrs.get("title", "")
110
+ _, e_val = eh.extract_season_episode(ep_title)
111
+
112
+ episodes.append(Episode(
113
+ season = s_val or 1,
114
+ episode = e_val or 1,
115
+ title = ep_title,
116
+ url = f"servers/{ep_id}"
117
+ ))
118
+
119
+ return SeriesInfo(**common_info, episodes=episodes)
120
+
121
+ async def load_links(self, url: str) -> list[ExtractResult]:
122
+ # url in load_links might be the full page URL for movies or "servers/epId" for episodes
123
+ if "servers/" in url:
124
+ data = url.split("/")[-1]
125
+ servers_url = f"servers/{data}"
126
+ elif "list/" in url:
127
+ data = url.split("/")[-1]
128
+ servers_url = f"list/{data}"
129
+ else:
130
+ # Re-fetch page to get contentId only if we don't have list/ or servers/
131
+ istek = await self.httpx.get(url)
132
+ secici = HTMLHelper(istek.text)
133
+ content_id = secici.select_attr("div.detail_page-watch", "data-id")
134
+ if not content_id:
135
+ return []
136
+ servers_url = f"list/{content_id}"
137
+
138
+ servers_resp = await self.httpx.get(f"{self.main_url}/ajax/episode/{servers_url}")
139
+ sh = HTMLHelper(servers_resp.text)
140
+ servers = sh.select("a.link-item")
141
+
142
+ results = []
143
+ for server in servers:
144
+ server_name = server.text(strip=True)
145
+ link_id = server.attrs.get("data-linkid") or server.attrs.get("data-id")
146
+ source_resp = await self.httpx.get(f"{self.main_url}/ajax/episode/sources/{link_id}")
147
+ source_data = source_resp.json()
148
+ video_url = source_data.get("link")
149
+
150
+ if video_url:
151
+ extract_result = await self.extract(video_url, name_override=server_name)
152
+ if extract_result:
153
+ results.extend(extract_result if isinstance(extract_result, list) else [extract_result])
154
+ else:
155
+ results.append(ExtractResult(
156
+ url = video_url,
157
+ name = f"{self.name} | {server_name}"
158
+ ))
159
+
160
+ return results
@@ -0,0 +1,250 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, MovieInfo, Episode, ExtractResult, HTMLHelper
4
+ import asyncio, time, json
5
+
6
+ class YabanciDizi(PluginBase):
7
+ name = "YabanciDizi"
8
+ language = "tr"
9
+ main_url = "https://yabancidizi.so"
10
+ favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
11
+ description = "Yabancidizi.so platformu üzerinden en güncel yabancı dizileri ve filmleri izleyebilir, favori içeriklerinizi takip edebilirsiniz."
12
+
13
+ main_page = {
14
+ f"{main_url}/kesfet/eyJvcmRlciI6ImRhdGVfYm90dG9tIiwia2F0ZWdvcnkiOlsiMTciXX0=" : "Diziler",
15
+ f"{main_url}/kesfet/eyJvcmRlciI6ImRhdGVfYm90dG9tIiwia2F0ZWdvcnkiOlsiMTgiXX0=" : "Filmler",
16
+ f"{main_url}/kesfet/eyJvcmRlciI6ImRhdGVfYm90dG9tIiwiY291bnRyeSI6eyJLUiI6IktSIn19" : "Kdrama",
17
+ f"{main_url}/kesfet/eyJvcmRlciI6ImRhdGVfYm90dG9tIiwiY291bnRyeSI6eyJKUCI6IkpQIn0sImNhdGVnb3J5IjpbXX0=" : "Jdrama",
18
+ f"{main_url}/kesfet/eyJvcmRlciI6ImRhdGVfYm90dG9tIiwiY2F0ZWdvcnkiOnsiMyI6IjMifX0=" : "Animasyon",
19
+ }
20
+
21
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
22
+ istek = await self.httpx.get(
23
+ url = url if page == 1 else f"{url}/{page}",
24
+ headers = {"Referer": f"{self.main_url}/"}
25
+ )
26
+ secici = HTMLHelper(istek.text)
27
+
28
+ results = []
29
+ for item in secici.select("li.mb-lg, li.segment-poster"):
30
+ title = secici.select_text("h2", item)
31
+ href = secici.select_attr("a", "href", item)
32
+ poster = secici.select_attr("img", "src", item)
33
+
34
+ if title and href:
35
+ results.append(MainPageResult(
36
+ category = category,
37
+ title = title,
38
+ url = self.fix_url(href),
39
+ poster = self.fix_url(poster),
40
+ ))
41
+
42
+ return results
43
+
44
+ async def search(self, query: str) -> list[SearchResult]:
45
+ istek = await self.httpx.post(
46
+ url = f"{self.main_url}/search?qr={query}",
47
+ headers = {
48
+ "X-Requested-With" : "XMLHttpRequest",
49
+ "Referer" : f"{self.main_url}/"
50
+ }
51
+ )
52
+
53
+ try:
54
+ raw = istek.json()
55
+ res_array = raw.get("data", {}).get("result", [])
56
+
57
+ results = []
58
+ for item in res_array:
59
+ title = item.get("s_name")
60
+ image = item.get("s_image")
61
+ slug = item.get("s_link")
62
+ s_type = item.get("s_type") # 0: dizi, 1: film
63
+
64
+ poster = f"{self.main_url}/uploads/series/{image}" if image else None
65
+
66
+ if s_type == "1":
67
+ href = f"{self.main_url}/film/{slug}"
68
+ else:
69
+ href = f"{self.main_url}/dizi/{slug}"
70
+
71
+ if title and slug:
72
+ results.append(SearchResult(
73
+ title = title,
74
+ url = self.fix_url(href),
75
+ poster = self.fix_url(poster)
76
+ ))
77
+ return results
78
+ except Exception:
79
+ return []
80
+
81
+ async def load_item(self, url: str) -> SeriesInfo | MovieInfo:
82
+ istek = await self.httpx.get(url, follow_redirects=True)
83
+ secici = HTMLHelper(istek.text)
84
+
85
+ title = (secici.select_attr("meta[property='og:title']", "content") or "").split("|")[0].strip() or secici.select_text("h1")
86
+ poster = secici.select_poster("meta[property='og:image']")
87
+ description = secici.select_text("p#tv-series-desc")
88
+ year = secici.extract_year("td div.truncate")
89
+ tags = secici.meta_list("Türü", container_selector="div.item")
90
+ rating = secici.meta_value("IMDb Puanı", container_selector="div.item")
91
+ duration = int(secici.regex_first(r"(\d+)", secici.meta_value("Süre", container_selector="div.item")) or 0)
92
+ actors = secici.meta_list("Oyuncular", container_selector="div.item") or secici.select_texts("div#common-cast-list div.item h5")
93
+
94
+ common_info = {
95
+ "url" : url,
96
+ "poster" : self.fix_url(poster),
97
+ "title" : title,
98
+ "description" : description,
99
+ "tags" : tags,
100
+ "rating" : rating,
101
+ "year" : year,
102
+ "actors" : actors,
103
+ "duration" : duration
104
+ }
105
+
106
+ if "/film/" in url:
107
+ return MovieInfo(**common_info)
108
+
109
+ episodes = []
110
+ for bolum in secici.select("div.episodes-list div.ui td:has(h6)"):
111
+ link = secici.select_first("a", bolum)
112
+ if link:
113
+ href = link.attrs.get("href")
114
+ name = secici.select_text("h6", bolum) or link.text(strip=True)
115
+ s, e = secici.extract_season_episode(href)
116
+ episodes.append(Episode(
117
+ season = s or 1,
118
+ episode = e or 1,
119
+ title = name,
120
+ url = self.fix_url(href)
121
+ ))
122
+
123
+ if episodes and (episodes[0].episode or 0) > (episodes[-1].episode or 0):
124
+ episodes.reverse()
125
+
126
+ return SeriesInfo(**common_info, episodes=episodes)
127
+
128
+ async def load_links(self, url: str) -> list[ExtractResult]:
129
+ loop = asyncio.get_event_loop()
130
+
131
+ # 1. Ana sayfayı çek
132
+ istek = await loop.run_in_executor(None, lambda: self.cloudscraper.get(url, headers={"Referer": f"{self.main_url}/"}))
133
+ secici = HTMLHelper(istek.text)
134
+
135
+ results = []
136
+ timestamp_ms = int(time.time() * 1000) - 50000
137
+
138
+ # 2. Dil Tablarını Bul
139
+ tabs = secici.select("div#series-tabs a")
140
+
141
+ async def process_tab(tab_el):
142
+ data_eid = tab_el.attrs.get("data-eid")
143
+ data_type = tab_el.attrs.get("data-type") # 1: Altyazı, 2: Dublaj
144
+ if not data_eid or not data_type:
145
+ return []
146
+
147
+ dil_adi = "Dublaj" if data_type == "2" else "Altyazı"
148
+
149
+ try:
150
+ post_resp = await loop.run_in_executor(None, lambda: self.cloudscraper.post(
151
+ url = f"{self.main_url}/ajax/service",
152
+ headers = {
153
+ "X-Requested-With" : "XMLHttpRequest",
154
+ "Referer" : url
155
+ },
156
+ data = {
157
+ "lang" : data_type,
158
+ "episode" : data_eid,
159
+ "type" : "langTab"
160
+ },
161
+ cookies = {"udys": str(timestamp_ms)}
162
+ ))
163
+
164
+ res_json = post_resp.json()
165
+ if not res_json.get("data"): return []
166
+
167
+ res_sel = HTMLHelper(res_json["data"])
168
+ sources = []
169
+
170
+ for item in res_sel.select("div.item"):
171
+ name = item.text(strip=True)
172
+ data_link = item.attrs.get("data-link")
173
+ if not data_link: continue
174
+
175
+ # Link normalizasyonu
176
+ safe_link = data_link.replace("/", "_").replace("+", "-")
177
+
178
+ # API Endpoint belirleme
179
+ api_path = None
180
+ if "VidMoly" in name:
181
+ api_path = "moly"
182
+ elif "Okru" in name:
183
+ api_path = "ruplay"
184
+ elif "Mac" in name:
185
+ api_path = "drive"
186
+
187
+ if api_path:
188
+ sources.append({
189
+ "name" : name,
190
+ "api_url" : f"{self.main_url}/api/{api_path}/{safe_link}",
191
+ "dil" : dil_adi
192
+ })
193
+
194
+ tab_results = []
195
+ for src in sources:
196
+ try:
197
+ # API sayfasını çekip içindeki iframe'i bulalım
198
+ api_resp = await loop.run_in_executor(None, lambda: self.cloudscraper.get(
199
+ src["api_url"],
200
+ headers={"Referer": f"{self.main_url}/"},
201
+ cookies={"udys": str(timestamp_ms)}
202
+ ))
203
+
204
+ api_sel = HTMLHelper(api_resp.text)
205
+ iframe = api_sel.select_attr("iframe", "src")
206
+
207
+ if not iframe and "drive" in src["api_url"]:
208
+ t_sec = int(time.time())
209
+ drives_url = f"{src['api_url'].replace('/api/drive/', '/api/drives/')}?t={t_sec}"
210
+ api_resp = await loop.run_in_executor(None, lambda: self.cloudscraper.get(
211
+ drives_url,
212
+ headers={"Referer": src["api_url"]},
213
+ cookies={"udys": str(timestamp_ms)}
214
+ ))
215
+ api_sel = HTMLHelper(api_resp.text)
216
+ iframe = api_sel.select_attr("iframe", "src")
217
+
218
+ if iframe:
219
+ prefix = f"{src['dil']} | {src['name']}"
220
+ extracted = await self.extract(self.fix_url(iframe), prefix=prefix)
221
+ if extracted:
222
+ tab_results.extend(extracted if isinstance(extracted, list) else [extracted])
223
+ except Exception:
224
+ continue
225
+ return tab_results
226
+
227
+ except Exception:
228
+ return []
229
+
230
+ if tabs:
231
+ results_groups = await asyncio.gather(*(process_tab(tab) for tab in tabs))
232
+ for group in results_groups:
233
+ results.extend(group)
234
+ else:
235
+ # Tab yoksa mevcut sayfada iframe ara
236
+ iframe = secici.select_attr("iframe", "src")
237
+ if iframe:
238
+ extracted = await self.extract(self.fix_url(iframe), name_override="Main")
239
+ if extracted:
240
+ results.extend(extracted if isinstance(extracted, list) else [extracted])
241
+
242
+ # Duplicate kontrolü
243
+ unique_results = []
244
+ seen = set()
245
+ for res in results:
246
+ if res.url and res.url not in seen:
247
+ unique_results.append(res)
248
+ seen.add(res.url)
249
+
250
+ return unique_results
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: KekikStream
3
- Version: 2.2.9
3
+ Version: 2.5.3
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