From 3b5aac130ad722f4f80ef1445b9d3485cb50586f Mon Sep 17 00:00:00 2001 From: mhdzumair Date: Thu, 2 Jan 2025 11:50:16 +0530 Subject: [PATCH] Add support for EasyDebrid streaming provider --- db/config.py | 1 + db/schemas.py | 1 + docs/configuration.md | 1 + metrics/redis_metrics.py | 1 + resources/html/configure.html | 5 ++ resources/html/home.html | 1 + resources/js/config_script.js | 1 + streaming_providers/easydebrid/__init__.py | 0 streaming_providers/easydebrid/client.py | 71 +++++++++++++++++ streaming_providers/easydebrid/utils.py | 89 ++++++++++++++++++++++ streaming_providers/mapper.py | 8 ++ utils/const.py | 1 + 12 files changed, 180 insertions(+) create mode 100644 streaming_providers/easydebrid/__init__.py create mode 100644 streaming_providers/easydebrid/client.py create mode 100644 streaming_providers/easydebrid/utils.py diff --git a/db/config.py b/db/config.py index 790e858..bb92505 100644 --- a/db/config.py +++ b/db/config.py @@ -39,6 +39,7 @@ class Settings(BaseSettings): "premiumize", "qbittorrent", "stremthru", + "easydebrid", ] ] = Field(default_factory=list) diff --git a/db/schemas.py b/db/schemas.py index 13b4e2f..f5341dc 100644 --- a/db/schemas.py +++ b/db/schemas.py @@ -142,6 +142,7 @@ class StreamingProvider(BaseModel): "premiumize", "qbittorrent", "stremthru", + "easydebrid", ] = Field(alias="sv") stremthru_store_name: ( Literal[ diff --git a/docs/configuration.md b/docs/configuration.md index 80437d4..57925f1 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -35,6 +35,7 @@ These settings define the basic configuration and identity of your MediaFusion i - "premiumize": Premiumize service - "qbittorrent": qBittorrent client - "stremthru": StremThru service + - "easydebrid": EasyDebrid service ## Database and Cache Settings diff --git a/metrics/redis_metrics.py b/metrics/redis_metrics.py index 9c440f9..b35d96a 100644 --- a/metrics/redis_metrics.py +++ b/metrics/redis_metrics.py @@ -128,6 +128,7 @@ async def get_debrid_cache_metrics() -> Dict[str, Any]: "realdebrid", "seedr", "torbox", + "easydebrid", ] try: diff --git a/resources/html/configure.html b/resources/html/configure.html index 640a867..5ceb230 100644 --- a/resources/html/configure.html +++ b/resources/html/configure.html @@ -100,6 +100,11 @@ "label": "AllDebrid", "type": "Premium" }, + { + "value": "easydebrid", + "label": "EasyDebrid", + "type": "Premium" + }, { "value": "qbittorrent", "label": "qBittorrent - WebDav", diff --git a/resources/html/home.html b/resources/html/home.html index 902ccd5..6af7f68 100644 --- a/resources/html/home.html +++ b/resources/html/home.html @@ -46,6 +46,7 @@
  • 🔗 Debrid-Link (Premium)
  • ✨ Premiumize (Premium)
  • 🏠 AllDebrid (Premium)
  • +
  • 📦 EasyDebrid (Premium)
  • 🔒 qBittorrent - WebDav (Free/Premium)
  • diff --git a/resources/js/config_script.js b/resources/js/config_script.js index 4d52f7e..bf4a3b6 100644 --- a/resources/js/config_script.js +++ b/resources/js/config_script.js @@ -12,6 +12,7 @@ const providerSignupLinks = { debridlink: 'https://debrid-link.com/id/kHgZs', alldebrid: 'https://alldebrid.com/?uid=3ndha&lang=en', torbox: ['https://torbox.app/subscription?referral=339b923e-fb23-40e7-8031-4af39c212e3c', 'https://torbox.app/subscription?referral=e2a28977-99ed-43cd-ba2c-e90dc398c49c', 'https://torbox.app/subscription?referral=38f1c266-8a6c-40b2-a6d2-2148e77dafc9'], + easydebrid: 'https://paradise-cloud.com/products/easydebrid', premiumize: 'https://www.premiumize.me', qbittorrent: 'https://github.com/mhdzumair/MediaFusion/tree/main/streaming_providers/qbittorrent#qbittorrent-webdav-setup-options-with-mediafusion', stremthru: 'https://github.com/MunifTanjim/stremthru?tab=readme-ov-file#configuration', diff --git a/streaming_providers/easydebrid/__init__.py b/streaming_providers/easydebrid/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/streaming_providers/easydebrid/client.py b/streaming_providers/easydebrid/client.py new file mode 100644 index 0000000..29dc6e0 --- /dev/null +++ b/streaming_providers/easydebrid/client.py @@ -0,0 +1,71 @@ +from typing import Optional + +from streaming_providers.debrid_client import DebridClient +from streaming_providers.exceptions import ProviderException + + +class EasyDebrid(DebridClient): + BASE_URL = "https://easydebrid.com/api/v1" + + def __init__(self, token: Optional[str] = None, user_ip: Optional[str] = None): + self.user_ip = user_ip + super().__init__(token) + + async def initialize_headers(self): + self.headers = {"Authorization": f"Bearer {self.token}"} + if self.user_ip: + self.headers['X-Forwarded-For'] = self.user_ip + + async def disable_access_token(self): + pass + + async def __aexit__(self, exc_type, exc_val, exc_tb): + await super().__aexit__(exc_type, exc_val, exc_tb) + + async def _handle_service_specific_errors(self, error_data: dict, status_code: int): + error_code = error_data.get("error") + match error_code: + case "Unsupported link for direct download.": + raise ProviderException( + "Unsupported link for direct download.", + "unsupported_direct_download.mp4", + ) + + async def _make_request( + self, + method: str, + url: str, + data: Optional[dict | str] = None, + json: Optional[dict] = None, + params: Optional[dict] = None, + is_return_none: bool = False, + is_expected_to_fail: bool = False, + retry_count: int = 0, + ) -> dict: + params = params or {} + url = self.BASE_URL + url + return await super()._make_request( + method, url, data, json, params, is_return_none, is_expected_to_fail + ) + + async def get_torrent_instant_availability(self, urls: list[str]): + response = await self._make_request( + "POST", + "/link/lookup", + json={"urls": urls}, + ) + return response.get("cached", []) + + async def create_download_link(self, magnet): + response = await self._make_request( + "POST", + "/link/generate", + json={"url": magnet}, + ) + return response + + async def get_torrent_info(self, torrent_id: str) -> dict: + pass + + async def get_user_info(self): + return await self._make_request("GET", "/user/details") diff --git a/streaming_providers/easydebrid/utils.py b/streaming_providers/easydebrid/utils.py new file mode 100644 index 0000000..7dbc761 --- /dev/null +++ b/streaming_providers/easydebrid/utils.py @@ -0,0 +1,89 @@ +import asyncio +import logging +from typing import Any, Dict, List, Optional, Iterator + +from db.models import TorrentStreams +from db.schemas import UserData +from streaming_providers.exceptions import ProviderException +from streaming_providers.parser import select_file_index_from_torrent +from streaming_providers.easydebrid.client import EasyDebrid + + +async def get_video_url_from_easydebrid( + magnet_link: str, + user_data: UserData, + filename: str, + user_ip: str, + episode: Optional[int] = None, + **kwargs: Any, +) -> str: + async with EasyDebrid( + token=user_data.streaming_provider.token, + user_ip=user_ip, + ) as easydebrid_client: + torrent_info = await easydebrid_client.create_download_link(magnet_link) + file_index = await select_file_index_from_torrent( + torrent_info, + filename, + episode, + name_key="filename", + ) + return torrent_info["files"][file_index]["url"] + + +def divide_chunks(lst: List[Any], n: int) -> Iterator[List[Any]]: + """Yield successive n-sized chunks from lst.""" + for i in range(0, len(lst), n): + yield lst[i : i + n] + + +async def update_chunk_cache_status( + easydebrid_client: EasyDebrid, streams_chunk: List[TorrentStreams] +) -> None: + """Update cache status for a chunk of streams.""" + try: + instant_availability_data = ( + await easydebrid_client.get_torrent_instant_availability( + [f"magnet:?xt=urn:btih:{stream.id}" for stream in streams_chunk] + ) + ) + for stream, instant_availability in zip( + streams_chunk, instant_availability_data + ): + stream.cached = instant_availability + except ProviderException as e: + logging.error(f"Failed to get cached status from easydebrid for a chunk: {e}") + + +async def update_easydebrid_cache_status( + streams: List[TorrentStreams], user_data: UserData, user_ip: str, **kwargs: Any +) -> None: + """Updates the cache status of streams based on Easydebrid's instant availability.""" + async with EasyDebrid( + token=user_data.streaming_provider.token, + user_ip=user_ip, + ) as easydebrid_client: + chunks = list(divide_chunks(streams, 50)) + update_tasks = [ + update_chunk_cache_status(easydebrid_client, chunk) for chunk in chunks + ] + await asyncio.gather(*update_tasks) + + +async def validate_easydebrid_credentials( + user_data: UserData, user_ip: str, **kwargs: Any +) -> Dict[str, str]: + """Validates the EasyDebrid credentials.""" + try: + async with EasyDebrid( + token=user_data.streaming_provider.token, + user_ip=user_ip, + ) as easydebrid_client: + await easydebrid_client.get_user_info() + return {"status": "success"} + + except ProviderException as error: + return { + "status": "error", + "message": f"Failed to validate EasyDebrid credentials: {error.message}", + } diff --git a/streaming_providers/mapper.py b/streaming_providers/mapper.py index 9c5820c..8dbf8f9 100644 --- a/streaming_providers/mapper.py +++ b/streaming_providers/mapper.py @@ -68,6 +68,11 @@ from streaming_providers.stremthru.utils import ( get_video_url_from_stremthru, validate_stremthru_credentials, ) +from streaming_providers.easydebrid.utils import ( + update_easydebrid_cache_status, + get_video_url_from_easydebrid, + validate_easydebrid_credentials, +) # Define provider-specific cache update functions CACHE_UPDATE_FUNCTIONS = { @@ -81,6 +86,7 @@ CACHE_UPDATE_FUNCTIONS = { "premiumize": update_pm_cache_status, "qbittorrent": update_qbittorrent_cache_status, "stremthru": update_st_cache_status, + "easydebrid": update_easydebrid_cache_status, } # Define provider-specific downloaded info hashes fetch functions @@ -123,6 +129,7 @@ GET_VIDEO_URL_FUNCTIONS = { "seedr": get_video_url_from_seedr, "torbox": get_video_url_from_torbox, "stremthru": get_video_url_from_stremthru, + "easydebrid": get_video_url_from_easydebrid, } @@ -137,4 +144,5 @@ VALIDATE_CREDENTIALS_FUNCTIONS = { "seedr": validate_seedr_credentials, "torbox": validate_torbox_credentials, "stremthru": validate_stremthru_credentials, + "easydebrid": validate_easydebrid_credentials, } diff --git a/utils/const.py b/utils/const.py index e379bb7..815e811 100644 --- a/utils/const.py +++ b/utils/const.py @@ -183,6 +183,7 @@ STREAMING_PROVIDERS_SHORT_NAMES = { "seedr": "SDR", "torbox": "TRB", "stremthru": "ST", + "easydebrid": "ED", } CERTIFICATION_MAPPING = {