kappa 0.1.1.pre → 0.1.2.pre

Sign up to get free protection for your applications and to get access to all the features.
data/lib/kappa/base.rb ADDED
@@ -0,0 +1,9 @@
1
+ class Class
2
+ def module_class(sym)
3
+ parts = name.split('::')
4
+ parts[-1] = sym
5
+ parts.inject(Kernel) { |const, part|
6
+ const.const_get(part)
7
+ }
8
+ end
9
+ end
@@ -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
@@ -0,0 +1,16 @@
1
+ module Kappa
2
+ module IdEquality
3
+ def hash
4
+ @id.hash
5
+ end
6
+
7
+ def eql?(other)
8
+ other && (self.class == other.class) && (self.id == other.id)
9
+ end
10
+
11
+ def ==(other)
12
+ eql?(other)
13
+ end
14
+ end
15
+ end
16
+
@@ -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
@@ -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