KekikStream 2.4.2__py3-none-any.whl → 2.4.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. KekikStream/Core/Extractor/ExtractorBase.py +3 -2
  2. KekikStream/Core/HTMLHelper.py +134 -40
  3. KekikStream/Core/Plugin/PluginBase.py +3 -2
  4. KekikStream/Extractors/CloseLoad.py +30 -54
  5. KekikStream/Extractors/ContentX.py +27 -72
  6. KekikStream/Extractors/DonilasPlay.py +33 -77
  7. KekikStream/Extractors/DzenRu.py +10 -24
  8. KekikStream/Extractors/ExPlay.py +20 -38
  9. KekikStream/Extractors/Filemoon.py +19 -45
  10. KekikStream/Extractors/HDMomPlayer.py +24 -56
  11. KekikStream/Extractors/HDPlayerSystem.py +13 -31
  12. KekikStream/Extractors/HotStream.py +14 -32
  13. KekikStream/Extractors/JFVid.py +3 -24
  14. KekikStream/Extractors/JetTv.py +21 -34
  15. KekikStream/Extractors/MailRu.py +11 -29
  16. KekikStream/Extractors/MixPlayHD.py +15 -28
  17. KekikStream/Extractors/MixTiger.py +17 -40
  18. KekikStream/Extractors/MolyStream.py +17 -21
  19. KekikStream/Extractors/Odnoklassniki.py +28 -104
  20. KekikStream/Extractors/PeaceMakerst.py +18 -45
  21. KekikStream/Extractors/PixelDrain.py +8 -16
  22. KekikStream/Extractors/PlayerFilmIzle.py +22 -41
  23. KekikStream/Extractors/RapidVid.py +21 -35
  24. KekikStream/Extractors/SetPlay.py +18 -43
  25. KekikStream/Extractors/SibNet.py +7 -17
  26. KekikStream/Extractors/Sobreatsesuyp.py +23 -45
  27. KekikStream/Extractors/TRsTX.py +23 -53
  28. KekikStream/Extractors/TurboImgz.py +7 -14
  29. KekikStream/Extractors/VCTPlay.py +10 -28
  30. KekikStream/Extractors/VidHide.py +10 -31
  31. KekikStream/Extractors/VidMoly.py +65 -99
  32. KekikStream/Extractors/VidMoxy.py +16 -27
  33. KekikStream/Extractors/VidPapi.py +24 -54
  34. KekikStream/Extractors/VideoSeyred.py +19 -40
  35. KekikStream/Extractors/Videostr.py +42 -99
  36. KekikStream/Extractors/Vidoza.py +8 -15
  37. KekikStream/Extractors/YildizKisaFilm.py +13 -31
  38. KekikStream/Plugins/BelgeselX.py +63 -69
  39. KekikStream/Plugins/DiziBox.py +16 -36
  40. KekikStream/Plugins/DiziMom.py +37 -129
  41. KekikStream/Plugins/DiziPal.py +26 -75
  42. KekikStream/Plugins/DiziYou.py +44 -152
  43. KekikStream/Plugins/Dizilla.py +18 -44
  44. KekikStream/Plugins/FilmBip.py +10 -24
  45. KekikStream/Plugins/FilmEkseni.py +12 -32
  46. KekikStream/Plugins/FilmMakinesi.py +24 -77
  47. KekikStream/Plugins/FilmModu.py +11 -18
  48. KekikStream/Plugins/Filmatek.py +13 -39
  49. KekikStream/Plugins/Full4kizle.py +33 -133
  50. KekikStream/Plugins/FullHDFilm.py +23 -93
  51. KekikStream/Plugins/FullHDFilmizlesene.py +10 -29
  52. KekikStream/Plugins/HDFilmCehennemi.py +27 -66
  53. KekikStream/Plugins/JetFilmizle.py +19 -20
  54. KekikStream/Plugins/KultFilmler.py +16 -50
  55. KekikStream/Plugins/RecTV.py +47 -85
  56. KekikStream/Plugins/SelcukFlix.py +29 -47
  57. KekikStream/Plugins/SetFilmIzle.py +28 -84
  58. KekikStream/Plugins/SezonlukDizi.py +27 -59
  59. KekikStream/Plugins/Sinefy.py +37 -100
  60. KekikStream/Plugins/SinemaCX.py +12 -18
  61. KekikStream/Plugins/Sinezy.py +11 -12
  62. KekikStream/Plugins/SuperFilmGeldi.py +8 -13
  63. KekikStream/Plugins/UgurFilm.py +14 -14
  64. KekikStream/Plugins/Watch32.py +42 -74
  65. KekikStream/Plugins/YabanciDizi.py +33 -87
  66. {kekikstream-2.4.2.dist-info → kekikstream-2.4.3.dist-info}/METADATA +1 -1
  67. kekikstream-2.4.3.dist-info/RECORD +93 -0
  68. kekikstream-2.4.2.dist-info/RECORD +0 -93
  69. {kekikstream-2.4.2.dist-info → kekikstream-2.4.3.dist-info}/WHEEL +0 -0
  70. {kekikstream-2.4.2.dist-info → kekikstream-2.4.3.dist-info}/entry_points.txt +0 -0
  71. {kekikstream-2.4.2.dist-info → kekikstream-2.4.3.dist-info}/licenses/LICENSE +0 -0
  72. {kekikstream-2.4.2.dist-info → kekikstream-2.4.3.dist-info}/top_level.txt +0 -0
