KekikStream 2.0.2__py3-none-any.whl → 2.2.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 (54) hide show
  1. KekikStream/Core/Extractor/ExtractorBase.py +7 -2
  2. KekikStream/Core/Plugin/PluginBase.py +61 -13
  3. KekikStream/Extractors/CloseLoad.py +17 -2
  4. KekikStream/Extractors/ContentX.py +19 -2
  5. KekikStream/Extractors/DonilasPlay.py +86 -0
  6. KekikStream/Extractors/Filemoon.py +78 -0
  7. KekikStream/Extractors/MixTiger.py +5 -5
  8. KekikStream/Extractors/Odnoklassniki.py +6 -0
  9. KekikStream/Extractors/PeaceMakerst.py +6 -0
  10. KekikStream/Extractors/PlayerFilmIzle.py +8 -5
  11. KekikStream/Extractors/RapidVid.py +21 -5
  12. KekikStream/Extractors/SetPlay.py +13 -4
  13. KekikStream/Extractors/VCTPlay.py +41 -0
  14. KekikStream/Extractors/VidHide.py +11 -2
  15. KekikStream/Extractors/VidMoly.py +52 -30
  16. KekikStream/Extractors/YTDLP.py +88 -54
  17. KekikStream/Plugins/BelgeselX.py +196 -0
  18. KekikStream/Plugins/DiziBox.py +8 -12
  19. KekikStream/Plugins/DiziPal.py +11 -22
  20. KekikStream/Plugins/DiziYou.py +9 -17
  21. KekikStream/Plugins/Dizilla.py +19 -14
  22. KekikStream/Plugins/FilmBip.py +5 -8
  23. KekikStream/Plugins/FilmMakinesi.py +31 -21
  24. KekikStream/Plugins/FilmModu.py +14 -18
  25. KekikStream/Plugins/FullHDFilm.py +83 -27
  26. KekikStream/Plugins/FullHDFilmizlesene.py +6 -8
  27. KekikStream/Plugins/HDFilmCehennemi.py +133 -57
  28. KekikStream/Plugins/JetFilmizle.py +29 -14
  29. KekikStream/Plugins/KultFilmler.py +13 -15
  30. KekikStream/Plugins/RecTV.py +16 -24
  31. KekikStream/Plugins/RoketDizi.py +22 -32
  32. KekikStream/Plugins/SelcukFlix.py +99 -80
  33. KekikStream/Plugins/SetFilmIzle.py +252 -0
  34. KekikStream/Plugins/SezonlukDizi.py +43 -9
  35. KekikStream/Plugins/SineWix.py +13 -21
  36. KekikStream/Plugins/Sinefy.py +13 -10
  37. KekikStream/Plugins/SinemaCX.py +35 -38
  38. KekikStream/Plugins/Sinezy.py +5 -8
  39. KekikStream/Plugins/SuperFilmGeldi.py +36 -27
  40. KekikStream/Plugins/UgurFilm.py +7 -9
  41. KekikStream/__init__.py +17 -36
  42. {kekikstream-2.0.2.dist-info → kekikstream-2.2.0.dist-info}/METADATA +6 -3
  43. kekikstream-2.2.0.dist-info/RECORD +81 -0
  44. KekikStream/Extractors/ContentX_.py +0 -40
  45. KekikStream/Extractors/FirePlayer.py +0 -60
  46. KekikStream/Extractors/Odnoklassniki_.py +0 -11
  47. KekikStream/Extractors/PeaceMakerst_.py +0 -7
  48. KekikStream/Extractors/RapidVid_.py +0 -7
  49. KekikStream/Extractors/VidMoly_.py +0 -7
  50. kekikstream-2.0.2.dist-info/RECORD +0 -82
  51. {kekikstream-2.0.2.dist-info → kekikstream-2.2.0.dist-info}/WHEEL +0 -0
  52. {kekikstream-2.0.2.dist-info → kekikstream-2.2.0.dist-info}/entry_points.txt +0 -0
  53. {kekikstream-2.0.2.dist-info → kekikstream-2.2.0.dist-info}/licenses/LICENSE +0 -0
  54. {kekikstream-2.0.2.dist-info → kekikstream-2.2.0.dist-info}/top_level.txt +0 -0
@@ -9,6 +9,12 @@ class VidMoly(ExtractorBase):
9
9
  name = "VidMoly"
