KekikStream 2.1.7__tar.gz → 2.3.6__tar.gz

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 (98) hide show
  1. kekikstream-2.3.6/KekikStream/Core/HTMLHelper.py +134 -0
  2. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Core/Plugin/PluginBase.py +17 -4
  3. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Core/Plugin/PluginLoader.py +3 -2
  4. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Core/Plugin/PluginManager.py +2 -2
  5. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Core/__init__.py +2 -0
  6. kekikstream-2.3.6/KekikStream/Extractors/CloseLoad.py +76 -0
  7. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/ContentX.py +33 -31
  8. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/DonilasPlay.py +10 -10
  9. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/DzenRu.py +3 -3
  10. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/ExPlay.py +10 -10
  11. kekikstream-2.3.6/KekikStream/Extractors/Filemoon.py +89 -0
  12. kekikstream-2.3.6/KekikStream/Extractors/JFVid.py +40 -0
  13. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/JetTv.py +4 -4
  14. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/MixPlayHD.py +10 -11
  15. kekikstream-2.3.6/KekikStream/Extractors/MolyStream.py +41 -0
  16. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/Odnoklassniki.py +4 -4
  17. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/PeaceMakerst.py +3 -3
  18. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/PixelDrain.py +6 -5
  19. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/PlayerFilmIzle.py +11 -11
  20. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/RapidVid.py +22 -11
  21. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/SetPlay.py +10 -10
  22. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/SetPrime.py +3 -6
  23. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/SibNet.py +4 -5
  24. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/Sobreatsesuyp.py +5 -5
  25. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/TRsTX.py +5 -5
  26. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/TurboImgz.py +3 -4
  27. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/TurkeyPlayer.py +5 -5
  28. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/VidHide.py +15 -10
  29. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/VidMoly.py +26 -19
  30. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/VidMoxy.py +8 -9
  31. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/VidPapi.py +5 -7
  32. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/VideoSeyred.py +3 -3
  33. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/BelgeselX.py +51 -43
  34. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/DiziBox.py +93 -65
  35. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/DiziPal.py +64 -50
  36. kekikstream-2.3.6/KekikStream/Plugins/DiziWatch.py +212 -0
  37. kekikstream-2.3.6/KekikStream/Plugins/DiziYou.py +247 -0
  38. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/Dizilla.py +121 -68
  39. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/FilmBip.py +38 -34
  40. kekikstream-2.3.6/KekikStream/Plugins/FilmMakinesi.py +193 -0
  41. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/FilmModu.py +64 -41
  42. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/FullHDFilm.py +87 -58
  43. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/FullHDFilmizlesene.py +79 -40
  44. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/HDFilmCehennemi.py +111 -104
  45. kekikstream-2.3.6/KekikStream/Plugins/JetFilmizle.py +174 -0
  46. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/KultFilmler.py +56 -51
  47. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/RecTV.py +7 -4
  48. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/RoketDizi.py +35 -38
  49. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/SelcukFlix.py +16 -17
  50. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/SetFilmIzle.py +104 -99
  51. kekikstream-2.3.6/KekikStream/Plugins/SezonlukDizi.py +214 -0
  52. kekikstream-2.3.6/KekikStream/Plugins/Sinefy.py +270 -0
  53. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/SinemaCX.py +69 -48
  54. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/Sinezy.py +53 -66
  55. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/SuperFilmGeldi.py +56 -39
  56. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/UgurFilm.py +48 -35
  57. kekikstream-2.3.6/KekikStream/Plugins/YabanciDizi.py +274 -0
  58. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/requirements.txt +1 -1
  59. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream.egg-info/PKG-INFO +40 -32
  60. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream.egg-info/SOURCES.txt +6 -1
  61. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream.egg-info/requires.txt +2 -2
  62. {kekikstream-2.1.7 → kekikstream-2.3.6}/PKG-INFO +40 -32
  63. {kekikstream-2.1.7 → kekikstream-2.3.6}/README.md +37 -29
  64. {kekikstream-2.1.7 → kekikstream-2.3.6}/setup.py +3 -3
  65. kekikstream-2.1.7/KekikStream/Extractors/CloseLoad.py +0 -41
  66. kekikstream-2.1.7/KekikStream/Extractors/MolyStream.py +0 -34
  67. kekikstream-2.1.7/KekikStream/Plugins/DiziYou.py +0 -189
  68. kekikstream-2.1.7/KekikStream/Plugins/FilmMakinesi.py +0 -131
  69. kekikstream-2.1.7/KekikStream/Plugins/JetFilmizle.py +0 -145
  70. kekikstream-2.1.7/KekikStream/Plugins/SezonlukDizi.py +0 -159
  71. kekikstream-2.1.7/KekikStream/Plugins/Sinefy.py +0 -241
  72. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/CLI/__init__.py +0 -0
  73. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/CLI/pypi_kontrol.py +0 -0
  74. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Core/Extractor/ExtractorBase.py +0 -0
  75. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Core/Extractor/ExtractorLoader.py +0 -0
  76. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Core/Extractor/ExtractorManager.py +0 -0
  77. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Core/Extractor/ExtractorModels.py +0 -0
  78. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Core/Extractor/YTDLPCache.py +0 -0
  79. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Core/Media/MediaHandler.py +0 -0
  80. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Core/Media/MediaManager.py +0 -0
  81. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Core/Plugin/PluginModels.py +0 -0
  82. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Core/UI/UIManager.py +0 -0
  83. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/HDPlayerSystem.py +0 -0
  84. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/MailRu.py +0 -0
  85. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/MixTiger.py +0 -0
  86. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/TauVideo.py +0 -0
  87. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/VCTPlay.py +0 -0
  88. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/YTDLP.py +0 -0
  89. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Extractors/YildizKisaFilm.py +0 -0
  90. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/Plugins/SineWix.py +0 -0
  91. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/__init__.py +0 -0
  92. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream/__main__.py +0 -0
  93. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream.egg-info/dependency_links.txt +0 -0
  94. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream.egg-info/entry_points.txt +0 -0
  95. {kekikstream-2.1.7 → kekikstream-2.3.6}/KekikStream.egg-info/top_level.txt +0 -0
  96. {kekikstream-2.1.7 → kekikstream-2.3.6}/LICENSE +0 -0
  97. {kekikstream-2.1.7 → kekikstream-2.3.6}/MANIFEST.in +0 -0
  98. {kekikstream-2.1.7 → kekikstream-2.3.6}/setup.cfg +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,17 @@ 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}
