KekikStream 2.2.8__py3-none-any.whl → 2.3.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.

Potentially problematic release.


This version of KekikStream might be problematic. Click here for more details.

Files changed (62) hide show
  1. KekikStream/Core/HTMLHelper.py +134 -0
  2. KekikStream/Core/Plugin/PluginBase.py +22 -4
  3. KekikStream/Core/Plugin/PluginLoader.py +3 -2
  4. KekikStream/Core/Plugin/PluginManager.py +2 -2
  5. KekikStream/Core/__init__.py +2 -0
  6. KekikStream/Extractors/CloseLoad.py +12 -13
  7. KekikStream/Extractors/ContentX.py +33 -31
  8. KekikStream/Extractors/DonilasPlay.py +10 -10
  9. KekikStream/Extractors/DzenRu.py +3 -3
  10. KekikStream/Extractors/ExPlay.py +10 -10
  11. KekikStream/Extractors/Filemoon.py +47 -37
  12. KekikStream/Extractors/JetTv.py +4 -4
  13. KekikStream/Extractors/MixPlayHD.py +10 -11
  14. KekikStream/Extractors/MolyStream.py +16 -9
  15. KekikStream/Extractors/Odnoklassniki.py +4 -4
  16. KekikStream/Extractors/PeaceMakerst.py +3 -3
  17. KekikStream/Extractors/PixelDrain.py +6 -5
  18. KekikStream/Extractors/PlayerFilmIzle.py +6 -10
  19. KekikStream/Extractors/RapidVid.py +8 -7
  20. KekikStream/Extractors/SetPlay.py +10 -10
  21. KekikStream/Extractors/SetPrime.py +3 -6
  22. KekikStream/Extractors/SibNet.py +4 -5
  23. KekikStream/Extractors/Sobreatsesuyp.py +5 -5
  24. KekikStream/Extractors/TRsTX.py +5 -5
  25. KekikStream/Extractors/TurboImgz.py +3 -4
  26. KekikStream/Extractors/TurkeyPlayer.py +5 -5
  27. KekikStream/Extractors/VidHide.py +4 -7
  28. KekikStream/Extractors/VidMoly.py +37 -25
  29. KekikStream/Extractors/VidMoxy.py +8 -9
  30. KekikStream/Extractors/VidPapi.py +5 -7
  31. KekikStream/Extractors/VideoSeyred.py +3 -3
  32. KekikStream/Plugins/BelgeselX.py +40 -51
  33. KekikStream/Plugins/DiziBox.py +53 -81
  34. KekikStream/Plugins/DiziPal.py +50 -72
  35. KekikStream/Plugins/DiziYou.py +96 -83
  36. KekikStream/Plugins/Dizilla.py +101 -86
  37. KekikStream/Plugins/FilmBip.py +29 -50
  38. KekikStream/Plugins/FilmMakinesi.py +84 -46
  39. KekikStream/Plugins/FilmModu.py +27 -41
  40. KekikStream/Plugins/FullHDFilm.py +57 -62
  41. KekikStream/Plugins/FullHDFilmizlesene.py +32 -57
  42. KekikStream/Plugins/HDFilmCehennemi.py +51 -65
  43. KekikStream/Plugins/JetFilmizle.py +38 -51
  44. KekikStream/Plugins/KultFilmler.py +43 -67
  45. KekikStream/Plugins/RecTV.py +34 -9
  46. KekikStream/Plugins/RoketDizi.py +89 -111
  47. KekikStream/Plugins/SelcukFlix.py +102 -93
  48. KekikStream/Plugins/SetFilmIzle.py +110 -117
  49. KekikStream/Plugins/SezonlukDizi.py +88 -106
  50. KekikStream/Plugins/Sinefy.py +70 -70
  51. KekikStream/Plugins/SinemaCX.py +31 -55
  52. KekikStream/Plugins/Sinezy.py +27 -54
  53. KekikStream/Plugins/SuperFilmGeldi.py +25 -44
  54. KekikStream/Plugins/UgurFilm.py +23 -48
  55. KekikStream/Plugins/YabanciDizi.py +285 -0
  56. {kekikstream-2.2.8.dist-info → kekikstream-2.3.9.dist-info}/METADATA +1 -1
  57. kekikstream-2.3.9.dist-info/RECORD +84 -0
  58. kekikstream-2.2.8.dist-info/RECORD +0 -82
  59. {kekikstream-2.2.8.dist-info → kekikstream-2.3.9.dist-info}/WHEEL +0 -0
  60. {kekikstream-2.2.8.dist-info → kekikstream-2.3.9.dist-info}/entry_points.txt +0 -0
  61. {kekikstream-2.2.8.dist-info → kekikstream-2.3.9.dist-info}/licenses/LICENSE +0 -0
  62. {kekikstream-2.2.8.dist-info → kekikstream-2.3.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,134 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from selectolax.parser import HTMLParser, Node
4
+ import re
5
+
6
+
7
+ class HTMLHelper:
8
+ """
9
+ Selectolax ile HTML parsing işlemlerini temiz, kısa ve okunabilir hale getiren yardımcı sınıf.
10
+ """
11
+
12
+ def __init__(self, html: str):
13
+ self.parser = HTMLParser(html)
14
+ self.html = html
15
+
16
+ # ========================
17
+ # TEMEL SELECTOR İŞLEMLERİ
18
+ # ========================
19
+
20
+ def _target(self, element: Node | None) -> Node | HTMLParser:
21
+ """İşlem yapılacak temel elementi döndürür."""
22
+ return element if element is not None else self.parser
23
+
24
+ def select(self, selector: str, element: Node | None = None) -> list[Node]:
25
+ """CSS selector ile tüm eşleşen elementleri döndür."""
26
+ return self._target(element).css(selector)
27
+
28
+ def select_first(self, selector: str | None, element: Node | None = None) -> Node | None:
29
+ """CSS selector ile ilk eşleşen elementi döndür."""
30
+ if not selector:
31
+ return element
32
+
33
+ return self._target(element).css_first(selector)
34
+
35
+ def select_text(self, selector: str | None = None, element: Node | None = None, strip: bool = True) -> str | None:
36
+ """CSS selector ile element bul ve text içeriğini döndür."""
37
+ el = self.select_first(selector, element)
38
+ if not el:
39
+ return None
40
+
41
+ val = el.text(strip=strip)
42
+ return val if val else None
43
+
44
+ def select_attr(self, selector: str | None, attr: str, element: Node | None = None) -> str | None:
45
+ """CSS selector ile element bul ve attribute değerini döndür."""
46
+ el = self.select_first(selector, element)
47
+ return el.attrs.get(attr) if el else None
48
+
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]:
57
+ """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
+ # ----------------------------------------------
64
+
65
+ def select_poster(self, selector: str = "img", element: Node | None = None) -> str | None:
66
+ """Poster URL'sini çıkar. Önce data-src, sonra src dener."""
67
+ el = self.select_first(selector, element)
68
+ if not el:
69
+ return None
70
+
71
+ return el.attrs.get("data-src") or el.attrs.get("src")
72
+
73
+ # ========================
74
+ # REGEX İŞLEMLERİ
75
+ # ========================
76
+
77
+ def _source(self, target: str | int | None) -> str:
78
+ """Regex için kaynak metni döndürür."""
79
+ return target if isinstance(target, str) else self.html
80
+
81
+ def _flags(self, target: str | int | None, flags: int) -> int:
82
+ """Regex flags değerini döndürür."""
83
+ return target if isinstance(target, int) else flags
84
+
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))
88
+ if not match:
89
+ return None
90
+
91
+ try:
92
+ return match.group(1)
93
+ except IndexError:
94
+ return match.group(0)
95
+
96
+ def regex_all(self, pattern: str, target: str | int | None = None, flags: int = 0) -> list[str]:
97
+ """Regex ile tüm eşleşmeleri döndür."""
98
+ return re.findall(pattern, self._source(target), self._flags(target, flags))
99
+
100
+ def regex_replace(self, pattern: str, repl: str, target: str | int | None = None, flags: int = 0) -> str:
101
+ """Regex ile replace yap."""
102
+ return re.sub(pattern, repl, self._source(target), flags)
103
+
104
+ # ========================
105
+ # ÖZEL AYIKLAYICILAR
106
+ # ========================
107
+
108
+ @staticmethod
109
+ def extract_season_episode(text: str) -> tuple[int | None, int | None]:
110
+ """Metin içinden sezon ve bölüm numarasını çıkar."""
111
+ # S01E05 formatı
112
+ if m := re.search(r"[Ss](\d+)[Ee](\d+)", text):
113
+ return int(m.group(1)), int(m.group(2))
114
+
115
+ # Ayrı ayrı ara
116
+ s = re.search(r"(\d+)\.\s*[Ss]ezon|[Ss]ezon[- ]?(\d+)|-(\d+)-sezon", text, re.I)
117
+ e = re.search(r"(\d+)\.\s*[Bb]ölüm|[Bb]olum[- ]?(\d+)|-(\d+)-bolum|[Ee](\d+)", text, re.I)
118
+
119
+ # İlk bulunan grubu al (None değilse)
120
+ s_val = next((int(g) for g in s.groups() if g), None) if s else None
121
+ e_val = next((int(g) for g in e.groups() if g), None) if e else None
122
+
123
+ return s_val, e_val
124
+
125
+ def extract_year(self, *selectors: str, pattern: str = r"(\d{4})") -> int | None:
126
+ """Birden fazla selector veya regex ile yıl bilgisini çıkar."""
127
+ for selector in selectors:
128
+ if text := self.select_text(selector):
129
+ if m := re.search(r"(\d{4})", text):
130
+ return int(m.group(1))
131
+
132
+ val = self.regex_first(pattern)
133
+ return int(val) if val and val.isdigit() else None
134
+
@@ -25,14 +25,22 @@ class PluginBase(ABC):
25
25
  self.main_page = {url.replace(self.main_url, new_url): category for url, category in self.main_page.items()}
