srl-api 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0f91b1fa2a4677c8f682dc2b4a67a6d663430958
4
+ data.tar.gz: 2ca3b63a2e19f54087825cf3f476ef45d50ef3a0
5
+ SHA512:
6
+ metadata.gz: '0181c321261059c20869a9dcfccd53b9313c45e118cc0e7c0d43905a5f0c3baa72cda609b81d1c614101ac88b4490f6f67cd50355717aea1153f7f315d96ec68'
7
+ data.tar.gz: 3120014cc38f42ef7d6066122e46d842d291f76bb51da0931e310df46bc599f017164f8dc706d656fd14a3d18c2b569b38bc7817a0a9d81f56e42e9b0e463f6f
@@ -0,0 +1,35 @@
1
+ SpeedRunsLive Ruby Client
2
+ =========================
3
+
4
+ Ruby library to retrieve data from SpeedRunsLive.com.
5
+
6
+
7
+ ## Installation
8
+
9
+ The gem is hosted on RubyGems, so you can either install it
10
+ with `gem install srl-api` or, if you are using bundler, by adding
11
+ it to your program's Gemfile and running `bundle install`.
12
+
13
+
14
+ ## Documentation
15
+
16
+ `rdoc lib/` from the project root, or let RubyGems do it for you in $GEM_HOME.
17
+
18
+ ## Usage
19
+
20
+ Short version:
21
+
22
+ require 'srl'
23
+
24
+ puts SRL.past_races(player: 'Artea', game: 'ffhacks').size # => Some number probably in the low 20s.
25
+
26
+ The `examples/` directory should provide you with an adequate amount
27
+ of usable, real-world examples.
28
+
29
+ Client code should focus on the `SRL` module, with its relevant functions
30
+ being found in `lib/srl/api.rb`.
31
+
32
+
33
+ ## Maintainer
34
+
35
+ Brian Edmonds "Artea" <brian@bedmonds.net>
@@ -0,0 +1,11 @@
1
+ require_relative 'srl/api'
2
+
3
+ module SRL
4
+ # The current version of srl-api.
5
+ RELEASE = '0.2.0'.freeze
6
+
7
+ # Return the current release version as a dotted string.
8
+ def self.release
9
+ RELEASE
10
+ end
11
+ end
@@ -0,0 +1,89 @@
1
+ require 'net/http'
2
+ require 'json'
3
+
4
+ require_relative 'typedefs'
5
+ require_relative 'utils'
6
+
7
+ module SRL
8
+ class << self
9
+ # Fetch usage data about a specific game, identified
10
+ # by its abbreviation.
11
+ #
12
+ # call-seq: game(abbreviation) -> obj
13
+ def game(abbrev)
14
+ res = query("stat", game: abbrev)
15
+ game = Game.from_hash(res.fetch('game'))
16
+ game.stats = res.fetch('stats')
17
+
18
+ game
19
+ end
20
+
21
+ # Fetch a player's public profile, by name.
22
+ # Raises a NameError if the given name does not exist.
23
+ #
24
+ # call-seq: player(name) -> obj
25
+ def player(name)
26
+ player = Player.from_hash(query("players/#{name}"))
27
+
28
+ raise NameError, "Player '#{name}' not found." unless player.exists?
29
+
30
+ player
31
+ end
32
+
33
+ # Fetch the leaderboard for a specific game, identified
34
+ # by its abbreviation.
35
+ #
36
+ # call-seq: leaderboard(abbrev) -> obj
37
+ def leaderboard(abbrev)
38
+ SRL::Utils.collection(
39
+ query("leaderboard/#{abbrev}").fetch('leaders'),
40
+ Player
41
+ )
42
+ end
43
+
44
+ # Return an array of Race objects for races currently being
45
+ # run or set up.
46
+ #
47
+ # call-seq: current_races -> array
48
+ def current_races
49
+ SRL::Utils.collection(query('races').fetch('races'), Race)
50
+ end
51
+
52
+ # Return an array of PastRace objects for completed races.
53
+ # call-seq: current_races -> array
54
+ def current_races
55
+ def completed_races(args = {})
56
+ res = query('pastraces', args)
57
+ ResultSet.new(
58
+ SRL::Utils.collection(res.fetch('pastraces'), PastRace),
59
+ count: res.fetch('count'),
60
+ page: args.fetch(:page, 1),
61
+ page_size: args.fetch(:page_size, 25)
62
+ )
63
+ end
64
+ alias past_races completed_races
65
+
66
+ private
67
+
68
+ # SpeedRunsLive API URL.
69
+ API = 'http://api.speedrunslive.com/'.freeze
70
+
71
+ # Return a hash with the results of a query to the SRL API.
72
+ def query(url, params = {})
73
+ url = URI([API, url].join) # *hiss* "URI" has been wrong for years!
74
+ url.query = URI.encode_www_form(params) unless params.empty?
75
+
76
+ res = Net::HTTP.get_response(url)
77
+ raise NetworkError, res unless res.is_a?(Net::HTTPSuccess)
78
+
79
+ JSON.parse(res.body)
80
+ end
81
+
82
+ end
83
+ # Raised when an HTTP request to the SRL API server fails,
84
+ # whether due to a malformed request or the server being down.
85
+ #
86
+ # [FIXME] Add actual descriptions of what fucked up and
87
+ # possible solutions to the issue.
88
+ class NetworkError < StandardError; end
89
+ end
@@ -0,0 +1,337 @@
1
+ require 'time'
2
+
3
+ require_relative 'unmarshalable'
4
+
5
+ module SRL
6
+ class Game
7
+ include Unmarshalable
8
+
9
+ # This game's ID as per the SRL data.
10
+ attr_reader :oid
11
+ alias game_id oid
12
+
13
+ # This game's complete name.
14
+ attr_reader :name
15
+
16
+ # This game's abbreviation, as used by RaceBot.
17
+ attr_reader :abbrev
18
+ alias abbreviation abbrev
19
+ alias short_name abbrev
20
+
21
+ # This game's popularity rating, according to SRL data.
22
+ attr_reader :popularity
23
+
24
+ # This game's position in the popularity contest,
25
+ # according to SRL data.
26
+ attr_reader :popularityrank
27
+ alias popularity_rank popularityrank
28
+
29
+ # The amount of ranked players on this game's leaderboard.
30
+ attr_reader :leadersCount
31
+ alias leaders_count leadersCount
32
+ alias num_leaders leadersCount
33
+
34
+ # This game's leaderboard, as an array of Players.
35
+ #
36
+ # While generally sorted by rank, this method does not
37
+ # guarantee the order of the players return.
38
+ #
39
+ # If you absolutely need them sorted, use the `leaders_by_rank`
40
+ # method or call `sort_by(&:rank)` on this value.
41
+ attr_reader :leaders
42
+ def leaders=(arr)
43
+ @leaders = SRL::Utils.collection(arr, Runner)
44
+ end
45
+
46
+ # Statistics about this game. Things like the number of races,
47
+ # number of players, total time played and raced.
48
+ #
49
+ # [SEE] SRL::Statistics
50
+ attr_accessor :stats
51
+ def stats=(val)
52
+ @statistics =
53
+ val.is_a?(Statistics) ? val
54
+ : Statistics.from_hash(val)
55
+ end
56
+ alias statistics stats
57
+
58
+ # An array of players on this game's leaderboard, sorted by their
59
+ # rank.
60
+ #
61
+ #
62
+ def leaders_by_rank(dir = :asc)
63
+ raise ArgumentError unless %i(asc desc).include?(dir)
64
+
65
+ dir == :asc ? leaders.sort_by(&:rank)
66
+ : leaders.sort_by(&:rank).reverse
67
+ end
68
+
69
+ class Runner
70
+ include Unmarshalable
71
+
72
+ # This player's name on the SRL website.
73
+ attr_reader :name
74
+
75
+ # This player's TrueSkill rating for a particular game.
76
+ attr_reader :trueskill
77
+ alias rating trueskill
78
+
79
+ # This player's position on the leaderboards for a specific game.
80
+ attr_reader :rank
81
+ alias position rank
82
+ end
83
+ end
84
+
85
+ class Race
86
+ include Unmarshalable
87
+
88
+ STATES = [
89
+ :open,
90
+ :unknown,
91
+ :in_progress,
92
+ :complete
93
+ ].freeze
94
+
95
+ # The IRC channel suffix for this race.
96
+ attr_reader :oid
97
+ alias channel oid
98
+
99
+ # The state of this race. Entry Open / In Progress / Completed
100
+ # [FIXME] Switch to enum-like behaviour with symbols.
101
+ attr_reader :state
102
+
103
+ def status
104
+ STATES[state - 1]
105
+ rescue
106
+ :unknown
107
+ end
108
+
109
+ # The game associate with this race.
110
+ attr_reader :game
111
+ def game=(game)
112
+ @game = game.is_a?(Game) ? game
113
+ : Game.from_hash(game)
114
+ end
115
+
116
+ # The players enlisted in this race as an array of Entrants.
117
+ attr_reader :entrants
118
+ def entrants=(arr)
119
+ # Account for the api returning a hash with the entrants' name
120
+ # as key instead of an array, despite repeating the entrants name
121
+ # in `displayname`.
122
+ arr = arr.is_a?(Hash) ? arr.values : arr
123
+ @entrants = SRL::Utils.collection(arr, Entrant)
124
+ end
125
+
126
+ class Entrant
127
+ include Unmarshalable
128
+
129
+ # This entrant's player name.
130
+ attr_reader :displayname
131
+ alias name displayname
132
+
133
+ # This entrant's Twitch account name.
134
+ attr_reader :twitch
135
+
136
+ # The position that this entrant finished this race in.
137
+ attr_reader :place
138
+ alias position place
139
+
140
+ # The comment entered by this entrant for this race,
141
+ # if applicable.
142
+ attr_reader :message
143
+ alias comment message
144
+
145
+ # The number of seconds that this entrant took to complete
146
+ # the race goal.
147
+ #
148
+ # = Notes
149
+ # A time of -1 indicates a forfeit.
150
+ attr_reader :time
151
+
152
+ # Did this entrant forfeit the race?
153
+ def forfeit?
154
+ time == -1
155
+ end
156
+
157
+ # The state of this entrant in the race.
158
+ # Is he ready, finished, neither?
159
+ #
160
+ # [FIXME] Switch to an enum-like implementation with symbols.
161
+ attr_reader :statetext
162
+ end
163
+ end
164
+
165
+ class PastRace
166
+ include Unmarshalable
167
+
168
+ attr_reader :game
169
+ def game=(game)
170
+ @game = game.is_a?(Game) ? game : Game.from_hash(game)
171
+ end
172
+
173
+ attr_reader :date
174
+ def date=(val)
175
+ @date = Time.at(val.to_i).utc.to_datetime
176
+ end
177
+
178
+ attr_reader :goal
179
+
180
+ attr_reader :results
181
+ def results=(arr)
182
+ @results = SRL::Utils.collection(arr, Result)
183
+ end
184
+
185
+ # Result of an individual racer's time and rating adjustments.
186
+ class Result
187
+ include Unmarshalable
188
+
189
+ # ID of the race this result entry is associated with.
190
+ #
191
+ # [NOTE] Not to be confused with the SRL Channel ID.
192
+ attr_reader :race
193
+ alias race_id race
194
+
195
+ # Which place did this runner finish in?
196
+ attr_reader :place
197
+ alias position place
198
+
199
+ # The runner's name
200
+ attr_reader :player
201
+ alias name player
202
+
203
+ # Number of seconds the run lasted.
204
+ attr_reader :time
205
+
206
+ # Optional comment entered by the runner.
207
+ attr_reader :message
208
+ alias comment message
209
+
210
+ attr_reader :oldtrueskill
211
+ alias old_rating oldtrueskill
212
+
213
+ attr_reader :newtrueskill
214
+ alias new_rating newtrueskill
215
+
216
+ attr_reader :trueskillchange
217
+ alias rating_adjustment trueskillchange
218
+ end
219
+ end
220
+
221
+ class Statistics
222
+ include Unmarshalable
223
+
224
+ # Number of races that a particular game has had.
225
+ attr_reader :totalRaces
226
+ alias total_races totalRaces
227
+
228
+ # Number of players that have participated in a race of a
229
+ # given game.
230
+ attr_reader :totalPlayers
231
+ alias total_players totalPlayers
232
+
233
+ # The ID of the race with the highest number of entrants.
234
+ attr_reader :largestRace
235
+ alias largest_race_id largestRace
236
+
237
+ # Number of entrants in the largest race of the game associated
238
+ # with these Statistics.
239
+ attr_reader :largestRaceSize
240
+ alias largest_race_player_count largestRaceSize
241
+ alias largest_race_size largestRaceSize
242
+
243
+ # Number of seconds that this game has been raced.
244
+ # A sum of the worst time of each race.
245
+ attr_reader :totalRaceTime
246
+ alias total_race_time totalRaceTime
247
+
248
+ # Number of seconds that this game has been played for.
249
+ # A sum of all the times in all races.
250
+ attr_reader :totalTimePlayed
251
+ alias total_time_played totalTimePlayed
252
+ end
253
+
254
+ # A registered user of SpeedRunsLive.com
255
+ class Player
256
+ include Unmarshalable
257
+
258
+ attr_reader :oid
259
+ alias player_id oid
260
+
261
+ # This player's registered name on SpeedRunsLive.
262
+ # [NOTE] This might not be the same name that he has registered on IRC.
263
+ attr_reader :name
264
+
265
+ # --
266
+ # Stream information for the player
267
+ # ++
268
+ # This player's profile name on a streaming service.
269
+ attr_reader :channel
270
+
271
+ # Streaming platform used by this player. For example: Twitch.
272
+ attr_reader :api
273
+ def api=(val)
274
+ @api = val.intern
275
+ end
276
+
277
+ # This player's YouTube channel.
278
+ attr_reader :youtube
279
+
280
+ # This player's Twitter name.
281
+ attr_reader :twitter
282
+
283
+ # URL to this player's stream.
284
+ #
285
+ # [FIXME] Add support for non-twitch streams.
286
+ def stream
287
+ api == :twitch ? "https://twitch.tv/#{channel}/"
288
+ : 'Unsupported'
289
+ end
290
+
291
+ # Does this player exist?
292
+ def exists?
293
+ player_id != 0
294
+ end
295
+ end
296
+
297
+ # Wrapper around various calls to paginated data, such as past races.
298
+ # Contains pagination information, and information on all records,
299
+ # on top of the current page's records.
300
+ class ResultSet
301
+ # Records for this result set.
302
+ # [NOTE]
303
+ # Always an array, though the type of object contained in the array
304
+ # can vary depending on the query that spawned it.
305
+ attr_reader :results
306
+ alias data results
307
+ alias records results
308
+ alias items results
309
+
310
+ # The page of this result set.
311
+ attr_reader :page
312
+
313
+ attr_reader :page_size
314
+ alias per_page page_size
315
+
316
+ # Total number of records matching the query for this result set.
317
+ attr_reader :count
318
+ alias total_records count
319
+ alias num_records count
320
+
321
+ def initialize(results, params = {})
322
+ @results = results
323
+ @page = params.fetch(:page)
324
+ @page_size = params.fetch(:page_size)
325
+ @count = params.fetch(:count)
326
+ end
327
+
328
+ def num_pages
329
+ (count.to_f / page_size.to_f).ceil
330
+ end
331
+ alias pages num_pages
332
+
333
+ def last_page?
334
+ page == num_pages
335
+ end
336
+ end
337
+ end
@@ -0,0 +1,61 @@
1
+ module SRL
2
+ # = Summary
3
+ # Extension class method to allow easy instantiation of objects from
4
+ # a hash, such as the ones returned in JSON form from the SRL API.
5
+ #
6
+ # = Notes
7
+ # Any "id" key is converted to "oid" to not overwrite the default
8
+ # Ruby Object.id behavior.
9
+ #
10
+ # = Usage
11
+ # Simply include this module in any class to add a `.from_hash` class
12
+ # method to instantiate it from a hash.
13
+ #
14
+ # So long as the hash passed to `from_hash` has keys, in string or
15
+ # symbol form, that match the name of methods that an instance of
16
+ # the class responds to, instance variables with that same name will
17
+ # be assigned the value pointed at in this hash.
18
+ #
19
+ # = Example
20
+ #
21
+ # class Player
22
+ # include SRL::Unmarshalable
23
+ #
24
+ # attr_reader :name
25
+ # attr_reader :rank
26
+ # attr_reader :trueskill
27
+ # end
28
+ #
29
+ # p = Player.from_hash({
30
+ # name: "Foobar",
31
+ # trueskill: 0xDEADBEEF,
32
+ # rank: 9001
33
+ # })
34
+ #
35
+ # puts "It's over 9000!" if p.rank > 9000 # => outputs "It's over 9000!"
36
+ module Unmarshalable
37
+ def self.included(base)
38
+ base.extend(ClassMethods)
39
+ end
40
+
41
+ module ClassMethods
42
+ def from_hash(hash = {})
43
+ obj = new
44
+
45
+ hash.each do |k, v|
46
+ # Be a good boy and do not overwrite the standard ruby Object.id
47
+ k = :oid if k.intern == :id
48
+ next unless obj.respond_to?(k)
49
+
50
+ if obj.respond_to?("#{k}=")
51
+ obj.send("#{k}=", v)
52
+ else
53
+ obj.instance_variable_set(:"@#{k}", v)
54
+ end
55
+ end
56
+
57
+ obj
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,19 @@
1
+ module SRL
2
+ module Utils
3
+ # Return an array of +klass+ objects from a source array
4
+ # of hashes.
5
+ #
6
+ # = Notes
7
+ # If +src+ is already an array of +klass+, this function simply
8
+ # returns +src+.
9
+ def self.collection(src, klass)
10
+ raise ArgumentError unless src.is_a?(Array)
11
+ raise ArgumentError unless klass.respond_to?(:from_hash)
12
+
13
+ return [] if src.empty?
14
+ return src if src.first.is_a?(klass)
15
+
16
+ src.map { |i| klass.from_hash(i) }
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: srl-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Brian 'Artea' Edmonds
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-13 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: |
14
+ Library to query the SpeedRunsLive.com API and deal with the results
15
+ in plain old Ruby.
16
+ email:
17
+ - brian@bedmonds.net
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files:
21
+ - README.md
22
+ files:
23
+ - README.md
24
+ - lib/srl.rb
25
+ - lib/srl/api.rb
26
+ - lib/srl/typedefs.rb
27
+ - lib/srl/unmarshalable.rb
28
+ - lib/srl/utils.rb
29
+ homepage: https://github.com/bedmonds/srl-api/
30
+ licenses:
31
+ - MIT
32
+ metadata: {}
33
+ post_install_message:
34
+ rdoc_options:
35
+ - "--main"
36
+ - README.md
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 2.3.0
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 2.6.11
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Ruby Client for the SpeedRunsLive.com API
55
+ test_files: []