Skip to content

Commit

Permalink
fix: refactor and re-enable alldebrid
Browse files Browse the repository at this point in the history
The recent realdebrid download refactor broke alldebrid.
This change updates alldebrid to match the new realdebrid patterns.
  • Loading branch information
the-eversio authored and dreulavelle committed Sep 29, 2024
1 parent 0889a03 commit 61bc680
Show file tree
Hide file tree
Showing 12 changed files with 510 additions and 624 deletions.
4 changes: 2 additions & 2 deletions src/program/downloaders/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def __init__(self):
self.speed_mode = settings_manager.settings.downloaders.prefer_speed_over_quality
self.service = next((service for service in [
RealDebridDownloader(),
#AllDebridDownloader(),
AllDebridDownloader(),
#TorBoxDownloader()
] if service.initialized), None)

Expand Down Expand Up @@ -133,4 +133,4 @@ def update_item_attributes(item: MediaItem, names: tuple[str, str]):
episode.file = file["filename"]
episode.folder = item.folder
episode.alternative_folder = item.alternative_folder
episode.active_stream = {**item.active_stream, "files": [ episode.file ] }
episode.active_stream = {**item.active_stream, "files": [ episode.file ] }
776 changes: 175 additions & 601 deletions src/program/downloaders/alldebrid.py

Large diffs are not rendered by default.

28 changes: 8 additions & 20 deletions src/program/downloaders/realdebrid.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
from datetime import datetime

from loguru import logger
from requests import ConnectTimeout

import utils.request as request
from program.media.item import MediaItem
from program.settings.manager import settings_manager as settings
from requests import ConnectTimeout
from utils import request
from utils.ratelimiter import RateLimiter

from .shared import VIDEO_EXTENSIONS, FileFinder
from .shared import VIDEO_EXTENSIONS, FileFinder, premium_days_left

BASE_URL = "https://api.real-debrid.com/rest/1.0"

