231 lines
5.5 KiB
Go
231 lines
5.5 KiB
Go
package download
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type SABnzbdClient struct {
|
|
baseURL string
|
|
apiKey string
|
|
client *http.Client
|
|
}
|
|
|
|
func NewSABnzbdClient(baseURL, apiKey string) *SABnzbdClient {
|
|
return &SABnzbdClient{
|
|
baseURL: strings.TrimRight(baseURL, "/"),
|
|
apiKey: apiKey,
|
|
client: &http.Client{Timeout: 30 * time.Second},
|
|
}
|
|
}
|
|
|
|
func (s *SABnzbdClient) Add(ctx context.Context, url string, category string) (string, error) {
|
|
if category == "" {
|
|
category = "umm"
|
|
}
|
|
apiURL := fmt.Sprintf("%s/api?mode=addurl&output=json&apikey=%s&name=%s&nzbname=umm&cat=%s",
|
|
s.baseURL, s.apiKey, url, category)
|
|
|
|
resp, err := s.doRequest(ctx, apiURL)
|
|
if err != nil {
|
|
return "", fmt.Errorf("sabnzbd add: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var result sabAddResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return "", fmt.Errorf("sabnzbd add decode: %w", err)
|
|
}
|
|
|
|
if !result.Status {
|
|
return "", fmt.Errorf("sabnzbd add failed: %s", result.Error)
|
|
}
|
|
|
|
if len(result.NzoIDs) == 0 {
|
|
return "", fmt.Errorf("sabnzbd add: no nzo_id returned")
|
|
}
|
|
|
|
return result.NzoIDs[0], nil
|
|
}
|
|
|
|
func (s *SABnzbdClient) GetProgress(ctx context.Context, id string) (*DownloadProgress, error) {
|
|
apiURL := fmt.Sprintf("%s/api?mode=queue&output=json&apikey=%s", s.baseURL, s.apiKey)
|
|
|
|
resp, err := s.doRequest(ctx, apiURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("sabnzbd queue: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var result sabQueueResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return nil, fmt.Errorf("sabnzbd queue decode: %w", err)
|
|
}
|
|
|
|
for _, slot := range result.Queue.Slots {
|
|
if slot.NzoID == id {
|
|
sizeMB, _ := strconv.ParseFloat(slot.SizeTotal, 64)
|
|
downloadedMB, _ := strconv.ParseFloat(slot.MB, 64)
|
|
size := int64(sizeMB * 1024 * 1024)
|
|
|
|
progress := float64(0)
|
|
if sizeMB > 0 {
|
|
progress = (downloadedMB / sizeMB) * 100
|
|
}
|
|
|
|
eta := parseTimeLeft(slot.TimeLeft)
|
|
|
|
return &DownloadProgress{
|
|
ID: slot.NzoID,
|
|
Name: slot.Filename,
|
|
Status: slot.Status,
|
|
Progress: progress,
|
|
Speed: int64(slot.Speed * 1024),
|
|
ETA: eta,
|
|
Size: size,
|
|
}, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("sabnzbd: item %s not found in queue", id)
|
|
}
|
|
|
|
func (s *SABnzbdClient) GetCompleted(ctx context.Context) ([]CompletedDownload, error) {
|
|
apiURL := fmt.Sprintf("%s/api?mode=history&output=json&apikey=%s", s.baseURL, s.apiKey)
|
|
|
|
resp, err := s.doRequest(ctx, apiURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("sabnzbd history: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
var result sabHistoryResponse
|
|
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
|
return nil, fmt.Errorf("sabnzbd history decode: %w", err)
|
|
}
|
|
|
|
var completed []CompletedDownload
|
|
for _, slot := range result.History.Slots {
|
|
if strings.EqualFold(slot.Status, "Completed") {
|
|
size, _ := strconv.ParseInt(slot.Size, 10, 64)
|
|
completed = append(completed, CompletedDownload{
|
|
ID: slot.NzoID,
|
|
Name: slot.Name,
|
|
OutputPath: slot.Storage,
|
|
Size: size,
|
|
Status: slot.Status,
|
|
})
|
|
}
|
|
}
|
|
|
|
return completed, nil
|
|
}
|
|
|
|
func (s *SABnzbdClient) Remove(ctx context.Context, id string) error {
|
|
apiURL := fmt.Sprintf("%s/api?mode=queue&output=json&apikey=%s&name=delete&value=%s&del_files=1",
|
|
s.baseURL, s.apiKey, id)
|
|
|
|
resp, err := s.doRequest(ctx, apiURL)
|
|
if err != nil {
|
|
return fmt.Errorf("sabnzbd remove: %w", err)
|
|
}
|
|
resp.Body.Close()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *SABnzbdClient) Pause(ctx context.Context, id string) error {
|
|
apiURL := fmt.Sprintf("%s/api?mode=queue&output=json&apikey=%s&name=pause&id=%s",
|
|
s.baseURL, s.apiKey, id)
|
|
|
|
resp, err := s.doRequest(ctx, apiURL)
|
|
if err != nil {
|
|
return fmt.Errorf("sabnzbd pause: %w", err)
|
|
}
|
|
resp.Body.Close()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *SABnzbdClient) Resume(ctx context.Context, id string) error {
|
|
apiURL := fmt.Sprintf("%s/api?mode=queue&output=json&apikey=%s&name=resume&id=%s",
|
|
s.baseURL, s.apiKey, id)
|
|
|
|
resp, err := s.doRequest(ctx, apiURL)
|
|
if err != nil {
|
|
return fmt.Errorf("sabnzbd resume: %w", err)
|
|
}
|
|
resp.Body.Close()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *SABnzbdClient) doRequest(ctx context.Context, url string) (*http.Response, error) {
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.client.Do(req)
|
|
}
|
|
|
|
func parseTimeLeft(timeLeft string) int {
|
|
if timeLeft == "" {
|
|
return 0
|
|
}
|
|
parts := strings.Split(timeLeft, ":")
|
|
if len(parts) != 3 {
|
|
return 0
|
|
}
|
|
hours, _ := strconv.Atoi(parts[0])
|
|
minutes, _ := strconv.Atoi(parts[1])
|
|
seconds, _ := strconv.Atoi(parts[2])
|
|
return hours*3600 + minutes*60 + seconds
|
|
}
|
|
|
|
type sabAddResponse struct {
|
|
Status bool `json:"status"`
|
|
NzoIDs []string `json:"nzo_ids"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
type sabQueueResponse struct {
|
|
Queue struct {
|
|
Slots []sabQueueSlot `json:"slots"`
|
|
} `json:"queue"`
|
|
}
|
|
|
|
type sabQueueSlot struct {
|
|
NzoID string `json:"nzo_id"`
|
|
Filename string `json:"filename"`
|
|
Status string `json:"status"`
|
|
MB string `json:"mb"`
|
|
SizeTotal string `json:"sizeleft"`
|
|
TimeLeft string `json:"timeleft"`
|
|
Speed float64 `json:"speed"`
|
|
Percentage string `json:"percentage"`
|
|
}
|
|
|
|
type sabHistoryResponse struct {
|
|
History struct {
|
|
Slots []sabHistorySlot `json:"slots"`
|
|
} `json:"history"`
|
|
}
|
|
|
|
type sabHistorySlot struct {
|
|
NzoID string `json:"nzo_id"`
|
|
Name string `json:"name"`
|
|
Status string `json:"status"`
|
|
Storage string `json:"storage"`
|
|
Size string `json:"size"`
|
|
}
|
|
|
|
func init() {
|
|
_ = io.EOF
|
|
}
|