KekikStream 2.3.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.
Files changed (85) hide show
  1. KekikStream/Core/Extractor/ExtractorBase.py +3 -2
  2. KekikStream/Core/Extractor/ExtractorLoader.py +8 -14
  3. KekikStream/Core/HTMLHelper.py +120 -49
  4. KekikStream/Core/Plugin/PluginBase.py +30 -12
  5. KekikStream/Core/Plugin/PluginLoader.py +12 -14
  6. KekikStream/Core/Plugin/PluginManager.py +2 -2
  7. KekikStream/Core/Plugin/PluginModels.py +0 -3
  8. KekikStream/Extractors/Abstream.py +27 -0
  9. KekikStream/Extractors/CloseLoad.py +30 -54
  10. KekikStream/Extractors/ContentX.py +27 -72
  11. KekikStream/Extractors/DonilasPlay.py +33 -77
  12. KekikStream/Extractors/DzenRu.py +10 -24
  13. KekikStream/Extractors/ExPlay.py +20 -38
  14. KekikStream/Extractors/Filemoon.py +21 -46
  15. KekikStream/Extractors/HDMomPlayer.py +30 -0
  16. KekikStream/Extractors/HDPlayerSystem.py +13 -31
  17. KekikStream/Extractors/HotStream.py +27 -0
  18. KekikStream/Extractors/JFVid.py +3 -24
  19. KekikStream/Extractors/JetTv.py +21 -34
  20. KekikStream/Extractors/JetV.py +55 -0
  21. KekikStream/Extractors/MailRu.py +11 -29
  22. KekikStream/Extractors/MixPlayHD.py +15 -28
  23. KekikStream/Extractors/MixTiger.py +17 -40
  24. KekikStream/Extractors/MolyStream.py +17 -21
  25. KekikStream/Extractors/Odnoklassniki.py +40 -104
  26. KekikStream/Extractors/PeaceMakerst.py +18 -45
  27. KekikStream/Extractors/PixelDrain.py +8 -16
  28. KekikStream/Extractors/PlayerFilmIzle.py +22 -41
  29. KekikStream/Extractors/RapidVid.py +21 -35
  30. KekikStream/Extractors/SetPlay.py +18 -43
  31. KekikStream/Extractors/SibNet.py +7 -17
  32. KekikStream/Extractors/Sobreatsesuyp.py +23 -45
  33. KekikStream/Extractors/TRsTX.py +23 -53
  34. KekikStream/Extractors/TurboImgz.py +7 -14
  35. KekikStream/Extractors/VCTPlay.py +10 -28
  36. KekikStream/Extractors/Veev.py +145 -0
  37. KekikStream/Extractors/VidBiz.py +62 -0
  38. KekikStream/Extractors/VidHide.py +58 -30
  39. KekikStream/Extractors/VidMoly.py +65 -99
  40. KekikStream/Extractors/VidMoxy.py +16 -27
  41. KekikStream/Extractors/VidPapi.py +24 -54
  42. KekikStream/Extractors/VideoSeyred.py +19 -40
  43. KekikStream/Extractors/Videostr.py +58 -0
  44. KekikStream/Extractors/Vidoza.py +18 -0
  45. KekikStream/Extractors/Vtbe.py +38 -0
  46. KekikStream/Extractors/YTDLP.py +2 -2
  47. KekikStream/Extractors/YildizKisaFilm.py +13 -31
  48. KekikStream/Extractors/Zeus.py +61 -0
  49. KekikStream/Plugins/BelgeselX.py +97 -77
  50. KekikStream/Plugins/DiziBox.py +28 -45
  51. KekikStream/Plugins/DiziMom.py +179 -0
  52. KekikStream/Plugins/DiziPal.py +95 -161
  53. KekikStream/Plugins/DiziYou.py +51 -147
  54. KekikStream/Plugins/Dizilla.py +40 -61
  55. KekikStream/Plugins/FilmBip.py +90 -39
  56. KekikStream/Plugins/FilmEkseni.py +199 -0
  57. KekikStream/Plugins/FilmMakinesi.py +72 -73
  58. KekikStream/Plugins/FilmModu.py +25 -35
  59. KekikStream/Plugins/Filmatek.py +184 -0
  60. KekikStream/Plugins/FilmciBaba.py +155 -0
  61. KekikStream/Plugins/FullHDFilmizlesene.py +16 -37
  62. KekikStream/Plugins/HDFilm.py +243 -0
  63. KekikStream/Plugins/HDFilmCehennemi.py +242 -189
  64. KekikStream/Plugins/JetFilmizle.py +101 -69
  65. KekikStream/Plugins/KultFilmler.py +138 -104
  66. KekikStream/Plugins/RecTV.py +52 -73
  67. KekikStream/Plugins/RoketDizi.py +18 -27
  68. KekikStream/Plugins/SelcukFlix.py +30 -48
  69. KekikStream/Plugins/SetFilmIzle.py +76 -104
  70. KekikStream/Plugins/SezonlukDizi.py +90 -94
  71. KekikStream/Plugins/Sinefy.py +195 -167
  72. KekikStream/Plugins/SinemaCX.py +148 -78
  73. KekikStream/Plugins/Sinezy.py +29 -31
  74. KekikStream/Plugins/SuperFilmGeldi.py +12 -17
  75. KekikStream/Plugins/UgurFilm.py +85 -38
  76. KekikStream/Plugins/Watch32.py +160 -0
  77. KekikStream/Plugins/YabanciDizi.py +176 -211
  78. {kekikstream-2.3.9.dist-info → kekikstream-2.5.3.dist-info}/METADATA +1 -1
  79. kekikstream-2.5.3.dist-info/RECORD +99 -0
  80. {kekikstream-2.3.9.dist-info → kekikstream-2.5.3.dist-info}/WHEEL +1 -1
  81. KekikStream/Plugins/FullHDFilm.py +0 -249
  82. kekikstream-2.3.9.dist-info/RECORD +0 -84
  83. {kekikstream-2.3.9.dist-info → kekikstream-2.5.3.dist-info}/entry_points.txt +0 -0
  84. {kekikstream-2.3.9.dist-info → kekikstream-2.5.3.dist-info}/licenses/LICENSE +0 -0
  85. {kekikstream-2.3.9.dist-info → kekikstream-2.5.3.dist-info}/top_level.txt +0 -0
