node build fixed
This commit is contained in:
627
seanime-2.9.10/internal/debrid/torbox/torbox.go
Normal file
627
seanime-2.9.10/internal/debrid/torbox/torbox.go
Normal file
@@ -0,0 +1,627 @@
|
||||
package torbox
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"seanime/internal/constants"
|
||||
"seanime/internal/debrid/debrid"
|
||||
"seanime/internal/util"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/samber/mo"
|
||||
)
|
||||
|
||||
type (
|
||||
TorBox struct {
|
||||
baseUrl string
|
||||
apiKey mo.Option[string]
|
||||
client *http.Client
|
||||
logger *zerolog.Logger
|
||||
}
|
||||
|
||||
Response struct {
|
||||
Success bool `json:"success"`
|
||||
Detail string `json:"detail"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
File struct {
|
||||
ID int `json:"id"`
|
||||
MD5 string `json:"md5"`
|
||||
S3Path string `json:"s3_path"`
|
||||
Name string `json:"name"`
|
||||
Size int `json:"size"`
|
||||
MimeType string `json:"mimetype"`
|
||||
ShortName string `json:"short_name"`
|
||||
}
|
||||
|
||||
Torrent struct {
|
||||
ID int `json:"id"`
|
||||
Hash string `json:"hash"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Magnet string `json:"magnet"`
|
||||
Size int64 `json:"size"`
|
||||
Active bool `json:"active"`
|
||||
AuthID string `json:"auth_id"`
|
||||
DownloadState string `json:"download_state"`
|
||||
Seeds int `json:"seeds"`
|
||||
Peers int `json:"peers"`
|
||||
Ratio float64 `json:"ratio"`
|
||||
Progress float64 `json:"progress"`
|
||||
DownloadSpeed float64 `json:"download_speed"`
|
||||
UploadSpeed float64 `json:"upload_speed"`
|
||||
Name string `json:"name"`
|
||||
ETA int64 `json:"eta"`
|
||||
Server float64 `json:"server"`
|
||||
TorrentFile bool `json:"torrent_file"`
|
||||
ExpiresAt string `json:"expires_at"`
|
||||
DownloadPresent bool `json:"download_present"`
|
||||
DownloadFinished bool `json:"download_finished"`
|
||||
Files []*File `json:"files"`
|
||||
InactiveCheck int `json:"inactive_check"`
|
||||
Availability float64 `json:"availability"`
|
||||
}
|
||||
|
||||
TorrentInfo struct {
|
||||
Name string `json:"name"`
|
||||
Hash string `json:"hash"`
|
||||
Size int64 `json:"size"`
|
||||
Files []*TorrentInfoFile `json:"files"`
|
||||
}
|
||||
|
||||
TorrentInfoFile struct {
|
||||
Name string `json:"name"` // e.g. "Big Buck Bunny/Big Buck Bunny.mp4"
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
InstantAvailabilityItem struct {
|
||||
Name string `json:"name"`
|
||||
Hash string `json:"hash"`
|
||||
Size int64 `json:"size"`
|
||||
Files []struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
} `json:"files"`
|
||||
}
|
||||
)
|
||||
|
||||
func NewTorBox(logger *zerolog.Logger) debrid.Provider {
|
||||
return &TorBox{
|
||||
baseUrl: "https://api.torbox.app/v1/api",
|
||||
apiKey: mo.None[string](),
|
||||
client: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
Transport: &http.Transport{
|
||||
MaxIdleConns: 10,
|
||||
MaxIdleConnsPerHost: 5,
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
},
|
||||
},
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TorBox) GetSettings() debrid.Settings {
|
||||
return debrid.Settings{
|
||||
ID: "torbox",
|
||||
Name: "TorBox",
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TorBox) doQuery(method, uri string, body io.Reader, contentType string) (*Response, error) {
|
||||
return t.doQueryCtx(context.Background(), method, uri, body, contentType)
|
||||
}
|
||||
|
||||
func (t *TorBox) doQueryCtx(ctx context.Context, method, uri string, body io.Reader, contentType string) (*Response, error) {
|
||||
apiKey, found := t.apiKey.Get()
|
||||
if !found {
|
||||
return nil, debrid.ErrNotAuthenticated
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, uri, body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header.Add("Content-Type", contentType)
|
||||
req.Header.Add("Authorization", "Bearer "+apiKey)
|
||||
req.Header.Add("User-Agent", "Seanime/"+constants.Version)
|
||||
|
||||
resp, err := t.client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
bodyB, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("request failed: code %d, body: %s", resp.StatusCode, string(bodyB))
|
||||
}
|
||||
|
||||
bodyB, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.logger.Error().Err(err).Msg("torbox: Failed to read response body")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var ret Response
|
||||
if err := json.Unmarshal(bodyB, &ret); err != nil {
|
||||
trimmedBody := string(bodyB)
|
||||
if len(trimmedBody) > 2000 {
|
||||
trimmedBody = trimmedBody[:2000] + "..."
|
||||
}
|
||||
t.logger.Error().Err(err).Msg("torbox: Failed to decode response, response body: " + trimmedBody)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !ret.Success {
|
||||
return nil, fmt.Errorf("request failed: %s", ret.Detail)
|
||||
}
|
||||
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
func (t *TorBox) Authenticate(apiKey string) error {
|
||||
t.apiKey = mo.Some(apiKey)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TorBox) GetInstantAvailability(hashes []string) map[string]debrid.TorrentItemInstantAvailability {
|
||||
|
||||
t.logger.Trace().Strs("hashes", hashes).Msg("torbox: Checking instant availability")
|
||||
|
||||
availability := make(map[string]debrid.TorrentItemInstantAvailability)
|
||||
|
||||
if len(hashes) == 0 {
|
||||
return availability
|
||||
}
|
||||
|
||||
var hashBatches [][]string
|
||||
|
||||
for i := 0; i < len(hashes); i += 100 {
|
||||
end := i + 100
|
||||
if end > len(hashes) {
|
||||
end = len(hashes)
|
||||
}
|
||||
hashBatches = append(hashBatches, hashes[i:end])
|
||||
}
|
||||
|
||||
for _, batch := range hashBatches {
|
||||
resp, err := t.doQuery("GET", t.baseUrl+fmt.Sprintf("/torrents/checkcached?hash=%s&format=list&list_files=true", strings.Join(batch, ",")), nil, "application/json")
|
||||
if err != nil {
|
||||
return availability
|
||||
}
|
||||
|
||||
marshaledData, _ := json.Marshal(resp.Data)
|
||||
|
||||
var items []*InstantAvailabilityItem
|
||||
err = json.Unmarshal(marshaledData, &items)
|
||||
if err != nil {
|
||||
return availability
|
||||
}
|
||||
|
||||
for _, item := range items {
|
||||
availability[item.Hash] = debrid.TorrentItemInstantAvailability{
|
||||
CachedFiles: make(map[string]*debrid.CachedFile),
|
||||
}
|
||||
|
||||
for idx, file := range item.Files {
|
||||
availability[item.Hash].CachedFiles[strconv.Itoa(idx)] = &debrid.CachedFile{
|
||||
Name: file.Name,
|
||||
Size: file.Size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return availability
|
||||
}
|
||||
|
||||
func (t *TorBox) AddTorrent(opts debrid.AddTorrentOptions) (string, error) {
|
||||
|
||||
// Check if the torrent is already added by checking existing torrents
|
||||
if opts.InfoHash != "" {
|
||||
// First check if it's already in our account using a more efficient approach
|
||||
torrents, err := t.getTorrents()
|
||||
if err == nil {
|
||||
for _, torrent := range torrents {
|
||||
if torrent.Hash == opts.InfoHash {
|
||||
return strconv.Itoa(torrent.ID), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// Small delay to avoid rate limiting
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
|
||||
t.logger.Trace().Str("magnetLink", opts.MagnetLink).Msg("torbox: Adding torrent")
|
||||
|
||||
err := writer.WriteField("magnet", opts.MagnetLink)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("torbox: Failed to add torrent: %w", err)
|
||||
}
|
||||
|
||||
err = writer.WriteField("seed", "1")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("torbox: Failed to add torrent: %w", err)
|
||||
}
|
||||
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("torbox: Failed to add torrent: %w", err)
|
||||
}
|
||||
|
||||
resp, err := t.doQuery("POST", t.baseUrl+"/torrents/createtorrent", &body, writer.FormDataContentType())
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("torbox: Failed to add torrent: %w", err)
|
||||
}
|
||||
|
||||
type data struct {
|
||||
ID int `json:"torrent_id"`
|
||||
Name string `json:"name"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
|
||||
marshaledData, _ := json.Marshal(resp.Data)
|
||||
|
||||
var d data
|
||||
err = json.Unmarshal(marshaledData, &d)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("torbox: Failed to add torrent: %w", err)
|
||||
}
|
||||
|
||||
t.logger.Debug().Str("torrentId", strconv.Itoa(d.ID)).Str("torrentName", d.Name).Str("torrentHash", d.Hash).Msg("torbox: Torrent added")
|
||||
|
||||
return strconv.Itoa(d.ID), nil
|
||||
}
|
||||
|
||||
// GetTorrentStreamUrl blocks until the torrent is downloaded and returns the stream URL for the torrent file by calling GetTorrentDownloadUrl.
|
||||
func (t *TorBox) GetTorrentStreamUrl(ctx context.Context, opts debrid.StreamTorrentOptions, itemCh chan debrid.TorrentItem) (streamUrl string, err error) {
|
||||
|
||||
t.logger.Trace().Str("torrentId", opts.ID).Str("fileId", opts.FileId).Msg("torbox: Retrieving stream link")
|
||||
|
||||
doneCh := make(chan struct{})
|
||||
|
||||
go func(ctx context.Context) {
|
||||
defer func() {
|
||||
close(doneCh)
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
err = ctx.Err()
|
||||
return
|
||||
case <-time.After(4 * time.Second):
|
||||
torrent, _err := t.GetTorrent(opts.ID)
|
||||
if _err != nil {
|
||||
t.logger.Error().Err(_err).Msg("torbox: Failed to get torrent")
|
||||
err = fmt.Errorf("torbox: Failed to get torrent: %w", _err)
|
||||
return
|
||||
}
|
||||
|
||||
itemCh <- *torrent
|
||||
|
||||
// Check if the torrent is ready
|
||||
if torrent.IsReady {
|
||||
time.Sleep(1 * time.Second)
|
||||
downloadUrl, err := t.GetTorrentDownloadUrl(debrid.DownloadTorrentOptions{
|
||||
ID: opts.ID,
|
||||
FileId: opts.FileId, // Filename
|
||||
})
|
||||
if err != nil {
|
||||
t.logger.Error().Err(err).Msg("torbox: Failed to get download URL")
|
||||
return
|
||||
}
|
||||
|
||||
streamUrl = downloadUrl
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}(ctx)
|
||||
|
||||
<-doneCh
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (t *TorBox) GetTorrentDownloadUrl(opts debrid.DownloadTorrentOptions) (downloadUrl string, err error) {
|
||||
|
||||
t.logger.Trace().Str("torrentId", opts.ID).Msg("torbox: Retrieving download link")
|
||||
|
||||
apiKey, found := t.apiKey.Get()
|
||||
if !found {
|
||||
return "", fmt.Errorf("torbox: Failed to get download URL: %w", debrid.ErrNotAuthenticated)
|
||||
}
|
||||
|
||||
url := t.baseUrl + fmt.Sprintf("/torrents/requestdl?token=%s&torrent_id=%s&zip_link=true", apiKey, opts.ID)
|
||||
if opts.FileId != "" {
|
||||
// Get the actual file ID
|
||||
torrent, err := t.getTorrent(opts.ID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("torbox: Failed to get download URL: %w", err)
|
||||
}
|
||||
var fId string
|
||||
for _, f := range torrent.Files {
|
||||
if f.ShortName == opts.FileId {
|
||||
fId = strconv.Itoa(f.ID)
|
||||
break
|
||||
}
|
||||
}
|
||||
if fId == "" {
|
||||
return "", fmt.Errorf("torbox: Failed to get download URL, file not found")
|
||||
}
|
||||
url = t.baseUrl + fmt.Sprintf("/torrents/requestdl?token=%s&torrent_id=%s&file_id=%s", apiKey, opts.ID, fId)
|
||||
}
|
||||
|
||||
resp, err := t.doQuery("GET", url, nil, "application/json")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("torbox: Failed to get download URL: %w", err)
|
||||
}
|
||||
|
||||
marshaledData, _ := json.Marshal(resp.Data)
|
||||
|
||||
var d string
|
||||
err = json.Unmarshal(marshaledData, &d)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("torbox: Failed to get download URL: %w", err)
|
||||
}
|
||||
|
||||
t.logger.Debug().Str("downloadUrl", d).Msg("torbox: Download link retrieved")
|
||||
|
||||
return d, nil
|
||||
}
|
||||
|
||||
func (t *TorBox) GetTorrent(id string) (ret *debrid.TorrentItem, err error) {
|
||||
torrent, err := t.getTorrent(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret = toDebridTorrent(torrent)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (t *TorBox) getTorrent(id string) (ret *Torrent, err error) {
|
||||
|
||||
resp, err := t.doQuery("GET", t.baseUrl+fmt.Sprintf("/torrents/mylist?bypass_cache=true&id=%s", id), nil, "application/json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("torbox: Failed to get torrent: %w", err)
|
||||
}
|
||||
|
||||
marshaledData, _ := json.Marshal(resp.Data)
|
||||
|
||||
err = json.Unmarshal(marshaledData, &ret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("torbox: Failed to parse torrent: %w", err)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// GetTorrentInfo uses the info hash to return the torrent's data.
|
||||
// For cached torrents, it uses the /checkcached endpoint for faster response.
|
||||
// For uncached torrents, it falls back to /torrentinfo endpoint.
|
||||
func (t *TorBox) GetTorrentInfo(opts debrid.GetTorrentInfoOptions) (ret *debrid.TorrentInfo, err error) {
|
||||
|
||||
if opts.InfoHash == "" {
|
||||
return nil, fmt.Errorf("torbox: No info hash provided")
|
||||
}
|
||||
|
||||
resp, err := t.doQuery("GET", t.baseUrl+fmt.Sprintf("/torrents/checkcached?hash=%s&format=object&list_files=true", opts.InfoHash), nil, "application/json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("torbox: Failed to check cached torrent: %w", err)
|
||||
}
|
||||
|
||||
// If the torrent is cached
|
||||
if resp.Data != nil {
|
||||
data, ok := resp.Data.(map[string]interface{})
|
||||
if ok {
|
||||
if torrentData, exists := data[opts.InfoHash]; exists {
|
||||
marshaledData, _ := json.Marshal(torrentData)
|
||||
|
||||
var torrent TorrentInfo
|
||||
err = json.Unmarshal(marshaledData, &torrent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("torbox: Failed to parse cached torrent: %w", err)
|
||||
}
|
||||
|
||||
ret = toDebridTorrentInfo(&torrent)
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not cached, fall back
|
||||
resp, err = t.doQuery("GET", t.baseUrl+fmt.Sprintf("/torrents/torrentinfo?hash=%s&timeout=15", opts.InfoHash), nil, "application/json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("torbox: Failed to get torrent info: %w", err)
|
||||
}
|
||||
|
||||
// DEVNOTE: Handle incorrect TorBox API response
|
||||
data, ok := resp.Data.(map[string]interface{})
|
||||
if ok {
|
||||
if _, ok := data["data"]; ok {
|
||||
if _, ok := data["data"].(map[string]interface{}); ok {
|
||||
data = data["data"].(map[string]interface{})
|
||||
} else {
|
||||
return nil, fmt.Errorf("torbox: Failed to parse response")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
marshaledData, _ := json.Marshal(data)
|
||||
|
||||
var torrent TorrentInfo
|
||||
err = json.Unmarshal(marshaledData, &torrent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("torbox: Failed to parse torrent: %w", err)
|
||||
}
|
||||
|
||||
ret = toDebridTorrentInfo(&torrent)
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (t *TorBox) GetTorrents() (ret []*debrid.TorrentItem, err error) {
|
||||
|
||||
torrents, err := t.getTorrents()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("torbox: Failed to get torrents: %w", err)
|
||||
}
|
||||
|
||||
// Limit the number of torrents to 500
|
||||
if len(torrents) > 500 {
|
||||
torrents = torrents[:500]
|
||||
}
|
||||
|
||||
for _, t := range torrents {
|
||||
ret = append(ret, toDebridTorrent(t))
|
||||
}
|
||||
|
||||
slices.SortFunc(ret, func(i, j *debrid.TorrentItem) int {
|
||||
return cmp.Compare(j.AddedAt, i.AddedAt)
|
||||
})
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (t *TorBox) getTorrents() (ret []*Torrent, err error) {
|
||||
|
||||
resp, err := t.doQuery("GET", t.baseUrl+"/torrents/mylist?bypass_cache=true", nil, "application/json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("torbox: Failed to get torrents: %w", err)
|
||||
}
|
||||
|
||||
marshaledData, _ := json.Marshal(resp.Data)
|
||||
|
||||
err = json.Unmarshal(marshaledData, &ret)
|
||||
if err != nil {
|
||||
t.logger.Error().Err(err).Msg("Failed to parse torrents")
|
||||
return nil, fmt.Errorf("torbox: Failed to parse torrents: %w", err)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func toDebridTorrent(t *Torrent) (ret *debrid.TorrentItem) {
|
||||
|
||||
addedAt, _ := time.Parse(time.RFC3339Nano, t.CreatedAt)
|
||||
|
||||
completionPercentage := int(t.Progress * 100)
|
||||
|
||||
ret = &debrid.TorrentItem{
|
||||
ID: strconv.Itoa(t.ID),
|
||||
Name: t.Name,
|
||||
Hash: t.Hash,
|
||||
Size: t.Size,
|
||||
FormattedSize: util.Bytes(uint64(t.Size)),
|
||||
CompletionPercentage: completionPercentage,
|
||||
ETA: util.FormatETA(int(t.ETA)),
|
||||
Status: toDebridTorrentStatus(t),
|
||||
AddedAt: addedAt.Format(time.RFC3339),
|
||||
Speed: util.ToHumanReadableSpeed(int(t.DownloadSpeed)),
|
||||
Seeders: t.Seeds,
|
||||
IsReady: t.DownloadPresent,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func toDebridTorrentInfo(t *TorrentInfo) (ret *debrid.TorrentInfo) {
|
||||
|
||||
var files []*debrid.TorrentItemFile
|
||||
for idx, f := range t.Files {
|
||||
nameParts := strings.Split(f.Name, "/")
|
||||
var name string
|
||||
|
||||
if len(nameParts) == 1 {
|
||||
name = nameParts[0]
|
||||
} else {
|
||||
name = nameParts[len(nameParts)-1]
|
||||
}
|
||||
|
||||
files = append(files, &debrid.TorrentItemFile{
|
||||
ID: name, // Set the ID to the og name so GetStreamUrl can use that to get the real file ID
|
||||
Index: idx,
|
||||
Name: name, // e.g. "Big Buck Bunny.mp4"
|
||||
Path: fmt.Sprintf("/%s", f.Name), // e.g. "/Big Buck Bunny/Big Buck Bunny.mp4"
|
||||
Size: f.Size,
|
||||
})
|
||||
}
|
||||
|
||||
ret = &debrid.TorrentInfo{
|
||||
Name: t.Name,
|
||||
Hash: t.Hash,
|
||||
Size: t.Size,
|
||||
Files: files,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func toDebridTorrentStatus(t *Torrent) debrid.TorrentItemStatus {
|
||||
if t.DownloadFinished && t.DownloadPresent {
|
||||
switch t.DownloadState {
|
||||
case "uploading":
|
||||
return debrid.TorrentItemStatusSeeding
|
||||
default:
|
||||
return debrid.TorrentItemStatusCompleted
|
||||
}
|
||||
}
|
||||
|
||||
switch t.DownloadState {
|
||||
case "downloading", "metaDL":
|
||||
return debrid.TorrentItemStatusDownloading
|
||||
case "stalled", "stalled (no seeds)":
|
||||
return debrid.TorrentItemStatusStalled
|
||||
case "completed", "cached":
|
||||
return debrid.TorrentItemStatusCompleted
|
||||
case "uploading":
|
||||
return debrid.TorrentItemStatusSeeding
|
||||
case "paused":
|
||||
return debrid.TorrentItemStatusPaused
|
||||
default:
|
||||
return debrid.TorrentItemStatusOther
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TorBox) DeleteTorrent(id string) error {
|
||||
|
||||
type body = struct {
|
||||
ID int `json:"torrent_id"`
|
||||
Operation string `json:"operation"`
|
||||
}
|
||||
|
||||
b := body{
|
||||
ID: util.StringToIntMust(id),
|
||||
Operation: "delete",
|
||||
}
|
||||
|
||||
marshaledData, _ := json.Marshal(b)
|
||||
|
||||
_, err := t.doQuery("POST", t.baseUrl+fmt.Sprintf("/torrents/controltorrent"), bytes.NewReader(marshaledData), "application/json")
|
||||
if err != nil {
|
||||
return fmt.Errorf("torbox: Failed to delete torrent: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user