26
26
  self.main_url = new_url
27
27
 
28
- def __init__(self):
28
+ def __init__(self, proxy: str | dict | None = None):
29
29
  # cloudscraper - for bypassing Cloudflare
30
30
  self.cloudscraper = CloudScraper()
31
+ if proxy:
32
+ self.cloudscraper.proxies = proxy if isinstance(proxy, dict) else {"http": proxy, "https": proxy}
33
+
34
+ # Convert dict proxy to string for httpx if necessary
35
+ httpx_proxy = proxy
36
+ if isinstance(proxy, dict):
37
+ httpx_proxy = proxy.get("https") or proxy.get("http")
31
38
 
32
39
  # httpx - lightweight and safe for most HTTP requests
33
40
  self.httpx = AsyncClient(
34
41
  timeout = 3,
35
- follow_redirects = True
42
+ follow_redirects = True,
43
+ proxy = httpx_proxy
36
44
  )
37
45
  self.httpx.headers.update(self.cloudscraper.headers)
38
46
  self.httpx.cookies.update(self.cloudscraper.cookies)
@@ -122,7 +130,14 @@ class PluginBase(ABC):
122
130
  try:
123
131
  data = await extractor.extract(url, referer=referer)
124
132
 
125
- # prefix varsa name'e ekle
133
+ # Liste ise her bir öğe için prefix ekle
134
+ if isinstance(data, list):
135
+ for item in data:
136
+ if prefix and item.name:
137
+ item.name = f"{prefix} | {item.name}"
138
+ return data
139
+
140
+ # Tekil öğe ise
126
141
  if prefix and data.name:
