KekikStream 2.4.2__py3-none-any.whl → 2.4.4__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 (72) hide show
  1. KekikStream/Core/Extractor/ExtractorBase.py +3 -2
  2. KekikStream/Core/HTMLHelper.py +134 -40
  3. KekikStream/Core/Plugin/PluginBase.py +3 -2
  4. KekikStream/Extractors/CloseLoad.py +30 -54
  5. KekikStream/Extractors/ContentX.py +27 -72
  6. KekikStream/Extractors/DonilasPlay.py +33 -77
  7. KekikStream/Extractors/DzenRu.py +10 -24
  8. KekikStream/Extractors/ExPlay.py +20 -38
  9. KekikStream/Extractors/Filemoon.py +19 -45
  10. KekikStream/Extractors/HDMomPlayer.py +24 -56
  11. KekikStream/Extractors/HDPlayerSystem.py +13 -31
  12. KekikStream/Extractors/HotStream.py +14 -32
  13. KekikStream/Extractors/JFVid.py +3 -24
  14. KekikStream/Extractors/JetTv.py +21 -34
  15. KekikStream/Extractors/MailRu.py +11 -29
  16. KekikStream/Extractors/MixPlayHD.py +15 -28
  17. KekikStream/Extractors/MixTiger.py +17 -40
  18. KekikStream/Extractors/MolyStream.py +17 -21
  19. KekikStream/Extractors/Odnoklassniki.py +28 -104
  20. KekikStream/Extractors/PeaceMakerst.py +18 -45
  21. KekikStream/Extractors/PixelDrain.py +8 -16
  22. KekikStream/Extractors/PlayerFilmIzle.py +22 -41
  23. KekikStream/Extractors/RapidVid.py +21 -35
  24. KekikStream/Extractors/SetPlay.py +18 -43
  25. KekikStream/Extractors/SibNet.py +7 -17
  26. KekikStream/Extractors/Sobreatsesuyp.py +23 -45
  27. KekikStream/Extractors/TRsTX.py +23 -53
  28. KekikStream/Extractors/TurboImgz.py +7 -14
  29. KekikStream/Extractors/VCTPlay.py +10 -28
  30. KekikStream/Extractors/VidHide.py +10 -31
  31. KekikStream/Extractors/VidMoly.py +65 -99
  32. KekikStream/Extractors/VidMoxy.py +16 -27
  33. KekikStream/Extractors/VidPapi.py +24 -54
  34. KekikStream/Extractors/VideoSeyred.py +19 -40
  35. KekikStream/Extractors/Videostr.py +42 -99
  36. KekikStream/Extractors/Vidoza.py +8 -15
  37. KekikStream/Extractors/YildizKisaFilm.py +13 -31
  38. KekikStream/Plugins/BelgeselX.py +63 -69
  39. KekikStream/Plugins/DiziBox.py +16 -36
  40. KekikStream/Plugins/DiziMom.py +37 -129
  41. KekikStream/Plugins/DiziPal.py +71 -164
  42. KekikStream/Plugins/DiziYou.py +44 -152
  43. KekikStream/Plugins/Dizilla.py +18 -44
  44. KekikStream/Plugins/FilmBip.py +10 -24
  45. KekikStream/Plugins/FilmEkseni.py +12 -32
  46. KekikStream/Plugins/FilmMakinesi.py +24 -77
  47. KekikStream/Plugins/FilmModu.py +11 -18
  48. KekikStream/Plugins/Filmatek.py +13 -39
  49. KekikStream/Plugins/Full4kizle.py +33 -133
  50. KekikStream/Plugins/FullHDFilm.py +23 -93
  51. KekikStream/Plugins/FullHDFilmizlesene.py +10 -29
  52. KekikStream/Plugins/HDFilmCehennemi.py +27 -66
  53. KekikStream/Plugins/JetFilmizle.py +19 -20
  54. KekikStream/Plugins/KultFilmler.py +16 -50
  55. KekikStream/Plugins/RecTV.py +47 -85
  56. KekikStream/Plugins/SelcukFlix.py +29 -47
  57. KekikStream/Plugins/SetFilmIzle.py +28 -84
  58. KekikStream/Plugins/SezonlukDizi.py +27 -59
  59. KekikStream/Plugins/Sinefy.py +37 -100
  60. KekikStream/Plugins/SinemaCX.py +12 -18
  61. KekikStream/Plugins/Sinezy.py +11 -12
  62. KekikStream/Plugins/SuperFilmGeldi.py +8 -13
  63. KekikStream/Plugins/UgurFilm.py +14 -14
  64. KekikStream/Plugins/Watch32.py +42 -74
  65. KekikStream/Plugins/YabanciDizi.py +33 -87
  66. {kekikstream-2.4.2.dist-info → kekikstream-2.4.4.dist-info}/METADATA +1 -1
  67. kekikstream-2.4.4.dist-info/RECORD +93 -0
  68. kekikstream-2.4.2.dist-info/RECORD +0 -93
  69. {kekikstream-2.4.2.dist-info → kekikstream-2.4.4.dist-info}/WHEEL +0 -0
  70. {kekikstream-2.4.2.dist-info → kekikstream-2.4.4.dist-info}/entry_points.txt +0 -0
  71. {kekikstream-2.4.2.dist-info → kekikstream-2.4.4.dist-info}/licenses/LICENSE +0 -0
  72. {kekikstream-2.4.2.dist-info → kekikstream-2.4.4.dist-info}/top_level.txt +0 -0
