stream-fusion-better/stream_fusion/utils/debrid/premiusize.py.save
2025-05-04 09:31:01 +02:00

400 lines
18 KiB
Text

import json
from stream_fusion.settings import settings
from stream_fusion.utils.debrid.base_debrid import BaseDebrid
from stream_fusion.utils.general import get_info_hash_from_magnet, season_episode_in_filename
from stream_fusion.logging_config import logger
import time
import asyncio
class Premiumize(BaseDebrid):
def __init__(self, config, session=None):
super().__init__(config, session=session)
self.base_url = "https://www.premiumize.me/api"
self.api_key = config.get('PMToken') or settings.pm_token
if not self.api_key:
logger.error("No Premiumize API key found in config or settings")
raise ValueError("Premiumize API key is required")
# Note: La vérification du token est maintenant asynchrone et ne peut pas être appelée dans __init__
# Elle sera appelée à la première utilisation de l'API
async def _check_token(self):
"""Vérifier la validité du token en appelant l'API account/info"""
url = f"{self.base_url}/account/info"
response = await self.json_response(
url,
method='post',
data={'apikey': self.api_key}
)
if not response or response.get("status") != "success":
logger.error(f"Invalid Premiumize API key: {self.api_key}")
raise ValueError("Invalid Premiumize API key")
logger.info("Premiumize API key is valid")
async def add_magnet(self, magnet, ip=None):
url = f"{self.base_url}/transfer/create?apikey={self.api_key}"
# Vérifier si c'est un pack de saison
info_hash = get_info_hash_from_magnet(magnet)
is_season_pack = self._check_if_season_pack(magnet)
# Créer le dictionnaire de formulaire
form = {'src': magnet}
# Ajouter folder_name seulement si c'est un pack de saison
if is_season_pack:
form['folder_name'] = f"season_pack_{info_hash}"
# Ajouter des logs pour le débogage
logger.debug(f"Premiumize: Sending add_magnet request with data: {form}")
response = await self.json_response(url, method='post', data=form)
if is_season_pack and response and response.get("status") == "success":
# Si c'est un pack de saison, on attend que tous les fichiers soient disponibles
transfer_id = response.get("id")
if transfer_id:
# Attendre que le transfert soit terminé
if await self._wait_for_season_pack(transfer_id):
# Une fois terminé, récupérer les détails du dossier
folder_details = await self.get_folder_or_file_details(transfer_id)
if folder_details and folder_details.get("content"):
# Trier les fichiers par taille pour prendre le plus gros fichier vidéo
video_files = [f for f in folder_details["content"]
if f.get("mime_type", "").startswith("video/")]
if video_files:
largest_file = max(video_files, key=lambda x: x.get("size", 0))
response["selected_file"] = {
"id": largest_file.get("id"),
"name": largest_file.get("name"),
"size": largest_file.get("size"),
"link": largest_file.get("link"),
"stream_link": largest_file.get("stream_link")
}
return response
def _check_if_season_pack(self, magnet):
"""Vérifie si le magnet link correspond à un pack de saison"""
# Vérifie les patterns communs dans le nom du torrent
name = magnet.lower()
season_indicators = [
"complete.season",
"season.complete",
"s01.complete",
"saison.complete",
"season.pack",
"pack.saison",
".s01.",
".s02.",
".s03.",
".s04.",
".s05.",
"saison"
]
return any(indicator in name for indicator in season_indicators)
async def _wait_for_season_pack(self, transfer_id, timeout=300):
"""Attend que tous les fichiers d'un pack de saison soient disponibles"""
start_time = time.time()
while time.time() - start_time < timeout:
transfer_info = await self.get_folder_or_file_details(transfer_id)
if transfer_info and transfer_info.get("status") == "success":
# Vérifier si des fichiers vidéo sont présents
if transfer_info.get("content"):
video_files = [f for f in transfer_info["content"]
if f.get("mime_type", "").startswith("video/")]
if video_files:
return True
await asyncio.sleep(5) # Utiliser asyncio.sleep au lieu de time.sleep
return False
async def add_torrent(self, torrent_file):
url = f"{self.base_url}/transfer/create?apikey={self.api_key}"
form = {'file': torrent_file}
return await self.json_response(url, method='post', data=form)
async def list_transfers(self):
url = f"{self.base_url}/transfer/list?apikey={self.api_key}"
return await self.json_response(url)
async def get_folder_or_file_details(self, item_id, is_folder=True):
if is_folder:
logger.info(f"Getting folder details with id: {item_id}")
url = f"{self.base_url}/folder/list?id={item_id}&apikey={self.api_key}"
else:
logger.info(f"Getting file details with id: {item_id}")
url = f"{self.base_url}/item/details?id={item_id}&apikey={self.api_key}"
return await self.json_response(url)
async def get_availability(self, hash):
"""Get availability for a single hash"""
url = f"{self.base_url}/cache/check?apikey={self.api_key}&items[]={hash}"
response = await self.json_response(url)
if not response or response.get("status") != "success":
logger.error("Invalid response from Premiumize API")
return {"transcoded": [False]}
return {
"transcoded": response.get("transcoded", [False])
}
async def get_availability_bulk(self, hashes_or_magnets, ip=None):
"""Get availability for multiple hashes or magnets"""
if not hashes_or_magnets:
return {}
logger.info(f"Checking availability for {len(hashes_or_magnets)} items")
logger.debug(f"Using Premiumize API key: {self.api_key}")
# Construire l'URL avec les paramètres
params = []
for hash in hashes_or_magnets:
params.append(f"items[]={hash}")
url = f"{self.base_url}/cache/check"
response = await self.json_response(
url,
method='post',
data={
'apikey': self.api_key,
'items[]': hashes_or_magnets
}
)
logger.info(f"Raw Premiumize response: {response}")
if not response or response.get("status") != "success":
logger.error("Invalid response from Premiumize API")
return {}
# Format response to match expected structure
result = {}
for i, hash_or_magnet in enumerate(hashes_or_magnets):
# Vérifier si le fichier est disponible en utilisant le champ response
is_available = bool(response.get("response", [])[i]) if isinstance(response.get("response", []), list) and i < len(response["response"]) else False
# Récupérer le nom du fichier s'il est disponible
filename = None
if isinstance(response.get("filename", []), list) and i < len(response["filename"]):
filename = response["filename"][i]
# Récupérer la taille du fichier et la convertir en entier
filesize = 0
if isinstance(response.get("filesize", []), list) and i < len(response["filesize"]):
try:
filesize = int(response["filesize"][i]) if response["filesize"][i] is not None else 0
except (ValueError, TypeError):
filesize = 0
result[hash_or_magnet] = {
"transcoded": is_available,
"filename": filename,
"filesize": filesize
}
logger.info(f"Formatted response: {result}")
logger.info(f"Got availability for {len(result)} items")
return result
async def start_background_caching(self, magnet, query=None):
"""Start caching a magnet link in the background."""
# Afficher le début du magnet dans les logs
if isinstance(magnet, str):
magnet_preview = magnet[:50] + "..." if len(magnet) > 50 else magnet
logger.info(f"Starting background caching for magnet: {magnet_preview}")
else:
logger.info(f"Starting background caching for magnet object: {type(magnet)}")
try:
# Extraire le magnet si c'est un dictionnaire
if isinstance(magnet, dict) and magnet.get("magnet"):
magnet = magnet.get("magnet")
# Vérifier si c'est un pack de saison
info_hash = get_info_hash_from_magnet(magnet)
is_season_pack = self._check_if_season_pack(magnet)
# Créer le dictionnaire de formulaire
form = {'src': magnet}
# Ajouter folder_name seulement si c'est un pack de saison
if is_season_pack:
form['folder_name'] = f"season_pack_{info_hash}"
# Ajouter l'apikey
url = f"{self.base_url}/transfer/create"
# Ajouter des logs pour le débogage
logger.debug(f"Premiumize: Sending background caching request with data: {form}")
response = await self.json_response(url, method='post', data={**form, 'apikey': self.api_key})
if not response:
logger.error("Premiumize: Aucune réponse reçue de l'API pour start_background_caching")
return False
elif response.get("status") != "success":
logger.error(f"Premiumize: Échec du démarrage du caching en arrière-plan. Réponse: {response}")
return False
transfer_id = response.get("id")
if not transfer_id:
logger.error("Premiumize: Aucun ID de transfert retourné")
return False
logger.info(f"Premiumize: Démarrage réussi du caching en arrière-plan avec l'ID de transfert: {transfer_id}")
return True
except Exception as e:
logger.error(f"Premiumize: Erreur lors du démarrage du caching en arrière-plan: {str(e)}")
return False
async def get_stream_link(self, query, config, ip=None):
"""Get a stream link for a magnet link"""
if not query:
logger.warning("Premiumize: Aucun query fourni pour get_stream_link")
return None
# Afficher le début du magnet dans les logs
if isinstance(query, str):
query_preview = query[:50] + "..." if len(query) > 50 else query
logger.info(f"Premiumize: Récupération du lien de streaming pour le magnet: {query_preview}")
elif isinstance(query, dict) and query.get("magnet"):
magnet_preview = query["magnet"][:50] + "..." if len(query["magnet"]) > 50 else query["magnet"]
logger.info(f"Premiumize: Récupération du lien de streaming pour le magnet: {magnet_preview}")
else:
logger.info(f"Premiumize: Récupération du lien de streaming pour l'objet query: {type(query)}")
# Vérifier si c'est une série et extraire la saison/épisode
season = None
episode = None
if isinstance(query, dict):
magnet = query.get("magnet")
if not magnet:
logger.error("No magnet link in query")
return None
# Vérifier si c'est une série
if query.get("type") == "series" and query.get("season") and query.get("episode"):
season = query["season"].replace("S", "") if isinstance(query["season"], str) else query["season"]
episode = query["episode"].replace("E", "") if isinstance(query["episode"], str) else query["episode"]
try:
season = int(season)
episode = int(episode)
except (ValueError, TypeError):
logger.error(f"Invalid season/episode format: {season}/{episode}")
return None
else:
magnet = query
# Essayer d'abord le téléchargement direct
try:
response = await self.json_response(
f"{self.base_url}/transfer/directdl",
method="post",
data={"apikey": self.api_key, "src": magnet}
)
if response and response.get("status") == "success":
logger.info("Got direct download response")
if "content" in response and response["content"]:
# Si c'est une série, chercher l'épisode correspondant
if season is not None and episode is not None:
matching_files = []
for file in response["content"]:
filename = file.get("path", "").split("/")[-1]
if season_episode_in_filename(filename, season, episode):
matching_files.append(file)
if matching_files:
# Prendre le plus gros fichier parmi ceux qui correspondent
selected_file = max(matching_files, key=lambda x: x.get("size", 0))
stream_link = selected_file.get("stream_link") or selected_file.get("link")
if stream_link:
logger.info(f"Found matching episode stream link: {stream_link[:50]}...")
return stream_link
# Si ce n'est pas une série ou si aucun fichier ne correspond,
# prendre le plus gros fichier vidéo
video_files = [f for f in response["content"]
if isinstance(f.get("path", ""), str) and
f.get("path", "").lower().endswith((".mkv", ".mp4", ".avi", ".m4v"))]
if video_files:
largest_file = max(video_files, key=lambda x: x.get("size", 0))
stream_link = largest_file.get("stream_link") or largest_file.get("link")
if stream_link:
logger.info(f"Found stream link: {stream_link[:50]}...")
return stream_link
elif response.get("location"):
logger.info(f"Found direct location: {response['location'][:50]}...")
return response["location"]
except Exception as e:
logger.error(f"Error in direct download: {str(e)}")
# Continue avec la méthode standard si le téléchargement direct échoue
# Si le téléchargement direct a échoué, essayer la méthode standard
logger.info(f"Premiumize: Tentative d'ajout du magnet: {magnet[:50]}...")
response = await self.add_magnet(magnet, ip)
if not response:
logger.error("Premiumize: Aucune réponse reçue de l'API pour add_magnet")
return None
elif response.get("status") != "success":
logger.error(f"Premiumize: Échec de l'ajout du magnet. Réponse: {response}")
return None
logger.info(f"Premiumize: Magnet ajouté avec succès. ID: {response.get('id')}")
# Récupérer l'ID du transfert
transfer_id = response.get("id")
if not transfer_id:
logger.error("No transfer ID in response")
return None
# Attendre que le transfert soit terminé
if not await self._wait_for_season_pack(transfer_id):
logger.error("Transfer timed out")
return None
# Récupérer les détails du dossier
folder_details = await self.get_folder_or_file_details(transfer_id)
if not folder_details or not folder_details.get("content"):
logger.error("No content in folder details")
return None
# Filtrer les fichiers vidéo
video_files = [f for f in folder_details["content"]
if isinstance(f.get("mime_type", ""), str) and
f.get("mime_type", "").startswith("video/")]
# Si c'est une série, chercher l'épisode correspondant
if season is not None and episode is not None:
matching_files = []
for file in video_files:
filename = file.get("name", "")
if season_episode_in_filename(filename, season, episode):
matching_files.append(file)
if matching_files:
# Prendre le plus gros fichier parmi ceux qui correspondent
selected_file = max(matching_files, key=lambda x: x.get("size", 0))
logger.info(f"Selected matching episode file: {selected_file.get('name')}")
return selected_file.get("stream_link")
# Si ce n'est pas une série ou si aucun fichier ne correspond,
# prendre le plus gros fichier vidéo
if video_files:
selected_file = max(video_files, key=lambda x: x.get("size", 0))
logger.info(f"Selected largest video file: {selected_file.get('name')}")
return selected_file.get("stream_link")
logger.error("No suitable video file found")
return None