KekikStream 1.7.6__py3-none-any.whl → 1.9.0__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 (66) hide show
  1. KekikStream/Core/Extractor/ExtractorBase.py +2 -14
  2. KekikStream/Core/Extractor/ExtractorModels.py +5 -7
  3. KekikStream/Core/Media/MediaHandler.py +44 -26
  4. KekikStream/Core/Media/MediaManager.py +0 -3
  5. KekikStream/Core/Plugin/PluginBase.py +2 -15
  6. KekikStream/Core/Plugin/PluginModels.py +25 -26
  7. KekikStream/Extractors/CloseLoad.py +1 -2
  8. KekikStream/Extractors/ContentX.py +0 -2
  9. KekikStream/Extractors/DzenRu.py +0 -1
  10. KekikStream/Extractors/ExPlay.py +0 -1
  11. KekikStream/Extractors/FirePlayer.py +4 -5
  12. KekikStream/Extractors/HDPlayerSystem.py +0 -1
  13. KekikStream/Extractors/JetTv.py +0 -1
  14. KekikStream/Extractors/MailRu.py +1 -2
  15. KekikStream/Extractors/MixPlayHD.py +0 -1
  16. KekikStream/Extractors/MixTiger.py +1 -5
  17. KekikStream/Extractors/MolyStream.py +5 -5
  18. KekikStream/Extractors/Odnoklassniki.py +6 -6
  19. KekikStream/Extractors/PeaceMakerst.py +0 -1
  20. KekikStream/Extractors/PixelDrain.py +0 -1
  21. KekikStream/Extractors/PlayerFilmIzle.py +5 -5
  22. KekikStream/Extractors/RapidVid.py +0 -1
  23. KekikStream/Extractors/SetPlay.py +0 -1
  24. KekikStream/Extractors/SetPrime.py +0 -1
  25. KekikStream/Extractors/SibNet.py +0 -1
  26. KekikStream/Extractors/Sobreatsesuyp.py +0 -1
  27. KekikStream/Extractors/TRsTX.py +0 -1
  28. KekikStream/Extractors/TauVideo.py +0 -1
  29. KekikStream/Extractors/TurboImgz.py +0 -1
  30. KekikStream/Extractors/TurkeyPlayer.py +5 -5
  31. KekikStream/Extractors/VidHide.py +5 -5
  32. KekikStream/Extractors/VidMoly.py +0 -1
  33. KekikStream/Extractors/VidMoxy.py +0 -1
  34. KekikStream/Extractors/VidPapi.py +0 -1
  35. KekikStream/Extractors/VideoSeyred.py +0 -1
  36. KekikStream/Extractors/YTDLP.py +109 -0
  37. KekikStream/Extractors/YildizKisaFilm.py +0 -1
  38. KekikStream/Plugins/DiziBox.py +3 -8
  39. KekikStream/Plugins/DiziPal.py +5 -5
  40. KekikStream/Plugins/DiziYou.py +44 -19
  41. KekikStream/Plugins/Dizilla.py +38 -27
  42. KekikStream/Plugins/FilmBip.py +1 -1
  43. KekikStream/Plugins/FilmMakinesi.py +1 -5
  44. KekikStream/Plugins/FilmModu.py +3 -3
  45. KekikStream/Plugins/FullHDFilmizlesene.py +1 -5
  46. KekikStream/Plugins/HDFilmCehennemi.py +14 -21
  47. KekikStream/Plugins/JetFilmizle.py +2 -6
  48. KekikStream/Plugins/RecTV.py +12 -16
  49. KekikStream/Plugins/RoketDizi.py +105 -93
  50. KekikStream/Plugins/SelcukFlix.py +160 -67
  51. KekikStream/Plugins/SezonlukDizi.py +1 -5
  52. KekikStream/Plugins/SineWix.py +4 -8
  53. KekikStream/Plugins/Sinefy.py +72 -51
  54. KekikStream/Plugins/SinemaCX.py +4 -4
  55. KekikStream/Plugins/Sinezy.py +74 -42
  56. KekikStream/Plugins/UgurFilm.py +2 -6
  57. KekikStream/__init__.py +5 -8
  58. KekikStream/requirements.txt +2 -3
  59. kekikstream-1.9.0.dist-info/METADATA +290 -0
  60. kekikstream-1.9.0.dist-info/RECORD +86 -0
  61. kekikstream-1.7.6.dist-info/METADATA +0 -110
  62. kekikstream-1.7.6.dist-info/RECORD +0 -85
  63. {kekikstream-1.7.6.dist-info → kekikstream-1.9.0.dist-info}/WHEEL +0 -0
  64. {kekikstream-1.7.6.dist-info → kekikstream-1.9.0.dist-info}/entry_points.txt +0 -0
  65. {kekikstream-1.7.6.dist-info → kekikstream-1.9.0.dist-info}/licenses/LICENSE +0 -0
  66. {kekikstream-1.7.6.dist-info → kekikstream-1.9.0.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import kekik_cache, PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult, Subtitle
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult, Subtitle
4
4
  from parsel import Selector
5
5
  from Kekik.Sifreleme import Packer, StreamDecoder
6
6
  import random, string, re
@@ -12,8 +12,7 @@ class HDFilmCehennemi(PluginBase):
12
12
  favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
13
13
  description = "Türkiye'nin en hızlı hd film izleme sitesi"
14
14
 
15
- # Bu site domain değişikliği yapıyor ve potansiyel anti-bot koruması var
16
- requires_cffi = True
15
+
17
16
 
18
17
  main_page = {
19
18
  f"{main_url}" : "Yeni Eklenen Filmler",
@@ -32,9 +31,8 @@ class HDFilmCehennemi(PluginBase):
32
31
  f"{main_url}/tur/romantik-filmleri-izle-1" : "Romantik Filmleri"
33
32
  }
34
33
 
35
- #@kekik_cache(ttl=60*60)
36
34
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
37
- istek = await self.cffi.get(f"{url}", allow_redirects=True)
35
+ istek = await self.httpx.get(f"{url}", follow_redirects=True)
38
36
  secici = Selector(istek.text)
39
37
 
40
38
  return [
@@ -47,9 +45,8 @@ class HDFilmCehennemi(PluginBase):
47
45
  for veri in secici.css("div.section-content a.poster")
48
46
  ]
49
47
 
50
- #@kekik_cache(ttl=60*60)
51
48
  async def search(self, query: str) -> list[SearchResult]:
52
- istek = await self.cffi.get(
49
+ istek = await self.httpx.get(
53
50
  url = f"{self.main_url}/search/?q={query}",
54
51
  headers = {
55
52
  "Referer" : f"{self.main_url}/",
@@ -76,9 +73,8 @@ class HDFilmCehennemi(PluginBase):
76
73
 
77
74
  return results
78
75
 
79
- #@kekik_cache(ttl=60*60)
80
76
  async def load_item(self, url: str) -> MovieInfo:
81
- istek = await self.cffi.get(url, headers = {"Referer": f"{self.main_url}/"})
77
+ istek = await self.httpx.get(url, headers = {"Referer": f"{self.main_url}/"})
82
78
  secici = Selector(istek.text)
83
79
 
84
80
  title = secici.css("h1.section-title::text").get().strip()
@@ -111,11 +107,10 @@ class HDFilmCehennemi(PluginBase):
111
107
  def generate_random_cookie(self):
112
108
  return "".join(random.choices(string.ascii_letters + string.digits, k=16))
113
109
 
114
- #@kekik_cache(ttl=15*60)
115
110
  async def cehennempass(self, video_id: str) -> list[dict]:
116
111
  results = []
117
112
 
118
- istek = await self.cffi.post(
113
+ istek = await self.httpx.post(
119
114
  url = "https://cehennempass.pw/process_quality_selection.php",
120
115
  headers = {
121
116
  "Referer" : f"https://cehennempass.pw/download/{video_id}",
@@ -132,7 +127,7 @@ class HDFilmCehennemi(PluginBase):
132
127
  "referer" : f"https://cehennempass.pw/download/{video_id}"
133
128
  })
134
129
 
135
- istek = await self.cffi.post(
130
+ istek = await self.httpx.post(
136
131
  url = "https://cehennempass.pw/process_quality_selection.php",
137
132
  headers = {
138
133
  "Referer" : f"https://cehennempass.pw/download/{video_id}",
@@ -151,10 +146,9 @@ class HDFilmCehennemi(PluginBase):
151
146
 
152
147
  return results
153
148
 
154
- #@kekik_cache(ttl=15*60)
155
149
  async def invoke_local_source(self, iframe: str, source: str, url: str):
156
- self.cffi.headers.update({"Referer": f"{self.main_url}/"})
157
- istek = await self.cffi.get(iframe)
150
+ self.httpx.headers.update({"Referer": f"{self.main_url}/"})
151
+ istek = await self.httpx.get(iframe)
158
152
 
159
153
  try:
160
154
  eval_func = re.compile(r'\s*(eval\(function[\s\S].*)\s*').findall(istek.text)[0]
@@ -182,9 +176,8 @@ class HDFilmCehennemi(PluginBase):
182
176
  "subtitles" : subtitles
183
177
  }]
184
178
 
185
- #@kekik_cache(ttl=15*60)
186
179
  async def load_links(self, url: str) -> list[dict]:
187
- istek = await self.cffi.get(url)
180
+ istek = await self.httpx.get(url)
188
181
  secici = Selector(istek.text)
189
182
 
190
183
  results = []
@@ -195,7 +188,7 @@ class HDFilmCehennemi(PluginBase):
195
188
  source = f"{link.css('::text').get().replace('(HDrip Xbet)', '').strip()} {lang_code}"
196
189
  video_id = link.css("::attr(data-video)").get()
197
190
 
198
- api_get = await self.cffi.get(
191
+ api_get = await self.httpx.get(
199
192
  url = f"{self.main_url}/video/{video_id}/",
200
193
  headers = {
201
194
  "Content-Type" : "application/json",
@@ -219,9 +212,9 @@ class HDFilmCehennemi(PluginBase):
219
212
 
220
213
  return results
221
214
 
222
- async def play(self, name: str, url: str, referer: str, subtitles: list[Subtitle]):
223
- extract_result = ExtractResult(name=name, url=url, referer=referer, subtitles=subtitles)
224
- self.media_handler.title = name
215
+ async def play(self, **kwargs):
216
+ extract_result = ExtractResult(**kwargs)
217
+ self.media_handler.title = kwargs.get("name")
225
218
  if self.name not in self.media_handler.title:
226
219
  self.media_handler.title = f"{self.name} | {self.media_handler.title}"
227
220
 
@@ -1,6 +1,6 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import kekik_cache, PluginBase, MainPageResult, SearchResult, MovieInfo
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo
4
4
  from parsel import Selector
5
5
 
6
6
  class JetFilmizle(PluginBase):
@@ -19,9 +19,8 @@ class JetFilmizle(PluginBase):
19
19
  f"{main_url}/kategoriler/yesilcam-filmleri-izlee/page/" : "Yeşilçam Filmleri"
20
20
  }
21
21
 
22
- #@kekik_cache(ttl=60*60)
23
22
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
24
- istek = await self.httpx.get(f"{url}{page}", allow_redirects=True)
23
+ istek = await self.httpx.get(f"{url}{page}", follow_redirects=True)
25
24
  secici = Selector(istek.text)
26
25
 
27
26
  return [
@@ -34,7 +33,6 @@ class JetFilmizle(PluginBase):
34
33
  for veri in secici.css("article.movie") if veri.css("h2 a::text, h3 a::text, h4 a::text, h5 a::text, h6 a::text").get()
35
34
  ]
36
35
 
37
- #@kekik_cache(ttl=60*60)
38
36
  async def search(self, query: str) -> list[SearchResult]:
39
37
  istek = await self.httpx.post(
40
38
  url = f"{self.main_url}/filmara.php",
@@ -60,7 +58,6 @@ class JetFilmizle(PluginBase):
60
58
 
61
59
  return results
62
60
 
63
- #@kekik_cache(ttl=60*60)
64
61
  async def load_item(self, url: str) -> MovieInfo:
65
62
  istek = await self.httpx.get(url)
66
63
  secici = Selector(istek.text)
@@ -92,7 +89,6 @@ class JetFilmizle(PluginBase):
92
89
  actors = actors
93
90
  )
94
91
 
95
- #@kekik_cache(ttl=15*60)
96
92
  async def load_links(self, url: str) -> list[dict]:
97
93
  istek = await self.httpx.get(url)
98
94
  secici = Selector(istek.text)
@@ -1,6 +1,6 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import kekik_cache, PluginBase, MainPageResult, SearchResult, MovieInfo, Episode, SeriesInfo, ExtractResult, Subtitle
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, Episode, SeriesInfo, ExtractResult, Subtitle
4
4
  from json import dumps, loads
5
5
  import re
6
6
 
@@ -30,7 +30,6 @@ class RecTV(PluginBase):
30
30
  f"{main_url}/api/movie/by/filtres/5/created/SAYFA/{sw_key}/" : "Romantik"
31
31
  }
32
32
 
33
- #@kekik_cache(ttl=60*60)
34
33
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
35
34
  self.httpx.headers.update({"user-agent": "okhttp/4.12.0"})
36
35
  istek = await self.httpx.get(f"{url.replace('SAYFA', str(int(page) - 1))}")
@@ -46,7 +45,6 @@ class RecTV(PluginBase):
46
45
  for veri in veriler
47
46
  ]
48
47
 
49
- #@kekik_cache(ttl=60*60)
50
48
  async def search(self, query: str) -> list[SearchResult]:
51
49
  self.httpx.headers.update({"user-agent": "okhttp/4.12.0"})
52
50
  istek = await self.httpx.get(f"{self.main_url}/api/search/{query}/{self.sw_key}/")
@@ -67,7 +65,6 @@ class RecTV(PluginBase):
67
65
  for veri in tum_veri
68
66
  ]
69
67
 
70
- #@kekik_cache(ttl=60*60)
71
68
  async def load_item(self, url: str) -> MovieInfo:
72
69
  self.httpx.headers.update({"user-agent": "okhttp/4.12.0"})
73
70
  veri = loads(url)
@@ -119,10 +116,7 @@ class RecTV(PluginBase):
119
116
  actors = []
120
117
  )
121
118
 
122
- #@kekik_cache(ttl=15*60)
123
119
  async def load_links(self, url: str) -> list[dict]:
124
- self.media_handler.headers.update({"User-Agent": "googleusercontent"})
125
-
126
120
  try:
127
121
  veri = loads(url)
128
122
  except Exception:
@@ -132,9 +126,10 @@ class RecTV(PluginBase):
132
126
  # Eğer dizi bölümü ise (bizim oluşturduğumuz yapı)
133
127
  if veri.get("is_episode"):
134
128
  return [{
135
- "url" : veri.get("url"),
136
- "name" : veri.get("title", "Bölüm"),
137
- "referer" : "https://twitter.com/"
129
+ "url" : veri.get("url"),
130
+ "name" : veri.get("title", "Bölüm"),
131
+ "user_agent" : "googleusercontent",
132
+ "referer" : "https://twitter.com/"
138
133
  }]
139
134
 
140
135
  # Film ise (RecTV API yapısı)
@@ -146,16 +141,17 @@ class RecTV(PluginBase):
146
141
  continue
147
142
 
148
143
  results.append({
149
- "url" : video_link,
150
- "name" : f"{veri.get('title')} - {kaynak.get('title')}",
151
- "referer" : "https://twitter.com/"
144
+ "url" : video_link,
145
+ "name" : f"{veri.get('title')} - {kaynak.get('title')}",
146
+ "user_agent" : "googleusercontent",
147
+ "referer" : "https://twitter.com/"
152
148
  })
153
149
 
154
150
  return results
155
151
 
156
- async def play(self, name: str, url: str, referer: str, subtitles: list[Subtitle]):
157
- extract_result = ExtractResult(name=name, url=url, referer=referer, subtitles=subtitles)
158
- self.media_handler.title = name
152
+ async def play(self, **kwargs):
153
+ extract_result = ExtractResult(**kwargs)
154
+ self.media_handler.title = kwargs.get("name")
159
155
  if self.name not in self.media_handler.title:
160
156
  self.media_handler.title = f"{self.name} | {self.media_handler.title}"
161
157
 
@@ -2,15 +2,16 @@
2
2
 
3
3
  from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, MovieInfo
4
4
  from parsel import Selector
5
- import re, base64, json, urllib.parse
5
+ import re, base64, json
6
6
 
7
7
  class RoketDizi(PluginBase):
8
8
  name = "RoketDizi"
9
9
  lang = "tr"
10
- main_url = "https://flatscher.net"
10
+ main_url = "https://roketdizi.to"
11
+ favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
12
+ description = "Türkiye'nin en tatlış yabancı dizi izleme sitesi. Türkçe dublaj, altyazılı, eski ve yeni yabancı dizilerin yanı sıra kore (asya) dizileri izleyebilirsiniz."
13
+
11
14
 
12
- # Domain doğrulama ve anti-bot mekanizmaları var
13
- requires_cffi = True
14
15
 
15
16
  main_page = {
16
17
  "dizi/tur/aksiyon" : "Aksiyon",
@@ -25,81 +26,70 @@ class RoketDizi(PluginBase):
25
26
 
26
27
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
27
28
  full_url = f"{self.main_url}/{url}?&page={page}"
28
- resp = await self.cffi.get(full_url)
29
- sel = Selector(resp.text)
29
+ resp = await self.httpx.get(full_url)
30
+ sel = Selector(resp.text)
30
31
 
31
32
  results = []
32
33
 
33
34
  for item in sel.css("div.w-full.p-4 span.bg-\\[\\#232323\\]"):
34
- title = item.css("span.font-normal.line-clamp-1::text").get()
35
- href = item.css("a::attr(href)").get()
36
- poster= item.css("img::attr(src)").get()
35
+ title = item.css("span.font-normal.line-clamp-1::text").get()
36
+ href = item.css("a::attr(href)").get()
37
+ poster = item.css("img::attr(src)").get()
37
38
 
38
39
  if title and href:
39
40
  results.append(MainPageResult(
40
- category=category,
41
- title=title,
42
- url=self.fix_url(href),
43
- poster=self.fix_url(poster)
41
+ category = category,
42
+ title = title,
43
+ url = self.fix_url(href),
44
+ poster = self.fix_url(poster)
44
45
  ))
45
46
  return results
46
47
 
47
- async def get_domain(self):
48
- try:
49
- domain_list = await self.cffi.get("https://raw.githubusercontent.com/Kraptor123/domainListesi/refs/heads/main/eklenti_domainleri.txt")
50
- if domain_list.status_code == 200:
51
- for line in domain_list.text.split("|"):
52
- if line.strip().startswith("RoketDizi"):
53
- domain = line.split(":")[-1].strip()
54
- if "http" not in domain:
55
- domain = f"https://{domain}"
56
- return domain
57
- except Exception:
58
- pass
59
- return self.main_url
60
-
61
48
  async def search(self, query: str) -> list[SearchResult]:
62
- current_domain = await self.get_domain()
63
-
64
- # Get Cookies and Keys
65
- main_req = await self.cffi.get(current_domain)
66
- sel = Selector(main_req.text)
67
-
68
- c_key = sel.css("input[name='cKey']::attr(value)").get()
69
- c_value = sel.css("input[name='cValue']::attr(value)").get()
70
-
71
- post_url = f"{current_domain}/api/bg/searchContent?searchterm={query}"
49
+ post_url = f"{self.main_url}/api/bg/searchContent?searchterm={query}"
72
50
 
73
51
  headers = {
74
- "Accept": "application/json, text/javascript, */*; q=0.01",
75
- "X-Requested-With": "XMLHttpRequest",
76
- "Referer": f"{current_domain}/",
77
- "CNT": "vakTR"
52
+ "Accept" : "application/json, text/javascript, */*; q=0.01",
53
+ "X-Requested-With" : "XMLHttpRequest",
54
+ "Referer" : f"{self.main_url}/",
78
55
  }
79
-
80
- data = {}
81
- if c_key and c_value:
82
- data = {"cKey": c_key, "cValue": c_value}
83
56
 
84
- search_req = await self.cffi.post(post_url, data=data, headers=headers)
57
+ search_req = await self.httpx.post(post_url, headers=headers)
85
58
 
86
59
  try:
87
60
  resp_json = search_req.json()
88
- if not resp_json.get("state"):
61
+
62
+ # Response is base64 encoded!
63
+ if not resp_json.get("success"):
64
+ return []
65
+
66
+ encoded_response = resp_json.get("response", "")
67
+ if not encoded_response:
68
+ return []
69
+
70
+ # Decode base64
71
+ decoded = base64.b64decode(encoded_response).decode('utf-8')
72
+ data = json.loads(decoded)
73
+
74
+ if not data.get("state"):
89
75
  return []
90
76
 
91
- html_content = resp_json.get("html", "").strip()
92
- sel_results = Selector(html_content)
93
-
94
77
  results = []
95
- items = re.findall(r'<a href="([^"]+)".*?data-srcset="([^"]+).*?<span class="text-white">([^<]+)', html_content, re.DOTALL)
78
+ result_items = data.get("result", [])
96
79
 
97
- for href, poster, title in items:
98
- results.append(SearchResult(
99
- title=title.strip(),
100
- url=self.fix_url(href.strip(), current_domain),
101
- poster=self.fix_url(poster.strip(), current_domain)
102
- ))
80
+ for item in result_items:
81
+ title = item.get("object_name", "")
82
+ slug = item.get("used_slug", "")
83
+ poster = item.get("object_poster_url", "")
84
+
85
+ if title and slug:
86
+ # Construct full URL from slug
87
+ full_url = f"{self.main_url}/{slug}"
88
+ results.append(SearchResult(
89
+ title = title.strip(),
90
+ url = full_url,
91
+ poster = self.fix_url(poster) if poster else None
92
+ ))
103
93
 
104
94
  return results
105
95
 
@@ -108,21 +98,48 @@ class RoketDizi(PluginBase):
108
98
 
109
99
  async def load_item(self, url: str) -> SeriesInfo:
110
100
  # Note: Handling both Movie and Series logic in one, returning SeriesInfo generally or MovieInfo
111
- resp = await self.cffi.get(url)
101
+ resp = await self.httpx.get(url)
112
102
  sel = Selector(resp.text)
113
103
 
114
104
  title = sel.css("h1.text-white::text").get()
115
105
  poster = sel.css("div.w-full.page-top img::attr(src)").get()
116
106
  description = sel.css("div.mt-2.text-sm::text").get()
117
107
 
118
- year = None # Implement if critical
108
+ # Tags - genre bilgileri (Detaylar bölümünde)
109
+ tags = []
110
+ genre_text = sel.css("h3.text-white.opacity-90::text").get()
111
+ if genre_text:
112
+ tags = [t.strip() for t in genre_text.split(",")]
119
113
 
120
- tags = sel.css("h3.text-white.opacity-60::text").get()
121
- if tags:
122
- tags = [t.strip() for t in tags.split(",")]
123
-
114
+ # Rating
124
115
  rating = sel.css("div.flex.items-center span.text-white.text-sm::text").get()
125
- actors = sel.css("div.global-box h5::text").getall()
116
+
117
+ # Year ve Actors - Detaylar (Details) bölümünden
118
+ year = None
119
+ actors = []
120
+
121
+ # Detaylar bölümündeki tüm flex-col div'leri al
122
+ detail_items = sel.css("div.flex.flex-col")
123
+ for item in detail_items:
124
+ # Label ve value yapısı: span.text-base ve span.text-sm.opacity-90
125
+ label = item.css("span.text-base::text").get()
126
+ value = item.css("span.text-sm.opacity-90::text").get()
127
+
128
+ if label and value:
129
+ label = label.strip()
130
+ value = value.strip()
131
+
132
+ # Yayın tarihi (yıl)
133
+ if label == "Yayın tarihi":
134
+ # "16 Ekim 2018" formatından yılı çıkar
135
+ year_match = re.search(r'\d{4}', value)
136
+ if year_match:
137
+ year = year_match.group()
138
+
139
+ # Yaratıcılar veya Oyuncular
140
+ elif label in ["Yaratıcılar", "Oyuncular"]:
141
+ if value:
142
+ actors.append(value)
126
143
 
127
144
  # Check urls for episodes
128
145
  all_urls = re.findall(r'"url":"([^"]*)"', resp.text)
@@ -130,37 +147,42 @@ class RoketDizi(PluginBase):
130
147
 
131
148
  episodes = []
132
149
  if is_series:
133
- seen_eps = set()
150
+ # Dict kullanarak duplicate'leri önle ama sıralı tut
151
+ episodes_dict = {}
134
152
  for u in all_urls:
135
- if "bolum" in u and u not in seen_eps:
136
- seen_eps.add(u)
153
+ if "bolum" in u and u not in episodes_dict:
137
154
  season_match = re.search(r'/sezon-(\d+)', u)
138
155
  ep_match = re.search(r'/bolum-(\d+)', u)
139
156
 
140
157
  season = int(season_match.group(1)) if season_match else 1
141
158
  episode_num = int(ep_match.group(1)) if ep_match else 1
142
159
 
143
- episodes.append(Episode(
144
- season=season,
145
- episode=episode_num,
146
- title=f"{season}. Sezon {episode_num}. Bölüm", # Placeholder title
147
- url=self.fix_url(u, self.get_domain_sync(url))
148
- ))
160
+ # Key olarak (season, episode) tuple kullan
161
+ key = (season, episode_num)
162
+ episodes_dict[key] = Episode(
163
+ season = season,
164
+ episode = episode_num,
165
+ title = f"{season}. Sezon {episode_num}. Bölüm",
166
+ url = self.fix_url(u)
167
+ )
168
+
169
+ # Sıralı liste oluştur
170
+ episodes = [episodes_dict[key] for key in sorted(episodes_dict.keys())]
149
171
 
150
172
  return SeriesInfo(
151
- title=title,
152
- url=url,
153
- poster=self.fix_url(poster),
154
- description=description,
155
- tags=tags,
156
- rating=rating,
157
- actors=actors,
158
- episodes=episodes,
159
- year=year
173
+ title = title,
174
+ url = url,
175
+ poster = self.fix_url(poster),
176
+ description = description,
177
+ tags = tags,
178
+ rating = rating,
179
+ actors = actors,
180
+ episodes = episodes,
181
+ year = year
160
182
  )
161
183
 
162
184
  async def load_links(self, url: str) -> list[dict]:
163
- resp = await self.cffi.get(url)
185
+ resp = await self.httpx.get(url)
164
186
  sel = Selector(resp.text)
165
187
 
166
188
  next_data = sel.css("script#__NEXT_DATA__::text").get()
@@ -195,13 +217,3 @@ class RoketDizi(PluginBase):
195
217
 
196
218
  except Exception:
197
219
  return []
198
-
199
- def fix_url(self, url: str, domain:str=None) -> str:
200
- if not url: return ""
201
- if url.startswith("http"): return url
202
- base = domain or self.main_url
203
- return f"https:{url}" if url.startswith("//") else urllib.parse.urljoin(base, url)
204
-
205
- def get_domain_sync(self, url:str):
206
- parsed = urllib.parse.urlparse(url)
207
- return f"{parsed.scheme}://{parsed.netloc}"