KekikStream 2.3.7__tar.gz → 2.3.9__tar.gz

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 (91) hide show
  1. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/MolyStream.py +1 -0
  2. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/VidMoly.py +13 -0
  3. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/DiziPal.py +20 -9
  4. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/DiziYou.py +14 -18
  5. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/Dizilla.py +50 -59
  6. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/FilmBip.py +8 -4
  7. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/FilmMakinesi.py +1 -1
  8. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/FullHDFilmizlesene.py +7 -16
  9. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/JetFilmizle.py +6 -1
  10. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/KultFilmler.py +2 -1
  11. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/RecTV.py +27 -5
  12. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/RoketDizi.py +76 -78
  13. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/SelcukFlix.py +90 -67
  14. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/SetFilmIzle.py +27 -8
  15. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/YabanciDizi.py +25 -14
  16. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream.egg-info/PKG-INFO +1 -1
  17. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream.egg-info/SOURCES.txt +0 -1
  18. {kekikstream-2.3.7 → kekikstream-2.3.9}/PKG-INFO +1 -1
  19. {kekikstream-2.3.7 → kekikstream-2.3.9}/setup.py +1 -1
  20. kekikstream-2.3.7/KekikStream/Plugins/DiziWatch.py +0 -212
  21. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/CLI/__init__.py +0 -0
  22. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/CLI/pypi_kontrol.py +0 -0
  23. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Extractor/ExtractorBase.py +0 -0
  24. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Extractor/ExtractorLoader.py +0 -0
  25. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Extractor/ExtractorManager.py +0 -0
  26. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Extractor/ExtractorModels.py +0 -0
  27. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Extractor/YTDLPCache.py +0 -0
  28. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/HTMLHelper.py +0 -0
  29. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Media/MediaHandler.py +0 -0
  30. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Media/MediaManager.py +0 -0
  31. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Plugin/PluginBase.py +0 -0
  32. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Plugin/PluginLoader.py +0 -0
  33. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Plugin/PluginManager.py +0 -0
  34. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/Plugin/PluginModels.py +0 -0
  35. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/UI/UIManager.py +0 -0
  36. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Core/__init__.py +0 -0
  37. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/CloseLoad.py +0 -0
  38. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/ContentX.py +0 -0
  39. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/DonilasPlay.py +0 -0
  40. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/DzenRu.py +0 -0
  41. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/ExPlay.py +0 -0
  42. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/Filemoon.py +0 -0
  43. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/HDPlayerSystem.py +0 -0
  44. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/JFVid.py +0 -0
  45. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/JetTv.py +0 -0
  46. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/MailRu.py +0 -0
  47. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/MixPlayHD.py +0 -0
  48. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/MixTiger.py +0 -0
  49. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/Odnoklassniki.py +0 -0
  50. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/PeaceMakerst.py +0 -0
  51. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/PixelDrain.py +0 -0
  52. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/PlayerFilmIzle.py +0 -0
  53. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/RapidVid.py +0 -0
  54. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/SetPlay.py +0 -0
  55. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/SetPrime.py +0 -0
  56. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/SibNet.py +0 -0
  57. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/Sobreatsesuyp.py +0 -0
  58. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/TRsTX.py +0 -0
  59. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/TauVideo.py +0 -0
  60. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/TurboImgz.py +0 -0
  61. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/TurkeyPlayer.py +0 -0
  62. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/VCTPlay.py +0 -0
  63. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/VidHide.py +0 -0
  64. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/VidMoxy.py +0 -0
  65. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/VidPapi.py +0 -0
  66. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/VideoSeyred.py +0 -0
  67. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/YTDLP.py +0 -0
  68. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Extractors/YildizKisaFilm.py +0 -0
  69. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/BelgeselX.py +0 -0
  70. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/DiziBox.py +0 -0
  71. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/FilmModu.py +0 -0
  72. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/FullHDFilm.py +0 -0
  73. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/HDFilmCehennemi.py +0 -0
  74. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/SezonlukDizi.py +0 -0
  75. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/SineWix.py +0 -0
  76. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/Sinefy.py +0 -0
  77. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/SinemaCX.py +0 -0
  78. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/Sinezy.py +0 -0
  79. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/SuperFilmGeldi.py +0 -0
  80. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/Plugins/UgurFilm.py +0 -0
  81. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/__init__.py +0 -0
  82. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/__main__.py +0 -0
  83. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream/requirements.txt +0 -0
  84. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream.egg-info/dependency_links.txt +0 -0
  85. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream.egg-info/entry_points.txt +0 -0
  86. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream.egg-info/requires.txt +0 -0
  87. {kekikstream-2.3.7 → kekikstream-2.3.9}/KekikStream.egg-info/top_level.txt +0 -0
  88. {kekikstream-2.3.7 → kekikstream-2.3.9}/LICENSE +0 -0
  89. {kekikstream-2.3.7 → kekikstream-2.3.9}/MANIFEST.in +0 -0
  90. {kekikstream-2.3.7 → kekikstream-2.3.9}/README.md +0 -0
  91. {kekikstream-2.3.7 → kekikstream-2.3.9}/setup.cfg +0 -0
