Initial commit
This commit is contained in:
202
app/jackett_api.py
Normal file
202
app/jackett_api.py
Normal file
@@ -0,0 +1,202 @@
|
||||
import requests
|
||||
import logging
|
||||
import xml.etree.ElementTree as ET
|
||||
from datetime import datetime
|
||||
from dateutil import parser as date_parser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class JackettAPI:
|
||||
"""Classe pour interagir avec l'API Jackett"""
|
||||
|
||||
def __init__(self, base_url, api_key):
|
||||
self.base_url = base_url.rstrip('/')
|
||||
self.api_key = api_key
|
||||
self.session = requests.Session()
|
||||
self.session.headers.update({
|
||||
'User-Agent': 'Lycostorrent/2.0'
|
||||
})
|
||||
|
||||
def get_indexers(self):
|
||||
"""Récupère la liste des indexers (trackers) configurés dans Jackett"""
|
||||
try:
|
||||
url = f"{self.base_url}/api/v2.0/indexers/all/results/torznab/api"
|
||||
params = {
|
||||
'apikey': self.api_key,
|
||||
't': 'indexers',
|
||||
'configured': 'true'
|
||||
}
|
||||
|
||||
response = self.session.get(url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
|
||||
root = ET.fromstring(response.content)
|
||||
|
||||
indexers = []
|
||||
for indexer in root.findall('.//indexer'):
|
||||
indexer_data = {
|
||||
'id': indexer.get('id'),
|
||||
'name': indexer.find('title').text if indexer.find('title') is not None else 'Unknown',
|
||||
'type': indexer.get('type', 'public'),
|
||||
}
|
||||
indexers.append(indexer_data)
|
||||
|
||||
logger.info(f"✅ {len(indexers)} indexers récupérés depuis Jackett")
|
||||
return indexers
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"❌ Erreur connexion Jackett: {e}")
|
||||
return self._get_indexers_fallback()
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Erreur récupération indexers: {e}")
|
||||
return []
|
||||
|
||||
def _get_indexers_fallback(self):
|
||||
"""Méthode alternative pour récupérer les indexers"""
|
||||
try:
|
||||
url = f"{self.base_url}/api/v2.0/indexers"
|
||||
params = {'apikey': self.api_key}
|
||||
|
||||
response = self.session.get(url, params=params, timeout=10)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
|
||||
indexers = []
|
||||
for indexer in data:
|
||||
if indexer.get('configured', False):
|
||||
indexers.append({
|
||||
'id': indexer.get('id'),
|
||||
'name': indexer.get('name', 'Unknown'),
|
||||
'type': indexer.get('type', 'public'),
|
||||
})
|
||||
|
||||
return indexers
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Erreur fallback indexers: {e}")
|
||||
return []
|
||||
|
||||
def search(self, query, indexers=None, category=None, max_results=2000):
|
||||
"""
|
||||
Effectue une recherche sur Jackett avec pagination.
|
||||
|
||||
Args:
|
||||
query: Terme de recherche
|
||||
indexers: Liste des trackers à utiliser
|
||||
category: ID de catégorie Jackett (2000=films, 5000=séries, etc.)
|
||||
max_results: Nombre max de résultats
|
||||
|
||||
Returns:
|
||||
Liste de résultats formatés
|
||||
"""
|
||||
try:
|
||||
url = f"{self.base_url}/api/v2.0/indexers/all/results"
|
||||
all_results = []
|
||||
offset = 0
|
||||
limit_per_request = 500
|
||||
|
||||
while len(all_results) < max_results:
|
||||
params = {
|
||||
'apikey': self.api_key,
|
||||
'Query': query,
|
||||
'limit': limit_per_request,
|
||||
'offset': offset,
|
||||
}
|
||||
|
||||
# Ajouter les trackers si spécifiés
|
||||
if indexers and len(indexers) > 0:
|
||||
params['Tracker[]'] = indexers
|
||||
|
||||
# Ajouter la catégorie si spécifiée
|
||||
if category:
|
||||
params['Category'] = category
|
||||
|
||||
logger.info(f"🔍 Requête Jackett: query='{query}', offset={offset}, limit={limit_per_request}")
|
||||
|
||||
response = self.session.get(url, params=params, timeout=60)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
results = data.get('Results', [])
|
||||
|
||||
if not results:
|
||||
break
|
||||
|
||||
all_results.extend(results)
|
||||
|
||||
# Si on a moins de résultats que la limite, c'est qu'on a tout récupéré
|
||||
if len(results) < limit_per_request:
|
||||
break
|
||||
|
||||
offset += limit_per_request
|
||||
|
||||
# Formater les résultats
|
||||
formatted_results = [self._format_result(r) for r in all_results[:max_results]]
|
||||
|
||||
logger.info(f"✅ {len(formatted_results)} résultats récupérés au total")
|
||||
return formatted_results
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
logger.error("⏱️ Timeout lors de la recherche Jackett")
|
||||
return []
|
||||
except requests.exceptions.RequestException as e:
|
||||
logger.error(f"❌ Erreur connexion Jackett: {e}")
|
||||
return []
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Erreur recherche: {e}", exc_info=True)
|
||||
return []
|
||||
|
||||
def _format_result(self, result):
|
||||
"""Formate un résultat Jackett"""
|
||||
try:
|
||||
# Parser la date
|
||||
publish_date = result.get('PublishDate', '')
|
||||
try:
|
||||
if publish_date:
|
||||
dt = date_parser.parse(publish_date)
|
||||
formatted_date = dt.strftime('%Y-%m-%d %H:%M')
|
||||
else:
|
||||
formatted_date = 'N/A'
|
||||
except:
|
||||
formatted_date = 'N/A'
|
||||
|
||||
return {
|
||||
'Title': result.get('Title', 'Sans titre'),
|
||||
'Tracker': result.get('Tracker', 'Unknown'),
|
||||
'Category': result.get('CategoryDesc', 'N/A'),
|
||||
'PublishDate': formatted_date,
|
||||
'PublishDateRaw': publish_date,
|
||||
'Size': result.get('Size', 0),
|
||||
'SizeFormatted': self._format_size(result.get('Size', 0)),
|
||||
'Seeders': result.get('Seeders', 0),
|
||||
'Peers': result.get('Peers', 0),
|
||||
'Link': result.get('Link', ''),
|
||||
'MagnetUri': result.get('MagnetUri', ''),
|
||||
'Guid': result.get('Guid', ''),
|
||||
'Details': result.get('Details', ''),
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Erreur formatage résultat: {e}")
|
||||
return result
|
||||
|
||||
def _format_size(self, size_bytes):
|
||||
"""Convertit une taille en bytes vers un format lisible"""
|
||||
try:
|
||||
size_bytes = int(size_bytes)
|
||||
if size_bytes == 0:
|
||||
return "0 B"
|
||||
|
||||
units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
unit_index = 0
|
||||
size = float(size_bytes)
|
||||
|
||||
while size >= 1024 and unit_index < len(units) - 1:
|
||||
size /= 1024
|
||||
unit_index += 1
|
||||
|
||||
if unit_index >= 2: # MB et plus
|
||||
return f"{size:.2f} {units[unit_index]}"
|
||||
else:
|
||||
return f"{size:.0f} {units[unit_index]}"
|
||||
except:
|
||||
return "N/A"
|
||||
Reference in New Issue
Block a user