Skip to content

Commit 0b9ba0d

Browse files
committed
feat: Add StreamWish and VidStack extractors; implement DDizi and ShowFlix plugins; bump version to 2.5.9
1 parent 1e47b73 commit 0b9ba0d

File tree

6 files changed

+602
-45
lines changed

6 files changed

+602
-45
lines changed
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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+
from contextlib import suppress
6+
7+
class StreamWish(ExtractorBase):
8+
name = "StreamWish"
9+
main_url = "https://streamwish.to"
10+
11+
supported_domains = [
12+
"streamwish.to", "streamwish.site", "streamwish.xyz", "streamwish.com",
13+
"embedwish.com", "mwish.pro", "dwish.pro", "wishembed.pro", "wishembed.com",
14+
"kswplayer.info", "wishfast.top", "sfastwish.com", "strwish.xyz", "strwish.com",
15+
"flaswish.com", "awish.pro", "obeywish.com", "jodwish.com", "swhoi.com",
16+
"multimovies.cloud", "uqloads.xyz", "doodporn.xyz", "cdnwish.com", "asnwish.com",
17+
"nekowish.my.id", "neko-stream.click", "swdyu.com", "wishonly.site", "playerwish.com",
18+
"streamhls.to", "hlswish.com"
19+
]
20+
21+
def can_handle_url(self, url: str) -> bool:
22+
return any(domain in url for domain in self.supported_domains)
23+
24+
def resolve_embed_url(self, url: str) -> str:
25+
# Kotlin: /f/ -> /, /e/ -> /
26+
if "/f/" in url:
27+
return url.replace("/f/", "/")
28+
if "/e/" in url:
29+
return url.replace("/e/", "/")
30+
return url
31+
32+
async def extract(self, url: str, referer: str = None) -> ExtractResult:
33+
base_url = self.get_base_url(url)
34+
embed_url = self.resolve_embed_url(url)
35+
istek = await self.httpx.get(
36+
url = embed_url,
37+
headers = {
38+
"Accept" : "*/*",
39+
"Connection" : "keep-alive",
40+
"Referer" : f"{base_url}/",
41+
"Origin" : f"{base_url}/",
42+
"User-Agent" : "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
43+
},
44+
follow_redirects=True
45+
)
46+
text = istek.text
47+
48+
unpacked = ""
49+
# Eval script bul
50+
if eval_match := HTMLHelper(text).regex_first(r'(eval\s*\(\s*function[\s\S]+?)<\/script>'):
51+
with suppress(Exception):
52+
unpacked = Packer.unpack(eval_match)
53+
54+
content = unpacked or text
55+
sel = HTMLHelper(content)
56+
57+
# Regex: file:\s*"(.*?m3u8.*?)"
58+
m3u8_url = sel.regex_first(r'file:\s*["\']([^"\']+\.m3u8[^"\']*)["\']')
59+
60+
if not m3u8_url:
61+
# Fallback to sources: Kotlin mantığı
62+
m3u8_url = sel.regex_first(r'sources:\s*\[\s*{\s*file:\s*["\']([^"\']+)["\']')
63+
64+
if not m3u8_url:
65+
# p,a,c,k,e,d içinde olabilir
66+
m3u8_url = sel.regex_first(r'["\'](https?://[^"\']+\.m3u8[^"\']*)["\']')
67+
68+
if not m3u8_url:
69+
# t.r.u.e pattern fallback
70+
m3u8_url = sel.regex_first(r'file\s*:\s*["\']([^"\']+)["\']')
71+
72+
if not m3u8_url:
73+
raise ValueError(f"StreamWish: m3u8 bulunamadı. {url}")
74+
75+
return ExtractResult(
76+
name = self.name,
77+
url = self.fix_url(m3u8_url),
78+
referer = f"{base_url}/",
79+
user_agent = self.httpx.headers.get("User-Agent", "")
80+
)

KekikStream/Extractors/VidHide.py

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, HTMLHelper
44
from Kekik.Sifreleme import Packer
5+
from contextlib import suppress
56
import re
67

78
class VidHide(ExtractorBase):
@@ -17,7 +18,7 @@ class VidHide(ExtractorBase):
1718
"kinoger.be",
1819
"smoothpre.com",
1920
"dhtpre.com",
20-
"peytonepre.com"
21+
"peytonepre.com",
2122
]
2223

2324
def can_handle_url(self, url: str) -> bool:
@@ -37,69 +38,70 @@ def get_embed_url(self, url: str) -> str:
3738

