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
+ from contextlib import suppress
6
5
 
7
6
  class FilmBip(PluginBase):
8
7
  name = "FilmBip"
@@ -37,23 +36,20 @@ class FilmBip(PluginBase):
37
36
  page_url = page_url.rstrip("/")
38
37
 
39
38
  istek = await self.httpx.get(page_url)
40
- secici = HTMLParser(istek.text)
39
+ secici = HTMLHelper(istek.text)
41
40
 
42
41
  results = []
43
- for veri in secici.css("div.poster-long"):
44
- img = veri.css_first("a.block img.lazy")
45
- link_el = veri.css_first("a.block")
46
-
47
- title = img.attrs.get("alt") if img else None
48
- href = link_el.attrs.get("href") if link_el else None
49
- poster = (img.attrs.get("data-src") or img.attrs.get("src")) if img else None
42
+ for veri in secici.select("div.poster-long"):
43
+ title = secici.select_attr("a.block img.lazy", "alt", veri)
44
+ href = secici.select_attr("a.block", "href", veri)
45
+ poster = secici.select_poster("a.block img.lazy", veri)
50
46
 
51
47
  if title and href:
52
48
  results.append(MainPageResult(
53
49
  category = category,
54
50
  title = title,
55
51
  url = self.fix_url(href),
56
- poster = self.fix_url(poster) if poster else None,
52
+ poster = self.fix_url(poster),
57
53
  ))
58
54
 
59
55
  return results
@@ -80,92 +76,126 @@ class FilmBip(PluginBase):
80
76
  except Exception:
81
77
  return []
82
78
 
83
- secici = HTMLParser(html_content)
79
+ secici = HTMLHelper(html_content)
84
80
 
85
81
  results = []
86
- for veri in secici.css("li"):
87
- link_el = veri.css_first("a.block.truncate")
88
- href_el = veri.css_first("a")
89
- img_el = veri.css_first("img.lazy")
90
-
91
- title = link_el.text(strip=True) if link_el else None
92
- href = href_el.attrs.get("href") if href_el else None
93
- poster = img_el.attrs.get("data-src") if img_el else None
82
+ for veri in secici.select("li"):
83
+ title = secici.select_text("a.block.truncate", veri)
84
+ href = secici.select_attr("a", "href", veri)
85
+ poster = secici.select_attr("img.lazy", "data-src", veri)
94
86
 
95
87
  if title and href:
96
88
  results.append(SearchResult(
97
89
  title = title.strip(),
98
90
  url = self.fix_url(href),
99
- poster = self.fix_url(poster) if poster else None,
91
+ poster = self.fix_url(poster),
100
92
  ))
101
93
 
102
94
  return results
103
95
 
104
96
  async def load_item(self, url: str) -> MovieInfo:
105
97
  istek = await self.httpx.get(url)