@@ -9,6 +9,7 @@ class MolyStream(ExtractorBase):
9
9
 
10
10
  # Birden fazla domain destekle
11
11
  supported_domains = [
12
+ "dbx.molystream.org",
12
13
  "ydx.molystream.org",
13
14
  "yd.sheila.stream",
14
15
  "ydf.popcornvakti.net",
@@ -24,6 +24,8 @@ class VidMoly(ExtractorBase):
24
24
 
25
25
  if ".me" in url:
26
26
  url = url.replace(".me", ".net")
27
+ if ".to" in url:
28
+ url = url.replace(".to", ".net")
27
29
 
28
30
  # VidMoly bazen redirect ediyor, takip et
29
31
  response = await self.httpx.get(url, follow_redirects=True)
@@ -70,6 +72,17 @@ class VidMoly(ExtractorBase):
70
72
  if sub.get("kind") == "captions"
71
73
  ]
72
74
 
75
+ if "#EXTM3U" in response.text:
76
+ for line in response.text.splitlines():
77
+ line = line.strip().replace('"', '').replace("'", "")
78
+ if line.startswith("http"):
79
+ return ExtractResult(
80
+ name = self.name,
81
+ url = line,
82
+ referer = self.main_url,
83
+ subtitles = subtitles
84
+ )
85
+
73
86
  if script_str := resp_sec.regex_first(r"sources:\s*\[(.*?)\],", flags= re.DOTALL):
74
87
  script_content = script_str
75
88
  # Video kaynaklarını ayrıştır
@@ -127,18 +127,27 @@ class DiziPal(PluginBase):
127
127
 
128
128
  poster = self.fix_url(secici.select_attr("meta[property='og:image']", "content")) if secici.select_attr("meta[property='og:image']", "content") else None
129
129
 
130
- # XPath yerine regex ile HTML'den çıkarma
131
- year = secici.regex_first(r'(?is)Yapım Yılı.*?<div[^>]*>(\d{4})</div>', secici.html)
132
-
133
- description = secici.select_text("div.summary p")
130
+ # Sidebar bilgilerini topla
131
+ info = {}
132
+ for li in secici.select("li"):
133
+ key = secici.select_text("div.key", li)
134
+ val = secici.select_text("div.value", li)
135
+ if key and val:
136
+ info[key.strip(":")] = val.strip()
137
+
138
+ year = info.get("Yapım Yılı")
139
+ rating = info.get("IMDB Puanı")
140
+
141
+ tags_raw = info.get("Türler", "")
142
+ tags = [t.strip() for t in tags_raw.split() if t.strip()] if tags_raw else None
134
143
 
135
- rating = secici.regex_first(r'(?is)IMDB Puanı.*?<div[^>]*>([0-9.]+)</div>', secici.html)
144
+ actors_raw = info.get("Oyuncular")
145
+ actors = [a.strip() for a in actors_raw.split(",") if a.strip()] if actors_raw else None
136
146
 
