KekikStream 1.7.1__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 (88) hide show
  1. KekikStream/Core/Extractor/ExtractorBase.py +20 -9
  2. KekikStream/Core/Extractor/ExtractorLoader.py +25 -17
  3. KekikStream/Core/Extractor/ExtractorManager.py +53 -9
  4. KekikStream/Core/Extractor/ExtractorModels.py +5 -7
  5. KekikStream/Core/Extractor/YTDLPCache.py +35 -0
  6. KekikStream/Core/Media/MediaHandler.py +44 -26
  7. KekikStream/Core/Media/MediaManager.py +0 -3
  8. KekikStream/Core/Plugin/PluginBase.py +82 -22
  9. KekikStream/Core/Plugin/PluginLoader.py +11 -7
  10. KekikStream/Core/Plugin/PluginModels.py +25 -26
  11. KekikStream/Core/__init__.py +1 -0
  12. KekikStream/Extractors/CloseLoad.py +21 -7
  13. KekikStream/Extractors/ContentX.py +21 -6
  14. KekikStream/Extractors/DonilasPlay.py +86 -0
  15. KekikStream/Extractors/DzenRu.py +38 -0
  16. KekikStream/Extractors/ExPlay.py +53 -0
  17. KekikStream/Extractors/Filemoon.py +78 -0
  18. KekikStream/Extractors/HDPlayerSystem.py +41 -0
  19. KekikStream/Extractors/JetTv.py +45 -0
  20. KekikStream/Extractors/MailRu.py +3 -4
  21. KekikStream/Extractors/MixPlayHD.py +2 -3
  22. KekikStream/Extractors/MixTiger.py +57 -0
  23. KekikStream/Extractors/MolyStream.py +5 -5
  24. KekikStream/Extractors/Odnoklassniki.py +13 -7
  25. KekikStream/Extractors/PeaceMakerst.py +10 -5
  26. KekikStream/Extractors/PixelDrain.py +1 -2
  27. KekikStream/Extractors/PlayerFilmIzle.py +65 -0
  28. KekikStream/Extractors/RapidVid.py +23 -8
  29. KekikStream/Extractors/SetPlay.py +66 -0
  30. KekikStream/Extractors/SetPrime.py +45 -0
  31. KekikStream/Extractors/SibNet.py +2 -3
  32. KekikStream/Extractors/Sobreatsesuyp.py +4 -5
  33. KekikStream/Extractors/TRsTX.py +4 -5
  34. KekikStream/Extractors/TauVideo.py +2 -3
  35. KekikStream/Extractors/TurboImgz.py +2 -3
  36. KekikStream/Extractors/TurkeyPlayer.py +34 -0
  37. KekikStream/Extractors/VCTPlay.py +41 -0
  38. KekikStream/Extractors/VidHide.py +81 -0
  39. KekikStream/Extractors/VidMoly.py +55 -34
  40. KekikStream/Extractors/VidMoxy.py +2 -3
  41. KekikStream/Extractors/VidPapi.py +89 -0
  42. KekikStream/Extractors/VideoSeyred.py +3 -4
  43. KekikStream/Extractors/YTDLP.py +211 -0
  44. KekikStream/Extractors/YildizKisaFilm.py +41 -0
  45. KekikStream/Plugins/BelgeselX.py +196 -0
  46. KekikStream/Plugins/DiziBox.py +25 -34
  47. KekikStream/Plugins/DiziPal.py +24 -35
  48. KekikStream/Plugins/DiziYou.py +54 -37
  49. KekikStream/Plugins/Dizilla.py +66 -46
  50. KekikStream/Plugins/FilmBip.py +142 -0
  51. KekikStream/Plugins/FilmMakinesi.py +36 -28
  52. KekikStream/Plugins/FilmModu.py +20 -24
  53. KekikStream/Plugins/FullHDFilm.py +220 -0
  54. KekikStream/Plugins/FullHDFilmizlesene.py +9 -15
  55. KekikStream/Plugins/HDFilmCehennemi.py +141 -69
  56. KekikStream/Plugins/JetFilmizle.py +85 -52
  57. KekikStream/Plugins/KultFilmler.py +217 -0
  58. KekikStream/Plugins/RecTV.py +22 -34
  59. KekikStream/Plugins/RoketDizi.py +222 -0
  60. KekikStream/Plugins/SelcukFlix.py +328 -0
  61. KekikStream/Plugins/SetFilmIzle.py +252 -0
  62. KekikStream/Plugins/SezonlukDizi.py +54 -21
  63. KekikStream/Plugins/SineWix.py +17 -29
  64. KekikStream/Plugins/Sinefy.py +241 -0
  65. KekikStream/Plugins/SinemaCX.py +154 -0
  66. KekikStream/Plugins/Sinezy.py +143 -0
  67. KekikStream/Plugins/SuperFilmGeldi.py +130 -0
  68. KekikStream/Plugins/UgurFilm.py +13 -19
  69. KekikStream/__init__.py +47 -56
  70. KekikStream/requirements.txt +3 -4
  71. kekikstream-2.2.0.dist-info/METADATA +312 -0
  72. kekikstream-2.2.0.dist-info/RECORD +81 -0
  73. KekikStream/Extractors/FourCX.py +0 -7
  74. KekikStream/Extractors/FourPichive.py +0 -7
  75. KekikStream/Extractors/FourPlayRu.py +0 -7
  76. KekikStream/Extractors/HDStreamAble.py +0 -7
  77. KekikStream/Extractors/Hotlinger.py +0 -7
  78. KekikStream/Extractors/OkRuHTTP.py +0 -7
  79. KekikStream/Extractors/OkRuSSL.py +0 -7
  80. KekikStream/Extractors/Pichive.py +0 -7
  81. KekikStream/Extractors/PlayRu.py +0 -7
  82. KekikStream/Extractors/VidMolyMe.py +0 -7
  83. kekikstream-1.7.1.dist-info/METADATA +0 -109
  84. kekikstream-1.7.1.dist-info/RECORD +0 -63
  85. {kekikstream-1.7.1.dist-info → kekikstream-2.2.0.dist-info}/WHEEL +0 -0
  86. {kekikstream-1.7.1.dist-info → kekikstream-2.2.0.dist-info}/entry_points.txt +0 -0
  87. {kekikstream-1.7.1.dist-info → kekikstream-2.2.0.dist-info}/licenses/LICENSE +0 -0
  88. {kekikstream-1.7.1.dist-info → kekikstream-2.2.0.dist-info}/top_level.txt +0 -0
