Les versions :latest et :fenrir-xx embarquent une fonctionnalité qui interroge les débrideurs pour connaître le nombre de slots d’upload disponibles sur le compte utilisé.
Problème : AllDebrid ne gère pas ça via son API.
Second effet Kiss Cool : cette option ne peut pour l’instant pas être désactivée, donc AD en erreur d’upload quand un magnet/.torrent n’est pas en cache.
L’astuce consiste à revenir sur une version plus ancienne de Decypharr, la :beta fonctionnant.
services:
decypharr:
image: ghcr.io/sirrobot01/decypharr:beta
#image: cy01/blackhole:fenrir
#image: cy01/blackhole:fenrir-09
container_name: decypharr
restart: always
privileged: true
cap_add:
- SYS_ADMIN
security_opt:
- apparmor:unconfined
ports:
- 8282:8282
volumes:
- /mnt/:/mnt/:rshared
- /mnt/Fichiers/decypharr/qbit:/mnt/Fichiers/decypharr/qbit
- /mnt/Docker/decypharr/:/app
environment:
- TZ=Europe/Paris
devices:
- /dev/fuse:/dev/fuse:rwm
Et j’en profite pour vous présenter l’excellent script de Bouby, lecteur du blog (\o/), qui permet de nettoyer les magnets liés à un compte AD. En effet, chez AD un compte est limité à 5000 liens. Il faut donc parfois faire un peu de ménage, surtout quand on fait des bibliothèques Plex/Jellyfin où il faut clairement plusieurs comptes AD.
Vous les voyez sur l’onglet Magnets de votre compte ou via l’API

