Files
seanime-docker/seanime-2.9.10/internal/torrent_clients/qbittorrent/client.go
2025-09-20 14:08:38 +01:00

163 lines
4.1 KiB
Go

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
}