tvdb_party_v2 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: f9c718ce38af6c092f521077676b680edb594856
4
- data.tar.gz: 1cd8852e202b6e7ebec6afd4d13b27c6e53652d7
2
+ SHA256:
3
+ metadata.gz: 7fa7d3d9796bb041f8623ddad6f372f4cfdbd46b743d1d54d1fb88e89a58c412
4
+ data.tar.gz: 60270c0b7c6a3e835833ceb0adf77cea4acfd7fb5cffc1d2526d96ced0233c9d
5
5
  SHA512:
6
- metadata.gz: 7c568cdeeead6b1eb2349e34e1ad472e7f5b90850a9c486b58ae7902eace70628cafd46c72396ab04ea04cd5f39319c5951826b4a8d24d54c7e798908ce55b60
7
- data.tar.gz: 58fea0ec4dfe93a1bf6a7e8bac944a8d89878aed0dcca29bcaac1a93f0e76e9a4afc9806622d3321b67d3b7343fbd463f3f8307919a3d63aa685a29442f01d82
6
+ metadata.gz: 1507bef194bedb6d6d429cf1257b65c0356aea15572ff9e08d2cd05492749608ec0d45598784b9bdd22e181dc07eee34140fb7a1325a11198cb28bdb2fe713be
7
+ data.tar.gz: dddeb93d3e82a740758b073fc4b239809dbc0425ba313410c0cd0623baeabf67215719f2aae0e115d23379eef6fc013e9be13410ada4dc1158dde86fb3cb0497
data/.gitignore CHANGED
@@ -7,3 +7,10 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+
11
+ # Markdown docs
12
+ *.md
13
+ !README.md
14
+
15
+ # Credentials
16
+ .env*
data/Gemfile.lock CHANGED
@@ -1,26 +1,49 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tvdb_party_v2 (0.1.6)
4
+ tvdb_party_v2 (0.2.0)
5
+ httparty (~> 0.24)
5
6
 
6
7
  GEM
7
8
  remote: https://rubygems.org/
8
9
  specs:
9
- httparty (0.16.2)
10
+ addressable (2.9.0)
11
+ public_suffix (>= 2.0.2, < 8.0)
12
+ bigdecimal (4.1.3)
13
+ crack (1.0.1)
14
+ bigdecimal
15
+ rexml
16
+ csv (3.3.6)
17
+ drb (2.2.3)
18
+ hashdiff (1.2.1)
19
+ httparty (0.24.2)
20
+ csv
21
+ mini_mime (>= 1.0.0)
10
22
  multi_xml (>= 0.5.2)
11
- minitest (5.11.3)
23
+ mini_mime (1.1.2)
24
+ minitest (6.0.6)
25
+ drb (~> 2.0)
26
+ prism (~> 1.5)
12
27
  multi_xml (0.6.0)
13
- rake (10.5.0)
28
+ prism (1.9.0)
29
+ public_suffix (7.0.5)
30
+ rake (13.4.2)
31
+ rexml (3.4.4)
32
+ webmock (3.26.4)
33
+ addressable (>= 2.8.0)
34
+ crack (>= 0.3.2)
35
+ hashdiff (>= 0.4.0, < 2.0.0)
14
36
 
15
37
  PLATFORMS
16
38
  ruby
17
39
 
18
40
  DEPENDENCIES
19
- bundler (~> 1.16)
41
+ bundler (>= 2.0)
20
42
  httparty
21
43
  minitest
22
- rake (~> 10.0)
44
+ rake (~> 13.0)
23
45
  tvdb_party_v2!
46
+ webmock (~> 3.0)
24
47
 
25
48
  BUNDLED WITH
26
- 1.16.1
49
+ 4.0.21
data/README.md CHANGED
@@ -1,13 +1,15 @@
1
1
  # TvdbPartyV2
2
2
 
