Files
ncore-hnr/internal/qbit/client.go
Zsolt Alföldi ecea084003 Add support for notification dry run feature
- Introduced NOTIFICATION_DRY_RUN configuration option in .env.example and k8s/secret.example.yaml.
- Updated README.md to include usage instructions for the new dry run feature.
- Implemented logic in app.go to preview notifications without sending them when the dry run option is enabled.
- Enhanced config.go to load the new configuration option and validate notification types accordingly.
- Added a new function in notify.go to generate manual-needed notification messages for preview.
2026-05-07 00:35:05 +02:00

122 lines
2.9 KiB
Go

package qbit
import (
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/cookiejar"
"net/url"
"regexp"
"strings"
"ncore-hnr/internal/model"
)
var releaseSeparator = regexp.MustCompile(`[._\-\[\]()+]+`)
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 {
torrentName := normalizeName(torrent.Name)
if torrentName == normalized || strings.HasPrefix(torrentName, 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 {
value = releaseSeparator.ReplaceAllString(value, " ")
return strings.ToLower(strings.Join(strings.Fields(value), " "))
}