class

QBittorrent::Client

Inherits Reference < Object

HTTP client for the qBittorrent WebUI API v2 (qBittorrent 5.x).

Authentication is cookie/session based, not an API key. On the first authenticated request (or an explicit #login) the client posts the credentials to auth/login, captures the session cookie, and attaches it — together with a Referer/Origin header matching the server host — to every subsequent call.

The session-cookie name is dynamic on real 5.x builds: it is QBT_SID_<port> (e.g. QBT_SID_8080), not the SID name older docs describe. The client therefore stores both the cookie name and value and echoes them verbatim. Sessions expire (server default 3600 s); a 403 from an authenticated call triggers a single transparent re-login + retry.

client = QBittorrent::Client.new("http://localhost:8080", "admin", "adminadmin")
body = client.request_text("GET", "app/version") # => "v5.2.3"
client.logout

Endpoint groups (app, torrents, ...) are built on top of the request helpers below (#request, #request_text, #request_json).

Constants

BASE_PATH = "/api/v2/"

Common path prefix for every WebUI API v2 method.

Constructors

new(base_url : String, username : String, password : String)

Creates a client for the qBittorrent instance at base_url (e.g. http://host:8080 or https://host). Credentials are used lazily: no network call happens until the first request or an explicit #login.

Source

Class methods

build_multipart(form : Hash(String, String) | Nil = nil, files : Hash(String, MultipartFile) | Nil = nil) : Tuple(String, Bytes)

Builds a multipart/form-data body from form fields and files parts. Returns {content_type, body} where content_type is the multipart/form-data; boundary=... header value and body is the encoded bytes. Pure (no I/O beyond the in-memory buffer) so the encoding is unit-testable without a server.

Source
build_request_path(path : String, query : Hash(String, String) | Nil = nil) : String

Builds the full request path (/api/v2/<path> plus an optional encoded query string). path may be given with or without a leading slash.

Source
interpret_login(status_code : Int32, body : String, set_cookies : Array(String)) : Tuple(String, String)

Interprets a raw auth/login response. Returns the session cookie as {name, value} on success; raises AuthError for every failure mode. Pure (no I/O) so the auth decision logic is unit-testable without a server.

Success is any 2xx status (real 5.x: 204, empty body; legacy: 200 Ok.) that also sets a session cookie — the Ok. body is NOT required. Failures: HTTP 401 or legacy 200 Fails. (bad credentials); HTTP 403 (banned IP / host-header check); a 2xx with no cookie.

Source
origin_for(uri : URI) : String

Derives the Referer/Origin value (scheme://host[:port]) from uri.

Source
origin_for(base_url : String) : String

Derives the Referer/Origin value (scheme://host[:port]) from uri.

Source

Instance methods

logged_in?

Whether a session cookie is currently held.

Source
login

Performs POST auth/login, storing the session cookie on success.

Success on real 5.x is HTTP 204 with an empty body that sets a QBT_SID_<port> cookie; older builds answer 200 Ok.. Both are accepted.

Raises AuthError on wrong credentials (HTTP 401, or the legacy 200 Fails.), a 2xx response with no session cookie, or HTTP 403 (banned IP / failed host-header validation).

Source
logout

Performs POST auth/logout (if a session is held) and clears the session.

Source
origin

The Referer/Origin value sent on every request (server host).

Source
request(method : String, path : String, query : Hash(String, String) | Nil = nil, form : Hash(String, String) | Nil = nil) : HTTP::Client::Response

Core request primitive used by the endpoint groups.

Ensures a session exists (logging in on demand), attaches the session cookie and Referer/Origin headers, sends query as a URL query string and form as an application/x-www-form-urlencoded body, and returns the raw HTTP::Client::Response.

On a 403 (expired/invalidated session) it re-logs-in once and retries. Any remaining non-2xx response raises ApiError carrying the status code and body.

Source
request_json(type : T.class, method : String, path : String, query : Hash(String, String) | Nil = nil, form : Hash(String, String) | Nil = nil) : T forall T

Like #request but deserializes the JSON body into type.

info = client.request_json(BuildInfo, "GET", "app/buildInfo")
Source
request_multipart(method : String, path : String, form : Hash(String, String) | Nil = nil, files : Hash(String, MultipartFile) | Nil = nil) : HTTP::Client::Response

Like #request but sends a multipart/form-data body — required by endpoints that accept file uploads (notably torrents/add with .torrent files). form carries the plain text fields and files the file parts as fieldname => {filename:, content:}. Shares the session/login handling of #request (a 403 triggers a single re-login + retry) and raises ApiError on any non-2xx response.

Source
request_text(method : String, path : String, query : Hash(String, String) | Nil = nil, form : Hash(String, String) | Nil = nil) : String

Like #request but returns the response body as text. Action endpoints return an empty string on success.

Source

Nested types