File size: 6,126 Bytes
2a09f27 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | import os
from fastapi import FastAPI, HTTPException
from fastapi.staticfiles import StaticFiles
from fastapi.responses import FileResponse
from pydantic import BaseModel
from qbittorrent import Client
from typing import List, Optional
# --- Configuration ---
# The qBittorrent-nox daemon is started on port 8080 inside the container.
# The default credentials for qbittorrent-nox are admin/adminadmin.
# In a real-world scenario, these should be securely managed.
QB_HOST = os.getenv("QB_HOST", "localhost")
QB_PORT = os.getenv("QB_PORT", "8080")
QB_USER = os.getenv("QB_USER", "admin")
QB_PASS = os.getenv("QB_PASS", "adminadmin")
# --- FastAPI App Initialization ---
app = FastAPI(
title="qBittorrent Magnet Link API",
description="A simple FastAPI service to add magnet links to a running qBittorrent instance with download tracking.",
version="2.0.0"
)
# Mount static files (frontend)
app.mount("/static", StaticFiles(directory="app/static"), name="static")
# --- Pydantic Models ---
class MagnetLink(BaseModel):
magnet_link: str
class StatusResponse(BaseModel):
status: str
message: str
class TorrentInfo(BaseModel):
name: str
hash: str
state: str
progress: float
downloaded: int
total_size: int
upload_speed: int
download_speed: int
eta: int
num_seeds: int
num_leechs: int
class TorrentListResponse(BaseModel):
torrents: List[TorrentInfo]
total_count: int
# --- Utility Function for qBittorrent Client ---
def get_qb_client():
"""Initializes and logs into the qBittorrent client."""
try:
qb = Client(f'http://{QB_HOST}:{QB_PORT}')
qb.login(QB_USER, QB_PASS)
return qb
except Exception as e:
raise HTTPException(status_code=503, detail=f"Could not connect or log in to qBittorrent: {e}")
# --- Endpoints ---
@app.get("/", response_class=FileResponse)
async def serve_frontend():
"""Serves the main HTML frontend."""
return FileResponse("app/static/index.html")
@app.get("/health", response_model=StatusResponse)
async def health_check():
"""Checks the health of the FastAPI service and qBittorrent connection."""
try:
qb = get_qb_client()
# A simple call to verify connection and authentication
version = qb.app.version
return StatusResponse(status="ok", message=f"FastAPI is running and connected to qBittorrent v{version}")
except HTTPException as e:
raise e
except Exception as e:
raise HTTPException(status_code=500, detail=f"Internal server error during health check: {e}")
@app.post("/api/add_torrent", response_model=StatusResponse)
async def add_torrent(link: MagnetLink):
"""Adds a magnet link to the qBittorrent download queue."""
qb = get_qb_client()
try:
# The add_torrent method handles both magnet links and .torrent files
qb.torrents_add(urls=link.magnet_link)
return StatusResponse(
status="success",
message=f"Successfully added magnet link to qBittorrent"
)
except Exception as e:
# Log the error and return a user-friendly message
print(f"Error adding torrent: {e}")
raise HTTPException(status_code=500, detail=f"Failed to add torrent: {e}")
@app.get("/api/torrents", response_model=TorrentListResponse)
async def get_torrents():
"""Fetches the list of all torrents with their current status."""
qb = get_qb_client()
try:
torrents = qb.torrents()
torrent_list = []
for torrent in torrents:
torrent_info = TorrentInfo(
name=torrent.get('name', 'Unknown'),
hash=torrent.get('hash', ''),
state=torrent.get('state', 'unknown'),
progress=torrent.get('progress', 0) * 100, # Convert to percentage
downloaded=torrent.get('downloaded', 0),
total_size=torrent.get('total_size', 0),
upload_speed=torrent.get('upspeed', 0),
download_speed=torrent.get('dlspeed', 0),
eta=torrent.get('eta', 0),
num_seeds=torrent.get('num_seeds', 0),
num_leechs=torrent.get('num_leechs', 0),
)
torrent_list.append(torrent_info)
return TorrentListResponse(torrents=torrent_list, total_count=len(torrent_list))
except Exception as e:
print(f"Error fetching torrents: {e}")
raise HTTPException(status_code=500, detail=f"Failed to fetch torrents: {e}")
@app.delete("/api/torrents/{torrent_hash}")
async def delete_torrent(torrent_hash: str, delete_files: bool = False):
"""Deletes a torrent from the qBittorrent instance."""
qb = get_qb_client()
try:
qb.torrents_delete(torrent_hashes=torrent_hash, delete_files=delete_files)
return StatusResponse(
status="success",
message=f"Successfully deleted torrent"
)
except Exception as e:
print(f"Error deleting torrent: {e}")
raise HTTPException(status_code=500, detail=f"Failed to delete torrent: {e}")
@app.post("/api/torrents/{torrent_hash}/pause")
async def pause_torrent(torrent_hash: str):
"""Pauses a torrent."""
qb = get_qb_client()
try:
qb.torrents_pause(torrent_hashes=torrent_hash)
return StatusResponse(
status="success",
message=f"Successfully paused torrent"
)
except Exception as e:
print(f"Error pausing torrent: {e}")
raise HTTPException(status_code=500, detail=f"Failed to pause torrent: {e}")
@app.post("/api/torrents/{torrent_hash}/resume")
async def resume_torrent(torrent_hash: str):
"""Resumes a torrent."""
qb = get_qb_client()
try:
qb.torrents_resume(torrent_hashes=torrent_hash)
return StatusResponse(
status="success",
message=f"Successfully resumed torrent"
)
except Exception as e:
print(f"Error resuming torrent: {e}")
raise HTTPException(status_code=500, detail=f"Failed to resume torrent: {e}")
|