10
10
  main_url = "https://vidmoly.to"
11
11
 
12
+ # Birden fazla domain destekle
13
+ supported_domains = ["vidmoly.to", "vidmoly.me", "vidmoly.net"]
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
  async def extract(self, url: str, referer: str = None) -> ExtractResult:
13
19
  if referer:
14
20
  self.httpx.headers.update({"Referer": referer})
@@ -17,11 +23,11 @@ class VidMoly(ExtractorBase):
17
23
  "Sec-Fetch-Dest" : "iframe",
18
24
  })
19
25
 
20
- if self.main_url.endswith(".me"):
21
- self.main_url = self.main_url.replace(".me", ".net")
26
+ if ".me" in url:
22
27
  url = url.replace(".me", ".net")
23
28
 
24
- response = await self.httpx.get(url)
29
+ # VidMoly bazen redirect ediyor, takip et
30
+ response = await self.httpx.get(url, follow_redirects=True)
25
31
  if "Select number" in response.text:
26
32
  secici = Selector(response.text)
27
33
  response = await self.httpx.post(
@@ -33,21 +39,10 @@ class VidMoly(ExtractorBase):
33
39
  "ts" : secici.css("input[name='ts']::attr(value)").get(),
34
40
  "nonce" : secici.css("input[name='nonce']::attr(value)").get(),
35
41
  "ctok" : secici.css("input[name='ctok']::attr(value)").get()
36
- }
42
+ },
43
+ follow_redirects=True
37
44
  )
38
45
 
39
- script_match = re.search(r"sources:\s*\[(.*?)\],", response.text, re.DOTALL)
40
- script_content = script_match[1] if script_match else None
41
-
42
- if not script_content:
43
- raise ValueError("Gerekli script bulunamadı.")
44
-
45
- # Video kaynaklarını ayrıştır
46
- video_data = self._add_marks(script_content, "file")
47
- try:
48
- video_sources = json.loads(f"[{video_data}]")
49
- except json.JSONDecodeError as hata:
50
- raise ValueError("Video kaynakları ayrıştırılamadı.") from hata
51
46
 
52
47
  # Altyazı kaynaklarını ayrıştır
53
48
  subtitles = []
@@ -66,22 +61,49 @@ class VidMoly(ExtractorBase):
66
61
  for sub in subtitle_sources
67
62
  if sub.get("kind") == "captions"
68
63
  ]
69
- # İlk video kaynağını al
70
- video_url = None
71
- for source in video_sources:
72
- if file_url := source.get("file"):
73
- video_url = file_url
74
- break
75
64
 
76
- if not video_url:
77
- raise ValueError("Video URL bulunamadı.")
65
+ script_match = re.search(r"sources:\s*\[(.*?)\],", response.text, re.DOTALL)
66
+ if script_match:
67
+ script_content = script_match[1]
68
+ # Video kaynaklarını ayrıştır
69
+ video_data = self._add_marks(script_content, "file")
70
+ try:
71
+ video_sources = json.loads(f"[{video_data}]")
72
+ # İlk video kaynağını al
73
+ for source in video_sources:
74
+ if file_url := source.get("file"):
75
+ return ExtractResult(
76
+ name = self.name,
77
+ url = file_url,
78
+ referer = self.main_url,
79
+ subtitles = subtitles
80
+ )
81
+ except json.JSONDecodeError:
82
+ pass
83
+
84
+ # Fallback: Doğrudan file regex ile ara (Kotlin mantığı)
85
+ # file:"..." veya file: "..."
86
+ if file_match := re.search(r'file\s*:\s*["\']([^"\']+\.m3u8[^"\']*)["\']', response.text):
87
+ return ExtractResult(
88
+ name = self.name,
89
+ url = file_match.group(1),
90
+ referer = self.main_url,
91
+ subtitles = subtitles
92
+ )
93
+
94
+ # Fallback 2: Herhangi bir file (m3u8 olma şartı olmadan ama tercihen)
95
+ if file_match := re.search(r'file\s*:\s*["\']([^"\']+)["\']', response.text):
96
+ url_candidate = file_match.group(1)
97
+ # Resim dosyalarını hariç tut
98
+ if not url_candidate.endswith(('.jpg', '.png', '.jpeg')):
99
+ return ExtractResult(
100
+ name = self.name,
101
+ url = url_candidate,
102
+ referer = self.main_url,
103
+ subtitles = subtitles
104
+ )
78
105
 
