kappa 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,142 +1,129 @@
1
- require 'httparty'
2
- require 'addressable/uri'
3
- require 'securerandom'
4
- require 'json'
5
- require 'singleton'
6
- require 'set'
7
-
8
- module Kappa
9
- # @private
10
- class ConnectionBase
11
- include HTTParty
12
-
13
- def initialize(base_url = DEFAULT_BASE_URL)
14
- @base_url = Addressable::URI.parse(base_url)
15
-
16
- # TODO: Expose client_id so clients of the library can (optionally) set this
17
- # themselves and avoid rate limiting. Clients should still have the option to
18
- # not set this and use a randomly generated ID.
19
-
20
- uuid = SecureRandom.uuid
21
- # TODO: Use current library version.
22
- @client_id = "Kappa-v1-#{uuid}"
23
- end
24
-
25
- def get(path, query = nil)
26
- request_url = @base_url + path
27
-
28
- headers = {
29
- 'Client-ID' => @client_id,
30
- }.merge(custom_headers)
31
-
32
- response = self.class.get(request_url, :headers => headers, :query => query)
33
-
34
- # TODO: Handle non-JSON response
35
- # TODO: Handle invalid JSON
36
- # TODO: Handle non-200 codes
37
- # TODO: Include HTTP status code in the return value
38
-
39
- json = response.body
40
- return JSON.parse(json)
41
- end
42
-
43
- def accumulate(options)
44
- path = options[:path]
45
- params = options[:params] || {}
46
- json = options[:json]
47
- sub_json = options[:sub_json]
48
- klass = options[:class]
49
-
50
- total_limit = options[:limit]
51
- page_limit = params[:limit] || 100
52
-
53
- objects = []
54
- ids = Set.new
55
-
56
- paginated(path, params) do |response_json|
57
- current_objects = response_json[json]
58
- current_objects.each do |object_json|
59
- object_json = object_json[sub_json] if sub_json
60
- object = klass.new(object_json)
61
- if ids.add?(object.id)
62
- objects << object
63
- if objects.count == total_limit
64
- return objects
65
- end
66
- end
67
- end
68
-
69
- !current_objects.empty? && (current_objects.count >= page_limit)
70
- end
71
-
72
- return objects
73
- end
74
-
75
- def paginated(path, params = {})
76
- limit = [params[:limit] || 100, 100].min
77
- offset = params[:offset] || 0
78
-
79
- path_uri = Addressable::URI.parse(path)
80
- query = { 'limit' => limit, 'offset' => offset }
81
- path_uri.query_values ||= {}
82
- path_uri.query_values = path_uri.query_values.merge(query)
83
-
84
- request_url = path_uri.to_s
85
-
86
- params = params.dup
87
- params.delete(:limit)
88
- params.delete(:offset)
89
-
90
- json = get(request_url, params)
91
-
92
- # TODO: Hande request retry.
93
- loop do
94
- break if json['error'] && (json['status'] == 503)
95
- break if !yield(json)
96
-
97
- links = json['_links']
98
- next_url = links['next']
99
-
100
- next_uri = Addressable::URI.parse(next_url)
101
- offset = next_uri.query_values['offset'].to_i
102
-
103
- total = json['_total']
104
- break if total && (offset > total)
105
-
106
- request_url = next_url
107
- json = get(request_url)
108
- end
109
- end
110
-
111
- private
112
- DEFAULT_BASE_URL = 'https://api.twitch.tv/kraken/'
113
- end
114
- end
115
-
116
- module Kappa::V2
117
- # @private
118
- module Connection
119
- class Impl < Kappa::ConnectionBase
120
- include Singleton
121
-
122
- private
123
- def custom_headers
124
- { 'Accept' => 'application/vnd.twitchtv.v2+json' }
125
- end
126
- end
127
-
128
- def self.included(base)
129
- base.extend(ClassMethods)
130
- end
131
-
132
- def connection
133
- Impl.instance
134
- end
135
-
136
- module ClassMethods
137
- def connection
138
- Impl.instance
139
- end
140
- end
141
- end
142
- end
1
+ require 'httparty'
2
+ require 'addressable/uri'
3
+ require 'securerandom'
4
+ require 'json'
5
+ require 'singleton'
6
+ require 'set'
7
+
8
+ module Kappa
9
+ # @private
10
+ class ConnectionBase
11
+ include HTTParty
12
+
13
+ def initialize(base_url = DEFAULT_BASE_URL)
14
+ @base_url = Addressable::URI.parse(base_url)
15
+
16
+ uuid = SecureRandom.uuid
17
+ @client_id = "Kappa-v1-#{uuid}"
18
+ end
19
+
20
+ def get(path, query = nil)
21
+ request_url = @base_url + path
22
+
23
+ headers = {
24
+ 'Client-ID' => @client_id,
25
+ }.merge(custom_headers)
26
+
27
+ response = self.class.get(request_url, :headers => headers, :query => query)
28
+
29
+ json = response.body
30
+ return JSON.parse(json)
31
+ end
32
+
33
+ def accumulate(options)
34
+ path = options[:path]
35
+ params = options[:params] || {}
36
+ json = options[:json]
37
+ sub_json = options[:sub_json]
38
+ klass = options[:class]
39
+
40
+ raise ArgumentError, 'json' if json.nil?
41
+ raise ArgumentError, 'path' if path.nil?
42
+ raise ArgumentError, 'class' if klass.nil?
43
+
44
+ total_limit = options[:limit]
45
+ page_limit = [total_limit || 100, 100].min
46
+ offset = options[:offset] || 0
47
+
48
+ objects = []
49
+ ids = Set.new
50
+
51
+ paginated(path, page_limit, offset, params) do |response_json|
52
+ current_objects = response_json[json]
53
+ current_objects.each do |object_json|
54
+ object_json = object_json[sub_json] if sub_json
55
+ object = klass.new(object_json)
56
+ if ids.add?(object.id)
57
+ objects << object
58
+ if !total_limit.nil? && (objects.count == total_limit)
59
+ return objects
60
+ end
61
+ end
62
+ end
63
+
64
+ !current_objects.empty? && (current_objects.count >= page_limit)
65
+ end
66
+
67
+ return objects
68
+ end
69
+
70
+ def paginated(path, limit, offset, params = {})
71
+ path_uri = Addressable::URI.parse(path)
72
+ query = { 'limit' => limit, 'offset' => offset }
73
+ path_uri.query_values ||= {}
74
+ path_uri.query_values = path_uri.query_values.merge(query)
75
+
76
+ request_url = path_uri.to_s
77
+
78
+ json = get(request_url, params)
79
+
80
+ loop do
81
+ break if json['error'] && (json['status'] == 503)
82
+ break if !yield(json)
83
+
84
+ links = json['_links']
85
+ next_url = links['next']
86
+
87
+ next_uri = Addressable::URI.parse(next_url)
88
+ offset = next_uri.query_values['offset'].to_i
89
+
90
+ total = json['_total']
91
+ break if total && (offset > total)
92
+
93
+ request_url = next_url
94
+ json = get(request_url)
95
+ end
96
+ end
97
+
98
+ private
99
+ DEFAULT_BASE_URL = 'https://api.twitch.tv/kraken/'
100
+ end
101
+ end
102
+
103
+ module Kappa::V2
104
+ # @private
105
+ module Connection
106
+ class Impl < Kappa::ConnectionBase
107
+ include Singleton
108
+
109
+ private
110
+ def custom_headers
111
+ { 'Accept' => 'application/vnd.twitchtv.v2+json' }
112
+ end
113
+ end
114
+
115
+ def self.included(base)
116
+ base.extend(ClassMethods)
117
+ end
118
+
119
+ def connection
120
+ Impl.instance
121
+ end
122
+
123
+ module ClassMethods
124
+ def connection
125
+ Impl.instance
126
+ end
127
+ end
128
+ end
129
+ end
@@ -1,161 +1,147 @@
1
- module Kappa::V2
2
- # Games are categories (e.g. League of Legends, Diablo 3) used by streams and channels.
3
- # Games can be searched for by query.
4
- # @see Games
5
- class Game
6
- include Kappa::IdEquality
7
-
8
- # @private
9
- def initialize(hash)
10
- @channel_count = hash['channels']
11
- @viewer_count = hash['viewers']
12
-
13
- game = hash['game']
14
- @id = game['_id']
15
- @name = game['name']
16
- @giantbomb_id = game['giantbomb_id']
17
- @box_images = Images.new(game['box'])
18
- @logo_images = Images.new(game['logo'])
19
- end
20
-
21
- # @return [Fixnum] Unique Twitch ID.
22
- attr_reader :id
23
-
24
- # @return [String] User-friendly game name.
25
- attr_reader :name
26
-
27
- # @return [Fixnum] Unique game ID for GiantBomb.com.
28
- attr_reader :giantbomb_id
29
-
30
- # @return [Images] Set of images for the game's box art.
31
- attr_reader :box_images
32
-
33
- # @return [Images] Set of images for the game's logo.
34
- attr_reader :logo_images
35
-
36
- # @return [Fixnum] Total number of channels currently streaming this game on Twitch.
37
- attr_reader :channel_count
38
-
39
- # @return [Fixnum] Total number of viewers across all channels currently watching this game on Twitch.
40
- attr_reader :viewer_count
41
- end
42
-
43
- # A game suggestion returned by Twitch when searching for games via `Games.find`.
44
- # @see Games.find
45
- class GameSuggestion
46
- include Kappa::IdEquality
47
-
48
- # @private
49
- def initialize(hash)
50
- @id = hash['_id']
51
- @name = hash['name']
52
- @giantbomb_id = hash['giantbomb_id']
53
- @popularity = hash['popularity']
54
- @box_images = Images.new(hash['box'])
55
- @logo_images = Images.new(hash['logo'])
56
- end
57
-
58
- # @return [Fixnum] Unique Twitch ID.
59
- attr_reader :id
60
-
61
- # @return [String] Game name.
62
- attr_reader :name
63
-
64
- # @return [Fixnum] Unique game ID for GiantBomb.com.
65
- attr_reader :giantbomb_id
66
-
67
- # @return [Fixnum] Relative popularity metric. Higher number means more popular.
68
- attr_reader :popularity
69
-
70
- # @return [Images] Set of images for the game's box art.
71
- attr_reader :box_images
72
-
73
- # @return [Images] Set of images for the game's logo.
74
- attr_reader :logo_images
75
- end
76
-
77
- # Query class used for finding top games or finding games by name.
78
- # @see Game
79
- # @see GameSuggestion
80
- class Games
81
- include Connection
82
-
83
- # Get a list of games with the highest number of current viewers on Twitch.
84
- # @example
85
- # Games.top
86
- # @example
87
- # Games.top(:limit => 10)
88
- # @param options [Hash] Filter criteria.
89
- # @option options [Boolean] :hls (false) TODO
90
- # @option options [Fixnum] :limit (none) Limit on the number of results returned.
91
- # @option options [Fixnum] :offset (0) Offset into the result set to begin enumeration.
92
- # @see Game
93
- # @see https://github.com/justintv/Twitch-API/blob/master/v2_resources/games.md#get-gamestop GET /games/top
94
- # @return [Array<Game>] List of games sorted by number of current viewers on Twitch, most popular first.
95
- def self.top(options = {})
96
- params = {}
97
-
98
- # TODO: Support :offset.
99
- # TODO: Support :hls.
100
-
101
- limit = options[:limit]
102
- if limit && (limit < 100)
103
- params[:limit] = limit
104
- else
105
- params[:limit] = 100
106
- limit = 0
107
- end
108
-
109
- return connection.accumulate(
110
- :path => 'games/top',
111
- :params => params,
112
- :json => 'top',
113
- :class => Game,
114
- :limit => limit
115
- )
116
- end
117
-
118
- # Get a list of games with names similar to the specified name.
119
- # @example
120
- # Games.find(:name => 'diablo')
121
- # @example
122
- # Games.find(:name => 'starcraft', :live => true)
123
- # @param options [Hash] Search criteria.
124
- # @option options [String] :name Game name search term. This can be a partial name, e.g. `"league"`.
125
- # @option options [Boolean] :live (false) If `true`, only returns games that are currently live on at least one channel.
126
- # @see GameSuggestion
127
- # @see https://github.com/justintv/Twitch-API/blob/master/v2_resources/search.md#get-searchgames GET /search/games
128
- # @raise [ArgumentError] If `:name` is not specified.
129
- # @return [Array<GameSuggestion>] List of games matching the criteria.
130
- def self.find(options)
131
- raise ArgumentError if options.nil? || options[:name].nil?
132
-
133
- name = options[:name]
134
-
135
- params = {
136
- :query => name,
137
- :type => 'suggest'
138
- }
139
-
140
- if options[:live]
141
- params.merge!(:live => true)
142
- end
143
-
144
- # TODO: Use connection#accumulate here.
145
-
146
- games = []
147
- ids = Set.new
148
-
149
- json = connection.get('search/games', params)
150
- all_games = json['games']
151
- all_games.each do |game_json|
152
- game = GameSuggestion.new(game_json)
153
- if ids.add?(game.id)
154
- games << game
155
- end
156
- end
157
-
158
- games
159
- end
160
- end
161
- end
1
+ module Kappa::V2
2
+ # Games are categories (e.g. League of Legends, Diablo 3) used by streams and channels.
3
+ # Games can be searched for by query.
4
+ # @see Games
5
+ class Game
6
+ include Kappa::IdEquality
7
+
8
+ # @private
9
+ def initialize(hash)
10
+ @channel_count = hash['channels']
11
+ @viewer_count = hash['viewers']
12
+
13
+ game = hash['game']
14
+ @id = game['_id']
15
+ @name = game['name']
16
+ @giantbomb_id = game['giantbomb_id']
17
+ @box_images = Images.new(game['box'])
18
+ @logo_images = Images.new(game['logo'])
19
+ end
20
+
21
+ # @return [Fixnum] Unique Twitch ID.
22
+ attr_reader :id
23
+
24
+ # @return [String] User-friendly game name.
25
+ attr_reader :name
26
+
27
+ # @return [Fixnum] Unique game ID for GiantBomb.com.
28
+ attr_reader :giantbomb_id
29
+
30
+ # @return [Images] Set of images for the game's box art.
31
+ attr_reader :box_images
32
+
33
+ # @return [Images] Set of images for the game's logo.
34
+ attr_reader :logo_images
35
+
36
+ # @return [Fixnum] Total number of channels currently streaming this game on Twitch.
37
+ attr_reader :channel_count
38
+
39
+ # @return [Fixnum] Total number of viewers across all channels currently watching this game on Twitch.
40
+ attr_reader :viewer_count
41
+ end
42
+
43
+ # A game suggestion returned by Twitch when searching for games via `Games.find`.
44
+ # @see Games.find
45
+ class GameSuggestion
46
+ include Kappa::IdEquality
47
+
48
+ # @private
49
+ def initialize(hash)
50
+ @id = hash['_id']
51
+ @name = hash['name']
52
+ @giantbomb_id = hash['giantbomb_id']
53
+ @popularity = hash['popularity']
54
+ @box_images = Images.new(hash['box'])
55
+ @logo_images = Images.new(hash['logo'])
56
+ end
57
+
58
+ # @return [Fixnum] Unique Twitch ID.
59
+ attr_reader :id
60
+
61
+ # @return [String] Game name.
62
+ attr_reader :name
63
+
64
+ # @return [Fixnum] Unique game ID for GiantBomb.com.
65
+ attr_reader :giantbomb_id
66
+
67
+ # @return [Fixnum] Relative popularity metric. Higher number means more popular.
68
+ attr_reader :popularity
69
+
70
+ # @return [Images] Set of images for the game's box art.
71
+ attr_reader :box_images
72
+
73
+ # @return [Images] Set of images for the game's logo.
74
+ attr_reader :logo_images
75
+ end
76
+
77
+ # Query class used for finding top games or finding games by name.
78
+ # @see Game
79
+ # @see GameSuggestion
80
+ class Games
81
+ include Connection
82
+
83
+ # Get a list of games with the highest number of current viewers on Twitch.
84
+ # @example
85
+ # Games.top
86
+ # @example
87
+ # Games.top(:limit => 10)
88
+ # @param options [Hash] Filter criteria.
89
+ # @option options [Boolean] :hls (nil) If `true`, limit the games to those that have any streams using HLS (HTTP Live Streaming). If `false` or `nil`, do not limit.
90
+ # @option options [Fixnum] :limit (none) Limit on the number of results returned.
91
+ # @option options [Fixnum] :offset (0) Offset into the result set to begin enumeration.
92
+ # @see Game
93
+ # @see https://github.com/justintv/Twitch-API/blob/master/v2_resources/games.md#get-gamestop GET /games/top
94
+ # @return [Array<Game>] List of games sorted by number of current viewers on Twitch, most popular first.
95
+ def self.top(options = {})
96
+ params = {}
97
+
98
+ if options[:hls]
99
+ params[:hls] = true
100
+ end
101
+
102
+ return connection.accumulate(
103
+ :path => 'games/top',
104
+ :params => params,
105
+ :json => 'top',
106
+ :class => Game,
107
+ :limit => options[:limit],
108
+ :offset => options[:offset]
109
+ )
110
+ end
111
+
112
+ # Get a list of games with names similar to the specified name.
113
+ # @example
114
+ # Games.find(:name => 'diablo')
115
+ # @example
116
+ # Games.find(:name => 'starcraft', :live => true)
117
+ # @param options [Hash] Search criteria.
118
+ # @option options [String] :name Game name search term. This can be a partial name, e.g. `"league"`.
119
+ # @option options [Boolean] :live (false) If `true`, only returns games that are currently live on at least one channel.
120
+ # @option options [Fixnum] :limit (none) Limit on the number of results returned.
121
+ # @see GameSuggestion
122
+ # @see https://github.com/justintv/Twitch-API/blob/master/v2_resources/search.md#get-searchgames GET /search/games
123
+ # @raise [ArgumentError] If `:name` is not specified.
124
+ # @return [Array<GameSuggestion>] List of games matching the criteria.
125
+ def self.find(options)
126
+ raise ArgumentError, 'options' if options.nil?
127
+ raise ArgumentError, 'name' if options[:name].nil?
128
+
129
+ params = {
130
+ :query => options[:name],
131
+ :type => 'suggest'
132
+ }
133
+
134
+ if options[:live]
135
+ params.merge!(:live => true)
136
+ end
137
+
138
+ return connection.accumulate(
139
+ :path => 'search/games',
140
+ :params => params,
141
+ :json => 'games',
142
+ :class => GameSuggestion,
143
+ :limit => options[:limit]
144
+ )
145
+ end
146
+ end
147
+ end