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.
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,8 @@
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, Episode, SeriesInfo, ExtractResult
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, Episode, SeriesInfo, ExtractResult, HTMLHelper
4
4
  from json import dumps, loads
5
- import re
5
+ import re, contextlib
6
6
 
7
7
  class RecTV(PluginBase):
8
8
  name = "RecTV"
@@ -65,56 +65,60 @@ class RecTV(PluginBase):
65
65
  for veri in tum_veri
66
66
  ]
67
67
 
68
- async def load_item(self, url: str) -> MovieInfo:
68
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
69
69
  self.httpx.headers.update({"user-agent": "okhttp/4.12.0"})
70
70
  veri = loads(url)
71
71
 
72
- match veri.get("type"):
73
- case "serie":
74
- dizi_istek = await self.httpx.get(f"{self.main_url}/api/season/by/serie/{veri.get('id')}/{self.sw_key}/")
75
- dizi_veri = dizi_istek.json()
76
-
77
- episodes = []
78
- for season in dizi_veri:
79
- for episode in season.get("episodes"):
80
- # Bölüm için gerekli bilgileri JSON olarak sakla
81
- ep_data = {
82
- "url" : self.fix_url(episode.get("sources")[0].get("url")),
83
- "title" : f"{veri.get('title')} | {season.get('title', '1. Sezon')} {episode.get('title', '1. Bölüm')}",
84
- "is_episode" : True
85
- }
86
-
87
- ep_model = Episode(
88
- season = int(re.search(r"(\d+)\.S", season.get("title")).group(1)) if re.search(r"(\d+)\.S", season.get("title")) else 1,
89
- episode = int(re.search(r"Bölüm (\d+)", episode.get("title")).group(1)) if re.search(r"Bölüm (\d+)", episode.get("title")) else 1,
90
- title = episode.get("title"),
91
- url = dumps(ep_data),
92
- )
93
-
94
- episodes.append(ep_model)
95
-
96
- return SeriesInfo(
97
- url = url,
98
- poster = self.fix_url(veri.get("image")),
99
- title = veri.get("title"),
100
- description = veri.get("description"),
101
- tags = [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
102
- rating = veri.get("imdb") or veri.get("rating"),
103
- year = veri.get("year"),
104
- actors = [],
105
- episodes = episodes
106
- )
107
- case _:
108
- return MovieInfo(
109
- url = url,
110
- poster = self.fix_url(veri.get("image")),
111
- title = veri.get("title"),
112
- description = veri.get("description"),
113
- tags = [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
114
- rating = veri.get("imdb") or veri.get("rating"),
115
- year = veri.get("year"),
116
- actors = []
117
- )
72
+ # Süreyi dakikaya çevir (Örn: "1h 59min")
73
+ duration_raw = veri.get("duration")
74
+ duration = None
75
+ if duration_raw:
76
+ with contextlib.suppress(Exception):
77
+ h = int(HTMLHelper(duration_raw).regex_first(r"(\d+)h") or 0)
78
+ m = int(HTMLHelper(duration_raw).regex_first(r"(\d+)min") or 0)
79
+ duration = h * 60 + m
80
+
81
+ common_info = {
82
+ "url" : url,
83
+ "poster" : self.fix_url(veri.get("image")),
84
+ "title" : veri.get("title"),
85
+ "description" : veri.get("description"),
86
+ "tags" : [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
87
+ "rating" : str(veri.get("imdb") or veri.get("rating") or ""),
88
+ "year" : str(veri.get("year") or ""),
89
+ "duration" : duration
90
+ }
91
+
92
+ if veri.get("type") == "serie":
93
+ dizi_istek = await self.httpx.get(f"{self.main_url}/api/season/by/serie/{veri.get('id')}/{self.sw_key}/")
94
+ dizi_veri = dizi_istek.json()
95
+
96
+ episodes = []
97
+ for season in dizi_veri:
98
+ s_title = season.get("title", "").strip()
99
+ s, _ = HTMLHelper.extract_season_episode(s_title)
100
+ for ep in season.get("episodes"):
101
+ e_title = ep.get("title", "").strip()
102
+ _, e = HTMLHelper.extract_season_episode(e_title)
103
+ for source in ep.get("sources"):
104
+ tag = ""
105
+ clean_s = s_title
106
+ if "dublaj" in s_title.lower():
107
+ tag = " (Dublaj)"; clean_s = re.sub(r"\s*dublaj\s*", "", s_title, flags=re.I).strip()
108
+ elif "altyaz" in s_title.lower():
109
+ tag = " (Altyazı)"; clean_s = re.sub(r"\s*altyaz[ıi]\s*", "", s_title, flags=re.I).strip()
110
+
111
+ ep_data = {"url": self.fix_url(source.get("url")), "title": f"{veri.get('title')} | {s_title} {e_title} - {source.get('title')}", "is_episode": True}
112
+ episodes.append(Episode(
113
+ season = s or 1,
114
+ episode = e or 1,
115
+ title = f"{clean_s} {e_title}{tag} - {source.get('title')}",
116
+ url = dumps(ep_data)
117
+ ))
118
+
119
+ return SeriesInfo(**common_info, episodes=episodes, actors=[])
120
+
121
+ return MovieInfo(**common_info, actors=[])
118
122
 
119
123
  async def load_links(self, url: str) -> list[ExtractResult]:
120
124
  try:
@@ -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, SeriesInfo, Episode, ExtractResult, MovieInfo
4
- from selectolax.parser import HTMLParser
5
- import re, base64, json
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult, MovieInfo, HTMLHelper
4
+ import base64, json
6
5
 
7
6
  class RoketDizi(PluginBase):
8
7
  name = "RoketDizi"
@@ -24,26 +23,20 @@ class RoketDizi(PluginBase):
24
23
 
25
24
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
26
25
  istek = await self.httpx.get(f"{url}?&page={page}")
27
- secici = HTMLParser(istek.text)
26
+ secici = HTMLHelper(istek.text)
28
27
 
29
28
  results = []
30
-
31
- # Use div.new-added-list to find the container, then get items
32
- for item in secici.css("div.new-added-list > span"):
33
- title_el = item.css_first("span.line-clamp-1")
34
- link_el = item.css_first("a")
35
- img_el = item.css_first("img")
36
-
37
- title = title_el.text(strip=True) if title_el else None
38
- href = link_el.attrs.get("href") if link_el else None
39
- poster = img_el.attrs.get("src") if img_el else None
29
+ for item in secici.select("div.new-added-list > span"):
30
+ title = secici.select_text("span.line-clamp-1", item)
31
+ href = secici.select_attr("a", "href", item)
32
+ poster = secici.select_attr("img", "src", item)
40
33
 
41
34
  if title and href:
42
35
  results.append(MainPageResult(
43
36
  category = category,
44
37
  title = self.clean_title(title),
45
38
  url = self.fix_url(href),
46
- poster = self.fix_url(poster) if poster else None
39
+ poster = self.fix_url(poster)
47
40
  ))
48
41
 
49
42
  return results
@@ -57,7 +50,7 @@ class RoketDizi(PluginBase):
57
50
  "Referer" : f"{self.main_url}/",
58
51
  }
59
52
  )
60
-
53
+
61
54
  try:
62
55
  veri = istek.json()
63
56
  encoded = veri.get("response", "")
@@ -81,7 +74,7 @@ class RoketDizi(PluginBase):
81
74
  results.append(SearchResult(
82
75
  title = self.clean_title(title.strip()),
83
76
  url = self.fix_url(f"{self.main_url}/{slug}"),
84
- poster = self.fix_url(poster) if poster else None
77
+ poster = self.fix_url(poster)
85
78
  ))
86
79
 
87
80
  return results
@@ -89,117 +82,95 @@ class RoketDizi(PluginBase):
89
82
  except Exception:
90
83
  return []
91
84
 
92
- async def load_item(self, url: str) -> SeriesInfo:
93
- # Note: Handling both Movie and Series logic in one, returning SeriesInfo generally or MovieInfo
85
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
94
86
  resp = await self.httpx.get(url)
95
- sel = HTMLParser(resp.text)
96
- html_text = resp.text
97
-
98
- title_el = sel.css_first("h1.text-white")
99
- title = title_el.text(strip=True) if title_el else None
100
-
101
- poster_el = sel.css_first("div.w-full.page-top img")
102
- poster = poster_el.attrs.get("src") if poster_el else None
103
-
104
- desc_el = sel.css_first("div.mt-2.text-sm")
105
- description = desc_el.text(strip=True) if desc_el else None
106
-
107
- # Tags - genre bilgileri (Detaylar bölümünde)
108
- tags = []
109
- genre_el = sel.css_first("h3.text-white.opacity-90")
110
- if genre_el:
111
- genre_text = genre_el.text(strip=True)
112
- if genre_text:
113
- tags = [t.strip() for t in genre_text.split(",")]
114
-
115
- # Rating
116
- rating_el = sel.css_first("span.text-white.text-sm.font-bold")
117
- rating = rating_el.text(strip=True) if rating_el else None
118
-
119
- # Year ve Actors - Detaylar (Details) bölümünden
120
- year = None
121
- actors = []
122
-
123
- # Detaylar bölümündeki tüm flex-col div'leri al
124
- detail_items = sel.css("div.flex.flex-col")
125
- for item in detail_items:
126
- label_el = item.css_first("span.text-base")
127
- value_el = item.css_first("span.text-sm.opacity-90")
128
-
129
- label = label_el.text(strip=True) if label_el else None
130
- value = value_el.text(strip=True) if value_el else None
131
-
132
- if label and value:
133
- # Yayın tarihi (yıl)
134
- if label == "Yayın tarihi":
135
- # "16 Ekim 2018" formatından yılı çıkar
136
- year_match = re.search(r'\d{4}', value)
137
- if year_match:
138
- year = year_match.group()
139
-
140
- # Yaratıcılar veya Oyuncular
141
- elif label in ["Yaratıcılar", "Oyuncular"]:
142
- if value:
143
- actors.append(value)
144
-
145
- # Check urls for episodes
146
- all_urls = re.findall(r'"url":"([^"]*)"', html_text)
147
- is_series = any("bolum-" in u for u in all_urls)
148
-
149
- episodes = []
150
- if is_series:
151
- # Dict kullanarak duplicate'leri önle ama sıralı tut
152
- episodes_dict = {}
153
- for u in all_urls:
154
- if "bolum" in u and u not in episodes_dict:
155
- season_match = re.search(r'/sezon-(\d+)', u)
156
- ep_match = re.search(r'/bolum-(\d+)', u)
157
-
158
- season = int(season_match.group(1)) if season_match else 1
159
- episode_num = int(ep_match.group(1)) if ep_match else 1
160
-
161
- # Key olarak (season, episode) tuple kullan
162
- key = (season, episode_num)
163
- episodes_dict[key] = Episode(
164
- season = season,
165
- episode = episode_num,
166
- title = f"{season}. Sezon {episode_num}. Bölüm",
167
- url = self.fix_url(u)
168
- )
169
-
170
- # Sıralı liste oluştur
171
- episodes = [episodes_dict[key] for key in sorted(episodes_dict.keys())]
172
-
173
- return SeriesInfo(
174
- title = title,
175
- url = url,
176
- poster = self.fix_url(poster) if poster else None,
177
- description = description,
178
- tags = tags,
179
- rating = rating,
180
- actors = actors,
181
- episodes = episodes,
182
- year = year
183
- )
87
+ sel = HTMLHelper(resp.text)
88
+
89
+ next_data_text = sel.select_text("script#__NEXT_DATA__")
90
+ if not next_data_text:
91
+ return SeriesInfo(url=url, title=sel.select_text("h1") or "Bilinmeyen")
92
+
93
+ try:
94
+ next_data = json.loads(next_data_text)
95
+ secure_data_raw = next_data["props"]["pageProps"]["secureData"]
96
+ secure_data = json.loads(base64.b64decode(secure_data_raw).decode('utf-8'))
97
+
98
+ content_item = secure_data.get("contentItem", {})
99
+ content = secure_data.get("content", {}).get("result", {})
100
+
101
+ title = content_item.get("original_title") or content_item.get("culture_title")
102
+ poster = content_item.get("poster_url") or content_item.get("face_url")
103
+ description = content_item.get("description")
104
+ rating = str(content_item.get("imdb_point") or "")
105
+ year = str(content_item.get("release_year") or "")
106
+ tags = content_item.get("categories", "").split(",")
107
+
108
+ actors = []
109
+ casts_data = content.get("getSerieCastsById") or content.get("getMovieCastsById")
110
+ if casts_data and casts_data.get("result"):
111
+ actors = [cast.get("name") for cast in casts_data["result"] if cast.get("name")]
112
+
113
+ episodes = []
114
+ if "Series" in str(content.get("FindedType")):
115
+ all_urls = HTMLHelper(resp.text).regex_all(r'"url":"([^"]*)"')
116
+ episodes_dict = {}
117
+ for u in all_urls:
118
+ if "bolum" in u and u not in episodes_dict:
119
+ s_match = HTMLHelper(u).regex_first(r'/sezon-(\d+)')
120
+ e_match = HTMLHelper(u).regex_first(r'/bolum-(\d+)')
121
+ s_val = int(s_match) if s_match else 1
122
+ e_val = int(e_match) if e_match else 1
123
+ episodes_dict[(s_val, e_val)] = Episode(
124
+ season = s_val,
125
+ episode = e_val,
126
+ title = f"{s_val}. Sezon {e_val}. Bölüm",
127
+ url = self.fix_url(u)
128
+ )
129
+ episodes = [episodes_dict[key] for key in sorted(episodes_dict.keys())]
130
+
131
+ return SeriesInfo(
132
+ url = url,
133
+ poster = self.fix_url(poster),
134
+ title = self.clean_title(title),
135
+ description = description,
136
+ tags = tags,
137
+ rating = rating,
138
+ year = year,
139
+ actors = actors,
140
+ episodes = episodes
141
+ )
142
+ else:
143
+ return MovieInfo(
144
+ url = url,
145
+ poster = self.fix_url(poster),
146
+ title = self.clean_title(title),
147
+ description = description,
148
+ tags = tags,
149
+ rating = rating,
150
+ year = year,
151
+ actors = actors
152
+ )
153
+
154
+ except Exception:
155
+ # Fallback to simple extraction if JSON parsing fails
156
+ return SeriesInfo(
157
+ url = url,
158
+ title = self.clean_title(sel.select_text("h1")) or "Bilinmeyen"
159
+ )
184
160
 
185
161
  async def load_links(self, url: str) -> list[ExtractResult]:
186
162
  resp = await self.httpx.get(url)
187
- sel = HTMLParser(resp.text)
188
-
189
- next_data_el = sel.css_first("script#__NEXT_DATA__")
190
- if not next_data_el:
191
- return []
163
+ sel = HTMLHelper(resp.text)
192
164
 
193
- next_data = next_data_el.text(strip=True)
165
+ next_data = sel.select_text("script#__NEXT_DATA__")
194
166
  if not next_data:
195
167
  return []
196
168
 
197
169
  try:
198
- data = json.loads(next_data)
199
- secure_data = data["props"]["pageProps"]["secureData"]
170
+ data = json.loads(next_data)
171
+ secure_data = data["props"]["pageProps"]["secureData"]
200
172
  decoded_json = json.loads(base64.b64decode(secure_data).decode('utf-8'))
201
173
 
202
- # secureData içindeki RelatedResults -> getEpisodeSources -> result dizisini al
203
174
  sources = decoded_json.get("RelatedResults", {}).get("getEpisodeSources", {}).get("result", [])
204
175
 
205
176
  seen_urls = set()
@@ -208,12 +179,10 @@ class RoketDizi(PluginBase):
208
179
  source_content = source.get("source_content", "")
209
180
 
210
181
  # iframe URL'ini source_content'ten çıkar
211
- iframe_match = re.search(r'<iframe[^>]*src=["\']([^"\']*)["\']', source_content)
212
- if not iframe_match:
182
+ iframe_url = HTMLHelper(source_content).regex_first(r'<iframe[^>]*src=["\']([^"\']*)["\']')
183
+ if not iframe_url:
213
184
  continue
214
185
 
215
- iframe_url = iframe_match.group(1)
216
-
217
186
  # Fix URL protocol
218
187
  if not iframe_url.startswith("http"):
219
188
  if iframe_url.startswith("//"):
@@ -222,8 +191,8 @@ class RoketDizi(PluginBase):
222
191
  iframe_url = "https://" + iframe_url
223
192
 
224
193
  iframe_url = self.fix_url(iframe_url)
225
-
226
- # Deduplicate
194
+
195
+ # Deduplicate
227
196
  if iframe_url in seen_urls:
228
197
  continue
229
198
  seen_urls.add(iframe_url)
@@ -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, SeriesInfo, Episode, ExtractResult
4
- from selectolax.parser import HTMLParser
5
- import re, base64, json, urllib.parse
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult, HTMLHelper
4
+ import base64, json, urllib.parse
6
5
 
7
6
  class SelcukFlix(PluginBase):
8
7
  name = "SelcukFlix"
@@ -34,17 +33,13 @@ class SelcukFlix(PluginBase):
34
33
  if "tum-bolumler" in url:
35
34
  try:
36
35
  resp = await self.httpx.get(url)
37
- sel = HTMLParser(resp.text)
36
+ sel = HTMLHelper(resp.text)
38
37
 
39
- for item in sel.css("div.col-span-3 a"):
40
- name_el = item.css_first("h2")
41
- ep_el = item.css_first("div.opacity-80")
42
- img_el = item.css_first("div.image img")
43
-
44
- name = name_el.text(strip=True) if name_el else None
45
- ep_info = ep_el.text(strip=True) if ep_el else None
46
- href = item.attrs.get("href")
47
- poster = img_el.attrs.get("src") if img_el else None
38
+ for item in sel.select("div.col-span-3 a"):
39
+ name = sel.select_text("h2", item)
40
+ ep_info = sel.select_text("div.opacity-80", item)
41
+ href = sel.select_attr("a", "href", item)
42
+ poster = sel.select_attr("div.image img", "src", item)
48
43
 
49
44
  if name and href:
50
45
  title = f"{name} - {ep_info}" if ep_info else name
@@ -57,7 +52,7 @@ class SelcukFlix(PluginBase):
57
52
  category = category,
58
53
  title = title,
59
54
  url = final_url,
60
- poster = self.fix_url(poster) if poster else None
55
+ poster = self.fix_url(poster)
61
56
  ))