106
- secici = HTMLParser(istek.text)
107
- html_text = istek.text
108
-
109
- title_el = secici.css_first("div.page-title h1")
110
- title = title_el.text(strip=True) if title_el else ""
111
-
112
- og_image = secici.css_first("meta[property='og:image']")
113
- poster = og_image.attrs.get("content") if og_image else None
114
-
115
- trailer_el = secici.css_first("div.series-profile-trailer")
116
- trailer = trailer_el.attrs.get("data-yt") if trailer_el else None
117
-
118
- desc_el = secici.css_first("div.series-profile-infos-in.article p")
119
- if not desc_el:
120
- desc_el = secici.css_first("div.series-profile-summary p")
121
- description = desc_el.text(strip=True) if desc_el else None
122
-
123
- tags = [a.text(strip=True) for a in secici.css("div.series-profile-type.tv-show-profile-type a") if a.text(strip=True)]
124
-
125
- # XPath yerine regex kullanarak yıl, süre vs. çıkarma
126
- year = None
127
- year_match = re.search(r'Yapım yılı.*?<p[^>]*>(\d{4})</p>', html_text, re.IGNORECASE | re.DOTALL)
128
- if year_match:
129
- year = year_match.group(1)
130
-
131
- duration = None
132
- duration_match = re.search(r'Süre.*?<p[^>]*>(\d+)', html_text, re.IGNORECASE | re.DOTALL)
133
- if duration_match:
134
- duration = duration_match.group(1)
135
-
136
- rating = None
137
- rating_match = re.search(r'IMDB Puanı.*?<span[^>]*>([0-9.]+)</span>', html_text, re.IGNORECASE | re.DOTALL)
138
- if rating_match:
139
- rating = rating_match.group(1)
140
-
141
- actors = [img.attrs.get("alt") for img in secici.css("div.series-profile-cast ul li a img") if img.attrs.get("alt")]
98
+ secici = HTMLHelper(istek.text)
99
+
100
+ title = self.clean_title(secici.select_direct_text("div.page-title h1"))
101
+ poster = secici.select_poster("div.series-profile-image a img")
102
+ description = secici.select_text("div.series-profile-infos-in.article p") or secici.select_text("div.series-profile-summary p")
103
+ tags = secici.select_texts("div.series-profile-type.tv-show-profile-type a")
104
+ year = secici.extract_year("div.series-profile-infos-in") or secici.regex_first(r"\((\d{4})\)", title)
105
+ duration = secici.regex_first(r"(\d+)", secici.meta_value("Süre", container_selector="div.series-profile-infos"))
106
+ rating = secici.meta_value("IMDB Puanı", container_selector="div.series-profile-infos")
107
+ rating = rating.split("(")[0] if rating else None
108
+ actors = secici.select_attrs("div.series-profile-cast ul li a img", "alt")
142
109
 
143
110
  return MovieInfo(
144
111
  url = url,
145
- poster = self.fix_url(poster) if poster else None,
146
- title = self.clean_title(title) if title else "",
112
+ poster = self.fix_url(poster),
113
+ title = title,
147
114
  description = description,
148
115
  tags = tags,
149
116
  year = year,
150
117
  rating = rating,
151
- duration = int(duration) if duration else None,
118
+ duration = duration,
152
119
  actors = actors,
153
120
  )
154
121
 
155
122
  async def load_links(self, url: str) -> list[ExtractResult]:
156
123
  istek = await self.httpx.get(url)
157
- secici = HTMLParser(istek.text)
124
+ secici = HTMLHelper(istek.text)
158
125
 
159
126
  results = []
