KekikStream 2.1.4__py3-none-any.whl → 2.1.9__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 (36) hide show
  1. KekikStream/Core/Plugin/PluginBase.py +20 -21
  2. KekikStream/Extractors/CloseLoad.py +17 -2
  3. KekikStream/Extractors/Filemoon.py +78 -0
  4. KekikStream/Extractors/RapidVid.py +15 -5
  5. KekikStream/Extractors/VidHide.py +11 -2
  6. KekikStream/Plugins/BelgeselX.py +7 -7
  7. KekikStream/Plugins/DiziBox.py +2 -2
  8. KekikStream/Plugins/DiziPal.py +8 -8
  9. KekikStream/Plugins/DiziYou.py +8 -8
  10. KekikStream/Plugins/Dizilla.py +2 -2
  11. KekikStream/Plugins/FilmBip.py +2 -2
  12. KekikStream/Plugins/FilmMakinesi.py +2 -2
  13. KekikStream/Plugins/FilmModu.py +8 -8
  14. KekikStream/Plugins/FullHDFilm.py +81 -22
  15. KekikStream/Plugins/FullHDFilmizlesene.py +2 -2
  16. KekikStream/Plugins/HDFilmCehennemi.py +79 -39
  17. KekikStream/Plugins/JetFilmizle.py +2 -2
  18. KekikStream/Plugins/KultFilmler.py +10 -9
  19. KekikStream/Plugins/RecTV.py +15 -15
  20. KekikStream/Plugins/RoketDizi.py +2 -2
  21. KekikStream/Plugins/SelcukFlix.py +68 -44
  22. KekikStream/Plugins/SetFilmIzle.py +21 -21
  23. KekikStream/Plugins/SezonlukDizi.py +19 -2
  24. KekikStream/Plugins/SineWix.py +13 -13
  25. KekikStream/Plugins/Sinefy.py +6 -6
  26. KekikStream/Plugins/SinemaCX.py +31 -22
  27. KekikStream/Plugins/Sinezy.py +2 -2
  28. KekikStream/Plugins/SuperFilmGeldi.py +30 -19
  29. KekikStream/Plugins/UgurFilm.py +2 -2
  30. KekikStream/__init__.py +15 -15
  31. {kekikstream-2.1.4.dist-info → kekikstream-2.1.9.dist-info}/METADATA +5 -3
  32. {kekikstream-2.1.4.dist-info → kekikstream-2.1.9.dist-info}/RECORD +36 -35
  33. {kekikstream-2.1.4.dist-info → kekikstream-2.1.9.dist-info}/WHEEL +0 -0
  34. {kekikstream-2.1.4.dist-info → kekikstream-2.1.9.dist-info}/entry_points.txt +0 -0
  35. {kekikstream-2.1.4.dist-info → kekikstream-2.1.9.dist-info}/licenses/LICENSE +0 -0
  36. {kekikstream-2.1.4.dist-info → kekikstream-2.1.9.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@ from ...CLI import konsol
4
4
  from abc import ABC, abstractmethod
5
5
  from cloudscraper import CloudScraper
6
6
  from httpx import AsyncClient
7
- from .PluginModels import MainPageResult, SearchResult, MovieInfo
7
+ from .PluginModels import MainPageResult, SearchResult, MovieInfo, SeriesInfo
8
8
  from ..Media.MediaHandler import MediaHandler
9
9
  from ..Extractor.ExtractorManager import ExtractorManager
10
10
  from ..Extractor.ExtractorModels import ExtractResult
@@ -55,31 +55,31 @@ class PluginBase(ABC):
55
55
  pass
56
56
 
57
57
  @abstractmethod
58
- async def load_item(self, url: str) -> MovieInfo:
58
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
59
59
  """Bir medya öğesi hakkında detaylı bilgi döndürür."""
60
60
  pass
61
61
 
62
62
  @abstractmethod
63
- async def load_links(self, url: str) -> list[dict]:
63
+ async def load_links(self, url: str) -> list[ExtractResult]:
64
64
  """
65
65
  Bir medya öğesi için oynatma bağlantılarını döndürür.
66
-
66
+
67
67
  Args:
68
68
  url: Medya URL'si
69
-
69
+
70
70
  Returns:
71
- Dictionary listesi, her biri şu alanları içerir:
71
+ ExtractResult listesi, her biri şu alanları içerir:
72
72
  - url (str, zorunlu): Video URL'si
73
73
  - name (str, zorunlu): Gösterim adı (tüm bilgileri içerir)
74
74
  - referer (str, opsiyonel): Referer header
75
- - subtitles (list, opsiyonel): Altyazı listesi
76
-
75
+ - subtitles (list[Subtitle], opsiyonel): Altyazı listesi
76
+
77
77
  Example:
78
78
  [
79
- {
80
- "url": "https://example.com/video.m3u8",
81
- "name": "HDFilmCehennemi | 1080p TR Dublaj"
82
- }
79
+ ExtractResult(
80
+ url="https://example.com/video.m3u8",
81
+ name="HDFilmCehennemi | 1080p TR Dublaj"
82
+ )
83
83
  ]
84
84
  """
85
85
  pass
@@ -97,7 +97,7 @@ class PluginBase(ABC):
97
97
 
98
98
  return f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
99
99
 
100
- async def extract(self, url: str, referer: str = None, prefix: str | None = None) -> dict | None:
100
+ async def extract(self, url: str, referer: str = None, prefix: str | None = None) -> ExtractResult | None:
101
101
  """
102
102
  Extractor ile video URL'sini çıkarır.
103
103
 
@@ -107,8 +107,8 @@ class PluginBase(ABC):
107
107
  prefix: İsmin başına eklenecek opsiyonel etiket (örn: "Türkçe Dublaj")
108
108
 
109
109
  Returns:
110
- dict: Extractor sonucu (name prefix ile birleştirilmiş) veya None
111
-
110
+ ExtractResult: Extractor sonucu (name prefix ile birleştirilmiş) veya None
111
+
112
112
  Extractor bulunamadığında veya hata oluştuğunda uyarı verir.
113
113
  """
114
114
  if referer is None:
@@ -121,13 +121,12 @@ class PluginBase(ABC):
121
121
 
122
122
  try:
123
123
  data = await extractor.extract(url, referer=referer)
124
- result = data.dict()
125
-
124
+
126
125
  # prefix varsa name'e ekle
127
- if prefix and result.get("name"):
128
- result["name"] = f"{prefix} | {result['name']}"
129
-
130
- return result
126
+ if prefix and data.name:
127
+ data.name = f"{prefix} | {data.name}"
128
+
129
+ return data
131
130
  except Exception as hata:
132
131
  konsol.log(f"[red][!] {self.name} » Extractor hatası ({extractor.name}): {hata}")
133
132
  return None
@@ -1,7 +1,8 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import ExtractorBase, ExtractResult
3
+ from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
4
4
  from Kekik.Sifreleme import Packer, StreamDecoder
5
+ from parsel import Selector
5
6
  import re
6
7
 
7
8
  class CloseLoadExtractor(ExtractorBase):
@@ -15,12 +16,26 @@ class CloseLoadExtractor(ExtractorBase):
15
16
  istek = await self.httpx.get(url)
16
17
  istek.raise_for_status()
17
18
 
19
+ # Video URL'sini çıkar
18
20
  eval_func = re.compile(r'\s*(eval\(function[\s\S].*)\s*').findall(istek.text)[0]
19
21
  m3u_link = StreamDecoder.extract_stream_url(Packer.unpack(eval_func))
20
22
 
23
+ # Subtitle'ları parse et (Kotlin referansı: track elementleri)
24
+ subtitles = []
25
+ secici = Selector(istek.text)
26
+ for track in secici.css("track"):
27
+ raw_src = track.css("::attr(src)").get() or ""
28
+ raw_src = raw_src.strip()
29
+ label = track.css("::attr(label)").get() or track.css("::attr(srclang)").get() or "Altyazı"
30
+
31
+ if raw_src:
32
+ full_url = raw_src if raw_src.startswith("http") else f"{self.main_url}{raw_src}"
33
+ subtitles.append(Subtitle(name=label, url=full_url))
34
+
21
35
  return ExtractResult(
22
36
  name = self.name,
23
37
  url = m3u_link,
24
38
  referer = self.main_url,
25
- subtitles = []
39
+ subtitles = subtitles
26
40
  )
41
+
@@ -0,0 +1,78 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import ExtractorBase, ExtractResult
4
+ from Kekik.Sifreleme import Packer
5
+ from parsel import Selector
6
+ import re
7
+
8
+ class Filemoon(ExtractorBase):
9
+ name = "Filemoon"
10
+ main_url = "https://filemoon.to"
11
+
12
+ # Filemoon'un farklı domainlerini destekle
13
+ supported_domains = [
14
+ "filemoon.to",
15
+ "filemoon.in",
16
+ "filemoon.sx",
17
+ "filemoon.nl",
18
+ "filemoon.com"
19
+ ]
20
+
21
+ def can_handle_url(self, url: str) -> bool:
22
+ return any(domain in url for domain in self.supported_domains)
23
+
24
+ async def extract(self, url: str, referer: str = None) -> ExtractResult:
25
+ headers = {
26
+ "Referer" : url,
27
+ "Sec-Fetch-Dest" : "iframe",
28
+ "Sec-Fetch-Mode" : "navigate",
29
+ "Sec-Fetch-Site" : "cross-site",
30
+ }
31
+ self.httpx.headers.update(headers)
32
+
33
+ # İlk sayfayı al
34
+ istek = await self.httpx.get(url)
35
+ response = istek.text
36
+ secici = Selector(response)
37
+
38
+ # Eğer iframe varsa, iframe'e git
39
+ iframe_src = secici.css("iframe::attr(src)").get()
40
+ if iframe_src:
41
+ iframe_url = self.fix_url(iframe_src)
42
+ self.httpx.headers.update({
43
+ "Accept-Language" : "en-US,en;q=0.5",
44
+ "Sec-Fetch-Dest" : "iframe"
45
+ })
46
+ istek = await self.httpx.get(iframe_url)
47
+ response = istek.text
48
+
49
+ # Packed script'i bul ve unpack et
50
+ m3u8_url = None
51
+
52
+ if Packer.detect_packed(response):
53
+ try:
54
+ unpacked = Packer.unpack(response)
55
+ # sources:[{file:"..." pattern'ını ara
56
+ if match := re.search(r'sources:\s*\[\s*\{\s*file:\s*"([^"]+)"', unpacked):
57
+ m3u8_url = match.group(1)
58
+ elif match := re.search(r'file:\s*"([^"]*?\.m3u8[^"]*)"', unpacked):
59
+ m3u8_url = match.group(1)
60
+ except Exception:
61
+ pass
62
+
63
+ # Fallback: Doğrudan response'ta ara
64
+ if not m3u8_url:
65
+ if match := re.search(r'sources:\s*\[\s*\{\s*file:\s*"([^"]+)"', response):
66
+ m3u8_url = match.group(1)
67
+ elif match := re.search(r'file:\s*"([^"]*?\.m3u8[^"]*)"', response):
68
+ m3u8_url = match.group(1)
69
+
70
+ if not m3u8_url:
71
+ raise ValueError(f"Filemoon: Video URL bulunamadı. {url}")
72
+
73
+ return ExtractResult(
74
+ name = self.name,
75
+ url = self.fix_url(m3u8_url),
76
+ referer = f"{self.main_url}/",
77
+ subtitles = []
78
+ )
@@ -1,7 +1,7 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
3
  from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
4
- from Kekik.Sifreleme import Packer, HexCodec
4
+ from Kekik.Sifreleme import Packer, HexCodec, StreamDecoder
5
5
  import re, base64
6
6
 
7
7
  class RapidVid(ExtractorBase):
@@ -39,15 +39,25 @@ class RapidVid(ExtractorBase):
39
39
  subtitles.append(Subtitle(name=decoded_lang, url=sub_url.replace("\\", "")))
40
40
 
41
41
  try:
42
+ decoded_url = None
43
+
44
+ # Method 1: file": "..." pattern (HexCodec)
42
45
  if extracted_value := re.search(r'file": "(.*)",', istek.text):
43
46
  escaped_hex = extracted_value[1]
44
47
  decoded_url = HexCodec.decode(escaped_hex)
45
- else:
46
- av_encoded = re.search(r"av\('([^']+)'\)", istek.text)
47
- if not av_encoded:
48
- raise ValueError("AV encoding not found.")
49
48
 
49
+ # Method 2: av('...') pattern
50
+ elif av_encoded := re.search(r"av\('([^']+)'\)", istek.text):
50
51
  decoded_url = self.decode_secret(av_encoded[1])
52
+
53
+ # Method 3: Packed script with dc_* function (StreamDecoder)
54
+ elif Packer.detect_packed(istek.text):
55
+ unpacked = Packer.unpack(istek.text)
56
+ decoded_url = StreamDecoder.extract_stream_url(unpacked)
57
+
58
+ if not decoded_url:
59
+ raise ValueError("No valid video URL pattern found.")
60
+
51
61
  except Exception as hata:
52
62
  raise RuntimeError(f"Extraction failed: {hata}") from hata
53
63
 
@@ -9,6 +9,12 @@ class VidHide(ExtractorBase):
9
9
  name = "VidHide"
10
10
  main_url = "https://vidhidepro.com"
11
11
 
12
+ # Birden fazla domain destekle
13
+ supported_domains = ["vidhidepro.com", "vidhide.com", "rubyvidhub.com"]
14
+
15
+ def can_handle_url(self, url: str) -> bool:
16
+ return any(domain in url for domain in self.supported_domains)
17
+
12
18
  def get_embed_url(self, url: str) -> str:
13
19
  if "/d/" in url:
14
20
  return url.replace("/d/", "/v/")
@@ -20,6 +26,9 @@ class VidHide(ExtractorBase):
20
26
  return url.replace("/f/", "/v/")
21
27
 
22
28
  async def extract(self, url: str, referer: str = None) -> ExtractResult:
29
+ # Dinamik base URL kullan
30
+ base_url = self.get_base_url(url)
31
+
23
32
  if referer:
24
33
  self.httpx.headers.update({"Referer": referer})
25
34
 
@@ -27,7 +36,7 @@ class VidHide(ExtractorBase):
27
36
  "Sec-Fetch-Dest" : "empty",
28
37
  "Sec-Fetch-Mode" : "cors",
29
38
  "Sec-Fetch-Site" : "cross-site",
30
- "Origin" : self.main_url,
39
+ "Origin" : base_url,
31
40
  })