@@ -1,6 +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, SeriesInfo, Episode, Subtitle, ExtractResult, HTMLHelper
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, Subtitle, ExtractResult, HTMLHelper
4
4
 
5
5
  class DiziYou(PluginBase):
6
6
  name = "DiziYou"
@@ -42,7 +42,7 @@ class DiziYou(PluginBase):
42
42
  category = category,
43
43
  title = title,
44
44
  url = self.fix_url(href),
45
- poster = self.fix_url(poster) if poster else None,
45
+ poster = self.fix_url(poster),
46
46
  ))
47
47
 
48
48
  return results
@@ -61,7 +61,7 @@ class DiziYou(PluginBase):
61
61
  results.append(SearchResult(
62
62
  title = title,
63
63
  url = self.fix_url(href),
64
- poster = self.fix_url(poster) if poster else None,
64
+ poster = self.fix_url(poster),
65
65
  ))
66
66
 
67
67
  return results
@@ -69,87 +69,29 @@ class DiziYou(PluginBase):
69
69
  async def load_item(self, url: str) -> SeriesInfo:
70
70
  istek = await self.httpx.get(url)
71
71
  secici = HTMLHelper(istek.text)
72
- html_text = istek.text
73
72
 
74
- # Title - div.title h1 içinde
75
- title = (secici.select_text("div.title h1") or "").strip()
76
-
77
- # Fallback: Eğer title boşsa URL'den çıkar (telif kısıtlaması olan sayfalar için)
78
- if not title:
79
- # URL'den slug'ı al: https://www.diziyou.one/jasmine/ -> jasmine -> Jasmine
80
- slug = url.rstrip('/').split('/')[-1]
81
- title = slug.replace('-', ' ').title()
82
-
83
- # Poster
84
- poster_src = secici.select_attr("div.category_image img", "src") or secici.select_attr("meta[property='og:image']", "content")
85
- poster = self.fix_url(poster_src) if poster_src else ""
86
-
87
- # Year - regex ile çıkarma (xpath yerine)
88
- year = secici.regex_first(r"(?is)Yapım Yılı.*?(\d{4})", secici.html)
89
-
90
- description_el = secici.select("div.diziyou_desc") or secici.select("div#icerikcat")
91
- description = ""
92
- if description_el:
93
- # Scriptleri temizle
94
- for script in secici.select("script", description_el[0]):
95
- script.decompose()
96
- description = secici.select_text(None, description_el[0])
97
-
98
- tags = [secici.select_text(None, a) for a in secici.select("div.genres a") if secici.select_text(None, a)]
99
-
100
- # Rating - daha spesifik regex ile
101
- rating = secici.regex_first(r"(?is)IMDB\s*:\s*</span>([0-9.]+)", secici.html)
102
-
103
- # Actors - regex ile
104
- actors_raw = secici.regex_first(r"(?is)Oyuncular.*?</span>([^<]+)", secici.html)
105
- actors = [actor.strip() for actor in actors_raw.split(",") if actor.strip()] if actors_raw else []
73
+ poster = secici.select_poster("div.category_image img")
74
+ title = secici.select_text("h1.title-border")
75
+ description = secici.select_direct_text("div#icerikcatright")
76
+ tags = secici.select_texts("div.genres a")
77
+ rating = secici.regex_first(r"(?is)IMDB\s*:\s*</span>([0-9.]+)", secici.html)
78
+ year = secici.extract_year("div#icerikcat2")
79
+ raw_actors = secici.meta_value("Oyuncular", container_selector="div#icerikcat2")
80
+ actors = [x.strip() for x in raw_actors.split(",")] if raw_actors else None
106
81
 