160
-
161
- for player in secici.css("div#tv-spoox2"):
162
- iframe_el = player.css_first("iframe")
163
- iframe = iframe_el.attrs.get("src") if iframe_el else None
164
-
165
- if iframe:
166
- iframe = self.fix_url(iframe)
167
- data = await self.extract(iframe)
168
- if data:
169
- results.append(data)
127
+ for tab in secici.select("ul.tab.alternative-group li[data-number]"):
128
+ tab_id = tab.attrs.get("data-number")
129
+ tab_name = secici.select_text(None, tab)
130
+ tab_hash = tab.attrs.get("data-group-hash")
131
+
132
+ if not tab_id:
133
+ continue
134
+
135
+ button_data = [] # (player_name, iframe_url)
136
+
137
+ # İlgili content divini bul
138
+ content_div = secici.select_first(f"div#{tab_id}")
139
+
140
+ # Eğer div var ve içi doluysa oradan al
141
+ if content_div and secici.select("ul li button", content_div):
142
+ buttons = secici.select("ul li button", content_div)
143
+ for btn in buttons:
144
+ button_data.append((btn.text(strip=True), btn.attrs.get("data-hhs")))
145
+
146
+ elif tab_hash:
147
+ # Div yok veya boş, AJAX ile çek
148
+ with suppress(Exception):
149
+ hash_resp = await self.httpx.post(
150
+ url = f"{self.main_url}/get/video/group",
151
+ headers = {
152
+ "X-Requested-With" : "XMLHttpRequest",
153
+ "Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8",
154
+ "Referer" : url
155
+ },
156
+ data = {"hash": tab_hash}
157
+ )
158
+
159
+ if hash_resp.status_code == 200:
160
+ json_data = hash_resp.json()
161
+ if json_data.get("success"):
162
+ # 1. Videos listesi (API yanıtı)
163
+ if videos := json_data.get("videos"):
164
+ for vid in videos:
165
+ button_data.append((vid.get("name"), vid.get("link")))
166
+
167
+ # 2. HTML content (Fallback)
168
+ else:
169
+ html_content = json_data.get("content") or json_data.get("html") or json_data.get("theme")
170
+ if html_content:
171
+ sub_helper = HTMLHelper(html_content)
172
+ sub_btns = sub_helper.select("ul li button")
173
+ for btn in sub_btns:
174
+ button_data.append((btn.text(strip=True), btn.attrs.get("data-hhs")))
175
+
176
+ for player_name, iframe_url in button_data:
177
+ with suppress(Exception):
178
+ if iframe_url:
179
+ data = await self.extract(
180
+ url = self.fix_url(iframe_url),
181
+ name_override = f"{tab_name} | {player_name}"
182
+ )
183
+ if data:
184
+ if isinstance(data, list):
185
+ results.extend(data)
186
+ else:
187
+ results.append(data)
188
+
189
+ # Eğer hiç sonuç bulunamazsa fallback
190
+ if not results:
191
+ for player in secici.select("div#tv-spoox2"):
192
+ if iframe := secici.select_attr("iframe", "src", player):
193
+ iframe = self.fix_url(iframe)
194
+ data = await self.extract(iframe)
195
+ if data:
196
+ if isinstance(data, list):
197
+ results.extend(data)
198
+ else:
199
+ results.append(data)
170
200
 
171
201
  return results