32
41
 
33
42
  embed_url = self.get_embed_url(url)
@@ -66,7 +75,7 @@ class VidHide(ExtractorBase):
66
75
  return ExtractResult(
67
76
  name = self.name,
68
77
  url = self.fix_url(m3u8_url),
69
- referer = f"{self.main_url}/",
78
+ referer = f"{base_url}/",
70
79
  user_agent = self.httpx.headers.get("User-Agent", ""),
71
80
  subtitles = []
72
81
  )
@@ -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 PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult
4
4
  from parsel import Selector
5
5
  import re
6
6
 
@@ -164,7 +164,7 @@ class BelgeselX(PluginBase):
164
164
  episodes = episodes
165
165
  )
166
166
 
167
- async def load_links(self, url: str) -> list[dict]:
167
+ async def load_links(self, url: str) -> list[ExtractResult]:
168
168
  istek = await self.httpx.get(url)
169
169
  text = istek.text
170
170
 
@@ -187,10 +187,10 @@ class BelgeselX(PluginBase):
187
187
  source_name = "Google" if quality == "FULL" else self.name
188
188
  quality_str = "1080p" if quality == "FULL" else quality
189
189
 
190
- links.append({
191
- "url" : video_url,
192
- "name" : f"{source_name} | {quality_str}",
193
- "referer" : url
194
- })
190
+ links.append(ExtractResult(
191
+ url = video_url,
192
+ name = f"{source_name} | {quality_str}",
193
+ referer = url
194
+ ))
195
195
 