107
82
  episodes = []
108
- # Episodes - div#scrollbar-container a (kısıtlı alan)
109
83
  for link in secici.select("div#scrollbar-container a"):
110
- ep_href = secici.select_attr(None, "href", link)
111
- if not ep_href:
112
- continue
113
-
114
- # Link metni veya alt başlık al
115
- ep_name = (secici.select_text(None, link) or "").strip()
116
- title_child = secici.select_text("div.baslik", link) or secici.select_text("div.bolumismi", link)
117
- if title_child:
118
- ep_name = title_child
119
-
120
- # Önce metin üzerinden sezon/bölüm çıkart
121
- s_val, e_val = HTMLHelper.extract_season_episode(ep_name)
122
-
123
- # URL bazlı kalıplar: -1-sezon-2-bolum gibi
124
- if not (s_val or e_val):
125
- pairs = HTMLHelper(ep_href).regex_all(r"-(\d+)-sezon-(\d+)-bolum")
126
- if pairs:
127
- s_val, e_val = int(pairs[0][0]), int(pairs[0][1])
128
- else:
129
- pairs = HTMLHelper(ep_href).regex_all(r"(\d+)-sezon-(\d+)-bolum")
130
- if pairs:
131
- s_val, e_val = int(pairs[0][0]), int(pairs[0][1])
132
- else:
133
- e_val_str = HTMLHelper(ep_href).regex_first(r"(\d+)-bolum")
134
- if e_val_str:
135
- e_val = int(e_val_str)
136
- # Metin üzerinden son bir deneme
137
- if not e_val:
138
- e_str = HTMLHelper(ep_name).regex_first(r"(\d+)\s*\.\s*[Bb]ölüm")
139
- if e_str:
140
- e_val = int(e_str)
141
- if not s_val:
142
- s_str = HTMLHelper(ep_name).regex_first(r"(\d+)\s*\.\s*[Ss]ezon")
143
- if s_str:
144
- s_val = int(s_str)
145
-
146
- if e_val or HTMLHelper(ep_href).regex_first(r"-\d+-sezon-\d+-bolum"):
147
- episodes.append(Episode(
148
- season = s_val,
149
- episode = e_val,
150
- title = ep_name if ep_name else None,
151
- url = self.fix_url(ep_href),
152
- ))
84
+ href = secici.select_attr(None, "href", link)
85
+ if href:
86
+ name = secici.select_text("div.bolumismi", link).strip("()")
87
+ s, e = secici.extract_season_episode(f"{name} {href}")
88
+ if e:
89
+ episodes.append(Episode(
90
+ season = s or 1,
91
+ episode = e,
92
+ title = name,
93
+ url = self.fix_url(href)
94
+ ))
153
95
 