@@ -9,22 +9,28 @@ 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
- self.cffi.headers.update({"Referer": referer})
20
+ self.httpx.headers.update({"Referer": referer})
15
21
 
16
- self.cffi.headers.update({
22
+ self.httpx.headers.update({
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.cffi.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
- response = await self.cffi.post(
33
+ response = await self.httpx.post(
28
34
  url = url,
29
35
  data = {
30
36
  "op" : secici.css("input[name='op']::attr(value)").get(),
@@ -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,23 +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
- headers = {},
84
- subtitles = subtitles
85
- )
106
+ raise ValueError("Video URL bulunamadı.")
86
107
 
87
108
  def _add_marks(self, text: str, field: str) -> str:
88
109
  """
@@ -10,9 +10,9 @@ class VidMoxy(ExtractorBase):
10
10
 
11
11
  async def extract(self, url, referer=None) -> ExtractResult:
12
12
  if referer:
13
- self.cffi.headers.update({"Referer": referer})
13
+ self.httpx.headers.update({"Referer": referer})
14
14
 
15
- istek = await self.cffi.get(url)
15
+ istek = await self.httpx.get(url)
16
16
  istek.raise_for_status()
17
17
 
18
18
  subtitles = []
@@ -45,6 +45,5 @@ class VidMoxy(ExtractorBase):
45
45
  name = self.name,
46
46
  url = m3u_link,
47
47
  referer = self.main_url,
48
- headers = {},
49
48
  subtitles = subtitles
50
49
  )
@@ -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, Subtitle
4
+ import re
5
+
6
+ class VidPapi(ExtractorBase):
7
+ name = "VidApi"
8
+ main_url = "https://vidpapi.xyz"
9
+
10
+ async def extract(self, url, referer=None) -> ExtractResult:
11
+ ext_ref = referer or ""
12
+
13
+ # URL parsing
14
+ if "video/" in url:
15
+ vid_id = url.split("video/")[-1]
16
+ else:
17
+ vid_id = url.split("?data=")[-1]
18
+
19
+ # 1. Altyazıları çek
20
+ sub_url = f"{self.main_url}/player/index.php?data={vid_id}"
21
+ sub_headers = {
22
+ "Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8",
23
+ "X-Requested-With" : "XMLHttpRequest",
24
+ "User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0",
25
+ "Referer" : ext_ref or "https://kultfilmler.pro/"
26
+ }
27
+
28
+ subtitles = []
29
+ try:
30
+ sub_istek = await self.httpx.post(
31
+ url = sub_url,
32
+ headers = sub_headers,
33
+ data = {"hash": vid_id, "r": "https://kultfilmler.pro/"}
34
+ )
35
+
36
+ subtitle_match = re.search(r'var playerjsSubtitle = "([^"]*)"', sub_istek.text, re.IGNORECASE)
37
+ if subtitle_match and subtitle_match.group(1):
38
+ raw_subs = subtitle_match.group(1)
39
+
40
+ found_subs = re.findall(r'\[(.*?)\](.*?)(?:,|$)', raw_subs)
41
+ for lang, sub_link in found_subs:
42
+ lang = lang.strip()
43
+ if "Türkçe" in lang:
44
+ lang_code = "tr"
45
+ lang_name = "Turkish"
46
+ elif "İngilizce" in lang:
47
+ lang_code = "en"
48
+ lang_name = "English"
49
+ else:
50
+ lang_code = lang[:2].lower()
51
+ lang_name = lang
52
+
53
+ subtitles.append(Subtitle(
54
+ name = lang_name,
55
+ url = sub_link.strip()
56
+ ))
57
+
58
+ except Exception as e:
59
+ pass
60
+
61
+ # 2. Videoyu çek
62
+ video_url = f"{self.main_url}/player/index.php?data={vid_id}&do=getVideo"
63
+ video_headers = sub_headers.copy()
64
+
65
+ response = await self.httpx.post(
66
+ url = video_url,
67
+ headers = video_headers,
68
+ data = {"hash": vid_id, "r": "https://kultfilmler.pro/"}
69
+ )
70
+ response.raise_for_status()
71
+
72
+ try:
73
+ video_data = response.json()
74
+ except Exception:
75
+ return None
76
+
77
+ stream_url = video_data.get("securedLink")
78
+ if not stream_url or not stream_url.strip():
79
+ stream_url = video_data.get("videoSource")
80
+
81
+ if not stream_url:
82
+ raise ValueError("No video link found in VidPapi response")
83
+
84
+ return ExtractResult(
85
+ name = self.name,
86
+ url = stream_url,
87
+ referer = ext_ref or self.main_url,
88
+ subtitles = subtitles
89
+ )
@@ -9,18 +9,18 @@ class VideoSeyred(ExtractorBase):
9
9
 
10
10
  async def extract(self, url, referer=None) -> ExtractResult:
11
11
  if referer:
12
- self.cffi.headers.update({"Referer": referer})
12
+ self.httpx.headers.update({"Referer": referer})
13
13
 
14
14
  video_id = url.split("embed/")[1].split("?")[0]
15
15
  if len(video_id) > 10:
16
- kontrol = await self.cffi.get(url)
16
+ kontrol = await self.httpx.get(url)
17
17
  kontrol.raise_for_status()
18
18
 
19
19
  video_id = re.search(r"playlist\/(.*)\.json", kontrol.text)[1]
20
20
 
21
21
  video_url = f"{self.main_url}/playlist/{video_id}.json"
22
22
 
23
- response = await self.cffi.get(video_url)
23
+ response = await self.httpx.get(video_url)
24
24
  response.raise_for_status()
25
25
 
26
26
  try:
@@ -43,7 +43,6 @@ class VideoSeyred(ExtractorBase):
43
43
  name = self.name,
44
44
  url = self.fix_url(source["file"]),
45
45
  referer = self.main_url,
46
- headers = {},
47
46
  subtitles = subtitles,
48
47
  )
49
48
  for source in response_data.get("sources", [])
@@ -0,0 +1,211 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, get_ytdlp_extractors
4
+ from urllib.parse import urlparse
5
+ import yt_dlp, re, sys, os
6
+
7
+ class YTDLP(ExtractorBase):
8
+ name = "yt-dlp"
9
+ main_url = "" # Universal - tüm siteleri destekler
10
+
11
+ _FAST_DOMAIN_RE = None # compiled mega-regex (host üstünden)
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
+
93
+ @classmethod
94
+ def _init_fast_domain_regex(cls):
95
+ """
96
+ Fast domain regex'i initialize et
97
+ """
98
+ if cls._FAST_DOMAIN_RE is not None:
99
+ return
100
+
101
+ domains = set()
102
+ extractors = get_ytdlp_extractors()
103
+
104
+ for ie in extractors:
105
+ valid = getattr(ie, "_VALID_URL", None)
106
+ if not valid or not isinstance(valid, str):
107
+ continue
108
+
109
+ domains |= cls._extract_literal_domains(valid)
110
+ domains |= cls._extract_regex_tld_domains(valid)
111
+ domains |= cls._extract_alternation_domains(valid)
112
+
113
+ # Hiç domain çıkmazsa (çok uç durum) fallback: boş regex
114
+ if not domains:
115
+ cls._FAST_DOMAIN_RE = re.compile(r"$^") # hiçbir şeye match etmez
116
+ return
117
+
118
+ joined = "|".join(re.escape(d) for d in sorted(domains))
119
+ cls._FAST_DOMAIN_RE = re.compile(rf"(?:^|.*\.)(?:{joined})$", re.IGNORECASE)
120
+
121
+ def __init__(self):
122
+ self.__class__._init_fast_domain_regex()
123
+
124
+ def can_handle_url(self, url: str) -> bool:
125
+ """
126
+ Fast-path: URL host'unu tek mega-regex ile kontrol et
127
+ """
128
+ # URL parse + host al
129
+ try:
130
+ parsed = urlparse(url)
131
+ host = (parsed.hostname or "").lower()
132
+ except Exception:
133
+ host = ""
134
+
135
+ # Şemasız URL desteği: "youtube.com/..." gibi
136
+ if not host and "://" not in url:
137
+ try:
138
+ parsed = urlparse("https://" + url)
139
+ host = (parsed.hostname or "").lower()
140
+ except Exception:
141
+ host = ""
142
+
143
+ # Fast-path
144
+ if host and self.__class__._FAST_DOMAIN_RE.search(host):
145
+ return True
146
+
147
+ # yt-dlp işleyemezse False döndür
148
+ return False
149
+
150
+ async def extract(self, url: str, referer: str | None = None) -> ExtractResult:
151
+ ydl_opts = {
152
+ "quiet" : True,
153
+ "no_warnings" : True,
154
+ "extract_flat" : False, # Tam bilgi al
155
+ "format" : "best", # En iyi kalite
156
+ "no_check_certificates" : True,
157
+ "socket_timeout" : 3,
158
+ "retries" : 1
159
+ }
160
+
161
+ # Referer varsa header olarak ekle
162
+ if referer:
163
+ ydl_opts["http_headers"] = {"Referer": referer}
164
+
165
+ with yt_dlp.YoutubeDL(ydl_opts) as ydl:
166
+ info = ydl.extract_info(url, download=False)
167
+
168
+ if not info:
169
+ raise ValueError("yt-dlp video bilgisi döndürmedi")
170
+
171
+ # Video URL'sini al
172
+ video_url = info.get("url")
173
+ if not video_url:
174
+ # Bazen formatlar listesinde olabilir
175
+ formats = info.get("formats", [])
176
+ if formats:
177
+ video_url = formats[-1].get("url") # Son format (genellikle en iyi)
178
+
179
+ if not video_url:
180
+ raise ValueError("Video URL bulunamadı")
181
+
182
+ # Altyazıları çıkar
183
+ subtitles = []
184
+ if subtitle_data := info.get("subtitles"):
185
+ for lang, subs in subtitle_data.items():
186
+ for sub in subs:
187
+ if sub_url := sub.get("url"):
188
+ subtitles.append(
189
+ Subtitle(
190
+ name=f"{lang} ({sub.get('ext', 'unknown')})",
191
+ url=sub_url
192
+ )
193
+ )
194
+
195
+ # User-Agent al
196
+ user_agent = None
197
+ http_headers = info.get("http_headers", {})
198
+ if http_headers:
199
+ user_agent = http_headers.get("User-Agent")
200
+
201
+ return ExtractResult(
202
+ name = self.name,
203
+ url = video_url,
204
+ referer = referer or info.get("webpage_url"),
205
+ user_agent = user_agent,
206
+ subtitles = subtitles
207
+ )
208
+
209
+ async def close(self):
210
+ """yt-dlp için cleanup gerekmez"""
211
+ pass
@@ -0,0 +1,41 @@
1
+ # Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2
+
3
+ from KekikStream.Core import ExtractorBase, ExtractResult
4
+
5
+ class YildizKisaFilm(ExtractorBase):
6
+ name = "YildizKisaFilm"
7
+ main_url = "https://yildizkisafilm.org"
8
+
9
+ async def extract(self, url, referer=None) -> ExtractResult:
10
+ ext_ref = referer or ""
11
+
12
+ if "video/" in url:
13
+ vid_id = url.split("video/")[-1]
14
+ else:
15
+ vid_id = url.split("?data=")[-1]
16
+
17
+ post_url = f"{self.main_url}/player/index.php?data={vid_id}&do=getVideo"
18
+
19
+ response = await self.httpx.post(
20
+ url = post_url,
21
+ data = {"hash": vid_id, "r": ext_ref},
22
+ headers = {
23
+ "Referer" : ext_ref,
24
+ "Content-Type" : "application/x-www-form-urlencoded; charset=UTF-8",
25
+ "X-Requested-With" : "XMLHttpRequest"
26
+ }
27
+ )
28
+ response.raise_for_status()
29
+
30
+ video_data = response.json()
31
+ m3u_link = video_data.get("securedLink")
32
+
33
+ if not m3u_link:
34
+ raise ValueError("securedLink not found in response")
35
+
36
+ return ExtractResult(
37
+ name = self.name,
38
+ url = m3u_link,
39
+ referer = ext_ref,
40
+ subtitles = []
41
+ )