package arr import ( "context" "fmt" "net/http" "net/url" "strings" ) // Release is an indexer search result from an interactive search. type Release struct { GUID string `json:"guid"` Title string `json:"title"` Indexer string `json:"indexer"` IndexerID int `json:"indexerId"` Protocol string `json:"protocol"` // usenet or torrent Size int64 `json:"size"` AgeMinutes float64 `json:"ageMinutes"` QualityWeight int `json:"qualityWeight"` CustomFormatScore int `json:"customFormatScore"` ReleaseGroup string `json:"releaseGroup"` Approved bool `json:"approved"` Rejected bool `json:"rejected"` TemporarilyRejected bool `json:"temporarilyRejected"` Rejections []string `json:"rejections"` FullSeason bool `json:"fullSeason"` Quality struct { Quality struct { Name string `json:"name"` Resolution int `json:"resolution"` } `json:"quality"` } `json:"quality"` Languages []struct { Name string `json:"name"` } `json:"languages"` CustomFormats []struct { Name string `json:"name"` } `json:"customFormats"` } // QualityName returns the release's quality, e.g. "Bluray-1080p". func (r Release) QualityName() string { return r.Quality.Quality.Name } // LanguageNames returns the release's languages joined by commas. func (r Release) LanguageNames() string { names := make([]string, len(r.Languages)) for i, l := range r.Languages { names[i] = l.Name } return strings.Join(names, ", ") } // Releases runs an interactive search with the given query, e.g. movieId=12. // Searching queries every indexer and can take a while; use a generous timeout. func (c *Client) Releases(ctx context.Context, query url.Values) ([]Release, error) { var releases []Release err := c.Do(ctx, http.MethodGet, "release", query, nil, &releases) return releases, err } // Grab sends a release found by a previous search to the download client. // Releases that were rejected can be grabbed as well. func (c *Client) Grab(ctx context.Context, r Release) error { if r.GUID == "" { return fmt.Errorf("release has no guid") } body := map[string]any{"guid": r.GUID, "indexerId": r.IndexerID} return c.Do(ctx, http.MethodPost, "release", nil, body, nil) } // Attrs derives the filterable properties from what the release announces: // its title, quality, custom formats and languages. Codec and HDR only show up // when the uploader put them into the title. func (r Release) Attrs() FileAttrs { names := []string{r.Title, r.Quality.Quality.Name} for _, cf := range r.CustomFormats { names = append(names, cf.Name) } text := strings.Join(names, " ") a := FileAttrs{Resolution: r.Quality.Quality.Resolution, Codec: codecOf(text)} if a.Resolution == 0 { if m := reRes.FindStringSubmatch(text); m != nil { a.Resolution = atoi(m[1]) } } a.DV = reDV.MatchString(text) a.HDR = reHDR.MatchString(text) a.Audio = audioTags(text) for _, l := range r.Languages { if l.Name != "" { a.Languages = append(a.Languages, l.Name) } } return a }