154
96
  return SeriesInfo(
155
97
  url = url,
@@ -167,77 +109,39 @@ class DiziYou(PluginBase):
167
109
  istek = await self.httpx.get(url)
168
110
  secici = HTMLHelper(istek.text)
169
111
 
170
- # Title ve episode name - None kontrolü ekle
171
- item_title = secici.select_text("div.title h1")
172
- ep_name = secici.select_text("div#bolum-ismi")
173
-
174
- # Player src'den item_id çıkar
175
- # Player src'den item_id çıkar - önce özel player seçicisini dene
176
- player_src = None
177
- # Yaygın locatorlar
178
- for sel in ["iframe#diziyouPlayer", "div.player iframe", "iframe[src*='/player/']", "iframe[src*='/episodes/']", "iframe"]:
179
- p = secici.select_attr(sel, "src")
180
- if p and any(x in p.lower() for x in ["/player/", "/episodes/", "diziyou"]):
181
- player_src = p
182
- break
183
-
184
- # Eğer hâlâ bulunamadıysa, varsa bir bölüm sayfasına git ve oradan player'ı çek
185
- if not player_src:
186
- for a in secici.select("a"):
187
- href = secici.select_attr("a", "href", a)
188
- if not href:
189
- continue
190
- if HTMLHelper(href).regex_first(r"(-\d+-sezon-\d+-bolum|/bolum|/episode|/episodes|/play)"):
191
- break
192
-
193
- if not player_src:
194
- return [] # Player bulunamadıysa boş liste döndür
195
-
196
- item_id = player_src.split("/")[-1].replace(".html", "")
197
-
198
- subtitles = []
199
- stream_urls = []
200
-
201
- for secenek in secici.select("span.diziyouOption"):
202
- opt_id = secici.select_attr("span.diziyouOption", "id", secenek)
203
- op_name = secici.select_text("span.diziyouOption", secenek)
204
-
205
- match opt_id:
206
- case "turkceAltyazili":
207
- subtitles.append(Subtitle(
208
- name = op_name,
209
- url = self.fix_url(f"{self.main_url.replace('www', 'storage')}/subtitles/{item_id}/tr.vtt"),
210
- ))
211
- veri = {
212
- "dil": "Orjinal Dil (TR Altyazı)",
213
- "url": f"{self.main_url.replace('www', 'storage')}/episodes/{item_id}/play.m3u8"
214
- }
215
- if veri not in stream_urls:
216
- stream_urls.append(veri)
217
- case "ingilizceAltyazili":
218
- subtitles.append(Subtitle(
219
- name = op_name,
220
- url = self.fix_url(f"{self.main_url.replace('www', 'storage')}/subtitles/{item_id}/en.vtt"),
221
- ))
222
- veri = {
223
- "dil": "Orjinal Dil (EN Altyazı)",
224
- "url": f"{self.main_url.replace('www', 'storage')}/episodes/{item_id}/play.m3u8"
225
- }
226
- if veri not in stream_urls:
227
- stream_urls.append(veri)
228
- case "turkceDublaj":
229
- stream_urls.append({
230
- "dil": "Türkçe Dublaj",
231
- "url": f"{self.main_url.replace('www', 'storage')}/episodes/{item_id}_tr/play.m3u8"
232
- })
112
+ # Player iframe'inden ID'yi yakala
113
+ iframe_src = secici.select_attr("iframe#diziyouPlayer", "src") or secici.select_attr("iframe[src*='/player/']", "src")
114
+ if not iframe_src:
115
+ return []
116
+
117
+ item_id = iframe_src.split("/")[-1].replace(".html", "")
118
+ base_storage = self.main_url.replace("www", "storage")
119
+
120
+ subtitles = []
121
+ for sub in [("turkceAltyazili", "tr", "Türkçe"), ("ingilizceAltyazili", "en", "İngilizce")]:
122
+ if secici.select_first(f"span#{sub[0]}"):
123
+ subtitles.append(Subtitle(
124
+ name = f"{sub[2]} Altyazı",
125
+ url = f"{base_storage}/subtitles/{item_id}/{sub[1]}.vtt"
126
+ ))
233
127
 
234
128
  results = []
235
- for stream in stream_urls:
129
+ # Altyazılı Seçenek (Eğer varsa)
130
+ if secici.select_first("span#turkceAltyazili") or secici.select_first("span#ingilizceAltyazili"):
131
+ results.append(ExtractResult(
132
+ name = "Altyazılı",
133
+ url = f"{base_storage}/episodes/{item_id}/play.m3u8",
134
+ referer = url,
135
+ subtitles = subtitles
136
+ ))
137
+
138
+ # Dublaj Seçeneği (Eğer varsa)
139
+ if secici.select_first("span#turkceDublaj"):
236
140
  results.append(ExtractResult(
237
- url = stream.get("url"),
238
- name = f"{stream.get('dil')}",
141
+ name = "Türkçe Dublaj",
142
+ url = f"{base_storage}/episodes/{item_id}_tr/play.m3u8",
239
143
  referer = url,
240
144
  subtitles = subtitles
241
145
  ))
242
146
 
243
- return results
147
+ return results
@@ -1,10 +1,10 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult, HTMLHelper
4
- from json import loads
5
- from urllib.parse import urlparse, urlunparse
6
- from Crypto.Cipher import AES
7
- from base64 import b64decode
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult, HTMLHelper
4
+ from json import loads
5
+ from urllib.parse import urlparse, urlunparse
6
+ from Crypto.Cipher import AES
7
+ from base64 import b64decode
8
8
 
9
9
  class Dizilla(PluginBase):
10
10
  name = "Dizilla"
@@ -44,7 +44,7 @@ class Dizilla(PluginBase):
44
44
  category = category,
45
45
  title = veri.get("original_title"),
46
46
  url = self.fix_url(f"{self.main_url}/{veri.get('used_slug')}"),
47
- poster = self.fix_poster_url(self.fix_url(veri.get("object_poster_url"))),
47
+ poster = self.fix_poster_url(self.fix_url(veri.get("poster_url"))),
48
48
  )
