Files
comet/comet/utils/general.py
T
2024-07-17 00:37:36 +02:00

416 lines
12 KiB
Python
Raw 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.
import base64
import hashlib
import json
import re
import aiohttp
import bencodepy
from comet.utils.logger import logger
from comet.utils.models import settings, ConfigModel
translation_table = {
"ā": "a",
"ă": "a",
"ą": "a",
"ć": "c",
"č": "c",
"ç": "c",
"ĉ": "c",
"ċ": "c",
"ď": "d",
"đ": "d",
"è": "e",
"é": "e",
"ê": "e",
"ë": "e",
"ē": "e",
"ĕ": "e",
"ę": "e",
"ě": "e",
"ĝ": "g",
"ğ": "g",
"ġ": "g",
"ģ": "g",
"ĥ": "h",
"î": "i",
"ï": "i",
"ì": "i",
"í": "i",
"ī": "i",
"ĩ": "i",
"ĭ": "i",
"ı": "i",
"ĵ": "j",
"ķ": "k",
"ĺ": "l",
"ļ": "l",
"ł": "l",
"ń": "n",
"ň": "n",
"ñ": "n",
"ņ": "n",
"ʼn": "n",
"ó": "o",
"ô": "o",
"õ": "o",
"ö": "o",
"ø": "o",
"ō": "o",
"ő": "o",
"œ": "oe",
"ŕ": "r",
"ř": "r",
"ŗ": "r",
"š": "s",
"ş": "s",
"ś": "s",
"ș": "s",
"ß": "ss",
"ť": "t",
"ţ": "t",
"ū": "u",
"ŭ": "u",
"ũ": "u",
"û": "u",
"ü": "u",
"ù": "u",
"ú": "u",
"ų": "u",
"ű": "u",
"ŵ": "w",
"ý": "y",
"ÿ": "y",
"ŷ": "y",
"ž": "z",
"ż": "z",
"ź": "z",
"æ": "ae",
"ǎ": "a",
"ǧ": "g",
"ə": "e",
"ƒ": "f",
"ǐ": "i",
"ǒ": "o",
"ǔ": "u",
"ǚ": "u",
"ǜ": "u",
"ǹ": "n",
"ǻ": "a",
"ǽ": "ae",
"ǿ": "o",
}
translation_table = str.maketrans(translation_table)
info_hash_pattern = re.compile(r"\b([a-fA-F0-9]{40})\b")
def translate(title: str):
return title.translate(translation_table)
def is_video(title: str):
return title.endswith(
tuple(
[
".mkv",
".mp4",
".avi",
".mov",
".flv",
".wmv",
".webm",
".mpg",
".mpeg",
".m4v",
".3gp",
".3g2",
".ogv",
".ogg",
".drc",
".gif",
".gifv",
".mng",
".avi",
".mov",
".qt",
".wmv",
".yuv",
".rm",
".rmvb",
".asf",
".amv",
".m4p",
".m4v",
".mpg",
".mp2",
".mpeg",
".mpe",
".mpv",
".mpg",
".mpeg",
".m2v",
".m4v",
".svi",
".3gp",
".3g2",
".mxf",
".roq",
".nsv",
".flv",
".f4v",
".f4p",
".f4a",
".f4b",
]
)
)
def bytes_to_size(bytes: int):
sizes = ["Bytes", "KB", "MB", "GB", "TB"]
if bytes == 0:
return "0 Byte"
i = 0
while bytes >= 1024 and i < len(sizes) - 1:
bytes /= 1024
i += 1
return f"{round(bytes, 2)} {sizes[i]}"
def config_check(b64config: str):
try:
config = json.loads(base64.b64decode(b64config).decode())
validated_config = ConfigModel(**config)
return validated_config.model_dump()
except:
return False
def get_debrid_extension(debridService: str):
debrid_extension = "?" # Unknown
if debridService == "realdebrid":
debrid_extension = "RD"
elif debridService == "alldebrid":
debrid_extension = "AD"
elif debridService == "premiumize":
debrid_extension = "PM"
elif debridService == "torbox":
debrid_extension = "TB"
elif debridService == "debridlink":
debrid_extension = "DL"
return debrid_extension
async def get_indexer_manager(
session: aiohttp.ClientSession,
indexer_manager_type: str,
indexers: list,
query: str,
):
results = []
try:
indexers = [indexer.replace("_", " ") for indexer in indexers]
timeout = aiohttp.ClientTimeout(total=settings.INDEXER_MANAGER_TIMEOUT)
if indexer_manager_type == "jackett":
response = await session.get(
f"{settings.INDEXER_MANAGER_URL}/api/v2.0/indexers/all/results?apikey={settings.INDEXER_MANAGER_API_KEY}&Query={query}&Tracker[]={'&Tracker[]='.join(indexer for indexer in indexers)}",
timeout=timeout,
)
response = await response.json()
for result in response["Results"]:
results.append(result)
if indexer_manager_type == "prowlarr":
get_indexers = await session.get(
f"{settings.INDEXER_MANAGER_URL}/api/v1/indexer",
headers={"X-Api-Key": settings.INDEXER_MANAGER_API_KEY},
)
get_indexers = await get_indexers.json()
indexers_id = []
for indexer in get_indexers:
if (
indexer["name"].lower() in indexers
or indexer["definitionName"].lower() in indexers
):
indexers_id.append(indexer["id"])
response = await session.get(
f"{settings.INDEXER_MANAGER_URL}/api/v1/search?query={query}&indexerIds={'&indexerIds='.join(str(indexer_id) for indexer_id in indexers_id)}&type=search",
headers={"X-Api-Key": settings.INDEXER_MANAGER_API_KEY},
)
response = await response.json()
for result in response:
result["InfoHash"] = result["infoHash"] if "infoHash" in result else None
result["Title"] = result["title"]
result["Link"] = result["downloadUrl"]
result["Tracker"] = result["indexer"]
results.append(result)
except Exception as e:
logger.warning(
f"Exception while getting {indexer_manager_type} results for {query} with {indexers}: {e}"
)
pass
return results
async def get_zilean(
session: aiohttp.ClientSession, name: str, log_name: str, season: int, episode: int
):
results = []
try:
if season is None:
get_dmm = await session.post(
f"{settings.ZILEAN_URL}/dmm/search", json={"queryText": name}
)
get_dmm = await get_dmm.json()
if isinstance(get_dmm, list):
take_first = get_dmm[: settings.ZILEAN_TAKE_FIRST]
for result in take_first:
object = {
"Title": result["filename"],
"InfoHash": result["infoHash"],
"Tracker": "DMM",
}
results.append(object)
else:
get_dmm = await session.get(
f"{settings.ZILEAN_URL}/dmm/filtered?query={name}&season={season}&episode={episode}"
)
get_dmm = await get_dmm.json()
if isinstance(get_dmm, list):
take_first = get_dmm[: settings.ZILEAN_TAKE_FIRST]
for result in take_first:
object = {
"Title": result["rawTitle"],
"InfoHash": result["infoHash"],
"Tracker": "DMM",
}
results.append(object)
logger.info(f"{len(results)} torrents found for {log_name} with Zilean")
except Exception as e:
logger.warning(
f"Exception while getting torrents for {log_name} with Zilean: {e}"
)
pass
return results
async def get_torrent_hash(session: aiohttp.ClientSession, torrent: tuple):
index = torrent[0]
torrent = torrent[1]
if "InfoHash" in torrent and torrent["InfoHash"] is not None:
return (index, torrent["InfoHash"].lower())
url = torrent["Link"]
try:
timeout = aiohttp.ClientTimeout(total=settings.GET_TORRENT_TIMEOUT)
response = await session.get(url, allow_redirects=False, timeout=timeout)
if response.status == 200:
torrent_data = await response.read()
torrent_dict = bencodepy.decode(torrent_data)
info = bencodepy.encode(torrent_dict[b"info"])
hash = hashlib.sha1(info).hexdigest()
else:
location = response.headers.get("Location", "")
if not location:
return (index, None)
match = info_hash_pattern.search(location)
if not match:
return (index, None)
hash = match.group(1).upper()
return (index, hash.lower())
except Exception as e:
logger.warning(
f"Exception while getting torrent info hash for {torrent['indexer'] if 'indexer' in torrent else (torrent['Tracker'] if 'Tracker' in torrent else '')}|{url}: {e}"
)
return (index, None)
async def get_balanced_hashes(hashes: dict, config: dict):
max_results = config["maxResults"]
max_size = config["maxSize"]
config_resolutions = config["resolutions"]
config_languages = {
language.replace("_", " ").capitalize() for language in config["languages"]
}
include_all_languages = "All" in config_languages
include_all_resolutions = "All" in config_resolutions
include_unknown_resolution = (
include_all_resolutions or "Unknown" in config_resolutions
)
hashes_by_resolution = {}
for hash, hash_data in hashes.items():
hash_info = hash_data["data"]
if max_size != 0 and hash_info["size"] > max_size:
continue
if (
not include_all_languages
and not hash_info["is_multi_audio"]
and not any(lang in hash_info["language"] for lang in config_languages)
):
continue
resolution = hash_info["resolution"]
if not resolution:
if not include_unknown_resolution:
continue
resolution_key = "Unknown"
else:
resolution_key = resolution[0]
if not include_all_resolutions and resolution_key not in config_resolutions:
continue
if resolution_key not in hashes_by_resolution:
hashes_by_resolution[resolution_key] = []
hashes_by_resolution[resolution_key].append(hash)
total_resolutions = len(hashes_by_resolution)
if max_results == 0 or total_resolutions == 0:
return hashes_by_resolution
hashes_per_resolution = max_results // total_resolutions
extra_hashes = max_results % total_resolutions
balanced_hashes = {}
for resolution, hash_list in hashes_by_resolution.items():
selected_count = hashes_per_resolution + (1 if extra_hashes > 0 else 0)
balanced_hashes[resolution] = hash_list[:selected_count]
if extra_hashes > 0:
extra_hashes -= 1
selected_total = sum(len(hashes) for hashes in balanced_hashes.values())
if selected_total < max_results:
missing_hashes = max_results - selected_total
for resolution, hash_list in hashes_by_resolution.items():
if missing_hashes <= 0:
break
current_count = len(balanced_hashes[resolution])
available_hashes = hash_list[current_count : current_count + missing_hashes]
balanced_hashes[resolution].extend(available_hashes)
missing_hashes -= len(available_hashes)
return balanced_hashes