@@ -0,0 +1,199 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult, HTMLHelper
4
+ import asyncio
5
+
6
+ class FilmEkseni(PluginBase):
7
+ name = "FilmEkseni"
8
+ language = "tr"
9
+ main_url = "https://filmekseni.cc"
10
+ favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
11
+ description = "Film Ekseni ⚡️ Vizyonda ki, en güncel ve en yeni filmleri full hd kalitesinde türkçe dublaj ve altyazı seçenekleriyle 1080p olarak izleyebileceğiniz adresiniz."
12
+
13
+ main_page = {
14
+ f"{main_url}/tur/aile-filmleri/page" : "Aile Filmleri",
15
+ f"{main_url}/tur/aksiyon-filmleri/page" : "Aksiyon Filmleri",
16
+ f"{main_url}/tur/animasyon-film-izle/page" : "Animasyon Filmleri",
17
+ f"{main_url}/tur/bilim-kurgu-filmleri/page" : "Bilim Kurgu Filmleri",
18
+ f"{main_url}/tur/biyografi-filmleri/page" : "Biyografi Filmleri",
19
+ f"{main_url}/tur/dram-filmleri-izle/page" : "Dram Filmleri",
20
+ f"{main_url}/tur/fantastik-filmler/page" : "Fantastik Filmleri",
21
+ f"{main_url}/tur/gerilim-filmleri/page" : "Gerilim Filmleri",
22
+ f"{main_url}/tur/gizem-filmleri/page" : "Gizem Filmleri",
23
+ f"{main_url}/tur/komedi-filmleri/page" : "Komedi Filmleri",
24
+ f"{main_url}/tur/korku-filmleri/page" : "Korku Filmleri",
25
+ f"{main_url}/tur/macera-filmleri/page" : "Macera Filmleri",
26
+ f"{main_url}/tur/romantik-filmler/page" : "Romantik Filmleri",
27
+ f"{main_url}/tur/savas-filmleri/page" : "Savaş Filmleri",
28
+ f"{main_url}/tur/suc-filmleri/page" : "Suç Filmleri",
29
+ f"{main_url}/tur/tarih-filmleri/page" : "Tarih Filmleri",
30
+ }
31
+
32
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
33
+ istek = await self.httpx.get(f"{url}/{page}/")
34
+ secici = HTMLHelper(istek.text)
35
+ posters = secici.select("div.poster")
36
+
37
+ return [
38
+ MainPageResult(
39
+ category = category,
40
+ title = self.clean_title(secici.select_text("h2", veri)),
41
+ url = secici.select_attr("a", "href", veri),
42
+ poster = secici.select_attr("img", "data-src", veri)
43
+ )
44
+ for veri in posters
45
+ ]
46
+
47
+ async def search(self, query: str) -> list[SearchResult]:
48
+ istek = await self.httpx.post(
49
+ url = f"{self.main_url}/search/",
50
+ headers = {
51
+ "X-Requested-With" : "XMLHttpRequest",
52
+ "Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8",
53
+ "Referer" : self.main_url,
54
+ },
55
+ data = {"query": query}
56
+ )
57
+
58
+ veriler = istek.json().get("result", [])
59
+
60
+ return [
61
+ SearchResult(
62
+ title = veri.get("title"),
63
+ url = f"{self.main_url}/{veri.get('slug')}",
64
+ poster = f"{self.main_url}/uploads/poster/{veri.get('cover')}" if veri.get('cover') else None,
65
+ )
66
+ for veri in veriler
67
+ ]
68
+
69
+ async def load_item(self, url: str) -> MovieInfo:
70
+ istek = await self.httpx.get(url)
71
+ secici = HTMLHelper(istek.text)
72
+
73
+ title = self.clean_title(secici.select_text("div.page-title h1"))
74
+ poster = secici.select_poster("picture.poster-auto img")
75
+ description = secici.select_direct_text("article.text-white p")
76
+ year = secici.extract_year("div.page-title", "strong a")
77
+ tags = secici.select_texts("div.pb-2 a[href*='/tur/']")
78
+ rating = secici.select_text("div.rate")
79
+ duration = secici.regex_first(r"(\d+)", secici.select_text("div.d-flex.flex-column.text-nowrap"))
80
+ actors = secici.select_texts("div.card-body.p-0.pt-2 .story-item .story-item-title")
81
+
82
+ return MovieInfo(
83
+ url = url,
84
+ poster = self.fix_url(poster),
85
+ title = title,
86
+ description = description,
87
+ tags = tags,
88
+ rating = rating,
89
+ year = year,
90
+ actors = actors,
91
+ duration = duration
92
+ )
93
+
94
+ async def _get_source_links(self, name: str, url: str, is_active: bool, initial_helper: HTMLHelper | None = None) -> list[ExtractResult]:
95
+ try:
96
+ if is_active and initial_helper:
97
+ secici = initial_helper
98
+ else:
99
+ resp = await self.httpx.get(url)
100
+ secici = HTMLHelper(resp.text)
101
+
102
+ iframe = secici.select_first("div.card-video iframe")
103
+ if not iframe:
104
+ return []
105
+
106
+ iframe_url = iframe.attrs.get("data-src") or iframe.attrs.get("src")
107
+ if not iframe_url:
108
+ return []
109
+
110
+ iframe_url = self.fix_url(iframe_url)
111
+ results = []
112
+
113
+ # VIP / EksenLoad mantığı
114
+ if "eksenload" in iframe_url or name == "VIP":
115
+ video_id = iframe_url.split("/")[-1]
116
+ master_url = f"https://eksenload.site/uploads/encode/{video_id}/master.m3u8"
117
+ results.append(ExtractResult(
118
+ url = master_url,
119
+ name = name,
120
+ referer = self.main_url
121
+ ))
122
+ else:
123
+ # Diğerleri (Moly, vs.) için extract
124
+ # Name override: "Kaynak Adı | Player Adı" olacak şekilde
125
+ extracted = await self.extract(iframe_url, name_override=name)
126
+ if extracted:
127
+ if isinstance(extracted, list):
128
+ results.extend(extracted)
129
+ else:
130
+ results.append(extracted)
131
+
132
+ return results
133
+ except Exception:
134
+ return []
135
+
136
+ async def load_links(self, url: str) -> list[ExtractResult]:
137
+ istek = await self.httpx.get(url)
138
+ secici = HTMLHelper(istek.text)
139
+
140
+ # Dil sekmelerini bul (Dublaj, Altyazı vb.)
141
+ # Fragman vb. linkleri dahil etmemek için sadece 'a.nav-link' bakıyoruz
142
+ lang_tabs = [
143
+ tab for tab in secici.select("ul.nav-tabs.nav-slider a.nav-link")
144
+ if "fragman" not in tab.text().lower()
145
+ ]
146
+
147
+ # Player panellerini bul
148
+ tab_panes = secici.select("div.tab-pane")
149
+
150
+ sources = [] # (name, url, is_active)
151
+
152
+ # Eğer dil sekmeleri ve paneller eşleşiyorsa (ideal durum)
153
+ if lang_tabs and tab_panes:
154
+ for i, pane in enumerate(tab_panes):
155
+ if i >= len(lang_tabs):
156
+ break
157
+
158
+ lang_name = lang_tabs[i].text(strip=True)
159
+ player_links = secici.select("a.nav-link", element=pane)
160
+
161
+ for link in player_links:
162
+ p_name = link.text(strip=True)
163
+ if not p_name or any(x in p_name.lower() for x in ["paylaş", "indir", "hata"]):
164
+ continue
165
+
166
+ href = link.attrs.get("href")
167
+ if not href or href == "#":
168
+ continue
169
+
170
+ # Yeni isim "Moly | Türkçe Dublaj"
171
+ full_name = f"{p_name} | {lang_name}"
172
+ is_active = "active" in link.attrs.get("class", "")
173
+
174
+ sources.append((full_name, self.fix_url(href), is_active))
175
+
176
+ # Eğer panel yapısı beklediğimizden farklıysa eski mantığa dön
177
+ if not sources:
178
+ if nav_links := secici.select("nav.card-nav a.nav-link"):
179
+ seen_urls = set()
180
+ for link in nav_links:
181
+ if link.attrs.get("href") == "#":
182
+ continue # Sinema Modu vb.
183
+
184
+ name = link.text(strip=True)
185
+ href = link.attrs.get("href")
186
+ is_active = "active" in link.attrs.get("class", "")
187
+
188
+ if href and href not in seen_urls:
189
+ seen_urls.add(href)
190
+ sources.append((name, self.fix_url(href), is_active))
191
+ else:
192
+ # Nav yoksa mevcut sayfayı (Varsayılan/VIP) al
193
+ sources.append(("VIP", url, True))
194
+
195
+ tasks = []
196
+ for name, link_url, is_active in sources:
197
+ tasks.append(self._get_source_links(name, link_url, is_active, secici if is_active else None))
198
+
199
+ return [item for sublist in await asyncio.gather(*tasks) for item in sublist]
@@ -1,7 +1,6 @@
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
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult, HTMLHelper
5
4
 
