kappa 0.1.1.pre → 0.1.2.pre
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 +3 -2
- data/lib/kappa.rb +12 -780
- data/lib/kappa/base.rb +9 -0
- data/lib/kappa/channel.rb +140 -0
- data/lib/kappa/connection.rb +119 -0
- data/lib/kappa/game.rb +122 -0
- data/lib/kappa/id_equality.rb +16 -0
- data/lib/kappa/images.rb +33 -0
- data/lib/kappa/stream.rb +151 -0
- data/lib/kappa/team.rb +83 -0
- data/lib/kappa/twitch.rb +11 -0
- data/lib/kappa/user.rb +113 -0
- data/lib/kappa/version.rb +1 -1
- data/lib/kappa/video.rb +66 -0
- metadata +45 -2
data/lib/kappa/base.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
module Kappa
|
2
|
+
class ChannelBase
|
3
|
+
include IdEquality
|
4
|
+
|
5
|
+
def initialize(arg, connection)
|
6
|
+
@connection = connection
|
7
|
+
|
8
|
+
case arg
|
9
|
+
when Hash
|
10
|
+
parse(arg)
|
11
|
+
when String
|
12
|
+
json = @connection.get("channels/#{arg}")
|
13
|
+
parse(json)
|
14
|
+
else
|
15
|
+
raise ArgumentError
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Kappa::V2
|
22
|
+
# TODO:
|
23
|
+
# c.subscriptions
|
24
|
+
# c.start_commercial
|
25
|
+
# c.reset_stream_key
|
26
|
+
# c.foo = 'bar' ; c.save!
|
27
|
+
# Current user's channel
|
28
|
+
class Channel < Kappa::ChannelBase
|
29
|
+
def initialize(arg, connection = Connection.instance)
|
30
|
+
super(arg, connection)
|
31
|
+
end
|
32
|
+
|
33
|
+
def mature?
|
34
|
+
@mature
|
35
|
+
end
|
36
|
+
|
37
|
+
# TODO: Move these into derived classes?
|
38
|
+
def stream
|
39
|
+
# TODO: Use _links instead of hard-coding.
|
40
|
+
json = @connection.get("streams/#{@name}")
|
41
|
+
stream_json = json['stream']
|
42
|
+
Stream.new(stream_json, @connection)
|
43
|
+
end
|
44
|
+
|
45
|
+
def streaming?
|
46
|
+
stream.live?
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# GET /channels/:channel/editors
|
51
|
+
# https://github.com/justintv/Twitch-API/blob/master/v2_resources/channels.md#get-channelschanneleditors
|
52
|
+
#
|
53
|
+
def editors
|
54
|
+
# TODO
|
55
|
+
end
|
56
|
+
|
57
|
+
#
|
58
|
+
# GET /channels/:channels/videos
|
59
|
+
# https://github.com/justintv/Twitch-API/blob/master/v2_resources/videos.md#get-channelschannelvideos
|
60
|
+
#
|
61
|
+
def videos(params = {})
|
62
|
+
# TODO
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# GET /channels/:channel/follows
|
67
|
+
# https://github.com/justintv/Twitch-API/blob/master/v2_resources/channels.md#get-channelschannelfollows
|
68
|
+
# TODO: Warning: this set can be very large, this can run for very long time, recommend using :limit/:offset.
|
69
|
+
#
|
70
|
+
def followers(params = {})
|
71
|
+
limit = params[:limit] || 0
|
72
|
+
|
73
|
+
followers = []
|
74
|
+
ids = Set.new
|
75
|
+
|
76
|
+
@connection.paginated("channels/#{@name}/follows", params) do |json|
|
77
|
+
current_followers = json['follows']
|
78
|
+
current_followers.each do |follow_json|
|
79
|
+
user_json = follow_json['user']
|
80
|
+
user = User.new(user_json, @connection)
|
81
|
+
if ids.add?(user.id)
|
82
|
+
followers << user
|
83
|
+
if followers.count == limit
|
84
|
+
return followers
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
!current_followers.empty?
|
90
|
+
end
|
91
|
+
|
92
|
+
followers
|
93
|
+
end
|
94
|
+
|
95
|
+
# TODO: Requires authentication.
|
96
|
+
def subscribers
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# GET /channels/:channel/subscriptions/:user
|
101
|
+
# https://github.com/justintv/Twitch-API/blob/master/v2_resources/subscriptions.md#get-channelschannelsubscriptionsuser
|
102
|
+
#
|
103
|
+
# TODO: Requires authentication.
|
104
|
+
def has_subscriber?(user)
|
105
|
+
# Support User object or username (string)
|
106
|
+
end
|
107
|
+
|
108
|
+
attr_reader :id
|
109
|
+
attr_reader :background_url
|
110
|
+
attr_reader :banner_url
|
111
|
+
attr_reader :created_at
|
112
|
+
attr_reader :stream_delay_sec
|
113
|
+
attr_reader :display_name
|
114
|
+
attr_reader :game_name
|
115
|
+
attr_reader :logo_url
|
116
|
+
attr_reader :name
|
117
|
+
attr_reader :status
|
118
|
+
attr_reader :updated_at
|
119
|
+
attr_reader :url
|
120
|
+
attr_reader :video_banner_url
|
121
|
+
|
122
|
+
private
|
123
|
+
def parse(hash)
|
124
|
+
@id = hash['_id']
|
125
|
+
@background_url = hash['background']
|
126
|
+
@banner_url = hash['banner']
|
127
|
+
@created_at = DateTime.parse(hash['created_at'])
|
128
|
+
@stream_delay_sec = hash['delay']
|
129
|
+
@display_name = hash['display_name']
|
130
|
+
@game_name = hash['game']
|
131
|
+
@logo_url = hash['logo']
|
132
|
+
@mature = hash['mature'] || false
|
133
|
+
@name = hash['name']
|
134
|
+
@status = hash['status']
|
135
|
+
@updated_at = DateTime.parse(hash['updated_at'])
|
136
|
+
@url = hash['url']
|
137
|
+
@video_banner_url = hash['video_banner']
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'addressable/uri'
|
3
|
+
require 'securerandom'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Kappa
|
7
|
+
class ConnectionBase
|
8
|
+
include HTTParty
|
9
|
+
debug_output $stdout
|
10
|
+
|
11
|
+
def initialize(base_url = DEFAULT_BASE_URL)
|
12
|
+
@base_url = Addressable::URI.parse(base_url)
|
13
|
+
|
14
|
+
uuid = SecureRandom.uuid
|
15
|
+
# TODO: Use current library version.
|
16
|
+
@client_id = "Kappa-v1-#{uuid}"
|
17
|
+
|
18
|
+
@last_request_time = Time.now - RATE_LIMIT_SEC
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.instance
|
22
|
+
@connection ||= self.class_eval do
|
23
|
+
self.new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(path, query = nil)
|
28
|
+
request_url = @base_url + path
|
29
|
+
|
30
|
+
# Handle non-JSON response
|
31
|
+
# Handle invalid JSON
|
32
|
+
# Handle non-200 codes
|
33
|
+
|
34
|
+
headers = {
|
35
|
+
'Client-ID' => @client_id,
|
36
|
+
}.merge(custom_headers)
|
37
|
+
|
38
|
+
response = rate_limit do
|
39
|
+
self.class.get(request_url, :headers => headers, :query => query)
|
40
|
+
end
|
41
|
+
|
42
|
+
json = response.body
|
43
|
+
return JSON.parse(json)
|
44
|
+
end
|
45
|
+
|
46
|
+
def paginated(path, params = {})
|
47
|
+
limit = [params[:limit] || 100, 100].min
|
48
|
+
offset = params[:offset] || 0
|
49
|
+
|
50
|
+
path_uri = Addressable::URI.parse(path)
|
51
|
+
query = { 'limit' => limit, 'offset' => offset }
|
52
|
+
path_uri.query_values ||= {}
|
53
|
+
path_uri.query_values = path_uri.query_values.merge(query)
|
54
|
+
|
55
|
+
request_url = path_uri.to_s
|
56
|
+
|
57
|
+
params = params.dup
|
58
|
+
params.delete(:limit)
|
59
|
+
params.delete(:offset)
|
60
|
+
|
61
|
+
# TODO: Hande request retry.
|
62
|
+
loop do
|
63
|
+
json = get(request_url, params)
|
64
|
+
|
65
|
+
if json['error'] && (json['status'] == 503)
|
66
|
+
break
|
67
|
+
end
|
68
|
+
|
69
|
+
break if !yield(json)
|
70
|
+
|
71
|
+
links = json['_links']
|
72
|
+
next_url = links['next']
|
73
|
+
|
74
|
+
next_uri = Addressable::URI.parse(next_url)
|
75
|
+
offset = next_uri.query_values['offset'].to_i
|
76
|
+
|
77
|
+
total = json['_total']
|
78
|
+
break if total && (offset > total)
|
79
|
+
|
80
|
+
request_url = next_url
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
def rate_limit
|
86
|
+
delta = Time.now - @last_request_time
|
87
|
+
delay = [RATE_LIMIT_SEC - delta, 0].max
|
88
|
+
|
89
|
+
sleep delay if delay > 0
|
90
|
+
|
91
|
+
begin
|
92
|
+
return yield
|
93
|
+
ensure
|
94
|
+
@last_request_time = Time.now
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
RATE_LIMIT_SEC = 1
|
99
|
+
DEFAULT_BASE_URL = 'https://api.twitch.tv/kraken/'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
module Kappa::V2
|
104
|
+
class Connection < Kappa::ConnectionBase
|
105
|
+
private
|
106
|
+
def custom_headers
|
107
|
+
{ 'Accept' => 'application/vnd.twitchtv.v2+json' }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
module Kappa::V3
|
113
|
+
class Connection < Kappa::ConnectionBase
|
114
|
+
private
|
115
|
+
def custom_headers
|
116
|
+
{ 'Accept' => 'application/vnd.twitchtv.v3+json' }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
data/lib/kappa/game.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
module Kappa
|
2
|
+
class GameBase
|
3
|
+
include IdEquality
|
4
|
+
|
5
|
+
def initialize(arg)
|
6
|
+
case arg
|
7
|
+
when Hash
|
8
|
+
parse(arg)
|
9
|
+
else
|
10
|
+
raise ArgumentError
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class GameSuggestionBase
|
16
|
+
include IdEquality
|
17
|
+
|
18
|
+
def initialize(arg)
|
19
|
+
case arg
|
20
|
+
when Hash
|
21
|
+
parse(arg)
|
22
|
+
else
|
23
|
+
raise ArgumentError
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module Kappa::V2
|
30
|
+
class Game < Kappa::GameBase
|
31
|
+
attr_reader :id
|
32
|
+
attr_reader :name
|
33
|
+
attr_reader :giantbomb_id
|
34
|
+
attr_reader :box_images
|
35
|
+
attr_reader :logo_images
|
36
|
+
attr_reader :channel_count
|
37
|
+
attr_reader :viewer_count
|
38
|
+
|
39
|
+
private
|
40
|
+
def parse(hash)
|
41
|
+
@channel_count = hash['channels']
|
42
|
+
@viewer_count = hash['viewers']
|
43
|
+
|
44
|
+
game = hash['game']
|
45
|
+
@id = game['_id']
|
46
|
+
@name = game['name']
|
47
|
+
@giantbomb_id = game['giantbomb_id']
|
48
|
+
@box_images = Images.new(game['box'])
|
49
|
+
@logo_images = Images.new(game['logo'])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class GameSuggestion < Kappa::GameSuggestionBase
|
54
|
+
attr_reader :id
|
55
|
+
attr_reader :name
|
56
|
+
attr_reader :giantbomb_id
|
57
|
+
attr_reader :popularity
|
58
|
+
attr_reader :box_images
|
59
|
+
attr_reader :logo_images
|
60
|
+
|
61
|
+
private
|
62
|
+
def parse(hash)
|
63
|
+
@id = hash['_id']
|
64
|
+
@name = hash['name']
|
65
|
+
@giantbomb_id = hash['giantbomb_id']
|
66
|
+
@popularity = hash['popularity']
|
67
|
+
@box_images = Images.new(hash['box'])
|
68
|
+
@logo_images = Images.new(hash['logo'])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Games
|
73
|
+
#
|
74
|
+
# GET /games/top
|
75
|
+
# https://github.com/justintv/Twitch-API/blob/master/v2_resources/games.md#get-gamestop
|
76
|
+
#
|
77
|
+
def top(params = {})
|
78
|
+
limit = params[:limit] || 0
|
79
|
+
|
80
|
+
games = []
|
81
|
+
ids = Set.new
|
82
|
+
|
83
|
+
@conn.paginated('games/top', params) do |json|
|
84
|
+
current_games = json['top']
|
85
|
+
current_games.each do |game_json|
|
86
|
+
game = Game.new(game_json)
|
87
|
+
if ids.add?(game.id)
|
88
|
+
games << game
|
89
|
+
if games.count == limit
|
90
|
+
return games
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
games
|
97
|
+
end
|
98
|
+
|
99
|
+
#
|
100
|
+
# GET /search/games
|
101
|
+
# https://github.com/justintv/Twitch-API/blob/master/v2_resources/search.md#get-searchgames
|
102
|
+
#
|
103
|
+
def search(params = {})
|
104
|
+
live = params[:live] || false
|
105
|
+
name = params[:name]
|
106
|
+
|
107
|
+
games = []
|
108
|
+
ids = Set.new
|
109
|
+
|
110
|
+
json = @conn.get('search/games', :query => name, :type => 'suggest', :live => live)
|
111
|
+
all_games = json['games']
|
112
|
+
all_games.each do |game_json|
|
113
|
+
game = GameSuggestion.new(game_json)
|
114
|
+
if ids.add?(game.id)
|
115
|
+
games << game
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
games
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/kappa/images.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Kappa
|
2
|
+
class ImagesBase
|
3
|
+
def initialize(arg)
|
4
|
+
case arg
|
5
|
+
when Hash
|
6
|
+
parse(arg)
|
7
|
+
else
|
8
|
+
raise ArgumentError
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module Kappa::V2
|
15
|
+
class Images < Kappa::ImagesBase
|
16
|
+
def url(width, height)
|
17
|
+
@template_url.gsub('{width}', width.to_s, '{height}', height.to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :large_url
|
21
|
+
attr_reader :medium_url
|
22
|
+
attr_reader :small_url
|
23
|
+
attr_reader :template_url
|
24
|
+
|
25
|
+
private
|
26
|
+
def parse(hash)
|
27
|
+
@large_url = hash['large']
|
28
|
+
@medium_url = hash['medium']
|
29
|
+
@small_url = hash['small']
|
30
|
+
@template_url = hash['template']
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/kappa/stream.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Kappa
|
4
|
+
class StreamBase
|
5
|
+
include IdEquality
|
6
|
+
|
7
|
+
def initialize(hash, connection = self.class.default_connection)
|
8
|
+
@connection = connection
|
9
|
+
parse(hash)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.get(stream_name, connection = default_connection)
|
13
|
+
json = connection.get("streams/#{stream_name}")
|
14
|
+
stream = json['stream']
|
15
|
+
if json['status'] == 404 || !stream
|
16
|
+
nil
|
17
|
+
else
|
18
|
+
new(stream, connection)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def self.default_connection
|
24
|
+
self.class.module_class(:Connection).instance
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module Kappa::V2
|
30
|
+
class Stream < Kappa::StreamBase
|
31
|
+
attr_reader :id
|
32
|
+
attr_reader :broadcaster
|
33
|
+
attr_reader :game_name
|
34
|
+
attr_reader :name
|
35
|
+
attr_reader :viewer_count
|
36
|
+
attr_reader :preview_url
|
37
|
+
attr_reader :channel
|
38
|
+
|
39
|
+
private
|
40
|
+
def parse(hash)
|
41
|
+
@id = hash['_id']
|
42
|
+
@broadcaster = hash['broadcaster']
|
43
|
+
@game_name = hash['game']
|
44
|
+
@name = hash['name']
|
45
|
+
@viewer_count = hash['viewers']
|
46
|
+
@preview_url = hash['preview']
|
47
|
+
@channel = Channel.new(hash['channel'], @connection)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Streams
|
52
|
+
def self.all
|
53
|
+
end
|
54
|
+
|
55
|
+
#
|
56
|
+
# GET /streams
|
57
|
+
# https://github.com/justintv/Twitch-API/blob/master/v2_resources/streams.md
|
58
|
+
# :game (single, string), :channel (string array), :limit (int), :offset (int), :embeddable (bool), :hls (bool)
|
59
|
+
# TODO: Support Kappa::Vx::Game object for the :game param.
|
60
|
+
# TODO: Support Kappa::Vx::Channel object for the :channel param.
|
61
|
+
#
|
62
|
+
def self.where(args)
|
63
|
+
check = args.dup
|
64
|
+
check.delete(:limit)
|
65
|
+
check.delete(:offset)
|
66
|
+
raise ArgumentError if check.empty?
|
67
|
+
|
68
|
+
params = {}
|
69
|
+
|
70
|
+
if args[:channel]
|
71
|
+
params[:channel] = args[:channel].join(',')
|
72
|
+
end
|
73
|
+
|
74
|
+
if args[:game]
|
75
|
+
params[:game] = args[:game]
|
76
|
+
end
|
77
|
+
|
78
|
+
limit = args[:limit]
|
79
|
+
if limit && (limit < 25)
|
80
|
+
params[:limit] = limit
|
81
|
+
else
|
82
|
+
params[:limit] = 25
|
83
|
+
limit = 0
|
84
|
+
end
|
85
|
+
|
86
|
+
streams = []
|
87
|
+
ids = Set.new
|
88
|
+
|
89
|
+
connection = Connection.instance
|
90
|
+
connection.paginated('streams', params) do |json|
|
91
|
+
current_streams = json['streams']
|
92
|
+
current_streams.each do |stream_json|
|
93
|
+
stream = Stream.new(stream_json, connection)
|
94
|
+
if ids.add?(stream.id)
|
95
|
+
streams << stream
|
96
|
+
if streams.count == limit
|
97
|
+
return streams
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
!current_streams.empty?
|
103
|
+
end
|
104
|
+
|
105
|
+
streams
|
106
|
+
end
|
107
|
+
|
108
|
+
#
|
109
|
+
# GET /streams/featured
|
110
|
+
# https://github.com/justintv/Twitch-API/blob/master/v2_resources/streams.md#get-streamsfeatured
|
111
|
+
#
|
112
|
+
def self.featured(params = {})
|
113
|
+
limit = params[:limit] || 0
|
114
|
+
|
115
|
+
streams = []
|
116
|
+
ids = Set.new
|
117
|
+
|
118
|
+
connection = Connection.instance
|
119
|
+
connection.paginated('streams/featured', params) do |json|
|
120
|
+
current_streams = json['featured']
|
121
|
+
current_streams.each do |featured_json|
|
122
|
+
# TODO: Capture more information from the featured_json structure (need a FeaturedStream class?)
|
123
|
+
stream_json = featured_json['stream']
|
124
|
+
stream = Stream.new(stream_json, connection)
|
125
|
+
if ids.add?(stream.id)
|
126
|
+
streams << stream
|
127
|
+
if streams.count == limit
|
128
|
+
return streams
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
!current_streams.empty?
|
134
|
+
end
|
135
|
+
|
136
|
+
streams
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
module Kappa::V3
|
142
|
+
class Stream < Kappa::StreamBase
|
143
|
+
def initialize(arg, connection = Connection.instance)
|
144
|
+
super(arg, connection)
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
def parse(hash)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|