@@ -49,6 +49,7 @@ class ExtractorBase(ABC):
49
49
  return ""
50
50
 
51
51
  if url.startswith("http") or url.startswith("{\""):
52
- return url
52
+ return url.replace("\\", "")
53
53
 
54
- return f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
54
+ url = f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
55
+ return url.replace("\\", "")
@@ -1,7 +1,9 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from selectolax.parser import HTMLParser, Node
3
+ from __future__ import annotations
4
+
4
5
  import re
6
+ from selectolax.parser import HTMLParser, Node
5
7
 
6
8
 
7
9
  class HTMLHelper:
@@ -10,96 +12,192 @@ class HTMLHelper:
10
12
  """
11
13
 
12
14
  def __init__(self, html: str):
13
- self.parser = HTMLParser(html)
14
15
  self.html = html
16
+ self.parser = HTMLParser(html)
15
17
 
16
18
  # ========================
17
- # TEMEL SELECTOR İŞLEMLERİ
19
+ # SELECTOR (CSS) İŞLEMLERİ
18
20
  # ========================
19
21
 
20
- def _target(self, element: Node | None) -> Node | HTMLParser:
22
+ def _root(self, element: Node | None) -> Node | HTMLParser:
21
23
  """İşlem yapılacak temel elementi döndürür."""
22
24
  return element if element is not None else self.parser
23
25
 
24
26
  def select(self, selector: str, element: Node | None = None) -> list[Node]:
25
27
  """CSS selector ile tüm eşleşen elementleri döndür."""
26
- return self._target(element).css(selector)
28
+ return self._root(element).css(selector)
27
29
 
28
30
  def select_first(self, selector: str | None, element: Node | None = None) -> Node | None:
29
31
  """CSS selector ile ilk eşleşen elementi döndür."""
30
32
  if not selector:
31
33
  return element
32
-
33
- return self._target(element).css_first(selector)
34
+ return self._root(element).css_first(selector)
34
35
 
35
36
  def select_text(self, selector: str | None = None, element: Node | None = None, strip: bool = True) -> str | None:
36
37
  """CSS selector ile element bul ve text içeriğini döndür."""
37
38
  el = self.select_first(selector, element)
38
39
  if not el:
39
40
  return None
40
-
41
41
  val = el.text(strip=strip)
42
- return val if val else None
42
+ return val or None
43
+
44
+ def select_texts(self, selector: str, element: Node | None = None, strip: bool = True) -> list[str]:
45
+ """CSS selector ile tüm eşleşen elementlerin text içeriklerini döndür."""
46
+ out: list[str] = []
47
+ for el in self.select(selector, element):
48
+ txt = el.text(strip=strip)
49
+ if txt:
50
+ out.append(txt)
51
+ return out
43
52
 
44
53
  def select_attr(self, selector: str | None, attr: str, element: Node | None = None) -> str | None:
45
54
  """CSS selector ile element bul ve attribute değerini döndür."""
46
55
  el = self.select_first(selector, element)
47
56
  return el.attrs.get(attr) if el else None
48
57
 
49
- def select_all_text(self, selector: str, element: Node | None = None, strip: bool = True) -> list[str]:
50
- """CSS selector ile tüm eşleşen elementlerin text içeriklerini döndür."""
51
- return [
52
- txt for el in self.select(selector, element)
53
- if (txt := el.text(strip=strip))
54
- ]
55
-
56
- def select_all_attr(self, selector: str, attr: str, element: Node | None = None) -> list[str]:
58
+ def select_attrs(self, selector: str, attr: str, element: Node | None = None) -> list[str]:
57
59
  """CSS selector ile tüm eşleşen elementlerin attribute değerlerini döndür."""
58
- return [
59
- val for el in self.select(selector, element)
60
- if (val := el.attrs.get(attr))
61
- ]
62
-
63
- # ----------------------------------------------
60
+ out: list[str] = []
61
+ for el in self.select(selector, element):
62
+ val = el.attrs.get(attr)
63
+ if val:
64
+ out.append(val)
65
+ return out
64
66
 
65
67
  def select_poster(self, selector: str = "img", element: Node | None = None) -> str | None:
66
68
  """Poster URL'sini çıkar. Önce data-src, sonra src dener."""
