package qbit import ( "encoding/json" "fmt" "io" "net/http" "net/http/cookiejar" "net/url" "strings" "ncore-hnr/internal/model" ) type Client struct { baseURL string httpClient *http.Client } func New(baseURL string, httpClient *http.Client) *Client { if httpClient == nil { jar, _ := cookiejar.New(nil) httpClient = &http.Client{Jar: jar} } return &Client{baseURL: strings.TrimRight(baseURL, "/"), httpClient: httpClient} } func (c *Client) Login(username string, password string) error { values := url.Values{} values.Set("username", username) values.Set("password", password) resp, err := c.httpClient.PostForm(c.endpoint("/api/v2/auth/login"), values) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("qBittorrent login returned %s", resp.Status) } body, err := io.ReadAll(resp.Body) if err != nil { return err } if strings.TrimSpace(string(body)) != "Ok." { return fmt.Errorf("qBittorrent login failed") } return nil } func (c *Client) Torrents() ([]model.QBitTorrent, error) { resp, err := c.httpClient.Get(c.endpoint("/api/v2/torrents/info")) if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return nil, fmt.Errorf("qBittorrent torrent list returned %s", resp.Status) } var torrents []model.QBitTorrent if err := json.NewDecoder(resp.Body).Decode(&torrents); err != nil { return nil, err } return torrents, nil } func (c *Client) ForceStart(hash string) error { values := url.Values{} values.Set("hashes", hash) values.Set("value", "true") return c.postOK("/api/v2/torrents/setForceStart", values) } func (c *Client) Reannounce(hash string) error { values := url.Values{} values.Set("hashes", hash) return c.postOK("/api/v2/torrents/reannounce", values) } func MatchByName(ncoreName string, torrents []model.QBitTorrent) (model.QBitTorrent, bool) { for _, torrent := range torrents { if torrent.Name == ncoreName { return torrent, true } } normalized := normalizeName(ncoreName) for _, torrent := range torrents { if normalizeName(torrent.Name) == normalized { return torrent, true } } return model.QBitTorrent{}, false } func (c *Client) postOK(path string, values url.Values) error { resp, err := c.httpClient.PostForm(c.endpoint(path), values) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("qBittorrent %s returned %s", path, resp.Status) } return nil } func (c *Client) endpoint(path string) string { return c.baseURL + path } func normalizeName(value string) string { return strings.ToLower(strings.Join(strings.Fields(value), " ")) }