KekikStream 2.4.2__py3-none-any.whl → 2.4.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 (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 +26 -75
  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.3.dist-info}/METADATA +1 -1
  67. kekikstream-2.4.3.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.3.dist-info}/WHEEL +0 -0
  70. {kekikstream-2.4.2.dist-info → kekikstream-2.4.3.dist-info}/entry_points.txt +0 -0
  71. {kekikstream-2.4.2.dist-info → kekikstream-2.4.3.dist-info}/licenses/LICENSE +0 -0
  72. {kekikstream-2.4.2.dist-info → kekikstream-2.4.3.dist-info}/top_level.txt +0 -0
@@ -62,7 +62,7 @@ class Dizilla(PluginBase):
62
62
  # Detay sayfasından poster vb. bilgileri al
63
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,
@@ -138,65 +138,39 @@ class Dizilla(PluginBase):
138
138
  secici = HTMLHelper(istek.text)
139
139
 
140
140
  next_data_text = secici.select_text("script#__NEXT_DATA__")
141
- if not next_data_text:
142
- return None
141
+ if not next_data_text: return None
143
142
 
144
143
  next_data = loads(next_data_text)
145
144
  secure_data = next_data.get("props", {}).get("pageProps", {}).get("secureData")
146
- if not secure_data:
147
- return None
145
+ if not secure_data: return None
148
146
 
149
147
  decrypted = await self.decrypt_response(secure_data)
150
148
  content = decrypted.get("contentItem", {})
151
- if not content:
152
- return None
149
+ if not content: return None
153
150
 
154
151
  title = content.get("original_title") or content.get("used_title")
155
152
  description = content.get("description") or content.get("used_description")
156
153
  rating = content.get("imdb_point") or content.get("local_vote_avg")
157
154
  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
155
+ poster = self.fix_poster_url(self.fix_url(content.get("back_url") or content.get("poster_url")))
188
156
 
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
- ))
157
+ tags = [cat.get("name") for cat in decrypted.get("RelatedResults", {}).get("getSerieCategoriesById", {}).get("result", [])]
158
+ actors = [cast.get("name") for cast in decrypted.get("RelatedResults", {}).get("getSerieCastsById", {}).get("result", [])]
159
+
160
+ episodes = []
161
+ for season in decrypted.get("RelatedResults", {}).get("getSerieSeasonAndEpisodes", {}).get("result", []):
162
+ s_no = season.get("season_no")
163
+ for ep in season.get("episodes", []):
164
+ e_no = ep.get("episode_no")
165
+ slug = ep.get("used_slug")
166
+ name = ep.get("episode_text") or ""
167
+ if not any(e.season == s_no and e.episode == e_no for e in episodes):
168
+ episodes.append(Episode(season=s_no, episode=e_no, title=name, url=self.fix_url(f"{self.main_url}/{slug}")))
195
169
 