67
69
  el = self.select_first(selector, element)
68
70
  if not el:
69
71
  return None
70
-
71
72
  return el.attrs.get("data-src") or el.attrs.get("src")
72
73
 
74
+ def select_direct_text(self, selector: str, element: Node | None = None, strip: bool = True) -> str | None:
75
+ """
76
+ Elementin yalnızca "kendi" düz metnini döndürür (child elementlerin text'ini katmadan).
77
+ Selectolax sürüm farklarına göre deep=False dene, yoksa node sibling-walk ile fallback yapar.
78
+ """
79
+ el = self.select_first(selector, element)
80
+ if not el:
81
+ return None
82
+
83
+ # 1) Bazı sürümlerde var: sadece direct text
84
+ try:
85
+ val = el.text(strip=strip, deep=False) # type: ignore[call-arg]
86
+ return val or None
87
+ except TypeError:
88
+ pass # deep parametresi yok, fallback'e geç
89
+
90
+ # 2) Fallback: direct children'ı el.child + next ile dolaş
91
+ parts: list[str] = []
92
+ ch = el.child
93
+ while ch is not None:
94
+ if ch.tag == "-text":
95
+ t = ch.text(strip=strip)
96
+ if t:
97
+ parts.append(t)
98
+ elif ch.tag == "br":
99
+ parts.append("\n")
100
+ ch = ch.next
101
+
102
+ out = "".join(parts).strip()
103
+ return out or None
104
+
105
+ # ========================
106
+ # META (LABEL -> VALUE) İŞLEMLERİ
107
+ # ========================
108
+
109
+ def meta_value(self, label: str, container_selector: str | None = None, strip: bool = True) -> str | None:
110
+ """
111
+ Herhangi bir container içinde: LABEL metnini içeren bir elementten SONRA gelen metni döndürür.
112
+ label örn: "Oyuncular", "Yapım Yılı", "IMDB"
113
+ """
114
+ needle = label.casefold()
115
+
116
+ # Belirli bir container varsa içinde ara, yoksa tüm dökümanda
117
+ targets = self.select(container_selector) if container_selector else [self.parser.body]
118
+
119
+ for root in targets:
120
+ if not root: continue
121
+
122
+ # Kalın/vurgulu elementlerde (span, strong, b, label, dt) label'ı ara
123
+ for label_el in self.select("span, strong, b, label, dt", root):
124
+ txt = (label_el.text(strip=True) or "").casefold()
125
+ if needle not in txt:
126
+ continue
127
+
128
+ # 1) Elementin kendi içindeki text'te LABEL: VALUE formatı olabilir
129
+ # "Oyuncular: Brad Pitt" gibi. LABEL: sonrasını al.
130
+ full_txt = label_el.text(strip=strip)
131
+ if ":" in full_txt and needle in full_txt.split(":")[0].casefold():
132
+ val = full_txt.split(":", 1)[1].strip()
133
+ if val: return val
134
+
135
+ # 2) Label sonrası gelen ilk text node'u veya element'i al
136
+ curr = label_el.next
137
+ while curr:
138
+ if curr.tag == "-text":
139
+ val = curr.text(strip=strip).strip(" :")
140
+ if val: return val
141
+ elif curr.tag != "br":
142
+ val = curr.text(strip=strip).strip(" :")
143
+ if val: return val
144
+ else: # <br> gördüysek satır bitmiştir
145
+ break
146
+ curr = curr.next
147
+
148
+ return None
149
+
150
+ def meta_list(self, label: str, container_selector: str | None = None, sep: str = ",") -> list[str]:
151
+ """meta_value(...) çıktısını veya label'ın ebeveynindeki linkleri listeye döndürür."""
152
+ needle = label.casefold()
153
+ targets = self.select(container_selector) if container_selector else [self.parser.body]
154
+
155
+ for root in targets:
156
+ if not root: continue
157
+ for label_el in self.select("span, strong, b, label, dt", root):
158
+ if needle in (label_el.text(strip=True) or "").casefold():
159
+ # Eğer elementin ebeveyninde linkler varsa (Kutucuklu yapı), onları al
160
+ links = self.select_texts("a", label_el.parent)
161
+ if links: return links
162
+
163
+ # Yoksa düz metin olarak meta_value mantığıyla al
164
+ raw = self.meta_value(label, container_selector=container_selector)
165
+ if not raw: return []
166
+ return [x.strip() for x in raw.split(sep) if x.strip()]
167
+
168
+ return []
169
+
73
170
  # ========================
