diff --git a/media/client.go b/media/client.go index b7b15dd..1749568 100644 --- a/media/client.go +++ b/media/client.go @@ -5,29 +5,28 @@ import ( "github.com/l3uddz/nabarr/logger" "github.com/l3uddz/nabarr/media/omdb" "github.com/l3uddz/nabarr/media/trakt" + "github.com/l3uddz/nabarr/media/tvdb" "github.com/rs/zerolog" ) type Client struct { trakt *trakt.Client omdb *omdb.Client + tvdb *tvdb.Client log zerolog.Logger } func New(cfg *Config) (*Client, error) { - // trakt + // validate trakt configured (it is mandatory) if cfg.Trakt.ClientId == "" { return nil, fmt.Errorf("trakt: no client_id specified") } - t := trakt.New(&cfg.Trakt) - - // omdb - o := omdb.New(&cfg.Omdb) return &Client{ - trakt: t, - omdb: o, + trakt: trakt.New(&cfg.Trakt), + omdb: omdb.New(&cfg.Omdb), + tvdb: tvdb.New(&cfg.Tvdb), log: logger.New(cfg.Verbosity).With().Logger(), }, nil diff --git a/media/config.go b/media/config.go index 1082255..e5d2706 100644 --- a/media/config.go +++ b/media/config.go @@ -3,11 +3,13 @@ package media import ( "github.com/l3uddz/nabarr/media/omdb" "github.com/l3uddz/nabarr/media/trakt" + "github.com/l3uddz/nabarr/media/tvdb" ) type Config struct { Trakt trakt.Config `yaml:"trakt"` Omdb omdb.Config `yaml:"omdb"` + Tvdb tvdb.Config `yaml:"tvdb"` Verbosity string `yaml:"verbosity,omitempty"` } diff --git a/media/show.go b/media/show.go index 2f9a10b..269e9f8 100644 --- a/media/show.go +++ b/media/show.go @@ -49,5 +49,15 @@ func (c *Client) GetShowInfo(item *FeedItem) (*Item, error) { mi.Omdb = *oi } + // tvdb + if ti, err := c.tvdb.GetItem(strconv.Itoa(t.Ids.Tvdb)); err != nil { + c.log.Debug(). + Err(err). + Int("tvdb_id", t.Ids.Tvdb). + Msg("Item was not found on tvdb") + } else if ti != nil { + mi.Tvdb = *ti + } + return mi, nil } diff --git a/media/struct.go b/media/struct.go index 0c78ddd..95f5284 100644 --- a/media/struct.go +++ b/media/struct.go @@ -3,6 +3,7 @@ package media import ( "encoding/xml" "github.com/l3uddz/nabarr/media/omdb" + "github.com/l3uddz/nabarr/media/tvdb" "github.com/pkg/errors" "time" ) @@ -29,6 +30,7 @@ type Item struct { // additional media provider data Omdb omdb.Item `json:"Omdb,omitempty"` + Tvdb tvdb.Item `json:"Tvdb,omitempty"` } type Rss struct { diff --git a/media/tvdb/config.go b/media/tvdb/config.go new file mode 100644 index 0000000..fbc8b63 --- /dev/null +++ b/media/tvdb/config.go @@ -0,0 +1,7 @@ +package tvdb + +type Config struct { + ApiKey string `yaml:"api_key"` + + Verbosity string `yaml:"verbosity,omitempty"` +} diff --git a/media/tvdb/media.go b/media/tvdb/media.go new file mode 100644 index 0000000..04c3e8d --- /dev/null +++ b/media/tvdb/media.go @@ -0,0 +1,58 @@ +package tvdb + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/l3uddz/nabarr/util" + "github.com/lucperkins/rek" +) + +var ( + ErrItemNotFound = errors.New("not found") +) + +func (c *Client) GetItem(tvdbId string) (*Item, error) { + // empty item when appropriate + if c.apiKey == "" || tvdbId == "" { + return nil, nil + } + + // prepare request + reqUrl := util.JoinURL(c.apiURL, "series", tvdbId) + c.log.Trace(). + Str("url", reqUrl). + Msg("Searching tvdb") + + // send request + c.rl.Take() + resp, err := rek.Get(reqUrl, rek.Headers(c.apiHeaders), rek.Timeout(c.apiTimeout)) + if err != nil { + return nil, fmt.Errorf("request lookup: %w", err) + } + defer resp.Body().Close() + + // validate response + if resp.StatusCode() != 200 { + return nil, fmt.Errorf("validate lookup response: %s", resp.Status()) + } + + // decode response + b := new(lookupResponse) + if err := json.NewDecoder(resp.Body()).Decode(b); err != nil { + return nil, fmt.Errorf("decode lookup response: %w", err) + } + + if b.Data.SeriesName == "" { + return nil, fmt.Errorf("item with imdbId: %v: %w", tvdbId, ErrItemNotFound) + } + + return &Item{ + Runtime: util.Atoi(b.Data.Runtime, 0), + Language: b.Data.Language, + Genre: b.Data.Genre, + AirsDayOfWeek: b.Data.AirsDayOfWeek, + SiteRating: b.Data.SiteRating, + SiteRatingCount: b.Data.SiteRatingCount, + }, nil +} diff --git a/media/tvdb/struct.go b/media/tvdb/struct.go new file mode 100644 index 0000000..7add71d --- /dev/null +++ b/media/tvdb/struct.go @@ -0,0 +1,42 @@ +package tvdb + +type lookupResponse struct { + Data struct { + Id int `json:"id,omitempty"` + SeriesId string `json:"seriesId,omitempty"` + SeriesName string `json:"seriesName,omitempty"` + Aliases []interface{} `json:"aliases,omitempty"` + Season string `json:"season,omitempty"` + Poster string `json:"poster,omitempty"` + Banner string `json:"banner,omitempty"` + Fanart string `json:"fanart,omitempty"` + Status string `json:"status,omitempty"` + FirstAired string `json:"firstAired,omitempty"` + Network string `json:"network,omitempty"` + NetworkId string `json:"networkId,omitempty"` + Runtime string `json:"runtime,omitempty"` + Language string `json:"language,omitempty"` + Genre []string `json:"genre,omitempty"` + Overview string `json:"overview,omitempty"` + LastUpdated int `json:"lastUpdated,omitempty"` + AirsDayOfWeek string `json:"airsDayOfWeek,omitempty"` + AirsTime string `json:"airsTime,omitempty"` + Rating interface{} `json:"rating,omitempty"` + ImdbId string `json:"imdbId,omitempty"` + Zap2ItId string `json:"zap2itId,omitempty"` + Added string `json:"added,omitempty"` + AddedBy int `json:"addedBy,omitempty"` + SiteRating float64 `json:"siteRating,omitempty"` + SiteRatingCount int `json:"siteRatingCount,omitempty"` + Slug string `json:"slug,omitempty"` + } `json:"data"` +} + +type Item struct { + Runtime int `json:"Runtime,omitempty"` + Language string `json:"Language,omitempty"` + Genre []string `json:"Genre,omitempty"` + AirsDayOfWeek string `json:"AirsDayOfWeek,omitempty"` + SiteRating float64 `json:"SiteRating,omitempty"` + SiteRatingCount int `json:"SiteRatingCount,omitempty"` +} diff --git a/media/tvdb/tvdb.go b/media/tvdb/tvdb.go new file mode 100644 index 0000000..9f02c7a --- /dev/null +++ b/media/tvdb/tvdb.go @@ -0,0 +1,33 @@ +package tvdb + +import ( + "fmt" + "github.com/l3uddz/nabarr/logger" + "github.com/rs/zerolog" + "go.uber.org/ratelimit" + "time" +) + +type Client struct { + apiKey string + log zerolog.Logger + rl ratelimit.Limiter + + apiURL string + apiHeaders map[string]string + apiTimeout time.Duration +} + +func New(cfg *Config) *Client { + return &Client{ + apiKey: cfg.ApiKey, + log: logger.New(cfg.Verbosity).With().Logger(), + rl: ratelimit.New(1, ratelimit.WithoutSlack), + + apiURL: "https://api.thetvdb.com", + apiHeaders: map[string]string{ + "Authorization": fmt.Sprintf("Bearer %s", cfg.ApiKey), + }, + apiTimeout: 30 * time.Second, + } +}