196
170
  return SeriesInfo(
197
171
  url = url,
198
172
  poster = poster,
199
- title = title,
173
+ title = title or "Bilinmiyor",
200
174
  description = description,
201
175
  tags = tags,
202
176
  rating = str(rating) if rating else None,
@@ -95,38 +95,24 @@ class FilmBip(PluginBase):
95
95
  async def load_item(self, url: str) -> MovieInfo:
96
96
  istek = await self.httpx.get(url)
97
97
  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
98
 
99
+ title = self.clean_title(secici.select_direct_text("div.page-title h1"))
100
+ poster = secici.select_poster("div.series-profile-image a img")
106
101
  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")]
102
+ tags = secici.select_texts("div.series-profile-type.tv-show-profile-type a")
103
+ year = secici.extract_year("div.series-profile-infos-in") or secici.regex_first(r"\((\d{4})\)", title)
104
+ duration = secici.regex_first(r"(\d+)", secici.meta_value("Süre", container_selector="div.series-profile-infos"))
105
+ rating = secici.meta_value("IMDB Puanı", container_selector="div.series-profile-infos")
106
+ rating = rating.split("(")[0] if rating else None
107
+ actors = secici.select_attrs("div.series-profile-cast ul li a img", "alt")
122
108
 
123
109
  return MovieInfo(
124
110
  url = url,
125
111
  poster = self.fix_url(poster) if poster else None,
126
- title = HTMLHelper(title).regex_replace(r"\(\d{4}\)", "").strip() if title else "",
112
+ title = title or "",
127
113
  description = description,
128
114
  tags = tags,
129
- year = year,
115
+ year = str(year) if year else None,
130
116
  rating = rating,
131
117
  duration = int(duration) if duration else None,
132
118
  actors = actors,
@@ -70,45 +70,25 @@ class FilmEkseni(PluginBase):
70
70
  istek = await self.httpx.get(url)
71
71
  helper = HTMLHelper(istek.text)
72
72
 
73
- raw_title = helper.select_text("div.page-title h1")
74
- title = raw_title.replace(" izle", "").strip() if raw_title else "Bilinmiyor"
75
-
76
- poster = helper.select_attr("picture.poster-auto > source:nth-child(2)", "data-srcset")
77
- description = helper.select_text("article.text-white")
78
- year = helper.select_text("strong a")
79
-
80
- tags_raw = helper.select_all_text("div.pb-2")
81
- tags = []
82
- for tag_str in tags_raw:
83
- if tag_str.startswith("Tür:"):
84
- tags.extend([t.strip() for t in tag_str.replace("Tür:", "").split(",")])
73
+ title = self.clean_title(helper.select_text("div.page-title h1"))
74
+ poster = helper.select_poster("picture.poster-auto img")
75
+ description = helper.select_direct_text("article.text-white p")
76
+ year = helper.extract_year("div.page-title", "strong a")
77
+ tags = helper.select_texts("div.pb-2 a[href*='/tur/']")
78
+ rating = helper.select_text("div.rate")
79
+ duration = helper.regex_first(r"(\d+)", helper.select_text("div.d-flex.flex-column.text-nowrap"))
80
+ actors = helper.select_texts("div.card-body.p-0.pt-2 .story-item .story-item-title")
85
81
 
86
- rating = helper.select_text("div.rate")
87
-
88
- duration = None
89
- duration_text = helper.select_text("div.d-flex.flex-column.text-nowrap")
90
- if duration_text:
91
- m = re.search(r"(\d+)", duration_text)
92
- if m:
93
- duration = int(m.group(1))
94
-
95
- actors_raw = helper.select("div.card-body.p-0.pt-2 .story-item")
96
- actors = []
97
- for actor in actors_raw:
98
- name = helper.select_text(".story-item-title", actor)
99
- if name:
100
- actors.append(name)
101
-
102
82
  return MovieInfo(
103
83
  url = url,
104
- poster = self.fix_url(poster),
105
- title = title,
84
+ poster = self.fix_url(poster) if poster else None,
85
+ title = title or "Bilinmiyor",
106
86
  description = description,
107
87
  tags = tags,
108
88
  rating = rating,
109
- year = year,
89
+ year = str(year) if year else None,
110
90
  actors = actors if actors else None,
111
- duration = duration
91
+ duration = int(duration) if duration else None
112
92
  )
113
93
 
114
94
  async def load_links(self, url: str) -> list[ExtractResult]:
@@ -76,94 +76,41 @@ class FilmMakinesi(PluginBase):
76
76
  istek = await self.httpx.get(url)
77
77
  secici = HTMLHelper(istek.text)
78
78
 
79
- title = secici.select_text("h1.title") or ""
79
+ title = self.clean_title(secici.select_text("h1.title"))
80
+ poster = secici.select_poster("img.cover-img")
81
+ description = secici.select_text("div.info-description p")
82
+ rating = secici.regex_first(r"(\d+[\d.]*)", secici.select_text("div.score"))
83
+ year = secici.select_text("span.date a")
84
+ actors = secici.select_texts("div.cast-name")
85
+ tags = secici.select_texts("div.type a[href*='/tur/']")
86
+ duration = secici.regex_first(r"(\d+)", secici.select_text("div.time"))
80
87
 
81
- poster = secici.select_attr("img.cover-img", "src") or ""
82
- poster = poster.strip()
83
-
84
- description = secici.select_text("div.info-description p") or ""
85
-
86
- rating_text = secici.select_text("div.score") or ""
87
- rating = None
88
- if rating_text:
89
- rating = rating_text.split()[0]
90
-
91
- year = secici.select_text("span.date a") or ""
92
-
93
- actors = secici.select_all_text("div.cast-name")
94
- tags = [a.text(strip=True) for a in secici.select("div.type a") if "/tur/" in (a.attrs.get("href") or "")]
95
-
96
- duration = None
97
- duration_text = secici.select_text("div.time") or None
98
- if duration_text:
99
- parts = duration_text.split()
100
- if len(parts) > 1:
101
- duration = parts[1].strip()
102
-
103
- # Dizi mi kontrol et - sezon/bölüm linkleri var mı?
104
88
  episodes = []
105
- all_links = secici.select("a[href]")
106
- for link in all_links:
89
+ for link in secici.select("a[href]"):
107
90
  href = link.attrs.get("href", "")
108
- pairs = HTMLHelper(href).regex_all(r"/sezon-(\d+)/bolum-(\d+)")
109
- if pairs:
110
- season_no = int(pairs[0][0])
111
- ep_no = int(pairs[0][1])
112
-
113
- # Bölüm başlığını çıkar - text'ten gerçek ismi al
114
- # Format: "22 Eylül 2014 / 44 dk /1. Sezon / 1. BölümPilot"
115
- full_text = link.text(strip=True)
116
- # "Bölüm" kelimesinden sonraki kısmı al
117
- ep_title = ""
118
- if "Bölüm" in full_text:
119
- parts = full_text.split("Bölüm")
120
- if len(parts) > 1:
121
- ep_title = parts[-1].strip()
122
-
123
- episodes.append(Episode(
124
- season = season_no,
125
- episode = ep_no,
126
- title = ep_title,
127
- url = self.fix_url(href)
128
- ))
91
+ s, e = secici.extract_season_episode(href)
92
+ if s and e:
93
+ name = link.text(strip=True).split("Bölüm")[-1].strip() if "Bölüm" in link.text() else ""
94
+ episodes.append(Episode(season=s, episode=e, title=name, url=self.fix_url(href)))
129
95
 
130
- # Bölümler varsa SeriesInfo döndür
96
+ # Tekrar edenleri temizle ve sırala
131
97
  if episodes:
132
- # Tekrar eden bölümleri kaldır
133
98
  seen = set()
134
- unique_episodes = []
99
+ unique = []
135
100
  for ep in episodes:
136
- key = (ep.season, ep.episode)
137
- if key not in seen:
138
- seen.add(key)
139
- unique_episodes.append(ep)
140
-
141
- # Sırala
142
- unique_episodes.sort(key=lambda x: (x.season or 0, x.episode or 0))
143
-
101
+ if (ep.season, ep.episode) not in seen:
102
+ seen.add((ep.season, ep.episode))
103
+ unique.append(ep)
104
+ unique.sort(key=lambda x: (x.season or 0, x.episode or 0))
105
+
144
106
  return SeriesInfo(
145
- url = url,
146
- poster = self.fix_url(poster) if poster else None,
147
- title = self.clean_title(title),
148
- description = description,
149
- tags = tags,
150
- rating = rating,
151
- year = year,
152
- actors = actors,
153
- duration = duration,
154
- episodes = unique_episodes
107
+ url=url, poster=self.fix_url(poster) if poster else None, title=title, description=description,
108
+ tags=tags, rating=rating, year=year, actors=actors, duration=duration, episodes=unique
155
109
  )
156
110
 
157
111
  return MovieInfo(
158
- url = url,
159
- poster = self.fix_url(poster) if poster else None,
160
- title = self.clean_title(title),
161
- description = description,
162
- tags = tags,
163
- rating = rating,
164
- year = year,
165
- actors = actors,
166
- duration = duration
112
+ url=url, poster=self.fix_url(poster) if poster else None, title=title, description=description,
113
+ tags=tags, rating=rating, year=year, actors=actors, duration=duration
167
114
  )
168
115
 
169
116
  async def load_links(self, url: str) -> list[ExtractResult]:
@@ -81,23 +81,15 @@ class FilmModu(PluginBase):
81
81
  istek = await self.httpx.get(url)
82
82
  secici = HTMLHelper(istek.text)
83
83
 
84
- org_title = secici.select_text("div.titles h1") or ""
85
- alt_title = secici.select_text("div.titles h2") or ""
86
- title = f"{org_title} - {alt_title}" if alt_title else org_title
87
-
88
- poster = secici.select_attr("img.img-responsive", "src") if secici.select_attr("img.img-responsive", "src") else None
89
-
90
- description = secici.select_text("p[itemprop='description']") or None
91
-
92
- tags = secici.select_all_text("a[href*='film-tur/']")
93
-
94
- year = secici.select_text("span[itemprop='dateCreated']") or None
95
-
96
- actors = []
97
- for a in secici.select("a[itemprop='actor']"):
98
- name = secici.select_text("span", a)
99
- if name:
100
- actors.append(name)
84
+ org_title = secici.select_text("div.titles h1")
85
+ alt_title = secici.select_text("div.titles h2")
86
+ title = f"{org_title} - {alt_title}" if alt_title else (org_title or "")
87
+ poster = secici.select_poster("img.img-responsive")
88
+ description = secici.select_text("p[itemprop='description']")
89
+ tags = secici.select_texts("a[href*='film-tur/']")
90
+ rating = secici.meta_value("IMDB")
91
+ year = secici.extract_year("span[itemprop='dateCreated']")
92
+ actors = secici.select_texts("a[itemprop='actor'] span")
101
93
 
102
94
  return MovieInfo(
103
95
  url = url,
@@ -105,7 +97,8 @@ class FilmModu(PluginBase):
105
97
  title = title,
106
98
  description = description,
107
99
  tags = tags,
108
- year = year,
100
+ rating = rating,
101
+ year = str(year) if year else None,
109
102
  actors = actors,
110
103
  )
111
104
 
@@ -81,52 +81,26 @@ class Filmatek(PluginBase):
81
81
  return results
82
82
 
83
83
  async def load_item(self, url: str) -> MovieInfo:
84
- istek = await self.httpx.get(url)
84
+ istek = await self.httpx.get(url)
85
85
  helper = HTMLHelper(istek.text)
86
86
 
87
- title = helper.select_text("div.data h1, h1") or "Bilinmiyor"
88
-
89
- poster_el = helper.select_first("div.poster img")
90
- poster = self.fix_url(poster_el.attrs.get("src")) if poster_el else None
91
- if not poster:
92
- poster = helper.select_attr("meta[property='og:image']", "content")
93
-
94
- description = helper.select_text("div.wp-content p")
95
- if not description:
96
- description = helper.select_attr("meta[property='og:description']", "content")
97
-
98
- year_text = helper.select_text("span.date")
99
- year = year_text.strip()[-4:] if year_text else None
100
-
101
- # Rating extraction updated
102
- rating = helper.select_text("span.dt_rating_vgs") or helper.select_text("span.dt_rating_vmanual")
103
-
104
- # Duration extraction
105
- duration = None
106
- duration_text = helper.select_text("span.runtime")
107
- if duration_text:
108
- # "80 Dak." -> "80"
109
- duration = duration_text.split()[0]
110
-
111
- tags = helper.select_all_text("div.sgeneros a")
112
-
113
- # Actors
114
- actors_list = []
115
- actor_els = helper.select("div.person")
116
- for el in actor_els:
117
- name = helper.select_text("div.name a", el)
118
- if name:
119
- actors_list.append(name.strip())
120
- actors = ", ".join(actors_list) if actors_list else None
87
+ title = self.clean_title(helper.select_text("div.data h1, h1"))
88
+ poster = helper.select_poster("div.poster img") or helper.select_attr("meta[property='og:image']", "content")
89
+ description = helper.select_text("div.wp-content p") or helper.select_attr("meta[property='og:description']", "content")
90
+ year = helper.extract_year("span.date")
91
+ rating = helper.select_text("span.dt_rating_vgs") or helper.select_text("span.dt_rating_vmanual")
92
+ duration = helper.regex_first(r"(\d+)", helper.select_text("span.runtime"))
93
+ tags = helper.select_texts("div.sgeneros a")
94
+ actors = helper.select_texts("div.person div.name a")
121
95
 
122
96
  return MovieInfo(
123
97
  url = url,
124
- title = title,
98
+ title = title or "Bilinmiyor",
125
99
  description = description,
126
- poster = poster,
127
- year = year,
100
+ poster = self.fix_url(poster) if poster else None,
101
+ year = str(year) if year else None,
128
102
  rating = rating,
129
- duration = duration,
103
+ duration = int(duration) if duration else None,
130
104
  tags = tags,
131
105
  actors = actors
132
106
  )
@@ -98,151 +98,51 @@ class Full4kizle(PluginBase):
98
98
  async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
99
99
  istek = await self.httpx.get(url)
100
100
  helper = HTMLHelper(istek.text)
101
-
102
- title_raw = helper.select_text("h1") or "Bilinmiyor"
103
- title = re.sub(r"(?i)izle", "", title_raw).strip()
104
-
105
- poster_el = helper.select_first(".poster img")
106
- poster = self.fix_url(poster_el.attrs.get("src")) if poster_el else None
107
-
108
- description = helper.select_text(".excerpt p")
109
-
110
- # Robust metadata extraction using Regex
111
-
112
- # Initialize year first
113
- year = None
114
-
115
- # Try .release first (legacy) or directly regex
116
- rel_text = helper.select_text(".release")
117
- if rel_text:
118
- m_y = re.search(r"(\d{4})", rel_text)
119
- if m_y: year = m_y.group(1)
120
-
121
- # Year fallbacks
122
- if not year:
123
- # Try finding year in text like "Yapım: 2024" or just isolated year in release date
124
- m_year = helper.regex_first(r"Yapım:\s*(\d{4})") or helper.regex_first(r"Yıl:\s*(\d{4})")
125
- if m_year:
126
- year = m_year
127
-
128
- # Rating
129
- rating_text = helper.select_text(".imdb-rating")
130
- if rating_text:
131
- rating = rating_text.replace("IMDB Puanı", "").strip()
132
- else:
133
- rating = helper.regex_first(r"IMDB\s*:\s*([\d\.]+)")
134
-
135
- # Duration
136
- duration = None
137
- duration_val = helper.regex_first(r"Süre:\s*(\d+)")
138
- if duration_val:
139
- duration = int(duration_val)
140
101
 
141
- # Actors - Extract from actor links
142
- actors = None
143
- actors_list = []
144
-
145
- # Site uses: <a href=".../oyuncular/...">Actor Name</a>
146
- actor_els = helper.select("a[href*='/oyuncular/']")
147
- if actor_els:
148
- actors_list = [el.text(strip=True) for el in actor_els if el.text(strip=True)]
149
-
150
- # Fallback: Try .cast-list selector
151
- if not actors_list:
152
- actor_els = helper.select(".cast-list .actor-name, .cast-list a")
153
- if actor_els:
154
- actors_list = [el.text(strip=True) for el in actor_els if el.text(strip=True)]
155
-
156
- if actors_list:
157
- actors = ", ".join(actors_list)
158
-
159
-
160
- # Tags (Genres) - Extract from genre links
161
- tags = None
162
- tags_list = []
163
-
164
- # Site uses: <a href=".../tur/...">Genre Name</a> or <a href=".../Kategori/tur/...">
165
- tag_els = helper.select("a[href*='/tur/'], a[href*='/Kategori/tur/']")
166
- if tag_els:
167
- tags_list = [el.text(strip=True) for el in tag_els if el.text(strip=True)]
168
-
169
- # Fallback: Try .genres selector
170
- if not tags_list:
171
- tag_els = helper.select(".genres a, .genre a")
172
- if tag_els:
173
- tags_list = [el.text(strip=True) for el in tag_els if el.text(strip=True)]
174
-
175
- # Remove duplicates while preserving order
176
- if tags_list:
177
- seen = set()
178
- unique_tags = []
179
- for tag in tags_list:
180
- if tag not in seen:
181
- seen.add(tag)
182
- unique_tags.append(tag)
183
- tags = unique_tags if unique_tags else None
102
+ title = self.clean_title(helper.select_text("h1"))
103
+ poster = helper.select_poster(".poster img")
104
+ description = helper.select_text(".excerpt p")
105
+ year = helper.extract_year(".release", ".movie-info")
106
+ rating = helper.regex_first(r"([\d\.]+)", helper.select_text(".imdb-rating"))
107
+ duration = int(helper.regex_first(r"(\d+)", helper.select_text(".movie-info")) or 0)
108
+ tags = helper.select_texts("a[href*='/tur/'], a[href*='/Kategori/tur/']")
109
+ actors = helper.select_texts("a[href*='/oyuncular/']") or helper.select_texts(".cast-list .actor-name, .cast-list a")
184
110
 
185
-
186
- # Check for Episodes to decide if Series or Movie
111
+ # Bölüm linklerini kontrol et
187
112
  ep_elements = helper.select(".parts-middle a, .parts-middle .part.active")
188
-
113
+
189
114
  if not ep_elements:
190
- # Movie
191
115
  return MovieInfo(
192
116
  url = url,
193
- title = title,
117
+ title = title or "Bilinmiyor",
194
118
  description = description,
195
- poster = poster,
196
- year = year,
119
+ poster = self.fix_url(poster) if poster else None,
120
+ year = str(year) if year else None,
197
121
  rating = rating,
198
122
  duration = duration,
199
123
  tags = tags,
200
124
  actors = actors
201
125
  )
202
- else:
203
- # Series
204
- episodes = []
205
- for i, el in enumerate(ep_elements):
206
- ep_name = helper.select_text(".part-name", el) or f"Bölüm {i+1}"
207
- ep_href = el.attrs.get("href")
208
- if not ep_href:
209
- ep_href = url # Current page if href is empty/active?
210
- ep_href = self.fix_url(ep_href)
211
-
212
- # Parse season/episode from name if possible
213
- # Kotlin: find digit for season, substringAfter("Sezon") digit for episode
214
- season = 1
215
- episode = i + 1
216
-
217
- # Simple heuristic similar to Kotlin
218
- # "1. Sezon 5. Bölüm"
219
- s_match = re.search(r"(\d+)\.\s*Sezon", ep_name)
220
- e_match = re.search(r"(\d+)\.\s*Bölüm", ep_name)
221
-
222
- if s_match:
223
- season = int(s_match.group(1))
224
- if e_match:
225
- episode = int(e_match.group(1))
226
-
227
- episodes.append(Episode(
228
- season = season,
229
- episode = episode,
230
- title = ep_name,
231
- url = ep_href
232
- ))
233
-
234
- return SeriesInfo(
235
- url = url,
236
- title = title,
237
- description = description,
238
- poster = poster,
239
- year = year,
240
- rating = rating,
241
- duration = duration,
242
- tags = tags,
243
- actors = actors,
244
- episodes = episodes
245
- )
126
+
127
+ episodes = []
128
+ for i, el in enumerate(ep_elements):
129
+ name = helper.select_text(".part-name", el) or f"Bölüm {i+1}"
130
+ href = el.attrs.get("href") or url
131
+ s, e = helper.extract_season_episode(name)
132
+ episodes.append(Episode(season=s or 1, episode=e or (i + 1), title=name, url=self.fix_url(href)))
133
+
134
+ return SeriesInfo(
135
+ url = url,
136
+ title = title or "Bilinmiyor",
137
+ description = description,
138
+ poster = self.fix_url(poster) if poster else None,
139
+ year = str(year) if year else None,
140
+ rating = rating,
141
+ duration = duration,
142
+ tags = tags,
143
+ actors = actors,
144
+ episodes = episodes
145
+ )
246
146
 
247
147
  async def load_links(self, url: str) -> list[ExtractResult]:
248
148
  istek = await self.httpx.get(url)