package qbittorrent import ( "fmt" "github.com/rs/zerolog" "net/http" "net/http/cookiejar" "net/url" "seanime/internal/torrent_clients/qbittorrent/application" "seanime/internal/torrent_clients/qbittorrent/log" "seanime/internal/torrent_clients/qbittorrent/rss" "seanime/internal/torrent_clients/qbittorrent/search" "seanime/internal/torrent_clients/qbittorrent/sync" "seanime/internal/torrent_clients/qbittorrent/torrent" "seanime/internal/torrent_clients/qbittorrent/transfer" "strings" "golang.org/x/net/publicsuffix" ) type Client struct { baseURL string logger *zerolog.Logger client *http.Client Username string Password string Port int Host string Path string DisableBinaryUse bool Tags string Application qbittorrent_application.Client Log qbittorrent_log.Client RSS qbittorrent_rss.Client Search qbittorrent_search.Client Sync qbittorrent_sync.Client Torrent qbittorrent_torrent.Client Transfer qbittorrent_transfer.Client } type NewClientOptions struct { Logger *zerolog.Logger Username string Password string Port int Host string Path string DisableBinaryUse bool Tags string } func NewClient(opts *NewClientOptions) *Client { baseURL := fmt.Sprintf("http://%s:%d/api/v2", opts.Host, opts.Port) if strings.HasPrefix(opts.Host, "https://") { opts.Host = strings.TrimPrefix(opts.Host, "https://") baseURL = fmt.Sprintf("https://%s:%d/api/v2", opts.Host, opts.Port) } client := &http.Client{} return &Client{ baseURL: baseURL, logger: opts.Logger, client: client, Username: opts.Username, Password: opts.Password, Port: opts.Port, Path: opts.Path, DisableBinaryUse: opts.DisableBinaryUse, Host: opts.Host, Tags: opts.Tags, Application: qbittorrent_application.Client{ BaseUrl: baseURL + "/app", Client: client, Logger: opts.Logger, }, Log: qbittorrent_log.Client{ BaseUrl: baseURL + "/log", Client: client, Logger: opts.Logger, }, RSS: qbittorrent_rss.Client{ BaseUrl: baseURL + "/rss", Client: client, Logger: opts.Logger, }, Search: qbittorrent_search.Client{ BaseUrl: baseURL + "/search", Client: client, Logger: opts.Logger, }, Sync: qbittorrent_sync.Client{ BaseUrl: baseURL + "/sync", Client: client, Logger: opts.Logger, }, Torrent: qbittorrent_torrent.Client{ BaseUrl: baseURL + "/torrents", Client: client, Logger: opts.Logger, }, Transfer: qbittorrent_transfer.Client{ BaseUrl: baseURL + "/transfer", Client: client, Logger: opts.Logger, }, } } func (c *Client) Login() error { endpoint := c.baseURL + "/auth/login" data := url.Values{} data.Add("username", c.Username) data.Add("password", c.Password) request, err := http.NewRequest("POST", endpoint, strings.NewReader(data.Encode())) if err != nil { return err } request.Header.Add("content-type", "application/x-www-form-urlencoded") resp, err := c.client.Do(request) if err != nil { return err } defer func() { if err := resp.Body.Close(); err != nil { c.logger.Err(err).Msg("failed to close login response body") } }() if resp.StatusCode != 200 { return fmt.Errorf("invalid status %s", resp.Status) } if len(resp.Cookies()) < 1 { return fmt.Errorf("no cookies in login response") } apiURL, err := url.Parse(c.baseURL) if err != nil { return err } jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) if err != nil { return err } jar.SetCookies(apiURL, []*http.Cookie{resp.Cookies()[0]}) c.client.Jar = jar return nil } func (c *Client) Logout() error { endpoint := c.baseURL + "/auth/logout" request, err := http.NewRequest("POST", endpoint, nil) if err != nil { return err } resp, err := c.client.Do(request) if err != nil { return err } if resp.StatusCode != 200 { return fmt.Errorf("invalid status %s", resp.Status) } return nil }