127
142
  data.name = f"{prefix} | {data.name}"
128
143
 
@@ -132,7 +147,10 @@ class PluginBase(ABC):
132
147
  return None
133
148
 
134
149
  @staticmethod
135
- def clean_title(title: str) -> str:
150
+ def clean_title(title: str | None) -> str | None:
151
+ if not title:
152
+ return None
153
+
136
154
  suffixes = [
137
155
  " izle",
138
156
  " full film",
@@ -6,8 +6,9 @@ from pathlib import Path
6
6
  import os, importlib.util, traceback
7
7
 
8
8
  class PluginLoader:
9
- def __init__(self, plugins_dir: str):
9
+ def __init__(self, plugins_dir: str, proxy: str | dict | None = None):
10
10
  # Yerel ve global eklenti dizinlerini ayarla
11
+ self.proxy = proxy
11
12
  self.local_plugins_dir = Path(plugins_dir).resolve()
12
13
  self.global_plugins_dir = Path(__file__).parent.parent.parent / plugins_dir
13
14
 
@@ -70,7 +71,7 @@ class PluginLoader:
70
71
  obj = getattr(module, attr)
71
72
  if isinstance(obj, type) and issubclass(obj, PluginBase) and obj is not PluginBase:
72
73
  # konsol.log(f"[yellow]Yüklenen sınıf\t\t: {module_name}.{obj.__name__} ({obj.__module__}.{obj.__name__})[/yellow]")
73
- return obj()
74
+ return obj(proxy=self.proxy)
74
75
 
75
76
  except Exception as hata:
76
77
  konsol.print(f"[red][!] Eklenti yüklenirken hata oluştu: {module_name}\nHata: {hata}")
@@ -4,9 +4,9 @@ from .PluginLoader import PluginLoader
4
4
  from .PluginBase import PluginBase
5
5
 
6
6
  class PluginManager:
7
- def __init__(self, plugin_dir="Plugins"):
7
+ def __init__(self, plugin_dir="Plugins", proxy: str | dict | None = None):
8
8
  # Eklenti yükleyiciyi başlat ve tüm eklentileri yükle
9
- self.plugin_loader = PluginLoader(plugin_dir)
9
+ self.plugin_loader = PluginLoader(plugin_dir, proxy=proxy)
10
10
  self.plugins = self.plugin_loader.load_all()
11
11
 
12
12
  def get_plugin_names(self):
@@ -17,3 +17,5 @@ from .Extractor.YTDLPCache import get_ytdlp_extractors
17
17
 
18
18
  from .Media.MediaManager import MediaManager
19
19
  from .Media.MediaHandler import MediaHandler
20
+
21
+ from .HTMLHelper import HTMLHelper
@@ -1,9 +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, Subtitle
3
+ from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, HTMLHelper
4
4
  from Kekik.Sifreleme import Packer, StreamDecoder
5
- from selectolax.parser import HTMLParser
6
- import re, json
5
+ import json
7
6
 
8
7
  class CloseLoadExtractor(ExtractorBase):
9
8
  name = "CloseLoad"
@@ -11,8 +10,8 @@ class CloseLoadExtractor(ExtractorBase):
11
10
 
12
11
  def _extract_from_json_ld(self, html: str) -> str | None:
13
12
  """JSON-LD script tag'inden contentUrl'i çıkar (Kotlin versiyonundaki gibi)"""
14
- secici = HTMLParser(html)
15
- for script in secici.css("script[type='application/ld+json']"):
13
+ secici = HTMLHelper(html)
14
+ for script in secici.select("script[type='application/ld+json']"):
16
15
  try:
17
16
  data = json.loads(script.text(strip=True))
18
17
  if content_url := data.get("contentUrl"):
@@ -20,17 +19,17 @@ class CloseLoadExtractor(ExtractorBase):
20
19
  return content_url
21
20
  except (json.JSONDecodeError, TypeError):
22
21
  # Regex ile contentUrl'i çıkarmayı dene
23
- match = re.search(r'"contentUrl"\s*:\s*"([^"]+)"', script.text())
24
- if match and match.group(1).startswith("http"):
25
- return match.group(1)
22
+ if content_url := secici.regex_first(r'"contentUrl"\s*:\s*"([^\"]+)"', script.text()):
23
+ if content_url.startswith("http"):
24
+ return content_url
26
25
  return None
27
26
 
28
27
  def _extract_from_packed(self, html: str) -> str | None:
29
28
  """Packed JavaScript'ten video URL'sini çıkar (fallback)"""
30
29
  try:
31
- eval_func = re.compile(r'\s*(eval\(function[\s\S].*)').findall(html)
32
- if eval_func:
33
- return StreamDecoder.extract_stream_url(Packer.unpack(eval_func[0]))
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]))
34
33
  except Exception:
35
34
  pass
36
35
  return None
@@ -59,8 +58,8 @@ class CloseLoadExtractor(ExtractorBase):
59
58
 
60
59
  # Subtitle'ları parse et (Kotlin referansı: track elementleri)
61
60
  subtitles = []
62
- secici = HTMLParser(istek.text)
63
- for track in secici.css("track"):
61
+ secici = HTMLHelper(istek.text)
62
+ for track in secici.select("track"):
64
63
  raw_src = track.attrs.get("src") or ""
65
64
  raw_src = raw_src.strip()
66
65
  label = track.attrs.get("label") or track.attrs.get("srclang") or "Altyazı"
@@ -1,7 +1,6 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
4
- import re
3
+ from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, HTMLHelper
5
4
 
6
5
  class ContentX(ExtractorBase):
7
6
  name = "ContentX"
@@ -31,15 +30,13 @@ class ContentX(ExtractorBase):
31
30
  istek.raise_for_status()
32
31
  i_source = istek.text
33
32
 
34
- i_extract = re.search(r"window\.openPlayer\('([^']+)'", i_source)
35
- if not i_extract:
33
+ i_extract_value = HTMLHelper(i_source).regex_first(r"window\.openPlayer\('([^']+)'")
34
+ if not i_extract_value:
36
35
  raise ValueError("i_extract is null")