3839
async def extract(self, url: str, referer: str = None) -> ExtractResult:
3940
base_url = self.get_base_url(url)
40-
self.httpx.headers.update({
41-
"Referer" : referer or base_url,
42-
"Origin" : base_url,
43-
})
44-
41+
name = "EarnVids" if any(x in base_url for x in ["smoothpre.com", "dhtpre.com", "peytonepre.com"]) else self.name
42+
43+
# Kotlin Headers
44+
headers = {
45+
"Sec-Fetch-Dest" : "empty",
46+
"Sec-Fetch-Mode" : "cors",
47+
"Sec-Fetch-Site" : "cross-site",
48+
"Origin" : f"{base_url}/",
49+
"Referer" : referer or f"{base_url}/",
50+
}
51+
4552
embed_url = self.get_embed_url(url)
46-
istek = await self.httpx.get(embed_url, follow_redirects=True)
53+
istek = await self.httpx.get(embed_url, headers=headers, follow_redirects=True)
4754
text = istek.text
4855

4956
# Silinmiş dosya kontrolü
50-
if "File is no longer available" in text or "File Not Found" in text:
51-
raise ValueError(f"VidHide: Video silinmiş. {url}")
57+
if any(x in text for x in ["File is no longer available", "File Not Found", "Video silinmiş"]):
58+
raise ValueError(f"{name}: Video silinmiş. {url}")
5259

5360
# JS Redirect Kontrolü (OneUpload vb.)
5461
if js_redirect := HTMLHelper(text).regex_first(r"window\.location\.replace\(['\"]([^'\"]+)['\"]\)") or \
5562
HTMLHelper(text).regex_first(r"window\.location\.href\s*=\s*['\"]([^'\"]+)['\"]"):
56-
# Redirect url'i al
5763
target_url = js_redirect
58-
# Bazen path relative olabilir ama genelde full url
5964
if not target_url.startswith("http"):
60-
# urljoin gerekebilir ama şimdilik doğrudan deneyelim veya fix_url
61-
target_url = self.fix_url(target_url) # fix_url base'e göre düzeltebilir mi? ExtractorBase.fix_url genelde şema ekler.
62-
pass
65+
target_url = self.fix_url(target_url)
6366

64-
# Yeniden istek at
6567
istek = await self.httpx.get(target_url, headers={"Referer": embed_url}, follow_redirects=True)
6668
text = istek.text
6769

6870
sel = HTMLHelper(text)
6971

7072
unpacked = ""
71-
# Eval script bul (regex ile daha sağlam)
73+
# Eval script bul
7274
if eval_match := sel.regex_first(r'(eval\s*\(\s*function[\s\S]+?)<\/script>'):
73-
try:
75+
with suppress(Exception):
7476
unpacked = Packer.unpack(eval_match)
7577
if "var links" in unpacked:
7678
unpacked = unpacked.split("var links")[1]
77-
except:
78-
pass
7979

8080
content = unpacked or text
81-
82-
# Regex: Kotlin mantığı (: "url")
83-
# Ayrıca sources: [...] mantığını da ekle
84-
m3u8_url = HTMLHelper(content).regex_first(r'sources:\s*\[\s*\{\s*file:\s*"([^"]+)"')
85-
86-
if not m3u8_url:
87-
# Genel arama (hls:, file: vb.)
88-
# Kotlin Regex: :\s*"(.*?m3u8.*?)"
89-
match = HTMLHelper(content).regex_first(r':\s*["\']([^"\']+\.m3u8[^"\']*)["\']')
90-
if match:
91-
m3u8_url = match
92-
93-
if not m3u8_url:
94-
# Son şans: herhangi bir m3u8 linki
95-
m3u8_url = HTMLHelper(content).regex_first(r'["\']([^"\']+\.m3u8[^"\']*)["\']')
96-
97-
if not m3u8_url:
98-
raise ValueError(f"VidHide: Video URL bulunamadı. {url}")
99-
100-
return ExtractResult(
101-
name = self.name,
102-
url = self.fix_url(m3u8_url),
103-
referer = f"{base_url}/",
104-
user_agent = self.httpx.headers.get("User-Agent", "")
105-
)
81+
82+
# Kotlin Exact Regex: :\s*"(.*?m3u8.*?)"
83+
m3u8_matches = re.findall(r':\s*["\']([^"\']+\.m3u8[^"\']*)["\']', content)
84+
85+
results = []
86+
for m3u8_url in m3u8_matches:
87+
results.append(ExtractResult(
88+
name = name,
89+
url = self.fix_url(m3u8_url),
90+
referer = f"{base_url}/",
91+
user_agent = self.httpx.headers.get("User-Agent", "")
92+
))
93+
94+
if not results:
95+
# Fallback for non-m3u8 or different patterns
96+
if m3u8_url := sel.regex_first(r'sources:\s*\[\s*\{\s*file:\s*"([^"]+)"'):
97+
results.append(ExtractResult(
98+
name = name,
99+
url = self.fix_url(m3u8_url),
100+
referer = f"{base_url}/",
101+
user_agent = self.httpx.headers.get("User-Agent", "")
102+
))
103+
104+
if not results:
105+
raise ValueError(f"{name}: Video URL bulunamadı. {url}")
106+
107+
return results[0] if len(results) == 1 else results

