KekikStream 2.2.2__py3-none-any.whl → 2.2.8__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 (35) hide show
  1. KekikStream/Extractors/CloseLoad.py +47 -11
  2. KekikStream/Extractors/Filemoon.py +7 -6
  3. KekikStream/Extractors/MolyStream.py +6 -5
  4. KekikStream/Extractors/PlayerFilmIzle.py +6 -2
  5. KekikStream/Extractors/VidHide.py +0 -1
  6. KekikStream/Extractors/VidMoly.py +17 -9
  7. KekikStream/Plugins/BelgeselX.py +39 -20
  8. KekikStream/Plugins/DiziBox.py +115 -59
  9. KekikStream/Plugins/DiziPal.py +87 -40
  10. KekikStream/Plugins/DiziYou.py +105 -64
  11. KekikStream/Plugins/Dizilla.py +58 -29
  12. KekikStream/Plugins/FilmBip.py +60 -31
  13. KekikStream/Plugins/FilmMakinesi.py +75 -51
  14. KekikStream/Plugins/FilmModu.py +73 -36
  15. KekikStream/Plugins/FullHDFilm.py +82 -48
  16. KekikStream/Plugins/FullHDFilmizlesene.py +94 -39
  17. KekikStream/Plugins/HDFilmCehennemi.py +109 -88
  18. KekikStream/Plugins/JetFilmizle.py +78 -50
  19. KekikStream/Plugins/KultFilmler.py +64 -34
  20. KekikStream/Plugins/RoketDizi.py +43 -26
  21. KekikStream/Plugins/SelcukFlix.py +27 -14
  22. KekikStream/Plugins/SetFilmIzle.py +74 -43
  23. KekikStream/Plugins/SezonlukDizi.py +102 -46
  24. KekikStream/Plugins/Sinefy.py +130 -101
  25. KekikStream/Plugins/SinemaCX.py +82 -37
  26. KekikStream/Plugins/Sinezy.py +61 -47
  27. KekikStream/Plugins/SuperFilmGeldi.py +72 -36
  28. KekikStream/Plugins/UgurFilm.py +72 -34
  29. KekikStream/requirements.txt +1 -1
  30. {kekikstream-2.2.2.dist-info → kekikstream-2.2.8.dist-info}/METADATA +39 -32
  31. {kekikstream-2.2.2.dist-info → kekikstream-2.2.8.dist-info}/RECORD +35 -35
  32. {kekikstream-2.2.2.dist-info → kekikstream-2.2.8.dist-info}/WHEEL +0 -0
  33. {kekikstream-2.2.2.dist-info → kekikstream-2.2.8.dist-info}/entry_points.txt +0 -0
  34. {kekikstream-2.2.2.dist-info → kekikstream-2.2.8.dist-info}/licenses/LICENSE +0 -0
  35. {kekikstream-2.2.2.dist-info → kekikstream-2.2.8.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,13 @@
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, Subtitle, ExtractResult
4
- from parsel import Selector
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, Subtitle, ExtractResult
4
+ from selectolax.parser import HTMLParser
5
5
  import re
6
6
 
7
7
  class DiziPal(PluginBase):
8
8
  name = "DiziPal"
9
9
  language = "tr"
10
- main_url = "https://dizipal1223.com"
10
+ main_url = "https://dizipal1224.com"
11
11
  favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
12
12
  description = "dizipal güncel, dizipal yeni ve gerçek adresi. dizipal en yeni dizi ve filmleri güvenli ve hızlı şekilde sunar."
13
13
 
@@ -27,19 +27,24 @@ class DiziPal(PluginBase):
27
27
 
28
28
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
29
29
  istek = await self.httpx.get(url)
30
- secici = Selector(istek.text)
30
+ secici = HTMLParser(istek.text)
31
31
 
32
32
  results = []
33
33
 
34
34
  if "/son-bolumler" in url:
35
35
  for veri in secici.css("div.episode-item"):
36
- name = veri.css("div.name::text").get()
37
- episode = veri.css("div.episode::text").get()
38
- href = veri.css("a::attr(href)").get()
39
- poster = veri.css("img::attr(src)").get()
36
+ name_el = veri.css_first("div.name")
37
+ episode_el = veri.css_first("div.episode")
38
+ link_el = veri.css_first("a")
39
+ img_el = veri.css_first("img")
40
+
41
+ name = name_el.text(strip=True) if name_el else None
42
+ episode = episode_el.text(strip=True) if episode_el else None
43
+ href = link_el.attrs.get("href") if link_el else None
44
+ poster = img_el.attrs.get("src") if img_el else None
40
45
 
41
46
  if name and href:
42
- ep_text = episode.strip().replace(". Sezon ", "x").replace(". Bölüm", "") if episode else ""
47
+ ep_text = episode.replace(". Sezon ", "x").replace(". Bölüm", "") if episode else ""
43
48
  title = f"{name} {ep_text}"
44
49
  # Son bölümler linkini dizi sayfasına çevir
45
50
  dizi_url = href.split("/sezon")[0] if "/sezon" in href else href
@@ -52,9 +57,13 @@ class DiziPal(PluginBase):
52
57
  ))
