- Update ports for Alloy, Grafana, Loki, Prometheus, Promtail, Tempo, and Uptime Kuma to new ranges - Add Arrtrix content management commands and subscriptions - Implement Radarr and Sonarr client logic for movie and series management - Add matrix commands for download and subscription management - Add subscription repository with database schema and logic - Update Arrtrix config and example config for content section - Update help text and command processor to include new commands - Update vendor hash for Arrtrix package
164 lines
4.4 KiB
Go
164 lines
4.4 KiB
Go
package arrclient
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"sneeuwvlok/packages/arrtrix/pkg/arr"
|
|
)
|
|
|
|
type RadarrClient struct {
|
|
http *httpClient
|
|
config RadarrConfig
|
|
}
|
|
|
|
type radarrMovie struct {
|
|
ID int64 `json:"id"`
|
|
Title string `json:"title"`
|
|
Year int `json:"year"`
|
|
TMDBID int64 `json:"tmdbId"`
|
|
Overview string `json:"overview"`
|
|
Monitored bool `json:"monitored"`
|
|
Path string `json:"path"`
|
|
}
|
|
|
|
func NewRadarrClient(config RadarrConfig) (*RadarrClient, error) {
|
|
config.ApplyDefaults()
|
|
if err := config.Validate(); err != nil {
|
|
return nil, err
|
|
}
|
|
httpClient, err := newHTTPClient(config.URL, config.APIKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &RadarrClient{http: httpClient, config: config}, nil
|
|
}
|
|
|
|
func (c *RadarrClient) ContentType() arr.ContentType {
|
|
return arr.ContentTypeMovies
|
|
}
|
|
|
|
func (c *RadarrClient) Search(ctx context.Context, query string) ([]SearchResult, error) {
|
|
var response []radarrMovie
|
|
if err := c.http.do(ctx, http.MethodGet, "/api/v3/movie/lookup", url.Values{"term": {strings.TrimSpace(query)}}, nil, &response); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
results := make([]SearchResult, 0, len(response))
|
|
for _, movie := range response {
|
|
if movie.TMDBID == 0 {
|
|
continue
|
|
}
|
|
results = append(results, SearchResult{
|
|
LookupID: movie.TMDBID,
|
|
Title: movie.Title,
|
|
Year: movie.Year,
|
|
Overview: movie.Overview,
|
|
})
|
|
}
|
|
return results, nil
|
|
}
|
|
|
|
func (c *RadarrClient) List(ctx context.Context, query string) ([]ManagedItem, error) {
|
|
var response []radarrMovie
|
|
if err := c.http.do(ctx, http.MethodGet, "/api/v3/movie", nil, nil, &response); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
items := make([]ManagedItem, 0, len(response))
|
|
for _, movie := range response {
|
|
if query != "" && !containsFold(movie.Title, query) && !containsFold(strconv.Itoa(movie.Year), query) {
|
|
continue
|
|
}
|
|
items = append(items, ManagedItem{
|
|
ID: movie.ID,
|
|
LookupID: movie.TMDBID,
|
|
Title: movie.Title,
|
|
Year: movie.Year,
|
|
Monitored: movie.Monitored,
|
|
Path: movie.Path,
|
|
})
|
|
}
|
|
return items, nil
|
|
}
|
|
|
|
func (c *RadarrClient) Add(ctx context.Context, result SearchResult) (*ManagedItem, error) {
|
|
payload := map[string]any{
|
|
"title": result.Title,
|
|
"tmdbId": result.LookupID,
|
|
"year": result.Year,
|
|
"qualityProfileId": c.config.QualityProfileID,
|
|
"rootFolderPath": c.config.RootFolderPath,
|
|
"minimumAvailability": c.config.MinimumAvailability,
|
|
"monitored": true,
|
|
"addOptions": map[string]any{
|
|
"searchForMovie": c.config.SearchOnAddValue(),
|
|
},
|
|
}
|
|
|
|
var response radarrMovie
|
|
if err := c.http.do(ctx, http.MethodPost, "/api/v3/movie", nil, payload, &response); err != nil {
|
|
return nil, err
|
|
}
|
|
item := ManagedItem{
|
|
ID: response.ID,
|
|
LookupID: response.TMDBID,
|
|
Title: response.Title,
|
|
Year: response.Year,
|
|
Monitored: response.Monitored,
|
|
Path: response.Path,
|
|
}
|
|
return &item, nil
|
|
}
|
|
|
|
func (c *RadarrClient) SetMonitored(ctx context.Context, id int64, monitored bool) (*ManagedItem, error) {
|
|
var movie map[string]any
|
|
endpoint := "/api/v3/movie/" + strconv.FormatInt(id, 10)
|
|
if err := c.http.do(ctx, http.MethodGet, endpoint, nil, nil, &movie); err != nil {
|
|
return nil, err
|
|
}
|
|
movie["monitored"] = monitored
|
|
|
|
var response radarrMovie
|
|
if err := c.http.do(ctx, http.MethodPut, endpoint, nil, movie, &response); err != nil {
|
|
return nil, err
|
|
}
|
|
item := ManagedItem{
|
|
ID: response.ID,
|
|
LookupID: response.TMDBID,
|
|
Title: response.Title,
|
|
Year: response.Year,
|
|
Monitored: response.Monitored,
|
|
Path: response.Path,
|
|
}
|
|
return &item, nil
|
|
}
|
|
|
|
func (c *RadarrClient) Delete(ctx context.Context, id int64) error {
|
|
return c.http.do(ctx, http.MethodDelete, "/api/v3/movie/"+strconv.FormatInt(id, 10), url.Values{
|
|
"deleteFiles": {"false"},
|
|
"addImportExclusion": {"false"},
|
|
}, nil, nil)
|
|
}
|
|
|
|
func PickSingleResult(results []SearchResult, query string) (SearchResult, error) {
|
|
switch len(results) {
|
|
case 0:
|
|
return SearchResult{}, fmt.Errorf("no matching result found for %q", query)
|
|
case 1:
|
|
return results[0], nil
|
|
default:
|
|
normalized := strings.TrimSpace(strings.ToLower(query))
|
|
for _, result := range results {
|
|
title := strings.ToLower(FormatSearchResult(result))
|
|
if title == normalized {
|
|
return result, nil
|
|
}
|
|
}
|
|
return SearchResult{}, fmt.Errorf("multiple results matched %q", query)
|
|
}
|
|
}
|