stream-fusion-old/stream_fusion/utils/debrid/realdebrid.py
LimeDrive 6fe4fe55f3 init
2024-07-09 04:13:29 +02:00

285 lines
12 KiB
Python

import re
import json
import time
from urllib.parse import unquote
import requests
from stream_fusion.constants import NO_CACHE_VIDEO_URL
from stream_fusion.utils.debrid.base_debrid import BaseDebrid
from stream_fusion.utils.general import get_info_hash_from_magnet
from stream_fusion.utils.general import is_video_file
from stream_fusion.utils.general import season_episode_in_filename
from stream_fusion.logging_config import logger
class RealDebrid(BaseDebrid):
def __init__(self, config):
super().__init__(config)
self.base_url = "https://api.real-debrid.com"
self.headers = {"Authorization": f"Bearer {self.config['debridKey']}"}
def add_magnet(self, magnet, ip=None):
url = f"{self.base_url}/rest/1.0/torrents/addMagnet"
data = {"magnet": magnet}
logger.info(f"Adding magnet to RD: {magnet}")
return self.get_json_response(url, method='post', headers=self.headers, data=data)
def add_torrent(self, torrent_file):
url = f"{self.base_url}/rest/1.0/torrents/addTorrent"
return self.get_json_response(url, method='put', headers=self.headers, data=torrent_file)
def delete_torrent(self, id):
url = f"{self.base_url}/rest/1.0/torrents/delete/{id}"
return self.get_json_response(url, method='delete', headers=self.headers)
def get_torrent_info(self, torrent_id):
logger.info(f"Getting torrent info for: {torrent_id}")
url = f"{self.base_url}/rest/1.0/torrents/info/{torrent_id}"
torrent_info = self.get_json_response(url, headers=self.headers)
if not torrent_info or 'files' not in torrent_info:
return None
return torrent_info
def select_files(self, torrent_id, file_id):
logger.info(f"Selecting file(s): {file_id}")
url = f"{self.base_url}/rest/1.0/torrents/selectFiles/{torrent_id}"
data = {"files": str(file_id)}
requests.post(url, headers=self.headers, data=data)
def unrestrict_link(self, link):
url = f"{self.base_url}/rest/1.0/unrestrict/link"
data = {"link": link}
return self.get_json_response(url, method='post', headers=self.headers, data=data)
def is_already_added(self, magnet):
hash = magnet.split("urn:btih:")[1].split("&")[0].lower()
url = f"{self.base_url}/rest/1.0/torrents"
torrents = self.get_json_response(url, headers=self.headers)
for torrent in torrents:
if torrent['hash'].lower() == hash:
return torrent['id']
return False
def wait_for_link(self, torrent_id, timeout=30, interval=5):
start_time = time.time()
while time.time() - start_time < timeout:
torrent_info = self.get_torrent_info(torrent_id)
if torrent_info and 'links' in torrent_info and len(torrent_info['links']) > 0:
return torrent_info['links']
time.sleep(interval)
return None
def get_availability_bulk(self, hashes_or_magnets, ip=None):
if len(hashes_or_magnets) == 0:
logger.info("No hashes to be sent to Real-Debrid.")
return dict()
url = f"{self.base_url}/rest/1.0/torrents/instantAvailability/{'/'.join(hashes_or_magnets)}"
return self.get_json_response(url, headers=self.headers)
def get_stream_link(self, query_string, config, ip=None):
query = json.loads(query_string)
magnet = query['magnet']
stream_type = query['type']
file_index = int(query['file_index']) if query['file_index'] is not None else None
season = query['season']
episode = query['episode']
torrent_download = unquote(query["torrent_download"]) if query["torrent_download"] is not None else None
info_hash = get_info_hash_from_magnet(magnet)
logger.info(f"RealDebrid get stream link for {stream_type} with hash: {info_hash}")
cached_torrent_ids = self.__get_cached_torrent_ids(info_hash)
logger.info(f"Found {len(cached_torrent_ids)} cached torrents with hash: {info_hash}")
torrent_info = None
if len(cached_torrent_ids) > 0:
if stream_type == "movie":
torrent_info = self.get_torrent_info(cached_torrent_ids[0])
elif stream_type == "series":
torrent_info = self.__get_cached_torrent_info(cached_torrent_ids, file_index, season, episode)
else:
return "Error: Unsupported stream type."
# The torrent is not yet added
if torrent_info is None:
torrent_info = self.__add_magnet_or_torrent(magnet, torrent_download, anon_magnet=config["anonymizeMagnets"])
if not torrent_info or 'files' not in torrent_info:
return "Error: Failed to get torrent info."
logger.info("Selecting file")
self.__select_file(torrent_info, stream_type, file_index, season, episode)
# == operator, to avoid adding the season pack twice and setting 5 as season pack treshold
if len(cached_torrent_ids) == 0 and stream_type == "series" and len(torrent_info["files"]) > 5:
logger.info("Prefetching season pack")
prefetched_torrent_info = self.__prefetch_season_pack(magnet, torrent_download)
if len(prefetched_torrent_info["links"]) > 0:
self.delete_torrent(torrent_info["id"])
torrent_info = prefetched_torrent_info
torrent_id = torrent_info["id"]
logger.info(f"Waiting for the link(s) to be ready for torrent ID: {torrent_id}")
# Waiting for the link(s) to be ready
links = self.wait_for_link(torrent_id)
if links is None:
return NO_CACHE_VIDEO_URL
if len(links) > 1:
logger.info("Finding appropiate link")
download_link = self.__find_appropiate_link(torrent_info, links, file_index, season, episode)
else:
download_link = links[0]
logger.info(f"Unrestricting the download link: {download_link}")
# Unrestricting the download link
unrestrict_response = self.unrestrict_link(download_link)
if not unrestrict_response or 'download' not in unrestrict_response:
return "Error: Failed to unrestrict link."
logger.info(f"Got download link: {unrestrict_response['download']}")
return unrestrict_response['download']
def __get_cached_torrent_ids(self, info_hash):
url = f"{self.base_url}/rest/1.0/torrents"
torrents = self.get_json_response(url, headers=self.headers)
logger.info(f"Searching users real-debrid downloads for {info_hash}")
torrent_ids = []
for torrent in torrents:
if torrent['hash'].lower() == info_hash:
torrent_ids.append(torrent['id'])
return torrent_ids
def __get_cached_torrent_info(self, cached_ids, file_index, season, episode):
cached_torrents = []
for cached_torrent_id in cached_ids:
cached_torrent_info = self.get_torrent_info(cached_torrent_id)
if self.__torrent_contains_file(cached_torrent_info, file_index, season, episode):
if len(cached_torrent_info["links"]) > 0: # If the links are ready
return cached_torrent_info
cached_torrents.append(cached_torrent_info)
if len(cached_torrents) == 0:
return None
return max(cached_torrents, key=lambda x: x['progress'])
def __torrent_contains_file(self, torrent_info, file_index, season, episode):
if not torrent_info or "files" not in torrent_info:
return False
if file_index is None:
for file in torrent_info["files"]:
if file["selected"] and season_episode_in_filename(file['path'], season, episode):
return True
else:
for file in torrent_info["files"]:
if file['id'] == file_index:
return file["selected"] == 1
return False
def __add_magnet_or_torrent(self, magnet, torrent_download=None, anon_magnet=False):
torrent_id = ""
if torrent_download is None:
logger.info(f"Adding magnet to RealDebrid")
if anon_magnet:
magnet = re.sub(r'&tr=[^&]*', '', magnet)
magnet_response = self.add_magnet(magnet)
logger.info(f"RealDebrid add magnet response: {magnet_response}")
if not magnet_response or 'id' not in magnet_response:
return "Error: Failed to add magnet."
torrent_id = magnet_response['id']
elif anon_magnet and torrent_download:
logger.info(f"Adding magnet to RealDebrid")
clean_magnet = re.sub(r'&tr=[^&]*', '', magnet)
magnet_response = self.add_magnet(clean_magnet)
logger.info(f"RealDebrid add magnet response: {magnet_response}")
if not magnet_response or 'id' not in magnet_response:
return "Error: Failed to add magnet."
torrent_id = magnet_response['id']
else:
logger.info(f"Downloading torrent file from Jackett")
torrent_file = self.donwload_torrent_file(torrent_download)
logger.info(f"Torrent file downloaded from Jackett")
logger.info(f"Adding torrent file to RealDebrid")
upload_response = self.add_torrent(torrent_file)
logger.info(f"RealDebrid add torrent file response: {upload_response}")
if not upload_response or 'id' not in upload_response:
return "Error: Failed to add torrent file."
torrent_id = upload_response['id']
logger.info(f"New torrent ID: {torrent_id}")
return self.get_torrent_info(torrent_id)
def __prefetch_season_pack(self, magnet, torrent_download):
torrent_info = self.__add_magnet_or_torrent(magnet, torrent_download)
video_file_indexes = []
for file in torrent_info["files"]:
if is_video_file(file["path"]):
video_file_indexes.append(str(file["id"]))
self.select_files(torrent_info["id"], ",".join(video_file_indexes))
time.sleep(10)
return self.get_torrent_info(torrent_info["id"])
def __select_file(self, torrent_info, stream_type, file_index, season, episode):
torrent_id = torrent_info["id"]
if file_index is not None:
logger.info(f"Selecting file_index: {file_index}")
self.select_files(torrent_id, file_index)
return
files = torrent_info["files"]
if stream_type == "movie":
largest_file_id = max(files, key=lambda x: x['bytes'])['id']
logger.info(f"Selecting file_index: {largest_file_id}")
self.select_files(torrent_id, largest_file_id)
elif stream_type == "series":
matching_files = []
for file in files:
if season_episode_in_filename(file["path"], season, episode):
matching_files.append(file)
largest_file_id = max(matching_files, key=lambda x: x['bytes'])['id']
logger.info(f"Selecting file_index: {largest_file_id}")
self.select_files(torrent_id, largest_file_id)
def __find_appropiate_link(self, torrent_info, links, file_index, season, episode):
selected_files = list(filter(lambda file: file["selected"] == 1, torrent_info["files"]))
index = 0
if file_index is not None:
for file in selected_files:
if file["id"] == file_index:
break
index += 1
else:
matching_indexes = []
for file in selected_files:
if season_episode_in_filename(file["path"], season, episode):
matching_indexes.append({"index": index, "file": file})
index += 1
index = max(matching_indexes, lambda x: x["file"]["bytes"])["index"]
if len(links) - 1 < index:
logger.debug(f"From selected files {selected_files}, index: {index} is out of range for {links}.")
return NO_CACHE_VIDEO_URL
return links[index]