status "success"
data { magnets: (704)[…] }
Un lien est ajouté quand on envoie un magnet ou .torrent sur son compte AD (captain Obvious!) mais aussi à CHAQUE recherche de contenu dessus via les outils Decypharr, DMM, Vortex, les addons sources de Stremio etc. Même si un contenu est en cache, le fait de le chercher en ajoutant un magnet/.torrent crée un lien sur votre compte.
Je n’ai pas cherché et ne sais absolument pas comment AD gère ça côté utilisateurs, si des liens vieux de plus de X mois/années/heures sont retirés ou non.
Quoi qu’il en soit voici le script Python de Bouby (encore merci !). J’ai augmenté les seuils en fonction de la limite de 5000 liens/compte.
#!/usr/bin/env python3
import requests
from datetime import datetime, timedelta
# ----------------------------------------
# CONFIGURATION DU SCRIPT (variables en dur)
# ----------------------------------------
# Clé API AllDebrid — obligatoire pour accéder aux magnets
API_KEY = "xxx"
# Dry-run : True = le script affiche ce qu’il ferait sans supprimer
# False = suppression réelle
DRY_RUN = False
# -----------------------------
# Seuils et limites pour la purge
# -----------------------------
THRESHOLD_HIGH = 4700 # déclenche la purge normale si nombre total de magnets > 900
THRESHOLD_LOW = 2700 # limite minimale après purge pour ne pas trop supprimer =700
MAX_DELETE = 2000 # nombre maximum de magnets à supprimer en mode normal
MIN_AGE_DAYS = 7 # ne supprimer que les magnets plus vieux que X jours (sauf urgence)
# -----------------------------
# Mode urgence
# -----------------------------
EMERGENCY_THRESHOLD = 4900 # si le nombre total de magnets > 950, on active la purge d'urgence
EMERGENCY_DELETE = 2000 # nombre de magnets à supprimer immédiatement en urgence, ignore l'âge minimum
# -----------------------------
# API endpoint AllDebrid
# -----------------------------
# v4.1 est le nouvel endpoint pour récupérer les magnets
BASE_URL = "https://api.alldebrid.com/v4.1/magnet/status"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# -----------------------------
# Catégories de statut
# -----------------------------
# Ces statuts sont utilisés pour trier les magnets par priorité
FAILED = {"Error", "error"} # magnets ayant rencontré une erreur
IN_PROGRESS = {"Processing"} # magnets en cours de téléchargement
DONE = {"Finished", "Ready"} # magnets terminés
# ----------------------------------------
# Récupérer tous les magnets depuis l’API
# ----------------------------------------
def get_magnets():
"""
Récupère la liste des magnets depuis l’API AllDebrid.
Retourne une liste vide si erreur ou endpoint obsolète.
"""
try:
r = requests.get(BASE_URL, headers=HEADERS, timeout=30)
r.raise_for_status()
data = r.json()
# Vérifie que la réponse contient bien les magnets
if "data" not in data or "magnets" not in data["data"]:
print("❌ Réponse API inattendue :")
print(data)
return []
return data["data"]["magnets"]
except requests.RequestException as e:
print("❌ Erreur réseau ou API :", e)
return []
# ----------------------------------------
# Supprimer un magnet par son ID
# ----------------------------------------
def delete_magnet(magnet_id):
"""
Supprime un magnet via l’API.
Si DRY_RUN = True, n’effectue pas la suppression mais affiche l’action.
"""
delete_url = "https://api.alldebrid.com/v4/magnet/delete"
if DRY_RUN:
print(f"[DRY-RUN] DELETE {magnet_id}")
return
try:
# POST pour supprimer le magnet
r = requests.post(delete_url, headers=HEADERS, data={"id": magnet_id})
r.raise_for_status()
print(f"✅ Supprimé magnet {magnet_id}")
except Exception as e:
print(f"❌ Erreur suppression {magnet_id} :", e)
# ----------------------------------------
# Trier et filtrer les magnets
# ----------------------------------------
def bucketize(magnets, ignore_age=False):
"""
Trie les magnets par statut et filtre par âge.
ignore_age=True ignore la limite MIN_AGE_DAYS (mode urgence)
"""
# Date limite selon l'âge minimum
cutoff = datetime.utcnow() - timedelta(days=MIN_AGE_DAYS)
# Séparer les magnets par statut pour appliquer la priorité
failed, progress, done = [], [], []
for m in magnets:
status = m.get("status", "")
created = m.get("uploadDate") or m.get("completionDate") or 0
created_dt = datetime.utcfromtimestamp(created) if created else datetime.utcnow()
# Filtrer par âge sauf si mode urgence
if not ignore_age and created_dt > cutoff:
continue
# Trier par statut
if status in FAILED:
failed.append(m)
elif status in IN_PROGRESS:
progress.append(m)
elif status in DONE:
done.append(m)
# Trier chaque catégorie par date de création (anciens d’abord)
for lst in (failed, progress, done):
lst.sort(key=lambda x: x.get("uploadDate", 0))
# Retourner tous les magnets dans l'ordre priorité : failed → progress → done
return failed + progress + done
# ----------------------------------------
# Fonction principale
# ----------------------------------------
def main():
"""
Logique principale :
- Récupère tous les magnets
- Vérifie les seuils
- Applique le mode urgence si nécessaire
- Supprime les magnets selon les règles
"""
magnets = get_magnets()
total = len(magnets)
print(f"Total magnets : {total}, Dry-run : {DRY_RUN}")
# Mode urgence
if total > EMERGENCY_THRESHOLD:
print("🚨 MODE URGENCE activé")
ordered = bucketize(magnets, ignore_age=True)
to_delete = ordered[:EMERGENCY_DELETE]
# Mode normal
elif total > THRESHOLD_HIGH:
print("Mode normal")
ordered = bucketize(magnets, ignore_age=False)
max_deletions = min(MAX_DELETE, total - THRESHOLD_LOW)
to_delete = ordered[:max_deletions]
else:
print("Seuil non atteint → rien à faire.")
return
# Supprimer les magnets sélectionnés
for m in to_delete:
mid = m.get("id")
name = m.get("filename") or m.get("name") or ""
status = m.get("status")
print(f"→ Suppression {status} : {name} (id={mid})")
delete_magnet(mid)
print("✨ Purge terminée.")
# ----------------------------------------
# Exécution du script
# ----------------------------------------
if __name__ == "__main__":
main()
Comme il le suggère, je le fais tourner avec un Docker Alpine. Chaque nuit à 3h.
services:
alldebrid-purger:
image: python:3.12-alpine
container_name: alldebrid-purger
restart: always
working_dir: /app
command: >
sh -c "
apk add --no-cache py3-pip &&
pip install --no-cache-dir requests &&
echo '0 3 * * * python /app/purge_alldebrid_magnets.py >> /logs/purge.log 2>&1'
> /etc/crontabs/root &&
crond -f -d 8
"
volumes:
- /mnt/Docker/alldebrid_purger:/app:ro
- /mnt/Docker/alldebrid_purger/logs:/logs
![]()