196
196
  return links
@@ -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 PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult
4
4
  from Kekik.Sifreleme import CryptoJS
5
5
  from parsel import Selector
6
6
  import re, urllib.parse, base64, contextlib, asyncio, time
@@ -174,7 +174,7 @@ class DiziBox(PluginBase):
174
174
 
175
175
  return results
176
176
 
177
- async def load_links(self, url: str) -> list[dict]:
177
+ async def load_links(self, url: str) -> list[ExtractResult]:
178
178
  istek = await self.httpx.get(url)
179
179
  secici = Selector(istek.text)
180
180
 
@@ -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 PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, Subtitle
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, Subtitle, ExtractResult
4
4
  from parsel import Selector
5
5
  import re
6
6
 
@@ -177,7 +177,7 @@ class DiziPal(PluginBase):
177
177
  duration = duration,
178
178
  )
179
179
 
180
- async def load_links(self, url: str) -> list[dict]:
180
+ async def load_links(self, url: str) -> list[ExtractResult]:
181
181
  # Reset headers to get HTML response
182
182
  self.httpx.headers.update({
183
183
  "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
@@ -220,12 +220,12 @@ class DiziPal(PluginBase):
220
220
  sub_url = sub_text.replace(f"[{lang}]", "")
221
221
  subtitles.append(Subtitle(name=lang, url=self.fix_url(sub_url)))
222
222
 
223
- results.append({
224
- "name" : self.name,
225
- "url" : m3u_link,
226
- "referer" : f"{self.main_url}/",
227
- "subtitles" : subtitles
228
- })
223
+ results.append(ExtractResult(
224
+ name = self.name,
225
+ url = m3u_link,
226
+ referer = f"{self.main_url}/",
227
+ subtitles = subtitles
228
+ ))
229
229
  else:
230
230
  # Extractor'a yönlendir
231
231
  data = await self.extract(iframe)
@@ -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 PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, Subtitle
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, Subtitle, ExtractResult
4
4
  from parsel import Selector
5
5
  import re
6
6
 
@@ -123,7 +123,7 @@ class DiziYou(PluginBase):
123
123
  actors = actors
124
124
  )
125
125
 
126
- async def load_links(self, url: str) -> list[dict]:
126
+ async def load_links(self, url: str) -> list[ExtractResult]:
127
127
  istek = await self.httpx.get(url)
128
128
  secici = Selector(istek.text)
129
129
 
@@ -179,11 +179,11 @@ class DiziYou(PluginBase):
179
179
 
180
180
  results = []
181
181
  for stream in stream_urls:
182
- results.append({
183
- "url" : stream.get("url"),
184
- "name" : f"{stream.get('dil')}",
185
- "referer" : url,
186
- "subtitles" : subtitles
187
- })
182
+ results.append(ExtractResult(
183
+ url = stream.get("url"),
184
+ name = f"{stream.get('dil')}",
185
+ referer = url,
186
+ subtitles = subtitles
187
+ ))
188
188
 
189
189
  return results
@@ -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 PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult
4
4
  from parsel import Selector
5
5
  from json import loads
6
6
  from urllib.parse import urlparse, urlunparse
@@ -170,7 +170,7 @@ class Dizilla(PluginBase):
170
170
  actors = actors
171
171
  )