74
171
  # REGEX İŞLEMLERİ
75
172
  # ========================
76
173
 
77
- def _source(self, target: str | int | None) -> str:
174
+ def _regex_source(self, target: str | int | None) -> str:
78
175
  """Regex için kaynak metni döndürür."""
79
176
  return target if isinstance(target, str) else self.html
80
177
 
81
- def _flags(self, target: str | int | None, flags: int) -> int:
178
+ def _regex_flags(self, target: str | int | None, flags: int) -> int:
82
179
  """Regex flags değerini döndürür."""
83
180
  return target if isinstance(target, int) else flags
84
181
 
85
- def regex_first(self, pattern: str, target: str | int | None = None, flags: int = 0) -> str | None:
86
- """Regex ile arama yap, ilk grubu döndür (grup yoksa tamamını)."""
87
- match = re.search(pattern, self._source(target), self._flags(target, flags))
182
+ def regex_first(self, pattern: str, target: str | int | None = None, flags: int = 0, group: int | None = 1) -> str | tuple | None:
183
+ """Regex ile arama yap, istenen grubu döndür (group=None ise tüm grupları tuple olarak döndür)."""
184
+ match = re.search(pattern, self._regex_source(target), self._regex_flags(target, flags))
88
185
  if not match:
89
186
  return None
90
-
91
- try:
92
- return match.group(1)
93
- except IndexError:
94
- return match.group(0)
187
+
188
+ if group is None:
189
+ return match.groups()
190
+
191
+ last_idx = match.lastindex or 0
192
+ return match.group(group) if last_idx >= group else match.group(0)
95
193
 
96
194
  def regex_all(self, pattern: str, target: str | int | None = None, flags: int = 0) -> list[str]:
97
195
  """Regex ile tüm eşleşmeleri döndür."""
98
- return re.findall(pattern, self._source(target), self._flags(target, flags))
196
+ return re.findall(pattern, self._regex_source(target), self._regex_flags(target, flags))
99
197
 
100
198
  def regex_replace(self, pattern: str, repl: str, target: str | int | None = None, flags: int = 0) -> str:
101
199
  """Regex ile replace yap."""
102
- return re.sub(pattern, repl, self._source(target), flags)
200
+ return re.sub(pattern, repl, self._regex_source(target), flags=self._regex_flags(target, flags))
103
201
 
104
202
  # ========================
105
203
  # ÖZEL AYIKLAYICILAR
@@ -108,15 +206,12 @@ class HTMLHelper:
108
206
  @staticmethod
109
207
  def extract_season_episode(text: str) -> tuple[int | None, int | None]:
110
208
  """Metin içinden sezon ve bölüm numarasını çıkar."""