@@ -65,94 +65,56 @@ 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
- season_title = season.get("title", "").strip()
80
- for episode in season.get("episodes"):
81
- ep_label = episode.get("title", "").strip()
82
- for source in episode.get("sources"):
83
- # Bölüm için gerekli bilgileri JSON olarak sakla
84
- ep_data = {
85
- "url" : self.fix_url(source.get("url")),
86
- "title" : f"{veri.get('title')} | {season_title} {ep_label} - {source.get('title')}",
87
- "is_episode" : True
88
- }
89
-
90
- # Extract season/episode numbers using helper
91
- s1, _ = HTMLHelper.extract_season_episode(season_title or "")
92
- _, e2 = HTMLHelper.extract_season_episode(ep_label or "")
93
-
94
- tag = ""
95
- clean_season = season_title
96
- if "dublaj" in season_title.lower():
97
- tag = " (Dublaj)"
98
- clean_season = re.sub(r"\s*dublaj\s*", "", season_title, flags=re.I).strip()
99
- elif any(x in season_title.lower() for x in ["altyazı", "altyazi"]):
100
- tag = " (Altyazı)"
101
- clean_season = re.sub(r"\s*altyaz[ıi]\s*", "", season_title, flags=re.I).strip()
102
-
103
- ep_model = Episode(
104
- season = s1 or 1,
105
- episode = e2 or 1,
106
- title = f"{clean_season} {ep_label}{tag} - {source.get('title')}",
107
- url = dumps(ep_data),
108
- )
109
-
110
- episodes.append(ep_model)
111
-
112
- # Süreyi dakikaya çevir (Örn: "1h 59min")
113
- duration_raw = veri.get("duration")
114
- duration = None
115
- if duration_raw:
116
- try:
117
- h = int(HTMLHelper(duration_raw).regex_first(r"(\d+)h") or 0)
118
- m = int(HTMLHelper(duration_raw).regex_first(r"(\d+)min") or 0)
119
- duration = h * 60 + m
120
- except: pass
121
-
122
- return SeriesInfo(
123
- url = url,
124
- poster = self.fix_url(veri.get("image")),
125
- title = veri.get("title"),
126
- description = veri.get("description"),
127
- tags = [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
128
- rating = str(veri.get("imdb") or veri.get("rating") or ""),
129
- year = str(veri.get("year") or ""),
130
- actors = [],
131
- duration = duration,
132
- episodes = episodes
133
- )
134
- case _:
135
- # Süreyi dakikaya çevir
136
- duration_raw = veri.get("duration")
137
- duration = None
138
- if duration_raw:
139
- try:
140
- h = int(HTMLHelper(duration_raw).regex_first(r"(\d+)h") or 0)
141
- m = int(HTMLHelper(duration_raw).regex_first(r"(\d+)min") or 0)
142
- duration = h * 60 + m
143
- except: pass
144
-
145
- return MovieInfo(
146
- url = url,
147
- poster = self.fix_url(veri.get("image")),
148
- title = veri.get("title"),
149
- description = veri.get("description"),
150
- tags = [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
151
- rating = str(veri.get("imdb") or veri.get("rating") or ""),
152
- year = str(veri.get("year") or ""),
153
- actors = [],
154
- duration = duration
155
- )
72
+ # Süreyi dakikaya çevir (Örn: "1h 59min")
73
+ duration_raw = veri.get("duration")
74
+ duration = None
75
+ if duration_raw:
76
+ try:
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
+ except: pass
81
+
82
+ common_info = {
83
+ "url" : url,
84
+ "poster" : self.fix_url(veri.get("image")),
85
+ "title" : veri.get("title"),
86
+ "description" : veri.get("description"),
87
+ "tags" : [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
88
+ "rating" : str(veri.get("imdb") or veri.get("rating") or ""),
89
+ "year" : str(veri.get("year") or ""),
90
+ "duration" : duration
91
+ }
92
+
93
+ if veri.get("type") == "serie":
94
+ dizi_istek = await self.httpx.get(f"{self.main_url}/api/season/by/serie/{veri.get('id')}/{self.sw_key}/")
95
+ dizi_veri = dizi_istek.json()
96
+
97
+ episodes = []
98
+ for season in dizi_veri:
99
+ s_title = season.get("title", "").strip()
100
+ s, _ = HTMLHelper.extract_season_episode(s_title)
101
+ for ep in season.get("episodes"):
102
+ e_title = ep.get("title", "").strip()
103
+ _, e = HTMLHelper.extract_season_episode(e_title)
104
+ for source in ep.get("sources"):
105
+ tag = ""
106
+ clean_s = s_title
107
+ if "dublaj" in s_title.lower():
108
+ tag = " (Dublaj)"; clean_s = re.sub(r"\s*dublaj\s*", "", s_title, flags=re.I).strip()
109
+ elif "altyaz" in s_title.lower():
110
+ tag = " (Altyazı)"; clean_s = re.sub(r"\s*altyaz[ıi]\s*", "", s_title, flags=re.I).strip()
111
+
112
+ ep_data = {"url": self.fix_url(source.get("url")), "title": f"{veri.get('title')} | {s_title} {e_title} - {source.get('title')}", "is_episode": True}
113
+ episodes.append(Episode(season=s or 1, episode=e or 1, title=f"{clean_s} {e_title}{tag} - {source.get('title')}", url=dumps(ep_data)))
114
+
115
+ return SeriesInfo(**common_info, episodes=episodes, actors=[])
116
+
117
+ return MovieInfo(**common_info, actors=[])
156
118
 
157
119
  async def load_links(self, url: str) -> list[ExtractResult]:
158
120
  try:
@@ -187,54 +187,55 @@ class SelcukFlix(PluginBase):
187
187
 
188
188
  next_data_text = sel.select_text("script#__NEXT_DATA__")
189
189
  if not next_data_text:
190
- return SeriesInfo(url=url, title=sel.select_text("h1") or "Bilinmeyen")
190
+ return SeriesInfo(url=url, title=self.clean_title(sel.select_text("h1")) or "Bilinmeyen")
191
191
 
192
192
  try:
193
193
  next_data = json.loads(next_data_text)
194
194
  secure_data_raw = next_data["props"]["pageProps"].get("secureData")
195
195
  if not secure_data_raw:
196
- return SeriesInfo(url=url, title=sel.select_text("h1") or "Bilinmeyen")
196
+ return SeriesInfo(url=url, title=self.clean_title(sel.select_text("h1")) or "Bilinmeyen")
197
197
 
198
198
  # Clean possible quotes from string before decoding
199
199
  if isinstance(secure_data_raw, str):
200
200
  secure_data_raw = secure_data_raw.strip('"')
201
201
 
202
- decoded_str = base64.b64decode(secure_data_raw).decode('utf-8')
203
- content_details = json.loads(decoded_str)
204
-
205
- # Sometimes content_details might be a string (double encoded)
206
- if isinstance(content_details, str):
207
- content_details = json.loads(content_details)
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)
208
204
 
209
- print(f"DEBUG: type(content_details)={type(content_details)}")
210
205
  item = content_details.get("contentItem", {})
211
- print(f"DEBUG: type(item)={type(item)}")
212
206
  related_results = content_details.get("RelatedResults", {})
213
207
 
214
- title = item.get("original_title") or item.get("culture_title") or item.get("originalTitle") or ""
215
- poster = self.clean_image_url(item.get("poster_url") or item.get("posterUrl") or item.get("face_url"))
216
- description = item.get("description") or item.get("used_description")
217
- rating = str(item.get("imdb_point") or item.get("imdbPoint") or "")
218
- year = str(item.get("release_year") or item.get("releaseYear") or "")
219
- duration = item.get("total_minutes") or item.get("totalMinutes")
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")
220
214
 
221
215
  tags = []
222
216
  tags_raw = item.get("category_names") or item.get("categoryNames") or item.get("categories")
223
217
  if isinstance(tags_raw, str):
224
- tags = [t.strip() for t in tags_raw.split(",")]
218
+ tags = [t.strip() for t in tags_raw.split(",") if t.strip()]
225
219
  elif isinstance(tags_raw, list):
226
220
  tags = [c.get("title") if isinstance(c, dict) else str(c) for c in tags_raw]
227
221
 
228
222
  actors = []
229
- actors_raw = item.get("actor_names") or item.get("actorNames")
230
- if isinstance(actors_raw, str):
231
- actors = [a.strip() for a in actors_raw.split(",")]
232
-
233
- # Casts from RelatedResults
234
223
  casts_data = related_results.get("getSerieCastsById") or related_results.get("getMovieCastsById")
235
224
  if casts_data and isinstance(casts_data, dict) and casts_data.get("result"):
236
225
  actors = [cast.get("name") for cast in casts_data["result"] if cast.get("name")]
237
226
 
227
+ common_info = {
228
+ "url" : url,
229
+ "poster" : poster,
230
+ "title" : title or "Bilinmiyor",
231
+ "description" : description,
232
+ "tags" : tags,
233
+ "rating" : rating,
234
+ "year" : year,
235
+ "actors" : actors,
236
+ "duration" : duration
237
+ }
238
+
238
239
  series_data = related_results.get("getSerieSeasonAndEpisodes")
239
240
  if series_data and isinstance(series_data, dict) and series_data.get("result"):
240
241
  episodes = []
@@ -249,31 +250,12 @@ class SelcukFlix(PluginBase):
249
250
  title = ep.get("ep_text") or ep.get("epText") or "",
250
251
  url = self.fix_url(ep_slug)
251
252
  ))
