KekikStream 1.9.9__py3-none-any.whl → 2.0.8__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.
- KekikStream/Core/Extractor/ExtractorBase.py +5 -5
- KekikStream/Core/Extractor/ExtractorLoader.py +25 -17
- KekikStream/Core/Extractor/ExtractorManager.py +1 -1
- KekikStream/Core/Extractor/YTDLPCache.py +35 -0
- KekikStream/Core/Plugin/PluginBase.py +4 -1
- KekikStream/Core/Plugin/PluginLoader.py +11 -7
- KekikStream/Core/__init__.py +1 -0
- KekikStream/Extractors/CloseLoad.py +1 -1
- KekikStream/Extractors/ContentX.py +13 -0
- KekikStream/Extractors/DonilasPlay.py +86 -0
- KekikStream/Extractors/Odnoklassniki.py +6 -0
- KekikStream/Extractors/PeaceMakerst.py +6 -0
- KekikStream/Extractors/RapidVid.py +6 -0
- KekikStream/Extractors/SetPlay.py +7 -1
- KekikStream/Extractors/VCTPlay.py +41 -0
- KekikStream/Extractors/VidMoly.py +52 -30
- KekikStream/Extractors/YTDLP.py +97 -58
- KekikStream/Plugins/BelgeselX.py +204 -0
- KekikStream/Plugins/Dizilla.py +22 -14
- KekikStream/Plugins/FilmMakinesi.py +3 -1
- KekikStream/Plugins/FilmModu.py +6 -2
- KekikStream/Plugins/FullHDFilmizlesene.py +1 -1
- KekikStream/Plugins/HDFilmCehennemi.py +83 -8
- KekikStream/Plugins/JetFilmizle.py +53 -38
- KekikStream/Plugins/KultFilmler.py +1 -1
- KekikStream/Plugins/RoketDizi.py +17 -24
- KekikStream/Plugins/SelcukFlix.py +51 -52
- KekikStream/Plugins/SetFilmIzle.py +259 -0
- KekikStream/Plugins/SezonlukDizi.py +28 -7
- KekikStream/Plugins/Sinefy.py +15 -9
- KekikStream/Plugins/SinemaCX.py +3 -7
- KekikStream/Plugins/Sinezy.py +16 -1
- KekikStream/Plugins/SuperFilmGeldi.py +1 -1
- KekikStream/Plugins/UgurFilm.py +1 -1
- {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/METADATA +27 -1
- kekikstream-2.0.8.dist-info/RECORD +81 -0
- KekikStream/Extractors/FourCX.py +0 -7
- KekikStream/Extractors/FourPichive.py +0 -7
- KekikStream/Extractors/FourPlayRu.py +0 -7
- KekikStream/Extractors/HDStreamAble.py +0 -7
- KekikStream/Extractors/Hotlinger.py +0 -7
- KekikStream/Extractors/OkRuHTTP.py +0 -7
- KekikStream/Extractors/OkRuSSL.py +0 -7
- KekikStream/Extractors/Pichive.py +0 -7
- KekikStream/Extractors/PlayRu.py +0 -7
- KekikStream/Extractors/VidMolyMe.py +0 -7
- kekikstream-1.9.9.dist-info/RECORD +0 -86
- {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/WHEEL +0 -0
- {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/entry_points.txt +0 -0
- {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/licenses/LICENSE +0 -0
- {kekikstream-1.9.9.dist-info → kekikstream-2.0.8.dist-info}/top_level.txt +0 -0
KekikStream/Extractors/YTDLP.py
CHANGED
|
@@ -1,8 +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
|
|
3
|
+
from KekikStream.Core import ExtractorBase, ExtractResult, Subtitle, get_ytdlp_extractors
|
|
4
4
|
from urllib.parse import urlparse
|
|
5
|
-
from yt_dlp.extractor import gen_extractors
|
|
6
5
|
import yt_dlp, re, sys, os
|
|
7
6
|
|
|
8
7
|
class YTDLP(ExtractorBase):
|
|
@@ -11,51 +10,120 @@ class YTDLP(ExtractorBase):
|
|
|
11
10
|
|
|
12
11
|
_FAST_DOMAIN_RE = None # compiled mega-regex (host üstünden)
|
|
13
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
|
+
|
|
14
45
|
@classmethod
|
|
15
|
-
def
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
18
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."""
|
|
19
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)
|
|
20
76
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
domain_pat = re.compile(r"(?:[a-z0-9-]+\\\.)+[a-z]{2,}", re.IGNORECASE)
|
|
77
|
+
# 2. Simple Non-Capturing Groups (?:xxx)? temizle (sadece alphanumeric ve escape)
|
|
78
|
+
before = re.sub(r"\(\?:[a-z0-9-]+\)\??", "", before)
|
|
24
79
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
if
|
|
80
|
+
# Son domain-like kelimeyi al
|
|
81
|
+
words = cls._DOMAIN_WORD_RE.findall(before)
|
|
82
|
+
if not words:
|
|
28
83
|
continue
|
|
29
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:
|
|
30
105
|
valid = getattr(ie, "_VALID_URL", None)
|
|
31
106
|
if not valid or not isinstance(valid, str):
|
|
32
107
|
continue
|
|
33
108
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# Çok agresif/şüpheli şeyleri elemek istersen burada filtre koyabilirsin
|
|
38
|
-
# (genelde gerek kalmıyor)
|
|
39
|
-
domains.add(d)
|
|
109
|
+
domains |= cls._extract_literal_domains(valid)
|
|
110
|
+
domains |= cls._extract_regex_tld_domains(valid)
|
|
111
|
+
domains |= cls._extract_alternation_domains(valid)
|
|
40
112
|
|
|
41
113
|
# Hiç domain çıkmazsa (çok uç durum) fallback: boş regex
|
|
42
114
|
if not domains:
|
|
43
115
|
cls._FAST_DOMAIN_RE = re.compile(r"$^") # hiçbir şeye match etmez
|
|
44
116
|
return
|
|
45
117
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
joined = "|".join(sorted(re.escape(d) for d in domains))
|
|
49
|
-
pattern = rf"(?:^|.*\.)(?:{joined})$"
|
|
50
|
-
cls._FAST_DOMAIN_RE = re.compile(pattern, re.IGNORECASE)
|
|
118
|
+
joined = "|".join(re.escape(d) for d in sorted(domains))
|
|
119
|
+
cls._FAST_DOMAIN_RE = re.compile(rf"(?:^|.*\.)(?:{joined})$", re.IGNORECASE)
|
|
51
120
|
|
|
52
121
|
def __init__(self):
|
|
53
122
|
self.__class__._init_fast_domain_regex()
|
|
54
123
|
|
|
55
124
|
def can_handle_url(self, url: str) -> bool:
|
|
56
125
|
"""
|
|
57
|
-
Fast-path: URL host'unu tek mega-regex ile kontrol et
|
|
58
|
-
Slow-path: gerekirse mevcut extract_info tabanlı kontrolün
|
|
126
|
+
Fast-path: URL host'unu tek mega-regex ile kontrol et
|
|
59
127
|
"""
|
|
60
128
|
# URL parse + host al
|
|
61
129
|
try:
|
|
@@ -76,39 +144,8 @@ class YTDLP(ExtractorBase):
|
|
|
76
144
|
if host and self.__class__._FAST_DOMAIN_RE.search(host):
|
|
77
145
|
return True
|
|
78
146
|
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
# stderr'ı geçici olarak kapat (hata mesajlarını gizle)
|
|
82
|
-
old_stderr = sys.stderr
|
|
83
|
-
sys.stderr = open(os.devnull, "w")
|
|
84
|
-
|
|
85
|
-
try:
|
|
86
|
-
ydl_opts = {
|
|
87
|
-
"simulate" : True, # Download yok, sadece tespit
|
|
88
|
-
"quiet" : True, # Log kirliliği yok
|
|
89
|
-
"no_warnings" : True, # Uyarı mesajları yok
|
|
90
|
-
"extract_flat" : True, # Minimal işlem
|
|
91
|
-
"no_check_certificates" : True,
|
|
92
|
-
"ignoreerrors" : True # Hataları yoksay
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
|
|
96
|
-
# URL'yi işleyebiliyor mu kontrol et
|
|
97
|
-
info = ydl.extract_info(url, download=False, process=False)
|
|
98
|
-
|
|
99
|
-
# Generic extractor ise atla
|
|
100
|
-
if info and info.get("extractor_key") != "Generic":
|
|
101
|
-
return True
|
|
102
|
-
|
|
103
|
-
return False
|
|
104
|
-
finally:
|
|
105
|
-
# stderr'ı geri yükle
|
|
106
|
-
sys.stderr.close()
|
|
107
|
-
sys.stderr = old_stderr
|
|
108
|
-
|
|
109
|
-
except Exception:
|
|
110
|
-
# yt-dlp işleyemezse False döndür
|
|
111
|
-
return False
|
|
147
|
+
# yt-dlp işleyemezse False döndür
|
|
148
|
+
return False
|
|
112
149
|
|
|
113
150
|
async def extract(self, url: str, referer: str | None = None) -> ExtractResult:
|
|
114
151
|
ydl_opts = {
|
|
@@ -116,7 +153,9 @@ class YTDLP(ExtractorBase):
|
|
|
116
153
|
"no_warnings" : True,
|
|
117
154
|
"extract_flat" : False, # Tam bilgi al
|
|
118
155
|
"format" : "best", # En iyi kalite
|
|
119
|
-
"no_check_certificates" : True
|
|
156
|
+
"no_check_certificates" : True,
|
|
157
|
+
"socket_timeout" : 3,
|
|
158
|
+
"retries" : 1
|
|
120
159
|
}
|
|
121
160
|
|
|
122
161
|
# Referer varsa header olarak ekle
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Bu araç @keyiflerolsun tarafından | @KekikAkademi için yazılmıştır.
|
|
2
|
+
|
|
3
|
+
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, SeriesInfo, Episode
|
|
4
|
+
from parsel import Selector
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
class BelgeselX(PluginBase):
|
|
8
|
+
name = "BelgeselX"
|
|
9
|
+
language = "tr"
|
|
10
|
+
main_url = "https://belgeselx.com"
|
|
11
|
+
favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
|
|
12
|
+
description = "2022 yılında son çıkan belgeselleri belgeselx.com'da izle. En yeni belgeseller, türkçe altyazılı yada dublaj olarak 1080p kalitesinde hd belgesel izle."
|
|
13
|
+
|
|
14
|
+
main_page = {
|
|
15
|
+
f"{main_url}/konu/turk-tarihi-belgeselleri&page=" : "Türk Tarihi",
|
|
16
|
+
f"{main_url}/konu/tarih-belgeselleri&page=" : "Tarih",
|
|
17
|
+
f"{main_url}/konu/seyehat-belgeselleri&page=" : "Seyahat",
|
|
18
|
+
f"{main_url}/konu/seri-belgeseller&page=" : "Seri",
|
|
19
|
+
f"{main_url}/konu/savas-belgeselleri&page=" : "Savaş",
|
|
20
|
+
f"{main_url}/konu/sanat-belgeselleri&page=" : "Sanat",
|
|
21
|
+
f"{main_url}/konu/psikoloji-belgeselleri&page=" : "Psikoloji",
|
|
22
|
+
f"{main_url}/konu/polisiye-belgeselleri&page=" : "Polisiye",
|
|
23
|
+
f"{main_url}/konu/otomobil-belgeselleri&page=" : "Otomobil",
|
|
24
|
+
f"{main_url}/konu/nazi-belgeselleri&page=" : "Nazi",
|
|
25
|
+
f"{main_url}/konu/muhendislik-belgeselleri&page=" : "Mühendislik",
|
|
26
|
+
f"{main_url}/konu/kultur-din-belgeselleri&page=" : "Kültür Din",
|
|
27
|
+
f"{main_url}/konu/kozmik-belgeseller&page=" : "Kozmik",
|
|
28
|
+
f"{main_url}/konu/hayvan-belgeselleri&page=" : "Hayvan",
|
|
29
|
+
f"{main_url}/konu/eski-tarih-belgeselleri&page=" : "Eski Tarih",
|
|
30
|
+
f"{main_url}/konu/egitim-belgeselleri&page=" : "Eğitim",
|
|
31
|
+
f"{main_url}/konu/dunya-belgeselleri&page=" : "Dünya",
|
|
32
|
+
f"{main_url}/konu/doga-belgeselleri&page=" : "Doğa",
|
|
33
|
+
f"{main_url}/konu/bilim-belgeselleri&page=" : "Bilim"
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def _to_title_case(text: str) -> str:
|
|
38
|
+
"""Türkçe için title case dönüşümü."""
|
|
39
|
+
return " ".join(
|
|
40
|
+
word.lower().replace("i", "İ").capitalize() if word.lower().startswith("i") else word.capitalize()
|
|
41
|
+
for word in text.split()
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
async def get_main_page(self, page: int, url: str, category: str) -> list[MainPageResult]:
|
|
45
|
+
istek = self.cloudscraper.get(f"{url}{page}")
|
|
46
|
+
secici = Selector(istek.text)
|
|
47
|
+
|
|
48
|
+
results = []
|
|
49
|
+
for item in secici.css("div.gen-movie-contain > div.gen-info-contain > div.gen-movie-info"):
|
|
50
|
+
title = item.css("div.gen-movie-info > h3 a::text").get()
|
|
51
|
+
href = item.css("div.gen-movie-info > h3 a::attr(href)").get()
|
|
52
|
+
parent = item.xpath("../../..")
|
|
53
|
+
poster = parent.css("div.gen-movie-img > img::attr(src)").get()
|
|
54
|
+
|
|
55
|
+
if title and href:
|
|
56
|
+
results.append(MainPageResult(
|
|
57
|
+
category = category,
|
|
58
|
+
title = self._to_title_case(title.strip()),
|
|
59
|
+
url = self.fix_url(href),
|
|
60
|
+
poster = self.fix_url(poster) if poster else None
|
|
61
|
+
))
|
|
62
|
+
|
|
63
|
+
return results
|
|
64
|
+
|
|
65
|
+
async def search(self, query: str) -> list[SearchResult]:
|
|
66
|
+
# Google Custom Search API kullanıyor
|
|
67
|
+
cx = "016376594590146270301:iwmy65ijgrm"
|
|
68
|
+
|
|
69
|
+
token_resp = self.cloudscraper.get(f"https://cse.google.com/cse.js?cx={cx}")
|
|
70
|
+
token_text = token_resp.text
|
|
71
|
+
|
|
72
|
+
cse_lib_match = re.search(r'cselibVersion": "(.*)"', token_text)
|
|
73
|
+
cse_tok_match = re.search(r'cse_token": "(.*)"', token_text)
|
|
74
|
+
|
|
75
|
+
if not cse_lib_match or not cse_tok_match:
|
|
76
|
+
return []
|
|
77
|
+
|
|
78
|
+
cse_lib = cse_lib_match.group(1)
|
|
79
|
+
cse_tok = cse_tok_match.group(1)
|
|
80
|
+
|
|
81
|
+
search_url = (
|
|
82
|
+
f"https://cse.google.com/cse/element/v1?"
|
|
83
|
+
f"rsz=filtered_cse&num=100&hl=tr&source=gcsc&cselibv={cse_lib}&cx={cx}"
|
|
84
|
+
f"&q={query}&safe=off&cse_tok={cse_tok}&sort=&exp=cc%2Capo&oq={query}"
|
|
85
|
+
f"&callback=google.search.cse.api9969&rurl=https%3A%2F%2Fbelgeselx.com%2F"
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
resp = self.cloudscraper.get(search_url)
|
|
89
|
+
resp_text = resp.text
|
|
90
|
+
|
|
91
|
+
titles = re.findall(r'"titleNoFormatting": "(.*?)"', resp_text)
|
|
92
|
+
urls = re.findall(r'"url": "(.*?)"', resp_text)
|
|
93
|
+
images = re.findall(r'"ogImage": "(.*?)"', resp_text)
|
|
94
|
+
|
|
95
|
+
results = []
|
|
96
|
+
for i, title in enumerate(titles):
|
|
97
|
+
url_val = urls[i] if i < len(urls) else None
|
|
98
|
+
poster = images[i] if i < len(images) else None
|
|
99
|
+
|
|
100
|
+
if not url_val or "diziresimleri" not in url_val:
|
|
101
|
+
# URL'den belgesel linkini oluştur
|
|
102
|
+
if poster and "diziresimleri" in poster:
|
|
103
|
+
file_name = poster.rsplit("/", 1)[-1]
|
|
104
|
+
file_name = re.sub(r"\.(jpe?g|png|webp)$", "", file_name)
|
|
105
|
+
url_val = f"{self.main_url}/belgeseldizi/{file_name}"
|
|
106
|
+
else:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
clean_title = title.split("İzle")[0].strip()
|
|
110
|
+
results.append(SearchResult(
|
|
111
|
+
title = self._to_title_case(clean_title),
|
|
112
|
+
url = url_val,
|
|
113
|
+
poster = poster
|
|
114
|
+
))
|
|
115
|
+
|
|
116
|
+
return results
|
|
117
|
+
|
|
118
|
+
async def load_item(self, url: str) -> SeriesInfo:
|
|
119
|
+
istek = await self.httpx.get(url)
|
|
120
|
+
secici = Selector(istek.text)
|
|
121
|
+
|
|
122
|
+
title = secici.css("h2.gen-title::text").get()
|
|
123
|
+
poster = secici.css("div.gen-tv-show-top img::attr(src)").get()
|
|
124
|
+
description = secici.css("div.gen-single-tv-show-info p::text").get()
|
|
125
|
+
|
|
126
|
+
tags = []
|
|
127
|
+
for tag_link in secici.css("div.gen-socail-share a[href*='belgeselkanali']"):
|
|
128
|
+
tag_href = tag_link.css("::attr(href)").get()
|
|
129
|
+
if tag_href:
|
|
130
|
+
tag_name = tag_href.rsplit("/", 1)[-1].replace("-", " ")
|
|
131
|
+
tags.append(self._to_title_case(tag_name))
|
|
132
|
+
|
|
133
|
+
episodes = []
|
|
134
|
+
counter = 0
|
|
135
|
+
for ep_item in secici.css("div.gen-movie-contain"):
|
|
136
|
+
ep_name = ep_item.css("div.gen-movie-info h3 a::text").get()
|
|
137
|
+
ep_href = ep_item.css("div.gen-movie-info h3 a::attr(href)").get()
|
|
138
|
+
|
|
139
|
+
if not ep_name or not ep_href:
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
season_text = ep_item.css("div.gen-single-meta-holder ul li::text").get() or ""
|
|
143
|
+
episode_match = re.search(r"Bölüm (\d+)", season_text)
|
|
144
|
+
season_match = re.search(r"Sezon (\d+)", season_text)
|
|
145
|
+
|
|
146
|
+
ep_episode = int(episode_match.group(1)) if episode_match else counter
|
|
147
|
+
ep_season = int(season_match.group(1)) if season_match else 1
|
|
148
|
+
|
|
149
|
+
counter += 1
|
|
150
|
+
|
|
151
|
+
episodes.append(Episode(
|
|
152
|
+
season = ep_season,
|
|
153
|
+
episode = ep_episode,
|
|
154
|
+
title = ep_name.strip(),
|
|
155
|
+
url = self.fix_url(ep_href)
|
|
156
|
+
))
|
|
157
|
+
|
|
158
|
+
return SeriesInfo(
|
|
159
|
+
url = url,
|
|
160
|
+
poster = self.fix_url(poster) if poster else None,
|
|
161
|
+
title = self._to_title_case(title.strip()) if title else None,
|
|
162
|
+
description = description.strip() if description else None,
|
|
163
|
+
tags = tags,
|
|
164
|
+
episodes = episodes
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
async def load_links(self, url: str) -> list[dict]:
|
|
168
|
+
istek = await self.httpx.get(url)
|
|
169
|
+
text = istek.text
|
|
170
|
+
|
|
171
|
+
# fnc_addWatch div'inden data-episode ID'sini al
|
|
172
|
+
ep_match = re.search(r'<div[^>]*class=["\'][^"\']*fnc_addWatch[^"\']*["\'][^>]*data-episode=["\'](\d+)["\']', text)
|
|
173
|
+
if not ep_match:
|
|
174
|
+
return []
|
|
175
|
+
|
|
176
|
+
episode_id = ep_match.group(1)
|
|
177
|
+
iframe_url = f"{self.main_url}/video/data/new4.php?id={episode_id}"
|
|
178
|
+
|
|
179
|
+
iframe_resp = await self.httpx.get(iframe_url, headers={"Referer": url})
|
|
180
|
+
iframe_text = iframe_resp.text
|
|
181
|
+
|
|
182
|
+
links = []
|
|
183
|
+
for match in re.finditer(r'file:"([^"]+)", label: "([^"]+)"', iframe_text):
|
|
184
|
+
video_url = match.group(1)
|
|
185
|
+
quality = match.group(2)
|
|
186
|
+
|
|
187
|
+
source_name = "Google" if quality == "FULL" else self.name
|
|
188
|
+
quality_str = "1080p" if quality == "FULL" else quality
|
|
189
|
+
|
|
190
|
+
links.append({
|
|
191
|
+
"url" : video_url,
|
|
192
|
+
"name" : f"{source_name} | {quality_str}",
|
|
193
|
+
"referer" : url
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
return links
|
|
197
|
+
|
|
198
|
+
async def play(self, **kwargs):
|
|
199
|
+
extract_result = ExtractResult(**kwargs)
|
|
200
|
+
self.media_handler.title = kwargs.get("name")
|
|
201
|
+
if self.name not in self.media_handler.title:
|
|
202
|
+
self.media_handler.title = f"{self.name} | {self.media_handler.title}"
|
|
203
|
+
|
|
204
|
+
self.media_handler.play_media(extract_result)
|
KekikStream/Plugins/Dizilla.py
CHANGED
|
@@ -137,7 +137,7 @@ class Dizilla(PluginBase):
|
|
|
137
137
|
year = veri.get("datePublished").split("-")[0]
|
|
138
138
|
|
|
139
139
|
# Tags extraction from page content (h3 tag)
|
|
140
|
-
tags_raw = secici.css("
|
|
140
|
+
tags_raw = secici.css("div.poster.poster h3::text").get()
|
|
141
141
|
tags = [t.strip() for t in tags_raw.split(",")] if tags_raw else []
|
|
142
142
|
|
|
143
143
|
rating = veri.get("aggregateRating", {}).get("ratingValue")
|
|
@@ -179,17 +179,25 @@ class Dizilla(PluginBase):
|
|
|
179
179
|
decrypted = await self.decrypt_response(secure_data)
|
|
180
180
|
results = decrypted.get("RelatedResults", {}).get("getEpisodeSources", {}).get("result", [])
|
|
181
181
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
iframe_src = Selector(result.get("source_content")).css("iframe::attr(src)").get()
|
|
185
|
-
iframe_url = self.fix_url(iframe_src)
|
|
186
|
-
if not iframe_url:
|
|
187
|
-
continue
|
|
182
|
+
if not results:
|
|
183
|
+
return []
|
|
188
184
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
185
|
+
# Get first source (matching Kotlin)
|
|
186
|
+
first_result = results[0]
|
|
187
|
+
source_content = first_result.get("source_content", "")
|
|
188
|
+
|
|
189
|
+
# Clean the source_content string (matching Kotlin: .replace("\"", "").replace("\\", ""))
|
|
190
|
+
cleaned_source = source_content.replace('"', '').replace('\\', '')
|
|
191
|
+
|
|
192
|
+
# Parse cleaned HTML
|
|
193
|
+
iframe_src = Selector(cleaned_source).css("iframe::attr(src)").get()
|
|
194
|
+
iframe_url = self.fix_url(iframe_src)
|
|
195
|
+
|
|
196
|
+
if not iframe_url:
|
|
197
|
+
return []
|
|
198
|
+
|
|
199
|
+
extractor = self.ex_manager.find_extractor(iframe_url)
|
|
200
|
+
return [{
|
|
201
|
+
"url" : iframe_url,
|
|
202
|
+
"name" : f"{extractor.name if extractor else 'Main Player'} | {first_result.get('language_name', 'Unknown')}",
|
|
203
|
+
}]
|
|
@@ -8,7 +8,7 @@ class FilmMakinesi(PluginBase):
|
|
|
8
8
|
language = "tr"
|
|
9
9
|
main_url = "https://filmmakinesi.to"
|
|
10
10
|
favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
|
|
11
|
-
description = "Film Makinesi
|
|
11
|
+
description = "Film Makinesi ile en yeni ve güncel filmleri Full HD kalite farkı ile izleyebilirsiniz. Film izle denildiğinde akla gelen en kaliteli film sitesi."
|
|
12
12
|
|
|
13
13
|
main_page = {
|
|
14
14
|
f"{main_url}/filmler-1/" : "Son Filmler",
|
|
@@ -83,6 +83,7 @@ class FilmMakinesi(PluginBase):
|
|
|
83
83
|
rating = rating.strip().split()[0]
|
|
84
84
|
year = secici.css("span.date a::text").get().strip()
|
|
85
85
|
actors = secici.css("div.cast-name::text").getall()
|
|
86
|
+
tags = secici.css("div.genre a::text").getall()
|
|
86
87
|
duration = secici.css("div.time::text").get()
|
|
87
88
|
if duration:
|
|
88
89
|
duration = duration.split()[1].strip()
|
|
@@ -92,6 +93,7 @@ class FilmMakinesi(PluginBase):
|
|
|
92
93
|
poster = self.fix_url(poster),
|
|
93
94
|
title = self.clean_title(title),
|
|
94
95
|
description = description,
|
|
96
|
+
tags = tags,
|
|
95
97
|
rating = rating,
|
|
96
98
|
year = year,
|
|
97
99
|
actors = actors,
|
KekikStream/Plugins/FilmModu.py
CHANGED
|
@@ -9,7 +9,7 @@ class FilmModu(PluginBase):
|
|
|
9
9
|
language = "tr"
|
|
10
10
|
main_url = "https://www.filmmodu.ws"
|
|
11
11
|
favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
|
|
12
|
-
description = "
|
|
12
|
+
description = "Film modun geldiyse yüksek kalitede yeni filmleri izle, 1080p izleyebileceğiniz reklamsız tek film sitesi."
|
|
13
13
|
|
|
14
14
|
main_page = {
|
|
15
15
|
f"{main_url}/hd-film-kategori/4k-film-izle?page=SAYFA" : "4K",
|
|
@@ -91,9 +91,13 @@ class FilmModu(PluginBase):
|
|
|
91
91
|
istek = await self.httpx.get(url)
|
|
92
92
|
secici = Selector(istek.text)
|
|
93
93
|
|
|
94
|
+
alternates = secici.css("div.alternates a")
|
|
95
|
+
if not alternates:
|
|
96
|
+
return [] # No alternates available
|
|
97
|
+
|
|
94
98
|
results = []
|
|
95
99
|
|
|
96
|
-
for alternatif in
|
|
100
|
+
for alternatif in alternates:
|
|
97
101
|
alt_link = self.fix_url(alternatif.css("::attr(href)").get())
|
|
98
102
|
alt_name = alternatif.css("::text").get()
|
|
99
103
|
|
|
@@ -10,7 +10,7 @@ class FullHDFilmizlesene(PluginBase):
|
|
|
10
10
|
language = "tr"
|
|
11
11
|
main_url = "https://www.fullhdfilmizlesene.tv"
|
|
12
12
|
favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
|
|
13
|
-
description = "
|
|
13
|
+
description = "Türkiye'nin ilk ve lider HD film izleme platformu, kaliteli ve sorunsuz hizmetiyle sinema keyfini zirveye taşır."
|
|
14
14
|
|
|
15
15
|
main_page = {
|
|
16
16
|
f"{main_url}/en-cok-izlenen-hd-filmler/" : "En Çok izlenen Filmler",
|
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
from KekikStream.Core import PluginBase, MainPageResult, SearchResult, MovieInfo, ExtractResult, Subtitle
|
|
4
4
|
from parsel import Selector
|
|
5
5
|
from Kekik.Sifreleme import Packer, StreamDecoder
|
|
6
|
-
import random, string, re
|
|
6
|
+
import random, string, re, base64
|
|
7
7
|
|
|
8
8
|
class HDFilmCehennemi(PluginBase):
|
|
9
9
|
name = "HDFilmCehennemi"
|
|
10
10
|
language = "tr"
|
|
11
11
|
main_url = "https://www.hdfilmcehennemi.ws"
|
|
12
12
|
favicon = f"https://www.google.com/s2/favicons?domain={main_url}&sz=64"
|
|
13
|
-
description = "Türkiye'nin en hızlı hd film izleme sitesi"
|
|
13
|
+
description = "Türkiye'nin en hızlı hd film izleme sitesi. Tek ve gerçek hdfilmcehennemi sitesi."
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
|
|
@@ -146,17 +146,85 @@ class HDFilmCehennemi(PluginBase):
|
|
|
146
146
|
|
|
147
147
|
return results
|
|
148
148
|
|
|
149
|
+
def hdch_decode(self, value_parts: list[str]) -> str:
|
|
150
|
+
"""
|
|
151
|
+
HDFilmCehennemi için özel decoder.
|
|
152
|
+
JavaScript sırası: join -> reverse -> atob -> atob -> shift_back
|
|
153
|
+
"""
|
|
154
|
+
try:
|
|
155
|
+
# 1. Parçaları birleştir
|
|
156
|
+
joined = ''.join(value_parts)
|
|
157
|
+
|
|
158
|
+
# 2. Ters çevir (REV)
|
|
159
|
+
result = StreamDecoder._reverse(joined)
|
|
160
|
+
|
|
161
|
+
# 3. İlk base64 decode (B64D)
|
|
162
|
+
result = StreamDecoder._base64_decode(result)
|
|
163
|
+
if result is None:
|
|
164
|
+
return ""
|
|
165
|
+
|
|
166
|
+
# 4. İkinci base64 decode (B64D) - JavaScript'te iki kez atob yapılıyor!
|
|
167
|
+
result = StreamDecoder._base64_decode(result)
|
|
168
|
+
if result is None:
|
|
169
|
+
return ""
|
|
170
|
+
|
|
171
|
+
# 5. Shift back (SHF)
|
|
172
|
+
result = StreamDecoder._shift_back(result)
|
|
173
|
+
|
|
174
|
+
return result
|
|
175
|
+
except Exception:
|
|
176
|
+
return ""
|
|
177
|
+
|
|
178
|
+
def extract_hdch_url(self, unpacked: str) -> str:
|
|
179
|
+
"""HDFilmCehennemi unpacked script'ten video URL'sini çıkar"""
|
|
180
|
+
# 1) Decode fonksiyonunun adını bul: function <NAME>(value_parts)
|
|
181
|
+
match_fn = re.search(r'function\s+(\w+)\s*\(\s*value_parts\s*\)', unpacked)
|
|
182
|
+
if not match_fn:
|
|
183
|
+
return ""
|
|
184
|
+
|
|
185
|
+
fn_name = match_fn.group(1)
|
|
186
|
+
|
|
187
|
+
# 2) Bu fonksiyonun array ile çağrıldığı yeri bul: <NAME>([ ... ])
|
|
188
|
+
array_call_regex = re.compile(rf'{re.escape(fn_name)}\(\s*\[(.*?)\]\s*\)', re.DOTALL)
|
|
189
|
+
match_call = array_call_regex.search(unpacked)
|
|
190
|
+
if not match_call:
|
|
191
|
+
return ""
|
|
192
|
+
|
|
193
|
+
array_body = match_call.group(1)
|
|
194
|
+
|
|
195
|
+
# 3) Array içindeki string parçalarını topla
|
|
196
|
+
parts = re.findall(r'["\']([^"\']+)["\']', array_body)
|
|
197
|
+
if not parts:
|
|
198
|
+
return ""
|
|
199
|
+
|
|
200
|
+
# 4) Özel decoder ile çöz
|
|
201
|
+
return self.hdch_decode(parts)
|
|
202
|
+
|
|
149
203
|
async def invoke_local_source(self, iframe: str, source: str, url: str):
|
|
150
|
-
self.httpx.headers.update({
|
|
204
|
+
self.httpx.headers.update({
|
|
205
|
+
"Referer": f"{self.main_url}/",
|
|
206
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0"
|
|
207
|
+
})
|
|
151
208
|
istek = await self.httpx.get(iframe)
|
|
152
209
|
|
|
210
|
+
if not istek.text:
|
|
211
|
+
return await self.cehennempass(iframe.split("/")[-1])
|
|
212
|
+
|
|
213
|
+
# eval(function...) içeren packed script bul
|
|
214
|
+
eval_match = re.search(r'(eval\(function[\s\S]+)', istek.text)
|
|
215
|
+
if not eval_match:
|
|
216
|
+
return await self.cehennempass(iframe.split("/")[-1])
|
|
217
|
+
|
|
153
218
|
try:
|
|
154
|
-
|
|
219
|
+
unpacked = Packer.unpack(eval_match.group(1))
|
|
155
220
|
except Exception:
|
|
156
221
|
return await self.cehennempass(iframe.split("/")[-1])
|
|
157
222
|
|
|
158
|
-
|
|
159
|
-
video_url =
|
|
223
|
+
# HDFilmCehennemi özel decoder ile video URL'sini çıkar
|
|
224
|
+
video_url = self.extract_hdch_url(unpacked)
|
|
225
|
+
|
|
226
|
+
if not video_url:
|
|
227
|
+
return await self.cehennempass(iframe.split("/")[-1])
|
|
160
228
|
|
|
161
229
|
subtitles = []
|
|
162
230
|
try:
|
|
@@ -200,8 +268,15 @@ class HDFilmCehennemi(PluginBase):
|
|
|
200
268
|
match = re.search(r'data-src=\\\"([^"]+)', api_get.text)
|
|
201
269
|
iframe = match[1].replace("\\", "") if match else None
|
|
202
270
|
|
|
203
|
-
if
|
|
204
|
-
|
|
271
|
+
if not iframe:
|
|
272
|
+
continue
|
|
273
|
+
|
|
274
|
+
# mobi URL'si varsa direkt kullan (query string'i kaldır)
|
|
275
|
+
if "mobi" in iframe:
|
|
276
|
+
iframe = iframe.split("?")[0] # rapidrame_id query param'ı kaldır
|
|
277
|
+
# mobi değilse ve rapidrame varsa rplayer kullan
|
|
278
|
+
elif "rapidrame" in iframe and "?rapidrame_id=" in iframe:
|
|
279
|
+
iframe = f"{self.main_url}/rplayer/{iframe.split('?rapidrame_id=')[1]}"
|
|
205
280
|
|
|
206
281
|
video_data_list = await self.invoke_local_source(iframe, source, url)
|
|
207
282
|
if not video_data_list:
|