KekikStream 2.3.6__tar.gz → 2.4.0__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.
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/HTMLHelper.py +2 -2
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/Plugin/PluginBase.py +6 -1
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/Plugin/PluginModels.py +0 -3
- kekikstream-2.4.0/KekikStream/Extractors/HDMomPlayer.py +62 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/MolyStream.py +1 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/VidMoly.py +13 -0
- kekikstream-2.4.0/KekikStream/Extractors/Videostr.py +115 -0
- kekikstream-2.4.0/KekikStream/Plugins/DiziMom.py +248 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/DiziPal.py +20 -9
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/DiziYou.py +14 -18
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/Dizilla.py +50 -59
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/FilmBip.py +8 -4
- kekikstream-2.4.0/KekikStream/Plugins/FilmEkseni.py +140 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/FilmMakinesi.py +1 -1
- kekikstream-2.4.0/KekikStream/Plugins/Filmatek.py +188 -0
- kekikstream-2.4.0/KekikStream/Plugins/Full4kizle.py +190 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/FullHDFilmizlesene.py +7 -16
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/JetFilmizle.py +6 -1
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/KultFilmler.py +2 -1
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/RecTV.py +59 -24
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/RoketDizi.py +76 -78
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/SelcukFlix.py +90 -67
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/SetFilmIzle.py +27 -8
- kekikstream-2.4.0/KekikStream/Plugins/Watch32.py +185 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/YabanciDizi.py +25 -14
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream.egg-info/PKG-INFO +3 -3
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream.egg-info/SOURCES.txt +7 -1
- {kekikstream-2.3.6 → kekikstream-2.4.0}/PKG-INFO +3 -3
- {kekikstream-2.3.6 → kekikstream-2.4.0}/README.md +2 -2
- {kekikstream-2.3.6 → kekikstream-2.4.0}/setup.py +1 -1
- kekikstream-2.3.6/KekikStream/Plugins/DiziWatch.py +0 -212
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/CLI/__init__.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/CLI/pypi_kontrol.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/Extractor/ExtractorBase.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/Extractor/ExtractorLoader.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/Extractor/ExtractorManager.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/Extractor/ExtractorModels.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/Extractor/YTDLPCache.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/Media/MediaHandler.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/Media/MediaManager.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/Plugin/PluginLoader.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/Plugin/PluginManager.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/UI/UIManager.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Core/__init__.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/CloseLoad.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/ContentX.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/DonilasPlay.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/DzenRu.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/ExPlay.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/Filemoon.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/HDPlayerSystem.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/JFVid.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/JetTv.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/MailRu.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/MixPlayHD.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/MixTiger.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/Odnoklassniki.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/PeaceMakerst.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/PixelDrain.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/PlayerFilmIzle.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/RapidVid.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/SetPlay.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/SetPrime.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/SibNet.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/Sobreatsesuyp.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/TRsTX.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/TauVideo.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/TurboImgz.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/TurkeyPlayer.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/VCTPlay.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/VidHide.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/VidMoxy.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/VidPapi.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/VideoSeyred.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/YTDLP.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Extractors/YildizKisaFilm.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/BelgeselX.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/DiziBox.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/FilmModu.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/FullHDFilm.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/HDFilmCehennemi.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/SezonlukDizi.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/SineWix.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/Sinefy.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/SinemaCX.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/Sinezy.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/SuperFilmGeldi.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/Plugins/UgurFilm.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/__init__.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/__main__.py +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream/requirements.txt +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream.egg-info/dependency_links.txt +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream.egg-info/entry_points.txt +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream.egg-info/requires.txt +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/KekikStream.egg-info/top_level.txt +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/LICENSE +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/MANIFEST.in +0 -0
- {kekikstream-2.3.6 → kekikstream-2.4.0}/setup.cfg +0 -0
|
@@ -113,8 +113,8 @@ class HTMLHelper:
|
|
|
113
113
|
return int(m.group(1)), int(m.group(2))
|
|
114
114
|
|
|
115
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]
|
|
116
|
+
s = re.search(r"(\d+)\.\s*[Ss]ezon|[Ss]ezon[- ]?(\d+)|-(\d+)-sezon|S(\d+)|(\d+)\.[Ss]", text, re.I)
|
|
117
|
+
e = re.search(r"(\d+)\.\s*[Bb][öo]l[üu]m|[Bb][öo]l[üu]m[- ]?(\d+)|-(\d+)-bolum|[Ee](\d+)", text, re.I)
|
|
118
118
|
|
|
119
119
|
# İlk bulunan grubu al (None değilse)
|
|
120
120
|
s_val = next((int(g) for g in s.groups() if g), None) if s else None
|
|
@@ -31,11 +31,16 @@ class PluginBase(ABC):
|
|
|
31
31
|
if proxy:
|
|
32
32
|
self.cloudscraper.proxies = proxy if isinstance(proxy, dict) else {"http": proxy, "https": proxy}
|
|
33
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")
|
|
38
|
+
|
|
34
39
|
# httpx - lightweight and safe for most HTTP requests
|
|
35
40
|
self.httpx = AsyncClient(
|
|
36
41
|
timeout = 3,
|
|
37
42
|
follow_redirects = True,
|
|
38
|
-
proxy =
|
|
43
|
+
proxy = httpx_proxy
|
|
39
44
|
)
|
|
40
45
|
self.httpx.headers.update(self.cloudscraper.headers)
|
|
41
46
|
self.httpx.cookies.update(self.cloudscraper.cookies)
|
|
@@ -0,0 +1,62 @@
|
|
|
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 AESManager
|
|
5
|
+
import re
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
class HDMomPlayer(ExtractorBase):
|
|
9
|
+
name = "HDMomPlayer"
|
|
10
|
+
main_url = "hdmomplayer.com"
|
|
11
|
+
|
|
12
|
+
async def extract(self, url: str, referer: str = None) -> ExtractResult | None:
|
|
13
|
+
if referer:
|
|
14
|
+
self.httpx.headers.update({"Referer": referer})
|
|
15
|
+
|
|
16
|
+
try:
|
|
17
|
+
response = await self.httpx.get(url)
|
|
18
|
+
page_source = response.text
|
|
19
|
+
|
|
20
|
+
m3u_link = None
|
|
21
|
+
|
|
22
|
+
# Regex for bePlayer matches
|
|
23
|
+
# Matches: bePlayer('PASS', '{DATA}');
|
|
24
|
+
helper = HTMLHelper(page_source)
|
|
25
|
+
be_matches = helper.regex_all(r"bePlayer\('([^']+)',\s*'(\{[^\}]+\})'\);")
|
|
26
|
+
|
|
27
|
+
if be_matches:
|
|
28
|
+
pass_val, data_val = be_matches[0]
|
|
29
|
+
|
|
30
|
+
try:
|
|
31
|
+
# Use Kekik.Sifreleme.AESManager as requested
|
|
32
|
+
decrypted = AESManager.decrypt(data_val, pass_val).replace("\\", "")
|
|
33
|
+
|
|
34
|
+
# Search for video_location in decrypted string
|
|
35
|
+
# Kotlin: video_location":"([^"]+)
|
|
36
|
+
m_loc = re.search(r'video_location":"([^"]+)"', decrypted)
|
|
37
|
+
if m_loc:
|
|
38
|
+
m3u_link = m_loc.group(1).replace(r"\/", "/")
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
if not m3u_link:
|
|
43
|
+
# Fallback regex
|
|
44
|
+
# file:"..."
|
|
45
|
+
m_file = re.search(r'file:"([^"]+)"', page_source)
|
|
46
|
+
if m_file:
|
|
47
|
+
m3u_link = m_file.group(1)
|
|
48
|
+
|
|
49
|
+
if not m3u_link:
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
# Fix URL if needed
|
|
53
|
+
if m3u_link.startswith("//"):
|
|
54
|
+
m3u_link = "https:" + m3u_link
|
|
55
|
+
|
|
56
|
+
return ExtractResult(
|
|
57
|
+
name = "HDMomPlayer",
|
|
58
|
+
url = m3u_link,
|
|
59
|
+
referer = url,
|
|
60
|
+
)
|
|
61
|
+
except Exception:
|
|
62
|
+
return None
|
|
@@ -24,6 +24,8 @@ class VidMoly(ExtractorBase):
|
|
|
24
24
|
|
|
25
25
|
if ".me" in url:
|
|
26
26
|
url = url.replace(".me", ".net")
|
|
27
|
+
if ".to" in url:
|
|
28
|
+
url = url.replace(".to", ".net")
|
|
27
29
|
|
|
28
30
|
# VidMoly bazen redirect ediyor, takip et
|
|
29
31
|
response = await self.httpx.get(url, follow_redirects=True)
|
|
@@ -70,6 +72,17 @@ class VidMoly(ExtractorBase):
|
|
|
70
72
|
if sub.get("kind") == "captions"
|
|
71
73
|
]
|
|
72
74
|
|
|
75
|
+
if "#EXTM3U" in response.text:
|
|
76
|
+
for line in response.text.splitlines():
|
|
77
|
+
line = line.strip().replace('"', '').replace("'", "")
|
|
78
|
+
if line.startswith("http"):
|
|
79
|
+
return ExtractResult(
|
|
80
|
+
name = self.name,
|
|
81
|
+
url = line,
|
|
82
|
+
referer = self.main_url,
|
|
83
|
+
subtitles = subtitles
|
|
84
|
+
)
|
|
85
|
+
|
|
73
86
|
if script_str := resp_sec.regex_first(r"sources:\s*\[(.*?)\],", flags= re.DOTALL):
|
|
74
87
|
script_content = script_str
|
|
75
88
|
# Video kaynaklarını ayrıştır
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
|
+
|
|
3
|
+
from KekikStream.Core import ExtractorBase, ExtractResult, HTMLHelper, Subtitle
|
|
4
|
+
from urllib.parse import quote
|
|
5
|
+
from json import loads
|
|
6
|
+
|
|
7
|
+
class Videostr(ExtractorBase):
|
|
8
|
+
name = "Videostr"
|
|
9
|
+
main_url = "videostr.net"
|
|
10
|
+
|
|
11
|
+
async def extract(self, url: str, referer: str = None) -> ExtractResult | None:
|
|
12
|
+
headers = {
|
|
13
|
+
"Accept": "*/*",
|
|
14
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
15
|
+
"Referer": "https://videostr.net",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# 1. Get nonce page
|
|
19
|
+
# Kotlin: url.substringAfterLast("/").substringBefore("?")
|
|
20
|
+
id = url.split("?")[0].split("/")[-1]
|
|
21
|
+
|
|
22
|
+
istek = await self.httpx.get(url, headers=headers)
|
|
23
|
+
if istek.status_code != 200:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
responsenonce = istek.text
|
|
27
|
+
|
|
28
|
+
# Find nonce
|
|
29
|
+
# Regex: \b[a-zA-Z0-9]{48}\b
|
|
30
|
+
# Or 3 blocks of 16 chars
|
|
31
|
+
helper = HTMLHelper(responsenonce)
|
|
32
|
+
nonce = helper.regex_first(r"\b[a-zA-Z0-9]{48}\b")
|
|
33
|
+
if not nonce:
|
|
34
|
+
# Fallback regex as per Kotlin: \b([a-zA-Z0-9]{16})\b.*?\b([a-zA-Z0-9]{16})\b.*?\b([a-zA-Z0-9]{16})\b
|
|
35
|
+
# In Python we can just look for the combined matches if regex_first handles grouping poorly or try finding all
|
|
36
|
+
import re
|
|
37
|
+
m = re.search(r"\b([a-zA-Z0-9]{16})\b.*?\b([a-zA-Z0-9]{16})\b.*?\b([a-zA-Z0-9]{16})\b", responsenonce, re.DOTALL)
|
|
38
|
+
if m:
|
|
39
|
+
nonce = m.group(1) + m.group(2) + m.group(3)
|
|
40
|
+
|
|
41
|
+
if not nonce:
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
# 2. Get Sources
|
|
45
|
+
api_url = f"https://videostr.net/embed-1/v3/e-1/getSources?id={id}&_k={nonce}"
|
|
46
|
+
|
|
47
|
+
api_resp = await self.httpx.get(api_url, headers=headers)
|
|
48
|
+
if api_resp.status_code != 200:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
# Parse JSON
|
|
52
|
+
try:
|
|
53
|
+
data = api_resp.json()
|
|
54
|
+
except:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
sources = data.get("sources", [])
|
|
58
|
+
if not sources:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
encrypted_file = sources[0].get("file")
|
|
62
|
+
if not encrypted_file:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
m3u8_url = None
|
|
66
|
+
|
|
67
|
+
if ".m3u8" in encrypted_file:
|
|
68
|
+
m3u8_url = encrypted_file
|
|
69
|
+
else:
|
|
70
|
+
# Decrypt
|
|
71
|
+
# Need key from github
|
|
72
|
+
key_url = "https://raw.githubusercontent.com/yogesh-hacker/MegacloudKeys/refs/heads/main/keys.json"
|
|
73
|
+
key_resp = await self.httpx.get(key_url)
|
|
74
|
+
if key_resp.status_code == 200:
|
|
75
|
+
try:
|
|
76
|
+
keys = key_resp.json()
|
|
77
|
+
vidstr_key = keys.get("vidstr") # As per Kotlin code: gson.fromJson(keyJson, Megakey::class.java)?.vidstr
|
|
78
|
+
|
|
79
|
+
if vidstr_key:
|
|
80
|
+
# Use Google Script to decrypt
|
|
81
|
+
decode_url = "https://script.google.com/macros/s/AKfycbxHbYHbrGMXYD2-bC-C43D3njIbU-wGiYQuJL61H4vyy6YVXkybMNNEPJNPPuZrD1gRVA/exec"
|
|
82
|
+
|
|
83
|
+
full_url = f"{decode_url}?encrypted_data={quote(encrypted_file)}&nonce={quote(nonce)}&secret={quote(vidstr_key)}"
|
|
84
|
+
|
|
85
|
+
decrypted_resp = await self.httpx.get(full_url)
|
|
86
|
+
if decrypted_resp.status_code == 200:
|
|
87
|
+
# Response is JSON {"file": "..."} usually or text?
|
|
88
|
+
# Kotlin says: Regex("\"file\":\"(.*?)\"").find(decryptedResponse)
|
|
89
|
+
m_file = re.search(r'"file":"(.*?)"', decrypted_resp.text)
|
|
90
|
+
if m_file:
|
|
91
|
+
m3u8_url = m_file.group(1).replace(r"\/", "/")
|
|
92
|
+
except Exception as e:
|
|
93
|
+
# print(f"Decryption error: {e}")
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
if not m3u8_url:
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
# Subtitles
|
|
100
|
+
# Kotlin: response.tracks
|
|
101
|
+
subtitles = []
|
|
102
|
+
tracks = data.get("tracks", [])
|
|
103
|
+
for t in tracks:
|
|
104
|
+
if t.get("kind") in ["captions", "subtitles"]:
|
|
105
|
+
subtitles.append(Subtitle(
|
|
106
|
+
name = t.get("label", "Altyazı"),
|
|
107
|
+
url = t.get("file")
|
|
108
|
+
))
|
|
109
|
+
|
|
110
|
+
return ExtractResult(
|
|
111
|
+
name = "Videostr",
|
|
112
|
+
url = m3u8_url,
|
|
113
|
+
referer = "https://videostr.net/",
|
|
114
|
+
subtitles= subtitles
|
|
115
|
+
)
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
|
+
|
|
3
|
+
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, SeriesInfo, Episode, ExtractResult, HTMLHelper
|
|
4
|
+
from json import dumps, loads
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
class DiziMom(PluginBase):
|
|
8
|
+
name = "DiziMom"
|
|
9
|
+
language = "tr"
|
|
10
|
+
main_url = "https://www.dizimom.one"
|
|
11
|
+
favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
|
|
12
|
+
description = "Binlerce yerli yabancı dizi arşivi, tüm sezonlar, kesintisiz bölümler. Sadece dizi izle, Dizimom heryerde seninle!"
|
|
13
|
+
|
|
14
|
+
main_page = {
|
|
15
|
+
f"{main_url}/tum-bolumler/page" : "Son Bölümler",
|
|
16
|
+
f"{main_url}/yerli-dizi-izle/page" : "Yerli Diziler",
|
|
17
|
+
f"{main_url}/yabanci-dizi-izle/page" : "Yabancı Diziler",
|
|
18
|
+
f"{main_url}/tv-programlari-izle/page" : "TV Programları",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
|
|
22
|
+
istek = await self.httpx.get(f"{url}/{page}/")
|
|
23
|
+
helper = HTMLHelper(istek.text)
|
|
24
|
+
|
|
25
|
+
if "/tum-bolumler/" in url:
|
|
26
|
+
items = helper.select("div.episode-box")
|
|
27
|
+
results = []
|
|
28
|
+
for item in items:
|
|
29
|
+
name_el = helper.select_first("div.episode-name a", item)
|
|
30
|
+
if not name_el: continue
|
|
31
|
+
name = name_el.text(strip=True).split(" izle")[0]
|
|
32
|
+
title = name.replace(".Sezon ", "x").replace(".Bölüm", "")
|
|
33
|
+
|
|
34
|
+
ep_href = self.fix_url(name_el.attrs.get("href"))
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
# Revert to standard categories if "tum-bolumler" is complex
|
|
38
|
+
return []
|
|
39
|
+
else:
|
|
40
|
+
items = helper.select("div.single-item")
|
|
41
|
+
return [
|
|
42
|
+
MainPageResult(
|
|
43
|
+
category = category,
|
|
44
|
+
title = helper.select_text("div.categorytitle a", item).split(" izle")[0],
|
|
45
|
+
url = self.fix_url(helper.select_attr("div.categorytitle a", "href", item)),
|
|
46
|
+
poster = self.fix_url(helper.select_attr("div.cat-img img", "src", item))
|
|
47
|
+
)
|
|
48
|
+
for item in items
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
async def search(self, query: str) -> list[SearchResult]:
|
|
52
|
+
url = f"{self.main_url}/?s={query}"
|
|
53
|
+
istek = await self.httpx.get(url)
|
|
54
|
+
helper = HTMLHelper(istek.text)
|
|
55
|
+
items = helper.select("div.single-item")
|
|
56
|
+
|
|
57
|
+
return [
|
|
58
|
+
SearchResult(
|
|
59
|
+
title = helper.select_text("div.categorytitle a", item).split(" izle")[0],
|
|
60
|
+
url = self.fix_url(helper.select_attr("div.categorytitle a", "href", item)),
|
|
61
|
+
poster = self.fix_url(helper.select_attr("div.cat-img img", "src", item))
|
|
62
|
+
)
|
|
63
|
+
for item in items
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
async def load_item(self, url: str) -> SeriesInfo:
|
|
67
|
+
istek = await self.httpx.get(url)
|
|
68
|
+
helper = HTMLHelper(istek.text)
|
|
69
|
+
|
|
70
|
+
title_raw = helper.select_text("div.title h1")
|
|
71
|
+
title = title_raw.split(" izle")[0] if title_raw else "Bilinmiyor"
|
|
72
|
+
|
|
73
|
+
poster = self.fix_url(helper.select_attr("div.category_image img", "src"))
|
|
74
|
+
|
|
75
|
+
# Custom extraction for fields that were using xpath_text
|
|
76
|
+
year = None
|
|
77
|
+
rating = None
|
|
78
|
+
actors = None
|
|
79
|
+
|
|
80
|
+
# Regex approach for specific fields might be safer/easier if structure varies
|
|
81
|
+
# Matches: Yapım Yılı : </span> 2025
|
|
82
|
+
year_val = helper.regex_first(r"Yapım Yılı\s*:\s*(?:</span>)?\s*(\d{4})")
|
|
83
|
+
if year_val:
|
|
84
|
+
year = int(year_val)
|
|
85
|
+
|
|
86
|
+
rating_val = helper.regex_first(r"IMDB\s*:\s*([\d\.]+)")
|
|
87
|
+
if rating_val:
|
|
88
|
+
rating = rating_val
|
|
89
|
+
|
|
90
|
+
actors_val = helper.regex_first(r"Oyuncular\s*:\s*(.+?)(?:</div>|<br|$)")
|
|
91
|
+
if not actors_val:
|
|
92
|
+
# Try selecting the div text directly if regex fails due to HTML tags
|
|
93
|
+
# Find div containing "Oyuncular :"
|
|
94
|
+
all_divs = helper.select("div")
|
|
95
|
+
for div in all_divs:
|
|
96
|
+
txt = div.text()
|
|
97
|
+
if "Oyuncular :" in txt:
|
|
98
|
+
actors_val = txt.split("Oyuncular :")[1].strip()
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
if actors_val:
|
|
102
|
+
# Remove footer / junk from actors
|
|
103
|
+
if "IMDB :" in actors_val:
|
|
104
|
+
actors_val = actors_val.split("IMDB :")[0].strip()
|
|
105
|
+
|
|
106
|
+
if "IMDB :" in actors_val:
|
|
107
|
+
actors_val = actors_val.split("IMDB :")[0].strip()
|
|
108
|
+
|
|
109
|
+
# Remove '×' and other junk if present at end
|
|
110
|
+
if "×" in actors_val:
|
|
111
|
+
actors_val = actors_val.split("×")[0].strip()
|
|
112
|
+
|
|
113
|
+
# Remove simple tags if any remaining
|
|
114
|
+
clean_actors = [a.strip() for a in actors_val.split(",")]
|
|
115
|
+
# Filter empty
|
|
116
|
+
clean_actors = [a for a in clean_actors if a]
|
|
117
|
+
|
|
118
|
+
actors = ", ".join(clean_actors)
|
|
119
|
+
|
|
120
|
+
description_raw = helper.select_text("div.category_desc")
|
|
121
|
+
description = None
|
|
122
|
+
if description_raw:
|
|
123
|
+
# Clean header "The Librarians izle..." etc. if present, usually it is fine.
|
|
124
|
+
# Clean "IMDB :" if attached
|
|
125
|
+
if "IMDB :" in description_raw:
|
|
126
|
+
description_raw = description_raw.split("IMDB :")[0].strip()
|
|
127
|
+
|
|
128
|
+
# Clean footer text start
|
|
129
|
+
# The footer block usually starts with "Dizimom, dizi ve film..."
|
|
130
|
+
# If we find "Dizimom," and it's not at the start (meaning it's part of the footer appended), split there.
|
|
131
|
+
# Note: The description might legitimately start with "Dizimom," strictly speaking, but unlikely for a series description.
|
|
132
|
+
if "Dizimom," in description_raw:
|
|
133
|
+
description = description_raw.split("Dizimom,")[0].strip()
|
|
134
|
+
elif "dizi izle film izle" in description_raw:
|
|
135
|
+
description = description_raw.split("dizi izle film izle")[0].strip()
|
|
136
|
+
else:
|
|
137
|
+
description = description_raw
|
|
138
|
+
|
|
139
|
+
# Fallback cleanup for JSON
|
|
140
|
+
if description and "{" in description:
|
|
141
|
+
description = description.split("{")[0].strip()
|
|
142
|
+
|
|
143
|
+
tags = helper.select_all_text("div.genres a")
|
|
144
|
+
|
|
145
|
+
# Improved year regex
|
|
146
|
+
if not year:
|
|
147
|
+
# Look for "Yapım Yılı : 2014" pattern in ANY text
|
|
148
|
+
# Get all text from category_text which usually contains it
|
|
149
|
+
meta_text = helper.select_text("div.category_text")
|
|
150
|
+
if meta_text:
|
|
151
|
+
match = re.search(r"Yapım Yılı\s*:\s*(\d{4})", meta_text)
|
|
152
|
+
if match:
|
|
153
|
+
year = int(match.group(1))
|
|
154
|
+
|
|
155
|
+
episodes = []
|
|
156
|
+
ep_items = helper.select("div.bolumust")
|
|
157
|
+
for item in ep_items:
|
|
158
|
+
ep_name_raw = helper.select_text("div.baslik", item)
|
|
159
|
+
ep_href = self.fix_url(helper.select_attr("a", "href", item))
|
|
160
|
+
|
|
161
|
+
if ep_name_raw:
|
|
162
|
+
# 1.Sezon 1.Bölüm
|
|
163
|
+
s_m = re.search(r"(\d+)\.Sezon", ep_name_raw)
|
|
164
|
+
e_m = re.search(r"(\d+)\.Bölüm", ep_name_raw)
|
|
165
|
+
|
|
166
|
+
season = int(s_m.group(1)) if s_m else 1
|
|
167
|
+
episode = int(e_m.group(1)) if e_m else 1
|
|
168
|
+
|
|
169
|
+
name = ep_name_raw.split(" izle")[0].replace(title, "").strip()
|
|
170
|
+
|
|
171
|
+
episodes.append(Episode(
|
|
172
|
+
season = season,
|
|
173
|
+
episode = episode,
|
|
174
|
+
title = name,
|
|
175
|
+
url = ep_href
|
|
176
|
+
))
|
|
177
|
+
|
|
178
|
+
return SeriesInfo(
|
|
179
|
+
url = url,
|
|
180
|
+
poster = poster,
|
|
181
|
+
title = title,
|
|
182
|
+
description = description,
|
|
183
|
+
tags = tags,
|
|
184
|
+
rating = rating,
|
|
185
|
+
year = str(year) if year else None,
|
|
186
|
+
actors = actors,
|
|
187
|
+
episodes = episodes
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
async def load_links(self, url: str) -> list[ExtractResult]:
|
|
191
|
+
# Login simulation headers
|
|
192
|
+
headers = {
|
|
193
|
+
"User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Safari/537.36",
|
|
194
|
+
"sec-ch-ua": 'Not/A)Brand";v="8", "Chromium";v="137", "Google Chrome";v="137"',
|
|
195
|
+
"sec-ch-ua-mobile": "?1",
|
|
196
|
+
"sec-ch-ua-platform": "Android"
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# Simulate login (as seen in Kotlin)
|
|
200
|
+
login_url = f"{self.main_url}/wp-login.php"
|
|
201
|
+
login_data = {
|
|
202
|
+
"log": "keyiflerolsun",
|
|
203
|
+
"pwd": "12345",
|
|
204
|
+
"rememberme": "forever",
|
|
205
|
+
"redirect_to": self.main_url
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
await self.httpx.post(login_url, headers=headers, data=login_data)
|
|
209
|
+
|
|
210
|
+
istek = await self.httpx.get(url, headers=headers)
|
|
211
|
+
helper = HTMLHelper(istek.text)
|
|
212
|
+
|
|
213
|
+
iframes = []
|
|
214
|
+
|
|
215
|
+
main_iframe = helper.select_attr("iframe[src]", "src")
|
|
216
|
+
if main_iframe:
|
|
217
|
+
iframes.append(main_iframe)
|
|
218
|
+
|
|
219
|
+
sources = helper.select("div.sources a")
|
|
220
|
+
for source in sources:
|
|
221
|
+
href = source.attrs.get("href")
|
|
222
|
+
if href:
|
|
223
|
+
sub_istek = await self.httpx.get(href, headers=headers)
|
|
224
|
+
sub_helper = HTMLHelper(sub_istek.text)
|
|
225
|
+
sub_iframe = sub_helper.select_attr("div.video p iframe", "src")
|
|
226
|
+
if sub_iframe:
|
|
227
|
+
iframes.append(sub_iframe)
|
|
228
|
+
|
|
229
|
+
results = []
|
|
230
|
+
for iframe_url in iframes:
|
|
231
|
+
# Check for known extractors
|
|
232
|
+
if iframe_url.startswith("//"):
|
|
233
|
+
iframe_url = f"https:{iframe_url}"
|
|
234
|
+
|
|
235
|
+
extract_result = await self.extract(iframe_url)
|
|
236
|
+
if extract_result:
|
|
237
|
+
if isinstance(extract_result, list):
|
|
238
|
+
results.extend(extract_result)
|
|
239
|
+
else:
|
|
240
|
+
results.append(extract_result)
|
|
241
|
+
else:
|
|
242
|
+
results.append(ExtractResult(
|
|
243
|
+
url = iframe_url,
|
|
244
|
+
name = f"{self.name} | External",
|
|
245
|
+
referer = self.main_url
|
|
246
|
+
))
|
|
247
|
+
|
|
248
|
+
return results
|
|
@@ -127,18 +127,27 @@ class DiziPal(PluginBase):
|
|
|
127
127
|
|
|
128
128
|
poster = self.fix_url(secici.select_attr("meta[property='og:image']", "content")) if secici.select_attr("meta[property='og:image']", "content") else None
|
|
129
129
|
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
# Sidebar bilgilerini topla
|
|
131
|
+
info = {}
|
|
132
|
+
for li in secici.select("li"):
|
|
133
|
+
key = secici.select_text("div.key", li)
|
|
134
|
+
val = secici.select_text("div.value", li)
|
|
135
|
+
if key and val:
|
|
136
|
+
info[key.strip(":")] = val.strip()
|
|
137
|
+
|
|
138
|
+
year = info.get("Yapım Yılı")
|
|
139
|
+
rating = info.get("IMDB Puanı")
|
|
140
|
+
|
|
141
|
+
tags_raw = info.get("Türler", "")
|
|
142
|
+
tags = [t.strip() for t in tags_raw.split() if t.strip()] if tags_raw else None
|
|
134
143
|
|
|
135
|
-
|
|
144
|
+
actors_raw = info.get("Oyuncular")
|
|
145
|
+
actors = [a.strip() for a in actors_raw.split(",") if a.strip()] if actors_raw else None
|
|
136
146
|
|
|
137
|
-
|
|
138
|
-
tags = [t.strip() for t in tags_raw.split() if t.strip()] if tags_raw else None
|
|
147
|
+
description = secici.select_text("div.summary p")
|
|
139
148
|
|
|
140
|
-
duration_raw =
|
|
141
|
-
duration = int(duration_raw) if duration_raw else None
|
|
149
|
+
duration_raw = info.get("Ortalama Süre")
|
|
150
|
+
duration = int(secici.regex_first(r"(\d+)", duration_raw)) if duration_raw else None
|
|
142
151
|
|
|
143
152
|
if "/dizi/" in url:
|
|
144
153
|
title = secici.select_text("div.cover h5")
|
|
@@ -177,6 +186,7 @@ class DiziPal(PluginBase):
|
|
|
177
186
|
year = year,
|
|
178
187
|
duration = duration,
|
|
179
188
|
episodes = episodes if episodes else None,
|
|
189
|
+
actors = actors,
|
|
180
190
|
)
|
|
181
191
|
else:
|
|
182
192
|
# Film için title - g-title div'lerinin 2. olanı
|
|
@@ -192,6 +202,7 @@ class DiziPal(PluginBase):
|
|
|
192
202
|
rating = rating,
|
|
193
203
|
year = year,
|
|
194
204
|
duration = duration,
|
|
205
|
+
actors = actors,
|
|
195
206
|
)
|
|
196
207
|
|
|
197
208
|
async def load_links(self, url: str) -> list[ExtractResult]:
|
|
@@ -72,7 +72,7 @@ class DiziYou(PluginBase):
|
|
|
72
72
|
html_text = istek.text
|
|
73
73
|
|
|
74
74
|
# Title - div.title h1 içinde
|
|
75
|
-
title = secici.select_text("div.title h1")
|
|
75
|
+
title = (secici.select_text("div.title h1") or "").strip()
|
|
76
76
|
|
|
77
77
|
# Fallback: Eğer title boşsa URL'den çıkar (telif kısıtlaması olan sayfalar için)
|
|
78
78
|
if not title:
|
|
@@ -81,23 +81,19 @@ class DiziYou(PluginBase):
|
|
|
81
81
|
title = slug.replace('-', ' ').title()
|
|
82
82
|
|
|
83
83
|
# Poster
|
|
84
|
-
poster_src = secici.select_attr("div.category_image img", "src")
|
|
84
|
+
poster_src = secici.select_attr("div.category_image img", "src") or secici.select_attr("meta[property='og:image']", "content")
|
|
85
85
|
poster = self.fix_url(poster_src) if poster_src else ""
|
|
86
86
|
|
|
87
87
|
# Year - regex ile çıkarma (xpath yerine)
|
|
88
88
|
year = secici.regex_first(r"(?is)Yapım Yılı.*?(\d{4})", secici.html)
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
desc_html = HTMLHelper(desc_html).regex_replace(r"(?s)<div id=\"icerikcat2\".*", "")
|
|
98
|
-
# Kalan HTML'den text çıkar
|
|
99
|
-
clean_sel = HTMLHelper(desc_html)
|
|
100
|
-
description = clean_sel.select_text()
|
|
90
|
+
description_el = secici.select("div.diziyou_desc") or secici.select("div#icerikcat")
|
|
91
|
+
description = ""
|
|
92
|
+
if description_el:
|
|
93
|
+
# Scriptleri temizle
|
|
94
|
+
for script in secici.select("script", description_el[0]):
|
|
95
|
+
script.decompose()
|
|
96
|
+
description = secici.select_text(None, description_el[0])
|
|
101
97
|
|
|
102
98
|
tags = [secici.select_text(None, a) for a in secici.select("div.genres a") if secici.select_text(None, a)]
|
|
103
99
|
|
|
@@ -109,9 +105,9 @@ class DiziYou(PluginBase):
|
|
|
109
105
|
actors = [actor.strip() for actor in actors_raw.split(",") if actor.strip()] if actors_raw else []
|
|
110
106
|
|
|
111
107
|
episodes = []
|
|
112
|
-
# Episodes -
|
|
113
|
-
for link in secici.select("a"):
|
|
114
|
-
ep_href = secici.select_attr(
|
|
108
|
+
# Episodes - div#scrollbar-container a (kısıtlı alan)
|
|
109
|
+
for link in secici.select("div#scrollbar-container a"):
|
|
110
|
+
ep_href = secici.select_attr(None, "href", link)
|
|
115
111
|
if not ep_href:
|
|
116
112
|
continue
|
|
117
113
|
|
|
@@ -179,9 +175,9 @@ class DiziYou(PluginBase):
|
|
|
179
175
|
# Player src'den item_id çıkar - önce özel player seçicisini dene
|
|
180
176
|
player_src = None
|
|
181
177
|
# Yaygın locatorlar
|
|
182
|
-
for sel in ["iframe#diziyouPlayer", "div.player iframe", "iframe[src*='/episodes/']", "iframe"]:
|
|
178
|
+
for sel in ["iframe#diziyouPlayer", "div.player iframe", "iframe[src*='/player/']", "iframe[src*='/episodes/']", "iframe"]:
|
|
183
179
|
p = secici.select_attr(sel, "src")
|
|
184
|
-
if p and
|
|
180
|
+
if p and any(x in p.lower() for x in ["/player/", "/episodes/", "diziyou"]):
|
|
185
181
|
player_src = p
|
|
186
182
|
break
|
|
187
183
|
|