From 15b22e75aa4c4e0d32e3dbc8d76cd1c45eeedde6 Mon Sep 17 00:00:00 2001 From: Goldy <153996346+g0ldyy@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:07:33 +0100 Subject: [PATCH] feat: easydebrid support --- comet/api/stream.py | 21 ++--- comet/debrid/easydebrid.py | 178 +++++++++++++++++++++++++++++++++++++ comet/debrid/manager.py | 3 + comet/templates/index.html | 25 +++++- comet/utils/general.py | 22 ++++- comet/utils/models.py | 2 +- 6 files changed, 232 insertions(+), 19 deletions(-) create mode 100644 comet/debrid/easydebrid.py diff --git a/comet/api/stream.py b/comet/api/stream.py index 239c21c2..be2a8485 100644 --- a/comet/api/stream.py +++ b/comet/api/stream.py @@ -147,7 +147,7 @@ async def stream( config["debridApiKey"] = settings.PROXY_DEBRID_STREAM_DEBRID_DEFAULT_APIKEY if config["debridApiKey"] == "": - services = ["realdebrid", "alldebrid", "premiumize", "torbox", "debridlink"] + services = ["realdebrid", "alldebrid", "premiumize", "torbox", "debridlink", "easydebrid"] debrid_emoji = "⬇️" else: services = [config["debridService"]] @@ -445,17 +445,6 @@ async def stream( ) if len_sorted_ranked_files == 0: - if config["debridApiKey"] == "realdebrid": - return { - "streams": [ - { - "name": "[⚠️] Comet", - "description": "RealDebrid API is unstable!", - "url": "https://comet.fast", - } - ] - } - return {"streams": []} sorted_ranked_files = { @@ -470,8 +459,14 @@ async def stream( sorted_ranked_files[hash]["data"]["tracker"] = torrents_by_hash[hash][ "Tracker" ] - sorted_ranked_files[hash]["data"]["size"] = files[hash]["size"] torrent_size = torrents_by_hash[hash]["Size"] + sorted_ranked_files[hash]["data"]["size"] = ( + files[hash]["size"] + if config["debridService"] != "easydebrid" + else torrent_size + if torrent_size + else 0 + ) sorted_ranked_files[hash]["data"]["torrent_size"] = ( torrent_size if torrent_size else files[hash]["size"] ) diff --git a/comet/debrid/easydebrid.py b/comet/debrid/easydebrid.py new file mode 100644 index 00000000..7af08048 --- /dev/null +++ b/comet/debrid/easydebrid.py @@ -0,0 +1,178 @@ +import aiohttp +import asyncio + +from RTN import parse + +from comet.utils.general import is_video +from comet.utils.logger import logger + + +class EasyDebrid: + def __init__(self, session: aiohttp.ClientSession, debrid_api_key: str, ip: str): + self.session = session + self.ip = ip + self.proxy = None + + self.api_url = "https://easydebrid.com/api/v1" + self.headers = {"Authorization": f"Bearer {debrid_api_key}"} + + if ip: + self.headers["X-Forwarded-For"] = ip + + async def check_premium(self): + try: + response = await self.session.get( + f"{self.api_url}/user/details", headers=self.headers + ) + data = await response.json() + return bool(data["paid_until"]) + except Exception as e: + logger.warning(f"Failed to check EasyDebrid premium status: {e}") + + return False + + async def get_instant(self, chunk): + try: + response = await self.session.post( + f"{self.api_url}/link/lookup", + json={"urls": chunk}, + headers=self.headers, + ) + data = await response.json() + + if not data or "cached" not in data: + return None + + return { + "status": "success", + "response": data["cached"], + "filename": data.get("filenames", []), + "filesize": [None] * len(chunk), + "hashes": chunk, + } + except Exception as e: + logger.warning( + f"Exception while checking hash instant availability on EasyDebrid: {e}" + ) + + async def get_files(self, torrent_hashes, type, season, episode, kitsu): + 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) + + files = {} + + if type == "series": + for result in responses: + if result["status"] != "success": + continue + + responses = result["response"] + filenames = result["filename"] + hashes = result["hashes"] + + for index, (is_cached, hash) in enumerate(zip(responses, hashes)): + if not is_cached: + continue + + hash_files = filenames[index] + + for filename in hash_files: + if not is_video(filename): + continue + + if "sample" in filename.lower(): + continue + + filename_parsed = parse(filename) + if not filename_parsed: + continue + + if episode not in filename_parsed.episodes: + continue + + if kitsu: + if filename_parsed.seasons: + continue + elif season not in filename_parsed.seasons: + continue + + files[hash] = { + "index": f"{season}|{episode}", + "title": filename, + "size": 0, # Size not available in lookup response + } + break # Found matching video file + else: + for result in responses: + if result["status"] != "success": + continue + + responses = result["response"] + filenames = result["filename"] + hashes = result["hashes"] + + for index, (is_cached, hash) in enumerate(zip(responses, hashes)): + if not is_cached: + continue + + hash_files = filenames[index] + + video_files = [f for f in hash_files if is_video(f)] + if not video_files: + continue + + # Use first valid video file found + files[hash] = { + "index": 0, + "title": video_files[0], + "size": 0, # Size not available in lookup response + } + + return files + + async def generate_download_link(self, hash, index): + try: + response = await self.session.post( + f"{self.api_url}/link/generate", + headers={**self.headers, "Content-Type": "application/json"}, + json={"url": f"magnet:?xt=urn:btih:{hash}"}, + ) + data = await response.json() + + if not data or "files" not in data: + return None + + video_files = [ + f + for f in data["files"] + if is_video(f["filename"]) and "sample" not in f["filename"].lower() + ] + + if not video_files: + return None + + if "|" in str(index): + season, episode = map(int, index.split("|")) + for file in video_files: + parsed = parse(file["filename"]) + if ( + parsed + and season in parsed.seasons + and episode in parsed.episodes + ): + return file["url"] + + largest_file = max(video_files, key=lambda x: x["size"]) + + return largest_file["url"] + except Exception as e: + logger.warning(f"Error generating link for {hash}|{index}: {e}") diff --git a/comet/debrid/manager.py b/comet/debrid/manager.py index 8b9385bd..415348c0 100644 --- a/comet/debrid/manager.py +++ b/comet/debrid/manager.py @@ -5,6 +5,7 @@ from .premiumize import Premiumize from .torbox import TorBox from .debridlink import DebridLink +from .easydebrid import EasyDebrid def getDebrid(session: aiohttp.ClientSession, config: dict, ip: str): @@ -20,3 +21,5 @@ def getDebrid(session: aiohttp.ClientSession, config: dict, ip: str): return TorBox(session, debrid_api_key) elif debrid_service == "debridlink": return DebridLink(session, debrid_api_key) + elif debrid_service == "easydebrid": + return EasyDebrid(session, debrid_api_key, ip) \ No newline at end of file diff --git a/comet/templates/index.html b/comet/templates/index.html index d4270595..361b11bc 100644 --- a/comet/templates/index.html +++ b/comet/templates/index.html @@ -540,9 +540,10 @@
- - Debrid-Link + TorBox + EasyDebrid + Debrid-Link All-Debrid Premiumize Real-Debrid @@ -552,7 +553,9 @@
@@ -570,17 +573,33 @@ document.getElementById("debridService").addEventListener("sl-change", function(event) { const selectedService = event.target.value; const apiKeyLink = document.getElementById("apiKeyLink"); + support = document.getElementById("support"); + warning = document.getElementById("warning"); if (selectedService === "realdebrid") { apiKeyLink.href = "https://real-debrid.com/apitoken"; + support.textContent = ""; + warning.textContent = ""; } else if (selectedService === "alldebrid") { apiKeyLink.href = "https://alldebrid.com/apikeys"; + support.textContent = ""; + warning.textContent = ""; } else if (selectedService === "premiumize") { apiKeyLink.href = "https://premiumize.me/account"; + support.textContent = ""; + warning.textContent = ""; } else if (selectedService === "torbox") { apiKeyLink.href = "https://torbox.app/settings"; + support.textContent = ""; + warning.textContent = ""; } else if (selectedService === "debridlink") { apiKeyLink.href = "https://debrid-link.com/webapp/apikey"; + support.textContent = ""; + warning.textContent = ""; + } else if (selectedService === "easydebrid") { + apiKeyLink.href = "https://paradise-cloud.com/products/easydebrid"; + support.textContent = " - Support me by using promo code 'goldy'! (5% OFF) - "; + warning.textContent = "warning"; } }); diff --git a/comet/utils/general.py b/comet/utils/general.py index 375da71b..08f0590a 100644 --- a/comet/utils/general.py +++ b/comet/utils/general.py @@ -234,6 +234,22 @@ def bytes_to_size(bytes: int): return f"{round(bytes, 2)} {sizes[i]}" +def size_to_bytes(size_str: str): + sizes = ["bytes", "kb", "mb", "gb", "tb"] + try: + value, unit = size_str.split() + value = float(value) + unit = unit.lower() + + if unit not in sizes: + return None + + multiplier = 1024 ** sizes.index(unit) + return int(value * multiplier) + except: + return None + + def config_check(b64config: str): try: config = orjson.loads(base64.b64decode(b64config).decode()) @@ -253,6 +269,7 @@ def get_debrid_extension(debridService: str, debridApiKey: str = None): "premiumize": "PM", "torbox": "TB", "debridlink": "DL", + "easydebrid": "ED", } return debrid_extensions.get(debridService, None) @@ -355,7 +372,7 @@ async def get_zilean( object = { "Title": result["raw_title"], "InfoHash": result["info_hash"], - "Size": result["size"], + "Size": int(result["size"]), "Tracker": "DMM", } @@ -391,12 +408,13 @@ async def get_torrentio(log_name: str, type: str, full_id: str): title_full = torrent["title"] title = title_full.split("\n")[0] tracker = title_full.split("⚙️ ")[1].split("\n")[0] + size = size_to_bytes(title_full.split("💾 ")[1].split(" ⚙️")[0]) results.append( { "Title": title, "InfoHash": torrent["infoHash"], - "Size": None, + "Size": size, "Tracker": f"Torrentio|{tracker}", } ) diff --git a/comet/utils/models.py b/comet/utils/models.py index 62cef9e5..6ba013f3 100644 --- a/comet/utils/models.py +++ b/comet/utils/models.py @@ -123,7 +123,7 @@ def check_max_size(cls, v): @field_validator("debridService") def check_debrid_service(cls, v): - if v not in ["realdebrid", "alldebrid", "premiumize", "torbox", "debridlink"]: + if v not in ["realdebrid", "alldebrid", "premiumize", "torbox", "debridlink", "easydebrid"]: raise ValueError("Invalid debridService") return v