172
172
 
173
- async def load_links(self, url: str) -> list[dict]:
173
+ async def load_links(self, url: str) -> list[ExtractResult]:
174
174
  istek = await self.httpx.get(url)
175
175
  secici = Selector(istek.text)
176
176
 
@@ -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 PluginBase, MainPageResult, SearchResult, MovieInfo
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult
4
4
  from parsel import Selector
5
5
 
6
6
  class FilmBip(PluginBase):
@@ -125,7 +125,7 @@ class FilmBip(PluginBase):
125
125
  actors = actors,
126
126
  )
127
127
 
128
- async def load_links(self, url: str) -> list[dict]:
128
+ async def load_links(self, url: str) -> list[ExtractResult]:
129
129
  istek = await self.httpx.get(url)
130
130
  secici = Selector(istek.text)
131
131
 
@@ -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 PluginBase, MainPageResult, SearchResult, MovieInfo
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult
4
4
  from parsel import Selector
5
5
 
6
6
  class FilmMakinesi(PluginBase):
@@ -104,7 +104,7 @@ class FilmMakinesi(PluginBase):
104
104
  duration = duration
105
105
  )
106
106
 
107
- async def load_links(self, url: str) -> list[dict]:
107
+ async def load_links(self, url: str) -> list[ExtractResult]:
108
108
  istek = await self.httpx.get(url)
