Documentation
¶
Overview ¶
Package arrapi provides typed, resilient read clients for the Sonarr and Radarr v3 HTTP APIs.
Two constructors return two concrete client types, so an operation can only be called against the instance that supports it: NewSonarr returns a *Sonarr (GetSeries, GetEpisodes) and NewRadarr returns a *Radarr (GetMovies). Both embed a shared core that exposes the endpoints common to either service (GetTags, GetSystemStatus, Ping, Close).
Every request is authenticated with the instance's X-Api-Key, bounded by a per-request timeout, and retried on transient failures (HTTP 429, any 5xx, and transient transport errors) with jittered exponential backoff via github.com/cplieger/httpx. Non-2xx responses surface as a *StatusError, which reports whether it was transient and lets callers detect a 404 with IsNotFound.
Response bodies are size-bounded before decoding to guard against oversized or malicious payloads. The clients own no goroutines and hold no locks; a single client is safe for concurrent use.
Index ¶
- func HasAnyTag(itemTags []int, ids map[int]struct{}) bool
- func IsNotFound(err error) bool
- func TagIDs(tags []Tag, labels ...string) map[int]struct{}
- type AlternateTitle
- type Episode
- type EpisodeFile
- type EventType
- type HistoryRecord
- type MediaInfo
- type Movie
- type MovieFile
- type Option
- type QualityProfile
- type Radarr
- func (c Radarr) Close()
- func (c Radarr) GetHistorySince(ctx context.Context, since time.Time, eventType EventType) ([]HistoryRecord, error)
- func (r *Radarr) GetMovies(ctx context.Context) ([]Movie, error)
- func (c Radarr) GetQualityProfiles(ctx context.Context) ([]QualityProfile, error)
- func (c Radarr) GetRootFolders(ctx context.Context) ([]RootFolder, error)
- func (c Radarr) GetSystemStatus(ctx context.Context) (SystemStatus, error)
- func (c Radarr) GetTags(ctx context.Context) ([]Tag, error)
- func (c Radarr) Ping(ctx context.Context) error
- func (r *Radarr) RefreshMovie(ctx context.Context, movieID int) error
- func (r *Radarr) RescanMovie(ctx context.Context, movieID int) error
- type RootFolder
- type Season
- type SeasonStatistics
- type Series
- type SeriesStatistics
- type Sonarr
- func (c Sonarr) Close()
- func (s *Sonarr) GetEpisodes(ctx context.Context, seriesID int) ([]Episode, error)
- func (c Sonarr) GetHistorySince(ctx context.Context, since time.Time, eventType EventType) ([]HistoryRecord, error)
- func (c Sonarr) GetQualityProfiles(ctx context.Context) ([]QualityProfile, error)
- func (c Sonarr) GetRootFolders(ctx context.Context) ([]RootFolder, error)
- func (s *Sonarr) GetSeries(ctx context.Context) ([]Series, error)
- func (c Sonarr) GetSystemStatus(ctx context.Context) (SystemStatus, error)
- func (c Sonarr) GetTags(ctx context.Context) ([]Tag, error)
- func (c Sonarr) Ping(ctx context.Context) error
- func (s *Sonarr) RefreshSeries(ctx context.Context, seriesID int) error
- func (s *Sonarr) RescanSeries(ctx context.Context, seriesID int) error
- type StatusError
- type SystemStatus
- type Tag
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func HasAnyTag ¶
HasAnyTag reports whether itemTags contains any of the tag IDs in ids. It is the companion to TagIDs for include/exclude filtering of series and movies.
func IsNotFound ¶
IsNotFound reports whether err is (or wraps) a *StatusError with a 404 status.
func TagIDs ¶
TagIDs returns the set of tag IDs whose labels match any of the given labels. Matching is case-insensitive and trims surrounding whitespace (Sonarr and Radarr store tag labels lowercased). Labels with no matching tag are simply absent from the result. It returns nil when no labels are supplied.
Example ¶
ExampleTagIDs shows resolving tag labels to IDs and using the result to filter items by tag.
package main
import (
"fmt"
"github.com/cplieger/arrapi"
)
func main() {
tags := []arrapi.Tag{
{ID: 1, Label: "anime"},
{ID: 2, Label: "4k"},
{ID: 3, Label: "kids"},
}
want := arrapi.TagIDs(tags, "anime", "kids")
fmt.Println(arrapi.HasAnyTag([]int{3, 9}, want)) // carries "kids"
fmt.Println(arrapi.HasAnyTag([]int{2}, want)) // only "4k"
}
Output: true false
Types ¶
type AlternateTitle ¶
type AlternateTitle struct {
Title string `json:"title"`
}
AlternateTitle is an alternate title for a series or movie.
type Episode ¶
type Episode struct {
EpisodeFile *EpisodeFile `json:"episodeFile,omitempty"`
Title string `json:"title,omitempty"`
AirDate string `json:"airDate,omitempty"`
ID int `json:"id"`
SeriesID int `json:"seriesId"`
SeasonNumber int `json:"seasonNumber"`
EpisodeNumber int `json:"episodeNumber"`
AbsoluteEpisodeNumber int `json:"absoluteEpisodeNumber,omitempty"`
HasFile bool `json:"hasFile"`
Monitored bool `json:"monitored"`
}
Episode is a Sonarr episode.
type EpisodeFile ¶
type EpisodeFile struct {
MediaInfo *MediaInfo `json:"mediaInfo,omitempty"`
RelativePath string `json:"relativePath,omitempty"`
Path string `json:"path,omitempty"`
SceneName string `json:"sceneName,omitempty"`
ReleaseGroup string `json:"releaseGroup,omitempty"`
ID int `json:"id"`
Size int64 `json:"size"`
}
EpisodeFile holds file details for a Sonarr episode.
type EventType ¶
type EventType int
EventType identifies a Sonarr/Radarr history event. Sonarr encodes it as an integer and Radarr as a string; UnmarshalJSON accepts either form.
const ( // EventGrabbed is a release grabbed from an indexer. EventGrabbed EventType = 1 // EventDownloadImported is a downloaded file imported into the library // (the "downloadFolderImported" event). EventDownloadImported EventType = 3 // EventDownloadFailed is a failed download. EventDownloadFailed EventType = 4 // EventFileDeleted is an episode-file or movie-file deletion. EventFileDeleted EventType = 5 // EventFileRenamed is an episode-file or movie-file rename. EventFileRenamed EventType = 6 )
func (*EventType) UnmarshalJSON ¶
UnmarshalJSON decodes the integer form (Sonarr) or the string form (Radarr). Unknown or negative values decode to 0 (unknown), which callers filter out; a value that is neither an int nor a string is an error.
type HistoryRecord ¶
type HistoryRecord struct {
Date time.Time `json:"date"`
Data map[string]string `json:"data,omitempty"`
SourceTitle string `json:"sourceTitle,omitempty"`
EventType EventType `json:"eventType"`
ID int `json:"id"`
SeriesID int `json:"seriesId,omitempty"`
EpisodeID int `json:"episodeId,omitempty"`
MovieID int `json:"movieId,omitempty"`
}
HistoryRecord is a single Sonarr or Radarr history event. Sonarr populates SeriesID/EpisodeID; Radarr populates MovieID. Data carries event-specific key/value detail (e.g. "importedPath").
func (*HistoryRecord) ImportedPath ¶
func (h *HistoryRecord) ImportedPath() string
ImportedPath returns the imported file path from a download-import event's data dictionary, or "" when absent.
type MediaInfo ¶
type MediaInfo struct {
AudioLanguages string `json:"audioLanguages,omitempty"`
Subtitles string `json:"subtitles,omitempty"`
VideoCodec string `json:"videoCodec,omitempty"`
AudioCodec string `json:"audioCodec,omitempty"`
}
MediaInfo holds media-analysis details for an episode or movie file.
type Movie ¶
type Movie struct {
MovieFile *MovieFile `json:"movieFile,omitempty"`
Title string `json:"title"`
SortTitle string `json:"sortTitle,omitempty"`
ImdbID string `json:"imdbId,omitempty"`
Path string `json:"path,omitempty"`
RootFolderPath string `json:"rootFolderPath,omitempty"`
Status string `json:"status,omitempty"`
AlternateTitles []AlternateTitle `json:"alternateTitles,omitempty"`
Tags []int `json:"tags"`
ID int `json:"id"`
TmdbID int `json:"tmdbId"`
Year int `json:"year"`
QualityProfileID int `json:"qualityProfileId,omitempty"`
HasFile bool `json:"hasFile"`
Monitored bool `json:"monitored"`
}
Movie is a Radarr movie.
type MovieFile ¶
type MovieFile struct {
MediaInfo *MediaInfo `json:"mediaInfo,omitempty"`
RelativePath string `json:"relativePath,omitempty"`
Path string `json:"path,omitempty"`
SceneName string `json:"sceneName,omitempty"`
ReleaseGroup string `json:"releaseGroup,omitempty"`
ID int `json:"id"`
Size int64 `json:"size"`
}
MovieFile holds file details for a Radarr movie.
type Option ¶
type Option func(*config)
Option configures a Sonarr or Radarr client at construction time.
func WithBaseDelay ¶
WithBaseDelay sets the base delay for the exponential backoff between retries. Default: 1s.
func WithHTTPClient ¶
WithHTTPClient sets the underlying *http.Client. When set, the caller owns the client's transport, timeout, and redirect policy; arrapi's defaults are not applied. A nil client is ignored. Use this to share a connection pool, pin a custom CA (see github.com/cplieger/httpx.CATransport), or inject a test server client.
func WithMaxAttempts ¶
WithMaxAttempts sets the total number of HTTP attempts (including the first) for a transient failure. Values below 1 are clamped to 1 (try exactly once). Default: 3.
func WithTimeout ¶
WithTimeout sets the per-request timeout applied when the caller's context carries no deadline of its own. Default: 120s.
type QualityProfile ¶
QualityProfile is a Sonarr or Radarr quality profile. Only the identity fields are modeled; the profile's item/cutoff detail is out of scope.
type Radarr ¶
type Radarr struct {
// contains filtered or unexported fields
}
Radarr is a client for a single Radarr v3 instance. The zero value is not usable; construct one with NewRadarr. A Radarr is safe for concurrent use.
func NewRadarr ¶
NewRadarr returns a Radarr client for the given base URL (e.g. "http://radarr:7878") and API key. It returns an error if the URL is not an absolute http(s) URL or the key is empty.
func (Radarr) Close ¶
func (c Radarr) Close()
Close releases idle connections held by the client's HTTP transport. It is safe to call more than once.
func (Radarr) GetHistorySince ¶
func (c Radarr) GetHistorySince(ctx context.Context, since time.Time, eventType EventType) ([]HistoryRecord, error)
GetHistorySince returns history records on or after since (the arr endpoint orders newest first). Pass an eventType to filter to a single kind of event, or 0 for all types. Available on both Sonarr and Radarr. It is not coalesced: each poll carries a distinct timestamp and must see fresh results.
func (Radarr) GetQualityProfiles ¶
func (c Radarr) GetQualityProfiles(ctx context.Context) ([]QualityProfile, error)
GetQualityProfiles returns every quality profile defined on the instance.
func (Radarr) GetRootFolders ¶
func (c Radarr) GetRootFolders(ctx context.Context) ([]RootFolder, error)
GetRootFolders returns every root folder configured on the instance.
func (Radarr) GetSystemStatus ¶
func (c Radarr) GetSystemStatus(ctx context.Context) (SystemStatus, error)
GetSystemStatus returns the instance's system status (version, app name).
func (Radarr) Ping ¶
Ping verifies connectivity and credentials against the instance's system-status endpoint. It returns nil on success, a *StatusError (a 401 for an invalid API key), or a transport error. A short timeout bounds it so config validation fails fast; it does not retry.
func (*Radarr) RefreshMovie ¶
RefreshMovie asks Radarr to refresh the movie's metadata and rescan its files.
type RootFolder ¶
type RootFolder struct {
Path string `json:"path"`
ID int `json:"id"`
FreeSpace int64 `json:"freeSpace,omitempty"`
Accessible bool `json:"accessible,omitempty"`
}
RootFolder is a configured root folder on a Sonarr or Radarr instance.
type Season ¶
type Season struct {
Statistics *SeasonStatistics `json:"statistics,omitempty"`
SeasonNumber int `json:"seasonNumber"`
Monitored bool `json:"monitored"`
}
Season is per-season metadata on a Sonarr series.
type SeasonStatistics ¶
type SeasonStatistics struct {
EpisodeFileCount int `json:"episodeFileCount"`
EpisodeCount int `json:"episodeCount"`
TotalEpisodeCount int `json:"totalEpisodeCount"`
}
SeasonStatistics holds per-season episode counts.
type Series ¶
type Series struct {
Statistics *SeriesStatistics `json:"statistics,omitempty"`
Title string `json:"title"`
SortTitle string `json:"sortTitle,omitempty"`
ImdbID string `json:"imdbId,omitempty"`
Path string `json:"path,omitempty"`
RootFolderPath string `json:"rootFolderPath,omitempty"`
Status string `json:"status,omitempty"`
Seasons []Season `json:"seasons,omitempty"`
AlternateTitles []AlternateTitle `json:"alternateTitles,omitempty"`
Tags []int `json:"tags"`
ID int `json:"id"`
TvdbID int `json:"tvdbId"`
TmdbID int `json:"tmdbId,omitempty"`
Year int `json:"year"`
QualityProfileID int `json:"qualityProfileId,omitempty"`
Monitored bool `json:"monitored"`
}
Series is a Sonarr series.
type SeriesStatistics ¶
type SeriesStatistics struct {
SeasonCount int `json:"seasonCount"`
EpisodeFileCount int `json:"episodeFileCount"`
EpisodeCount int `json:"episodeCount"`
TotalEpisodeCount int `json:"totalEpisodeCount"`
}
SeriesStatistics holds series-level episode counts.
type Sonarr ¶
type Sonarr struct {
// contains filtered or unexported fields
}
Sonarr is a client for a single Sonarr v3 instance. The zero value is not usable; construct one with NewSonarr. A Sonarr is safe for concurrent use.
func NewSonarr ¶
NewSonarr returns a Sonarr client for the given base URL (e.g. "http://sonarr:8989") and API key. It returns an error if the URL is not an absolute http(s) URL or the key is empty.
Example ¶
ExampleNewSonarr shows the construct → ping → fetch flow. It is compiled as documentation but not run (no Output comment), so it needs no live instance.
package main
import (
"context"
"fmt"
"github.com/cplieger/arrapi"
)
func main() {
ctx := context.Background()
sonarr, err := arrapi.NewSonarr("http://sonarr:8989", "your-api-key")
if err != nil {
return
}
defer sonarr.Close()
if err := sonarr.Ping(ctx); err != nil {
return
}
series, err := sonarr.GetSeries(ctx)
if err != nil {
return
}
fmt.Printf("%d series\n", len(series))
}
Output:
func (Sonarr) Close ¶
func (c Sonarr) Close()
Close releases idle connections held by the client's HTTP transport. It is safe to call more than once.
func (*Sonarr) GetEpisodes ¶
GetEpisodes returns all episodes for the given series, including episode-file details (release group, size, media info) where present.
func (Sonarr) GetHistorySince ¶
func (c Sonarr) GetHistorySince(ctx context.Context, since time.Time, eventType EventType) ([]HistoryRecord, error)
GetHistorySince returns history records on or after since (the arr endpoint orders newest first). Pass an eventType to filter to a single kind of event, or 0 for all types. Available on both Sonarr and Radarr. It is not coalesced: each poll carries a distinct timestamp and must see fresh results.
func (Sonarr) GetQualityProfiles ¶
func (c Sonarr) GetQualityProfiles(ctx context.Context) ([]QualityProfile, error)
GetQualityProfiles returns every quality profile defined on the instance.
func (Sonarr) GetRootFolders ¶
func (c Sonarr) GetRootFolders(ctx context.Context) ([]RootFolder, error)
GetRootFolders returns every root folder configured on the instance.
func (Sonarr) GetSystemStatus ¶
func (c Sonarr) GetSystemStatus(ctx context.Context) (SystemStatus, error)
GetSystemStatus returns the instance's system status (version, app name).
func (Sonarr) Ping ¶
Ping verifies connectivity and credentials against the instance's system-status endpoint. It returns nil on success, a *StatusError (a 401 for an invalid API key), or a transport error. A short timeout bounds it so config validation fails fast; it does not retry.
func (*Sonarr) RefreshSeries ¶
RefreshSeries asks Sonarr to refresh the series' metadata and rescan its files.
type StatusError ¶
StatusError is a non-2xx HTTP response from a Sonarr or Radarr API. It implements the httpx.Transient interface, so the httpx retry helpers treat a 429 or any 5xx as retryable and every 4xx as permanent.
func (*StatusError) Error ¶
func (e *StatusError) Error() string
Error implements the error interface.
func (*StatusError) IsTransient ¶
func (e *StatusError) IsTransient() bool
IsTransient reports whether the response is retryable: HTTP 429 (rate limited) or any 5xx (server error).
type SystemStatus ¶
type SystemStatus struct {
Version string `json:"version"`
AppName string `json:"appName,omitempty"`
InstanceName string `json:"instanceName,omitempty"`
}
SystemStatus is the subset of a Sonarr/Radarr system-status response used for connectivity checks and version reporting.