Refactor
This commit is contained in:
@@ -1,50 +1,72 @@
|
|||||||
|
|
||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_login import LoginManager
|
from flask_login import LoginManager
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
login_manager = LoginManager()
|
login_manager = LoginManager()
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
# Frontend path
|
# --- Chemins ---
|
||||||
base_dir = os.path.abspath(os.path.dirname(__file__))
|
base_dir = Path(__file__).resolve().parent
|
||||||
template_dir = os.path.join(base_dir, '..', 'frontend', 'templates')
|
template_dir = base_dir.parent / 'frontend' / 'templates'
|
||||||
static_dir = os.path.join(base_dir, '..', 'frontend', 'static')
|
static_dir = base_dir.parent / 'frontend' / 'static'
|
||||||
|
instance_path = base_dir.parent / 'instance'
|
||||||
|
instance_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# DB path
|
# --- Chargement des variables d'environnement ---
|
||||||
instance_path = os.path.join(base_dir, '..', 'instance')
|
env_path = base_dir.parent / '.env'
|
||||||
os.makedirs(instance_path, exist_ok=True)
|
load_dotenv(env_path)
|
||||||
|
|
||||||
|
# --- Vérification de la clé secrète ---
|
||||||
|
secret = os.getenv('FLASK_SECRET')
|
||||||
|
if not secret:
|
||||||
|
raise RuntimeError("FLASK_SECRET must be set in environment")
|
||||||
|
|
||||||
|
# --- Création de l'application ---
|
||||||
app = Flask(
|
app = Flask(
|
||||||
__name__,
|
__name__,
|
||||||
template_folder=template_dir,
|
template_folder=str(template_dir),
|
||||||
static_folder=static_dir,
|
static_folder=str(static_dir),
|
||||||
instance_path=os.path.abspath(instance_path)
|
instance_path=str(instance_path)
|
||||||
)
|
)
|
||||||
|
|
||||||
app.config['SECRET_KEY'] = 'IddAhxI%$G%2X3joI04i'
|
# --- Configuration ---
|
||||||
app.config['SQLALCHEMY_DATABASE_URI'] = f"sqlite:///{os.path.join(app.instance_path, 'users.db')}"
|
app.config.update(
|
||||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
SECRET_KEY=secret,
|
||||||
|
SQLALCHEMY_DATABASE_URI=f"sqlite:///{instance_path / 'users.db'}",
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS=False,
|
||||||
|
SESSION_COOKIE_HTTPONLY=True,
|
||||||
|
SESSION_COOKIE_SAMESITE='Lax',
|
||||||
|
SESSION_COOKIE_SECURE=os.getenv("FLASK_ENV") == "production"
|
||||||
|
)
|
||||||
|
|
||||||
# Init extensions
|
# --- Initialisation des extensions ---
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
login_manager.init_app(app)
|
login_manager.init_app(app)
|
||||||
login_manager.login_view = "login"
|
login_manager.login_view = "login"
|
||||||
|
login_manager.session_protection = "strong"
|
||||||
|
login_manager.login_message = "Veuillez vous connecter pour accéder à cette page."
|
||||||
|
login_manager.login_message_category = "warning"
|
||||||
|
|
||||||
# Import models for user_loader
|
from backend import routes
|
||||||
from backend.models import User
|
from backend.models import User
|
||||||
|
|
||||||
|
# Loader pour Flask-Login
|
||||||
@login_manager.user_loader
|
@login_manager.user_loader
|
||||||
def load_user(user_id):
|
def load_user(user_id):
|
||||||
return User.query.get(int(user_id))
|
return User.query.get(int(user_id))
|
||||||
|
|
||||||
|
# --- Chargement différé des modèles et routes ---
|
||||||
|
with app.app_context():
|
||||||
|
|
||||||
# Import routes and give app
|
# Création de la DB si nécessaire
|
||||||
from backend import routes
|
db.create_all()
|
||||||
|
|
||||||
|
# Initialisation des routes
|
||||||
routes.init_app(app)
|
routes.init_app(app)
|
||||||
|
|
||||||
# Create DB if needed
|
|
||||||
with app.app_context():
|
|
||||||
db.create_all()
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import requests
|
|
||||||
import os
|
|
||||||
|
|
||||||
ALLDEBRID_API_KEY = os.getenv("ALLDEBRID_API_KEY", "mtrQI4h583rHe2ZpvpbC")
|
|
||||||
NTFY_TOPIC_URL = os.getenv("NTFY_TOPIC_URL", "https://ntfy.lucasroyer.fr/alldebrid")
|
|
||||||
NTFY_TOKEN = os.getenv("NTFY_TOKEN", "tk_qqi1ayj2a0etxafgicl0h7ww71ofb") # Ton token pour topic protégé
|
|
||||||
|
|
||||||
def check_alldebrid_status():
|
|
||||||
# Retourne True si premium actif, False sinon
|
|
||||||
try:
|
|
||||||
r = requests.get(
|
|
||||||
"https://api.alldebrid.com/v4/user",
|
|
||||||
params={"agent": "ygg-service", "apikey": ALLDEBRID_API_KEY},
|
|
||||||
timeout=5
|
|
||||||
)
|
|
||||||
data = r.json()
|
|
||||||
return data.get("data", {}).get("user", {}).get("isPremium", False)
|
|
||||||
except Exception as e:
|
|
||||||
print("Erreur AllDebrid:", e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def send_ntfy(title, message):
|
|
||||||
# Envoie une notification sur le topic ntfy, avec token si nécessaire
|
|
||||||
headers = {"Title": title}
|
|
||||||
if NTFY_TOKEN:
|
|
||||||
headers["Authorization"] = f"Bearer {NTFY_TOKEN}"
|
|
||||||
|
|
||||||
try:
|
|
||||||
r = requests.post(
|
|
||||||
NTFY_TOPIC_URL,
|
|
||||||
data=message.encode("utf-8"),
|
|
||||||
headers=headers,
|
|
||||||
timeout=5
|
|
||||||
)
|
|
||||||
if r.status_code not in (200, 201):
|
|
||||||
print(f"❌ Échec notification ({r.status_code}): {r.text}")
|
|
||||||
else:
|
|
||||||
print(f"✅ Notification envoyée : {title}")
|
|
||||||
except Exception as e:
|
|
||||||
print("Erreur ntfy:", e)
|
|
||||||
144
backend/api.py
Normal file
144
backend/api.py
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
from io import BytesIO
|
||||||
|
import requests
|
||||||
|
import os
|
||||||
|
from backend.utils import format_size, calculate_age
|
||||||
|
|
||||||
|
YGG_PASSKEY = os.getenv("YGG_PASSKEY")
|
||||||
|
ALLDEBRID_API_KEY = os.getenv("ALLDEBRID_API_KEY")
|
||||||
|
NTFY_TOPIC_URL = os.getenv("NTFY_TOPIC_URL")
|
||||||
|
NTFY_TOKEN = os.getenv("NTFY_TOKEN")
|
||||||
|
|
||||||
|
MAX_SIZE_BYTES = 5*1024**3
|
||||||
|
NB_PAGES = 2
|
||||||
|
|
||||||
|
def check_alldebrid_status():
|
||||||
|
# Retourne True si premium actif, False sinon
|
||||||
|
try:
|
||||||
|
r = requests.get(
|
||||||
|
"https://api.alldebrid.com/v4/user",
|
||||||
|
params={"agent": "ygg-service", "apikey": ALLDEBRID_API_KEY},
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
data = r.json()
|
||||||
|
return data.get("data", {}).get("user", {}).get("isPremium", False)
|
||||||
|
except Exception as e:
|
||||||
|
print("Erreur AllDebrid:", e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def send_ntfy(title, message):
|
||||||
|
# Envoie une notification sur le topic ntfy, avec token si nécessaire
|
||||||
|
headers = {"Title": title}
|
||||||
|
if NTFY_TOKEN:
|
||||||
|
headers["Authorization"] = f"Bearer {NTFY_TOKEN}"
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = requests.post(
|
||||||
|
NTFY_TOPIC_URL,
|
||||||
|
data=message.encode("utf-8"),
|
||||||
|
headers=headers,
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
|
if r.status_code not in (200, 201):
|
||||||
|
print(f"❌ Échec notification ({r.status_code}): {r.text}")
|
||||||
|
else:
|
||||||
|
print(f"✅ Notification envoyée : {title}")
|
||||||
|
except Exception as e:
|
||||||
|
print("Erreur ntfy:", e)
|
||||||
|
|
||||||
|
def search_torrents(query: str, category_id: str | None = None) -> list[dict]:
|
||||||
|
"""Recherche des torrents sur l'API Yggtorrent, retourne la liste brute."""
|
||||||
|
url = "https://yggapi.eu/torrents"
|
||||||
|
params = {
|
||||||
|
"page": 1,
|
||||||
|
"q": query,
|
||||||
|
"order_by": "uploaded_at",
|
||||||
|
"per_page": 100
|
||||||
|
}
|
||||||
|
if category_id:
|
||||||
|
params["category_id"] = category_id
|
||||||
|
|
||||||
|
results = []
|
||||||
|
try:
|
||||||
|
for page in range(1, NB_PAGES + 1):
|
||||||
|
params['page'] = page
|
||||||
|
r = requests.get(url, params=params, timeout=5)
|
||||||
|
r.raise_for_status()
|
||||||
|
data = r.json()
|
||||||
|
results.extend(data)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print("Erreur API Yggtorrent:", e)
|
||||||
|
results = []
|
||||||
|
|
||||||
|
return results
|
||||||
|
|
||||||
|
def filter_and_format_torrents(torrents: list[dict]) -> list[dict]:
|
||||||
|
"""Filtre les torrents trop gros et formate la taille et l'âge."""
|
||||||
|
filtered = []
|
||||||
|
for t in torrents:
|
||||||
|
if t['size'] <= MAX_SIZE_BYTES:
|
||||||
|
t['size'] = format_size(t['size'])
|
||||||
|
days, human = calculate_age(t['uploaded_at'])
|
||||||
|
t['age_days'] = days
|
||||||
|
t['age_human'] = human
|
||||||
|
filtered.append(t)
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
def download_torrent_ygg(torrent_id: str) -> tuple[bytes, str]:
|
||||||
|
# Télécharge le fichier .torrent depuis Yggtorrent
|
||||||
|
url = f"https://yggapi.eu/torrent/{torrent_id}/download"
|
||||||
|
params = {
|
||||||
|
"passkey": YGG_PASSKEY,
|
||||||
|
"tracker_domain": "tracker.p2p-world.net"
|
||||||
|
}
|
||||||
|
r = requests.get(url, params=params, timeout=10, allow_redirects=True)
|
||||||
|
r.raise_for_status()
|
||||||
|
filename = f"{torrent_id}.torrent"
|
||||||
|
return r.content, filename
|
||||||
|
|
||||||
|
def upload_to_alldebrid(torrent_content: bytes, filename: str) -> str:
|
||||||
|
# Upload du torrent sur Alldebrid, retourne l'ID
|
||||||
|
headers = {"Authorization": f"Bearer {ALLDEBRID_API_KEY}"}
|
||||||
|
files = {'files[]': (filename, BytesIO(torrent_content))}
|
||||||
|
r = requests.post("https://api.alldebrid.com/v4/magnet/upload/file", headers=headers, files=files, timeout=20)
|
||||||
|
r.raise_for_status()
|
||||||
|
data = r.json()
|
||||||
|
if data.get("status") != "success":
|
||||||
|
raise RuntimeError(f"Upload Alldebrid échoué : {data}")
|
||||||
|
return data["data"]["files"][0]["id"]
|
||||||
|
|
||||||
|
def get_alldebrid_links(debrid_id: str) -> list[dict]:
|
||||||
|
# Récupère les liens directs Alldebrid à partir d'un ID
|
||||||
|
headers = {"Authorization": f"Bearer {ALLDEBRID_API_KEY}"}
|
||||||
|
|
||||||
|
# Récupération des fichiers du magnet
|
||||||
|
r = requests.get("https://api.alldebrid.com/v4/magnet/files", headers=headers, params={"id[]": debrid_id}, timeout=10)
|
||||||
|
r.raise_for_status()
|
||||||
|
data = r.json()
|
||||||
|
if data.get("status") != "success":
|
||||||
|
raise RuntimeError(f"Récupération liens Alldebrid échouée : {data}")
|
||||||
|
|
||||||
|
alldebrid_links = []
|
||||||
|
for magnet in data.get("data", {}).get("magnets", []):
|
||||||
|
for file in magnet.get("files", []):
|
||||||
|
if "e" in file:
|
||||||
|
for entry in file["e"]:
|
||||||
|
if "l" in entry:
|
||||||
|
alldebrid_links.append(entry["l"])
|
||||||
|
elif "l" in file:
|
||||||
|
alldebrid_links.append(file["l"])
|
||||||
|
|
||||||
|
# Débloquer les liens
|
||||||
|
direct_links = []
|
||||||
|
for link in alldebrid_links:
|
||||||
|
r2 = requests.get("https://api.alldebrid.com/v4/link/unlock", headers=headers, params={"link": link}, timeout=10)
|
||||||
|
r2.raise_for_status()
|
||||||
|
info = r2.json()
|
||||||
|
if info.get("status") != "success":
|
||||||
|
raise RuntimeError(f"Déblocage lien Alldebrid échoué : {info}")
|
||||||
|
direct_links.append({
|
||||||
|
"name": info["data"]["filename"],
|
||||||
|
"size": info["data"]["filesize"],
|
||||||
|
"link": info["data"]["link"]
|
||||||
|
})
|
||||||
|
|
||||||
|
return direct_links
|
||||||
@@ -1,15 +1,20 @@
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from flask import session
|
from flask import session
|
||||||
from flask_login import login_user
|
from flask_login import login_user
|
||||||
from . import db
|
|
||||||
from backend.models import User, LoginIP
|
from backend.api import check_alldebrid_status, send_ntfy
|
||||||
from backend.alldebrid import check_alldebrid_status, send_ntfy
|
from backend.security import verify_password, needs_rehash, hash_password
|
||||||
from backend.security import verify_password
|
|
||||||
|
|
||||||
MAX_ATTEMPTS = 5
|
MAX_ATTEMPTS = 5
|
||||||
BLOCK_TIME = timedelta(minutes=15)
|
BLOCK_TIME = timedelta(minutes=15)
|
||||||
|
|
||||||
def authenticate_user(username: str, password: str, ip: str):
|
def authenticate_user(username: str, password: str, ip: str):
|
||||||
|
from . import db
|
||||||
|
from backend.models import User, LoginIP
|
||||||
|
|
||||||
|
now = datetime.utcnow()
|
||||||
|
|
||||||
|
# --- Gestion IP ---
|
||||||
ip_record = LoginIP.query.filter_by(ip=ip).first()
|
ip_record = LoginIP.query.filter_by(ip=ip).first()
|
||||||
if not ip_record:
|
if not ip_record:
|
||||||
ip_record = LoginIP(ip=ip)
|
ip_record = LoginIP(ip=ip)
|
||||||
@@ -17,34 +22,41 @@ def authenticate_user(username: str, password: str, ip: str):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# IP bloquée
|
# IP bloquée
|
||||||
if ip_record.blocked_until and datetime.utcnow() < ip_record.blocked_until:
|
if ip_record.blocked_until and now < ip_record.blocked_until:
|
||||||
remaining = int((ip_record.blocked_until - datetime.utcnow()).total_seconds() // 60) + 1
|
remaining = int((ip_record.blocked_until - now).total_seconds() // 60) + 1
|
||||||
return None, f"Trop de tentatives depuis votre IP. Réessayez dans {remaining} min."
|
return None, f"Trop de tentatives depuis votre IP. Réessayez dans {remaining} min."
|
||||||
|
|
||||||
|
# --- Récupération utilisateur ---
|
||||||
user = User.query.filter_by(username=username).first()
|
user = User.query.filter_by(username=username).first()
|
||||||
if user and verify_password(password, user.password):
|
if user and verify_password(password, user.password):
|
||||||
# Reset IP
|
# Upgrade du hash si nécessaire
|
||||||
|
if needs_rehash(user.password):
|
||||||
|
user.password = hash_password(password)
|
||||||
|
|
||||||
|
# Reset compteur IP
|
||||||
ip_record.count = 0
|
ip_record.count = 0
|
||||||
ip_record.blocked_until = None
|
ip_record.blocked_until = None
|
||||||
|
ip_record.last_attempt = now
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
login_user(user)
|
login_user(user)
|
||||||
session['user'] = user.username
|
|
||||||
|
|
||||||
# Vérification AllDebrid
|
# Vérification AllDebrid
|
||||||
premium = check_alldebrid_status()
|
premium = check_alldebrid_status()
|
||||||
session['alldebrid_active'] = premium
|
session['alldebrid_active'] = premium
|
||||||
if premium:
|
if not premium:
|
||||||
send_ntfy("AllDebrid non premium", "Tentative avortée sur ygg-service !")
|
send_ntfy("AllDebrid non premium", "Tentative avortée sur ygg-service !")
|
||||||
|
|
||||||
return user, None
|
return user, None
|
||||||
|
|
||||||
|
# --- Échec de connexion ---
|
||||||
|
ip_record.count += 1
|
||||||
|
ip_record.last_attempt = now
|
||||||
|
if ip_record.count >= MAX_ATTEMPTS:
|
||||||
|
ip_record.blocked_until = now + BLOCK_TIME
|
||||||
|
msg = f"Trop de tentatives depuis votre IP. Blocage pour {BLOCK_TIME.seconds // 60} minutes."
|
||||||
else:
|
else:
|
||||||
ip_record.count += 1
|
msg = f"Identifiants invalides ({ip_record.count}/{MAX_ATTEMPTS})"
|
||||||
ip_record.last_attempt = datetime.utcnow()
|
|
||||||
if ip_record.count >= MAX_ATTEMPTS:
|
db.session.commit()
|
||||||
ip_record.blocked_until = datetime.utcnow() + BLOCK_TIME
|
return None, msg
|
||||||
msg = f"Trop de tentatives. Blocage pour {BLOCK_TIME.seconds // 60} minutes."
|
|
||||||
else:
|
|
||||||
msg = f"Identifiants invalides ({ip_record.count}/{MAX_ATTEMPTS})"
|
|
||||||
db.session.commit()
|
|
||||||
return None, msg
|
|
||||||
|
|||||||
@@ -1,27 +1,17 @@
|
|||||||
# Standard library
|
# Standard library
|
||||||
from datetime import timedelta
|
import time, os
|
||||||
from io import BytesIO
|
|
||||||
import time
|
|
||||||
|
|
||||||
# Third-party libraries
|
# Third-party libraries
|
||||||
import requests
|
import requests
|
||||||
from flask import render_template, request, redirect, url_for, session, flash, jsonify, make_response
|
from flask import render_template, request, redirect, url_for, session, flash, jsonify, make_response
|
||||||
from flask_login import logout_user, login_required, current_user
|
from flask_login import logout_user, login_required, current_user
|
||||||
from flask import request, render_template, redirect, url_for, flash
|
|
||||||
|
|
||||||
|
|
||||||
# Project imports
|
# Project imports
|
||||||
from backend.utils import format_size, calculate_age
|
|
||||||
from backend.auth import authenticate_user
|
from backend.auth import authenticate_user
|
||||||
|
from backend.api import search_torrents, filter_and_format_torrents, download_torrent_ygg, upload_to_alldebrid, get_alldebrid_links
|
||||||
|
|
||||||
MAX_ATTEMPTS = 5
|
YGG_PASSKEY = os.getenv("YGG_PASSKEY")
|
||||||
BLOCK_TIME = timedelta(minutes=15)
|
ALLDEBRID_KEY = os.getenv("ALLDEBRID_API_KEY")
|
||||||
|
|
||||||
MAX_SIZE_BYTES = 5*1024**3
|
|
||||||
NB_PAGES = 2
|
|
||||||
|
|
||||||
YGG_PASSKEY = "xj1MgNuyzFKCjOtnawGBC2egDOciUg04"
|
|
||||||
ALLDEBRID_KEY = "mtrQI4h583rHe2ZpvpbC"
|
|
||||||
|
|
||||||
def init_app(app):
|
def init_app(app):
|
||||||
|
|
||||||
@@ -66,46 +56,11 @@ def init_app(app):
|
|||||||
query = request.args.get('query')
|
query = request.args.get('query')
|
||||||
category_id = request.args.get('category_id')
|
category_id = request.args.get('category_id')
|
||||||
|
|
||||||
# Préparer l'URL
|
# 1. Recherche brute
|
||||||
url = "https://yggapi.eu/torrents"
|
torrents = search_torrents(query, category_id)
|
||||||
|
|
||||||
# Préparer les paramètres
|
# 2. Filtrage et formatage
|
||||||
params = {
|
filtered_results = filter_and_format_torrents(torrents)
|
||||||
"page": 1,
|
|
||||||
"q": query,
|
|
||||||
"order_by": "uploaded_at",
|
|
||||||
"per_page": 100
|
|
||||||
}
|
|
||||||
|
|
||||||
# Ajouter la catégorie seulement si elle est renseignée et non vide
|
|
||||||
if category_id:
|
|
||||||
params["category_id"] = category_id
|
|
||||||
|
|
||||||
results = []
|
|
||||||
|
|
||||||
# Appeler l'API
|
|
||||||
try:
|
|
||||||
for page in range(1, NB_PAGES):
|
|
||||||
params['page'] = page
|
|
||||||
print("Appel API page", page, "avec params:", params)
|
|
||||||
response = requests.get(url, params=params, timeout=5)
|
|
||||||
response.raise_for_status()
|
|
||||||
data = response.json() # ici c'est une liste directement
|
|
||||||
print(f"Nombre de torrents reçus page {page}:", len(data))
|
|
||||||
results.extend(data)
|
|
||||||
except Exception as e:
|
|
||||||
print("Erreur API Yggtorrent:", e)
|
|
||||||
results = []
|
|
||||||
|
|
||||||
filtered_results = []
|
|
||||||
|
|
||||||
for torrent in results:
|
|
||||||
if torrent['size'] <= MAX_SIZE_BYTES:
|
|
||||||
torrent['size'] = format_size(torrent['size'])
|
|
||||||
days, human = calculate_age(torrent['uploaded_at'])
|
|
||||||
torrent['age_days'] = days # to show
|
|
||||||
torrent['age_human'] = human # to sort
|
|
||||||
filtered_results.append(torrent)
|
|
||||||
|
|
||||||
return render_template(
|
return render_template(
|
||||||
'search_results.html',
|
'search_results.html',
|
||||||
@@ -117,124 +72,24 @@ def init_app(app):
|
|||||||
@app.route("/torrent/<torrent_id>")
|
@app.route("/torrent/<torrent_id>")
|
||||||
@login_required
|
@login_required
|
||||||
def torrent_details(torrent_id):
|
def torrent_details(torrent_id):
|
||||||
# --- 1er bloc : récupération du torrent ---
|
|
||||||
try:
|
try:
|
||||||
url = f"https://yggapi.eu/torrent/{torrent_id}/download"
|
# 1. Téléchargement depuis Ygg
|
||||||
params = {
|
torrent_content, filename = download_torrent_ygg(torrent_id)
|
||||||
"passkey": YGG_PASSKEY,
|
|
||||||
"tracker_domain": "tracker.p2p-world.net"
|
# 2. Upload sur Alldebrid
|
||||||
}
|
debrid_id = upload_to_alldebrid(torrent_content, filename)
|
||||||
response = requests.get(url, params=params, timeout=10, allow_redirects=True)
|
|
||||||
response.raise_for_status()
|
# Pause courte si nécessaire (simule un délai API)
|
||||||
torrent_file_content = response.content
|
time.sleep(2)
|
||||||
torrent_filename = f"{torrent_id}.torrent"
|
|
||||||
|
# 3. Récupération des liens directs
|
||||||
|
direct_links = get_alldebrid_links(debrid_id)
|
||||||
|
|
||||||
# Request error
|
|
||||||
except requests.exceptions.RequestException as e:
|
except requests.exceptions.RequestException as e:
|
||||||
print("Erreur lors de la récupération du torrent depuis Yggtorrent:", e)
|
print("Erreur HTTP:", e)
|
||||||
return "Erreur récupération torrent", 500
|
return "Erreur lors de la communication avec l'API", 500
|
||||||
|
except RuntimeError as e:
|
||||||
|
print(e)
|
||||||
|
return str(e), 500
|
||||||
|
|
||||||
# --- 2e bloc : upload à Alldebrid ---
|
|
||||||
try:
|
|
||||||
files = {'files[]': (torrent_filename, BytesIO(torrent_file_content))}
|
|
||||||
headers = {"Authorization": f"Bearer {ALLDEBRID_KEY}"}
|
|
||||||
|
|
||||||
upload_request = requests.post(
|
|
||||||
"https://api.alldebrid.com/v4/magnet/upload/file",
|
|
||||||
headers=headers,
|
|
||||||
files=files,
|
|
||||||
timeout=20
|
|
||||||
)
|
|
||||||
upload_request.raise_for_status()
|
|
||||||
first_answer = upload_request.json()
|
|
||||||
|
|
||||||
debrid_id = first_answer["data"]["files"][0]["id"]
|
|
||||||
|
|
||||||
# API error
|
|
||||||
if first_answer["status"] != "success":
|
|
||||||
print("API error 1 : upload Alldebrid:", first_answer)
|
|
||||||
return "API error 1 : upload Alldebrid", 500
|
|
||||||
|
|
||||||
# Request error
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print("Request error 1 : upload Alldebrid:", e)
|
|
||||||
return "Request error 1 : upload Alldebrid:", 500
|
|
||||||
|
|
||||||
print("Alldebrid id:",debrid_id)
|
|
||||||
|
|
||||||
time.sleep(2)
|
|
||||||
|
|
||||||
# --- 3e bloc : second appel Alldebrid ---
|
|
||||||
try:
|
|
||||||
headers = {"Authorization": f"Bearer {ALLDEBRID_KEY}"}
|
|
||||||
|
|
||||||
alldebrid_link_request = requests.get(
|
|
||||||
"https://api.alldebrid.com/v4/magnet/files",
|
|
||||||
headers=headers,
|
|
||||||
params={"id[]": debrid_id},
|
|
||||||
timeout=10
|
|
||||||
)
|
|
||||||
alldebrid_link_request.raise_for_status()
|
|
||||||
second_answer = alldebrid_link_request.json()
|
|
||||||
|
|
||||||
# API error
|
|
||||||
if second_answer.get("status") != "success":
|
|
||||||
print("API error 2 : get Alldebrid link:", second_answer)
|
|
||||||
return "API error 2 : get Alldebrid link", 500
|
|
||||||
|
|
||||||
alldebrid_links = []
|
|
||||||
|
|
||||||
for magnet in second_answer.get("data", {}).get("magnets", []):
|
|
||||||
for file in magnet.get("files", []):
|
|
||||||
# Cas où il y a une liste "e"
|
|
||||||
if "e" in file:
|
|
||||||
for entry in file["e"]:
|
|
||||||
if "l" in entry:
|
|
||||||
alldebrid_links.append(entry["l"])
|
|
||||||
# Cas où le lien est directement dans "l"
|
|
||||||
elif "l" in file:
|
|
||||||
alldebrid_links.append(file["l"])
|
|
||||||
|
|
||||||
# Request error
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print("Request error 2 : get Alldebrid link:", e)
|
|
||||||
return "Request error 2 : get Alldebrid link", 500
|
|
||||||
|
|
||||||
print("Alldebrid links:",alldebrid_links)
|
|
||||||
|
|
||||||
# --- 4e bloc : troisieme appel Alldebrid ---
|
|
||||||
direct_links = []
|
|
||||||
try:
|
|
||||||
headers = {"Authorization": f"Bearer {ALLDEBRID_KEY}"}
|
|
||||||
|
|
||||||
for link in alldebrid_links:
|
|
||||||
direct_link_request = requests.get(
|
|
||||||
"https://api.alldebrid.com/v4/link/unlock",
|
|
||||||
headers=headers,
|
|
||||||
params={"link": link},
|
|
||||||
timeout=10
|
|
||||||
)
|
|
||||||
direct_link_request.raise_for_status()
|
|
||||||
third_anwser = direct_link_request.json()
|
|
||||||
|
|
||||||
# API error
|
|
||||||
if third_anwser.get("status") != "success":
|
|
||||||
print("API error 3 : get direct link:", third_anwser)
|
|
||||||
return "API error 3 : get direct link", 500
|
|
||||||
|
|
||||||
# Récupérer les liens directs
|
|
||||||
direct_links.append({
|
|
||||||
"name": third_anwser["data"]["filename"],
|
|
||||||
"size": third_anwser["data"]["filesize"],
|
|
||||||
"link": third_anwser["data"]["link"]
|
|
||||||
})
|
|
||||||
|
|
||||||
# Request error
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print("API error 3 : get direct link:", e)
|
|
||||||
return "API error 3 : get direct link", 500
|
|
||||||
|
|
||||||
print("Direct links:",direct_links)
|
|
||||||
|
|
||||||
# 3. Retour du lien direct au client
|
|
||||||
return jsonify(direct_links)
|
return jsonify(direct_links)
|
||||||
Reference in New Issue
Block a user