31
33
 
32
34
  # httpx - lightweight and safe for most HTTP requests
33
35
  self.httpx = AsyncClient(
34
36
  timeout = 3,
35
- follow_redirects = True
37
+ follow_redirects = True,
38
+ proxy = proxy
36
39
  )
37
40
  self.httpx.headers.update(self.cloudscraper.headers)
38
41
  self.httpx.cookies.update(self.cloudscraper.cookies)
@@ -122,7 +125,14 @@ class PluginBase(ABC):
122
125
  try:
123
126
  data = await extractor.extract(url, referer=referer)
124
127
 
125
- # prefix varsa name'e ekle
128
+ # Liste ise her bir öğe için prefix ekle
129
+ if isinstance(data, list):
130
+ for item in data:
131
+ if prefix and item.name:
132
+ item.name = f"{prefix} | {item.name}"
133
+ return data
134
+
135
+ # Tekil öğe ise
126
136
  if prefix and data.name:
127
137
  data.name = f"{prefix} | {data.name}"
128
138
 
@@ -132,7 +142,10 @@ class PluginBase(ABC):
132
142
  return None
133
143
 
134
144
  @staticmethod
135
- def clean_title(title: str) -> str:
145
+ def clean_title(title: str | None) -> str | None:
146
+ if not title:
147
+ return None
148
+
136
149
  suffixes = [
137
150
  " izle",
138
151
  " 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
@@ -0,0 +1,76 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, HTMLHelper
4
+ from Kekik.Sifreleme import Packer, StreamDecoder
5
+ import json
6
+
7
+ class CloseLoadExtractor(ExtractorBase):
8
+ name = "CloseLoad"
9
+ main_url = "https://closeload.filmmakinesi.to"
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
+
41
+ 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
44
+ })
45
+
46
+ istek = await self.httpx.get(url)
47
+ istek.raise_for_status()
48
+
49
+ # Önce JSON-LD'den dene (daha güvenilir - Kotlin versiyonu gibi)
50
+ m3u_link = self._extract_from_json_ld(istek.text)
51
+
52
+ # Fallback: Packed JavaScript'ten çıkar
53
+ if not m3u_link:
54
+ m3u_link = self._extract_from_packed(istek.text)
55
+
56
+ if not m3u_link:
57
+ raise Exception("Video URL bulunamadı (ne JSON-LD ne de packed script'ten)")
58
+
59
+ # Subtitle'ları parse et (Kotlin referansı: track elementleri)
60
+ 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))
70
+
71
+ return ExtractResult(
72
+ name = self.name,
73
+ url = m3u_link,
74
+ referer = self.main_url,
75
+ subtitles = subtitles
76
+ )
@@ -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
@@ -0,0 +1,89 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import ExtractorBase, ExtractResult, HTMLHelper
4
+ from Kekik.Sifreleme import Packer
5
+
6
+ class Filemoon(ExtractorBase):
7
+ name = "Filemoon"
8
+ main_url = "https://filemoon.to"
9
+
10
+ # Filemoon'un farklı domainlerini destekle
11
+ supported_domains = [
12
+ "filemoon.to",
13
+ "filemoon.in",
14
+ "filemoon.sx",
15
+ "filemoon.nl",
16
+ "filemoon.com"
17
+ ]
18
+
19
+ def can_handle_url(self, url: str) -> bool:
20
+ return any(domain in url for domain in self.supported_domains)
21
+
22
+ async def extract(self, url: str, referer: str = None) -> ExtractResult:
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"
29
+ }
30
+ self.httpx.headers.update(default_headers)
31
+
32
+ # İlk sayfayı al
33
+ istek = await self.httpx.get(url)
34
+ response = istek.text
35
+ secici = HTMLHelper(response)
36
+
37
+ # Eğer iframe varsa, iframe'e git
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
56
+ iframe_url = self.fix_url(iframe_src)
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)
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:"(.*?)"')
74
+
75
+ if not m3u8_url:
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[^"]*)"')
79
+
80
+ if not m3u8_url:
81
+ raise ValueError(f"Filemoon: Video URL bulunamadı. {url}")
82
+
83
+ return ExtractResult(
84
+ name = self.name,
85
+ url = self.fix_url(m3u8_url),
86
+ referer = f"{self.main_url}/",
87
+ user_agent = default_headers["User-Agent"],
88
+ subtitles = []
89
+ )
@@ -0,0 +1,40 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import ExtractorBase, ExtractResult
4
+ import re
5
+
6
+ class JFVid(ExtractorBase):
7
+ name = "JFVid"
8
+ main_url = "https://jfvid.com"
9
+
10
+ # Birden fazla domain destekle
11
+ supported_domains = ["jfvid.com"]
12
+
13
+ def can_handle_url(self, url: str) -> bool:
14
+ return any(domain in url for domain in self.supported_domains)
15
+
16
+ async def extract(self, url: str, referer: str = None) -> ExtractResult:
17
+ # Dinamik base URL kullan
18
+ base_url = self.get_base_url(url)
19
+
20
+ if referer:
21
+ self.httpx.headers.update({"Referer": referer})
22
+
23
+ # /play/ endpoint'inden encodedId'yi al
24
+ # URL format: https://xxx.jfvid.com/play/{encodedId}
25
+ if "/play/" in url:
26
+ encoded_id = url.split("/play/")[-1]
27
+ stream_url = f"{base_url}/stream/{encoded_id}"
28
+ elif "/stream/" in url:
29
+ # Zaten stream URL ise doğrudan kullan
30
+ stream_url = url
31
+ else:
32
+ raise ValueError(f"JFVid: Desteklenmeyen URL formatı. {url}")
33
+
34
+ # Stream endpoint'i direkt m3u8 master playlist döndürür
35
+ return ExtractResult(
36
+ name = self.name,
37
+ url = stream_url,
38
+ referer = referer,
39
+ subtitles = []
40
+ )
@@ -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}")