3
- [![Gem Version](https://badge.fury.io/rb/tvdb_party_v2.svg)](https://badge.fury.io/rb/tvdb_party_v2) [![Dependency Status](https://beta.gemnasium.com/badges/github.com/davidpan/tvdb_party_v2.svg)](https://beta.gemnasium.com/projects/github.com/davidpan/tvdb_party_v2)
3
+ [![Gem Version](https://badge.fury.io/rb/tvdb_party_v2.svg)](https://badge.fury.io/rb/tvdb_party_v2)
4
4
 
5
- Simple Ruby library to talk to thetvdb.com API v2
5
+ Simple Ruby library to talk to thetvdb.com API v4. Migrated from the deprecated v2 API (the gem name is kept for continuity). Requires Ruby >= 3.0; tested on Ruby 3.4 and 4.0.
6
6
 
7
7
  # what is thetvdb.com?
8
8
 
9
9
  This site is an open tvshow database that can be modified by anybody.
10
- thetvdb.com api v2.1.2 (https://api.thetvdb.com/swagger)
10
+ thetvdb.com api v4 (https://api4.thetvdb.com/v4, docs: https://thetvdb.github.io/v4-api/)
11
+
12
+ > The legacy v2 API (api.thetvdb.com) has been decommissioned. v4 requires an API key; personal keys also require an active TheTVDB subscription PIN.
11
13
 
12
14
  ## Installation
13
15
 
@@ -29,8 +31,9 @@ Or install it yourself as:
29
31
 
30
32
 
31
33
  ```ruby
32
- # connect api
33
- @thetvdb = TvdbPartyV2::Search.new(apikey,username,userkey)
34
+ # connect api — v4 auth: apikey + optional pin (subscriber keys only)
35
+ @thetvdb = TvdbPartyV2::Search.new(apikey)
36
+ @thetvdb = TvdbPartyV2::Search.new(apikey, pin, 'en')
34
37
 
35
38
  # search tv show
36
39
  r = @thetvdb.search("Homeland")
@@ -41,21 +44,24 @@ puts homeland = @thetvdb.get_series_by_id(r.first["id"])
41
44
  # get episode
42
45
  puts s01e01 = homeland.episode(1,1)
43
46
 
44
- #get series fanart – pass in your language
45
- puts homeland.fanart.first.url
46
- => "https://www.thetvdb.com/banners/fanart/original/247897-18.jpg"
47
+ # get series fanart (banner URLs are built as https://www.thetvdb.com/banners/ + the artwork path)
48
+ homeland.fanart.first.url
49
+
50
+ # get series posters
51
+ homeland.posters.first.url
52
+
53
+ # get series banners
54
+ homeland.series_banners.first.url
47
55
 
48
- #get series posters
49
- puts homeland.posters.first.url
50
- => "https://www.thetvdb.com/banners/seasons/247897-1.jpg"
56
+ # get season posters (filtered by season number)
57
+ homeland.season_posters(2).first.url
51
58
 
52
- #get series banners
53
- puts homeland.series_banners.first.url
54
- => "https://www.thetvdb.com/banners/graphical/247897-g.jpg"
59
+ # v4 new: movies (v4-only entity)
60
+ movies = @thetvdb.movies
61
+ movie = @thetvdb.get_movie_by_id(12345)
55
62
 
56
- #get season posters
57
- puts homeland.season_posters(2).first.url
58
- => "https://www.thetvdb.com/banners/seasons/247897-2.jpg"
63
+ # v4 new: season detail with episodes
64
+ season = @thetvdb.get_season_by_id(46249)
59
65
 
60
66
  ```
61
67
 
@@ -75,11 +81,11 @@ The gem is available as open source under the terms of the [MIT License](https:/
75
81
 
76
82
  ## Code of Conduct
77
83
 
78
- Everyone interacting in the TvdbPartyV2 project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/tvdb_party_v2/blob/master/CODE_OF_CONDUCT.md).
84
+ Everyone interacting in the TvdbPartyV2 project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/davidpan/tvdb_party_v2/blob/master/CODE_OF_CONDUCT.md).
79
85
 
80
86
  ## Thanks
81
87
 
82
88
  Project Derived from maddox/tvdb_party.
83
89
 
84
- Thanks to thetvdb.com for their awesome database allowing us to meta out to our hearts consent.
90
+ Thanks to thetvdb.com for their awesome database allowing us to meta out to our heart's content.
85
91
  Project name graciously stolen from jduff/tmdb_party.
data/Rakefile CHANGED
@@ -1,10 +1,10 @@
1
- require "bundler/gem_tasks"
2
- task :default => :spec
1
+ require 'bundler/gem_tasks'
3
2
 
4
3
  require 'rake/testtask'
5
4
 
6
5
  Rake::TestTask.new do |t|
7
6
  t.libs << 'test'
7
+ t.test_files = FileList['test/**/test_*.rb']
8
8
  end
9
9
 
10
10
  desc "Run tests"
@@ -2,17 +2,18 @@ module TvdbPartyV2
2
2
  class Actor
3
3
  attr_accessor :id, :name, :role, :image, :sortorder
4
4
 
5
+ # v4 actor info comes from the series extended record's characters
5
6
  def initialize(options = {})
6
- @id = options["id"]
7
- @name = options["name"]
8
- @role = options["role"] if options["role"]
9
- @image = options["image"] if options["image"]
10
- @sortorder = options["sortOrder"] if options["sortOrder"]
7
+ @id = options["peopleId"] || options["id"]
8
+ @name = options["personName"]
9
+ @role = options["name"] if options["name"]
10
+ @image = options["personImgURL"] if options["personImgURL"]
11
+ @sortorder = options["sort"] if options["sort"]
11
12
  end
12
13
 
13
14
  def image_url
14
15
  return nil unless @image
15
- "http://www.thetvdb.com/banners/" + @image
16
+ @image.start_with?('http') ? @image : "https://www.thetvdb.com/banners/" + @image
16
17
  end
17
18
 
18
19
  end
@@ -1,33 +1,39 @@
1
1
  module TvdbPartyV2
2
2
  class Banner
3
- attr_accessor :banner_type, :banner_resolution, :subkey, :path, :thumbnail_path, :ratingaverage, :ratingcount
3
+ attr_accessor :banner_type, :banner_resolution, :subkey, :path, :thumbnail_path, :ratingaverage, :ratingcount, :season_id
4
4
 
5
+ # v4 artwork record: image/thumbnail are paths, type is an integer type id
5
6
  def initialize(options = {})
6
- @banner_type = options["keyType"]
7
- @banner_resolution = options["resolution"]
8
- @subkey = options["subKey"]
9
- @path = options["fileName"]
7
+ @banner_type = options["type"]
8
+ @banner_resolution = options["width"] && options["height"] ? "#{options["width"]}x#{options["height"]}" : nil
9
+ @path = options["image"]
10
+ @thumbnail_path = options["thumbnail"]
11
+ @season_id = options["seasonId"]
10
12
 
11
- if options["ratingsInfo"]["average"] && options["ratingsInfo"]["average"] > 0
12
- @ratingaverage = options["ratingsInfo"]["average"].to_f
13
+ if options["score"] && options["score"] > 0
14
+ @ratingaverage = options["score"].to_f
13
15
  else
14
16
  @ratingaverage = 0
15
17
  end
16
18
 
17
- if options["ratingsInfo"]["count"] && options["ratingsInfo"]["count"] > 0
18
- @ratingcount = options["ratingsInfo"]["count"]
19
- else
20
- @ratingcount = 0
21
- end
22
-
19
+ @ratingcount = 0 # v4 no longer provides rating counts
23
20
  end
24
21
 
25
22
  def url
26
- "https://www.thetvdb.com/banners/" + @path
23
+ banner_path_url(@path)
27
24
  end
28
25
 
29
26
  def thumb_url
30
- "https://www.thetvdb.com/banners/_cache/" + @path
27
+ banner_path_url(@thumbnail_path, cache: true)
28
+ end
29
+
30
+ private
31
+
32
+ def banner_path_url(path, cache: false)
33
+ return nil unless path
34
+ return path if path.start_with?('http')
35
+ path = "_cache/#{path}" if cache && !path.start_with?('_cache/')
36
+ "https://www.thetvdb.com/banners/" + path
31
37
  end
32
38
 
33
39
  end
@@ -7,49 +7,58 @@ module TvdbPartyV2
7
7
  def initialize(client, options = {})
8
8
  @client = client
9
9
  @id = options["id"]
10
- @season_number = options["airedSeason"]
11
- @number = options["airedEpisodeNumber"]
12
- @name = options["episodeName"]
10
+ @season_number = options["seasonNumber"]
11
+ @number = options["number"]
12
+ @name = options["name"]
13
13
  @overview = options["overview"]
14
- @director = options["directors"]
15
- @writer = options["writers"]
16
14
  @series_id = options["seriesId"]
17
- @season_id = options["airedSeasonID"]
18
- @imdb_id = options["imdbId"]
19
- if options["guestStars"]
20
- @guest_stars = options["guestStars"]
21
- else
22
- @guest_stars = []
23
- end
15
+ @season_id = season_id_from(options["seasons"])
16
+ @imdb_id = RemoteId.imdb_from(options["remoteIds"])
17
+ @guest_stars = guest_stars_from(options["characters"])
24
18
 
25
- if options["filename"].nil? || options["filename"].empty?
19
+ if options["image"].nil? || options["image"].empty?
26
20
  @thumb = nil
27
21
  else
28
- @thumb = "http://www.thetvdb.com/banners/" + options["filename"]
22
+ @thumb = "https://www.thetvdb.com/banners/" + options["image"]
29
23
  end
30
24
 
25
+ # v4 episode records no longer provide director/writer string arrays
26
+ @director = nil
27
+ @writer = nil
28
+
31
29
  begin
32
- @air_date = Time.parse(options["firstAired"]).to_i
33
- rescue
30
+ @air_date = Time.parse(options["aired"].to_s).to_i
31
+ rescue StandardError
34
32
  @air_date = 0
35
33
  end
36
34
 
37
- if options["siteRating"] && options["siteRating"] > 0
38
- @rating = options["siteRating"].to_f
35
+ if options["score"] && options["score"] > 0
36
+ @rating = options["score"].to_f
39
37
  else
40
38
  @rating = 0
41
39
  end
42
40
 
43
- if options["siteRatingCount"] && options["siteRatingCount"] > 0
44
- @ratingcount = options["siteRatingCount"]
45
- else
46
- @ratingcount = 0
47
- end
48
-
41
+ @ratingcount = 0 # v4 no longer provides rating counts
49
42
  end
50
43
 
51
44
  def series
52
- client.get_series_by_id(@series.id)
45
+ client.get_series_by_id(@series_id)
46
+ end
47
+
48
+ private
49
+
50
+ def season_id_from(seasons)
51
+ first = Array(seasons).first
52
+ first.is_a?(Hash) ? first["id"] : first
53
+ end
54
+
55
+ def guest_stars_from(characters)
56
+ Array(characters).select do |c|
57
+ next false unless c.is_a?(Hash)
58
+ type = c["peopleType"]
59
+ type = type["name"] if type.is_a?(Hash)
60
+ type.to_s =~ /guest/i
61
+ end.map { |c| c["personName"] }
53
62
  end
54
63
  end
55
64
 
@@ -0,0 +1,42 @@
1
+ module TvdbPartyV2
2
+
3
+ class Movie
4
+ attr_reader :client, :raw
5
+ attr_accessor :id, :name, :overview, :status, :runtime, :year, :genres, :image, :imdb_id, :rating, :first_release
6
+
7
+ def initialize(client, options = {})
8
+ @client = client
9
+ @raw = options || {}
10
+ @id = options["id"]
11
+ @name = options["name"]
12
+ @overview = options["overview"]
13
+ @status = status_name(options["status"])
14
+ @runtime = options["runtime"]
15
+ @year = options["year"]
16
+ @image = options["image"]
17
+ @imdb_id = RemoteId.imdb_from(options["remoteIds"])
18
+ @genres = Array(options["genres"]).map { |g| g.is_a?(Hash) ? g["name"] : g }
19
+
20
+ if options["score"] && options["score"] > 0
21
+ @rating = options["score"].to_f
22
+ else
23
+ @rating = 0
24
+ end
25
+
26
+ release = options["first_release"]
27
+ begin
28
+ @first_release = Time.parse(release.is_a?(Hash) ? release["date"].to_s : release.to_s).to_i
29
+ rescue StandardError
30
+ @first_release = 0
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def status_name(status)
37
+ return nil unless status
38
+ status.is_a?(Hash) ? status["name"] : status
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,22 @@
1
+ module TvdbPartyV2
2
+ # v4 remote id helpers: imdb id <-> remote_id conversion
3
+ module RemoteId
4
+ module_function
5
+
6
+ # Extract the imdb id (e.g. "tt1796960") from a remoteIds array
7
+ def imdb_from(remote_ids)
8
+ Array(remote_ids).each do |r|
9
+ next unless r.is_a?(Hash)
10
+ return r["id"] if r["sourceName"].to_s.casecmp?("imdb") || r["id"].to_s.start_with?("tt")
11
+ end
12
+ nil
13
+ end
14
+
15
+ # Convert an imdb id to the v4 remote_id query format (e.g. "imdb-tt1796960")
16
+ def for_imdb(imdb_id)
17
+ id = imdb_id.to_s
18
+ return id if id =~ /\Aimdb-/i
19
+ id.start_with?('tt') ? "imdb-#{id}" : "imdb-tt#{id}"
20
+ end
21
+ end
22
+ end
@@ -1,132 +1,170 @@
1
1
  module TvdbPartyV2
2
2
 
3
+ # Raised when the v4 login is rejected (bad apikey/pin or inactive key)
4
+ class LoginError < StandardError; end
5
+
3
6
  class Search
4
7
  include HTTParty
5
- base_uri 'https://api.thetvdb.com'
8
+ base_uri 'https://api4.thetvdb.com/v4'
6
9
  headers "Content-Type" => "application/json"
7
10
  attr_accessor :token, :language
8
11
 
9
- def initialize(apikey, username, userkey, language = 'en', cache_options = {})
12
+ # v4 auth: apikey required, subscribers may pass a pin; v2's username/userkey are gone
13
+ def initialize(apikey, pin = nil, language = 'en')
10
14
  @language = language
11
- thetvdb = self.class.post('/login',
12
- :body => {"apikey" => apikey,
13
- "username" => username,
14
- "userkey" => userkey}.to_json)
15
- @token = thetvdb['token']
16
- end
17
-
18
- #基于电视剧英文名称获取thetvdb的查询结果
19
- def search(series_name)
20
- response = self.get("/search/series?name=#{URI::encode(series_name)}")
21
- return [] unless response["data"]
22
- case response["data"]
23
- when Array
24
- response["data"]
25
- when Hash
26
- [response["data"]]
27
- else
28
- []
15
+ body = { "apikey" => apikey }
16
+ body["pin"] = pin unless pin.nil? || pin.to_s.empty?
17
+ response = self.class.post('/login', :body => body.to_json)
18
+ @token = response.dig('data', 'token')
19
+ if @token.nil?
20
+ status = response.response&.code
21
+ raise LoginError, "TheTVDB login failed (HTTP #{status}): #{response.parsed_response.to_s[0, 200]}"
29
22
  end
30
23
  end
31
24
 
32
- #基于imdbid获取thetvdb的查询结果
25
+ # Search series (or movies via type: 'movie') by name
26
+ def search(series_name, type = 'series')
27
+ response = self.get("/search?query=#{URI.encode_www_form_component(series_name)}&type=#{type}")
28
+ normalize_results(response)
29
+ end
30
+
31
+ # Search series by imdb id (remote_id looks like imdb-tt1796960)
33
32
  def search_by_imdb(imdb_id)
34
- response = self.get("/search/series?imdbId=#{imdb_id}")
35
- return [] unless response["data"]
33
+ response = self.get("/search?remote_id=#{RemoteId.for_imdb(imdb_id)}&type=series")
34
+ normalize_results(response)
35
+ end
36
36
 
37
- case response["data"]
38
- when Array
39
- response["data"]
40
- when Hash
41
- [response["data"]]
42
- else
43
- []
44
- end
37
+ # Search movies by name
38
+ def search_movies(movie_name)
39
+ search(movie_name, 'movie')
45
40
  end
46
41
 
47
- #基于电视剧id获取电视剧详细信息
42
+ # Get series details by series id
48
43
  def get_series_by_id(series_id, language = self.language)
49
- response = self.get("/series/#{series_id.to_s}")
50
- if response["data"]
51
- Series.new(self, response["data"])
52
- else
53
- nil
54
- end
44
+ fetch_entity("/series/#{series_id}/extended", Series)
55
45
  end
56
46
 
57
- #基于电视剧集id获取每集的详细信息
47
+ # Get episode details by episode id
58
48
  def get_episode_by_id(episode_id, language = self.language)
59
- response = self.get("/episodes/#{episode_id.to_s}")
60
- if response["data"]
61
- Episode.new(self, response["data"])
62
- else
63
- nil
64
- end
49
+ fetch_entity("/episodes/#{episode_id}/extended", Episode)
65
50
  end
66
51
 
67
- #基于季数、集数获取集的详细信息
68
- def get_episode(series, season_number, episode_number = nil, page_id = nil)
69
- if episode_number
70
- url = "/series/#{series.id}/episodes/query?airedSeason=#{season_number}&airedEpisode=#{episode_number}"
71
- else
72
- url = "/series/#{series.id}/episodes/query?airedSeason=#{season_number}"
73
- end
74
- url = url + "&page=#{page_id}" unless page_id.nil?
52
+ # Get movie details by movie id
53
+ def get_movie_by_id(movie_id)
54
+ fetch_entity("/movies/#{movie_id}/extended", Movie)
55
+ end
75
56
 
76
- response = self.get(url)
77
- return [] unless response["data"]
78
- data = []
79
- if response['links']['next'].nil?
80
- data = response["data"]
81
- else
82
- response["data"].each {|x| data << x}
83
- get_episode(series, season_number, episode_number, response['links']['next'])
84
- end
85
- data.map {|e| self.get_episode_by_id(e["id"])}
57
+ # Get season details (with episodes) by season id
58
+ def get_season_by_id(season_id)
59
+ fetch_entity("/seasons/#{season_id}/extended", Season)
86
60
  end
87
61
 
62
+ # Get episodes of a season/episode number (season_type: default/official/alternate/regional)
63
+ def get_episode(series, season_number, episode_number = nil, page = nil, season_type = 'default')
64
+ episodes = []
65
+ loop do
66
+ url = "/series/#{series.id}/episodes/#{season_type}?season=#{season_number}"
67
+ url = url + "&episodeNumber=#{episode_number}" if episode_number
68
+ url = url + "&page=#{page}" unless page.nil?
88
69
 
70
+ response = self.get(url)
71
+ data = response.dig('data', 'episodes') || []
72
+ episodes.concat(data)
89
73
 
90
- def get_actors(series)
91
- response = self.get("/series/#{series.id}/actors")
92
- if response["data"]
93
- response["data"].collect {|a| Actor.new(a)}
94
- else
95
- nil
74
+ next_page = response.dig('links', 'next')
75
+ break if next_page.nil? || next_page == false || next_page.to_s.empty? || data.empty?
76
+ page = next_page
96
77
  end
78
+ episodes.map { |e| self.get_episode_by_id(e["id"]) }
79
+ end
80
+
81
+ # Get actors (v4 returns them in the series extended record's characters)
82
+ def get_actors(series)
83
+ characters = series_data(series)["characters"]
84
+ return [] unless characters.is_a?(Array)
85
+ characters.collect { |c| Actor.new(c) }
97
86
  end
98
87
 
99
- # 获取季数
88
+ # Get season numbers
100
89
  def get_seasons(series)
101
- response = self.get("/series/#{series.id}/episodes/summary")
102
- if response["data"] && response["data"]["airedSeasons"]
103
- return response["data"]["airedSeasons"]
104
- else
105
- return []
106
- end
90
+ seasons = series_data(series)["seasons"]
91
+ return [] unless seasons.is_a?(Array)
92
+ seasons.map { |s| s.is_a?(Hash) ? s["number"] : s }
107
93
  end
108
94
 
95
+ # Get posters/banners etc. (v4 uses /series/{id}/artworks with integer type ids)
109
96
  def get_banners(series, keyType, subKey = nil)
110
- if subKey
111
- url = "/series/#{series.id}/images/query?keyType=#{keyType}&subKey=#{subKey}"
112
- else
113
- url = "/series/#{series.id}/images/query?keyType=#{keyType}"
97
+ url = "/series/#{series.id}/artworks"
98
+ type_id = ARTWORK_TYPES[keyType.to_s.downcase]
99
+ url = url + "?type=#{type_id}" if type_id
100
+
101
+ response = self.get(url)
102
+ data = response && response["data"]
103
+ return [] unless data
104
+
105
+ season_numbers = {}
106
+ Array(data["seasons"]).each do |s|
107
+ season_numbers[s["id"]] = s["number"] if s.is_a?(Hash)
108
+ end
109
+
110
+ artworks = data["artworks"].is_a?(Array) ? data["artworks"] : []
111
+ banners = artworks.map { |result| Banner.new(result) }
112
+ banners.each do |b|
113
+ b.subkey = season_numbers[b.season_id].to_s if b.season_id && season_numbers.key?(b.season_id)
114
114
  end
115
+ banners = banners.select { |b| b.subkey == subKey.to_s } unless subKey.nil?
116
+ banners
117
+ end
118
+
119
+ # List movies (movies are a v4-only entity)
120
+ def movies(page = nil)
121
+ url = "/movies"
122
+ url = url + "?page=#{page}" unless page.nil?
115
123
  response = self.get(url)
116
- return [] unless response["data"]
117
- case response["data"]
124
+ data = response && response["data"]
125
+ return [] unless data.is_a?(Array)
126
+ data.map { |result| Movie.new(self, result) }
127
+ end
128
+
129
+ def get(url)
130
+ return self.class.get(url,
131
+ :headers => {"Authorization" => 'Bearer ' + @token,"Accept-Language" => self.language}).parsed_response
132
+ end
133
+
134
+ private
135
+
136
+ # v4 classic artwork type ids (full list at /artwork/types)
137
+ ARTWORK_TYPES = {
138
+ 'series' => 1,
139
+ 'poster' => 2,
140
+ 'fanart' => 3,
141
+ 'season' => 6,
142
+ 'seasonwide' => 7
143
+ }.freeze
144
+
145
+ def normalize_results(response)
146
+ data = response && response["data"]
147
+ return [] unless data
148
+ case data
118
149
  when Array
119
- response["data"].map {|result| Banner.new(result)}
150
+ data
120
151
  when Hash
121
- [Banner.new(response["data"])]
152
+ [data]
122
153
  else
123
154
  []
124
155
  end
125
156
  end
126
157
 
127
- def get(url)
128
- return self.class.get(url,
129
- :headers => {"Authorization" => 'Bearer ' + @token,"Accept-Language" => self.language}).parsed_response
158
+ def fetch_entity(path, klass)
159
+ response = self.get(path)
160
+ response && response["data"] ? klass.new(self, response["data"]) : nil
161
+ end
162
+
163
+ def series_data(series)
164
+ raw = series.raw if series.respond_to?(:raw)
165
+ return raw if raw.is_a?(Hash) && !raw.empty?
166
+ response = self.get("/series/#{series.id}/extended")
167
+ (response && response["data"]) || {}
130
168
  end
131
169
  end
132
170
  end
@@ -0,0 +1,26 @@
1
+ module TvdbPartyV2
2
+
3
+ class Season
4
+ attr_reader :client, :raw
5
+ attr_accessor :id, :number, :name, :series_id, :image, :year
6
+
7
+ def initialize(client, options = {})
8
+ @client = client
9
+ @raw = options || {}
10
+ @id = options["id"]
11
+ @number = options["number"]
12
+ @name = options["name"]
13
+ @series_id = options["seriesId"]
14
+ @image = options["image"]
15
+ @year = options["year"]
16
+ end
17
+
18
+ # Episodes in this season (v4 returns them in the season extended record)
19
+ def episodes
20
+ eps = @raw["episodes"]
21
+ return [] unless eps.is_a?(Array)
22
+ eps.map { |e| Episode.new(client, e) }
23
+ end
24
+ end
25
+
26
+ end
@@ -1,43 +1,36 @@
1
1
  module TvdbPartyV2
2
2
 
3
3
  class Series
4
- attr_reader :client
5
- attr_accessor :id, :name, :overview, :seasons, :first_aired, :genres, :network, :rating, :runtime,
6
- :actors, :banners, :air_time, :imdb_id, :ratingcount, :status, :airs_dayofweek
4
+ attr_reader :client, :raw
5
+ attr_accessor :id, :name, :overview, :first_aired, :genres, :network, :rating, :runtime,
6
+ :banners, :air_time, :imdb_id, :ratingcount, :status, :airs_dayofweek
7
7
 
8
8
  def initialize(client, options = {})
9
9
  @client = client
10
+ @raw = options || {}
10
11
  @id = options["id"]
11
- @name = options["seriesName"]
12
+ @name = options["name"]
12
13
  @overview = options["overview"]
13
- @network = options["network"]
14
- @runtime = options["runtime"]
14
+ @network = company_name(options["originalNetwork"])
15
+ @runtime = options["averageRuntime"]
15
16
  @air_time = options['airsTime'] if options['airsTime']
16
- @imdb_id = options["imdbId"]
17
- @status = options["status"] if options["status"]
18
- @airs_dayofweek = options["airsDayOfWeek"]
17
+ @imdb_id = RemoteId.imdb_from(options["remoteIds"])
18
+ @status = status_name(options["status"])
19
+ @airs_dayofweek = options["airsDays"]
19
20
 
20
- if options["genre"]
21
- @genres = options["genre"]
22
- else
23
- @genres = []
24
- end
21
+ @genres = Array(options["genres"]).map { |g| g.is_a?(Hash) ? g["name"] : g }
25
22
 
26
- if options["siteRating"] && options["siteRating"] > 0
27
- @rating = options["siteRating"].to_f
23
+ if options["score"] && options["score"] > 0
24
+ @rating = options["score"].to_f
28
25
  else
29
26
  @rating = 0
30
27
  end
31
28
 
32
- if options["siteRatingCount"] && options["siteRatingCount"] > 0
33
- @ratingcount = options["siteRatingCount"].to_f
34
- else
35
- @ratingcount = 0
36
- end
29
+ @ratingcount = 0 # v4 no longer provides rating counts
37
30
 
38
31
  begin
39
- @first_aired = Time.parse(options["firstAired"]).to_i
40
- rescue
32
+ @first_aired = Time.parse(options["firstAired"].to_s).to_i
33
+ rescue StandardError
41
34
  @first_aired = 0
42
35
  end
43
36
  end
@@ -47,23 +40,23 @@ module TvdbPartyV2
47
40
  end
48
41
 
49
42
  def posters
50
- @banners_posters ||= client.get_banners(self, 'poster')
43
+ client.get_banners(self, 'poster')
51
44
  end
52
45
 
53
46
  def fanart
54
- @banners_fanarts ||= client.get_banners(self, 'fanart')
47
+ client.get_banners(self, 'fanart')
55
48
  end
56
49
 
57
50
  def series_banners
58
- @banners_series ||= client.get_banners(self, 'series')
51
+ client.get_banners(self, 'series')
59
52
  end
60
53
 
61
54
  def season_banners
62
- @banners_season ||= client.get_banners(self, 'season')
55
+ client.get_banners(self, 'season')
63
56
  end
64
57
 
65
58
  def seasonwide
66
- @banners_seasonwide ||= client.get_banners(self, 'seasonwide')
59
+ client.get_banners(self, 'seasonwide')
67
60
  end
68
61
 
69
62
  def season_posters(season_number)
@@ -74,20 +67,29 @@ module TvdbPartyV2
74
67
  seasonwide.select {|b| b.subkey == season_number.to_s}
75
68
  end
76
69
 
77
-
78
70
  def seasons
79
- @seasons ||= client.get_seasons(self)
71
+ client.get_seasons(self)
80
72
  end
81
73
 
82
-
83
74
  def actors
84
- @actors ||= client.get_actors(self)
75
+ client.get_actors(self)
85
76
  end
86
77
 
87
78
  def season(season_number)
88
- @season ||= client.get_episode(self, season_number)
79
+ client.get_episode(self, season_number)
89
80
  end
90
81
 
82
+ private
83
+
84
+ def company_name(company)
85
+ return nil unless company
86
+ company.is_a?(Hash) ? company["name"] : company
87
+ end
88
+
89
+ def status_name(status)
90
+ return nil unless status
91
+ status.is_a?(Hash) ? status["name"] : status
92
+ end
91
93
  end
92
94
 
93
95
  end
@@ -1,3 +1,3 @@
1
1
  module TvdbPartyV2
2
- VERSION = "0.1.7"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/tvdb_party_v2.rb CHANGED
@@ -1,8 +1,12 @@
1
1
  require "tvdb_party_v2/version"
2
2
  require 'rubygems'
3
+ require 'time'
3
4
  require 'httparty'
4
5
 
5
6
  directory = File.expand_path(File.dirname(__FILE__))
7
+ require File.join(directory, 'tvdb_party_v2', 'remote_id')
8
+ require File.join(directory, 'tvdb_party_v2', 'movie')
9
+ require File.join(directory, 'tvdb_party_v2', 'season')
6
10
  require File.join(directory, 'tvdb_party_v2', 'search')
7
11
  require File.join(directory, 'tvdb_party_v2', 'series')
8
12
  require File.join(directory, 'tvdb_party_v2', 'episode')
@@ -9,8 +9,8 @@ Gem::Specification.new do |spec|
9
9
  spec.authors = ["David Pan"]
10
10
  spec.email = ["davidpanchina@gmail.com"]
11
11
 
12
- spec.summary = %q{Simple Ruby library to talk to thetvdb.com's v2 API.}
13
- spec.description = %q{Simple Ruby library to talk to thetvdb.com's v2 API.}
12
+ spec.summary = %q{Simple Ruby library to talk to thetvdb.com's v4 API.}
13
+ spec.description = %q{Simple Ruby library to talk to thetvdb.com's v4 API.}
14
14
  spec.homepage = "https://github.com/davidpan/tvdb_party_v2"
15
15
  spec.license = "MIT"
16
16
 
@@ -30,6 +30,10 @@ Gem::Specification.new do |spec|
30
30
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
31
31
  spec.require_paths = ["lib"]
32
32
 
33
- spec.add_development_dependency "bundler", "~> 1.16"
34
- spec.add_development_dependency "rake", "~> 10.0"
33
+ spec.required_ruby_version = ">= 3.0"
34
+
35
+ spec.add_runtime_dependency "httparty", "~> 0.24"
36
+ spec.add_development_dependency "bundler", ">= 2.0"
37
+ spec.add_development_dependency "rake", "~> 13.0"
38
+ spec.add_development_dependency "webmock", "~> 3.0"
35
39
  end
metadata CHANGED
@@ -1,44 +1,71 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tvdb_party_v2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Pan
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2018-05-26 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: httparty
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.24'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.24'
13
26
  - !ruby/object:Gem::Dependency
14
27
  name: bundler
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '2.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
15
42
  requirement: !ruby/object:Gem::Requirement
16
43
  requirements:
17
44
  - - "~>"
18
45
  - !ruby/object:Gem::Version
19
- version: '1.16'
46
+ version: '13.0'
20
47
  type: :development
21
48
  prerelease: false
22
49
  version_requirements: !ruby/object:Gem::Requirement
23
50
  requirements:
24
51
  - - "~>"
25
52
  - !ruby/object:Gem::Version
26
- version: '1.16'
53
+ version: '13.0'
27
54
  - !ruby/object:Gem::Dependency
28
- name: rake
55
+ name: webmock
29
56
  requirement: !ruby/object:Gem::Requirement
30
57
  requirements:
31
58
  - - "~>"
32
59
  - !ruby/object:Gem::Version
33
- version: '10.0'
60
+ version: '3.0'
34
61
  type: :development
35
62
  prerelease: false
36
63
  version_requirements: !ruby/object:Gem::Requirement
37
64
  requirements:
38
65
  - - "~>"
39
66
  - !ruby/object:Gem::Version
40
- version: '10.0'
41
- description: Simple Ruby library to talk to thetvdb.com's v2 API.
67
+ version: '3.0'
68
+ description: Simple Ruby library to talk to thetvdb.com's v4 API.
42
69
  email:
43
70
  - davidpanchina@gmail.com
44
71
  executables: []
@@ -59,7 +86,10 @@ files:
59
86
  - lib/tvdb_party_v2/actor.rb
60
87
  - lib/tvdb_party_v2/banner.rb
61
88
  - lib/tvdb_party_v2/episode.rb
89
+ - lib/tvdb_party_v2/movie.rb
90
+ - lib/tvdb_party_v2/remote_id.rb
62
91
  - lib/tvdb_party_v2/search.rb
92
+ - lib/tvdb_party_v2/season.rb
63
93
  - lib/tvdb_party_v2/series.rb
64
94
  - lib/tvdb_party_v2/version.rb
65
95
  - tvdb_party_v2.gemspec
@@ -68,7 +98,6 @@ licenses:
68
98
  - MIT
69
99
  metadata:
70
100
  allowed_push_host: https://rubygems.org
71
- post_install_message:
72
101
  rdoc_options: []
73
102
  require_paths:
74
103
  - lib
@@ -76,16 +105,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
105
  requirements:
77
106
  - - ">="
78
107
  - !ruby/object:Gem::Version
79
- version: '0'
108
+ version: '3.0'
80
109
  required_rubygems_version: !ruby/object:Gem::Requirement
81
110
  requirements:
82
111
  - - ">="
83
112
  - !ruby/object:Gem::Version
84
113
  version: '0'
85
114
  requirements: []
86
- rubyforge_project:
87
- rubygems_version: 2.6.14.1
88
- signing_key:
115
+ rubygems_version: 4.0.21
89
116
  specification_version: 4
90
- summary: Simple Ruby library to talk to thetvdb.com's v2 API.
117
+ summary: Simple Ruby library to talk to thetvdb.com's v4 API.
91
118
  test_files: []