1530 lines
50 KiB
Go
1530 lines
50 KiB
Go
package qbittorrent
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"golang.org/x/exp/constraints"
|
|
)
|
|
|
|
type Torrent interface {
|
|
// GetTorrents get torrent list
|
|
GetTorrents(ctx context.Context, opt *TorrentOption) ([]*TorrentInfo, error)
|
|
// GetProperties get torrent generic properties
|
|
GetProperties(ctx context.Context, hash string) (*TorrentProperties, error)
|
|
// GetTrackers get torrent trackers
|
|
GetTrackers(ctx context.Context, hash string) ([]*TorrentTracker, error)
|
|
// GetWebSeeds get torrent web seeds
|
|
GetWebSeeds(ctx context.Context, hash string) ([]*TorrentWebSeed, error)
|
|
// GetContents get torrent contents, indexes(optional) of the files you want to retrieve
|
|
GetContents(ctx context.Context, hash string, indexes ...int) ([]*TorrentContent, error)
|
|
// GetPiecesStates get torrent pieces states
|
|
GetPiecesStates(ctx context.Context, hash string) ([]int, error)
|
|
// GetPiecesHashes get torrent pieces hashes
|
|
GetPiecesHashes(ctx context.Context, hash string) ([]string, error)
|
|
// PauseTorrents the hashes of the torrents you want to pause
|
|
PauseTorrents(ctx context.Context, hashes []string) error
|
|
// ResumeTorrents the hashes of the torrents you want to resume
|
|
ResumeTorrents(ctx context.Context, hashes []string) error
|
|
// DeleteTorrents the hashes of the torrents you want to delete, if set deleteFile to true,
|
|
// the downloaded data will also be deleted, otherwise has no effect.
|
|
DeleteTorrents(ctx context.Context, hashes []string, deleteFile bool) error
|
|
// RecheckTorrents the hashes of the torrents you want to recheck
|
|
RecheckTorrents(ctx context.Context, hashes []string) error
|
|
// ReAnnounceTorrents the hashes of the torrents you want to reannounce
|
|
ReAnnounceTorrents(ctx context.Context, hashes []string) error
|
|
// AddNewTorrent add torrents from server local file or from URLs. http://, https://,
|
|
// magnet: and bc://bt/ links are supported, but only one onetime
|
|
AddNewTorrent(ctx context.Context, opt *TorrentAddOption) error
|
|
// AddTrackers add trackers to torrent
|
|
AddTrackers(ctx context.Context, hash string, urls []string) error
|
|
// EditTrackers edit trackers
|
|
EditTrackers(ctx context.Context, hash, origUrl, newUrl string) error
|
|
// RemoveTrackers remove trackers
|
|
RemoveTrackers(ctx context.Context, hash string, urls []string) error
|
|
// AddPeers add peers for torrent, each peer is host:port
|
|
AddPeers(ctx context.Context, hashes []string, peers []string) error
|
|
// IncreasePriority increase torrent priority
|
|
IncreasePriority(ctx context.Context, hashes []string) error
|
|
// DecreasePriority decrease torrent priority
|
|
DecreasePriority(ctx context.Context, hashes []string) error
|
|
// MaxPriority maximal torrent priority
|
|
MaxPriority(ctx context.Context, hashes []string) error
|
|
// MinPriority minimal torrent priority
|
|
MinPriority(ctx context.Context, hashes []string) error
|
|
// SetFilePriority set file priority
|
|
SetFilePriority(ctx context.Context, hash string, id int, priority Priority) error
|
|
// GetDownloadLimit get torrent download limit
|
|
GetDownloadLimit(ctx context.Context, hashes []string) (map[string]int, error)
|
|
// SetDownloadLimit set torrent download limit, limit in bytes per second, if no limit please set value zero
|
|
SetDownloadLimit(ctx context.Context, hashes []string, limit int) error
|
|
// SetShareLimit set torrent share limit, ratioLimit: the maximum seeding ratio for the torrent, -2 means the
|
|
// global limit should be used, -1 means no limit; seedingTimeLimit: the maximum seeding time (minutes) for the
|
|
// torrent, -2 means the global limit should be used, -1 means no limit; inactiveSeedingTimeLimit: the maximum
|
|
// amount of time (minutes) the torrent is allowed to seed while being inactive, -2 means the global limit should
|
|
// be used, -1 means no limit.
|
|
SetShareLimit(ctx context.Context, hashes []string, ratioLimit float64, seedingTimeLimit, inactiveSeedingTimeLimit int) error
|
|
// GetUploadLimit get torrent upload limit
|
|
GetUploadLimit(ctx context.Context, hashes []string) (map[string]int, error)
|
|
// SetUploadLimit set torrent upload limit
|
|
SetUploadLimit(ctx context.Context, hashes []string, limit int) error
|
|
// SetLocation set torrent location
|
|
SetLocation(ctx context.Context, hashes []string, location string) error
|
|
// SetName set torrent name
|
|
SetName(ctx context.Context, hash string, name string) error
|
|
// SetCategory set torrent category
|
|
SetCategory(ctx context.Context, hashes []string, category string) error
|
|
// GetCategories get all categories
|
|
GetCategories(ctx context.Context) (map[string]*TorrentCategory, error)
|
|
// AddNewCategory add new category
|
|
AddNewCategory(ctx context.Context, category, savePath string) error
|
|
// EditCategory edit category
|
|
EditCategory(ctx context.Context, category, savePath string) error
|
|
// RemoveCategories remove categories
|
|
RemoveCategories(ctx context.Context, categories []string) error
|
|
// AddTags add torrent tags
|
|
AddTags(ctx context.Context, hashes []string, tags []string) error
|
|
// RemoveTags remove torrent tags
|
|
RemoveTags(ctx context.Context, hashes []string, tags []string) error
|
|
// GetTags get all tags
|
|
GetTags(ctx context.Context) ([]string, error)
|
|
// CreateTags create tags
|
|
CreateTags(ctx context.Context, tags []string) error
|
|
// DeleteTags delete tags
|
|
DeleteTags(ctx context.Context, tags []string) error
|
|
// SetAutomaticManagement set automatic torrent management
|
|
SetAutomaticManagement(ctx context.Context, hashes []string, enable bool) error
|
|
// ToggleSequentialDownload toggle sequential download
|
|
ToggleSequentialDownload(ctx context.Context, hashes []string) error
|
|
// SetFirstLastPiecePriority set first/last piece priority
|
|
SetFirstLastPiecePriority(ctx context.Context, hashes []string) error
|
|
// SetForceStart set force start
|
|
SetForceStart(ctx context.Context, hashes []string, force bool) error
|
|
// SetSuperSeeding set super seeding
|
|
SetSuperSeeding(ctx context.Context, hashes []string, enable bool) error
|
|
// RenameFile rename file
|
|
RenameFile(ctx context.Context, hash, oldPath, newPath string) error
|
|
// RenameFolder rename folder
|
|
RenameFolder(ctx context.Context, hash, oldPath, newPath string) error
|
|
}
|
|
|
|
type TorrentOption struct {
|
|
// Filter torrent list by state. Allowed state filters: all,downloading,seeding,completed,paused,
|
|
// active,inactive,resumed,stalled,stalled_uploading,stalled_downloading,errored
|
|
Filter string `schema:"filter,omitempty"`
|
|
// Category get torrents with the given category, empty string means "without category"; no "category"
|
|
// parameter means "any category"
|
|
Category string `schema:"category,omitempty"`
|
|
// Tag get torrents with the given tag, empty string means "without tag"; no "tag" parameter means "any tag"
|
|
Tag string `schema:"tag,omitempty"`
|
|
// Sort torrents by given key, they can be sorted using any field of the response's JSON array (which are documented below) as the sort key.
|
|
Sort string `schema:"sort,omitempty"`
|
|
// Reverse enable reverse sorting. Defaults to false
|
|
Reverse bool `schema:"reverse,omitempty"`
|
|
// Limit the number of torrents returned
|
|
Limit int `schema:"limit,omitempty"`
|
|
// Offset set offset (if less than 0, offset from end)
|
|
Offset int `schema:"offset,omitempty"`
|
|
// Hashes filter by hashes
|
|
Hashes []string `schema:"-"`
|
|
}
|
|
|
|
type TorrentState string
|
|
|
|
const (
|
|
TorrentStateError TorrentState = "error"
|
|
TorrentStateMissingFiles TorrentState = "missingFiles"
|
|
TorrentStateUploading TorrentState = "uploading"
|
|
TorrentStatePausedUP TorrentState = "pausedUP"
|
|
TorrentStateQueuedUP TorrentState = "queuedUP"
|
|
TorrentStateStalledUP TorrentState = "stalledUP"
|
|
TorrentStateCheckingUP TorrentState = "checkingUP"
|
|
TorrentStateForcedUP TorrentState = "forcedUP"
|
|
TorrentStateAllocating TorrentState = "allocating"
|
|
TorrentStateDownloading TorrentState = "downloading"
|
|
TorrentStateMetaDL TorrentState = "metaDL"
|
|
TorrentStatePausedDL TorrentState = "pausedDL"
|
|
TorrentStateQueuedDL TorrentState = "queuedDL"
|
|
TorrentStateStalledDL TorrentState = "stalledDL"
|
|
TorrentStateCheckingDL TorrentState = "checkingDL"
|
|
TorrentStateForcedDL TorrentState = "forcedDL"
|
|
TorrentStateCheckingResumeData TorrentState = "checkingResumeData"
|
|
TorrentStateMoving TorrentState = "moving"
|
|
TorrentStateUnknown TorrentState = "unknown"
|
|
)
|
|
|
|
type TorrentInfo struct {
|
|
AddedOn int `json:"added_on"`
|
|
AmountLeft int `json:"amount_left"`
|
|
AutoTmm bool `json:"auto_tmm"`
|
|
Availability float64 `json:"availability"`
|
|
Category string `json:"category"`
|
|
Completed int `json:"completed"`
|
|
CompletionOn int `json:"completion_on"`
|
|
ContentPath string `json:"content_path"`
|
|
DlLimit int `json:"dl_limit"`
|
|
Dlspeed int `json:"dlspeed"`
|
|
DownloadPath string `json:"download_path"`
|
|
Downloaded int `json:"downloaded"`
|
|
DownloadedSession int `json:"downloaded_session"`
|
|
Eta int `json:"eta"`
|
|
FLPiecePrio bool `json:"f_l_piece_prio"`
|
|
ForceStart bool `json:"force_start"`
|
|
Hash string `json:"hash"`
|
|
InactiveSeedingTimeLimit int `json:"inactive_seeding_time_limit"`
|
|
InfohashV1 string `json:"infohash_v1"`
|
|
InfohashV2 string `json:"infohash_v2"`
|
|
LastActivity int `json:"last_activity"`
|
|
MagnetURI string `json:"magnet_uri"`
|
|
MaxInactiveSeedingTime int `json:"max_inactive_seeding_time"`
|
|
MaxRatio int `json:"max_ratio"`
|
|
MaxSeedingTime int `json:"max_seeding_time"`
|
|
Name string `json:"name"`
|
|
NumComplete int `json:"num_complete"`
|
|
NumIncomplete int `json:"num_incomplete"`
|
|
NumLeechs int `json:"num_leechs"`
|
|
NumSeeds int `json:"num_seeds"`
|
|
Priority int `json:"priority"`
|
|
Progress float64 `json:"progress"`
|
|
Ratio float64 `json:"ratio"`
|
|
RatioLimit int `json:"ratio_limit"`
|
|
SavePath string `json:"save_path"`
|
|
SeedingTime int `json:"seeding_time"`
|
|
SeedingTimeLimit int `json:"seeding_time_limit"`
|
|
SeenComplete int `json:"seen_complete"`
|
|
SeqDl bool `json:"seq_dl"`
|
|
Size int `json:"size"`
|
|
State TorrentState `json:"state"`
|
|
SuperSeeding bool `json:"super_seeding"`
|
|
Tags string `json:"tags"`
|
|
TimeActive int `json:"time_active"`
|
|
TotalSize int `json:"total_size"`
|
|
Tracker string `json:"tracker"`
|
|
TrackersCount int `json:"trackers_count"`
|
|
UpLimit int `json:"up_limit"`
|
|
Uploaded int `json:"uploaded"`
|
|
UploadedSession int `json:"uploaded_session"`
|
|
Upspeed int `json:"upspeed"`
|
|
}
|
|
|
|
type TorrentProperties struct {
|
|
AdditionDate int `json:"addition_date,omitempty"`
|
|
Comment string `json:"comment,omitempty"`
|
|
CompletionDate int `json:"completion_date,omitempty"`
|
|
CreatedBy string `json:"created_by,omitempty"`
|
|
CreationDate int `json:"creation_date,omitempty"`
|
|
DlLimit int `json:"dl_limit,omitempty"`
|
|
DlSpeed int `json:"dl_speed,omitempty"`
|
|
DlSpeedAvg int `json:"dl_speed_avg,omitempty"`
|
|
DownloadPath string `json:"download_path,omitempty"`
|
|
Eta int `json:"eta,omitempty"`
|
|
Hash string `json:"hash,omitempty"`
|
|
InfohashV1 string `json:"infohash_v1,omitempty"`
|
|
InfohashV2 string `json:"infohash_v2,omitempty"`
|
|
IsPrivate bool `json:"is_private,omitempty"`
|
|
LastSeen int `json:"last_seen,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
NbConnections int `json:"nb_connections,omitempty"`
|
|
NbConnectionsLimit int `json:"nb_connections_limit,omitempty"`
|
|
Peers int `json:"peers,omitempty"`
|
|
PeersTotal int `json:"peers_total,omitempty"`
|
|
PieceSize int `json:"piece_size,omitempty"`
|
|
PiecesHave int `json:"pieces_have,omitempty"`
|
|
PiecesNum int `json:"pieces_num,omitempty"`
|
|
Reannounce int `json:"reannounce,omitempty"`
|
|
SavePath string `json:"save_path,omitempty"`
|
|
SeedingTime int `json:"seeding_time,omitempty"`
|
|
Seeds int `json:"seeds,omitempty"`
|
|
SeedsTotal int `json:"seeds_total,omitempty"`
|
|
ShareRatio float64 `json:"share_ratio,omitempty"`
|
|
TimeElapsed int `json:"time_elapsed,omitempty"`
|
|
TotalDownloaded int64 `json:"total_downloaded,omitempty"`
|
|
TotalDownloadedSession int64 `json:"total_downloaded_session,omitempty"`
|
|
TotalSize int64 `json:"total_size,omitempty"`
|
|
TotalUploaded int64 `json:"total_uploaded,omitempty"`
|
|
TotalUploadedSession int64 `json:"total_uploaded_session,omitempty"`
|
|
TotalWasted int `json:"total_wasted,omitempty"`
|
|
UpLimit int `json:"up_limit,omitempty"`
|
|
UpSpeed int `json:"up_speed,omitempty"`
|
|
UpSpeedAvg int `json:"up_speed_avg,omitempty"`
|
|
}
|
|
|
|
type TorrentTracker struct {
|
|
Msg string `json:"msg,omitempty"`
|
|
NumDownloaded int `json:"num_downloaded,omitempty"`
|
|
NumLeeches int `json:"num_leeches,omitempty"`
|
|
NumPeers int `json:"num_peers,omitempty"`
|
|
NumSeeds int `json:"num_seeds,omitempty"`
|
|
Status int `json:"status,omitempty"`
|
|
Tier int `json:"tier,omitempty"`
|
|
URL string `json:"url,omitempty"`
|
|
}
|
|
|
|
type TorrentWebSeed struct {
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
type Priority int
|
|
|
|
const (
|
|
PriorityDoNotDownload Priority = 0
|
|
PriorityNormal Priority = 1
|
|
PriorityHigh Priority = 6
|
|
PriorityMax Priority = 7
|
|
)
|
|
|
|
type TorrentContent struct {
|
|
Availability float64 `json:"availability,omitempty"`
|
|
Index int `json:"index,omitempty"`
|
|
IsSeed bool `json:"is_seed,omitempty"`
|
|
Name string `json:"name,omitempty"`
|
|
PieceRange []int `json:"piece_range,omitempty"`
|
|
Priority Priority `json:"priority,omitempty"`
|
|
Progress float64 `json:"progress,omitempty"`
|
|
Size int64 `json:"size,omitempty"`
|
|
}
|
|
|
|
type TorrentAddFileMetadata struct {
|
|
// Filename only used to distinguish two files in form-data, does not work on the server side,
|
|
// for different files, please give different identification names
|
|
Filename string
|
|
// Data read torrent file content and set to here
|
|
Data []byte
|
|
}
|
|
|
|
type TorrentAddOption struct {
|
|
URLs []string `schema:"-"` // torrents url
|
|
Torrents []*TorrentAddFileMetadata `schema:"-"` // raw data of torrent file
|
|
SavePath string `schema:"savepath,omitempty"` // download folder, optional
|
|
Cookies string `schema:"cookie,omitempty"` // cookie sent to download torrent file, optional
|
|
Category string `schema:"category,omitempty"` // category for the torrent, optional
|
|
Tags []string `schema:"-"` // tags for the torrent, optional
|
|
SkipChecking bool `schema:"skip_checking,omitempty"` // skip hash checking, optional
|
|
Paused bool `schema:"paused,omitempty"` // add torrent in the pause state, optional
|
|
RootFolder bool `schema:"root_folder,omitempty"` // create the root folder, optional
|
|
Rename string `schema:"rename,omitempty"` // rename torrent, optional
|
|
UpLimit int `schema:"upLimit,omitempty"` // set torrent upload speed, Unit in bytes/second, optional
|
|
DlLimit int `schema:"dlLimit,omitempty"` // set torrent download speed, Unit in bytes/second, optional
|
|
RatioLimit float64 `schema:"ratioLimit,omitempty"` // set torrent share ratio limit, optional
|
|
SeedingTimeLimit int `schema:"seedingTimeLimit,omitempty"` // set torrent seeding torrent limit, Unit in minutes, optional
|
|
AutoTMM bool `schema:"autoTMM,omitempty"` // whether Automatic Torrent Management should be used, optional
|
|
SequentialDownload string `schema:"sequentialDownload,omitempty"` // enable sequential download, optional
|
|
FirstLastPiecePrio string `schema:"firstLastPiecePrio,omitempty"` // prioritize download first last piece, optional
|
|
}
|
|
|
|
type TorrentCategory struct {
|
|
Name string `json:"name,omitempty"`
|
|
SavePath string `json:"savePath,omitempty"`
|
|
}
|
|
|
|
func (c *client) GetTorrents(ctx context.Context, opt *TorrentOption) ([]*TorrentInfo, error) {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.GetTorrents")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
err := encoder.Encode(opt, formData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(opt.Hashes) != 0 {
|
|
formData.Add("hashes", strings.Join(opt.Hashes, "|"))
|
|
}
|
|
|
|
apiUrl := fmt.Sprintf("%s/api/v2/torrents/info?%s", c.config.Address, formData.Encode())
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return nil, errors.New("get torrents failed: " + string(result.body))
|
|
}
|
|
|
|
var mainData []*TorrentInfo
|
|
if err := json.Unmarshal(result.body, &mainData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return mainData, nil
|
|
}
|
|
|
|
func (c *client) GetProperties(ctx context.Context, hash string) (*TorrentProperties, error) {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.GetProperties")
|
|
defer span.End()
|
|
|
|
apiUrl := fmt.Sprintf("%s/api/v2/torrents/properties?hash=%s", c.config.Address, hash)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return nil, errors.New("get torrent properties failed: " + string(result.body))
|
|
}
|
|
|
|
var mainData = new(TorrentProperties)
|
|
if err := json.Unmarshal(result.body, mainData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return mainData, nil
|
|
}
|
|
|
|
func (c *client) GetTrackers(ctx context.Context, hash string) ([]*TorrentTracker, error) {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.GetTrackers")
|
|
defer span.End()
|
|
|
|
apiUrl := fmt.Sprintf("%s/api/v2/torrents/trackers?hash=%s", c.config.Address, hash)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return nil, errors.New("get torrent trackers failed: " + string(result.body))
|
|
}
|
|
|
|
var mainData []*TorrentTracker
|
|
if err := json.Unmarshal(result.body, &mainData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return mainData, nil
|
|
}
|
|
|
|
func (c *client) GetWebSeeds(ctx context.Context, hash string) ([]*TorrentWebSeed, error) {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.GetWebSeeds")
|
|
defer span.End()
|
|
|
|
apiUrl := fmt.Sprintf("%s/api/v2/torrents/webseeds?hash=%s", c.config.Address, hash)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return nil, errors.New("get torrent web seeds failed: " + string(result.body))
|
|
}
|
|
|
|
var mainData []*TorrentWebSeed
|
|
if err := json.Unmarshal(result.body, &mainData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return mainData, nil
|
|
}
|
|
|
|
func sliceItoa[E constraints.Integer](in []E) []string {
|
|
out := make([]string, 0, len(in))
|
|
for _, v := range in {
|
|
out = append(out, strconv.FormatInt(int64(v), 10))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (c *client) GetContents(ctx context.Context, hash string, indexes ...int) ([]*TorrentContent, error) {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.GetContents")
|
|
defer span.End()
|
|
|
|
var apiUrl string
|
|
if len(indexes) != 0 {
|
|
|
|
apiUrl = fmt.Sprintf("%s/api/v2/torrents/files?hash=%s&indexes=%s", c.config.Address, hash, strings.Join(sliceItoa(indexes), "|"))
|
|
} else {
|
|
apiUrl = fmt.Sprintf("%s/api/v2/torrents/files?hash=%s", c.config.Address, hash)
|
|
}
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return nil, errors.New("get torrent web seeds failed: " + string(result.body))
|
|
}
|
|
|
|
var mainData []*TorrentContent
|
|
if err := json.Unmarshal(result.body, &mainData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return mainData, nil
|
|
}
|
|
|
|
func (c *client) GetPiecesStates(ctx context.Context, hash string) ([]int, error) {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.GetPiecesStates")
|
|
defer span.End()
|
|
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/pieceStates?hash=%s", c.config.Address, hash)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return nil, errors.New("get torrent pieces states failed: " + string(result.body))
|
|
}
|
|
|
|
var mainData []int
|
|
if err := json.Unmarshal(result.body, &mainData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return mainData, nil
|
|
}
|
|
|
|
func (c *client) GetPiecesHashes(ctx context.Context, hash string) ([]string, error) {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.GetPiecesHashes")
|
|
defer span.End()
|
|
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/pieceHashes?hash=%s", c.config.Address, hash)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return nil, errors.New("get torrent pieces states failed: " + string(result.body))
|
|
}
|
|
|
|
var mainData []string
|
|
if err := json.Unmarshal(result.body, &mainData); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return mainData, nil
|
|
}
|
|
|
|
func (c *client) PauseTorrents(ctx context.Context, hashes []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.PauseTorrents")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no torrent hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/pause", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("pause torrents failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) ResumeTorrents(ctx context.Context, hashes []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.ResumeTorrents")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no torrent hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/resume", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("resume torrents failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) DeleteTorrents(ctx context.Context, hashes []string, deleteFile bool) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.DeleteTorrents")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no torrent hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
formData.Add("deleteFile", strconv.FormatBool(deleteFile))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/resume", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("delete torrents failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) RecheckTorrents(ctx context.Context, hashes []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.RecheckTorrents")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no torrent hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/recheck", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("recheck torrents failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) ReAnnounceTorrents(ctx context.Context, hashes []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.ReAnnounceTorrents")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no torrent hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/reannounce", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("reannounce torrents failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) AddNewTorrent(ctx context.Context, opt *TorrentAddOption) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.AddNewTorrent")
|
|
defer span.End()
|
|
|
|
var requestBody bytes.Buffer
|
|
var writer = multipart.NewWriter(&requestBody)
|
|
|
|
if len(opt.URLs) == 0 && len(opt.Torrents) == 0 {
|
|
return errors.New("no torrent url or data provided")
|
|
}
|
|
|
|
if opt.SavePath != "" {
|
|
_ = writer.WriteField("savepath", opt.SavePath)
|
|
}
|
|
if opt.Cookies != "" {
|
|
_ = writer.WriteField("cookies", opt.Cookies)
|
|
}
|
|
if opt.Category != "" {
|
|
_ = writer.WriteField("category", opt.Category)
|
|
}
|
|
if len(opt.Tags) != 0 {
|
|
_ = writer.WriteField("tags", strings.Join(opt.Tags, ","))
|
|
}
|
|
if opt.SkipChecking {
|
|
_ = writer.WriteField("skip_checking", "true")
|
|
}
|
|
if opt.Paused {
|
|
_ = writer.WriteField("paused", "true")
|
|
}
|
|
if opt.RootFolder {
|
|
_ = writer.WriteField("root_folder", "true")
|
|
}
|
|
if opt.Rename != "" {
|
|
_ = writer.WriteField("rename", opt.Rename)
|
|
}
|
|
if opt.UpLimit != 0 {
|
|
_ = writer.WriteField("upLimit", strconv.Itoa(opt.UpLimit))
|
|
}
|
|
if opt.DlLimit != 0 {
|
|
_ = writer.WriteField("dlLimit", strconv.Itoa(opt.DlLimit))
|
|
}
|
|
if opt.RatioLimit != 0 {
|
|
_ = writer.WriteField("ratioLimit", strconv.FormatFloat(opt.RatioLimit, 'f', -1, 64))
|
|
}
|
|
if opt.SeedingTimeLimit != 0 {
|
|
_ = writer.WriteField("seedingTimeLimit", strconv.Itoa(opt.SeedingTimeLimit))
|
|
}
|
|
if opt.AutoTMM {
|
|
_ = writer.WriteField("autoTMM", "true")
|
|
}
|
|
if opt.SequentialDownload != "" {
|
|
_ = writer.WriteField("sequentialDownload", opt.SequentialDownload)
|
|
}
|
|
if opt.FirstLastPiecePrio != "" {
|
|
_ = writer.WriteField("firstLastPiecePrio", opt.FirstLastPiecePrio)
|
|
}
|
|
|
|
if len(opt.URLs) != 0 {
|
|
_ = writer.WriteField("urls", strings.Join(opt.URLs, "\n"))
|
|
} else if len(opt.Torrents) != 0 {
|
|
for _, torrent := range opt.Torrents {
|
|
formFile, err := writer.CreateFormFile("torrents", torrent.Filename)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = io.Copy(formFile, bytes.NewReader(torrent.Data))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
_ = writer.Close()
|
|
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/add", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
contentType: writer.FormDataContentType(),
|
|
body: &requestBody,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("add torrents failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) AddTrackers(ctx context.Context, hash string, urls []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.AddTrackers")
|
|
defer span.End()
|
|
|
|
if len(urls) == 0 {
|
|
return errors.New("no torrent tracker provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("urls", strings.Join(urls, "%0A"))
|
|
formData.Add("hash", hash)
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/addTrackers", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("add torrent trackers failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) EditTrackers(ctx context.Context, hash, origUrl, newUrl string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.EditTrackers")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("origUrl", origUrl)
|
|
formData.Add("newUrl", newUrl)
|
|
formData.Add("hash", hash)
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/editTracker", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("edit torrent trackers failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) RemoveTrackers(ctx context.Context, hash string, urls []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.RemoveTrackers")
|
|
defer span.End()
|
|
|
|
if len(urls) == 0 {
|
|
return errors.New("no torrent tracker provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hash", hash)
|
|
formData.Add("urls", strings.Join(urls, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/removeTrackers", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("remove torrent trackers failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) AddPeers(ctx context.Context, hashes []string, peers []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.AddPeers")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no hashes provided")
|
|
}
|
|
if len(peers) == 0 {
|
|
return errors.New("no peers provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
formData.Add("peers", strings.Join(peers, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/addPeers", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("addPeers torrents failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) IncreasePriority(ctx context.Context, hashes []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.IncreasePriority")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/increasePrio", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("increasePrio torrents failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) DecreasePriority(ctx context.Context, hashes []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.DecreasePriority")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/decreasePrio", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("decreasePrio torrents failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) MaxPriority(ctx context.Context, hashes []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.MaxPriority")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/topPrio", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("topPrio torrents failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) MinPriority(ctx context.Context, hashes []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.MinPriority")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/bottomPrio", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("bottomPrio torrents failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) SetFilePriority(ctx context.Context, hash string, id int, priority Priority) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.SetFilePriority")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("hash", hash)
|
|
formData.Add("id", strconv.Itoa(id))
|
|
formData.Add("priority", strconv.Itoa(int(priority)))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/filePrio", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("filePrio torrents failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) GetDownloadLimit(ctx context.Context, hashes []string) (map[string]int, error) {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.GetDownloadLimit")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return nil, errors.New("no hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/downloadLimit", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return nil, errors.New("get torrents download limit failed: " + string(result.body))
|
|
}
|
|
var data = make(map[string]int)
|
|
err = json.Unmarshal(result.body, &data)
|
|
return data, err
|
|
}
|
|
|
|
func (c *client) SetDownloadLimit(ctx context.Context, hashes []string, limit int) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.SetDownloadLimit")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
formData.Add("limit", strconv.Itoa(limit))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/setDownloadLimit", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("set torrents download limit failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) SetShareLimit(ctx context.Context, hashes []string, ratioLimit float64, seedingTimeLimit, inactiveSeedingTimeLimit int) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.SetShareLimit")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
formData.Add("ratioLimit", strconv.FormatFloat(ratioLimit, 'f', -1, 64))
|
|
formData.Add("seedingTimeLimit", strconv.Itoa(seedingTimeLimit))
|
|
formData.Add("inactiveSeedingTimeLimit", strconv.Itoa(inactiveSeedingTimeLimit))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/setShareLimits", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("set torrents share limit failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) GetUploadLimit(ctx context.Context, hashes []string) (map[string]int, error) {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.GetUploadLimit")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return nil, errors.New("no hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/uploadLimit", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return nil, errors.New("get torrents upload limit failed: " + string(result.body))
|
|
}
|
|
var data = make(map[string]int)
|
|
err = json.Unmarshal(result.body, &data)
|
|
return data, err
|
|
}
|
|
|
|
func (c *client) SetUploadLimit(ctx context.Context, hashes []string, limit int) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.SetUploadLimit")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
formData.Add("limit", strconv.Itoa(limit))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/setUploadLimit", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("set torrents upload limit failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) SetLocation(ctx context.Context, hashes []string, location string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.SetLocation")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
formData.Add("location", location)
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/setLocation", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("set torrents location failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) SetName(ctx context.Context, hash string, name string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.SetName")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("hash", hash)
|
|
formData.Add("name", name)
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/rename", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("set torrents name failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) SetCategory(ctx context.Context, hashes []string, category string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.SetCategory")
|
|
defer span.End()
|
|
|
|
if len(hashes) == 0 {
|
|
return errors.New("no hashes provided")
|
|
}
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
formData.Add("category", category)
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/setCategory", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("set torrents category failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) GetCategories(ctx context.Context) (map[string]*TorrentCategory, error) {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.GetCategories")
|
|
defer span.End()
|
|
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/categories", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return nil, errors.New("get torrents upload limit failed: " + string(result.body))
|
|
}
|
|
var data = make(map[string]*TorrentCategory)
|
|
err = json.Unmarshal(result.body, &data)
|
|
return data, err
|
|
}
|
|
|
|
func (c *client) AddNewCategory(ctx context.Context, category, savePath string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.AddNewCategory")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("category", category)
|
|
formData.Add("savePath", savePath)
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/createCategory", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("add new category failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) EditCategory(ctx context.Context, category, savePath string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.EditCategory")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("category", category)
|
|
formData.Add("savePath", savePath)
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/editCategory", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("add new category failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) RemoveCategories(ctx context.Context, categories []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.RemoveCategories")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("categories", strings.Join(categories, "\n"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/removeCategories", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("remove categories failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) AddTags(ctx context.Context, hashes []string, tags []string) error {
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
formData.Add("tags", strings.Join(tags, ","))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/addTags", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("add torrent tags failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) RemoveTags(ctx context.Context, hashes []string, tags []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.RemoveTags")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
formData.Add("tags", strings.Join(tags, ","))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/removeTags", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("remove torrent tags failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) GetTags(ctx context.Context) ([]string, error) {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.GetTags")
|
|
defer span.End()
|
|
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/tags", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodGet,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return nil, errors.New("get tags failed: " + string(result.body))
|
|
}
|
|
var data []string
|
|
err = json.Unmarshal(result.body, &data)
|
|
return data, err
|
|
}
|
|
|
|
func (c *client) CreateTags(ctx context.Context, tags []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.CreateTags")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("tags", strings.Join(tags, ","))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/createTags", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("create tags failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) DeleteTags(ctx context.Context, tags []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.DeleteTags")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("tags", strings.Join(tags, ","))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/deleteTags", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("delete tags failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) SetAutomaticManagement(ctx context.Context, hashes []string, enable bool) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.SetAutomaticManagement")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
formData.Add("enable", strconv.FormatBool(enable))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/setAutoManagement", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("set automatic management failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) ToggleSequentialDownload(ctx context.Context, hashes []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.ToggleSequentialDownload")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/toggleSequentialDownload", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("toggle sequential download failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) SetFirstLastPiecePriority(ctx context.Context, hashes []string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.SetFirstLastPiecePriority")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/toggleFirstLastPiecePrio", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("toggle first last piece prio failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) SetForceStart(ctx context.Context, hashes []string, force bool) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.SetForceStart")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
formData.Add("value", strconv.FormatBool(force))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/setForceStart", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("set force start failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) SetSuperSeeding(ctx context.Context, hashes []string, enable bool) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.SetSuperSeeding")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("hashes", strings.Join(hashes, "|"))
|
|
formData.Add("value", strconv.FormatBool(enable))
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/setSuperSeeding", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("set super seeding failed: " + string(result.body))
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (c *client) RenameFile(ctx context.Context, hash, oldPath, newPath string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.RenameFile")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("oldPath", oldPath)
|
|
formData.Add("newPath", newPath)
|
|
formData.Add("hash", hash)
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/renameFile", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("rename file failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *client) RenameFolder(ctx context.Context, hash, oldPath, newPath string) error {
|
|
ctx, span := trace.Start(ctx, "qbittorrent.Torrent.RenameFolder")
|
|
defer span.End()
|
|
|
|
var formData = url.Values{}
|
|
formData.Add("oldPath", oldPath)
|
|
formData.Add("newPath", newPath)
|
|
formData.Add("hash", hash)
|
|
var apiUrl = fmt.Sprintf("%s/api/v2/torrents/renameFolder", c.config.Address)
|
|
result, err := c.doRequest(ctx, &requestData{
|
|
url: apiUrl,
|
|
method: http.MethodPost,
|
|
body: strings.NewReader(formData.Encode()),
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if result.code != 200 {
|
|
return errors.New("rename folder failed: " + string(result.body))
|
|
}
|
|
return nil
|
|
}
|