6
5
  class FilmMakinesi(PluginBase):
7
6
  name = "FilmMakinesi"
@@ -36,89 +35,96 @@ class FilmMakinesi(PluginBase):
36
35
 
37
36
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
38
37
  istek = self.cloudscraper.get(f"{url}{'' if page == 1 else f'page/{page}/'}")
39
- secici = HTMLParser(istek.text)
38
+ secici = HTMLHelper(istek.text)
40
39
 
41
40
  results = []
42
- for veri in secici.css("div.item-relative"):
43
- title_el = veri.css_first("div.title")
44
- link_el = veri.css_first("a")
45
- img_el = veri.css_first("img")
46
-
47
- title = title_el.text(strip=True) if title_el else None
48
- href = link_el.attrs.get("href") if link_el else None
49
- poster = (img_el.attrs.get("data-src") or img_el.attrs.get("src")) if img_el else None
41
+ for veri in secici.select("div.item-relative"):
42
+ title = secici.select_text("div.title", veri)
43
+ href = secici.select_attr("a", "href", veri)
44
+ poster = secici.select_poster("img", veri)
50
45
 
51
46
  if title and href:
52
47
  results.append(MainPageResult(
53
48
  category = category,
54
49
  title = title,
55
50
  url = self.fix_url(href),
56
- poster = self.fix_url(poster) if poster else None,
51
+ poster = self.fix_url(poster),
57
52
  ))