252
-
253
- return SeriesInfo(
254
- url = url,
255
- poster = poster,
256
- title = self.clean_title(title),
257
- description = description,
258
- tags = tags,
259
- rating = rating,
260
- year = year,
261
- actors = actors,
262
- duration = duration,
263
- episodes = episodes
264
- )
265
- else:
266
- return MovieInfo(
267
- url = url,
268
- poster = poster,
269
- title = self.clean_title(title),
270
- description = description,
271
- tags = tags,
272
- rating = rating,
273
- year = year,
274
- actors = actors,
275
- duration = duration
276
- )
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")
277
259
 
278
260
  except Exception:
279
261
  return SeriesInfo(url=url, title=self.clean_title(sel.select_text("h1")) or "Bilinmeyen")
@@ -125,95 +125,39 @@ class SetFilmIzle(PluginBase):
125
125
  async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
126
126
  istek = self.cloudscraper.get(url)
127
127
  secici = HTMLHelper(istek.text)
128
- html_text = istek.text
129
-
130
- raw_title = secici.select_text("h1") or secici.select_text(".titles h1") or secici.select_attr("meta[property='og:title']", "content") or ""
131
- title = HTMLHelper(raw_title).regex_replace(r"(?i)\s*izle.*$", "", flags=0).strip()
132
-
133
- poster = secici.select_attr("div.poster img", "src")
134
128
 
