thegamesdb 1.1.2 → 2.0.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 +4 -4
- data/.github/workflows/master.yml +26 -0
- data/.github/workflows/rubocop.yml +20 -0
- data/.rubocop.yml +39 -0
- data/CHANGELOG.md +42 -0
- data/Gemfile +2 -0
- data/README.md +313 -52
- data/Rakefile +3 -0
- data/lib/thegamesdb.rb +115 -215
- data/lib/thegamesdb/developers.rb +17 -0
- data/lib/thegamesdb/games.rb +130 -0
- data/lib/thegamesdb/genres.rb +13 -0
- data/lib/thegamesdb/images.rb +7 -0
- data/lib/thegamesdb/platforms.rb +106 -0
- data/lib/thegamesdb/publishers.rb +15 -0
- data/lib/thegamesdb/version.rb +3 -1
- data/test/client_test.rb +16 -0
- data/test/developers_test.rb +16 -0
- data/test/games_test.rb +92 -42
- data/test/genres_test.rb +16 -0
- data/test/platform_test.rb +85 -51
- data/test/publishers_test.rb +16 -0
- data/test/test_helper.rb +3 -2
- data/thegamesdb.gemspec +14 -9
- metadata +23 -8
- data/.travis.yml +0 -24
- data/lib/thegamesdb/config.rb +0 -9
data/Rakefile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'rake/testtask'
|
2
4
|
require 'bundler/gem_tasks'
|
3
5
|
|
@@ -5,6 +7,7 @@ Rake::TestTask.new('test') do |t|
|
|
5
7
|
t.pattern = 'test/**/*_test.rb'
|
6
8
|
end
|
7
9
|
|
10
|
+
desc 'Run a Ruby console with gamesdb already loaded'
|
8
11
|
task :console do
|
9
12
|
require 'irb'
|
10
13
|
require 'irb/completion'
|
data/lib/thegamesdb.rb
CHANGED
@@ -1,246 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'thegamesdb/version'
|
2
|
-
require 'thegamesdb/
|
4
|
+
require 'thegamesdb/developers'
|
5
|
+
require 'thegamesdb/games'
|
6
|
+
require 'thegamesdb/genres'
|
7
|
+
require 'thegamesdb/platforms'
|
8
|
+
require 'thegamesdb/publishers'
|
3
9
|
require 'net/http'
|
4
10
|
require 'json'
|
5
11
|
|
6
|
-
# Client for TheGamesDB API (thegamesdb.net)
|
7
12
|
module Gamesdb
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# Parameters: platform id (int), page (int)
|
16
|
-
#
|
17
|
-
# = Returns:
|
18
|
-
# Array of Hashes with games info
|
19
|
-
#
|
20
|
-
def self.games_by_platform_id(platform_id, page = 1)
|
21
|
-
url = 'Games/ByPlatformID'
|
22
|
-
params = {id: platform_id, page: page, include: 'boxart'}
|
23
|
-
data = json_response(url, params)
|
24
|
-
process_platform_games(data)
|
25
|
-
end
|
13
|
+
# Client for TheGamesDB API (thegamesdb.net)
|
14
|
+
class Client
|
15
|
+
include Gamesdb::Developers
|
16
|
+
include Gamesdb::Games
|
17
|
+
include Gamesdb::Genres
|
18
|
+
include Gamesdb::Platforms
|
19
|
+
include Gamesdb::Publishers
|
26
20
|
|
27
|
-
|
28
|
-
|
29
|
-
#
|
30
|
-
# Parameters: none
|
31
|
-
#
|
32
|
-
# == Returns:
|
33
|
-
# Array of Hashes with platforms info
|
34
|
-
#
|
35
|
-
def self.platforms
|
36
|
-
url = 'Platforms'
|
37
|
-
params = { fields: 'icon,console,controller,developer,manufacturer,media,cpu,memory,graphics,sound,maxcontrollers,display,overview,youtube' }
|
38
|
-
data = json_response(url, params)
|
39
|
-
|
40
|
-
data['data']['platforms'].map do |p|
|
41
|
-
symbolize_keys(p.last)
|
42
|
-
end
|
43
|
-
end
|
21
|
+
BASE_URL = 'https://api.thegamesdb.net/v1/'
|
22
|
+
IMAGES_BASE_URL = 'https://legacy.thegamesdb.net/banners/'
|
44
23
|
|
45
|
-
|
46
|
-
# specified Platform ID.
|
47
|
-
# https://api.thegamesdb.net/#/operations/Platforms/PlatformsByPlatformID
|
48
|
-
#
|
49
|
-
# Parameters:
|
50
|
-
# - id - (int) The numeric ID of the platform in the GamesDB database
|
51
|
-
#
|
52
|
-
# == Returns:
|
53
|
-
# Hash with platform info
|
54
|
-
#
|
55
|
-
def self.platform_by_id(id)
|
56
|
-
url = 'Platforms/ByPlatformID'
|
57
|
-
params = {
|
58
|
-
id: id,
|
59
|
-
fields: 'icon,console,controller,developer,manufacturer,media,cpu,memory,graphics,sound,maxcontrollers,display,overview,youtube'
|
60
|
-
}
|
61
|
-
data = json_response(url, params)
|
62
|
-
|
63
|
-
response = data['data']['platforms'].values.first
|
64
|
-
symbolize_keys(response)
|
65
|
-
end
|
24
|
+
attr_reader :remaining_monthly_allowance, :extra_allowance, :allowance_refresh_timer
|
66
25
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
#
|
71
|
-
# Parameters:
|
72
|
-
# - id - (int) Game id
|
73
|
-
#
|
74
|
-
# == Returns:
|
75
|
-
# Hash with game info
|
76
|
-
#
|
77
|
-
def self.game_by_id(id)
|
78
|
-
url = 'Games/ByGameID'
|
79
|
-
params = {
|
80
|
-
id: id,
|
81
|
-
fields: 'players,publishers,genres,overview,last_updated,rating,platform,coop,youtube,os,processor,ram,hdd,video,sound,alternates',
|
82
|
-
include: 'boxart,platform'
|
83
|
-
}
|
84
|
-
data = json_response(url, params)
|
85
|
-
return [] if data['data']['count'] == 0
|
86
|
-
symbolize_keys(data['data']['games'].first)
|
87
|
-
end
|
26
|
+
def initialize(api_key)
|
27
|
+
@api_key = api_key
|
28
|
+
end
|
88
29
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
}
|
110
|
-
unless platform.nil?
|
111
|
-
params.merge!("filter[platform]" => platform)
|
30
|
+
# Perform request
|
31
|
+
#
|
32
|
+
# Used by every API endpoint, but can also be used manually.
|
33
|
+
#
|
34
|
+
# @param url [String] Required
|
35
|
+
# @param params [Hash] optional
|
36
|
+
#
|
37
|
+
# @return [Hash] Parsed JSON response
|
38
|
+
def perform_request(url, params = {})
|
39
|
+
raise ArgumentError, 'You need to set the API KEY to use the GamesDB API' unless @api_key
|
40
|
+
|
41
|
+
params = params.merge({ apikey: @api_key })
|
42
|
+
uri = URI(BASE_URL + url)
|
43
|
+
uri.query = URI.encode_www_form(params)
|
44
|
+
response = JSON.parse(Net::HTTP.get_response(uri).body)
|
45
|
+
refresh_allowances(response)
|
46
|
+
response
|
47
|
+
rescue StandardError => e
|
48
|
+
# TODO: Handle errors
|
49
|
+
raise e
|
112
50
|
end
|
113
51
|
|
114
|
-
|
115
|
-
process_platform_games(data)
|
116
|
-
end
|
52
|
+
private
|
117
53
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
#
|
124
|
-
# Parameters
|
125
|
-
# - id - (integer) The numeric ID of the game in Gamesdb that you
|
126
|
-
# like to fetch artwork details for
|
127
|
-
#
|
128
|
-
# == Returns:
|
129
|
-
# Hash with game art info: fanart (array), boxart (Hash, :front,
|
130
|
-
# :back), screenshots (array), fanart (array)
|
131
|
-
#
|
132
|
-
def self.game_images(id)
|
133
|
-
url = 'Games/Images'
|
134
|
-
data = json_response(url, games_id: id)
|
135
|
-
return [] if data.dig('data', 'count') == (0 || nil)
|
136
|
-
|
137
|
-
response = {}
|
138
|
-
response[:base_url] = data['data']['base_url']['original']
|
139
|
-
response[:logo] = process_logo(data['data'], id)
|
140
|
-
response[:boxart] = process_covers(data['data'], id)
|
141
|
-
response[:screenshot] = process_screenshots(data['data'], id)
|
142
|
-
response[:fanart] = process_fanart(data['data'], id)
|
143
|
-
response
|
144
|
-
end
|
145
|
-
|
146
|
-
def self.process_logo(data, id)
|
147
|
-
logo = data['images'][id.to_s].select { |a| a['type'] == "clearlogo" }
|
148
|
-
logo.empty? ? '' : logo.first['filename']
|
149
|
-
end
|
54
|
+
def refresh_allowances(response)
|
55
|
+
@remaining_monthly_allowance = response['remaining_monthly_allowance']
|
56
|
+
@extra_allowance = response['extra_allowance']
|
57
|
+
@allowance_refresh_timer = response['allowance_refresh_timer']
|
58
|
+
end
|
150
59
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
a['type'] == 'fanart'
|
60
|
+
def process_logo(data, id)
|
61
|
+
logo = data['images'][id.to_s].select { |a| a['type'] == 'clearlogo' }
|
62
|
+
logo.empty? ? '' : logo.first['filename']
|
155
63
|
end
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
width: width,
|
163
|
-
height: height
|
164
|
-
}
|
64
|
+
|
65
|
+
def process_fanart(data, id)
|
66
|
+
fanart = select_images(data, id, 'fanart')
|
67
|
+
return [] if fanart.empty?
|
68
|
+
|
69
|
+
fanart.map { |art| build_individual_fanart(art) }
|
165
70
|
end
|
166
|
-
fanarts
|
167
|
-
end
|
168
71
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
end
|
72
|
+
def process_covers(data, id)
|
73
|
+
covers = {}
|
74
|
+
boxart = select_images(data, id, 'boxart')
|
75
|
+
return [] if boxart.empty?
|
174
76
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
77
|
+
boxart.each do |art|
|
78
|
+
width, height = art['resolution'].split('x') unless art['resolution'].nil?
|
79
|
+
covers[art['side'].to_sym] = art_structure(art, width, height)
|
80
|
+
end
|
81
|
+
covers
|
179
82
|
end
|
180
|
-
|
181
|
-
|
182
|
-
width, height = art['resolution'].split(
|
183
|
-
|
83
|
+
|
84
|
+
def build_individual_fanart(art)
|
85
|
+
width, height = art['resolution'].split('x') unless art['resolution'].nil?
|
86
|
+
art_structure(art, width, height)
|
87
|
+
end
|
88
|
+
|
89
|
+
def art_structure(art, width, height)
|
90
|
+
{
|
184
91
|
url: art['filename'],
|
185
92
|
resolution: art['resolution'],
|
186
93
|
width: width,
|
187
94
|
height: height
|
188
95
|
}
|
189
96
|
end
|
190
|
-
covers
|
191
|
-
end
|
192
97
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
@configuration ||= Config.new
|
197
|
-
end
|
198
|
-
|
199
|
-
# Api call and xml parsing
|
200
|
-
def self.json_response(url, params = {})
|
201
|
-
params = params.merge({apikey: configuration.api_key})
|
202
|
-
|
203
|
-
uri = URI(BASE_URL + url)
|
204
|
-
uri.query = URI.encode_www_form(params)
|
205
|
-
request = Net::HTTP.get_response(uri)
|
206
|
-
response = JSON.parse(request.body)
|
207
|
-
response
|
208
|
-
end
|
98
|
+
def process_screenshots(data, id)
|
99
|
+
select_images(data, id, 'screenshot').map { |b| symbolize_keys(b) }
|
100
|
+
end
|
209
101
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
data['data']['games'].each do |elem|
|
215
|
-
id = elem['id']
|
216
|
-
games << {
|
217
|
-
name: elem['game_title'],
|
218
|
-
id: id,
|
219
|
-
release_date: elem['release_date'],
|
220
|
-
platform: elem['platform'],
|
221
|
-
developers: elem['developers'],
|
222
|
-
players: elem['players'],
|
223
|
-
publishers: elem['publishers'],
|
224
|
-
genres: elem['genres'],
|
225
|
-
overview: elem['overview'],
|
226
|
-
last_updated: elem['last_updated'],
|
227
|
-
rating: elem['rating'],
|
228
|
-
coop: elem['coop'],
|
229
|
-
youtube: elem['youtube'],
|
230
|
-
alternates: elem['alternates'],
|
231
|
-
image: if boxart = data.dig('include', 'boxart', 'data', id.to_s)
|
232
|
-
data['include']['boxart']['base_url']['original'] +
|
233
|
-
boxart.select { |a| a['side'] == 'front' }.first['filename'] || ''
|
234
|
-
end
|
235
|
-
}
|
102
|
+
def select_images(data, id, image_type)
|
103
|
+
data['images'][id.to_s].select do |a|
|
104
|
+
a['type'] == image_type
|
105
|
+
end
|
236
106
|
end
|
237
|
-
games
|
238
|
-
end
|
239
107
|
|
240
|
-
|
241
|
-
|
242
|
-
|
108
|
+
# Process games for platform_games
|
109
|
+
# rubocop:disable Metrics/AbcSize
|
110
|
+
# rubocop:disable Metrics/MethodLength
|
111
|
+
def process_platform_games(data)
|
112
|
+
data['data']['games'].map do |elem|
|
113
|
+
{
|
114
|
+
name: elem['game_title'],
|
115
|
+
id: elem['id'],
|
116
|
+
release_date: elem['release_date'],
|
117
|
+
platform: elem['platform'],
|
118
|
+
developers: elem['developers'],
|
119
|
+
players: elem['players'],
|
120
|
+
publishers: elem['publishers'],
|
121
|
+
genres: elem['genres'],
|
122
|
+
overview: elem['overview'],
|
123
|
+
last_updated: elem['last_updated'],
|
124
|
+
rating: elem['rating'],
|
125
|
+
coop: elem['coop'],
|
126
|
+
youtube: elem['youtube'],
|
127
|
+
alternates: elem['alternates'],
|
128
|
+
image: if (boxart = data.dig('include', 'boxart', 'data', elem['id'].to_s))
|
129
|
+
data['include']['boxart']['base_url']['original'] +
|
130
|
+
boxart.select { |a| a['side'] == 'front' }.first['filename'] || ''
|
131
|
+
end
|
132
|
+
}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
# rubocop:enable Metrics/AbcSize
|
136
|
+
# rubocop:enable Metrics/MethodLength
|
137
|
+
|
138
|
+
def symbolize_keys(hash)
|
139
|
+
new_hash = {}
|
140
|
+
hash.each_key do |key|
|
141
|
+
new_hash[key.to_sym] = hash.delete(key)
|
142
|
+
end
|
143
|
+
new_hash
|
243
144
|
end
|
244
|
-
hash
|
245
145
|
end
|
246
146
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamesdb
|
4
|
+
# Developers related API Endpoints
|
5
|
+
module Developers
|
6
|
+
# Fetches Developers list
|
7
|
+
#
|
8
|
+
# @see https://api.thegamesdb.net/#/Developers/Developers
|
9
|
+
#
|
10
|
+
# @return Array of Hashes with id and name as keys
|
11
|
+
def developers
|
12
|
+
url = 'Developers'
|
13
|
+
data = perform_request(url)
|
14
|
+
data['data']['developers'].map { |_id, developer| developer }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gamesdb
|
4
|
+
# Games related API Endpoints
|
5
|
+
module Games
|
6
|
+
# Method for listing platform's games
|
7
|
+
#
|
8
|
+
# @see https://api.thegamesdb.net/#/Games/GamesByPlatformID
|
9
|
+
#
|
10
|
+
# @param id [Integer]
|
11
|
+
# @param page [Integer]
|
12
|
+
#
|
13
|
+
# @return [Array] Array of Hashes with games info
|
14
|
+
#
|
15
|
+
def games_by_platform_id(platform_id, page = 1)
|
16
|
+
url = 'Games/ByPlatformID'
|
17
|
+
params = { id: platform_id, page: page, include: 'boxart' }
|
18
|
+
data = perform_request(url, params)
|
19
|
+
process_platform_games(data)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Method for getting game info
|
23
|
+
#
|
24
|
+
# @see https://api.thegamesdb.net/#/Games/GamesByGameID
|
25
|
+
#
|
26
|
+
# @param id [Integer|String] Game id or string of ',' delimited list
|
27
|
+
#
|
28
|
+
# @return [Array|Hash] Hash with game info
|
29
|
+
#
|
30
|
+
# rubocop:disable Metrics/MethodLength
|
31
|
+
def games_by_id(id)
|
32
|
+
url = 'Games/ByGameID'
|
33
|
+
params = {
|
34
|
+
id: id,
|
35
|
+
fields:
|
36
|
+
'players,publishers,genres,overview,last_updated,rating,platform,coop,youtube,os,processor,ram,hdd,'\
|
37
|
+
'video,sound,alternates',
|
38
|
+
include: 'boxart,platform'
|
39
|
+
}
|
40
|
+
data = perform_request(url, params)
|
41
|
+
return [] if (data['data']['count']).zero?
|
42
|
+
|
43
|
+
games = data['data']['games']
|
44
|
+
return symbolize_keys(games.first) if games.count == 1
|
45
|
+
|
46
|
+
games.map { |game| symbolize_keys(game) }
|
47
|
+
end
|
48
|
+
# rubocop:enable Metrics/MethodLength
|
49
|
+
|
50
|
+
# The GetGamesList API search returns a listing of games matched up with loose search terms.
|
51
|
+
#
|
52
|
+
# @see https://api.thegamesdb.net/#/Games/GamesByGameName
|
53
|
+
#
|
54
|
+
# @param name [String] game name (required)
|
55
|
+
# @param platform [Integer] (optional - platform id)
|
56
|
+
# @param page [Integer] (optional)
|
57
|
+
#
|
58
|
+
# @return [Hash] Hash with game info: id, name (not-unique), release_date, platform, etc.
|
59
|
+
#
|
60
|
+
# rubocop:disable Metrics/MethodLength
|
61
|
+
def games_by_name(name, platform: nil, page: 1)
|
62
|
+
url = 'Games/ByGameName'
|
63
|
+
params = {
|
64
|
+
fields:
|
65
|
+
'players,publishers,genres,overview,last_updated,rating,platform,coop,youtube,os,processor,ram,hdd'\
|
66
|
+
',video,sound,alternates',
|
67
|
+
include: 'boxart',
|
68
|
+
name: name,
|
69
|
+
page: page
|
70
|
+
}
|
71
|
+
params.merge!('filter[platform]' => platform) unless platform.nil?
|
72
|
+
|
73
|
+
data = perform_request(url, params)
|
74
|
+
process_platform_games(data)
|
75
|
+
end
|
76
|
+
# rubocop:enable Metrics/MethodLength
|
77
|
+
|
78
|
+
# This API feature returns a list of available artwork types and
|
79
|
+
# locations specific to the requested game id in the database. It
|
80
|
+
# also lists the resolution of any images available. Scrapers can be
|
81
|
+
# set to use a minimum or maximum resolution for specific images
|
82
|
+
#
|
83
|
+
# @see https://api.thegamesdb.net/#/Games/GamesImages
|
84
|
+
#
|
85
|
+
# @param id [Integer] The numeric ID of the game in Gamesdb that you like to fetch artwork details for
|
86
|
+
#
|
87
|
+
# @return [Hash] Hash with game art info: fanart (array), boxart (Hash,
|
88
|
+
# :front, :back), screenshots (array), fanart (array)
|
89
|
+
#
|
90
|
+
# rubocop:disable Metrics/AbcSize
|
91
|
+
def games_images(id)
|
92
|
+
url = 'Games/Images'
|
93
|
+
data = perform_request(url, games_id: id)
|
94
|
+
return [] if data.dig('data', 'count') == (0 || nil)
|
95
|
+
|
96
|
+
response = {}
|
97
|
+
response[:base_url] = data['data']['base_url']['original']
|
98
|
+
response[:logo] = process_logo(data['data'], id)
|
99
|
+
response[:boxart] = process_covers(data['data'], id)
|
100
|
+
response[:screenshot] = process_screenshots(data['data'], id)
|
101
|
+
response[:fanart] = process_fanart(data['data'], id)
|
102
|
+
response
|
103
|
+
end
|
104
|
+
|
105
|
+
# Fetch games update
|
106
|
+
#
|
107
|
+
# @see https://api.thegamesdb.net/#/Games/GamesUpdates
|
108
|
+
#
|
109
|
+
# @param last_edit_id [Integer] Required
|
110
|
+
# @param time [Integer] (optional)
|
111
|
+
# @param page [Integer] results page offset to return (optional)
|
112
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
113
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
114
|
+
def games_update(last_edit_id, arguments = {})
|
115
|
+
url = 'Games/Updates'
|
116
|
+
params = arguments.merge({ last_edit_id: last_edit_id })
|
117
|
+
data = perform_request(url, params)
|
118
|
+
|
119
|
+
regexp = /page=([0-9]+)/
|
120
|
+
response = {}
|
121
|
+
response[:updates] = data['data']['updates']
|
122
|
+
response[:previous_page] = data.dig('pages', 'previous')&.match(regexp)&.captures&.first&.to_i
|
123
|
+
response[:next_page] = data.dig('pages', 'next')&.match(regexp)&.captures&.first&.to_i
|
124
|
+
response
|
125
|
+
end
|
126
|
+
# rubocop:enable Metrics/AbcSize
|
127
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
128
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
129
|
+
end
|
130
|
+
end
|