137
- tags_raw = secici.regex_first(r'(?is)Türler.*?<div[^>]*>([^<]+)</div>', secici.html)
138
- tags = [t.strip() for t in tags_raw.split() if t.strip()] if tags_raw else None
147
+ description = secici.select_text("div.summary p")
139
148
 
140
- duration_raw = secici.regex_first(r'(?is)Ortalama Süre.*?<div[^>]*>(\d+)', secici.html)
141
- duration = int(duration_raw) if duration_raw else None
149
+ duration_raw = info.get("Ortalama Süre")
150
+ duration = int(secici.regex_first(r"(\d+)", duration_raw)) if duration_raw else None
142
151
 
143
152
  if "/dizi/" in url:
144
153
  title = secici.select_text("div.cover h5")
@@ -177,6 +186,7 @@ class DiziPal(PluginBase):
177
186
  year = year,
178
187
  duration = duration,
179
188
  episodes = episodes if episodes else None,
189
+ actors = actors,
180
190
  )
181
191
  else:
182
192
  # Film için title - g-title div'lerinin 2. olanı
@@ -192,6 +202,7 @@ class DiziPal(PluginBase):
192
202
  rating = rating,
193
203
  year = year,
194
204
  duration = duration,
205
+ actors = actors,
195
206
  )
196
207
 
197
208
  async def load_links(self, url: str) -> list[ExtractResult]:
@@ -72,7 +72,7 @@ class DiziYou(PluginBase):
72
72
  html_text = istek.text
73
73
 
74
74
  # Title - div.title h1 içinde
75
- title = secici.select_text("div.title h1")
75
+ title = (secici.select_text("div.title h1") or "").strip()
76
76
 
77
77
  # Fallback: Eğer title boşsa URL'den çıkar (telif kısıtlaması olan sayfalar için)
78
78
  if not title:
@@ -81,23 +81,19 @@ class DiziYou(PluginBase):
81
81
  title = slug.replace('-', ' ').title()
82
82
 
83
83
  # Poster
84
- poster_src = secici.select_attr("div.category_image img", "src")
84
+ poster_src = secici.select_attr("div.category_image img", "src") or secici.select_attr("meta[property='og:image']", "content")
85
85
  poster = self.fix_url(poster_src) if poster_src else ""
86
86
 
87
87
  # Year - regex ile çıkarma (xpath yerine)
88
88
  year = secici.regex_first(r"(?is)Yapım Yılı.*?(\d{4})", secici.html)
89
89
 
90
- description = None
91
- # Extract inner HTML via regex and clean
92
- desc_html = secici.regex_first(r'(?s)<div class="diziyou_desc">(.*?)</div>', secici.html)
93
- if desc_html:
94
- # Script taglarını kaldır
95
- desc_html = HTMLHelper(desc_html).regex_replace(r"(?s)<script.*?</script>", "")
96
- # div#icerikcat2 ve sonrasını kaldır (meta bilgileri içeriyor)
97
- desc_html = HTMLHelper(desc_html).regex_replace(r"(?s)<div id=\"icerikcat2\".*", "")
98
- # Kalan HTML'den text çıkar
99
- clean_sel = HTMLHelper(desc_html)
100
- description = clean_sel.select_text()
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])
101
97
 
102
98
  tags = [secici.select_text(None, a) for a in secici.select("div.genres a") if secici.select_text(None, a)]
103
99
 
@@ -109,9 +105,9 @@ class DiziYou(PluginBase):
109
105
  actors = [actor.strip() for actor in actors_raw.split(",") if actor.strip()] if actors_raw else []
110
106
 
111
107
  episodes = []
112
- # Episodes - daha fazla DOM/URL kalıbını destekle
113
- for link in secici.select("a"):
114
- ep_href = secici.select_attr("a", "href", link)
108
+ # Episodes - div#scrollbar-container a (kısıtlı alan)
109
+ for link in secici.select("div#scrollbar-container a"):
110
+ ep_href = secici.select_attr(None, "href", link)
115
111
  if not ep_href:
116
112
  continue
117
113
 
@@ -179,9 +175,9 @@ class DiziYou(PluginBase):
179
175
  # Player src'den item_id çıkar - önce özel player seçicisini dene
180
176
  player_src = None
181
177
  # Yaygın locatorlar
182
- for sel in ["iframe#diziyouPlayer", "div.player iframe", "iframe[src*='/episodes/']", "iframe"]:
178
+ for sel in ["iframe#diziyouPlayer", "div.player iframe", "iframe[src*='/player/']", "iframe[src*='/episodes/']", "iframe"]:
183
179
  p = secici.select_attr(sel, "src")
184
- if p and "youtube.com" not in p.lower():
180
+ if p and any(x in p.lower() for x in ["/player/", "/episodes/", "diziyou"]):
185
181
  player_src = p
186
182
  break
187
183
 
@@ -137,70 +137,60 @@ class Dizilla(PluginBase):
137
137
  istek = await self.httpx.get(url)
138
138
  secici = HTMLHelper(istek.text)
139
139
 
140
- title = secici.select_text("div.poster.poster h2")
141
- if not title:
140
+ next_data_text = secici.select_text("script#__NEXT_DATA__")
141
+ if not next_data_text:
142
142
  return None
143
143
 
144
- poster = secici.select_attr("div.w-full.page-top.relative img", "src")
145
- poster = self.fix_url(poster) if poster else None
146
-
147
- # Year extraction (Kotlin: [1] index for w-fit min-w-fit)
148
- info_boxes = secici.select("div.w-fit.min-w-fit")
149
- year = None
150
- if len(info_boxes) > 1:
151
- year_text = secici.select_text("span.text-sm.opacity-60", info_boxes[1])
152
- if year_text:
153
- year = year_text.split(" ")[-1] if " " in year_text else year_text
154
-
155
- description = secici.select_text("div.mt-2.text-sm")
156
-
157
- tags_text = secici.select_text("div.poster.poster h3")
158
- tags = [t.strip() for t in tags_text.split(",")] if tags_text else []
159
-
160
- actors = secici.select_all_text("div.global-box h5")
161
-
162
- episodeses = []
163
- # Seasons links iteration
164
- season_links = secici.select("div.flex.items-center.flex-wrap.gap-2.mb-4 a")
165
- for sezon in season_links:
166
- sezon_href = secici.select_attr("a", "href", sezon)
167
- sezon_href = self.fix_url(sezon_href)
168
- sezon_req = await self.httpx.get(sezon_href)
169
-
170
- season_num = None
171
- try:
172
- # URL'den sezon numarasını çek: ...-N-sezon formatı
173
- season_match = secici.regex_first(r"-(\d+)-sezon", sezon_href)
174
- if season_match:
175
- season_num = int(season_match)
176
- except:
177
- pass
178
-
179
- sezon_secici = HTMLHelper(sezon_req.text)
180
- for bolum in sezon_secici.select("div.episodes div.cursor-pointer"):
181
- # Kotlin: bolum.select("a").last()
182
- links = sezon_secici.select("a", bolum)
183
- if not links:
184
- continue
185
-
186
- ep_link = links[-1]
187
- ep_name = sezon_secici.select_text("a", ep_link)
188
- ep_href = sezon_secici.select_attr("a", "href", ep_link)
189
- ep_href = self.fix_url(ep_href)
144
+ next_data = loads(next_data_text)
145
+ secure_data = next_data.get("props", {}).get("pageProps", {}).get("secureData")
146
+ if not secure_data:
147
+ return None
148
+
149
+ decrypted = await self.decrypt_response(secure_data)
150
+ content = decrypted.get("contentItem", {})
151
+ if not content:
152
+ return None
153
+
154
+ title = content.get("original_title") or content.get("used_title")
155
+ description = content.get("description") or content.get("used_description")
156
+ rating = content.get("imdb_point") or content.get("local_vote_avg")
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 ""
190
183
 