KekikStream/Extractors/VidStack.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
2+
3+
from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle
4+
from Crypto.Cipher import AES
5+
from Crypto.Util import Padding
6+
import re
7+
8+
class VidStack(ExtractorBase):
9+
name = "VidStack"
10+
main_url = "https://vidstack.io"
11+
requires_referer = True
12+
13+
supported_domains = [
14+
"vidstack.io", "server1.uns.bio", "upns.one"
15+
]
16+
17+
def can_handle_url(self, url: str) -> bool:
18+
return any(domain in url for domain in self.supported_domains)
19+
20+
def decrypt_aes(self, input_hex: str, key: str, iv: str) -> str:
21+
try:
22+
cipher = AES.new(key.encode('utf-8'), AES.MODE_CBC, iv.encode('utf-8'))
23+
raw_data = bytes.fromhex(input_hex)
24+
decrypted = cipher.decrypt(raw_data)
25+
unpadded = Padding.unpad(decrypted, AES.block_size)
26+
return unpadded.decode('utf-8')
27+
except Exception as e:
28+
# print(f"DEBUG VidStack: {iv} -> {e}") # Debugging
29+
return None
30+
31+
async def extract(self, url: str, referer: str = None) -> ExtractResult | list[ExtractResult]:
32+
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0"}
33+
34+
# Hash ve Base URL çıkarma
35+
hash_val = url.split("#")[-1].split("/")[-1]
36+
base_url = self.get_base_url(url)
37+
38+
# API İsteği
39+
api_url = f"{base_url}/api/v1/video?id={hash_val}"
40+
istek = await self.httpx.get(api_url, headers=headers)
41+
42+
# Bazen yanıt tırnak içinde gelebilir, temizleyelim
43+
encoded_data = istek.text.strip().strip('"')
44+
45+
# AES Çözme
46+
key = "kiemtienmua911ca"
47+
ivs = ["1234567890oiuytr", "0123456789abcdef"]
48+
49+
decrypted_text = None
50+
for iv in ivs:
51+
decrypted_text = self.decrypt_aes(encoded_data, key, iv)
52+
if decrypted_text and '"source":' in decrypted_text:
53+
break
54+
55+
if not decrypted_text:
56+
# Hata mesajını daha detaylı verelim (debug için tırnaklanmış hali)
57+
raise ValueError(f"VidStack: AES çözme başarısız. {url} | Response: {istek.text[:50]}...")
58+
59+
# m3u8 ve Alt yazı çıkarma
60+
# Kotlin'de "source":"(.*?)" regex'i kullanılıyor
61+
m3u8_url = re.search(r'["\']source["\']\s*:\s*["\']([^"\']+)["\']', decrypted_text)
62+
if m3u8_url:
63+
m3u8_url = m3u8_url.group(1).replace("\\/", "/")
64+
else:
65+
raise ValueError(f"VidStack: m3u8 bulunamadı. {url}")
66+
67+
subtitles = []
68+
# Kotlin: "subtitle":\{(.*?)\}
69+
subtitle_section = re.search(r'["\']subtitle["\']\s*:\s*\{(.*?)\}', decrypted_text)
70+
if subtitle_section:
71+
section = subtitle_section.group(1)
72+
# Regex: "([^"]+)":\s*"([^"]+)"
73+
matches = re.finditer(r'["\']([^"\']+)["\']\s*:\s*["\']([^"\']+)["\']', section)
74+
for match in matches:
75+
lang = match.group(1)
76+
raw_path = match.group(2).split("#")[0]
77+
if raw_path:
78+
path = raw_path.replace("\\/", "/")
79+
sub_url = f"{self.main_url}{path}"
80+
subtitles.append(Subtitle(name=lang, url=self.fix_url(sub_url)))
81+
82+
return ExtractResult(
83+
name = self.name,
84+
url = self.fix_url(m3u8_url),
85+
referer = url,
86+
user_agent = headers["User-Agent"],
87+
subtitles = subtitles
88+
)

0 commit comments

Comments
 (0)