79
- return ExtractResult(
80
- name = self.name,
81
- url = video_url,
82
- referer = self.main_url,
83
- subtitles = subtitles
84
- )
106
+ raise ValueError("Video URL bulunamadı.")
85
107
 
86
108
  def _add_marks(self, text: str, field: str) -> str:
87
109
  """
@@ -10,6 +10,86 @@ class YTDLP(ExtractorBase):
10
10
 
11
11
  _FAST_DOMAIN_RE = None # compiled mega-regex (host üstünden)
12
12
 
13
+ _POPULAR_TLDS = {
14
+ "com", "net", "org", "tv", "io", "co", "me", "ly", "ru", "fr", "de", "es", "it",
15
+ "nl", "be", "ch", "at", "uk", "ca", "au", "jp", "kr", "cn", "in", "br", "mx",
16
+ "ar", "tr", "gov", "edu", "mil", "int", "info", "biz", "name", "pro", "aero",
17
+ "coop", "museum", "onion"
18
+ }
19
+
20
+ # 1. Literal TLD Regex: youtube\.com, vimeo\.com
21
+ # sorted by reverse length to prevent partial matches (e.g. 'co' matching 'com')
22
+ _LITERAL_TLD_RE = re.compile(
23
+ rf"([a-z0-9][-a-z0-9]*(?:\\\.[-a-z0-9]+)*\\\.(?:{'|'.join(sorted(_POPULAR_TLDS, key=len, reverse=True))}))",
24
+ re.IGNORECASE
25
+ )
26
+
27
+ # 2. Regex TLD Regex: dailymotion\.[a-z]{2,3}
28
+ _REGEX_TLD_RE = re.compile(
29
+ r"([a-z0-9][-a-z0-9]*)\\\.\[a-z\]\{?\d*,?\d*\}?",
30
+ re.IGNORECASE
31
+ )
32
+
33
+ # 3. Alternation TLD Regex: \.(?:com|net|org)
34
+ _ALT_TLD_RE = re.compile(
35
+ r"\\\.\(\?:([a-z|]+)\)",
36
+ re.IGNORECASE
37
+ )
38
+
39
+ # Kelime yakalayıcı (domain bulmak için)
40
+ _DOMAIN_WORD_RE = re.compile(
41
+ r"([a-z0-9][-a-z0-9]*)",
42
+ re.IGNORECASE
43
+ )
44
+
45
+ @classmethod
46
+ def _extract_literal_domains(cls, valid_url: str) -> set[str]:
47
+ """Pattern 1: Literal TLD domainlerini (youtube.com) çıkarır."""
48
+ return {
49
+ m.replace(r"\.", ".").lower()
50
+ for m in cls._LITERAL_TLD_RE.findall(valid_url)
51
+ }
52
+
53
+ @classmethod
54
+ def _extract_regex_tld_domains(cls, valid_url: str) -> set[str]:
55
+ """Pattern 2: Regex TLD domainlerini (dailymotion.[...]) çıkarır ve popüler TLD'lerle birleştirir."""
56
+ domains = set()
57
+ for base in cls._REGEX_TLD_RE.findall(valid_url):
58
+ base_domain = base.lower()
59
+ for tld in cls._POPULAR_TLDS:
60
+ domains.add(f"{base_domain}.{tld}")
61
+ return domains
62
+
63
+ @classmethod
64
+ def _extract_alternation_domains(cls, valid_url: str) -> set[str]:
65
+ """Pattern 3: Alternation TLD domainlerini (pornhub.(?:com|net)) çıkarır."""
66
+ domains = set()
67
+ for m in cls._ALT_TLD_RE.finditer(valid_url):
68
+ tlds = m.group(1).split("|")
69
+ start = m.start()
70
+
71
+ # Geriye doğru git ve domain'i bul
72
+ before = valid_url[:start]
73
+
74
+ # 1. Named Groups (?P<name> temizle
75
+ before = re.sub(r"\(\?P<[^>]+>", "", before)
76
+
77
+ # 2. Simple Non-Capturing Groups (?:xxx)? temizle (sadece alphanumeric ve escape)
78
+ before = re.sub(r"\(\?:[a-z0-9-]+\)\??", "", before)
79
+
80
+ # Son domain-like kelimeyi al
81
+ words = cls._DOMAIN_WORD_RE.findall(before)
82
+ if not words:
83
+ continue
84
+
85
+ base = words[-1].lower()
86
+ for tld in tlds:
87
+ tld = tld.strip().lower()
88
+ if tld and len(tld) <= 6:
89
+ domains.add(f"{base}.{tld}")
90
+
91
+ return domains
92
+
13
93
  @classmethod