37
- i_extract_value = i_extract[1]
38
36
 
39
37
  subtitles = []
40
38
  sub_urls = set()
41
- for match in re.finditer(r'"file":"([^"]+)","label":"([^"]+)"', i_source):
42
- sub_url, sub_lang = match.groups()
39
+ for sub_url, sub_lang in HTMLHelper(i_source).regex_all(r'"file":"([^\"]+)","label":"([^\"]+)"'):
43
40
 
44
41
  if sub_url in sub_urls:
45
42
  continue
@@ -50,8 +47,12 @@ class ContentX(ExtractorBase):
50
47
  name = sub_lang.replace("\\u0131", "ı")
51
48
  .replace("\\u0130", "İ")
52
49
  .replace("\\u00fc", "ü")
53
- .replace("\\u00e7", "ç"),
54
- url = self.fix_url(sub_url.replace("\\", ""))
50
+ .replace("\\u00e7", "ç")
51
+ .replace("\\u011f", "ğ")
52
+ .replace("\\u015f", "ş")
53
+ .replace("\\u011e", "Ğ")
54
+ .replace("\\u015e", "Ş"),
55
+ url = self.fix_url(sub_url.replace("\\/", "/").replace("\\", ""))
55
56
  )
56
57
  )
57
58
 
@@ -60,11 +61,11 @@ class ContentX(ExtractorBase):
60
61
  vid_source_request.raise_for_status()
61
62
 
62
63
  vid_source = vid_source_request.text
