thegamesdb 1.1.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|