14
94
  def _init_fast_domain_regex(cls):
15
95
  """
@@ -19,44 +99,31 @@ class YTDLP(ExtractorBase):
19
99
  return
20
100
 
21
101
  domains = set()
22
-
23
- # Merkezi cache'den extractorları al
24
102
  extractors = get_ytdlp_extractors()
25
103
 
26
- # yt-dlp extractor'larının _VALID_URL regex'lerinden domain yakala
27
- # Regex metinlerinde domainler genelde "\." şeklinde geçer.
28
- domain_pat = re.compile(r"(?:[a-z0-9-]+\\\.)+[a-z]{2,}", re.IGNORECASE)
29
-
30
104
  for ie in extractors:
31
105
  valid = getattr(ie, "_VALID_URL", None)
32
106
  if not valid or not isinstance(valid, str):
33
107
  continue
34
108
 
35
- for m in domain_pat.findall(valid):
36
- d = m.replace(r"\.", ".").lower()
37
-
38
- # Çok agresif/şüpheli şeyleri elemek istersen burada filtre koyabilirsin
39
- # (genelde gerek kalmıyor)
40
- domains.add(d)
109
+ domains |= cls._extract_literal_domains(valid)
110
+ domains |= cls._extract_regex_tld_domains(valid)
111
+ domains |= cls._extract_alternation_domains(valid)
41
112
 
42
113
  # Hiç domain çıkmazsa (çok uç durum) fallback: boş regex
43
114
  if not domains:
44
115
  cls._FAST_DOMAIN_RE = re.compile(r"$^") # hiçbir şeye match etmez
45
116
  return
46
117
 
47
- # Host eşleştirmesi: subdomain destekli (m.youtube.com, player.vimeo.com vs.)
48
- # (?:^|.*\.) (domain1|domain2|...) $
49
- joined = "|".join(sorted(re.escape(d) for d in domains))
50
- pattern = rf"(?:^|.*\.)(?:{joined})$"
51
- cls._FAST_DOMAIN_RE = re.compile(pattern, re.IGNORECASE)
118
+ joined = "|".join(re.escape(d) for d in sorted(domains))
119
+ cls._FAST_DOMAIN_RE = re.compile(rf"(?:^|.*\.)(?:{joined})$", re.IGNORECASE)
52
120
 
53
121
  def __init__(self):
54
122
  self.__class__._init_fast_domain_regex()
55
123
 
56
124
  def can_handle_url(self, url: str) -> bool:
57
125
  """