129
+ title = self.clean_title(secici.select_text("h1") or secici.select_text(".titles h1") or secici.select_attr("meta[property='og:title']", "content"))
130
+ poster = secici.select_poster("div.poster img")
135
131
  description = secici.select_text("div.wp-content p")
132
+ rating = secici.select_text("b#repimdb strong") or secici.regex_first(r"([\d.]+)", secici.select_text("div.imdb"))
133
+ year = secici.extract_year("div.extra span.valor")
134
+ tags = secici.select_texts("div.sgeneros a")
135
+ duration = int(secici.regex_first(r"(\d+)", secici.select_text("span.runtime")) or 0)
136
+ actors = secici.select_texts("span.valor a[href*='/oyuncu/']")
137
+
138
+ common_info = {
139
+ "url" : url,
140
+ "poster" : self.fix_url(poster) if poster else None,
141
+ "title" : title or "Bilinmiyor",
142
+ "description" : description,
143
+ "tags" : tags,
144
+ "rating" : rating,
145
+ "year" : str(year) if year else None,
146
+ "duration" : duration,
147
+ "actors" : actors
148
+ }
136
149
 
137
- rating = secici.select_text("b#repimdb strong") or secici.regex_first(r'repimdb"><strong>\s*([^<]+)</strong>', html_text)
138
-
139
- # Yıl için info bölümünden veya regex ile yakala
140
- year = secici.regex_first(r'(\d{4})', secici.select_text("div.extra span.valor") or secici.select_text("span.valor") or "")
141
- if not year:
142
- year = secici.regex_first(r'<span>(\d{4})</span>', html_text) or secici.regex_first(r'(\d{4})', html_text)
143
-
144
- tags = [a.text(strip=True) for a in secici.select("div.sgeneros a") if a.text(strip=True)]
145
-
146
- duration_text = secici.select_text("span.runtime")
147
- duration = int(secici.regex_first(r"\d+", duration_text)) if duration_text and secici.regex_first(r"\d+", duration_text) else None
148
-
149
- actors = [a.text(strip=True) for a in secici.select("span.valor a") if "/oyuncu/" in (a.attrs.get("href") or "")]
150
- if not actors:
151
- actors = secici.regex_all(r'href="[^"]*/oyuncu/[^"]*">([^<]+)</a>')
152
-
153
- trailer = None
154
- if trailer_id := secici.regex_first(r'embed/([^?]*)\?rel', html_text):
155
- trailer = f"https://www.youtube.com/embed/{trailer_id}"
156
-
157
- # Dizi mi film mi kontrol et
158
- is_series = "/dizi/" in url
159
-
160
- if is_series:
161
- year_elem = secici.select_text("a[href*='/yil/']")
162
- if year_elem:
163
- year = secici.regex_first(r"\d{4}", year_elem) or year
164
-
165
- # Duration from info section
166
- for span in secici.select("div#info span"):
167
- span_text = span.text(strip=True) if span.text() else ""
168
- if "Dakika" in span_text:
169
- duration = secici.regex_first(r"\d+", span_text) and int(secici.regex_first(r"\d+", span_text))
170
- break
171
-
150
+ if "/dizi/" in url:
172
151
  episodes = []
