| 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 |
|
|
| |
| |
| |
| |
| 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") |
|
|
| |
| 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" |
| ) |
|
|
| |
| app.mount("/static", StaticFiles(directory="app/static"), name="static") |
|
|
| |
| 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 |
|
|
| |
| 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}") |
|
|
| |
|
|
| @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() |
| |
| 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: |
| |
| qb.torrents_add(urls=link.magnet_link) |
| return StatusResponse( |
| status="success", |
| message=f"Successfully added magnet link to qBittorrent" |
| ) |
| except Exception as e: |
| |
| 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, |
| 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}") |
|
|