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 +5 -5
- data/.gitignore +7 -0
- data/Gemfile.lock +30 -7
- data/README.md +25 -19
- data/Rakefile +2 -2
- data/lib/tvdb_party_v2/actor.rb +7 -6
- data/lib/tvdb_party_v2/banner.rb +21 -15
- data/lib/tvdb_party_v2/episode.rb +34 -25
- data/lib/tvdb_party_v2/movie.rb +42 -0
- data/lib/tvdb_party_v2/remote_id.rb +22 -0
- data/lib/tvdb_party_v2/search.rb +125 -87
- data/lib/tvdb_party_v2/season.rb +26 -0
- data/lib/tvdb_party_v2/series.rb +35 -33
- data/lib/tvdb_party_v2/version.rb +1 -1
- data/lib/tvdb_party_v2.rb +4 -0
- data/tvdb_party_v2.gemspec +8 -4
- metadata +42 -15
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7fa7d3d9796bb041f8623ddad6f372f4cfdbd46b743d1d54d1fb88e89a58c412
|
|
4
|
+
data.tar.gz: 60270c0b7c6a3e835833ceb0adf77cea4acfd7fb5cffc1d2526d96ced0233c9d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1507bef194bedb6d6d429cf1257b65c0356aea15572ff9e08d2cd05492749608ec0d45598784b9bdd22e181dc07eee34140fb7a1325a11198cb28bdb2fe713be
|
|
7
|
+
data.tar.gz: dddeb93d3e82a740758b073fc4b239809dbc0425ba313410c0cd0623baeabf67215719f2aae0e115d23379eef6fc013e9be13410ada4dc1158dde86fb3cb0497
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,26 +1,49 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
tvdb_party_v2 (0.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
41
|
+
bundler (>= 2.0)
|
|
20
42
|
httparty
|
|
21
43
|
minitest
|
|
22
|
-
rake (~>
|
|
44
|
+
rake (~> 13.0)
|
|
23
45
|
tvdb_party_v2!
|
|
46
|
+
webmock (~> 3.0)
|
|
24
47
|
|
|
25
48
|
BUNDLED WITH
|
|
26
|
-
|
|
49
|
+
4.0.21
|
data/README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
# TvdbPartyV2
|
|
2
2
|
|
|
3
|
-
[](https://badge.fury.io/rb/tvdb_party_v2)
|
|
3
|
+
[](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
|
|
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
|
|
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
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
49
|
-
|
|
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
|
-
#
|
|
53
|
-
|
|
54
|
-
|
|
59
|
+
# v4 new: movies (v4-only entity)
|
|
60
|
+
movies = @thetvdb.movies
|
|
61
|
+
movie = @thetvdb.get_movie_by_id(12345)
|
|
55
62
|
|
|
56
|
-
#
|
|
57
|
-
|
|
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/
|
|
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
|
|
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
data/lib/tvdb_party_v2/actor.rb
CHANGED
|
@@ -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["
|
|
8
|
-
@role = options["
|
|
9
|
-
@image = options["
|
|
10
|
-
@sortorder = options["
|
|
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
|
-
"
|
|
16
|
+
@image.start_with?('http') ? @image : "https://www.thetvdb.com/banners/" + @image
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
end
|
data/lib/tvdb_party_v2/banner.rb
CHANGED
|
@@ -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["
|
|
7
|
-
@banner_resolution = options["
|
|
8
|
-
@
|
|
9
|
-
@
|
|
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["
|
|
12
|
-
@ratingaverage = options["
|
|
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
|
-
|
|
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
|
-
|
|
23
|
+
banner_path_url(@path)
|
|
27
24
|
end
|
|
28
25
|
|
|
29
26
|
def thumb_url
|
|
30
|
-
|
|
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["
|
|
11
|
-
@number = options["
|
|
12
|
-
@name = options["
|
|
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["
|
|
18
|
-
@imdb_id = options["
|
|
19
|
-
|
|
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["
|
|
19
|
+
if options["image"].nil? || options["image"].empty?
|
|
26
20
|
@thumb = nil
|
|
27
21
|
else
|
|
28
|
-
@thumb = "
|
|
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["
|
|
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["
|
|
38
|
-
@rating = options["
|
|
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
|
-
|
|
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(@
|
|
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
|
data/lib/tvdb_party_v2/search.rb
CHANGED
|
@@ -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://
|
|
8
|
+
base_uri 'https://api4.thetvdb.com/v4'
|
|
6
9
|
headers "Content-Type" => "application/json"
|
|
7
10
|
attr_accessor :token, :language
|
|
8
11
|
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@token
|
|
16
|
-
|
|
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
|
-
|
|
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
|
|
35
|
-
|
|
33
|
+
response = self.get("/search?remote_id=#{RemoteId.for_imdb(imdb_id)}&type=series")
|
|
34
|
+
normalize_results(response)
|
|
35
|
+
end
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
42
|
+
# Get series details by series id
|
|
48
43
|
def get_series_by_id(series_id, language = self.language)
|
|
49
|
-
|
|
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
|
-
|
|
47
|
+
# Get episode details by episode id
|
|
58
48
|
def get_episode_by_id(episode_id, language = self.language)
|
|
59
|
-
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|
|
150
|
+
data
|
|
120
151
|
when Hash
|
|
121
|
-
[
|
|
152
|
+
[data]
|
|
122
153
|
else
|
|
123
154
|
[]
|
|
124
155
|
end
|
|
125
156
|
end
|
|
126
157
|
|
|
127
|
-
def
|
|
128
|
-
|
|
129
|
-
|
|
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
|
data/lib/tvdb_party_v2/series.rb
CHANGED
|
@@ -1,43 +1,36 @@
|
|
|
1
1
|
module TvdbPartyV2
|
|
2
2
|
|
|
3
3
|
class Series
|
|
4
|
-
attr_reader :client
|
|
5
|
-
attr_accessor :id, :name, :overview, :
|
|
6
|
-
:
|
|
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["
|
|
12
|
+
@name = options["name"]
|
|
12
13
|
@overview = options["overview"]
|
|
13
|
-
@network = options["
|
|
14
|
-
@runtime = options["
|
|
14
|
+
@network = company_name(options["originalNetwork"])
|
|
15
|
+
@runtime = options["averageRuntime"]
|
|
15
16
|
@air_time = options['airsTime'] if options['airsTime']
|
|
16
|
-
@imdb_id = options["
|
|
17
|
-
@status = options["status"]
|
|
18
|
-
@airs_dayofweek = options["
|
|
17
|
+
@imdb_id = RemoteId.imdb_from(options["remoteIds"])
|
|
18
|
+
@status = status_name(options["status"])
|
|
19
|
+
@airs_dayofweek = options["airsDays"]
|
|
19
20
|
|
|
20
|
-
|
|
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["
|
|
27
|
-
@rating = options["
|
|
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
|
-
|
|
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
|
-
|
|
43
|
+
client.get_banners(self, 'poster')
|
|
51
44
|
end
|
|
52
45
|
|
|
53
46
|
def fanart
|
|
54
|
-
|
|
47
|
+
client.get_banners(self, 'fanart')
|
|
55
48
|
end
|
|
56
49
|
|
|
57
50
|
def series_banners
|
|
58
|
-
|
|
51
|
+
client.get_banners(self, 'series')
|
|
59
52
|
end
|
|
60
53
|
|
|
61
54
|
def season_banners
|
|
62
|
-
|
|
55
|
+
client.get_banners(self, 'season')
|
|
63
56
|
end
|
|
64
57
|
|
|
65
58
|
def seasonwide
|
|
66
|
-
|
|
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
|
-
|
|
71
|
+
client.get_seasons(self)
|
|
80
72
|
end
|
|
81
73
|
|
|
82
|
-
|
|
83
74
|
def actors
|
|
84
|
-
|
|
75
|
+
client.get_actors(self)
|
|
85
76
|
end
|
|
86
77
|
|
|
87
78
|
def season(season_number)
|
|
88
|
-
|
|
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
|
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')
|
data/tvdb_party_v2.gemspec
CHANGED
|
@@ -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
|
|
13
|
-
spec.description = %q{Simple Ruby library to talk to thetvdb.com's
|
|
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.
|
|
34
|
-
|
|
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.
|
|
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:
|
|
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: '
|
|
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: '
|
|
53
|
+
version: '13.0'
|
|
27
54
|
- !ruby/object:Gem::Dependency
|
|
28
|
-
name:
|
|
55
|
+
name: webmock
|
|
29
56
|
requirement: !ruby/object:Gem::Requirement
|
|
30
57
|
requirements:
|
|
31
58
|
- - "~>"
|
|
32
59
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
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: '
|
|
41
|
-
description: Simple Ruby library to talk to thetvdb.com's
|
|
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
|
-
|
|
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
|
|
117
|
+
summary: Simple Ruby library to talk to thetvdb.com's v4 API.
|
|
91
118
|
test_files: []
|