109
109
  secici = Selector(istek.text)
110
110
 
@@ -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 PluginBase, MainPageResult, SearchResult, MovieInfo, Subtitle
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, Subtitle, ExtractResult
4
4
  from parsel import Selector
5
5
  import re
6
6
 
@@ -87,7 +87,7 @@ class FilmModu(PluginBase):
87
87
  actors = [a.css("span::text").get() for a in secici.css("a[itemprop='actor']")],
88
88
  )
89
89
 
90
- async def load_links(self, url: str) -> list[dict]:
90
+ async def load_links(self, url: str) -> list[ExtractResult]:
91
91
  istek = await self.httpx.get(url)
92
92
  secici = Selector(istek.text)
93
93
 
@@ -124,11 +124,11 @@ class FilmModu(PluginBase):
124
124
  subtitle_url = None
125
125
 
126
126
  for source in source_data.get("sources", []):
127
- results.append({
128
- "name" : f"{self.name} | {alt_name} | {source.get('label', 'Bilinmiyor')}",
129
- "url" : self.fix_url(source["src"]),
130
- "referer" : f"{self.main_url}/",
131
- "subtitles" : [Subtitle(name="Türkçe", url=subtitle_url)] if subtitle_url else []
132
- })
127
+ results.append(ExtractResult(
128
+ name = f"{self.name} | {alt_name} | {source.get('label', 'Bilinmiyor')}",
129
+ url = self.fix_url(source["src"]),
130
+ referer = f"{self.main_url}/",
131
+ subtitles = [Subtitle(name="Türkçe", url=subtitle_url)] if subtitle_url else []
132
+ ))
133
133
 