63
- vid_extract = re.search(r'file":"([^"]+)"', vid_source)
64
- if not vid_extract:
64
+ m3u_link = HTMLHelper(vid_source).regex_first(r'file":"([^\"]+)"')
65
+ if not m3u_link:
65
66
  raise ValueError("vidExtract is null")
66
67
 
67
- m3u_link = vid_extract[1].replace("\\", "")
68
+ m3u_link = m3u_link.replace("\\", "").replace("/m.php", "/master.m3u8")
68
69
  results = [
69
70
  ExtractResult(
70
71
  name = self.name,
@@ -74,24 +75,25 @@ class ContentX(ExtractorBase):
74
75
  )
75
76
  ]
76
77
 
77
- if i_dublaj := re.search(r',\"([^"]+)\",\"Türkçe"', i_source):
78
- dublaj_value = i_dublaj[1]
79
- dublaj_source_request = await self.httpx.get(f"{base_url}/source2.php?v={dublaj_value}", headers={"Referer": referer or base_url})
80
- dublaj_source_request.raise_for_status()
81
-
82
- dublaj_source = dublaj_source_request.text
83
- dublaj_extract = re.search(r'file":"([^"]+)"', dublaj_source)
84
- if not dublaj_extract:
85
- raise ValueError("dublajExtract is null")
86
-
87
- dublaj_link = dublaj_extract[1].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
- )
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
96
98
 
97
99
  return results[0] if len(results) == 1 else results
@@ -1,8 +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, Subtitle
3
+ from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, HTMLHelper
4
4
  from Kekik.Sifreleme import AESManager
5
- import re, json
5
+ import json
6
6
 
7
7
  class DonilasPlay(ExtractorBase):
8
8
  name = "DonilasPlay"
@@ -20,10 +20,10 @@ class DonilasPlay(ExtractorBase):
20
20
  subtitles = []
21
21
 
22
22
  # bePlayer pattern
23
- be_player_match = re.search(r"bePlayer\('([^']+)',\s*'(\{[^}]+\})'\);", i_source)
24
- if be_player_match:
25
- be_player_pass = be_player_match.group(1)
26
- be_player_data = be_player_match.group(2)
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
27
 
28
28
  try:
29
29
  # AES decrypt
@@ -54,15 +54,15 @@ class DonilasPlay(ExtractorBase):
54
54
 
55
55
  # Fallback: file pattern
56
56
  if not m3u_link:
57
- file_match = re.search(r'file:"([^"]+)"', i_source)
57
+ file_match = hp.regex_first(r'file:"([^"]+)"')
58
58
  if file_match:
59
- m3u_link = file_match.group(1)
59
+ m3u_link = file_match
60
60
 
61
61
  # tracks pattern for subtitles
62
- tracks_match = re.search(r'tracks:\[([^\]]+)', i_source)
62
+ tracks_match = hp.regex_first(r'tracks:\[([^\]]+)')
63
63
  if tracks_match:
64
64
  try:
65
- tracks_str = f"[{tracks_match.group(1)}]"
65
+ tracks_str = f"[{tracks_match}]"
66
66
  tracks = json.loads(tracks_str)
67
67
  for track in tracks:
68
68
  file_url = track.get("file")
@@ -1,7 +1,6 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import ExtractorBase, ExtractResult
4
- import re
3
+ from KekikStream.Core import ExtractorBase, ExtractResult, HTMLHelper
5
4
 
6
5
  class DzenRu(ExtractorBase):
7
6
  name = "DzenRu"
@@ -18,7 +17,8 @@ class DzenRu(ExtractorBase):
18
17
  istek.raise_for_status()
19
18
 
20
19
  # okcdn.ru linklerini bul
21
- matches = re.findall(r'https://vd\d+\.okcdn\.ru/\?[^"\'\\\s]+', istek.text)
20
+ hp = HTMLHelper(istek.text)
21
+ matches = hp.regex_all(r'https://vd\d+\.okcdn\.ru/\?[^"\'\\\s]+')
22
22
 
23
23
  if not matches:
24
24
  raise ValueError("DzenRu video link not found")
@@ -1,7 +1,6 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
4
- import re
3
+ from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, HTMLHelper
5
4
  from urllib.parse import urlparse, parse_qs
6
5
 
7
6
  class ExPlay(ExtractorBase):
@@ -23,21 +22,22 @@ class ExPlay(ExtractorBase):
23
22
  istek = await self.httpx.get(clean_url)
24
23
  istek.raise_for_status()
25
24
 
25
+ hp = HTMLHelper(istek.text)
26
+
26
27
  # videoUrl çıkar
27
- video_url_match = re.search(r'videoUrl":"([^",]+)"', istek.text)
28
- if not video_url_match:
28
+ video_url = hp.regex_first(r'videoUrl":"([^",]+)"')
29
+ if not video_url:
29
30
  raise ValueError("videoUrl not found")
30
- video_url = video_url_match[1].replace("\\", "")
31
+ video_url = video_url.replace("\\", "")
31
32
 
32
33
  # videoServer çıkar
33
- video_server_match = re.search(r'videoServer":"([^",]+)"', istek.text)
34
- if not video_server_match:
34
+ video_server = hp.regex_first(r'videoServer":"([^",]+)"')
35
+ if not video_server:
35
36
  raise ValueError("videoServer not found")
36
- video_server = video_server_match[1]
37
37
 
38
38
  # title çıkar
39
- title_match = re.search(r'title":"([^",]+)"', istek.text)
40
- title = title_match[1].split(".")[-1] if title_match else "Unknown"
39
+ title = hp.regex_first(r'title":"([^",]+)"')
40
+ title = title.split(".")[-1] if title else "Unknown"
41
41
 
42
42
  if part_key and "turkce" in part_key.lower():
43
43
  title = part_key # Or nicer formatting like SetPlay
@@ -1,9 +1,7 @@
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, HTMLHelper
4
4
  from Kekik.Sifreleme import Packer
5
- from selectolax.parser import HTMLParser
6
- import re
7
5
 
8
6
  class Filemoon(ExtractorBase):
9
7
  name = "Filemoon"
@@ -22,51 +20,62 @@ class Filemoon(ExtractorBase):
22
20
  return any(domain in url for domain in self.supported_domains)
23
21
 
24
22
  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",
23
+ default_headers = {
24
+ "Referer" : url,
25
+ "Sec-Fetch-Dest" : "iframe",
26
+ "Sec-Fetch-Mode" : "navigate",
27
+ "Sec-Fetch-Site" : "cross-site",
28
+ "User-Agent" : "Mozilla/5.0 (X11; Linux x86_64; rv:137.0) Gecko/20100101 Firefox/137.0"
30
29
  }
31
- self.httpx.headers.update(headers)
30
+ self.httpx.headers.update(default_headers)
32
31
 
33
32
  # İlk sayfayı al
34
33
  istek = await self.httpx.get(url)
35
34
  response = istek.text
36
- secici = HTMLParser(response)
35
+ secici = HTMLHelper(response)
37
36
 
38
37
  # Eğer iframe varsa, iframe'e git
39
- iframe_el = secici.css_first("iframe")
40
- iframe_src = iframe_el.attrs.get("src") if iframe_el else None
41
- if iframe_src:
38
+ iframe_src = secici.select_attr("iframe", "src")
39
+
40
+ m3u8_url = None
41
+
42
+ if not iframe_src:
43
+ # Fallback: Script içinde ara (Kotlin: selectFirst("script:containsData(function(p,a,c,k,e,d))"))
44
+ script_data = ""
45
+ for script in secici.css("script"):
46
+ if "function(p,a,c,k,e,d)" in script.text():
47
+ script_data = script.text()
48
+ break
49
+
50
+ if script_data:
51
+ unpacked = Packer.unpack(script_data)
52
+ unpacked_sec = HTMLHelper(unpacked)
53
+ m3u8_url = unpacked_sec.regex_first(r'sources:\[\{file:"(.*?)"')
54
+ else:
55
+ # Iframe varsa devam et
42
56
  iframe_url = self.fix_url(iframe_src)