173
152
  for ep_item in secici.select("div#episodes ul.episodios li"):
174
- ep_href = secici.select_attr("h4.episodiotitle a", "href", ep_item)
175
- ep_name = secici.select_text("h4.episodiotitle a", ep_item)
176
-
177
- if not ep_href or not ep_name:
178
- continue
179
-
180
- ep_detail = ep_name
181
- ep_season = secici.regex_first(r"(\d+)\.\s*Sezon", ep_detail) or 1
182
- ep_episode = secici.regex_first(r"Sezon\s+(\d+)\.\s*Bölüm", ep_detail)
183
-
184
- ep_season = int(ep_season) if isinstance(ep_season, str) and ep_season.isdigit() else 1
185
- ep_episode = int(ep_episode) if isinstance(ep_episode, str) and ep_episode.isdigit() else None
186
-
187
- episodes.append(Episode(
188
- season = ep_season,
189
- episode = ep_episode,
190
- title = ep_name,
191
- url = self.fix_url(ep_href)
192
- ))
193
- return SeriesInfo(
194
- url = url,
195
- poster = self.fix_url(poster) if poster else None,
196
- title = title,
197
- description = description,
198
- tags = tags,
199
- rating = rating,
200
- year = year,
201
- duration = duration,
202
- actors = actors,
203
- episodes = episodes
204
- )
205
-
206
- return MovieInfo(
207
- url = url,
208
- poster = self.fix_url(poster) if poster else None,
209
- title = title,
210
- description = description,
211
- tags = tags,
212
- rating = rating,
213
- year = year,
214
- duration = duration,
215
- actors = actors
216
- )
153
+ href = secici.select_attr("h4.episodiotitle a", "href", ep_item)
154
+ name = secici.select_direct_text("h4.episodiotitle a", ep_item)
155
+ if href and name:
156
+ s, e = secici.extract_season_episode(name)
157
+ episodes.append(Episode(season=s or 1, episode=e or 1, title=name, url=self.fix_url(href)))
158
+ return SeriesInfo(**common_info, episodes=episodes)
159
+
160
+ return MovieInfo(**common_info)
217
161
 
