go_telegram/pkg/service/sonarr/sonarr.go

246 lines
6.6 KiB
Go

package sonarr
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"github.com/mattburchett/go_telegram/pkg/core/config"
"github.com/yanzay/tbot/v2"
)
// Response holds the information needed for Telegram callback.
type response struct {
Button string `json:"button"`
Callback string `json:"callback"`
}
type sonarrSearch []struct {
Title string `json:"title"`
SeasonCount int `json:"seasonCount"`
Year int `json:"year"`
TvdbID int `json:"tvdbId"`
Downloaded bool `json:"downloaded"`
QualityProfileID int `json:"qualityProfileId"`
TitleSlug string `json:"titleSlug"`
Images []struct {
CoverType string `json:"coverType"`
URL string `json:"url"`
} `json:"images"`
Seasons []struct {
SeasonNumber int `json:"seasonNumber"`
Monitored bool `json:"monitored"`
} `json:"seasons"`
ProfileID int `json:"profileId"`
}
type sonarrAdd struct {
Title string `json:"title"`
TvdbID int `json:"tvdbId"`
QualityProfileID int `json:"qualityProfileId"`
TitleSlug string `json:"titleSlug"`
Images []struct {
CoverType string `json:"coverType"`
URL string `json:"url"`
} `json:"images"`
Seasons []struct {
SeasonNumber int `json:"seasonNumber"`
Monitored bool `json:"monitored"`
} `json:"seasons"`
ProfileID int `json:"profileId"`
RootFolderPath string `json:"rootFolderPath"`
AddOptions struct {
IgnoreEpisodesWithFiles bool `json:"ignoreEpisodesWithFiles"`
IgnoreEpisodesWithoutFiles bool `json:"ignoreEpisodesWithoutFiles"`
SearchForMIssingEpisodes bool `json:"searchForMissingEpisodes"`
} `json:"addOptions"`
}
type rootFolderLookup []struct {
Path string `json:"path"`
FreeSpace int64 `json:"freeSpace"`
TotalSpace int64 `json:"totalSpace"`
UnmappedFolders []struct {
Name string `json:"name"`
Path string `json:"path"`
} `json:"unmappedFolders"`
ID int `json:"id"`
}
// Search performs the lookup actions within Sonarr.
func Search(m *tbot.Message, config config.Config) ([]response, error) {
var remote sonarrSearch
var local sonarrSearch
// Perform series lookup
remoteLookup, err := http.Get(config.Sonarr.URL +
"series/lookup?apikey=" +
config.Sonarr.APIKey +
"&term=" +
url.QueryEscape(strings.TrimPrefix(strings.TrimPrefix(m.Text, "/s"), " ")))
if err != nil {
return nil, err
}
// Perform series local database lookup.
localLookup, err := http.Get(config.Sonarr.URL + "series?apikey=" + config.Sonarr.APIKey)
if err != nil {
return nil, err
}
// Read remote and local Data
remoteData, err := ioutil.ReadAll(remoteLookup.Body)
if err != nil {
return nil, err
}
localData, err := ioutil.ReadAll(localLookup.Body)
if err != nil {
return nil, err
}
// Unmarshal remote and local JSON
remoteJSON := json.Unmarshal(remoteData, &remote)
if remoteJSON != nil {
return nil, err
}
localJSON := json.Unmarshal(localData, &local)
if localJSON != nil {
return nil, err
}
// Check for downloaded items.
for r := range remote {
for l := range local {
if remote[r].TvdbID == local[l].TvdbID {
remote[r].Downloaded = true
}
}
}
// Form URLs and return to Telegram
responseData := []response{}
for k := range remote {
responseData = append(responseData,
response{
fmt.Sprintf("%v%v%v (%v) - %v Seasons",
seasonHandler(remote[k].SeasonCount, config.Sonarr.SeasonLimit),
downloadedHandler(remote[k].Downloaded),
remote[k].Title,
remote[k].Year,
remote[k].SeasonCount),
fmt.Sprintf("%d%s%s", remote[k].TvdbID,
strings.TrimSuffix(downloadedHandler(remote[k].Downloaded), " "),
strings.TrimSuffix(seasonHandler(remote[k].SeasonCount, config.Sonarr.SeasonLimit), " ")),
})
}
return responseData, err
}
// Add will take the callback data and add the show to Sonarr.
func Add(callback string, config config.Config) string {
// Separate the TVDB ID
tvdbid := strings.TrimPrefix(strings.TrimSuffix(strings.TrimSuffix(callback, "+"), "*"), "tv_")
// Look it up, to gather the information needed and the title.
seriesLookup, err := http.Get(config.Sonarr.URL + "/series/lookup?apikey=" + config.Sonarr.APIKey + "&term=tvdb:" + tvdbid)
if err != nil {
return err.Error()
}
seriesData, err := ioutil.ReadAll(seriesLookup.Body)
if err != nil {
return err.Error()
}
series := sonarrSearch{}
seriesJSON := json.Unmarshal(seriesData, &series)
if seriesJSON != nil {
return err.Error()
}
// If the "downloaded" asterisk is already in place, go ahead and return.
if strings.Contains(callback, "*") {
return fmt.Sprintf("%v has already been requested for download.", series[0].Title)
}
// Gather the root folder location.
rootFolderLookupQuery, err := http.Get(config.Sonarr.URL + "/rootfolder?apikey=" + config.Sonarr.APIKey)
if err != nil {
return err.Error()
}
rootFolderData, err := ioutil.ReadAll(rootFolderLookupQuery.Body)
if err != nil {
return err.Error()
}
rootFolder := rootFolderLookup{}
rootFolderJSON := json.Unmarshal(rootFolderData, &rootFolder)
if rootFolderJSON != nil {
return err.Error()
}
// Form the JSON needed for adding to Sonarr.
seriesAdd := sonarrAdd{
TvdbID: series[0].TvdbID,
Title: series[0].Title,
// QualityProfileID: series[0].QualityProfileID,
TitleSlug: series[0].TitleSlug,
Images: series[0].Images,
Seasons: series[0].Seasons,
RootFolderPath: rootFolder[0].Path,
ProfileID: config.Sonarr.ProfileID,
}
seriesAdd.AddOptions.IgnoreEpisodesWithFiles = false
seriesAdd.AddOptions.IgnoreEpisodesWithoutFiles = false
seriesAdd.AddOptions.SearchForMIssingEpisodes = true
// Post it to Sonarr to be added.
seriesAddJSON, err := json.Marshal(seriesAdd)
if err != nil {
return err.Error()
}
seriesAddReq, err := http.Post(config.Sonarr.URL+"/series?apikey="+config.Sonarr.APIKey, "application/json", bytes.NewBuffer(seriesAddJSON))
if err != nil {
return err.Error()
}
if seriesAddReq.StatusCode != 201 {
return "There was an error processing this request."
}
return fmt.Sprintf("%s has been queued for download.", series[0].Title)
}
// downloadHandler returns the proper string that should be shown in the Telegram response.
func downloadedHandler(downloaded bool) string {
if downloaded {
return "* "
}
return ""
}
// seasonHandler returns the proper string that should be shown in the Telegram response.
// it is also used to prevent non-admins from downloading shows with a high number of seasons.
func seasonHandler(seasonCount int, seasonLimit int) string {
if seasonLimit == 0 {
return ""
}
if seasonCount >= seasonLimit {
return "+ "
}
return ""
}