kappa 0.2.0 → 0.3.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.
@@ -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