218
162
  async def load_links(self, url: str) -> list[ExtractResult]:
219
163
  istek = await self.httpx.get(url)
@@ -70,7 +70,7 @@ class SezonlukDizi(PluginBase):
70
70
  return results
71
71
 
72
72
  async def search(self, query: str) -> list[SearchResult]:
73
- istek = await self.httpx.get(f"{self.main_url}/diziler.asp?adi={query}")
73
+ istek = await self.httpx.get(f"{self.main_url}/diziler.asp?q={query}")
74
74
  secici = HTMLHelper(istek.text)
75
75
 
76
76
  results = []
@@ -92,63 +92,31 @@ class SezonlukDizi(PluginBase):
92
92
  istek = await self.httpx.get(url)
93
93
  secici = HTMLHelper(istek.text)
94
94
 
95
- title = secici.select_text("div.header") or ""
96
-
97
- poster = secici.select_attr("div.image img", "data-src") or ""
98
-
99
- # year: re_first yerine re.search
100
- year_text = secici.select_text("div.extra span") or ""
101
- year = secici.regex_first(r"(\d{4})", year_text)
102
-
103
- # xpath normalized-space yerine doğrudan ID ile element bulup text al
104
- description = secici.select_text("span#tartismayorum-konu") or ""
105
-
106
- tags = [a.text(strip=True) for a in secici.select("div.labels a[href*='tur']") if a.text(strip=True)]
107
-
108
- # rating: regex ile çıkar
109
- rating_text = secici.select_text("div.dizipuani a div") or ""
110
- rating = secici.regex_first(r"[\d.,]+", rating_text)
111
-
112
- actors = []
113
-
114
- actors_istek = await self.httpx.get(f"{self.main_url}/oyuncular/{url.split('/')[-1]}")
115
- actors_secici = HTMLHelper(actors_istek.text)
116
- for actor in actors_secici.select("div.doubling div.ui"):
117
- header_text = actors_secici.select_text("div.header", actor)
118
- if header_text:
119
- actors.append(header_text)
120
-
121
- episodes_istek = await self.httpx.get(f"{self.main_url}/bolumler/{url.split('/')[-1]}")
122
- episodes_secici = HTMLHelper(episodes_istek.text)
123
- episodes = []
124
-
125
- for sezon in episodes_secici.select("table.unstackable"):
126
- for bolum in episodes_secici.select("tbody tr", sezon):
127
- # td:nth-of-type selectolax'ta desteklenmiyor, alternatif yol: tüm td'leri alıp indexle
128
- tds = episodes_secici.select("td", bolum)
129
- if len(tds) < 4:
130
- continue
131
-
132
- # 4. td'den isim ve href
133
- ep_name = episodes_secici.select_text("a", tds[3])
134
- ep_href = episodes_secici.select_attr("a", "href", tds[3])
135
-
136
- # 3. td'den episode (re_first yerine regex)
137
- ep_episode_text = episodes_secici.select_text("a", tds[2]) or ""
138
- ep_episode = episodes_secici.regex_first(r"(\d+)", ep_episode_text)
139
-
140
- # 2. td'den season (re_first yerine regex)
141
- ep_season_text = tds[1].text(strip=True) if tds[1] else ""
142
- ep_season = secici.regex_first(r"(\d+)", ep_season_text)
143
-
144
- if ep_name and ep_href:
145
- episode = Episode(
146
- season = ep_season,
147
- episode = ep_episode,
148
- title = ep_name,
149
- url = self.fix_url(ep_href),
150
- )
151
- episodes.append(episode)
95
+ title = secici.select_text("div.header") or ""
96
+ poster = secici.select_poster("div.image img")
97
+ year = secici.extract_year("div.extra span")
98
+ description = secici.select_text("span#tartismayorum-konu")
99
+ tags = secici.select_texts("div.labels a[href*='tur']")
100
+ rating = secici.regex_first(r"[\d.,]+", secici.select_text("div.dizipuani a div"))
101
+
102
+ # Actors extraction
103
+ id_slug = url.split('/')[-1]
104
+ a_resp = await self.httpx.get(f"{self.main_url}/oyuncular/{id_slug}")
105
+ a_sel = HTMLHelper(a_resp.text)
106
+ actors = a_sel.select_texts("div.doubling div.ui div.header")
107
+
108
+ # Episodes extraction
109
+ e_resp = await self.httpx.get(f"{self.main_url}/bolumler/{id_slug}")
110
+ e_sel = HTMLHelper(e_resp.text)
111
+ episodes = []
112
+ for row in e_sel.select("table.unstackable tbody tr"):
113
+ tds = e_sel.select("td", row)
114
+ if len(tds) >= 4:
115
+ name = e_sel.select_text("a", tds[3])
116
+ href = e_sel.select_attr("a", "href", tds[3])
117
+ if name and href:
118
+ s, e = e_sel.extract_season_episode(f"{tds[1].text(strip=True)} {tds[2].text(strip=True)}")
119
+ episodes.append(Episode(season=s or 1, episode=e or 1, title=name, url=self.fix_url(href)))
152
120
 