134
134
  return results
@@ -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 PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult, Subtitle
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult, Subtitle
4
4
  from parsel import Selector
5
5
  import re, base64
6
6
 
@@ -14,23 +14,29 @@ class FullHDFilm(PluginBase):
14
14
  main_page = {
15
15
  f"{main_url}/tur/turkce-altyazili-film-izle" : "Altyazılı Filmler",
16
16
  f"{main_url}/tur/netflix-filmleri-izle" : "Netflix",
17
+ f"{main_url}/tur/yerli-film-izle" : "Yerli Film",
17
18
  f"{main_url}/category/aile-filmleri-izle" : "Aile",
18
19
  f"{main_url}/category/aksiyon-filmleri-izle" : "Aksiyon",
19
20
  f"{main_url}/category/animasyon-filmleri-izle" : "Animasyon",
20
21
  f"{main_url}/category/belgesel-filmleri-izle" : "Belgesel",
21
- f"{main_url}/category/bilim-kurgu-filmleri-izle" : "Bilim-Kurgu",
22
+ f"{main_url}/category/bilim-kurgu-filmleri-izle" : "Bilim Kurgu",
22
23
  f"{main_url}/category/biyografi-filmleri-izle" : "Biyografi",
23
24
  f"{main_url}/category/dram-filmleri-izle" : "Dram",
24
25
  f"{main_url}/category/fantastik-filmler-izle" : "Fantastik",
25
26
  f"{main_url}/category/gerilim-filmleri-izle" : "Gerilim",
26
27
  f"{main_url}/category/gizem-filmleri-izle" : "Gizem",
28
+ f"{main_url}/category/kisa" : "Kısa",
27
29
  f"{main_url}/category/komedi-filmleri-izle" : "Komedi",
28
30
  f"{main_url}/category/korku-filmleri-izle" : "Korku",
29
31
  f"{main_url}/category/macera-filmleri-izle" : "Macera",
32
+ f"{main_url}/category/muzik" : "Müzik",
33
+ f"{main_url}/category/muzikal-filmleri-izle" : "Müzikal",
30
34
  f"{main_url}/category/romantik-filmler-izle" : "Romantik",
31
35
  f"{main_url}/category/savas-filmleri-izle" : "Savaş",
36
+ f"{main_url}/category/spor-filmleri-izle" : "Spor",
32
37
  f"{main_url}/category/suc-filmleri-izle" : "Suç",
33
- f"{main_url}/tur/yerli-film-izle" : "Yerli Film",
38
+ f"{main_url}/category/tarih-filmleri-izle" : "Tarih",
39
+ f"{main_url}/category/western-filmleri-izle" : "Western",
34
40
  }
