import logging import requests log = logging.getLogger(__name__) class QBitClient: def __init__(self, url, username, password): self.url = url.rstrip("/") self.username = username self.password = password self.session = requests.Session() self._logged_in = False def login(self): """Authenticate with qBittorrent and store the session cookie.""" resp = self.session.post( f"{self.url}/api/v2/auth/login", data={"username": self.username, "password": self.password}, ) resp.raise_for_status() if resp.text.strip() == "Ok.": self._logged_in = True log.info("Logged in to qBittorrent at %s", self.url) else: raise RuntimeError(f"qBit login failed: {resp.text}") def _request(self, method, endpoint, **kwargs): """Make an API request, re-authenticating on 403.""" if not self._logged_in: self.login() resp = self.session.request(method, f"{self.url}{endpoint}", **kwargs) if resp.status_code == 403: log.info("Session expired, re-authenticating") self.login() resp = self.session.request(method, f"{self.url}{endpoint}", **kwargs) resp.raise_for_status() return resp def get_torrents(self, filter=None, category=None): """Return a list of torrent dicts, optionally filtered by state and/or category.""" params = {} if filter: params["filter"] = filter if category: params["category"] = category resp = self._request("GET", "/api/v2/torrents/info", params=params) return resp.json() def get_torrent_files(self, info_hash): """Return the file list for a specific torrent.""" resp = self._request("GET", "/api/v2/torrents/files", params={"hash": info_hash}) return resp.json()