Files
Barış Keser 0fb3e54316 Refactor and enhance Jikan client and sources
- Updated JikanCache and JikanClient classes to use Optional types for parameters.
- Improved error handling and dynamic video ID fetching in animecix.py.
- Removed unused Animely adapter and adjusted provider priorities.
- Cleaned up adapter.py by removing the SearchEngine class.
- Enhanced error handling and cookie management in tranime.py.
- Added direct search functionality in tranime.py to improve anime search results.
- Updated title matching logic in title_matching.py to use Optional types.
- Bumped version to 9.4.9.0.
2026-02-26 00:54:11 +03:00

315 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
BYPASS Modülü, TürkAnime'deki şifreyle saklanan elementlerini çözmek
ve firewall'i kandirmak için gerekli fonksiyonlari / rutinleri içerir.
- Fetch(url)->str Firefox TLS & HTTP/3 taklitli GET Request fonksiyonu
- obtain_key()->bytes TürkAnime'nin iframe şifrelerken kullandigi AES anahtari bulur
- decrypt_cipher(key, data)->str CryptoJS.AES.decrypt python implementasyonu
- get_real_url(cipher)->str TürkAnime'nin iframe şifresini çözüp gerçek video URL'sini getir
- decrypt_jsjiamiv7(cipher, key): Reverse jsjiamiv7 -> decodeURIComponent(base64(RC4(KSA + PRGA)))
- obtain_csrf()->str TürkAnime'nin encrypted tuttuğu csrf tokeni bul, decryptle,getir
- unmask_real_url(masked_url): Alucard, Bankai, Amaterasu, HDVID url maskesini çöz.
"""
import os
import re
from base64 import b64decode
import json
from hashlib import md5
from appdirs import user_cache_dir
from tempfile import NamedTemporaryFile
from Crypto.Cipher import AES
from curl_cffi import requests
# CF Bypass modülünü içe aktar
try:
from turkanime_api.common.cf_bypass import CFSession, CFBypassError
HAS_CF_BYPASS = True
except ImportError:
CFBypassError = ConnectionError # Fallback exception type
HAS_CF_BYPASS = False
def _get_cf_session():
"""CF bypass session döndür."""
if not HAS_CF_BYPASS:
return None
try:
return CFSession(timeout=60)
except Exception:
return None
session = None
BASE_URL = "https://turkanime.tv/"
def fetch(path, headers={}):
"""Curl-cffi kullanarak HTTP/3 ve Firefox TLS Fingerprint Impersonation
eyleyerek GET request atmak IUAM aktif olmadigi sürece CF'yi bypassliyor. """
global session, BASE_URL
# Init: Çerezleri cart curt oluştur, yeni domain geldiyse yönlendir.
if session is None:
session = requests.Session(impersonate="firefox", allow_redirects=True)
try:
res = session.get(BASE_URL + "/")
if res.status_code != 200:
raise ConnectionError(f"Status: {res.status_code}")
BASE_URL = res.url
BASE_URL = BASE_URL[:-1] if BASE_URL.endswith('/') else BASE_URL
except Exception as e:
# CF engeli - cf_bypass ile dene
print(f"[TurkAnime] curl_cffi başarısız ({e}), CF bypass deneniyor...")
cf_session = _get_cf_session()
if cf_session is not None:
try:
res = cf_session.get(BASE_URL)
if res.status_code == 200:
BASE_URL = res.url if hasattr(res, 'url') else BASE_URL
BASE_URL = BASE_URL[:-1] if BASE_URL.endswith('/') else BASE_URL
# Session çerezlerini aktar
for name, value in cf_session.cookies.items():
session.cookies.set(name, value)
print(f"[TurkAnime] CF bypass başarılı ({cf_session.last_method})")
except CFBypassError:
raise ConnectionError("Tüm CF bypass yöntemleri başarısız")
else:
raise ConnectionError("CF bypass modülü mevcut değil")
if path is None:
return ""
# Get request'i yolla
path = path if path.startswith("/") else "/" + path
headers["X-Requested-With"] = "XMLHttpRequest"
try:
resp = session.get(BASE_URL + path, headers=headers)
if resp.status_code == 403:
# CF engeli - fallback dene
cf_session = _get_cf_session()
if cf_session is not None:
resp = cf_session.get(BASE_URL + path, headers=headers)
if resp.status_code == 200:
return resp.text
raise ConnectionError("Cloudflare engeli aşılamadı")
return resp.text
except Exception as e:
# Hata durumunda CF bypass ile tekrar dene
cf_session = _get_cf_session()
if cf_session is not None:
try:
resp = cf_session.get(BASE_URL + path, headers=headers)
return resp.text
except CFBypassError:
pass
raise
"""
Videoların gerçek URL'lerini decryptleyen fonksiyonlar
örn: eyJjdCI6IldXUmRNWFdCMG15T253dXUmRNWFd3V -> https://dv97.sibnet.ru/15/80/112314.mp4
"""
def obtain_key() -> bytes:
"""
Şifreli iframe url'sini decryptlemek için gerekli anahtarı döndürür.
Javascript dosyalarının isimleri ve anahtar, periyodik olarak değiştiğinden,
güncel şifre için aşağıdaki algoritmayla tersine mühendislik yapıyoruz:
- /embed/ endpointin çağırdığı 2. javascript dosyasını aç.
- Bu dosyanın içinde çağırılan diğer iki javascript dosyasını da regexle bul.
- Bu iki dosyadan içinde "decrypt" ifadesi geçeni seç
- Bir liste olarak obfuscate edilmiş bu javascript dosyasından şifreyi edin.
"""
try:
# İlk javascript dosyasını ve importladığı dosyaları bul.
js1 = fetch(
re.findall(
r"/embed/js/embeds\..*?\.js",
fetch("/embed/#/url/"))[1]
)
js1_imports = re.findall("[a-z0-9]{16}",js1)
# Bu dosyalardan içinde "decrypt" ifadesi geçen dosyayı bul.
j2 = fetch(f'/embed/js/embeds.{js1_imports[0]}.js')
if "'decrypt'" not in j2:
j2 = fetch(f'/embed/js/embeds.{js1_imports[1]}.js')
# Obfuscated listeyi parse'la.
match = re.search(
'function a\\d_0x[\\w]{1,4}\\(\\){var _0x\\w{3,8}=\\[(.*?)\\];',j2
)
if match is None:
return b""
obfuscate_list = match.group(1)
# Listedeki en uzun elemanı, yani şifremizi bul.
return max(
obfuscate_list.split("','"),
key=lambda i:len( re.sub(r"\\x\d\d","?",i))
).encode()
except (IndexError, AttributeError):
return b""
def decrypt_cipher(key: bytes, data: bytes) -> str:
""" CryptoJS.AES.decrypt'in python implementasyonu
referans:
- https://stackoverflow.com/a/36780727
- https://gist.github.com/ysfchn/e96304fb41375bad0fdf9a5e837da631
"""
def salted_key(data: bytes, salt: bytes, output: int = 48):
assert len(salt) == 8, len(salt)
data += salt
key = md5(data).digest()
final_key = key
while len(final_key) < output:
key = md5(key + data).digest()
final_key += key
return final_key[:output]
def unpad(data: bytes) -> bytes:
return data[:-(data[-1] if isinstance(data[-1],int) else ord(data[-1]))]
# Remove URL path from the string.
b64 = b64decode(data)
cipher = json.loads(b64)
cipher_text = b64decode(cipher["ct"])
iv = bytes.fromhex(cipher["iv"])
salt = bytes.fromhex(cipher["s"])
# Create new AES object with using salted key as key.
crypt = AES.new(salted_key(key, salt, output=32), iv=iv, mode=AES.MODE_CBC)
# Decrypt link and unpad it.
try:
return unpad(crypt.decrypt(cipher_text)).decode("utf-8")
except (UnicodeDecodeError, ValueError):
return ""
def get_real_url(url_cipher: str, cache=True) -> str:
""" Videonun gerçek url'sini decrypt'le, parolayı da cache'le. """
cache_file = os.path.join(user_cache_dir(),"turkanimu_key.cache")
# Daha önceden cache'lenmiş key varsa onunla şifreyi çözmeyi dene.
if cache and os.path.isfile(cache_file):
with open(cache_file,"r",encoding="utf-8") as f:
cached_key = f.read().strip().encode()
plaintext = decrypt_cipher(cached_key, url_cipher.encode())
if plaintext:
return plaintext
# Cache'lenmiş key işe yaramadıysa, yeni key'i edin ve decryptlemeyi dene.
key = obtain_key()
plaintext = decrypt_cipher(key, url_cipher.encode())
if not plaintext:
raise ValueError("Embed URLsinin şifresi çözülemedi.")
# Cache'i güncelle
if cache:
with open(cache_file,"w",encoding="utf-8") as f:
f.write(key.decode("utf-8"))
return plaintext
"""
TürkAnime'nin kendi player'larından url çıkartan fonksiyonlar (Alucard, Bankai, Amaterasu vs.)
örn: http://turkanime.co/sources/UW1EN2VPcExLUXpiaDRqcnV0d -> https://alucard.stream/cdn/playlist/3S3CtAJxAZ
"""
PLAYERJS_URL = "/js/player.js"
PLAYERJS_CSRF = None
def decrypt_jsjiamiv7(ciphertext, key):
"""
jsjiamiv7 obfuscator ile şifrelenmiş bi cipher'ı decryptleyen fonksiyon
- Cipher nedense non-standart bir alfabeyle translate edilmiş, onu normal base64 alfabesine çevir
- Sonra base64 decode eyle
- Sonra RC4 (KSA + PRGA) algoritması ile şifreyi çöz https://en.wikipedia.org/wiki/RC4
- Galiba bu internette ilk. v5, v6 decode'layan buldum da, v7 decodelayan proje bulamadım.
"""
_CUSTOM = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/"
_STD = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
_TRANSLATE = str.maketrans(_CUSTOM, _STD)
t = ciphertext.translate(_TRANSLATE)
t += "=" * (-len(t) % 4)
data = b64decode(t).decode("utf-8")
S = list(range(256))
j = 0
klen = len(key)
# KSA
for i in range(256):
j = (j + S[i] + ord(key[i % klen])) & 0xff
S[i], S[j] = S[j], S[i]
# PRGA
i = j = 0
out = []
for ch in data:
i = (i + 1) & 0xff
j = (j + S[i]) & 0xff
S[i], S[j] = S[j], S[i]
out.append(chr(ord(ch) ^ S[(S[i] + S[j]) & 0xff]))
return "".join(out)
def obtain_csrf():
"""
/js/player.js dosyasındaki jsjiamiv7 ile şifrelenmiş csrf tokeni edin.
- regex ile key'i çıkar ve ciphertext olabilecek bütün text'leri çıkar
- bütün adayları key ile decryptlemeyi dene, başarılı çıkan sonuç csrf tokenidir.
"""
res = fetch(PLAYERJS_URL)
# Key'i çıkar
key = re.findall(r"csrf-token':[^\n\)]+'([^']+)'\)", res, re.IGNORECASE)
# Bütün Ciphertext adaylarını çıkar
candidates = re.findall(r"'([a-zA-Z\d\+\/]{96,156})',",res)
assert key and candidates
key = key[0]
# Hepsini decrypt'lemeyi dene, başarılı olanı döndür
decrypted_list = [decrypt_jsjiamiv7(ct,key) for ct in candidates]
return next((i for i in decrypted_list if re.search(r"^[a-zA-Z/\+]+$",i)), None)
def unmask_real_url(url_mask):
""" TürkAnime'nin kendi playerlarının url maskesini çözer. """
global PLAYERJS_CSRF
assert "turkanime" in url_mask
if PLAYERJS_CSRF is None:
try:
PLAYERJS_CSRF = obtain_csrf()
if PLAYERJS_CSRF is None:
raise LookupError
except:
print("ERROR: CSRF bulunamadı.")
return url_mask
MASK = url_mask.split("/player/")[1]
headers = {"Csrf-Token": PLAYERJS_CSRF, "cf_clearance": "dull"}
res = fetch(f"/sources/{MASK}/false",headers)
try:
url = json.loads(res)["response"]["sources"][-1]["file"]
if url.startswith("//"):
url = "https:" + url
except:
return url_mask
return url
def get_alucard_m3u8(url):
""" MPV'nin video'yu oynatabilmesi için en yüksek çözünürlüklü alucard m3u8 stream'i indir"""
global session, BASE_URL
if session is None:
session = requests.Session(impersonate="firefox", allow_redirects=True)
res = session.get(BASE_URL)
assert res.status_code == 200, ConnectionError
BASE_URL = res.url
BASE_URL = BASE_URL[:-1] if BASE_URL.endswith('/') else BASE_URL
res = session.get(url)
m3u8_url = re.findall("https://.*",res.text)[-1]
res = session.get(m3u8_url)
with NamedTemporaryFile(suffix=".m3u8", delete=False) as m3u8:
m3u8.write(res.text.encode())
return m3u8.name