58
- Fast-path: URL host'unu tek mega-regex ile kontrol et (loop yok)
59
- Slow-path: gerekirse mevcut extract_info tabanlı kontrolün
126
+ Fast-path: URL host'unu tek mega-regex ile kontrol et
60
127
  """
61
128
  # URL parse + host al
62
129
  try:
@@ -77,41 +144,8 @@ class YTDLP(ExtractorBase):
77
144
  if host and self.__class__._FAST_DOMAIN_RE.search(host):
78
145
  return True
79
146
 
80
- # SLOW PATH: Diğer siteler için yt-dlp'nin native kontrolü
81
- try:
82
- # stderr'ı geçici olarak kapat (hata mesajlarını gizle)
83
- old_stderr = sys.stderr
84
- sys.stderr = open(os.devnull, "w")
85
-
86
- try:
87
- ydl_opts = {
88
- "simulate" : True, # Download yok, sadece tespit
89
- "quiet" : True, # Log kirliliği yok
90
- "no_warnings" : True, # Uyarı mesajları yok
91
- "extract_flat" : True, # Minimal işlem
92
- "no_check_certificates" : True,
93
- "ignoreerrors" : True, # Hataları yoksay
94
- "socket_timeout" : 3,
95
- "retries" : 1
96
- }
97
-
98
- with yt_dlp.YoutubeDL(ydl_opts) as ydl:
99
- # URL'yi işleyebiliyor mu kontrol et
100
- info = ydl.extract_info(url, download=False, process=False)
101
-
102
- # Generic extractor ise atla
103
- if info and info.get("extractor_key") != "Generic":
104
- return True
105
-
106
- return False
107
- finally:
108
- # stderr'ı geri yükle
109
- sys.stderr.close()
110
- sys.stderr = old_stderr
111
-
112
- except Exception:
113
- # yt-dlp işleyemezse False döndür
114
- return False
147
+ # yt-dlp işleyemezse False döndür
148
+ return False
115
149
 
116
150
  async def extract(self, url: str, referer: str | None = None) -> ExtractResult:
117
151
  ydl_opts = {
@@ -0,0 +1,196 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode, ExtractResult
4
+ from parsel import Selector
5
+ import re
6
+
7
+ class BelgeselX(PluginBase):
8
+ name = "BelgeselX"
9
+ language = "tr"
10
+ main_url = "https://belgeselx.com"
11
+ favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
12
+ description = "2022 yılında son çıkan belgeselleri belgeselx.com'da izle. En yeni belgeseller, türkçe altyazılı yada dublaj olarak 1080p kalitesinde hd belgesel izle."
13
+
14
+ main_page = {
15
+ f"{main_url}/konu/turk-tarihi-belgeselleri&page=" : "Türk Tarihi",
16
+ f"{main_url}/konu/tarih-belgeselleri&page=" : "Tarih",
17
+ f"{main_url}/konu/seyehat-belgeselleri&page=" : "Seyahat",
18
+ f"{main_url}/konu/seri-belgeseller&page=" : "Seri",
19
+ f"{main_url}/konu/savas-belgeselleri&page=" : "Savaş",
20
+ f"{main_url}/konu/sanat-belgeselleri&page=" : "Sanat",
21
+ f"{main_url}/konu/psikoloji-belgeselleri&page=" : "Psikoloji",
22
+ f"{main_url}/konu/polisiye-belgeselleri&page=" : "Polisiye",
23
+ f"{main_url}/konu/otomobil-belgeselleri&page=" : "Otomobil",
24
+ f"{main_url}/konu/nazi-belgeselleri&page=" : "Nazi",
25
+ f"{main_url}/konu/muhendislik-belgeselleri&page=" : "Mühendislik",
26
+ f"{main_url}/konu/kultur-din-belgeselleri&page=" : "Kültür Din",
27
+ f"{main_url}/konu/kozmik-belgeseller&page=" : "Kozmik",
28
+ f"{main_url}/konu/hayvan-belgeselleri&page=" : "Hayvan",
29
+ f"{main_url}/konu/eski-tarih-belgeselleri&page=" : "Eski Tarih",
30
+ f"{main_url}/konu/egitim-belgeselleri&page=" : "Eğitim",
31
+ f"{main_url}/konu/dunya-belgeselleri&page=" : "Dünya",
32
+ f"{main_url}/konu/doga-belgeselleri&page=" : "Doğa",
33
+ f"{main_url}/konu/bilim-belgeselleri&page=" : "Bilim"
34
+ }
35
+
36
+ @staticmethod
37
+ def _to_title_case(text: str) -> str:
38
+ """Türkçe için title case dönüşümü."""
39
+ return " ".join(
40
+ word.lower().replace("i", "İ").capitalize() if word.lower().startswith("i") else word.capitalize()
41
+ for word in text.split()
42
+ )
43
+
44
+ async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
45
+ istek = self.cloudscraper.get(f"{url}{page}")
46
+ secici = Selector(istek.text)
47
+
48
+ results = []
49
+ for item in secici.css("div.gen-movie-contain > div.gen-info-contain > div.gen-movie-info"):
50
+ title = item.css("div.gen-movie-info > h3 a::text").get()
51
+ href = item.css("div.gen-movie-info > h3 a::attr(href)").get()
52
+ parent = item.xpath("../../..")
53
+ poster = parent.css("div.gen-movie-img > img::attr(src)").get()
54
+
55
+ if title and href:
56
+ results.append(MainPageResult(
57
+ category = category,
58
+ title = self._to_title_case(title.strip()),
59
+ url = self.fix_url(href),
60
+ poster = self.fix_url(poster) if poster else None
61
+ ))
62
+
63
+ return results
64
+
65
+ async def search(self, query: str) -> list[SearchResult]:
66
+ # Google Custom Search API kullanıyor
67
+ cx = "016376594590146270301:iwmy65ijgrm"
68
+
69
+ token_resp = self.cloudscraper.get(f"https://cse.google.com/cse.js?cx={cx}")
70
+ token_text = token_resp.text
71
+
72
+ cse_lib_match = re.search(r'cselibVersion": "(.*)"', token_text)
73
+ cse_tok_match = re.search(r'cse_token": "(.*)"', token_text)
74
+
75
+ if not cse_lib_match or not cse_tok_match:
76
+ return []
77
+
78
+ cse_lib = cse_lib_match.group(1)
79
+ cse_tok = cse_tok_match.group(1)
80
+
81
+ search_url = (
82
+ f"https://cse.google.com/cse/element/v1?"
83
+ f"rsz=filtered_cse&num=100&hl=tr&source=gcsc&cselibv={cse_lib}&cx={cx}"
84
+ f"&q={query}&safe=off&cse_tok={cse_tok}&sort=&exp=cc%2Capo&oq={query}"
85
+ f"&callback=google.search.cse.api9969&rurl=https%3A%2F%2Fbelgeselx.com%2F"
86
+ )
87
+
88
+ resp = self.cloudscraper.get(search_url)
89
+ resp_text = resp.text
90
+
91
+ titles = re.findall(r'"titleNoFormatting": "(.*?)"', resp_text)
92
+ urls = re.findall(r'"url": "(.*?)"', resp_text)
93
+ images = re.findall(r'"ogImage": "(.*?)"', resp_text)
94
+
95
+ results = []
96
+ for i, title in enumerate(titles):
97
+ url_val = urls[i] if i < len(urls) else None
98
+ poster = images[i] if i < len(images) else None
99
+
100
+ if not url_val or "diziresimleri" not in url_val:
101
+ # URL'den belgesel linkini oluştur
102
+ if poster and "diziresimleri" in poster:
103
+ file_name = poster.rsplit("/", 1)[-1]
104
+ file_name = re.sub(r"\.(jpe?g|png|webp)$", "", file_name)
105
+ url_val = f"{self.main_url}/belgeseldizi/{file_name}"
106
+ else:
107
+ continue
108
+
109
+ clean_title = title.split("İzle")[0].strip()
110
+ results.append(SearchResult(
111
+ title = self._to_title_case(clean_title),
112
+ url = url_val,
113
+ poster = poster
114
+ ))
115
+
116
+ return results
117
+
118
+ async def load_item(self, url: str) -> SeriesInfo:
119
+ istek = await self.httpx.get(url)
120
+ secici = Selector(istek.text)
121
+
122
+ title = secici.css("h2.gen-title::text").get()
123
+ poster = secici.css("div.gen-tv-show-top img::attr(src)").get()
124
+ description = secici.css("div.gen-single-tv-show-info p::text").get()
125
+
126
+ tags = []
127
+ for tag_link in secici.css("div.gen-socail-share a[href*='belgeselkanali']"):
128
+ tag_href = tag_link.css("::attr(href)").get()
129
+ if tag_href:
130
+ tag_name = tag_href.rsplit("/", 1)[-1].replace("-", " ")
131
+ tags.append(self._to_title_case(tag_name))
132
+
133
+ episodes = []
134
+ counter = 0
135
+ for ep_item in secici.css("div.gen-movie-contain"):
136
+ ep_name = ep_item.css("div.gen-movie-info h3 a::text").get()
137
+ ep_href = ep_item.css("div.gen-movie-info h3 a::attr(href)").get()
138
+
139
+ if not ep_name or not ep_href:
140
+ continue
141
+
142
+ season_text = ep_item.css("div.gen-single-meta-holder ul li::text").get() or ""
143
+ episode_match = re.search(r"Bölüm (\d+)", season_text)
144
+ season_match = re.search(r"Sezon (\d+)", season_text)
145
+
146
+ ep_episode = int(episode_match.group(1)) if episode_match else counter
147
+ ep_season = int(season_match.group(1)) if season_match else 1
148
+
149
+ counter += 1
150
+
151
+ episodes.append(Episode(
152
+ season = ep_season,
153
+ episode = ep_episode,
154
+ title = ep_name.strip(),
155
+ url = self.fix_url(ep_href)
156
+ ))
157
+
158
+ return SeriesInfo(
159
+ url = url,
160
+ poster = self.fix_url(poster) if poster else None,
161
+ title = self._to_title_case(title.strip()) if title else None,
162
+ description = description.strip() if description else None,
163
+ tags = tags,
164
+ episodes = episodes
165
+ )
166
+
167
+ async def load_links(self, url: str) -> list[ExtractResult]:
168
+ istek = await self.httpx.get(url)
169
+ text = istek.text
170
+
171
+ # fnc_addWatch div'inden data-episode ID'sini al
172
+ ep_match = re.search(r'<div[^>]*class=["\'][^"\']*fnc_addWatch[^"\']*["\'][^>]*data-episode=["\'](\d+)["\']', text)
173
+ if not ep_match:
174
+ return []
175
+
176
+ episode_id = ep_match.group(1)
177
+ iframe_url = f"{self.main_url}/video/data/new4.php?id={episode_id}"
178
+
179
+ iframe_resp = await self.httpx.get(iframe_url, headers={"Referer": url})
180
+ iframe_text = iframe_resp.text
181
+
182
+ links = []
183
+ for match in re.finditer(r'file:"([^"]+)", label: "([^"]+)"', iframe_text):
184
+ video_url = match.group(1)
185
+ quality = match.group(2)
186
+
187
+ source_name = "Google" if quality == "FULL" else self.name
188
+ quality_str = "1080p" if quality == "FULL" else quality
189
+
190
+ links.append(ExtractResult(
191
+ url = video_url,
192
+ name = f"{source_name} | {quality_str}",
193
+ referer = url
194
+ ))
195
+
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
 
@@ -182,11 +182,9 @@ class DiziBox(PluginBase):
182
182
  if main_iframe := secici.css("div#video-area iframe::attr(src)").get():
183
183
  if decoded := await self._iframe_decode(self.name, main_iframe, url):
184
184
  for iframe in decoded:
185
- extractor = self.ex_manager.find_extractor(iframe)
186
- results.append({
187
- "url" : iframe,
188
- "name" : f"{extractor.name if extractor else 'Main Player'}"
189
- })
185
+ data = await self.extract(iframe)
186
+ if data:
187
+ results.append(data)
190
188
 
191
189
  for alternatif in secici.css("div.video-toolbar option[value]"):
192
190
  alt_name = alternatif.css("::text").get()
@@ -203,10 +201,8 @@ class DiziBox(PluginBase):
203
201
  if alt_iframe := alt_secici.css("div#video-area iframe::attr(src)").get():
204
202
  if decoded := await self._iframe_decode(alt_name, alt_iframe, url):
205
203
  for iframe in decoded:
206
- extractor = self.ex_manager.find_extractor(iframe)
207
- results.append({
208
- "url" : iframe,
209
- "name" : f"{extractor.name if extractor else alt_name}"
210
- })
204
+ data = await self.extract(iframe, prefix=alt_name)
205
+ if data:
206
+ results.append(data)
211
207
 
212
208
  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, SeriesInfo, Episode, ExtractResult, 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,27 +220,16 @@ 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
- extractor = self.ex_manager.find_extractor(iframe)
232
- results.append({
233
- "name" : f"{extractor.name if extractor else self.name}",
234
- "url" : iframe,
235
- "referer" : f"{self.main_url}/",
236
- })
231
+ data = await self.extract(iframe)
232
+ if data:
233
+ results.append(data)
237
234
 
238
235
  return results
239
-
240
- async def play(self, **kwargs):
241
- extract_result = ExtractResult(**kwargs)
242
- self.media_handler.title = kwargs.get("name")
243
- if self.name not in self.media_handler.title:
244
- self.media_handler.title = f"{self.name} | {self.media_handler.title}"
245
-
246
- self.media_handler.play_media(extract_result)
@@ -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,19 +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
- })
188
-
189
- return results
190
-
191
- async def play(self, **kwargs):
192
- extract_result = ExtractResult(**kwargs)
193
- self.media_handler.title = kwargs.get("name")
194
- if self.name not in self.media_handler.title:
195
- self.media_handler.title = f"{self.name} | {self.media_handler.title}"
196
-
197
- self.media_handler.play_media(extract_result)
182
+ results.append(ExtractResult(
183
+ url = stream.get("url"),
184
+ name = f"{stream.get('dil')}",
185
+ referer = url,
186
+ subtitles = subtitles
187
+ ))
188
+
189
+ return results