43
- self.httpx.headers.update({
44
- "Accept-Language" : "en-US,en;q=0.5",
45
- "Sec-Fetch-Dest" : "iframe"
46
- })
47
- istek = await self.httpx.get(iframe_url)
57
+ iframe_headers = default_headers.copy()
58
+ iframe_headers["Accept-Language"] = "en-US,en;q=0.5"
59
+
60
+ istek = await self.httpx.get(iframe_url, headers=iframe_headers)
48
61
  response = istek.text
62
+ secici = HTMLHelper(response)
63
+
64
+ script_data = ""
65
+ for script in secici.select("script"):
66
+ if "function(p,a,c,k,e,d)" in script.text():
67
+ script_data = script.text()
68
+ break
69
+
70
+ if script_data:
71
+ unpacked = Packer.unpack(script_data)
72
+ unpacked_sec = HTMLHelper(unpacked)
73
+ m3u8_url = unpacked_sec.regex_first(r'sources:\[\{file:"(.*?)"')
49
74
 
50
- # Packed script'i bul ve unpack et
51
- m3u8_url = None
52
-
53
- if Packer.detect_packed(response):
54
- try:
55
- unpacked = Packer.unpack(response)
56
- # sources:[{file:"...» pattern'ını ara
57
- if match := re.search(r'sources:\s*\[\s*\{\s*file:\s*"([^"]+)"', unpacked):
58
- m3u8_url = match.group(1)
59
- elif match := re.search(r'file:\s*"([^"]*?\.m3u8[^"]*)"', unpacked):
60
- m3u8_url = match.group(1)
61
- except Exception:
62
- pass
63
-
64
- # Fallback: Doğrudan response'ta ara
65
75
  if not m3u8_url:
66
- if match := re.search(r'sources:\s*\[\s*\{\s*file:\s*"([^"]+)"', response):
67
- m3u8_url = match.group(1)
68
- elif match := re.search(r'file:\s*"([^"]*?\.m3u8[^"]*)"', response):
69
- m3u8_url = match.group(1)
76
+ # Son çare: Normal response içinde ara
77
+ resp_sec = HTMLHelper(response)
78
+ m3u8_url = resp_sec.regex_first(r'sources:\s*\[\s*\{\s*file:\s*"([^"]+)"') or resp_sec.regex_first(r'file:\s*"([^\"]*?\.m3u8[^"]*)"')
70
79
 
71
80
  if not m3u8_url:
72
81
  raise ValueError(f"Filemoon: Video URL bulunamadı. {url}")
@@ -75,5 +84,6 @@ class Filemoon(ExtractorBase):
75
84
  name = self.name,
76
85
  url = self.fix_url(m3u8_url),
77
86
  referer = f"{self.main_url}/",
87
+ user_agent = default_headers["User-Agent"],
78
88
  subtitles = []
79
89
  )
@@ -1,7 +1,7 @@
1
1
  # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
2
 
3
- from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
4
- import re, json
3
+ from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, HTMLHelper
4
+ import json
5
5
 
6
6
  class JetTv(ExtractorBase):
7
7
  name = "JetTv"
@@ -31,8 +31,8 @@ class JetTv(ExtractorBase):
31
31
 
32
32
  # 2. Yöntem: Regex Fallback
33
33
  if not master_url:
34
- if match := re.search(r"file: '([^']*)'", document, re.IGNORECASE):
35
- master_url = match.group(1)
34
+ hp = HTMLHelper(document)
35
+ master_url = hp.regex_first(r"(?i)file: '([^']*)'") or master_url
36
36
 
37
37
  if not master_url:
38
38
  raise ValueError(f"JetTv: Video kaynağı bulunamadı. {url}")