35
41
 
36
42
  async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
@@ -69,14 +75,13 @@ class FullHDFilm(PluginBase):
69
75
  if veri.css("img::attr(alt)").get()
70
76
  ]
71
77
 
72
- async def load_item(self, url: str) -> MovieInfo:
78
+ async def load_item(self, url: str) -> MovieInfo | SeriesInfo:
73
79
  istek = await self.httpx.get(url)
74
80
  secici = Selector(istek.text)
75
81
 
76
- title = secici.css("h1::text").get()
77
- poster = self.fix_url(secici.css("div.poster img::attr(src)").get())
78
- description = secici.css("div#details div.text::text").get() or \
79
- secici.css("div#details div::text").get()
82
+ title = secici.css("h1::text").get() or ""
83
+ title = title.strip() if title else ""
84
+ poster = self.fix_url(secici.css("div.poster img::attr(src)").get() or "")
80
85
 
81
86
  actors_text = secici.css("div.oyuncular.info::text").get()
82
87
  if actors_text:
@@ -89,20 +94,72 @@ class FullHDFilm(PluginBase):
89
94
  tags = secici.css("div.tur.info a::text").getall()
90
95
  rating = secici.css("div.imdb::text").re_first(r"IMDb\s*([\d\.]+)")
91
96
 
92
- # Açıklama usually above .others
97
+ # Description
93
98
  description = secici.xpath("//div[contains(@class, 'others')]/preceding-sibling::div[1]//text()").getall()
94
99
  description = "".join(description).strip() if description else None
95
100
 