58
53
 
59
54
  return results
60
55
 
61
56
  async def search(self, query: str) -> list[SearchResult]:
62
57
  istek = await self.httpx.get(f"{self.main_url}/arama/?s={query}")
63
- secici = HTMLParser(istek.text)
58
+ secici = HTMLHelper(istek.text)
64
59
 
65
60
  results = []
66
- for article in secici.css("div.item-relative"):
67
- title_el = article.css_first("div.title")
68
- link_el = article.css_first("a")
69
- img_el = article.css_first("img")
70
-
71
- title = title_el.text(strip=True) if title_el else None
72
- href = link_el.attrs.get("href") if link_el else None
73
- poster = (img_el.attrs.get("data-src") or img_el.attrs.get("src")) if img_el else None
61
+ for article in secici.select("div.item-relative"):
62
+ title = secici.select_text("div.title", article)
63
+ href = secici.select_attr("a", "href", article)
64
+ poster = secici.select_poster("img", article)
74
65
 
75
66
  if title and href:
76
67
  results.append(SearchResult(
77
- title = title.strip(),
78
- url = self.fix_url(href.strip()),
79
- poster = self.fix_url(poster.strip()) if poster else None,
68
+ title = title,
69
+ url = self.fix_url(href),
70
+ poster = self.fix_url(poster),
80
71
  ))
81
72
 
82
73
  return results
83
74
 
84
- async def load_item(self, url: str) -> MovieInfo:
75
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
85
76
  istek = await self.httpx.get(url)
86
- secici = HTMLParser(istek.text)
87
-
88
- title_el = secici.css_first("h1.title")
89
- title = title_el.text(strip=True) if title_el else ""
90
-
91
- poster_el = secici.css_first("img.cover-img")
92
- poster = poster_el.attrs.get("src", "").strip() if poster_el else ""
93
-
94
- desc_el = secici.css_first("div.info-description p")
95
- description = desc_el.text(strip=True) if desc_el else ""
96
-
97
- rating_el = secici.css_first("div.score")
98
- rating = None
99
- if rating_el:
100
- rating_text = rating_el.text(strip=True)
101
- if rating_text:
102
- rating = rating_text.split()[0]
103
-
104
- year_el = secici.css_first("span.date a")
105
- year = year_el.text(strip=True) if year_el else ""
106
-
107
- actors = [el.text(strip=True) for el in secici.css("div.cast-name") if el.text(strip=True)]
108
- tags = [el.text(strip=True) for el in secici.css("div.genre a") if el.text(strip=True)]
77
+ secici = HTMLHelper(istek.text)
78
+
79
+ title = self.clean_title(secici.select_text("h1.title"))
80
+ poster = secici.select_poster("img.cover-img")
81
+ description = secici.select_text("div.info-description")
82
+ rating = secici.select_text("div.info div.imdb b")
83
+ year = secici.select_text("span.date a")
84
+ actors = secici.select_texts("div.cast-name")
85
+ tags = secici.select_texts("div.type a[href*='/tur/']")
86
+ duration = secici.regex_first(r"(\d+)", secici.select_text("div.time"))
87
+
88
+ episodes = []
89
+ for link in secici.select("a[href]"):
90
+ href = link.attrs.get("href", "")
91
+ s, e = secici.extract_season_episode(href)
92
+ if s and e:
93
+ name = link.text(strip=True).split("Bölüm")[-1].strip() if "Bölüm" in link.text() else ""
94
+ episodes.append(Episode(
95
+ season = s,
96
+ episode = e,
97
+ title = name,
98
+ url = self.fix_url(href)
99
+ ))
109
100
 