191
- # Episode number (first link's text usually)
192
- ep_num = None
193
- try:
194
- ep_num_text = sezon_secici.select_text("a", links[0])
195
- ep_num = int(ep_num_text) if ep_num_text else None
196
- except:
197
- pass
198
-
199
- episodeses.append(Episode(
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
188
+
189
+ episodes.append(Episode(
200
190
  season = season_num,
201
191
  episode = ep_num,
202
192
  title = ep_name,
203
- url = ep_href
193
+ url = self.fix_url(f"{self.main_url}/{ep_slug}")
204
194
  ))
205
195
 
206
196
  return SeriesInfo(
@@ -209,8 +199,9 @@ class Dizilla(PluginBase):
209
199
  title = title,
210
200
  description = description,
211
201
  tags = tags,
202
+ rating = str(rating) if rating else None,
212
203
  year = str(year) if year else None,
213
- episodes = episodeses,
204
+ episodes = episodes,
214
205
  actors = actors
215
206
  )
216
207
 
@@ -108,18 +108,22 @@ class FilmBip(PluginBase):
108
108
  tags = secici.select_all_text("div.series-profile-type.tv-show-profile-type a")
109
109
 
110
110
  # XPath yerine regex kullanarak yıl, süre vs. çıkarma
111
- year = secici.regex_first(r'(?i)Yapım yılı.*?<p[^>]*>(\d{4})</p>', secici.html)
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)
112
115
 
113
- duration = secici.regex_first(r'(?i)Süre.*?<p[^>]*>(\d+)', secici.html)
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
114
118
 
115
- rating = secici.regex_first(r'(?i)IMDB Puanı.*?<span[^>]*>([0-9.]+)</span>', secici.html)
119
+ rating = secici.regex_first(r"(?is)IMDB Puan\u0131.*?<span[^>]*>(.*?)<\/span>")
116
120
 
117
121
  actors = [img.attrs.get("alt") for img in secici.select("div.series-profile-cast ul li a img") if img.attrs.get("alt")]
118
122
 
119
123
  return MovieInfo(
120
124
  url = url,
121
125
  poster = self.fix_url(poster) if poster else None,
122
- title = self.clean_title(title) if title else "",
126
+ title = HTMLHelper(title).regex_replace(r"\(\d{4}\)", "").strip() if title else "",
123
127
  description = description,
124
128
  tags = tags,
125
129
  year = year,
@@ -91,7 +91,7 @@ class FilmMakinesi(PluginBase):
91
91
  year = secici.select_text("span.date a") or ""
92
92
 
93
93
  actors = secici.select_all_text("div.cast-name")
94
- tags = secici.select_all_text("div.genre a")
94
+ tags = [a.text(strip=True) for a in secici.select("div.type a") if "/tur/" in (a.attrs.get("href") or "")]
95
95
 
96
96
  duration = None
97
97
  duration_text = secici.select_text("div.time") or None
@@ -93,19 +93,13 @@ class FullHDFilmizlesene(PluginBase):
93
93
 
94
94
  tags = secici.select_all_text("a[rel='category tag']")
95
95
 
96
- # Rating: normalize-space yerine doğrudan class ile ve son kelimeyi al
97
- rating_text = secici.select_text("div.puanx-puan") or None
98
- rating = None
99
- if rating_text:
100
- parts = rating_text.split()
101
- rating = parts[-1] if parts else None
96
+ # Rating: regex ile sayısal değeri yakala
97
+ rating_text = secici.select_text("div.puanx-puan") or ""
98
+ rating = secici.regex_first(r"(\d+\.\d+|\d+)", rating_text)
102
99
 
103
100
  # Year: ilk yıl formatında değer
104
- year_text = secici.select_text("div.dd a.category") or None
105
- year = None
106
- if year_text:
107
- parts = year_text.split()
108
- year = parts[0] if parts else None
101
+ year_text = secici.select_text("div.dd a.category") or ""
102
+ year = secici.regex_first(r"(\d{4})", year_text)
109
103
 
110
104
  # Actors: nth-child yerine tüm li'leri alıp 2. index
111
105
  lis = secici.select("div.film-info ul li")
@@ -113,11 +107,8 @@ class FullHDFilmizlesene(PluginBase):
113
107
  if len(lis) >= 2:
114
108
  actors = secici.select_all_text("a > span", lis[1])
115
109
 
116
- duration = "0"
117
- duration_text = secici.select_text("span.sure") or None
118
- if duration_text:
119
- duration_parts = duration_text.split()
120
- duration = duration_parts[0] if duration_parts else "0"
110
+ # Duration: regex ile yakala (örn: 201 dk)
111
+ duration = secici.regex_first(r"(\d+)\s*(?:dk|dakika)", html_text)
121
112
 
122
113
  return MovieInfo(
123
114
  url = url,
@@ -113,6 +113,10 @@ class JetFilmizle(PluginBase):
113
113
  year = secici.extract_year("div.yap")
114
114
 
115
115
  actors = secici.select_all_text("div[itemprop='actor'] a span")
116
+ if not actors: # Fallback to img alt
117
+ actors = [img.attrs.get("alt") for img in secici.select("div.oyuncular div.oyuncu img") if img.attrs.get("alt")]
118
+
119
+ duration = secici.regex_first(r"(\d+)\s*dk", istek.text)
116
120
 
117
121
  return MovieInfo(
118
122
  url = url,
@@ -122,7 +126,8 @@ class JetFilmizle(PluginBase):
122
126
  tags = tags,
123
127
  rating = rating,
124
128
  year = year,
125
- actors = actors
129
+ actors = actors,
130
+ duration = int(duration) if duration else None
126
131
  )
127
132
 
128
133
  async def load_links(self, url: str) -> list[ExtractResult]:
@@ -91,7 +91,8 @@ class KultFilmler(PluginBase):
91
91
  time_text = secici.select_text("li.time span")
92
92
  duration = secici.regex_first(r"(\d+)", time_text) if time_text else None
93
93
 
94
- rating = secici.select_text("div.imdb-count")
94
+ rating_text = secici.select_text("div.imdb-count")
95
+ rating = secici.regex_first(r"(\d+\.\d+|\d+)", rating_text) if rating_text else None
95
96
 
96
97
  actors = [a.text(strip=True) for a in secici.select("div.actors a") if a.text(strip=True)]
97
98
 
@@ -96,27 +96,49 @@ class RecTV(PluginBase):
96
96
 
97
97
  episodes.append(ep_model)
98
98
 
99
+ # Süreyi dakikaya çevir (Örn: "1h 59min")
100
+ duration_raw = veri.get("duration")
101
+ duration = None
102
+ if duration_raw:
103
+ try:
104
+ h = int(HTMLHelper(duration_raw).regex_first(r"(\d+)h") or 0)
105
+ m = int(HTMLHelper(duration_raw).regex_first(r"(\d+)min") or 0)
106
+ duration = h * 60 + m
107
+ except: pass
108
+
99
109
  return SeriesInfo(
100
110
  url = url,
101
111
  poster = self.fix_url(veri.get("image")),
102
112
  title = veri.get("title"),
103
113
  description = veri.get("description"),
104
114
  tags = [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
105
- rating = veri.get("imdb") or veri.get("rating"),
106
- year = veri.get("year"),
115
+ rating = str(veri.get("imdb") or veri.get("rating") or ""),
116
+ year = str(veri.get("year") or ""),
107
117
  actors = [],
118
+ duration = duration,
108
119
  episodes = episodes
109
120
  )
110
121
  case _:
122
+ # Süreyi dakikaya çevir
123
+ duration_raw = veri.get("duration")
124
+ duration = None
125
+ if duration_raw:
126
+ try:
127
+ h = int(HTMLHelper(duration_raw).regex_first(r"(\d+)h") or 0)
128
+ m = int(HTMLHelper(duration_raw).regex_first(r"(\d+)min") or 0)
129
+ duration = h * 60 + m
130
+ except: pass
131
+
111
132
  return MovieInfo(
112
133
  url = url,
113
134
  poster = self.fix_url(veri.get("image")),
114
135
  title = veri.get("title"),
115
136
  description = veri.get("description"),
116
137
  tags = [genre.get("title") for genre in veri.get("genres")] if veri.get("genres") else [],
117
- rating = veri.get("imdb") or veri.get("rating"),
118
- year = veri.get("year"),
119
- actors = []
138
+ rating = str(veri.get("imdb") or veri.get("rating") or ""),
139
+ year = str(veri.get("year") or ""),
140
+ actors = [],
141
+ duration = duration
120
142
  )
121
143
 
122
144
  async def load_links(self, url: str) -> list[ExtractResult]:
@@ -84,89 +84,87 @@ class RoketDizi(PluginBase):
84
84
  except Exception:
85
85
  return []
86
86
 
87
- async def load_item(self, url: str) -> SeriesInfo:
88
- # Note: Handling both Movie and Series logic in one, returning SeriesInfo generally or MovieInfo
87
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
89
88
  resp = await self.httpx.get(url)
90
89
  sel = HTMLHelper(resp.text)
91
- html_text = resp.text
92
-
93
- title = sel.select_text("h1.text-white")
94
-
95
- poster = sel.select_attr("div.w-full.page-top img", "src")
96
-
97
- description = sel.select_text("div.mt-2.text-sm")
98
-
99
- # Tags - genre bilgileri (Detaylar bölümünde)
100
- tags = []
101
- genre_text = sel.select_text("h3.text-white.opacity-90")
102
- if genre_text:
103
- tags = [t.strip() for t in genre_text.split(",")]
104
-
105
- # Rating
106
- rating = sel.select_text("span.text-white.text-sm.font-bold")
107
-
108
- # Year ve Actors - Detaylar (Details) bölümünden
109
- year = None
110
- actors = []
90
+
91
+ next_data_text = sel.select_text("script#__NEXT_DATA__")
92
+ if not next_data_text:
93
+ return SeriesInfo(url=url, title=sel.select_text("h1") or "Bilinmeyen")
111
94
 
112
- # Detaylar bölümündeki tüm flex-col div'leri al
113
- detail_items = sel.select("div.flex.flex-col")
114
- for item in detail_items:
115
- label = sel.select_text("span.text-base", item)
116
- value = sel.select_text("span.text-sm.opacity-90", item)
95
+ try:
96
+ next_data = json.loads(next_data_text)
97
+ secure_data_raw = next_data["props"]["pageProps"]["secureData"]
98
+ secure_data = json.loads(base64.b64decode(secure_data_raw).decode('utf-8'))
117
99
 
118
- label = label if label else None
119
- value = value if value else None
100
+ content_item = secure_data.get("contentItem", {})
101
+ content = secure_data.get("content", {}).get("result", {})
120
102
 
121
- if label and value:
122
- # Yayın tarihi (yıl)
123
- if label == "Yayın tarihi":
124
- # "16 Ekim 2018" formatından yılı çıkar
125
- year = HTMLHelper(value).regex_first(r'\d{4}')
126
- # Yaratıcılar veya Oyuncular
127
- elif label in ["Yaratıcılar", "Oyuncular"]:
128
- if value:
129
- actors.append(value)
130
-
131
- # Check urls for episodes
132
- all_urls = HTMLHelper(html_text).regex_all(r'"url":"([^"]*)"')
133
- is_series = any("bolum-" in u for u in all_urls)
134
-
135
- episodes = []
136
- if is_series:
137
- # Dict kullanarak duplicate'leri önle ama sıralı tut
138
- episodes_dict = {}
139
- for u in all_urls:
140
- if "bolum" in u and u not in episodes_dict:
141
- season = HTMLHelper(u).regex_first(r'/sezon-(\d+)')
142
- ep_num = HTMLHelper(u).regex_first(r'/bolum-(\d+)')
143
-
144
- season = int(season) if season else 1
145
- episode_num = int(ep_num) if ep_num else 1
146
-
147
- # Key olarak (season, episode) tuple kullan
148
- key = (season, episode_num)
149
- episodes_dict[key] = Episode(
150
- season = season,
151
- episode = episode_num,
152
- title = f"{season}. Sezon {episode_num}. Bölüm",
153
- url = self.fix_url(u)
154
- )
155
-
156
- # Sıralı liste oluştur
157
- episodes = [episodes_dict[key] for key in sorted(episodes_dict.keys())]
158
-
159
- return SeriesInfo(
160
- title = title,
161
- url = url,
162
- poster = self.fix_url(poster) if poster else None,
163
- description = description,
164
- tags = tags,
165
- rating = rating,
166
- actors = actors,
167
- episodes = episodes,
168
- year = year
169
- )
103
+ title = content_item.get("original_title") or content_item.get("culture_title")
104
+ poster = content_item.get("poster_url") or content_item.get("face_url")
105
+ description = content_item.get("description")
106
+ rating = str(content_item.get("imdb_point") or "")
107
+ year = str(content_item.get("release_year") or "")
108
+ tags = content_item.get("categories", "").split(",")
109
+
110
+ # Actors extraction from getSerieCastsById or getMovieCastsById
111
+ actors = []
112
+ casts_data = content.get("getSerieCastsById") or content.get("getMovieCastsById")
113
+ if casts_data and casts_data.get("result"):
114
+ actors = [cast.get("name") for cast in casts_data["result"] if cast.get("name")]
115
+
116
+ # Episodes extraction
117
+ episodes = []
118
+ if "Series" in str(content.get("FindedType")):
119
+ # Check for episodes in SecureData -> RelatedResults -> getEpisodeSources (this might be for the current episode)
120
+ # Usually full episode list isn't in secureData, but we can get it from HTML or another API
121
+ # However, many times Next.js pages have them in props
122
+ # Let's fallback to the previous regex method for episodes if not in JSON
123
+ all_urls = HTMLHelper(resp.text).regex_all(r'"url":"([^"]*)"')
124
+ episodes_dict = {}
125
+ for u in all_urls:
126
+ if "bolum" in u and u not in episodes_dict:
127
+ s_match = HTMLHelper(u).regex_first(r'/sezon-(\d+)')
128
+ e_match = HTMLHelper(u).regex_first(r'/bolum-(\d+)')
129
+ s_val = int(s_match) if s_match else 1
130
+ e_val = int(e_match) if e_match else 1
131
+ episodes_dict[(s_val, e_val)] = Episode(
132
+ season = s_val,
133
+ episode = e_val,
134
+ title = f"{s_val}. Sezon {e_val}. Bölüm",
135
+ url = self.fix_url(u)
136
+ )
137
+ episodes = [episodes_dict[key] for key in sorted(episodes_dict.keys())]
138
+
139
+ return SeriesInfo(
140
+ url = url,
141
+ poster = self.fix_url(poster) if poster else None,
142
+ title = self.clean_title(title),
143
+ description = description,
144
+ tags = tags,
145
+ rating = rating,
146
+ year = year,
147
+ actors = actors,
148
+ episodes = episodes
149
+ )
150
+ else:
151
+ return MovieInfo(
152
+ url = url,
153
+ poster = self.fix_url(poster) if poster else None,
154
+ title = self.clean_title(title),
155
+ description = description,
156
+ tags = tags,
157
+ rating = rating,
158
+ year = year,
159
+ actors = actors
160
+ )
161
+
162
+ except Exception:
163
+ # Fallback to simple extraction if JSON parsing fails
164
+ return SeriesInfo(
165
+ url = url,
166
+ title = self.clean_title(sel.select_text("h1")) or "Bilinmeyen"
167
+ )
170
168
 
171
169
  async def load_links(self, url: str) -> list[ExtractResult]:
172
170
  resp = await self.httpx.get(url)