49
49
  for veri in veriler
50
50
  ])
@@ -54,21 +54,21 @@ class Dizilla(PluginBase):
54
54
 
55
55
  # Genel olarak dizi sayfalarına giden linkleri al
56
56
  for veri in secici.select('a[href*="/dizi/"]'):
57
- href = secici.select_attr('a', 'href', veri)
57
+ href = secici.select_attr('a', 'href', veri)
58
58
  title = secici.select_text(None, veri)
59
59
  if not href or not title:
60
60
  continue
61
61
 
62
62
  # Detay sayfasından poster vb. bilgileri al
63
- ep_req = await self.httpx.get(self.fix_url(href))
63
+ ep_req = await self.httpx.get(self.fix_url(href))
64
64
  ep_secici = HTMLHelper(ep_req.text)
65
- poster = ep_secici.select_attr('img.imgt', 'src') or ep_secici.select_attr('img', 'src')
65
+ poster = ep_secici.select_poster('img.imgt') or ep_secici.select_poster('img')
66
66
 
67
67
  ana_sayfa.append(MainPageResult(
68
68
  category = category,
69
69
  title = title,
70
70
  url = self.fix_url(href),
71
- poster = self.fix_url(poster) if poster else None
71
+ poster = self.fix_url(poster)
72
72
  ))
73
73
 
74
74
  return ana_sayfa
@@ -155,43 +155,25 @@ class Dizilla(PluginBase):
155
155
  description = content.get("description") or content.get("used_description")
156
156
  rating = content.get("imdb_point") or content.get("local_vote_avg")
157
157
  year = content.get("release_year")
158
-
159
- # Poster and Backdrop - prefer backdrop if available for SeriesInfo
160
- poster = self.fix_poster_url(self.fix_url(content.get("back_url") or content.get("poster_url")))
161
-
162
- # Tags
163
- tags = []
164
- categories = decrypted.get("RelatedResults", {}).get("getSerieCategoriesById", {}).get("result", [])
165
- for cat in categories:
166
- tags.append(cat.get("name"))
167
-
168
- # Actors
169
- actors = []
170
- casts = decrypted.get("RelatedResults", {}).get("getSerieCastsById", {}).get("result", [])
171
- for cast in casts:
172
- actors.append(cast.get("name"))
173
-
174
- # Episodes
175
- episodes = []
176
- seasons_data = decrypted.get("RelatedResults", {}).get("getSerieSeasonAndEpisodes", {}).get("result", [])
177
- for season_item in seasons_data:
178
- season_num = season_item.get("season_no")
179
- for ep_item in season_item.get("episodes", []):
180
- ep_num = ep_item.get("episode_no")
181
- ep_slug = ep_item.get("used_slug")
182
- ep_name = ep_item.get("episode_text") or ""
183
-
184
- # Filter out duplicate language entries if any (we just need one link per episode)
185
- # Usually they share the same slug for the episode page
186
- if any(e.season == season_num and e.episode == ep_num for e in episodes):
187
- continue
158
+ poster = self.fix_poster_url(self.fix_url(content.get("back_url") or content.get("poster_url")))
188
159
 