110
- duration_el = secici.css_first("div.time")
111
- duration = None
112
- if duration_el:
113
- duration_text = duration_el.text(strip=True)
114
- parts = duration_text.split()
115
- if len(parts) > 1:
116
- duration = parts[1].strip()
101
+ # Tekrar edenleri temizle ve sırala
102
+ if episodes:
103
+ seen = set()
104
+ unique = []
105
+ for ep in episodes:
106
+ if (ep.season, ep.episode) not in seen:
107
+ seen.add((ep.season, ep.episode))
108
+ unique.append(ep)
109
+ unique.sort(key=lambda x: (x.season or 0, x.episode or 0))
110
+
111
+ return SeriesInfo(
112
+ url = url,
113
+ poster = self.fix_url(poster),
114
+ title = title,
115
+ description = description,
116
+ tags = tags,
117
+ rating = rating,
118
+ year = year,
119
+ actors = actors,
120
+ duration = duration,
121
+ episodes = unique
122
+ )
117
123
 
118
124
  return MovieInfo(
119
125
  url = url,
120
- poster = self.fix_url(poster) if poster else None,
121
- title = self.clean_title(title),
126
+ poster = self.fix_url(poster),
127
+ title = title,
122
128
  description = description,
123
129
  tags = tags,
124
130
  rating = rating,
@@ -129,27 +135,58 @@ class FilmMakinesi(PluginBase):
129
135
 
130
136
  async def load_links(self, url: str) -> list[ExtractResult]:
131
137
  istek = await self.httpx.get(url)
132
- secici = HTMLParser(istek.text)
138
+ secici = HTMLHelper(istek.text)
133
139
 
134
- response = []
140
+ response = []
141
+ shared_subs = []
135
142
 
136
143
  # Video parts linklerini ve etiketlerini al
137
- for link in secici.css("div.video-parts a[data-video_url]"):
144
+ for link in secici.select("div.video-parts a[data-video_url]"):
138
145
  video_url = link.attrs.get("data-video_url")
139
146
  label = link.text(strip=True) if link.text(strip=True) else ""
140
147
 
141
148
  if video_url:
142
149
  data = await self.extract(video_url, prefix=label.split()[0] if label else None)
143
150
  if data:
144
- response.append(data)
151
+ if isinstance(data, list):
152
+ for d in data:
153
+ response.append(d)
154
+ if d.subtitles:
155
+ shared_subs.extend(d.subtitles)
156
+ else:
157
+ response.append(data)
158
+ if data.subtitles:
159
+ shared_subs.extend(data.subtitles)
145
160
 
146
161
  # Eğer video-parts yoksa iframe kullan
147
162
  if not response:
148
- iframe_el = secici.css_first("iframe")
149
- iframe_src = iframe_el.attrs.get("data-src") if iframe_el else None
163
+ iframe_src = secici.select_attr("iframe", "data-src")
150
164
  if iframe_src:
151
165
  data = await self.extract(iframe_src)
152
166
  if data:
153
- response.append(data)
167
+ if isinstance(data, list):
168
+ for d in data:
169
+ response.append(d)
170
+ if d.subtitles:
171
+ shared_subs.extend(d.subtitles)
172
+ else:
173
+ response.append(data)
174
+ if data.subtitles:
175
+ shared_subs.extend(data.subtitles)
176
+
177
+ # Altyazıları Dağıt
178
+ if shared_subs:
179
+ unique_subs = []
180
+ seen_urls = set()
181
+ for sub in shared_subs:
182
+ if sub.url not in seen_urls:
183
+ seen_urls.add(sub.url)
184
+ unique_subs.append(sub)
185
+
186
+ for res in response:
187
+ current_urls = {s.url for s in res.subtitles}
188
+ for sub in unique_subs:
189
+ if sub.url not in current_urls:
190
+ res.subtitles.append(sub)
154
191
 
155
192
  return response