Expand Down Expand Up @@ -39,17 +37,7 @@ def validate(self) -> bool:
if user_info:
expiration = user_info.get("expiration", "")
expiration_datetime = datetime.fromisoformat(expiration.replace("Z", "+00:00")).replace(tzinfo=None)
time_left = expiration_datetime - datetime.utcnow().replace(tzinfo=None)
days_left = time_left.days
hours_left, minutes_left = divmod(time_left.seconds // 3600, 60)
expiration_message = ""

if days_left > 0:
expiration_message = f"Your account expires in {days_left} days."
elif hours_left > 0:
expiration_message = f"Your account expires in {hours_left} hours and {minutes_left} minutes."
else:
expiration_message = "Your account expires soon."
expiration_message = premium_days_left(expiration_datetime)

if user_info.get("type", "") != "premium":
logger.error("You are not a premium member.")
Expand Down Expand Up @@ -97,7 +85,7 @@ def get_cached_containers(self, infohashes: list[str], needed_media: dict, break
def all_files_valid(file_dict: dict) -> bool:
return all(
any(
file["filename"].endswith(f'.{ext}') and "sample" not in file["filename"].lower()
file["filename"].endswith(f".{ext}") and "sample" not in file["filename"].lower()
for ext in VIDEO_EXTENSIONS
)
for file in file_dict.values()
Expand Down Expand Up @@ -158,7 +146,7 @@ def delete(url):

def add_torrent(infohash: str) -> int:
try:
id = post(f"torrents/addMagnet", data={"magnet": f"magnet:?xt=urn:btih:{infohash}"})["id"]
id = post("torrents/addMagnet", data={"magnet": f"magnet:?xt=urn:btih:{infohash}"})["id"]
except:
logger.warning(f"Failed to add torrent with infohash {infohash}")
id = None
Expand All @@ -174,7 +162,7 @@ def add_torrent_magnet(magnet: str) -> str:

def select_files(id: str, files: list[str]):
try:
post(f"torrents/selectFiles/{id}", data={"files": ','.join(files)})
post(f"torrents/selectFiles/{id}", data={"files": ",".join(files)})
except:
logger.warning(f"Failed to select files for torrent with id {id}")

Expand Down Expand Up @@ -208,4 +196,4 @@ def delete_torrent(id):
try:
delete(f"torrents/delete/{id}")
except:
logger.warning(f"Failed to delete torrent with id {id}")
logger.warning(f"Failed to delete torrent with id {id}")
19 changes: 18 additions & 1 deletion src/program/downloaders/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from program.media.state import States
from program.settings.manager import settings_manager

from datetime import datetime

DEFAULT_VIDEO_EXTENSIONS = ["mp4", "mkv", "avi"]
ALLOWED_VIDEO_EXTENSIONS = ["mp4", "mkv", "avi", "mov", "wmv", "flv", "m4v", "webm", "mpg", "mpeg", "m2ts", "ts"]

Expand Down Expand Up @@ -82,4 +84,19 @@ def get_needed_media(item: MediaItem) -> dict:
needed_media = {item.number: [episode.number for episode in item.episodes if episode.state in acceptable_states]}
elif item.type == "episode":
needed_media = {item.parent.number: [item.number]}
return needed_media
return needed_media

def premium_days_left(expiration: datetime) -> str:
"""Convert an expiration date into a message showing days remaining on the user's premium account"""
time_left = expiration - datetime.utcnow()
days_left = time_left.days
hours_left, minutes_left = divmod(time_left.seconds // 3600, 60)
expiration_message = ""

if days_left > 0:
expiration_message = f"Your account expires in {days_left} days."
elif hours_left > 0:
expiration_message = f"Your account expires in {hours_left} hours and {minutes_left} minutes."
else:
expiration_message = "Your account expires soon."
return expiration_message
176 changes: 176 additions & 0 deletions src/tests/test_alldebrid_downloader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import json

import pytest
from program.downloaders import alldebrid
from program.downloaders.alldebrid import (
AllDebridDownloader,
add_torrent,
get_instant_availability,
get_status,
get_torrents,
)
from program.settings.manager import settings_manager as settings


@pytest.fixture
def downloader(instant, upload, status, status_all, delete):
"""Instance of AllDebridDownloader with API calls mocked"""
# mock API calls
_get = alldebrid.get
def get(url, **params):
match url:
case "user":
return {"data": { "user": { "isPremium": True, "premiumUntil": 1735514599, } } }
case "magnet/instant":
return instant(url, **params)
case "magnet/upload":
return upload(url, **params)
case "magnet/delete":
return delete(url, **params)
case "magnet/status":
if params.get("id", False):
return status(url, **params)
else:
return status_all(url, **params)
case _:
raise Exception("unmatched api call")
alldebrid.get = get

alldebrid_settings = settings.settings.downloaders.all_debrid
alldebrid_settings.enabled = True
alldebrid_settings.api_key = "key"

downloader = AllDebridDownloader()
assert downloader.initialized
yield downloader

# tear down mock
alldebrid.get = get


## Downloader tests
def test_process_hashes(downloader):
hashes = downloader.process_hashes(["abc"], None, [False, True])
assert len(hashes) == 1


def test_download_cached(downloader):
torrent_id = downloader.download_cached({"infohash": "abc"})
assert torrent_id == MAGNET_ID


def test_get_torrent_names(downloader):
names = downloader.get_torrent_names(123)
assert names == ("Ubuntu 24.04", "Ubuntu 24.04")


## API parsing tests
def test_get_instant_availability(instant):
alldebrid.get = instant
infohashes = [UBUNTU]
availability = get_instant_availability(infohashes)
assert len(availability[0].get("files", [])) == 2


def test_get_instant_availability_unavailable(instant_unavailable):
alldebrid.get = instant_unavailable
infohashes = [UBUNTU]
availability = get_instant_availability(infohashes)
assert availability[0]["hash"] == UBUNTU


def test_add_torrent(upload):
alldebrid.get = upload
torrent_id = add_torrent(UBUNTU)
assert torrent_id == 251993753


def test_add_torrent_cached(upload_ready):
alldebrid.get = upload_ready
torrent_id = add_torrent(UBUNTU)
assert torrent_id == 251993753


def test_get_status(status):
alldebrid.get = status
torrent_status = get_status(251993753)
assert torrent_status["filename"] == "Ubuntu 24.04"


def test_get_status_unfinished(status_downloading):
alldebrid.get = status_downloading
torrent_status = get_status(251993753)
assert torrent_status["status"] == "Downloading"


def test_get_torrents(status_all):
alldebrid.get = status_all
torrents = get_torrents()
assert torrents[0]["status"] == "Ready"


def test_delete(delete):
alldebrid.get = delete
delete(123)


# Example requests - taken from real API calls
UBUNTU = "3648baf850d5930510c1f172b534200ebb5496e6"
MAGNET_ID = 251993753
@pytest.fixture
def instant():
"""GET /magnet/instant?magnets[0]=infohash (torrent available)"""
with open("src/tests/test_data/alldebrid_magnet_instant.json") as f:
body = json.load(f)
return lambda url, **params: body

@pytest.fixture
def instant_unavailable():
"""GET /magnet/instant?magnets[0]=infohash (torrent unavailable)"""
with open("src/tests/test_data/alldebrid_magnet_instant_unavailable.json") as f:
body = json.load(f)
return lambda url, **params: body

@pytest.fixture
def upload():
"""GET /magnet/upload?magnets[]=infohash (torrent not ready yet)"""
with open("src/tests/test_data/alldebrid_magnet_upload_not_ready.json") as f:
body = json.load(f)
return lambda url, **params: body

@pytest.fixture
def upload_ready():
"""GET /magnet/upload?magnets[]=infohash (torrent ready)"""
with open("src/tests/test_data/alldebrid_magnet_upload_ready.json") as f:
body = json.load(f)
return lambda url, **params: body

@pytest.fixture
def status():
"""GET /magnet/status?id=123 (debrid links ready)"""
with open("src/tests/test_data/alldebrid_magnet_status_one_ready.json") as f:
body = json.load(f)
return lambda url, **params: body

@pytest.fixture
def status_downloading():
"""GET /magnet/status?id=123 (debrid links not ready yet)"""
with open("src/tests/test_data/alldebrid_magnet_status_one_downloading.json") as f:
body = json.load(f)
return lambda url, **params: body

@pytest.fixture
def status_all():
"""GET /magnet/status (gets a list of all links instead of a single object)"""
# The body is the same as a single item, but with all your magnets in a list.
with open("src/tests/test_data/alldebrid_magnet_status_one_ready.json") as f:
body = json.load(f)
return lambda url, **params: {"status": "success", "data": {"magnets": [body["data"]["magnets"]]}}

@pytest.fixture
def delete():
"""GET /delete"""
with open("src/tests/test_data/alldebrid_magnet_delete.json") as f:
body = json.load(f)
return lambda url, **params: body

6 changes: 6 additions & 0 deletions src/tests/test_data/alldebrid_magnet_delete.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"status": "success",
"data": {
"message": "Magnet was successfully deleted"
}
}
16 changes: 16 additions & 0 deletions src/tests/test_data/alldebrid_magnet_instant.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"status": "success",
"data": {
"magnets": [
{
"magnet": "3648baf850d5930510c1f172b534200ebb5496e6",
"hash": "3648baf850d5930510c1f172b534200ebb5496e6",
"instant": true,
"files": [
{"n": "ubuntu-24.04-desktop-amd64.iso", "s": 6114656256},
{"n": "ubuntu-24.04-live-server-amd64.iso", "s": 2754981888}
]
}
]
}
}
12 changes: 12 additions & 0 deletions src/tests/test_data/alldebrid_magnet_instant_unavailable.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"status": "success",
"data": {
"magnets": [
{
"magnet": "3648baf850d5930510c1f172b534200ebb5496e6",
"hash": "3648baf850d5930510c1f172b534200ebb5496e6",
"instant": false
}
]
}
}
25 changes: 25 additions & 0 deletions src/tests/test_data/alldebrid_magnet_status_one_downloading.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"status": "success",
"data": {
"magnets": {
"id": 251993753,
"filename": "Ubuntu 24.04",
"size": 8869638144,
"hash": "3648baf850d5930510c1f172b534200ebb5496e6",
"status": "Downloading",
"statusCode": 1,
"downloaded": 165063971,
"uploaded": 0,
"seeders": 6,
"downloadSpeed": 4782727,
"processingPerc": 0,
"uploadSpeed": 0,
"uploadDate": 1727454272,
"completionDate": 0,
"links": [],
"type": "m",
"notified": false,
"version": 2
}
}
}
40 changes: 40 additions & 0 deletions src/tests/test_data/alldebrid_magnet_status_one_ready.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"status": "success",
"data": {
"magnets": {
"id": 251993753,
"filename": "Ubuntu 24.04",
"size": 8869638144,
"hash": "3648baf850d5930510c1f172b534200ebb5496e6",
"status": "Ready",
"statusCode": 4,
"downloaded": 8869638144,
"uploaded": 0,
"seeders": 0,
"downloadSpeed": 0,
"processingPerc": 0,
"uploadSpeed": 0,
"uploadDate": 1727454272,
"completionDate": 1727454803,
"links": [
{
"filename": "ubuntu-24.04-desktop-amd64.iso",
"size": 6114656256,
"files": [{"n": "ubuntu-24.04-desktop-amd64.iso", "s": 6114656256}],
"link": "https://alldebrid.com/f/REDACTED"
},
{
"filename": "ubuntu-24.04-live-server-amd64.iso",
"size": 2754981888,
"files": [
{"n": "ubuntu-24.04-live-server-amd64.iso", "s": 2754981888}
],
"link": "https://alldebrid.com/f/REDACTED"
}
],
"type": "m",
"notified": false,
"version": 2
}
}
}
Loading

0 comments on commit 61bc680

Please sign in to comment.