189
- episodes.append(Episode(
190
- season = season_num,
191
- episode = ep_num,
192
- title = ep_name,
193
- url = self.fix_url(f"{self.main_url}/{ep_slug}")
194
- ))
160
+ tags = [cat.get("name") for cat in decrypted.get("RelatedResults", {}).get("getSerieCategoriesById", {}).get("result", [])]
161
+ actors = [cast.get("name") for cast in decrypted.get("RelatedResults", {}).get("getSerieCastsById", {}).get("result", [])]
162
+
163
+ episodes = []
164
+ for season in decrypted.get("RelatedResults", {}).get("getSerieSeasonAndEpisodes", {}).get("result", []):
165
+ s_no = season.get("season_no")
166
+ for ep in season.get("episodes", []):
167
+ e_no = ep.get("episode_no")
168
+ slug = ep.get("used_slug")
169
+ name = ep.get("episode_text") or ""
170
+ if not any(e.season == s_no and e.episode == e_no for e in episodes):
171
+ episodes.append(Episode(
172
+ season = s_no,
173
+ episode = e_no,
174
+ title = name,
175
+ url = self.fix_url(f"{self.main_url}/{slug}")
176
+ ))
195
177
 
196
178
  return SeriesInfo(
197
179
  url = url,
@@ -199,21 +181,21 @@ class Dizilla(PluginBase):
199
181
  title = title,
200
182
  description = description,
201
183
  tags = tags,
202
- rating = str(rating) if rating else None,
203
- year = str(year) if year else None,
184
+ rating = rating,
185
+ year = year,
204
186
  episodes = episodes,
205
187
  actors = actors
206
188
  )
207
189
 
208
190
  async def load_links(self, url: str) -> list[ExtractResult]:
209
- istek = await self.httpx.get(url)
210
- secici = HTMLHelper(istek.text)
191
+ istek = await self.httpx.get(url)
192
+ secici = HTMLHelper(istek.text)
211
193
 
212
194
  next_data_text = secici.select_text("script#__NEXT_DATA__")
213
195
  if not next_data_text:
214
196
  return []
215
197
 
216
- next_data = loads(next_data_text)
198
+ next_data = loads(next_data_text)
217
199
  secure_data = next_data.get("props", {}).get("pageProps", {}).get("secureData", {})
218
200
  decrypted = await self.decrypt_response(secure_data)
219
201
  results = decrypted.get("RelatedResults", {}).get("getEpisodeSources", {}).get("result", [])
@@ -221,24 +203,21 @@ class Dizilla(PluginBase):
221
203
  if not results:
222
204
  return []
223
205
 
224
- # Get first source (matching Kotlin)
225
- first_result = results[0]
206
+ first_result = results[0]
226
207
  source_content = str(first_result.get("source_content", ""))
227
-
228
- # Clean the source_content string (matching Kotlin: .replace("\"", "").replace("\\", ""))
208
+
229
209
  cleaned_source = source_content.replace('"', '').replace('\\', '')
230
-
231
- # Parse cleaned HTML
210
+
232
211
  iframe_secici = HTMLHelper(cleaned_source)
233
- iframe_src = iframe_secici.select_attr("iframe", "src")
234
-
235
- # Referer check (matching Kotlin: loadExtractor(iframe, "${mainUrl}/", ...))
212
+ iframe_src = iframe_secici.select_attr("iframe", "src")
213
+
236
214
  iframe_url = self.fix_url(iframe_src) if iframe_src else None
237
-
215
+
238
216
  if not iframe_url:
239
217
  return []
240
218
 
241
219
  data = await self.extract(iframe_url, referer=f"{self.main_url}/", prefix=first_result.get('language_name', 'Unknown'))
242
220
  if not data:
243
221
  return []
