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.
- data/README.md +184 -148
- data/lib/kappa.rb +12 -11
- data/lib/kappa/channel.rb +143 -184
- data/lib/kappa/connection.rb +129 -142
- data/lib/kappa/game.rb +147 -161
- data/lib/kappa/id_equality.rb +17 -17
- data/lib/kappa/images.rb +34 -36
- data/lib/kappa/proxy.rb +32 -0
- data/lib/kappa/stream.rb +185 -175
- data/lib/kappa/team.rb +99 -99
- data/lib/kappa/twitch.rb +13 -11
- data/lib/kappa/user.rb +124 -132
- data/lib/kappa/version.rb +3 -1
- data/lib/kappa/video.rb +150 -100
- metadata +100 -38
data/lib/kappa/connection.rb
CHANGED
@@ -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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
page_limit
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
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
|
data/lib/kappa/game.rb
CHANGED
@@ -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 (
|
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
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
#
|
119
|
-
# @
|
120
|
-
#
|
121
|
-
# @
|
122
|
-
#
|
123
|
-
# @
|
124
|
-
# @
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|