153
121
  return SeriesInfo(
154
122
  url = url,
@@ -157,7 +125,7 @@ class SezonlukDizi(PluginBase):
157
125
  description = description,
158
126
  tags = tags,
159
127
  rating = rating,
160
- year = year,
128
+ year = str(year) if year else None,
161
129
  episodes = episodes,
162
130
  actors = actors
163
131
  )
@@ -135,114 +135,51 @@ class Sinefy(PluginBase):
135
135
  pass
136
136
  return []
137
137
 
138
- async def load_item(self, url: str) -> SeriesInfo:
138
+ async def load_item(self, url: str) -> SeriesInfo | MovieInfo:
139
139
  resp = await self.httpx.get(url)
140
140
  sel = HTMLHelper(resp.text)
141
141
 
142
- title = sel.select_text("h1")
143
-
144
- poster_info = sel.select_attr("div.ui.items img", "data-srcset")
145
- poster = None
146
- if poster_info:
147
- parts = str(poster_info).split(",")
148
- for p in parts:
149
- if "1x" in p:
150
- poster = p.strip().split(" ")[0]
151
- break
152
-
142
+ title = sel.select_direct_text("h1")
143
+ poster_attr = sel.select_attr("img.series-profile-thumb", "data-srcset") or sel.select_attr("img.series-profile-thumb", "srcset")
144
+ if poster_attr:
145
+ # "url 1x, url 2x" -> en sondakini (en yüksek kalite) al
146
+ poster = poster_attr.split(",")[-1].strip().split(" ")[0]
147
+ else:
148
+ poster = sel.select_poster("img.series-profile-thumb")
153
149
  description = sel.select_text("p#tv-series-desc")
150
+ tags = sel.select_texts("div.item.categories a")
151
+ rating = sel.select_text("span.color-imdb")
152
+ actors = sel.select_texts("div.content h5")
153
+ year = sel.extract_year("div.truncate")
154
+ duration = sel.regex_first(r"(\d+)", sel.select_text(".media-meta td:last-child"))
155
+ if duration == year or int(duration) < 40:
156
+ duration = None
157
+
158
+ common_info = {
159
+ "url" : url,
160
+ "poster" : self.fix_url(poster) if poster else None,
161
+ "title" : title or "Bilinmiyor",
162
+ "description" : description,
163
+ "tags" : tags,
164
+ "rating" : rating,
165
+ "year" : str(year) if year else None,
166
+ "actors" : actors,
167
+ "duration" : duration
168
+ }
154
169
 