62
57
  except Exception:
63
58
  pass
@@ -188,91 +183,88 @@ class SelcukFlix(PluginBase):
188
183
 
189
184
  async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
190
185
  resp = await self.httpx.get(url)
191
- sel = HTMLParser(resp.text)
192
-
193
- next_data_el = sel.css_first("script#__NEXT_DATA__")
194
- if not next_data_el:
195
- return None
196
-
197
- next_data = next_data_el.text(strip=True)
198
- if not next_data:
199
- return None
186
+ sel = HTMLHelper(resp.text)
187
+
188
+ next_data_text = sel.select_text("script#__NEXT_DATA__")
189
+ if not next_data_text:
190
+ return SeriesInfo(url=url, title=self.clean_title(sel.select_text("h1")) or "Bilinmeyen")
200
191
 
201
- data = json.loads(next_data)
202
- secure_data = data["props"]["pageProps"]["secureData"]
203
- raw_data = base64.b64decode(secure_data.replace('"', ''))
204
192
  try:
205
- decoded_str = raw_data.decode('utf-8')
206
- except UnicodeDecodeError:
207
- decoded_str = raw_data.decode('iso-8859-1')
208
-
209
- content_details = json.loads(decoded_str)
210
- item = content_details.get("contentItem", {})
211
-
212
- title = item.get("original_title") or item.get("originalTitle") or ""
213
- poster = self.clean_image_url(item.get("poster_url") or item.get("posterUrl"))
214
- description = item.get("description") or item.get("used_description")
215
- rating = str(item.get("imdb_point") or item.get("imdbPoint", ""))
216
- year = item.get("release_year") or item.get("releaseYear")
217
- duration = item.get("total_minutes") or item.get("totalMinutes")
218
-
219
- series_data = content_details.get("relatedData", {}).get("seriesData")
220
- if not series_data and "RelatedResults" in content_details:
221
- series_data = content_details["RelatedResults"].get("getSerieSeasonAndEpisodes", {}).get("result")
222
- if series_data and isinstance(series_data, list):
223
- pass
224
-
225
- # Dizi mi film mi kontrol et (Kotlin referansı)
226
- if series_data:
227
- episodes = []
228
- seasons_list = []
229
- if isinstance(series_data, dict):
230
- seasons_list = series_data.get("seasons", [])
231
- elif isinstance(series_data, list):
232
- seasons_list = series_data
233
-
234
- for season in seasons_list:
235
- if not isinstance(season, dict): continue
236
- s_no = season.get("season_no") or season.get("seasonNo")
237
- ep_list = season.get("episodes", [])
238
- for ep in ep_list:
239
- episodes.append(Episode(
240
- season = s_no,
241
- episode = ep.get("episode_no") or ep.get("episodeNo"),
242
- title = ep.get("ep_text") or ep.get("epText"),
243
- url = self.fix_url(ep.get("used_slug") or ep.get("usedSlug"))
244
- ))
193
+ next_data = json.loads(next_data_text)
194
+ secure_data_raw = next_data["props"]["pageProps"].get("secureData")
195
+ if not secure_data_raw:
196
+ return SeriesInfo(url=url, title=self.clean_title(sel.select_text("h1")) or "Bilinmeyen")
245
197
 
