trrr14 / main.py
Fred808's picture
Upload 5 files
2a09f27 verified
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}")