From 10a70f1da207b0de1b464cf2f87ea38e21e690ec Mon Sep 17 00:00:00 2001 From: l3uddz Date: Sun, 21 Feb 2021 22:30:56 +0000 Subject: [PATCH] media: support rss movie items with tmdb ids (#22) --- cmd/nabarr/main.go | 3 +++ media/movie.go | 12 +++++++++--- media/show.go | 12 +++++++++--- media/struct.go | 29 +++++++++++++++++++++++++++++ media/trakt/media.go | 8 ++++---- radarr/api.go | 22 +++++++++------------- radarr/queue.go | 29 +++++++++++++++-------------- rss/process.go | 23 ++++++++++++++++++----- sonarr/api.go | 12 +++++++++--- sonarr/queue.go | 25 +++++++++++++------------ 10 files changed, 118 insertions(+), 57 deletions(-) diff --git a/cmd/nabarr/main.go b/cmd/nabarr/main.go index c39a9d3..aef6965 100644 --- a/cmd/nabarr/main.go +++ b/cmd/nabarr/main.go @@ -207,6 +207,9 @@ func main() { case "imdb": testItem.Title = "Test.Mode.2021.BluRay.1080p.TrueHD.Atmos.7.1.AVC.HYBRID.REMUX-FraMeSToR" testItem.ImdbId = idParts[1] + case "tmdb": + testItem.Title = "Test.Mode.2021.BluRay.1080p.TrueHD.Atmos.7.1.AVC.HYBRID.REMUX-FraMeSToR" + testItem.TmdbId = idParts[1] case "tvdb": testItem.Title = "Test.Mode.S01E01.1080p.DTS-HD.MA.5.1.AVC.REMUX-FraMeSToR" testItem.TvdbId = idParts[1] diff --git a/media/movie.go b/media/movie.go index 10a7ad6..b001246 100644 --- a/media/movie.go +++ b/media/movie.go @@ -9,13 +9,19 @@ import ( ) func (c *Client) GetMovieInfo(item *FeedItem) (*Item, error) { + // retrieve and validate media provider data + mdp, mdi := item.GetProviderData() + if mdp == "" || mdi == "" { + return nil, fmt.Errorf("trakt: get movie: no media provider details found") + } + // lookup on trakt - t, err := c.trakt.GetMovie(item.ImdbId) + t, err := c.trakt.GetMovie(mdp, mdi) if err != nil { if errors.Is(err, trakt.ErrItemNotFound) { - return nil, fmt.Errorf("trakt: get movie: movie with imdbId %q: %w", item.ImdbId, ErrItemNotFound) + return nil, fmt.Errorf("trakt: get movie: movie with %sId %q: %w", mdp, mdi, ErrItemNotFound) } - return nil, fmt.Errorf("trakt: get movie: movie with imdbId %q: %w", item.ImdbId, err) + return nil, fmt.Errorf("trakt: get movie: movie with %sId %q: %w", mdp, mdi, err) } // transform trakt info diff --git a/media/show.go b/media/show.go index 269e9f8..b6626ae 100644 --- a/media/show.go +++ b/media/show.go @@ -8,13 +8,19 @@ import ( ) func (c *Client) GetShowInfo(item *FeedItem) (*Item, error) { + // retrieve and validate media provider data + mdp, mdi := item.GetProviderData() + if mdp == "" || mdi == "" { + return nil, fmt.Errorf("trakt: get show: no media provider details found") + } + // lookup on trakt - t, err := c.trakt.GetShow(item.TvdbId) + t, err := c.trakt.GetShow(mdp, mdi) if err != nil { if errors.Is(err, trakt.ErrItemNotFound) { - return nil, fmt.Errorf("trakt: get show: show with tvdbId %q: %w", item.TvdbId, ErrItemNotFound) + return nil, fmt.Errorf("trakt: get show: show with %sId %q: %w", mdp, mdi, ErrItemNotFound) } - return nil, fmt.Errorf("trakt: get show: show with tvdbId %q: %w", item.TvdbId, err) + return nil, fmt.Errorf("trakt: get show: show with %sId %q: %w", mdp, mdi, err) } // transform trakt info to MediaItem diff --git a/media/struct.go b/media/struct.go index 95f5284..a32441c 100644 --- a/media/struct.go +++ b/media/struct.go @@ -8,6 +8,8 @@ import ( "time" ) +/* Media Item(s) */ + type Item struct { TvdbId string `json:"TvdbId,omitempty"` TmdbId string `json:"TmdbId,omitempty"` @@ -33,6 +35,20 @@ type Item struct { Tvdb tvdb.Item `json:"Tvdb,omitempty"` } +func (i *Item) GetProviderData() (string, string) { + switch { + case i.TvdbId != "" && i.TvdbId != "0": + return "tvdb", i.TvdbId + case i.TmdbId != "" && i.TmdbId != "0": + return "tmdb", i.TmdbId + case i.ImdbId != "": + return "imdb", i.ImdbId + } + return "", "" +} + +/* Rss Item(s) */ + type Rss struct { Channel struct { Items []FeedItem `xml:"item"` @@ -53,6 +69,7 @@ type FeedItem struct { TvdbId string `xml:"tvdb,omitempty"` TvMazeId string ImdbId string `xml:"imdb,omitempty"` + TmdbId string `xml:"tmdb,omitempty"` Attributes []struct { XMLName xml.Name @@ -61,6 +78,18 @@ type FeedItem struct { } `xml:"attr"` } +func (f *FeedItem) GetProviderData() (string, string) { + switch { + case f.TvdbId != "" && f.TvdbId != "0": + return "tvdb", f.TvdbId + case f.TmdbId != "" && f.TmdbId != "0": + return "tmdb", f.TmdbId + case f.ImdbId != "": + return "imdb", f.ImdbId + } + return "", "" +} + // Time credits: https://github.com/mrobinsn/go-newznab/blob/cd89d9c56447859fa1298dc9a0053c92c45ac7ef/newznab/structs.go#L150 type Time struct { time.Time diff --git a/media/trakt/media.go b/media/trakt/media.go index 7784ee0..e5034be 100644 --- a/media/trakt/media.go +++ b/media/trakt/media.go @@ -13,9 +13,9 @@ var ( ErrItemNotFound = errors.New("not found") ) -func (c *Client) GetShow(tvdbId string) (*Show, error) { +func (c *Client) GetShow(providerType string, providerId string) (*Show, error) { // prepare request - reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, "search", "tvdb", tvdbId), + reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, "search", providerType, providerId), url.Values{ "type": []string{"show"}, "extended": []string{"full"}}) @@ -52,9 +52,9 @@ func (c *Client) GetShow(tvdbId string) (*Show, error) { return show, nil } -func (c *Client) GetMovie(imdbId string) (*Movie, error) { +func (c *Client) GetMovie(providerType string, providerId string) (*Movie, error) { // prepare request - reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, "search", "imdb", imdbId), + reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, "search", providerType, providerId), url.Values{ "type": []string{"movie"}, "extended": []string{"full"}}) diff --git a/radarr/api.go b/radarr/api.go index b8c315f..da1d580 100644 --- a/radarr/api.go +++ b/radarr/api.go @@ -69,19 +69,15 @@ func (c *Client) getQualityProfileId(profileName string) (int, error) { } func (c *Client) lookupMediaItem(item *media.Item) (*lookupRequest, error) { - // determine metadata id to use - mdType := "imdb" - mdId := item.ImdbId - - if item.TmdbId != "" && item.TmdbId != "0" { - // radarr prefers tmdb - mdType = "tmdb" - mdId = item.TmdbId + // retrieve and validate media provider data + mdp, mdi := item.GetProviderData() + if mdp == "" || mdi == "" { + return nil, fmt.Errorf("no media provider details found") } // prepare request reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, "movie", "lookup"), - url.Values{"term": []string{fmt.Sprintf("%s:%s", mdType, mdId)}}) + url.Values{"term": []string{fmt.Sprintf("%s:%s", mdp, mdi)}}) if err != nil { return nil, fmt.Errorf("generate movie lookup request url: %w", err) } @@ -106,19 +102,19 @@ func (c *Client) lookupMediaItem(item *media.Item) (*lookupRequest, error) { // find movie for _, s := range *b { - switch mdType { + switch mdp { case "tmdb": - if strconv.Itoa(s.TmdbId) == item.TmdbId { + if strconv.Itoa(s.TmdbId) == mdi { return &s, nil } default: - if s.ImdbId == item.ImdbId { + if s.ImdbId == mdi { return &s, nil } } } - return nil, fmt.Errorf("movie lookup %sId: %v: %w", mdType, mdId, ErrItemNotFound) + return nil, fmt.Errorf("movie lookup %sId: %v: %w", mdp, mdi, ErrItemNotFound) } func (c *Client) AddMediaItem(item *media.Item, opts ...nabarr.PvrOption) error { diff --git a/radarr/queue.go b/radarr/queue.go index f0b069a..64ab942 100644 --- a/radarr/queue.go +++ b/radarr/queue.go @@ -32,17 +32,18 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { return } - // validate item has required id(s) - if feedItem.ImdbId == "" { + // retrieve and validate media provider data + mdp, mdi := feedItem.GetProviderData() + if mdp == "" || mdi == "" { continue } // check cache / add item to cache - pvrCacheBucket := fmt.Sprintf("pvr_%s_%s", c.Type(), c.name) - cacheKey := fmt.Sprintf("imdb_%s", feedItem.ImdbId) + cacheBucket := fmt.Sprintf("pvr_%s_%s", c.Type(), c.name) + cacheKey := fmt.Sprintf("%s_%s", mdp, mdi) if !c.testMode { // not running in test mode, so use cache - if cacheValue, err := c.cache.Get(pvrCacheBucket, cacheKey); err == nil { + if cacheValue, err := c.cache.Get(cacheBucket, cacheKey); err == nil { // item already exists in the cache switch string(cacheValue) { case c.name: @@ -55,7 +56,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { } // insert temp cache entry - if err := c.cache.Put(pvrCacheBucket, cacheKey, []byte(c.cacheFiltersHash), c.cacheTempDuration); err != nil { + if err := c.cache.Put(cacheBucket, cacheKey, []byte(c.cacheFiltersHash), c.cacheTempDuration); err != nil { c.log.Error(). Err(err). Msg("Failed storing item in temp cache") @@ -69,7 +70,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { c.log.Debug(). Err(err). Str("feed_title", feedItem.Title). - Str("feed_imdb_id", feedItem.ImdbId). + Str(fmt.Sprintf("feed_%s_id", mdp), mdi). Str("feed_name", feedItem.Feed). Msg("Item was not found on trakt") continue @@ -78,7 +79,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { c.log.Error(). Err(err). Str("feed_title", feedItem.Title). - Str("feed_imdb_id", feedItem.ImdbId). + Str(fmt.Sprintf("feed_%s_id", mdp), mdi). Str("feed_name", feedItem.Feed). Msg("Failed finding item on trakt") continue @@ -90,11 +91,11 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { Msg("Item found on trakt") } - // validate tmdbId was found + // validate tmdbId was found (radarr works best with these) if mediaItem.TmdbId == "" || mediaItem.TmdbId == "0" { c.log.Warn(). Str("feed_title", mediaItem.FeedTitle). - Str("feed_imdb_id", feedItem.ImdbId). + Str(fmt.Sprintf("feed_%s_id", mdp), mdi). Str("feed_name", feedItem.Feed). Msg("Item had no tmdbId on trakt") continue @@ -133,7 +134,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { c.log.Warn(). Err(err). Str("feed_title", mediaItem.FeedTitle). - Str("feed_imdb_id", feedItem.ImdbId). + Str(fmt.Sprintf("feed_%s_id", mdp), mdi). Str("feed_name", feedItem.Feed). Msg("Item was not found via pvr lookup") continue @@ -142,7 +143,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { c.log.Error(). Err(err). Str("feed_title", mediaItem.FeedTitle). - Str("feed_imdb_id", feedItem.ImdbId). + Str(fmt.Sprintf("feed_%s_id", mdp), mdi). Str("feed_name", feedItem.Feed). Msg("Failed finding item via pvr lookup") continue @@ -160,7 +161,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { // add item to perm cache (items already in pvr) if !c.testMode { - if err := c.cache.Put(pvrCacheBucket, cacheKey, []byte(c.name), 0); err != nil { + if err := c.cache.Put(cacheBucket, cacheKey, []byte(c.name), 0); err != nil { c.log.Error(). Err(err). Msg("Failed storing item in perm cache") @@ -215,7 +216,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { // add item to perm cache (item was added to pvr) if !c.testMode { - if err := c.cache.Put(pvrCacheBucket, cacheKey, []byte(c.name), 0); err != nil { + if err := c.cache.Put(cacheBucket, cacheKey, []byte(c.name), 0); err != nil { c.log.Error(). Err(err). Msg("Failed storing item in perm cache") diff --git a/rss/process.go b/rss/process.go index a2feba8..31453b9 100644 --- a/rss/process.go +++ b/rss/process.go @@ -39,7 +39,7 @@ func (j *rssJob) queueItemWithPvrs(item *media.FeedItem) { case item.TvdbId != "" && pvr.Type() == "sonarr": // tvdbId is present, queue with sonarr pvr.QueueFeedItem(item) - case item.ImdbId != "" && pvr.Type() == "radarr": + case (item.ImdbId != "" || item.TmdbId != "") && pvr.Type() == "radarr": // imdbId is present, queue with radarr pvr.QueueFeedItem(item) } @@ -84,13 +84,13 @@ func (j *rssJob) getFeed() ([]media.FeedItem, error) { continue } - // guid seen before? + // item seen before? if cacheValue, err := j.cache.Get(j.name, i.GUID); err == nil { if string(cacheValue) == j.cacheFiltersHash { - // item has been seen before and the filters have not changed + // item has been seen before and the filter hash has not changed since continue } - // item has been seen, however the filters have changed since it was last seen, re-process + // item has been seen, however the filter hash has changed, re-process } // process feed item attributes @@ -106,11 +106,24 @@ func (j *rssJob) getFeed() ([]media.FeedItem, error) { } else { b.Channel.Items[p].ImdbId = fmt.Sprintf("tt%s", a.Value) } + case "tmdb", "tmdbid": + b.Channel.Items[p].TmdbId = a.Value } } // validate item - if (b.Channel.Items[p].TvdbId == "" || b.Channel.Items[p].TvdbId == "0") && b.Channel.Items[p].ImdbId == "" { + switch { + case b.Channel.Items[p].TvdbId != "" && b.Channel.Items[p].TvdbId != "0": + // tvdb id is present, allow processing + break + case b.Channel.Items[p].ImdbId != "": + // imdb id present, allow processing + break + case b.Channel.Items[p].TmdbId != "" && b.Channel.Items[p].TmdbId != "0": + // tmdb id present, allow processing + break + default: + // skip item as an expected media provider id was not present continue } diff --git a/sonarr/api.go b/sonarr/api.go index 7e30699..25be151 100644 --- a/sonarr/api.go +++ b/sonarr/api.go @@ -69,9 +69,15 @@ func (c *Client) getQualityProfileId(profileName string) (int, error) { } func (c *Client) lookupMediaItem(item *media.Item) (*lookupRequest, error) { + // retrieve and validate media provider data + mdp, mdi := item.GetProviderData() + if mdp == "" || mdi == "" { + return nil, fmt.Errorf("no media provider details found") + } + // prepare request reqUrl, err := util.URLWithQuery(util.JoinURL(c.apiURL, "series", "lookup"), - url.Values{"term": []string{fmt.Sprintf("tvdb:%s", item.TvdbId)}}) + url.Values{"term": []string{fmt.Sprintf("%s:%s", mdp, mdi)}}) if err != nil { return nil, fmt.Errorf("generate series lookup request url: %w", err) } @@ -96,12 +102,12 @@ func (c *Client) lookupMediaItem(item *media.Item) (*lookupRequest, error) { // find series for _, s := range *b { - if strconv.Itoa(s.TvdbId) == item.TvdbId { + if strconv.Itoa(s.TvdbId) == mdi { return &s, nil } } - return nil, fmt.Errorf("series lookup tvdbId: %v: %w", item.TvdbId, ErrItemNotFound) + return nil, fmt.Errorf("series lookup %sId: %v: %w", mdp, mdi, ErrItemNotFound) } func (c *Client) AddMediaItem(item *media.Item, opts ...nabarr.PvrOption) error { diff --git a/sonarr/queue.go b/sonarr/queue.go index f8ad068..1698a1e 100644 --- a/sonarr/queue.go +++ b/sonarr/queue.go @@ -34,17 +34,18 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { return } - // validate item has required id(s) - if feedItem.TvdbId == "" { + // retrieve and validate media provider data + mdp, mdi := feedItem.GetProviderData() + if mdp == "" || mdi == "" { continue } // check cache / add item to cache - pvrCacheBucket := fmt.Sprintf("pvr_%s_%s", c.Type(), c.name) - cacheKey := fmt.Sprintf("tvdb_%s", feedItem.TvdbId) + cacheBucket := fmt.Sprintf("pvr_%s_%s", c.Type(), c.name) + cacheKey := fmt.Sprintf("%s_%s", mdp, mdi) if !c.testMode { // not running in test mode, so use cache - if cacheValue, err := c.cache.Get(pvrCacheBucket, cacheKey); err == nil { + if cacheValue, err := c.cache.Get(cacheBucket, cacheKey); err == nil { // item already exists in the cache switch string(cacheValue) { case c.name: @@ -57,7 +58,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { } // insert temp cache entry - if err := c.cache.Put(pvrCacheBucket, cacheKey, []byte(c.cacheFiltersHash), c.cacheTempDuration); err != nil { + if err := c.cache.Put(cacheBucket, cacheKey, []byte(c.cacheFiltersHash), c.cacheTempDuration); err != nil { c.log.Error(). Err(err). Msg("Failed storing item in temp cache") @@ -71,7 +72,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { c.log.Debug(). Err(err). Str("feed_title", feedItem.Title). - Str("feed_tvdb_id", feedItem.TvdbId). + Str(fmt.Sprintf("feed_%s_id", mdp), mdi). Str("feed_name", feedItem.Feed). Msg("Item was not found on trakt") continue @@ -80,7 +81,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { c.log.Error(). Err(err). Str("feed_title", feedItem.Title). - Str("feed_tvdb_id", feedItem.TvdbId). + Str(fmt.Sprintf("feed_%s_id", mdp), mdi). Str("feed_name", feedItem.Feed). Msg("Failed finding item on trakt") continue @@ -125,7 +126,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { c.log.Warn(). Err(err). Str("feed_title", mediaItem.FeedTitle). - Str("feed_tvdb_id", feedItem.TvdbId). + Str(fmt.Sprintf("feed_%s_id", mdp), mdi). Str("feed_name", feedItem.Feed). Msg("Item was not found via pvr lookup") continue @@ -134,7 +135,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { c.log.Error(). Err(err). Str("feed_title", mediaItem.FeedTitle). - Str("feed_tvdb_id", feedItem.TvdbId). + Str(fmt.Sprintf("feed_%s_id", mdp), mdi). Str("feed_name", feedItem.Feed). Msg("Failed finding item via pvr lookup") continue @@ -151,7 +152,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { // add item to perm cache (items already in pvr) if !c.testMode { - if err := c.cache.Put(pvrCacheBucket, cacheKey, []byte(c.name), 0); err != nil { + if err := c.cache.Put(cacheBucket, cacheKey, []byte(c.name), 0); err != nil { c.log.Error(). Err(err). Msg("Failed storing item in perm cache") @@ -221,7 +222,7 @@ func (c *Client) queueProcessor(tail state.ShutdownTail) { // add item to perm cache (item was added to pvr) if !c.testMode { - if err := c.cache.Put(pvrCacheBucket, cacheKey, []byte(c.name), 0); err != nil { + if err := c.cache.Put(cacheBucket, cacheKey, []byte(c.name), 0); err != nil { c.log.Error(). Err(err). Msg("Failed storing item in perm cache")