diff --git a/comet/api/core.py b/comet/api/core.py index 6ac4306..875e06a 100644 --- a/comet/api/core.py +++ b/comet/api/core.py @@ -72,6 +72,8 @@ async def manifest(b64config: str = None): debrid_extension = "AD" elif config["debridService"] == "premiumize": debrid_extension = "PM" + elif config["debridService"] == "torbox": + debrid_extension = "TB" return { "id": settings.ADDON_ID, diff --git a/comet/api/stream.py b/comet/api/stream.py index d39b598..54e9f48 100644 --- a/comet/api/stream.py +++ b/comet/api/stream.py @@ -134,6 +134,8 @@ async def stream(request: Request, b64config: str, type: str, id: str): debrid_extension = "AD" elif config["debridService"] == "premiumize": debrid_extension = "PM" + elif config["debridService"] == "torbox": + debrid_extension = "TB" balanced_hashes = await get_balanced_hashes(sorted_ranked_files, config) @@ -282,6 +284,8 @@ async def stream(request: Request, b64config: str, type: str, id: str): debrid_extension = "AD" elif config["debridService"] == "premiumize": debrid_extension = "PM" + elif config["debridService"] == "torbox": + debrid_extension = "TB" balanced_hashes = await get_balanced_hashes(sorted_ranked_files, config) diff --git a/comet/debrid/alldebrid.py b/comet/debrid/alldebrid.py index 2856b21..bb6d9ea 100644 --- a/comet/debrid/alldebrid.py +++ b/comet/debrid/alldebrid.py @@ -35,7 +35,7 @@ class AllDebrid: async def get_instant(self, chunk: list): try: get_instant = await self.session.get( - f"{self.api_url}/magnet/instant?agent={self.agent}&magnets[]={'&magnets[]='.join(hash for hash in chunk)}" + f"{self.api_url}/magnet/instant?agent={self.agent}&magnets[]={'&magnets[]='.join(chunk)}" ) return await get_instant.json() except Exception as e: diff --git a/comet/debrid/manager.py b/comet/debrid/manager.py index 3003a02..92aef27 100644 --- a/comet/debrid/manager.py +++ b/comet/debrid/manager.py @@ -3,6 +3,7 @@ import aiohttp from .realdebrid import RealDebrid from .alldebrid import AllDebrid from .premiumize import Premiumize +from .torbox import TorBox def getDebrid(session: aiohttp.ClientSession, config: dict): @@ -10,7 +11,9 @@ def getDebrid(session: aiohttp.ClientSession, config: dict): debrid_api_key = config["debridApiKey"] if debrid_service == "realdebrid": return RealDebrid(session, debrid_api_key) - if debrid_service == "alldebrid": + elif debrid_service == "alldebrid": return AllDebrid(session, debrid_api_key) - if debrid_service == "premiumize": + elif debrid_service == "premiumize": return Premiumize(session, debrid_api_key) + elif debrid_service == "torbox": + return TorBox(session, debrid_api_key) diff --git a/comet/debrid/premiumize.py b/comet/debrid/premiumize.py index c1bfa41..04328ab 100644 --- a/comet/debrid/premiumize.py +++ b/comet/debrid/premiumize.py @@ -11,9 +11,9 @@ class Premiumize: def __init__(self, session: aiohttp.ClientSession, debrid_api_key: str): self.session = session self.proxy = None - self.debrid_api_key = debrid_api_key self.api_url = "https://premiumize.me/api" + self.debrid_api_key = debrid_api_key async def check_premium(self): try: @@ -36,7 +36,7 @@ class Premiumize: async def get_instant(self, chunk: list): try: response = await self.session.get( - f"{self.api_url}/cache/check?apikey={self.debrid_api_key}&items[]={'&items[]='.join(hash for hash in chunk)}" + f"{self.api_url}/cache/check?apikey={self.debrid_api_key}&items[]={'&items[]='.join(chunk)}" ) response = await response.json() diff --git a/comet/debrid/realdebrid.py b/comet/debrid/realdebrid.py index 32eb29d..a0ea316 100644 --- a/comet/debrid/realdebrid.py +++ b/comet/debrid/realdebrid.py @@ -32,7 +32,7 @@ class RealDebrid: async def get_instant(self, chunk: list): try: response = await self.session.get( - f"{self.api_url}/torrents/instantAvailability/{'/'.join(hash for hash in chunk)}" + f"{self.api_url}/torrents/instantAvailability/{'/'.join(chunk)}" ) return await response.json() except Exception as e: @@ -135,7 +135,13 @@ class RealDebrid: await self.session.post( f"{self.api_url}/torrents/selectFiles/{add_magnet['id']}", - data={"files": ",".join(str(file["id"]) for file in get_magnet_info["files"] if is_video(file["path"]))}, # "all" because bad + data={ + "files": ",".join( + str(file["id"]) + for file in get_magnet_info["files"] + if is_video(file["path"]) + ) + }, # "all" because bad proxy=self.proxy, ) diff --git a/comet/debrid/torbox.py b/comet/debrid/torbox.py new file mode 100644 index 0000000..c59ebd8 --- /dev/null +++ b/comet/debrid/torbox.py @@ -0,0 +1,146 @@ +import aiohttp +import asyncio + +from RTN import parse + +from comet.utils.general import is_video +from comet.utils.logger import logger + + +class TorBox: + def __init__(self, session: aiohttp.ClientSession, debrid_api_key: str): + session.headers["Authorization"] = f"Bearer {debrid_api_key}" + self.session = session + self.proxy = None + + self.api_url = "https://api.torbox.app/v1/api" + self.debrid_api_key = debrid_api_key + + async def check_premium(self): + try: + check_premium = await self.session.get( + f"{self.api_url}/user/me?settings=false" + ) + check_premium = await check_premium.text() + if '"success":true' in check_premium and '"plan":0' not in check_premium: + return True + except Exception as e: + logger.warning(f"Exception while checking premium status on TorBox: {e}") + + return False + + async def get_instant(self, chunk: list): + try: + response = await self.session.get( + f"{self.api_url}/torrents/checkcached?hash={','.join(chunk)}&list_files=true" + ) + return await response.json() + except Exception as e: + logger.warning( + f"Exception while checking hash instant availability on TorBox: {e}" + ) + + async def get_files( + self, torrent_hashes: list, type: str, season: str, episode: str + ): + chunk_size = 100 + chunks = [ + torrent_hashes[i : i + chunk_size] + for i in range(0, len(torrent_hashes), chunk_size) + ] + + tasks = [] + for chunk in chunks: + tasks.append(self.get_instant(chunk)) + + responses = await asyncio.gather(*tasks) + + availability = [] + for response in responses: + if response is None: + continue + + availability.append(response) + + files = {} + for result in availability: + if not result["success"]: + continue + + if type == "series": + for hash in result["data"]: + hash_files = result["data"][hash]["files"] + for file in hash_files: + filename = file["name"].split("/")[1] + + if not is_video(filename): + continue + + filename_parsed = parse(filename) + if ( + season in filename_parsed.season + and episode in filename_parsed.episode + ): + files[hash] = { + "index": hash_files.index(file), + "title": filename, + "size": file["size"], + } + + continue + + for hash in result["data"]: + hash_files = result["data"][hash]["files"] + for file in hash_files: + filename = file["name"].split("/")[1] + + if not is_video(filename): + continue + + files[hash] = { + "index": hash_files.index(file), + "title": filename, + "size": file["size"], + } + + return files + + async def generate_download_link(self, hash: str, index: str): + try: + get_torrents = await self.session.get( + f"{self.api_url}/torrents/mylist?bypass_cache=true" + ) + get_torrents = await get_torrents.json() + exists = False + for torrent in get_torrents["data"]: + if torrent["hash"] == hash: + torrent_id = torrent["id"] + exists = True + break + if not exists: + create_torrent = await self.session.post( + f"{self.api_url}/torrents/createtorrent", + data={"magnet": f"magnet:?xt=urn:btih:{hash}"}, + ) + create_torrent = await create_torrent.json() + torrent_id = create_torrent["data"]["torrent_id"] + + get_torrents = await self.session.get( + f"{self.api_url}/torrents/mylist?bypass_cache=true" + ) + get_torrents = await get_torrents.json() + + for torrent in get_torrents["data"]: + if torrent["id"] == torrent_id: + file_id = torrent["files"][int(index)]["id"] + + get_download_link = await self.session.get( + f"{self.api_url}/torrents/requestdl?token={self.debrid_api_key}&torrent_id={torrent_id}&file_id={file_id}&zip=false", + ) + get_download_link = await get_download_link.json() + + return get_download_link["data"] + except Exception as e: + logger.warning( + f"Exception while getting download link from TorBox for {hash}|{index}: {e}" + ) diff --git a/comet/templates/index.html b/comet/templates/index.html index c2d95cd..b4c981f 100644 --- a/comet/templates/index.html +++ b/comet/templates/index.html @@ -536,6 +536,7 @@ Real-Debrid All-Debrid Premiumize + TorBox @@ -558,6 +559,8 @@ apiKeyLink.href = "https://alldebrid.fr/apikeys"; } else if (selectedService === "premiumize") { apiKeyLink.href = "https://premiumize.me/account"; + } else if (selectedService === "torbox") { + apiKeyLink.href = "https://torbox.app/settings"; } }); diff --git a/comet/utils/models.py b/comet/utils/models.py index d6a092c..e0cdd7f 100644 --- a/comet/utils/models.py +++ b/comet/utils/models.py @@ -67,7 +67,7 @@ class ConfigModel(BaseModel): @field_validator("debridService") def check_debrid_service(cls, v): - if v not in ["realdebrid", "alldebrid", "premiumize"]: + if v not in ["realdebrid", "alldebrid", "premiumize", "torbox"]: raise ValueError("Invalid debridService") return v