111
- # S01E05 formatı
112
209
  if m := re.search(r"[Ss](\d+)[Ee](\d+)", text):
113
210
  return int(m.group(1)), int(m.group(2))
114
211
 
115
- # Ayrı ayrı ara
116
212
  s = re.search(r"(\d+)\.\s*[Ss]ezon|[Ss]ezon[- ]?(\d+)|-(\d+)-sezon|S(\d+)|(\d+)\.[Ss]", text, re.I)
117
213
  e = re.search(r"(\d+)\.\s*[Bb][öo]l[üu]m|[Bb][öo]l[üu]m[- ]?(\d+)|-(\d+)-bolum|[Ee](\d+)", text, re.I)
118
214
 
119
- # İlk bulunan grubu al (None değilse)
120
215
  s_val = next((int(g) for g in s.groups() if g), None) if s else None
121
216
  e_val = next((int(g) for g in e.groups() if g), None) if e else None
122
217
 
@@ -131,4 +226,3 @@ class HTMLHelper:
131
226
 
132
227
  val = self.regex_first(pattern)
133
228
  return int(val) if val and val.isdigit() else None
134
-
@@ -101,9 +101,10 @@ class PluginBase(ABC):
101
101
  return ""
102
102
 
103
103
  if url.startswith("http") or url.startswith("{\""):
104
- return url
104
+ return url.replace("\\", "")
105
105
 
106
- return f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
106
+ url = f"https:{url}" if url.startswith("//") else urljoin(self.main_url, url)
107
+ return url.replace("\\", "")
107
108
 
108
109
  async def extract(self, url: str, referer: str = None, prefix: str | None = None) -> ExtractResult | None:
109
110
  """
@@ -2,75 +2,51 @@
2
2
 
3
3
  from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, HTMLHelper
4
4
  from Kekik.Sifreleme import Packer, StreamDecoder
5
- import json
5
+ import json, contextlib
6
6
 
7
- class CloseLoadExtractor(ExtractorBase):
7
+ class CloseLoad(ExtractorBase):
8
8
  name = "CloseLoad"
9
9
  main_url = "https://closeload.filmmakinesi.to"
10
10
 
11
- def _extract_from_json_ld(self, html: str) -> str | None:
12
- """JSON-LD script tag'inden contentUrl'i çıkar (Kotlin versiyonundaki gibi)"""
13
- secici = HTMLHelper(html)
14
- for script in secici.select("script[type='application/ld+json']"):
15
- try:
16
- data = json.loads(script.text(strip=True))
17
- if content_url := data.get("contentUrl"):
18
- if content_url.startswith("http"):
19
- return content_url
20
- except (json.JSONDecodeError, TypeError):
21
- # Regex ile contentUrl'i çıkarmayı dene
22
- if content_url := secici.regex_first(r'"contentUrl"\s*:\s*"([^\"]+)"', script.text()):
23
- if content_url.startswith("http"):
24
- return content_url
25
- return None
26
-
27
- def _extract_from_packed(self, html: str) -> str | None:
28
- """Packed JavaScript'ten video URL'sini çıkar (fallback)"""
29
- try:
30
- packed = HTMLHelper(html).regex_all(r'\s*(eval\(function[\s\S].*)')
31
- if packed:
32
- return StreamDecoder.extract_stream_url(Packer.unpack(packed[0]))
33
- except Exception:
34
- pass
35
- return None
36
-
37
- async def extract(self, url, referer=None) -> ExtractResult:
38
- if referer:
39
- self.httpx.headers.update({"Referer": referer})
40
-
11
+ async def extract(self, url: str, referer: str = None) -> ExtractResult:
41
12
  self.httpx.headers.update({
42
- "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.0.0",
43
- "Origin": self.main_url
13
+ "Referer" : referer or self.main_url,
14
+ "Origin" : self.main_url
44
15
  })
45
16
 
46
- istek = await self.httpx.get(url)
47
- istek.raise_for_status()
17
+ resp = await self.httpx.get(url)
18
+ sel = HTMLHelper(resp.text)
48
19
 
49
- # Önce JSON-LD'den dene (daha güvenilir - Kotlin versiyonu gibi)
50
- m3u_link = self._extract_from_json_ld(istek.text)
20
+ # 1. JSON-LD'den Dene
21
+ m3u8_url = None
22
+ for script in sel.select("script[type='application/ld+json']"):
23
+ with contextlib.suppress(Exception):
24
+ data = json.loads(script.text(strip=True))
25
+ if content_url := data.get("contentUrl"):
26
+ if content_url.startswith("http"):
27
+ m3u8_url = content_url
28
+ break
51
29
 
52
- # Fallback: Packed JavaScript'ten çıkar
53
- if not m3u_link:
54
- m3u_link = self._extract_from_packed(istek.text)
30
+ # 2. Packed Script Fallback
31
+ if not m3u8_url:
32
+ if packed := sel.regex_first(r"(eval\(function\(p,a,c,k,e,d\).+?)\s*</script>"):
33
+ m3u8_url = StreamDecoder.extract_stream_url(Packer.unpack(packed))
55
34
 
56
- if not m3u_link:
57
- raise Exception("Video URL bulunamadı (ne JSON-LD ne de packed script'ten)")
35
+ if not m3u8_url:
36
+ raise ValueError(f"CloseLoad: Video URL bulunamadı. {url}")
58
37
 
59
- # Subtitle'ları parse et (Kotlin referansı: track elementleri)
60
38
  subtitles = []
61
- secici = HTMLHelper(istek.text)
62
- for track in secici.select("track"):
63
- raw_src = track.attrs.get("src") or ""
64
- raw_src = raw_src.strip()
65
- label = track.attrs.get("label") or track.attrs.get("srclang") or "Altyazı"
66
-
67
- if raw_src:
68
- full_url = raw_src if raw_src.startswith("http") else f"{self.main_url}{raw_src}"
69
- subtitles.append(Subtitle(name=label, url=full_url))
39
+ for track in sel.select("track"):
40
+ src = track.attrs.get("src")
41
+ if src:
42
+ subtitles.append(Subtitle(
43
+ name = track.attrs.get("label") or track.attrs.get("srclang") or "Altyazı",
44
+ url = self.fix_url(src)
45
+ ))
70
46
 
71
47
  return ExtractResult(
72
48
  name = self.name,
73
- url = m3u_link,
49
+ url = m3u8_url,
74
50
  referer = self.main_url,
75
51
  subtitles = subtitles
76
52
  )
@@ -19,81 +19,36 @@ class ContentX(ExtractorBase):
19
19
  def can_handle_url(self, url: str) -> bool:
20
20
  return any(domain in url for domain in self.supported_domains)
21
21
 
22
- async def extract(self, url, referer=None) -> list[ExtractResult]:
23
- if referer:
24
- self.httpx.headers.update({"Referer": referer})
22
+ async def extract(self, url: str, referer: str = None) -> list[ExtractResult] | ExtractResult:
23
+ ref = referer or self.get_base_url(url)
24
+ self.httpx.headers.update({"Referer": ref})
25
25
 
26
- # Dinamik base URL kullan
27
- base_url = self.get_base_url(url)
26
+ resp = await self.httpx.get(url)
27
+ sel = HTMLHelper(resp.text)
28
28
 
29
- istek = await self.httpx.get(url)
30
- istek.raise_for_status()
31
- i_source = istek.text
32
-
33
- i_extract_value = HTMLHelper(i_source).regex_first(r"window\.openPlayer\('([^']+)'")
34
- if not i_extract_value:
35
- raise ValueError("i_extract is null")
29
+ v_id = sel.regex_first(r"window\.openPlayer\('([^']+)'")
30
+ if not v_id:
31
+ raise ValueError(f"ContentX: ID bulunamadı. {url}")
36
32
 
37
33
  subtitles = []
38
- sub_urls = set()
39
- for sub_url, sub_lang in HTMLHelper(i_source).regex_all(r'"file":"([^\"]+)","label":"([^\"]+)"'):
40
-
41
- if sub_url in sub_urls:
42
- continue
43
-
44
- sub_urls.add(sub_url)
45
- subtitles.append(
46
- Subtitle(
47
- name = sub_lang.replace("\\u0131", "ı")
48
- .replace("\\u0130", "İ")
49
- .replace("\\u00fc", "ü")
50
- .replace("\\u00e7", "ç")
51
- .replace("\\u011f", "ğ")
52
- .replace("\\u015f", "ş")
53
- .replace("\\u011e", "Ğ")
54
- .replace("\\u015e", "Ş"),
55
- url = self.fix_url(sub_url.replace("\\/", "/").replace("\\", ""))
56
- )
57
- )
58
-
59
- # base_url kullan (contentx.me yerine)
60
- vid_source_request = await self.httpx.get(f"{base_url}/source2.php?v={i_extract_value}", headers={"Referer": referer or base_url})
61
- vid_source_request.raise_for_status()
62
-
63
- vid_source = vid_source_request.text
64
- m3u_link = HTMLHelper(vid_source).regex_first(r'file":"([^\"]+)"')
65
- if not m3u_link:
66
- raise ValueError("vidExtract is null")
67
-
68
- m3u_link = m3u_link.replace("\\", "").replace("/m.php", "/master.m3u8")
69
- results = [
70
- ExtractResult(
71
- name = self.name,
72
- url = m3u_link,
73
- referer = url,
74
- subtitles = subtitles
75
- )
76
- ]
77
-
78
- dublaj_value = HTMLHelper(i_source).regex_first(r'["\']([^"\']+)["\'],["\']Türkçe["\']')
79
- if dublaj_value:
80
- try:
81
- dublaj_source_request = await self.httpx.get(f"{base_url}/source2.php?v={dublaj_value}", headers={"Referer": referer or base_url})
82
- dublaj_source_request.raise_for_status()
83
-
84
- dublaj_source = dublaj_source_request.text
85
- dublaj_link = HTMLHelper(dublaj_source).regex_first(r'file":"([^\"]+)"')
86
- if dublaj_link:
87
- dublaj_link = dublaj_link.replace("\\", "")
88
- results.append(
89
- ExtractResult(
90
- name = f"{self.name} Türkçe Dublaj",
91
- url = dublaj_link,
92
- referer = url,
93
- subtitles = []
94
- )
95
- )
96
- except Exception:
97
- pass
34
+ for s_url, s_lang in sel.regex_all(r'"file":"([^\"]+)","label":"([^\"]+)"'):
35
+ decoded_lang = s_lang.encode().decode('unicode_escape')
36
+ subtitles.append(Subtitle(name=decoded_lang, url=self.fix_url(s_url.replace("\\", ""))))
37
+
38
+ results = []
39
+ # Base m3u8
40
+ vid_resp = await self.httpx.get(f"{self.get_base_url(url)}/source2.php?v={v_id}", headers={"Referer": url})
41
+ if m3u8_link := HTMLHelper(vid_resp.text).regex_first(r'file":"([^\"]+)"'):
42
+ m3u8_link = m3u8_link.replace("\\", "").replace("/m.php", "/master.m3u8")
43
+ results.append(ExtractResult(name=self.name, url=m3u8_link, referer=url, subtitles=subtitles))
44
+
45
+ # Dublaj Kontrolü
46
+ if dub_id := sel.regex_first(r'["\']([^"\']+)["\'],["\']Türkçe["\']'):
47
+ dub_resp = await self.httpx.get(f"{self.get_base_url(url)}/source2.php?v={dub_id}", headers={"Referer": url})
48
+ if dub_link := HTMLHelper(dub_resp.text).regex_first(r'file":"([^\"]+)"'):
49
+ results.append(ExtractResult(name=f"{self.name} Türkçe Dublaj", url=dub_link.replace("\\", ""), referer=url))
50
+
51
+ if not results:
52
+ raise ValueError(f"ContentX: Video linki bulunamadı. {url}")
98
53
 
99
54
  return results[0] if len(results) == 1 else results
@@ -2,85 +2,41 @@
2
2
 
3
3
  from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, HTMLHelper
4
4
  from Kekik.Sifreleme import AESManager
5
- import json
5
+ import json, contextlib
6
6
 
7
7
  class DonilasPlay(ExtractorBase):
8
8
  name = "DonilasPlay"
9
9
  main_url = "https://donilasplay.com"
10
10
 
11
- async def extract(self, url, referer=None) -> ExtractResult:
12
- if referer:
13
- self.httpx.headers.update({"Referer": referer})
14
-
15
- istek = await self.httpx.get(url)
16
- istek.raise_for_status()
17
- i_source = istek.text
18
-
19
- m3u_link = None
20
- subtitles = []
21
-
22
- # bePlayer pattern
23
- hp = HTMLHelper(i_source)
24
- be_player_matches = hp.regex_all(r"bePlayer\('([^']+)',\s*'(\{[^}]+\})'\);")
25
- if be_player_matches:
26
- be_player_pass, be_player_data = be_player_matches[0]
27
-
28
- try:
29
- # AES decrypt
30
- decrypted = AESManager.decrypt(be_player_data, be_player_pass)
31
- data = json.loads(decrypted)
32
-
33
- m3u_link = data.get("video_location")
34
-
35
- # Altyazıları işle
36
- str_subtitles = data.get("strSubtitles", [])
37
- if str_subtitles:
38
- for sub in str_subtitles:
39
- label = sub.get("label", "")
40
- file = sub.get("file", "")
41
- # Forced altyazıları hariç tut
42
- if "Forced" in label:
43
- continue
44
- if file:
45
- # Türkçe kontrolü
46
- keywords = ["tur", "tr", "türkçe", "turkce"]
47
- language = "Turkish" if any(k in label.lower() for k in keywords) else label
48
- subtitles.append(Subtitle(
49
- name = language,
50
- url = self.fix_url(file)
51
- ))
52
- except Exception:
53
- pass
54
-
55
- # Fallback: file pattern
56
- if not m3u_link:
57
- file_match = hp.regex_first(r'file:"([^"]+)"')
58
- if file_match:
59
- m3u_link = file_match
60
-
61
- # tracks pattern for subtitles
62
- tracks_match = hp.regex_first(r'tracks:\[([^\]]+)')
63
- if tracks_match:
64
- try:
65
- tracks_str = f"[{tracks_match}]"
66
- tracks = json.loads(tracks_str)
67
- for track in tracks:
68
- file_url = track.get("file")
69
- label = track.get("label", "")
70
- if file_url and "Forced" not in label:
71
- subtitles.append(Subtitle(
72
- name = label,
73
- url = self.fix_url(file_url)
74
- ))
75
- except Exception:
76
- pass
77
-
78
- if not m3u_link:
79
- raise ValueError("m3u link not found")
80
-
81
- return ExtractResult(
82
- name = self.name,
83
- url = m3u_link,
84
- referer = url,
85
- subtitles = subtitles
86
- )
11
+ async def extract(self, url: str, referer: str = None) -> ExtractResult:
12
+ self.httpx.headers.update({"Referer": referer or url})
13
+
14
+ resp = await self.httpx.get(url)
15
+ sel = HTMLHelper(resp.text)
16
+
17
+ m3u8_url = None
18
+ subtitles = []
19
+
20
+ # 1. bePlayer (AES)
21
+ if be_match := sel.regex_first(r"bePlayer\('([^']+)',\s*'(\{[^}]+\})'\);", group=None):
22
+ pass_val, data_val = be_match
23
+ with contextlib.suppress(Exception):
24
+ data = json.loads(AESManager.decrypt(data_val, pass_val))
25
+ m3u8_url = data.get("video_location")
26
+ for sub in data.get("strSubtitles", []):
27
+ if "Forced" not in sub.get("label", ""):
28
+ subtitles.append(Subtitle(name=sub.get("label"), url=self.fix_url(sub.get("file"))))
29
+
30
+ # 2. Fallback
31
+ if not m3u8_url:
32
+ m3u8_url = sel.regex_first(r'file:"([^"]+)"')
33
+ if tracks_match := sel.regex_first(r'tracks:\[([^\]]+)'):
34
+ with contextlib.suppress(Exception):
35
+ for track in json.loads(f"[{tracks_match}]"):
36
+ if "Forced" not in track.get("label", ""):
37
+ subtitles.append(Subtitle(name=track.get("label"), url=self.fix_url(track.get("file"))))
38
+
39
+ if not m3u8_url:
40
+ raise ValueError(f"DonilasPlay: Video linki bulunamadı. {url}")
41
+
42
+ return ExtractResult(name=self.name, url=m3u8_url, referer=url, subtitles=subtitles)