222
+
244
223
  return data if isinstance(data, list) else [data]
@@ -1,6 +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, HTMLHelper
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult, HTMLHelper
4
+ from contextlib import suppress
4
5
 
5
6
  class FilmBip(PluginBase):
6
7
  name = "FilmBip"
@@ -39,8 +40,8 @@ class FilmBip(PluginBase):
39
40
 
40
41
  results = []
41
42
  for veri in secici.select("div.poster-long"):
42
- title = secici.select_attr("a.block img.lazy", "alt", veri)
43
- href = secici.select_attr("a.block", "href", veri)
43
+ title = secici.select_attr("a.block img.lazy", "alt", veri)
44
+ href = secici.select_attr("a.block", "href", veri)
44
45
  poster = secici.select_poster("a.block img.lazy", veri)
45
46
 
46
47
  if title and href:
@@ -48,7 +49,7 @@ class FilmBip(PluginBase):
48
49
  category = category,
49
50
  title = title,
50
51
  url = self.fix_url(href),
51
- poster = self.fix_url(poster) if poster else None,
52
+ poster = self.fix_url(poster),
52
53
  ))
53
54
 
54
55
  return results
@@ -87,7 +88,7 @@ class FilmBip(PluginBase):
87
88
  results.append(SearchResult(
88
89
  title = title.strip(),
89
90
  url = self.fix_url(href),
90
- poster = self.fix_url(poster) if poster else None,
91
+ poster = self.fix_url(poster),
91
92
  ))
92
93
 
93
94
  return results
@@ -95,40 +96,26 @@ class FilmBip(PluginBase):
95
96
  async def load_item(self, url: str) -> MovieInfo:
96
97
  istek = await self.httpx.get(url)
97
98
  secici = HTMLHelper(istek.text)
98
- html_text = istek.text
99
-
100
- title = secici.select_text("div.page-title h1") or ""
101
-
102
- poster = secici.select_attr("meta[property='og:image']", "content")
103
-
104
- trailer = secici.select_attr("div.series-profile-trailer", "data-yt")
105
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")
106
102
  description = secici.select_text("div.series-profile-infos-in.article p") or secici.select_text("div.series-profile-summary p")
107
-
108
- tags = secici.select_all_text("div.series-profile-type.tv-show-profile-type a")
109
-
110
- # XPath yerine regex kullanarak yıl, süre vs. çıkarma
111
- year = secici.regex_first(r"(?is)Yap\u0131m y\u0131l\u0131.*?<p[^>]*>(.*?)<\/p>")
112
- if not year:
113
- # Fallback: Başlığın sonundaki parantezli yılı yakala
114
- year = secici.regex_first(r"\((\d{4})\)", title)
115
-
116
- duration_raw = secici.regex_first(r"(?is)S\u00fcre.*?<p[^>]*>(.*?)<\/p>")
117
- duration = secici.regex_first(r"(\d+)", duration_raw) if duration_raw else None
118
-
119
- rating = secici.regex_first(r"(?is)IMDB Puan\u0131.*?<span[^>]*>(.*?)<\/span>")
120
-
121
- actors = [img.attrs.get("alt") for img in secici.select("div.series-profile-cast ul li a img") if img.attrs.get("alt")]
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")
122
109
 
123
110
  return MovieInfo(
124
111
  url = url,
125
- poster = self.fix_url(poster) if poster else None,
126
- title = HTMLHelper(title).regex_replace(r"\(\d{4}\)", "").strip() if title else "",
112
+ poster = self.fix_url(poster),
113
+ title = title,
127
114
  description = description,
128
115
  tags = tags,
129
116
  year = year,
130
117
  rating = rating,
131
- duration = int(duration) if duration else None,
118
+ duration = duration,
132
119
  actors = actors,
133
120
  )
134
121
 
@@ -137,14 +124,78 @@ class FilmBip(PluginBase):
137
124
  secici = HTMLHelper(istek.text)
138
125
 
139
126
  results = []
140
-
141
- for player in secici.select("div#tv-spoox2"):
142
- iframe = secici.select_attr("iframe", "src", player)
143
-
144
- if iframe:
145
- iframe = self.fix_url(iframe)
146
- data = await self.extract(iframe)
147
- if data:
148
- 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)
149
200
 
150
201
  return results