155
- tags = [a.text(strip=True) for a in sel.select("div.item.categories a") if a.text(strip=True)]
156
-
157
- rating = sel.select_text("span.color-imdb")
158
-
159
- actors = [h5.text(strip=True) for h5 in sel.select("div.content h5") if h5.text(strip=True)]
160
-
161
- year = sel.select_text("span.item.year")
162
- if not year and title:
163
- # Try to extract year from title like "Movie Name(2024)"
164
- year_match = sel.regex_first(r"\((\d{4})\)", title)
165
- if year_match:
166
- year = year_match
167
-
168
170
  episodes = []
169
- episodes_box_list = sel.select("section.episodes-box")
170
-
171
- if episodes_box_list:
172
- episodes_box = episodes_box_list[0]
173
- # Sezon menüsünden sezon linklerini al
174
- season_menu = sel.select("div.ui.vertical.fluid.tabular.menu a.item", episodes_box)
175
-
176
- # Sezon tab içeriklerini al
177
- season_tabs = sel.select("div.ui.tab", episodes_box)
178
-
179
- # Eğer birden fazla sezon varsa, her sezon tab'ından bölümleri çek
180
- if season_tabs:
181
- for idx, season_tab in enumerate(season_tabs):
182
- # Sezon numarasını belirle
183
- current_season_no = idx + 1
184
-
185
- # Menüden sezon numarasını almaya çalış
186
- if idx < len(season_menu):
187
- menu_href = season_menu[idx].attrs.get("href", "")
188
- match = sel.regex_first(r"sezon-(\d+)", menu_href)
189
- if match:
190
- current_season_no = int(match)
191
-
192
- # Bu sezon tab'ından bölüm linklerini çek
193
- ep_links = sel.select("a[href*='bolum']", season_tab)
194
-
195
- seen_urls = set()
196
- for ep_link in ep_links:
197
- href = ep_link.attrs.get("href")
198
- if not href or href in seen_urls:
199
- continue
200
- seen_urls.add(href)
201
-
202
- # Bölüm numarasını URL'den çıkar
203
- ep_no = 0
204
- match_ep = sel.regex_first(r"bolum-(\d+)", href)
205
- if match_ep:
206
- ep_no = int(match_ep)
207
-
208
- # Bölüm başlığını çıkar (önce title attribute, sonra text)
209
- name = ep_link.attrs.get("title", "")
210
- if not name:
211
- name = sel.select_text("div.content div.header", ep_link)
212
- if not name:
213
- name = ep_link.text(strip=True)
214
-
215
- if href and ep_no > 0:
216
- episodes.append(Episode(
217
- season = current_season_no,
218
- episode = ep_no,
219
- title = name.strip() if name else f"{ep_no}. Bölüm",
220
- url = self.fix_url(href)
221
- ))
171
+ for tab in sel.select("div.ui.tab"):
172
+ for link in sel.select("a[href*='bolum']", tab):
173
+ href = link.attrs.get("href")
174
+ if href:
175
+ s, e = sel.extract_season_episode(href)
176
+ name = sel.select_text("div.content div.header", link) or link.text(strip=True)
177
+ episodes.append(Episode(season=s or 1, episode=e or 1, title=name, url=self.fix_url(href)))
222
178
 
223
179
  if episodes:
224
- return SeriesInfo(
225
- title = title,
226
- url = url,
227
- poster = self.fix_url(poster) if poster else None,
228
- description = description,
229
- rating = rating,
230
- tags = tags,
231
- actors = actors,
232
- year = year,
233
- episodes = episodes
234
- )
235
- else:
236
- return MovieInfo(
237
- title = title,
238
- url = url,
239
- poster = self.fix_url(poster) if poster else None,
240
- description = description,
241
- rating = rating,
242
- tags = tags,
243
- actors = actors,
244
- year = year
245
- )
180
+ return SeriesInfo(**common_info, episodes=episodes)
181
+
182
+ return MovieInfo(**common_info)
246
183
 
247
184
  async def load_links(self, url: str) -> list[ExtractResult]:
248
185
  resp = await self.httpx.get(url)