96
- return MovieInfo(
97
- url = url,
98
- poster = poster,
99
- title = self.clean_title(title) if title else "",
100
- description = description,
101
- tags = tags,
102
- year = year,
103
- actors = actors,
104
- rating = rating.strip() if rating else None,
105
- )
101
+ # Kotlin referansı: URL'de -dizi kontrolü veya tags içinde "dizi" kontrolü
102
+ is_series = "-dizi" in url.lower() or any("dizi" in tag.lower() for tag in tags)
103
+
104
+ if is_series:
105
+ episodes = []
106
+ part_elements = secici.css("li.psec")
107
+ part_names = secici.css("li.psec a::text").getall()
108
+
109
+ # pdata değerlerini çıkar
110
+ pdata_matches = re.findall(r"pdata\['([^']+)'\]\s*=\s*'([^']+)'", istek.text)
111
+
112
+ for idx, (part_id, part_name) in enumerate(zip([el.css("::attr(id)").get() for el in part_elements], part_names)):
113
+ if not part_name:
114
+ continue
115
+
116
+ part_name = part_name.strip()
117
+
118
+ # Fragman'ları atla
119
+ if "fragman" in part_name.lower() or (part_id and "fragman" in part_id.lower()):
120
+ continue
121
+
122
+ # Sezon ve bölüm numarası çıkar
123
+ sz_match = re.search(r'(\d+)\s*sezon', part_id.lower() if part_id else "")
124
+ ep_match = re.search(r'^(\d+)\.', part_name)
125
+
126
+ sz_num = int(sz_match.group(1)) if sz_match else 1
127
+ ep_num = int(ep_match.group(1)) if ep_match else idx + 1
128
+
129
+ # pdata'dan video URL'si çık (varsa)
130
+ video_url = url # Varsayılan olarak ana URL kullan
131
+ if idx < len(pdata_matches):
132
+ video_url = pdata_matches[idx][1] if pdata_matches[idx][1] else url
133
+
134
+ episodes.append(Episode(
135
+ season = sz_num,
136
+ episode = ep_num,
137
+ title = f"{sz_num}. Sezon {ep_num}. Bölüm",
138
+ url = url # Bölüm URL'leri load_links'te işlenecek
139
+ ))
140
+
141
+ return SeriesInfo(
142
+ url = url,
143
+ poster = poster,
144
+ title = self.clean_title(title) if title else "",
145
+ description = description,
146
+ tags = tags,
147
+ year = year,
148
+ actors = actors,
149
+ rating = rating.strip() if rating else None,
150
+ episodes = episodes
151
+ )
152
+ else:
153
+ return MovieInfo(
154
+ url = url,
155
+ poster = poster,
156
+ title = self.clean_title(title) if title else "",
157
+ description = description,
158
+ tags = tags,
159
+ year = year,
160
+ actors = actors,
161
+ rating = rating.strip() if rating else None,
162
+ )
106
163
 
107
164
  def _get_iframe(self, source_code: str) -> str:
108
165
  """Base64 kodlu iframe'i çözümle"""
@@ -131,7 +188,7 @@ class FullHDFilm(PluginBase):
131
188
 
132
189
  return None
133
190
 
134
- async def load_links(self, url: str) -> list[dict]:
191
+ async def load_links(self, url: str) -> list[ExtractResult]:
135
192
  self.httpx.headers.update({
136
193
  "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
137
194
  "Referer" : self.main_url
@@ -155,7 +212,9 @@ class FullHDFilm(PluginBase):
155
212
  if iframe_src:
156
213
  data = await self.extract(iframe_src)
157
214
  if data:
158
- data["subtitles"] = [Subtitle(name="Türkçe", url=subtitle_url)] if subtitle_url else []
159
- results.append(data)
215
+ # ExtractResult objesi immutable, yeni bir kopya oluştur
216
+ subtitles = [Subtitle(name="Türkçe", url=subtitle_url)] if subtitle_url else []
217
+ updated_data = data.model_copy(update={"subtitles": subtitles}) if subtitles else data
218
+ results.append(updated_data)
160
219
 
161
220
  return results