53
58
  else:
54
59
  for veri in secici.css("article.type2 ul li"):
55
- title = veri.css("span.title::text").get()
56
- href = veri.css("a::attr(href)").get()
57
- poster = veri.css("img::attr(src)").get()
60
+ title_el = veri.css_first("span.title")
61
+ link_el = veri.css_first("a")
62
+ img_el = veri.css_first("img")
63
+
64
+ title = title_el.text(strip=True) if title_el else None
65
+ href = link_el.attrs.get("href") if link_el else None
66
+ poster = img_el.attrs.get("src") if img_el else None
58
67
 
59
68
  if title and href:
60
69
  results.append(MainPageResult(
@@ -104,6 +113,18 @@ class DiziPal(PluginBase):
104
113
 
105
114
  return results
106
115
 
116
+ def _find_sibling_text(self, secici: HTMLParser, label_text: str) -> str | None:
117
+ """Bir label'ın kardeş div'inden text çıkarır (xpath yerine)"""
118
+ for div in secici.css("div"):
119
+ if div.text(strip=True) == label_text:
120
+ # Sonraki kardeş elementi bul
121
+ next_sibling = div.next
122
+ while next_sibling:
123
+ if hasattr(next_sibling, 'text') and next_sibling.text(strip=True):
124
+ return next_sibling.text(strip=True)
125
+ next_sibling = next_sibling.next if hasattr(next_sibling, 'next') else None
126
+ return None
127
+
107
128
  async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
108
129
  # Reset headers to get HTML response
109
130
  self.httpx.headers.update({
@@ -112,28 +133,51 @@ class DiziPal(PluginBase):
112
133
  self.httpx.headers.pop("X-Requested-With", None)
113
134
 
114
135
  istek = await self.httpx.get(url)
115
- secici = Selector(text=istek.text, type="html")
136
+ secici = HTMLParser(istek.text)
137
+ html_text = istek.text
138
+
139
+ og_image = secici.css_first("meta[property='og:image']")
140
+ poster = self.fix_url(og_image.attrs.get("content")) if og_image else None
141
+
142
+ # XPath yerine regex ile HTML'den çıkarma
143
+ year = None
144
+ year_match = re.search(r'Yapım Yılı.*?<div[^>]*>(\d{4})</div>', html_text, re.DOTALL | re.IGNORECASE)
145
+ if year_match:
146
+ year = year_match.group(1)
116
147
 
117
- poster = self.fix_url(secici.css("meta[property='og:image']::attr(content)").get())
118
- year = secici.xpath("//div[text()='Yapım Yılı']//following-sibling::div/text()").get()
119
- description = secici.css("div.summary p::text").get()
120
- rating = secici.xpath("//div[text()='IMDB Puanı']//following-sibling::div/text()").get()
121
- tags_raw = secici.xpath("//div[text()='Türler']//following-sibling::div/text()").get()
122
- tags = [t.strip() for t in tags_raw.split() if t.strip()] if tags_raw else None
148
+ desc_el = secici.css_first("div.summary p")
149
+ description = desc_el.text(strip=True) if desc_el else None
123
150
 
124
- dur_text = secici.xpath("//div[text()='Ortalama Süre']//following-sibling::div/text()").get()
125
- dur_match = re.search(r"(\d+)", dur_text or "")
126
- duration = int(dur_match[1]) if dur_match else None
151
+ rating = None
152
+ rating_match = re.search(r'IMDB Puanı.*?<div[^>]*>([0-9.]+)</div>', html_text, re.DOTALL | re.IGNORECASE)
153
+ if rating_match:
154
+ rating = rating_match.group(1)
155
+
156
+ tags = None
157
+ tags_match = re.search(r'Türler.*?<div[^>]*>([^<]+)</div>', html_text, re.DOTALL | re.IGNORECASE)
158
+ if tags_match:
159
+ tags_raw = tags_match.group(1)
160
+ tags = [t.strip() for t in tags_raw.split() if t.strip()]
161
+
162
+ duration = None
163
+ dur_match = re.search(r'Ortalama Süre.*?<div[^>]*>(\d+)', html_text, re.DOTALL | re.IGNORECASE)
164
+ if dur_match:
165
+ duration = int(dur_match.group(1))
127
166
 
128
167
  if "/dizi/" in url:
129
- title = secici.css("div.cover h5::text").get()
168
+ title_el = secici.css_first("div.cover h5")
169
+ title = title_el.text(strip=True) if title_el else None
130
170
 
131
171
  episodes = []
132
172
  for ep in secici.css("div.episode-item"):
133
- ep_name = ep.css("div.name::text").get()
134
- ep_href = ep.css("a::attr(href)").get()
135
- ep_text = ep.css("div.episode::text").get() or ""
136
- ep_parts = ep_text.strip().split(" ")
173
+ ep_name_el = ep.css_first("div.name")
174
+ ep_link_el = ep.css_first("a")
175
+ ep_episode_el = ep.css_first("div.episode")
176
+
177
+ ep_name = ep_name_el.text(strip=True) if ep_name_el else None
178
+ ep_href = ep_link_el.attrs.get("href") if ep_link_el else None
179
+ ep_text = ep_episode_el.text(strip=True) if ep_episode_el else ""
180
+ ep_parts = ep_text.split(" ")
137
181
 
138
182
  ep_season = None
139
183
  ep_episode = None
@@ -148,7 +192,7 @@ class DiziPal(PluginBase):
148
192
  episodes.append(Episode(
149
193
  season = ep_season,
150
194
  episode = ep_episode,
151
- title = ep_name.strip(),
195
+ title = ep_name,
152
196
  url = self.fix_url(ep_href),
153
197
  ))
154
198
 
@@ -156,24 +200,26 @@ class DiziPal(PluginBase):
156
200
  url = url,
157
201
  poster = poster,
158
202
  title = title,
159
- description = description.strip() if description else None,
203
+ description = description,
160
204
  tags = tags,
161
- rating = rating.strip() if rating else None,
162
- year = year.strip() if year else None,
205
+ rating = rating,
206
+ year = year,
163
207
  duration = duration,
164
208
  episodes = episodes if episodes else None,
165
209
  )
166
210
  else:
167
- title = secici.xpath("//div[@class='g-title'][2]/div/text()").get()
211
+ # Film için title - g-title div'lerinin 2. olanı
212
+ g_titles = secici.css("div.g-title div")
213
+ title = g_titles[1].text(strip=True) if len(g_titles) >= 2 else None
168
214
 
169
215
  return MovieInfo(
170
216
  url = url,
171
217
  poster = poster,
172
- title = title.strip() if title else None,
173
- description = description.strip() if description else None,
218
+ title = title,
219
+ description = description,
174
220
  tags = tags,
175
- rating = rating.strip() if rating else None,
176
- year = year.strip() if year else None,
221
+ rating = rating,
222
+ year = year,
177
223
  duration = duration,
178
224
  )
179
225
 
@@ -185,12 +231,13 @@ class DiziPal(PluginBase):
185
231
  self.httpx.headers.pop("X-Requested-With", None)
186
232
 
187
233
  istek = await self.httpx.get(url)
188
- secici = Selector(istek.text)
234
+ secici = HTMLParser(istek.text)
189
235
 
190
- iframe = secici.css(".series-player-container iframe::attr(src)").get()
191
- if not iframe:
192
- iframe = secici.css("div#vast_new iframe::attr(src)").get()
236
+ iframe_el = secici.css_first(".series-player-container iframe")
237
+ if not iframe_el:
238
+ iframe_el = secici.css_first("div#vast_new iframe")
193
239
 
240
+ iframe = iframe_el.attrs.get("src") if iframe_el else None
194
241
  if not iframe:
195
242
  return []
196
243
 
@@ -1,7 +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, Subtitle, ExtractResult
4
- from parsel import Selector
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, Subtitle, ExtractResult
4
+ from selectolax.parser import HTMLParser
5
5
  import re
6
6
 
7
7
  class DiziYou(PluginBase):
@@ -31,38 +31,57 @@ class DiziYou(PluginBase):
31
31
 
32
32
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
33
33
  istek = await self.httpx.get(f"{url.replace('SAYFA', str(page))}")
34
- secici = Selector(istek.text)
35
-
36
- return [
37
- MainPageResult(
38
- category = category,
39
- title = veri.css("div#categorytitle a::text").get(),
40
- url = self.fix_url(veri.css("div#categorytitle a::attr(href)").get()),
41
- poster = self.fix_url(veri.css("img::attr(src)").get()),
42
- )
43
- for veri in secici.css("div.single-item")
44
- ]
34
+ secici = HTMLParser(istek.text)
35
+
36
+ results = []
37
+ for veri in secici.css("div.single-item"):
38
+ title_el = veri.css_first("div#categorytitle a")
39
+ img_el = veri.css_first("img")
40
+
41
+ title = title_el.text(strip=True) if title_el else None
42
+ href = title_el.attrs.get("href") if title_el else None
43
+ poster = img_el.attrs.get("src") if img_el else None
44
+
45
+ if title and href:
46
+ results.append(MainPageResult(
47
+ category = category,
48
+ title = title,
49
+ url = self.fix_url(href),
50
+ poster = self.fix_url(poster) if poster else None,
51
+ ))
52
+
53
+ return results
45
54
 
46
55
  async def search(self, query: str) -> list[SearchResult]:
47
56
  istek = await self.httpx.get(f"{self.main_url}/?s={query}")
48
- secici = Selector(istek.text)
57
+ secici = HTMLParser(istek.text)
58
+
59
+ results = []
60
+ for afis in secici.css("div.incontent div#list-series"):
61
+ title_el = afis.css_first("div#categorytitle a")
62
+ img_el = afis.css_first("img")
63
+
64
+ title = title_el.text(strip=True) if title_el else None
65
+ href = title_el.attrs.get("href") if title_el else None
66
+ poster = (img_el.attrs.get("src") or img_el.attrs.get("data-src")) if img_el else None
67
+
68
+ if title and href:
69
+ results.append(SearchResult(
70
+ title = title,
71
+ url = self.fix_url(href),
72
+ poster = self.fix_url(poster) if poster else None,
73
+ ))
49
74
 
50
- return [
51
- SearchResult(
52
- title = afis.css("div#categorytitle a::text").get().strip(),
53
- url = self.fix_url(afis.css("div#categorytitle a::attr(href)").get()),
54
- poster = self.fix_url(afis.css("img::attr(src)").get() or afis.css("img::attr(data-src)").get())
55
- )
56
- for afis in secici.css("div.incontent div#list-series")
57
- ]
75
+ return results
58
76
 
59
77
  async def load_item(self, url: str) -> SeriesInfo:
60
78
  istek = await self.httpx.get(url)
61
- secici = Selector(istek.text)
79
+ secici = HTMLParser(istek.text)
80
+ html_text = istek.text
62
81
 
63
82
  # Title - div.title h1 içinde
64
- title_raw = secici.css("div.title h1::text").get()
65
- title = title_raw.strip() if title_raw else ""
83
+ title_el = secici.css_first("div.title h1")
84
+ title = title_el.text(strip=True) if title_el else ""
66
85
 
67
86
  # Fallback: Eğer title boşsa URL'den çıkar (telif kısıtlaması olan sayfalar için)
68
87
  if not title:
@@ -71,45 +90,66 @@ class DiziYou(PluginBase):
71
90
  title = slug.replace('-', ' ').title()
72
91
 
73
92
  # Poster
74
- poster_raw = secici.css("div.category_image img::attr(src)").get()
75
- poster = self.fix_url(poster_raw) if poster_raw else ""
76
- year = secici.xpath("//span[contains(., 'Yapım Yılı')]/following-sibling::text()[1]").get()
77
- description = secici.css("div.diziyou_desc::text").get()
78
- if description:
79
- description = description.strip()
80
- tags = secici.css("div.genres a::text").getall()
81
- rating = secici.xpath("//span[contains(., 'IMDB')]/following-sibling::text()[1]").get()
82
- _actors = secici.xpath("//span[contains(., 'Oyuncular')]/following-sibling::text()[1]").get()
83
- actors = [actor.strip() for actor in _actors.split(",")] if _actors else []
84
-
85
- episodes = []
86
- # Episodes - bolumust her bölüm için bir <a> içinde
87
- # :has() parsel'de çalışmıyor, XPath kullanıyoruz
88
- for link in secici.xpath('//a[div[@class="bolumust"]]'):
89
- ep_name_raw = link.css("div.baslik::text").get()
90
- if not ep_name_raw:
93
+ poster_el = secici.css_first("div.category_image img")
94
+ poster = self.fix_url(poster_el.attrs.get("src")) if poster_el else ""
95
+
96
+ # Year - regex ile çıkarma (xpath yerine)
97
+ year = None
98
+ year_match = re.search(r"Yapım Yılı.*?(\d{4})", html_text, re.DOTALL | re.IGNORECASE)
99
+ if year_match:
100
+ year = year_match.group(1)
101
+
102
+ desc_el = secici.css_first("div.diziyou_desc")
103
+ description = desc_el.text(strip=True) if desc_el else None
104
+
105
+ tags = [a.text(strip=True) for a in secici.css("div.genres a") if a.text(strip=True)]
106
+
107
+ # Rating - regex ile
108
+ rating = None
109
+ rating_match = re.search(r"IMDB.*?([0-9.]+)", html_text, re.DOTALL | re.IGNORECASE)
110
+ if rating_match:
111
+ rating = rating_match.group(1)
112
+
113
+ # Actors - regex ile
114
+ actors = []
115
+ actors_match = re.search(r"Oyuncular.*?</span>([^<]+)", html_text, re.DOTALL | re.IGNORECASE)
116
+ if actors_match:
117
+ actors = [actor.strip() for actor in actors_match.group(1).split(",") if actor.strip()]
118
+
119
+ episodes = []
120
+ # Episodes - bolumust div içeren a linklerini bul
121
+ for link in secici.css("a"):
122
+ bolumust = link.css_first("div.bolumust")
123
+ if not bolumust:
91
124
  continue
92
- ep_name = ep_name_raw.strip()
93
-
94
- ep_href = self.fix_url(link.css("::attr(href)").get())
125
+
126
+ baslik_el = link.css_first("div.baslik")
127
+ if not baslik_el:
128
+ continue
129
+
130
+ ep_name = baslik_el.text(strip=True)
131
+ ep_href = link.attrs.get("href")
95
132
  if not ep_href:
96
133
  continue
97
134
 
98
135
  # Bölüm ismi varsa al
99
- ep_name_raw_clean = link.css("div.bolumismi::text").get()
100
- ep_name_clean = ep_name_raw_clean.strip().replace("(", "").replace(")", "").strip() if ep_name_raw_clean else ep_name
136
+ bolumismi_el = link.css_first("div.bolumismi")
137
+ ep_name_clean = bolumismi_el.text(strip=True).replace("(", "").replace(")", "").strip() if bolumismi_el else ep_name
101
138
 
102
- ep_episode = re.search(r"(\d+)\. Bölüm", ep_name)[1]
103
- ep_season = re.search(r"(\d+)\. Sezon", ep_name)[1]
139
+ ep_episode_match = re.search(r"(\d+)\. Bölüm", ep_name)
140
+ ep_season_match = re.search(r"(\d+)\. Sezon", ep_name)
104
141
 
105
- episode = Episode(
106
- season = ep_season,
107
- episode = ep_episode,
108
- title = ep_name_clean,
109
- url = ep_href,
110
- )
142
+ ep_episode = ep_episode_match.group(1) if ep_episode_match else None
143
+ ep_season = ep_season_match.group(1) if ep_season_match else None
111
144
 
112
- episodes.append(episode)
145
+ if ep_episode and ep_season:
146
+ episode = Episode(
147
+ season = ep_season,
148
+ episode = ep_episode,
149
+ title = ep_name_clean,
150
+ url = self.fix_url(ep_href),
151
+ )
152
+ episodes.append(episode)
113
153
 
114
154
  return SeriesInfo(
115
155
  url = url,
@@ -125,17 +165,18 @@ class DiziYou(PluginBase):
125
165
 
126
166
  async def load_links(self, url: str) -> list[ExtractResult]:
127
167
  istek = await self.httpx.get(url)
128
- secici = Selector(istek.text)
168
+ secici = HTMLParser(istek.text)
129
169
 
130
170
  # Title ve episode name - None kontrolü ekle
131
- item_title_raw = secici.css("div.title h1::text").get()
132
- item_title = item_title_raw.strip() if item_title_raw else ""
171
+ title_el = secici.css_first("div.title h1")
172
+ item_title = title_el.text(strip=True) if title_el else ""
133
173
 
134
- ep_name_raw = secici.css("div#bolum-ismi::text").get()
135
- ep_name = ep_name_raw.strip() if ep_name_raw else ""
174
+ ep_name_el = secici.css_first("div#bolum-ismi")
175
+ ep_name = ep_name_el.text(strip=True) if ep_name_el else ""
136
176
 
137
177
  # Player src'den item_id çıkar
138
- player_src = secici.css("iframe#diziyouPlayer::attr(src)").get()
178
+ player_el = secici.css_first("iframe#diziyouPlayer")
179
+ player_src = player_el.attrs.get("src") if player_el else None
139
180
  if not player_src:
140
181
  return [] # Player bulunamadıysa boş liste döndür
141
182
 
@@ -145,8 +186,8 @@ class DiziYou(PluginBase):
145
186
  stream_urls = []
146
187
 
147
188
  for secenek in secici.css("span.diziyouOption"):
148
- opt_id = secenek.css("::attr(id)").get()
149
- op_name = secenek.css("::text").get()
189
+ opt_id = secenek.attrs.get("id")
190
+ op_name = secenek.text(strip=True)
150
191
 
151
192
  match opt_id:
152
193
  case "turkceAltyazili":
@@ -1,16 +1,16 @@
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
4
- from parsel import Selector
5
- from json import loads
6
- from urllib.parse import urlparse, urlunparse
7
- from Crypto.Cipher import AES
8
- from base64 import b64decode
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult
4
+ from selectolax.parser import HTMLParser
5
+ from json import loads
6
+ from urllib.parse import urlparse, urlunparse
7
+ from Crypto.Cipher import AES
8
+ from base64 import b64decode
9
9
 
10
10
  class Dizilla(PluginBase):
11
11
  name = "Dizilla"
12
12
  language = "tr"
13
- main_url = "https://dizilla40.com"
13
+ main_url = "https://dizilla.to"
14
14
  favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
15
15
  description = "1080p yabancı dizi izle. Türkçe altyazılı veya dublaj seçenekleriyle 1080p çözünürlükte yabancı dizilere anında ulaş. Popüler dizileri kesintisiz izle."
16
16
 
@@ -51,30 +51,45 @@ class Dizilla(PluginBase):
51
51
  ])
52
52
  else:
53
53
  istek = await self.httpx.get(url.replace("SAYFA", str(page)))
54
- secici = Selector(istek.text)
54
+ secici = HTMLParser(istek.text)
55
55
 
56
56
  for veri in secici.css("div.tab-content > div.grid a"):
57
- name = veri.css("h2::text").get()
58
- ep_name = veri.xpath("normalize-space(//div[contains(@class, 'opacity-80')])").get()
57
+ h2_el = veri.css_first("h2")
58
+ name = h2_el.text(strip=True) if h2_el else None
59
+
60
+ # opacity-80 div'den episode bilgisi - normalize-space yerine doğrudan text
61
+ opacity_el = veri.css_first("div[class*='opacity-80']")
62
+ ep_name = opacity_el.text(strip=True) if opacity_el else None
59
63
  if not ep_name:
60
64
  continue
61
65
 
62
66
  ep_name = ep_name.replace(". Sezon", "x").replace(". Bölüm", "").replace("x ", "x")
63
67
  title = f"{name} - {ep_name}"
64
68
 
65
- ep_req = await self.httpx.get(self.fix_url(veri.css("::attr(href)").get()))
66
- ep_secici = Selector(ep_req.text)
67
- href = self.fix_url(ep_secici.css("nav li:nth-of-type(3) a::attr(href)").get())
68
- poster = self.fix_url(ep_secici.css("img.imgt::attr(src)").get())
69
-
70
- ana_sayfa.append(
71
- MainPageResult(
72
- category = category,
73
- title = title,
74
- url = href,
75
- poster = poster
69
+ href = veri.attrs.get("href")
70
+ ep_req = await self.httpx.get(self.fix_url(href))
71
+ ep_secici = HTMLParser(ep_req.text)
72
+
73
+ # nav li'leri alıp 3. elemana erişme (nth-of-type yerine)
74
+ nav_lis = ep_secici.css("nav li")
75
+ if len(nav_lis) >= 3:
76
+ link_el = nav_lis[2].css_first("a")
77
+ href = link_el.attrs.get("href") if link_el else None
78
+ else:
79
+ href = None
80
+
81
+ poster_el = ep_secici.css_first("img.imgt")
82
+ poster = poster_el.attrs.get("src") if poster_el else None
83
+
84
+ if href:
85
+ ana_sayfa.append(
86
+ MainPageResult(
87
+ category = category,
88
+ title = title,
89
+ url = self.fix_url(href),
90
+ poster = self.fix_url(poster) if poster else None
91
+ )
76
92
  )
77
- )
78
93
 
79
94
  return ana_sayfa
80
95
 
@@ -125,8 +140,16 @@ class Dizilla(PluginBase):
125
140
 
126
141
  async def load_item(self, url: str) -> SeriesInfo:
127
142
  istek = await self.httpx.get(url)
128
- secici = Selector(istek.text)
129
- veri = loads(secici.xpath("//script[@type='application/ld+json']/text()").getall()[-1])
143
+ secici = HTMLParser(istek.text)
144
+
145
+ # application/ld+json script'lerini al
146
+ ld_json_scripts = secici.css("script[type='application/ld+json']")
147
+ if not ld_json_scripts:
148
+ return None
149
+
150
+ # Son script'i al
151
+ last_script = ld_json_scripts[-1]
152
+ veri = loads(last_script.text(strip=True))
130
153
 
131
154
  title = veri.get("name")
132
155
  if alt_title := veri.get("alternateName"):
@@ -137,7 +160,8 @@ class Dizilla(PluginBase):
137
160
  year = veri.get("datePublished").split("-")[0]
138
161
 
139
162
  # Tags extraction from page content (h3 tag)
140
- tags_raw = secici.css("div.poster.poster h3::text").get()
163
+ tags_el = secici.css_first("div.poster.poster h3")
164
+ tags_raw = tags_el.text(strip=True) if tags_el else ""
141
165
  tags = [t.strip() for t in tags_raw.split(",")] if tags_raw else []
142
166
 
143
167
  rating = veri.get("aggregateRating", {}).get("ratingValue")
@@ -172,9 +196,13 @@ class Dizilla(PluginBase):
172
196
 
173
197
  async def load_links(self, url: str) -> list[ExtractResult]:
174
198
  istek = await self.httpx.get(url)
175
- secici = Selector(istek.text)
199
+ secici = HTMLParser(istek.text)
200
+
201
+ next_data_el = secici.css_first("script#__NEXT_DATA__")
202
+ if not next_data_el:
203
+ return []
176
204
 
177
- next_data = loads(secici.css("script#__NEXT_DATA__::text").get())
205
+ next_data = loads(next_data_el.text(strip=True))
178
206
  secure_data = next_data.get("props", {}).get("pageProps", {}).get("secureData", {})
179
207
  decrypted = await self.decrypt_response(secure_data)
180
208
  results = decrypted.get("RelatedResults", {}).get("getEpisodeSources", {}).get("result", [])
@@ -190,8 +218,9 @@ class Dizilla(PluginBase):
190
218
  cleaned_source = source_content.replace('"', '').replace('\\', '')
191
219
 
192
220
  # Parse cleaned HTML
193
- iframe_src = Selector(cleaned_source).css("iframe::attr(src)").get()
194
- iframe_url = self.fix_url(iframe_src)
221
+ iframe_el = HTMLParser(cleaned_source).css_first("iframe")
222
+ iframe_src = iframe_el.attrs.get("src") if iframe_el else None
223
+ iframe_url = self.fix_url(iframe_src) if iframe_src else None
195
224
 
196
225
  if not iframe_url:
197
226
  return []