246
- return SeriesInfo(
247
- title = title,
248
- url = url,
249
- poster = poster,
250
- description = description,
251
- rating = rating,
252
- year = year,
253
- episodes = episodes
254
- )
255
- else:
256
- # Film ise MovieInfo döndür
257
- return MovieInfo(
258
- title = title,
259
- url = url,
260
- poster = poster,
261
- description = description,
262
- rating = rating,
263
- year = year,
264
- duration = duration
265
- )
198
+ # Clean possible quotes from string before decoding
199
+ if isinstance(secure_data_raw, str):
200
+ secure_data_raw = secure_data_raw.strip('"')
201
+
202
+ content_details = json.loads(base64.b64decode(secure_data_raw).decode('utf-8'))
203
+ if isinstance(content_details, str): content_details = json.loads(content_details)
204
+
205
+ item = content_details.get("contentItem", {})
206
+ related_results = content_details.get("RelatedResults", {})
207
+
208
+ title = self.clean_title(item.get("original_title") or item.get("culture_title") or item.get("originalTitle") or "")
209
+ poster = self.clean_image_url(item.get("poster_url") or item.get("posterUrl") or item.get("face_url"))
210
+ description = item.get("description") or item.get("used_description")
211
+ rating = str(item.get("imdb_point") or item.get("imdbPoint") or "")
212
+ year = str(item.get("release_year") or item.get("releaseYear") or "")
213
+ duration = item.get("total_minutes") or item.get("totalMinutes")
214
+
215
+ tags = []
216
+ tags_raw = item.get("category_names") or item.get("categoryNames") or item.get("categories")
217
+ if isinstance(tags_raw, str):
218
+ tags = [t.strip() for t in tags_raw.split(",") if t.strip()]
219
+ elif isinstance(tags_raw, list):
220
+ tags = [c.get("title") if isinstance(c, dict) else str(c) for c in tags_raw]
221
+
222
+ actors = []
223
+ casts_data = related_results.get("getSerieCastsById") or related_results.get("getMovieCastsById")
224
+ if casts_data and isinstance(casts_data, dict) and casts_data.get("result"):
225
+ actors = [cast.get("name") for cast in casts_data["result"] if cast.get("name")]
226
+
227
+ common_info = {
228
+ "url" : url,
229
+ "poster" : poster,
230
+ "title" : title,
231
+ "description" : description,
232
+ "tags" : tags,
233
+ "rating" : rating,
234
+ "year" : year,
235
+ "actors" : actors,
236
+ "duration" : duration
237
+ }
238
+
239
+ series_data = related_results.get("getSerieSeasonAndEpisodes")
240
+ if series_data and isinstance(series_data, dict) and series_data.get("result"):
241
+ episodes = []
242
+ for season in series_data["result"]:
243
+ s_no = season.get("season_no") or season.get("seasonNo") or 1
244
+ for ep in season.get("episodes", []):
245
+ ep_slug = ep.get("used_slug") or ep.get("usedSlug")
246
+ if ep_slug:
247
+ episodes.append(Episode(
248
+ season = s_no,
249
+ episode = ep.get("episode_no") or ep.get("episodeNo") or 1,
250
+ title = ep.get("ep_text") or ep.get("epText") or "",
251
+ url = self.fix_url(ep_slug)
252
+ ))
253
+ return SeriesInfo(**common_info, episodes=episodes)
254
+
255
+ return MovieInfo(**common_info)
256
+
257
+ except Exception:
258
+ return SeriesInfo(url=url, title=self.clean_title(sel.select_text("h1")) or "Bilinmeyen")
259
+
260
+ except Exception:
261
+ return SeriesInfo(url=url, title=self.clean_title(sel.select_text("h1")) or "Bilinmeyen")
266
262
 
267
263
  async def load_links(self, url: str) -> list[ExtractResult]:
268
264
  resp = await self.httpx.get(url)
269
- sel = HTMLParser(resp.text)
265
+ sel = HTMLHelper(resp.text)
270
266
 
271
- next_data_el = sel.css_first("script#__NEXT_DATA__")
272
- if not next_data_el:
273
- return []
274
-
275
- next_data = next_data_el.text(strip=True)
267
+ next_data = sel.select_text("script#__NEXT_DATA__")
276
268
  if not next_data:
277
269
  return []
278
270
 
@@ -312,9 +304,8 @@ class SelcukFlix(PluginBase):
312
304
  source_content = res[0].get("source_content") or res[0].get("sourceContent")
313
305
 
314
306
  if source_content:
315
- iframe_sel = HTMLParser(source_content)
316
- iframe_el = iframe_sel.css_first("iframe")
317
- iframe_src = iframe_el.attrs.get("src") if iframe_el else None
307
+ iframe_sel = HTMLHelper(source_content)
308
+ iframe_src = iframe_sel.select_attr("iframe", "src")
318
309
  if iframe_src:
319
310
  iframe_src = self.fix_url(iframe_src)
320
311
  # Hotlinger domain değişimi (Kotlin referansı)