kappa 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -7,6 +7,7 @@ Kappa is the Ruby library for interfacing with the [Twitch.tv API](https://githu
7
7
  [![Dependency Status](https://gemnasium.com/schmich/kappa.png)](https://gemnasium.com/schmich/kappa)
8
8
  [![Coverage Status](https://coveralls.io/repos/schmich/kappa/badge.png?branch=master)](https://coveralls.io/r/schmich/kappa?branch=master)
9
9
  [![Code Climate](https://codeclimate.com/github/schmich/kappa.png)](https://codeclimate.com/github/schmich/kappa)
10
+ [![Bitdeli](https://d2weczhvl823v0.cloudfront.net/schmich/kappa/trend.png)](https://bitdeli.com/free "Bitdeli")
10
11
 
11
12
  ## Getting Started
12
13
 
@@ -17,10 +18,22 @@ gem install kappa
17
18
  ```ruby
18
19
  require 'kappa'
19
20
 
20
- include Kappa::V2
21
+ frag = Twitch.channels.get('lethalfrag')
22
+ puts frag.streaming?
23
+ ```
24
+
25
+ ## Configuration
26
+
27
+ When making requests to Twitch, you must specify a client ID for your application.
28
+ If you do not specify a client ID, Twitch reserves the right to rate-limit your application
29
+ without warning.
30
+
31
+ Your client ID can be specified through configuration, for example:
21
32
 
22
- grubby = Channel.get('followgrubby')
23
- puts grubby.streaming?
33
+ ```ruby
34
+ Twitch.configure do |config|
35
+ config.client_id = 'sc2daily-v1.0.0'
36
+ end
24
37
  ```
25
38
 
26
39
  ## Overview
@@ -32,7 +45,7 @@ Channels serve as the home location for a [user's](#users) content. Channels hav
32
45
  See also [`Channel`](http://rdoc.info/gems/kappa/Kappa/V2/Channel) documentation.
33
46
 
34
47
  ```ruby
35
- c = Channel.get('destiny')
48
+ c = Twitch.channels.get('destiny')
36
49
  c.nil? # => false (channel exists)
37
50
  c.stream # => #<Kappa::V2::Stream> (current live stream)
38
51
  c.url # => "http://www.twitch.tv/destiny"
@@ -49,7 +62,7 @@ Streams are video broadcasts that are currently live. They belong to a [user](#u
49
62
  See also [`Stream`](http://rdoc.info/gems/kappa/Kappa/V2/Stream) and [`Streams`](http://rdoc.info/gems/kappa/Kappa/V2/Streams) documentation.
50
63
 
51
64
  ```ruby
52
- s = Stream.get('idrajit')
65
+ s = Twitch.streams.get('idrajit')
53
66
  s.nil? # => false (currently live)
54
67
  s.game_name # => "StarCraft II: Heart of the Swarm"
55
68
  s.viewer_count # => 7267
@@ -63,7 +76,7 @@ These are members of the Twitch community who have a Twitch account. If broadcas
63
76
  See also [`User`](http://rdoc.info/gems/kappa/Kappa/V2/User) documentation.
64
77
 
65
78
  ```ruby
66
- u = User.get('snoopeh')
79
+ u = Twitch.users.get('snoopeh')
67
80
  u.nil? # => false (user exists)
68
81
  u.channel # => #<Kappa::V2::Channel>
69
82
  u.following.map(&:name) # => ["national_esl1", "dreamhacklol", "riotgames"]
@@ -76,7 +89,7 @@ Videos are broadcasts or highlights owned by a [channel](#channels). Broadcasts
76
89
  See also [`Video`](http://rdoc.info/gems/kappa/Kappa/V2/Video) and [`Videos`](http://rdoc.info/gems/kappa/Kappa/V2/Videos) documentation.
77
90
 
78
91
  ```ruby
79
- v = Video.get('a395995729')
92
+ v = Twitch.videos.get('a395995729')
80
93
  v.nil? # => false (video exists)
81
94
  v.title # => "DreamHack Open Stockholm 26-27 April"
82
95
  v.game_name # => "StarCraft II: Heart of the Swarm"
@@ -91,7 +104,7 @@ Teams are an organization of [channels](#channels).
91
104
  See also [`Team`](http://rdoc.info/gems/kappa/Kappa/V2/Team) documentation.
92
105
 
93
106
  ```ruby
94
- t = Team.get('teamliquid')
107
+ t = Twitch.teams.get('teamliquid')
95
108
  t.nil? # => false (team exists)
96
109
  t.display_name # => "TeamLiquid"
97
110
  t.info # => "TeamLiquid is awesome. and esports. video games. \n\n"
@@ -105,12 +118,12 @@ Games are categories (e.g. League of Legends, Diablo 3) used by [streams](#strea
105
118
  See also [`Game`](http://rdoc.info/gems/kappa/Kappa/V2/Game), [`Games`](http://rdoc.info/gems/kappa/Kappa/V2/Games), and [`GameSuggestion`](http://rdoc.info/gems/kappa/Kappa/V2/GameSuggestion) documentation.
106
119
 
107
120
  ```ruby
108
- top = Games.top(:limit => 3)
121
+ top = Twitch.games.top(:limit => 3)
109
122
  top.map(&:name) # => ["League of Legends", "Dota 2", "StarCraft II: Heart of the Swarm"]
110
123
  ```
111
124
 
112
125
  ```ruby
113
- g = Games.top(:limit => 1).first
126
+ g = Twitch.games.top(:limit => 1).first
114
127
  g.name # => "League of Legends"
115
128
  g.channel_count # => 906
116
129
  g.viewer_count # => 79223
@@ -118,7 +131,7 @@ g.box_images.medium_url # =>"http://static-cdn.jtvnw.net/ttv-boxart/League%20of
118
131
  ```
119
132
 
120
133
  ```ruby
121
- s = Games.find(:name => 'diablo', :live => true)
134
+ s = Twitch.games.find(:name => 'diablo', :live => true)
122
135
  s.map(&:name) # => ["Diablo III", "Diablo II", "Diablo", "Diablo II: Lord of Destruction"]
123
136
  s.map(&:popularity) # => [120, 4, 1, 1]
124
137
  ```
@@ -149,26 +162,25 @@ backwards-compatible bugfixes will result in a new patch version (e.g. `x.x.1` t
149
162
 
150
163
  ### Twitch API versions
151
164
 
152
- Twitch supports multiple versions of their API simultaneously, with each version potentially providing different data and behaving differently. Because of this, you must specify which version of the Twitch API you wish to use. With Kappa, this is done with modules.
165
+ Twitch supports multiple versions of their API simultaneously, with each version potentially providing different data
166
+ and behaving differently. Because of this, you can specify which version of the Twitch API you wish to use.
167
+ This is done through Kappa configuration.
153
168
 
154
- For example, if you want to use the v2 Twitch API:
169
+ For example, if you want to use the V2 Twitch API:
155
170
 
156
171
  ```ruby
157
- # Option 1: Include the module once.
158
- include Kappa::V2
159
- c = Channel.get('day9tv')
160
- u = User.get('artosis')
161
- ```
162
- ```ruby
163
- # Option 2: Specify the full class name each time.
164
- c = Kappa::V2::Channel.get('day9tv')
165
- u = Kappa::V2::User.get('artosis')
172
+ Twitch.configure do |config|
173
+ config.client_id = 'sc2daily-v1.0.0'
174
+ config.api = Twitch::V2
175
+ end
166
176
  ```
167
177
 
178
+ `Twitch::V2` is the default and is currently the only supported API version.
179
+
168
180
  ## Contributing
169
181
 
170
182
  - [Fork and clone the repo.](http://help.github.com/fork-a-repo/)
171
- - [Create a branch for your changes.](http://learn.github.com/p/branching.html)
183
+ - [Create a branch for your changes.](http://git-scm.com/book/en/Git-Branching-Basic-Branching-and-Merging)
172
184
  - Run `bundle install` to install development requirements.
173
185
  - Implement your feature or bug fix.
174
186
  - Add specs under the `spec` folder to prevent regressions or to test new code.
@@ -1,6 +1,8 @@
1
1
  require 'kappa/id_equality'
2
2
  require 'kappa/proxy'
3
3
  require 'kappa/connection'
4
+ require 'kappa/configuration'
5
+ require 'kappa/query'
4
6
  require 'kappa/channel'
5
7
  require 'kappa/stream'
6
8
  require 'kappa/game'
@@ -8,5 +10,4 @@ require 'kappa/video'
8
10
  require 'kappa/team'
9
11
  require 'kappa/user'
10
12
  require 'kappa/images'
11
- require 'kappa/twitch'
12
13
  require 'kappa/version'
@@ -1,19 +1,20 @@
1
1
  require 'cgi'
2
2
  require 'time'
3
3
 
4
- module Kappa::V2
4
+ module Twitch::V2
5
5
  # Channels serve as the home location for a user's content. Channels have a stream, can run
6
6
  # commercials, store videos, display information and status, and have a customized page including
7
7
  # banners and backgrounds.
8
- # @see .get Channel.get
8
+ # @see Channels#get Channels#get
9
+ # @see Channels
9
10
  # @see Stream
10
11
  # @see User
11
12
  class Channel
12
- include Connection
13
- include Kappa::IdEquality
13
+ include Twitch::IdEquality
14
14
 
15
15
  # @private
16
- def initialize(hash)
16
+ def initialize(hash, query)
17
+ @query = query
17
18
  @id = hash['_id']
18
19
  @background_url = hash['background']
19
20
  @banner_url = hash['banner']
@@ -35,21 +36,6 @@ module Kappa::V2
35
36
  end
36
37
  end
37
38
 
38
- # Get a channel by name.
39
- # @param channel_name [String] The name of the channel to get. This is the same as the stream or user name.
40
- # @return [Channel] A valid `Channel` object if the channel exists, `nil` otherwise.
41
- def self.get(channel_name)
42
- encoded_name = CGI.escape(channel_name)
43
- json = connection.get("channels/#{encoded_name}")
44
-
45
- # HTTP 422 can happen if the channel is associated with a Justin.tv account.
46
- if !json || json['status'] == 404 || json['status'] == 422
47
- nil
48
- else
49
- new(json)
50
- end
51
- end
52
-
53
39
  # Does this channel have mature content? This flag is specified by the owner of the channel.
54
40
  # @return [Boolean] `true` if the channel has mature content, `false` otherwise.
55
41
  def mature?
@@ -61,7 +47,7 @@ module Kappa::V2
61
47
  # @return [Stream] Live stream object for this channel, or `nil` if the channel is not currently streaming.
62
48
  # @see #streaming?
63
49
  def stream
64
- Stream.get(@name)
50
+ @query.streams.get(@name)
65
51
  end
66
52
 
67
53
  # Does this channel currently have a live stream?
@@ -76,11 +62,13 @@ module Kappa::V2
76
62
  # @note This incurs an additional web request.
77
63
  # @return [User] The user that owns this channel.
78
64
  def user
79
- User.get(@name)
65
+ @query.users.get(@name)
80
66
  end
81
67
 
82
68
  # Get the users following this channel.
83
69
  # @note The number of followers is potentially very large, so it's recommended that you specify a `:limit`.
70
+ # @example
71
+ # f = c.followers(:limit => 20)
84
72
  # @param options [Hash] Filter criteria.
85
73
  # @option options [Fixnum] :limit (none) Limit on the number of results returned.
86
74
  # @option options [Fixnum] :offset (0) Offset into the result set to begin enumeration.
@@ -88,56 +76,141 @@ module Kappa::V2
88
76
  # @see https://github.com/justintv/Twitch-API/blob/master/v2_resources/channels.md#get-channelschannelfollows GET /channels/:channel/follows
89
77
  # @return [Array<User>] List of users following this channel.
90
78
  def followers(options = {})
91
- params = {}
92
-
93
- return connection.accumulate(
79
+ return @query.connection.accumulate(
94
80
  :path => "channels/#{@name}/follows",
95
- :params => params,
96
81
  :json => 'follows',
97
82
  :sub_json => 'user',
98
- :class => User,
83
+ :create => -> hash { User.new(hash, @query) },
99
84
  :limit => options[:limit],
100
85
  :offset => options[:offset]
101
86
  )
102
87
  end
103
88
 
89
+ # Get the videos for a channel, most recently created first.
90
+ # @note This incurs additional web requests.
91
+ # @example
92
+ # c = Twitch.channels.get('idrajit')
93
+ # v = c.videos(:type => :broadcasts)
94
+ # @param options [Hash] Filter criteria.
95
+ # @option options [Symbol] :type (:highlights) The type of videos to return. Valid values are `:broadcasts`, `:highlights`.
96
+ # @option options [Fixnum] :limit (none) Limit on the number of results returned.
97
+ # @option options [Fixnum] :offset (0) Offset into the result set to begin enumeration.
98
+ # @see Video
99
+ # @see https://github.com/justintv/Twitch-API/blob/master/v2_resources/videos.md#get-channelschannelvideos GET /channels/:channel/videos
100
+ # @raise [ArgumentError] If `:type` is not one of `:broadcasts` or `:highlights`.
101
+ # @return [Array<Video>] List of videos for the channel.
102
+ def videos(options = {})
103
+ params = {}
104
+
105
+ type = options[:type] || :highlights
106
+ if !type.nil?
107
+ if ![:broadcasts, :highlights].include?(type)
108
+ raise ArgumentError, 'type'
109
+ end
110
+
111
+ params[:broadcasts] = (type == :broadcasts)
112
+ end
113
+
114
+ return @query.connection.accumulate(
115
+ :path => "channels/#{@name}/videos",
116
+ :params => params,
117
+ :json => 'videos',
118
+ :create => -> hash { Video.new(hash, @query) },
119
+ :limit => options[:limit],
120
+ :offset => options[:offset]
121
+ )
122
+ end
123
+
124
+ # @example
125
+ # 23460970
104
126
  # @return [Fixnum] Unique Twitch ID.
105
127
  attr_reader :id
106
128
 
129
+ # @example
130
+ # "http://static-cdn.jtvnw.net/jtv_user_pictures/lethalfrag-channel_background_image-833a4324bc698c9b.jpeg"
107
131
  # @return [String] URL for background image.
108
132
  attr_reader :background_url
109
133
 
134
+ # @example
135
+ # "http://static-cdn.jtvnw.net/jtv_user_pictures/lethalfrag-channel_header_image-463a4670c91c2b61-640x125.jpeg"
110
136
  # @return [String] URL for banner image.
111
137
  attr_reader :banner_url
112
138
 
139
+ # @example
140
+ # 2011-07-15 07:53:58 UTC
113
141
  # @return [Time] When the channel was created (UTC).
114
142
  attr_reader :created_at
115
143
 
144
+ # @example
145
+ # "Lethalfrag"
146
+ # @see #name
116
147
  # @return [String] User-friendly display name. This name is used for the channel's page title.
117
148
  attr_reader :display_name
118
149
 
150
+ # @example
151
+ # "Super Meat Boy"
119
152
  # @return [String] Name of the primary game for this channel.
120
153
  attr_reader :game_name
121
154
 
155
+ # @example
156
+ # "http://static-cdn.jtvnw.net/jtv_user_pictures/lethalfrag-profile_image-050adf252718823b-300x300.png"
122
157
  # @return [String] URL for the logo image.
123
158
  attr_reader :logo_url
124
159
 
160
+ # @example
161
+ # "lethalfrag"
162
+ # @see #display_name
125
163
  # @return [String] Unique Twitch name.
126
164
  attr_reader :name
127
165
 
166
+ # @example
167
+ # "(Day 563/731) | Dinner and a Game (Cooking at http://twitch.tv/lookatmychicken)"
128
168
  # @return [String] Current status set by the channel's owner.
129
169
  attr_reader :status
130
170
 
171
+ # @example
172
+ # 2013-07-21 05:27:58 UTC
131
173
  # @return [Time] When the channel was last updated (UTC). For example, when a stream is started, its channel is updated.
132
174
  attr_reader :updated_at
133
175
 
176
+ # @example
177
+ # "http://www.twitch.tv/lethalfrag"
134
178
  # @return [String] The URL for the channel's main page.
135
179
  attr_reader :url
136
180
 
181
+ # @example
182
+ # "http://static-cdn.jtvnw.net/jtv_user_pictures/lethalfrag-channel_offline_image-3b801b2ccc11830b-640x360.jpeg"
137
183
  # @return [String] URL for the image shown when the stream is offline.
138
184
  attr_reader :video_banner_url
139
185
 
186
+ # @see Team
140
187
  # @return [Array<Team>] The list of teams that this channel is associated with. Not all channels have associated teams.
141
188
  attr_reader :teams
142
189
  end
190
+
191
+ # Query class for finding channels.
192
+ # @see Channel
193
+ class Channels
194
+ # @private
195
+ def initialize(query)
196
+ @query = query
197
+ end
198
+
199
+ # Get a channel by name.
200
+ # @example
201
+ # c = Twitch.channels.get('day9tv')
202
+ # @param channel_name [String] The name of the channel to get. This is the same as the stream or user name.
203
+ # @return [Channel] A valid `Channel` object if the channel exists, `nil` otherwise.
204
+ def get(channel_name)
205
+ encoded_name = CGI.escape(channel_name)
206
+ json = @query.connection.get("channels/#{encoded_name}")
207
+
208
+ # HTTP 422 can happen if the channel is associated with a Justin.tv account.
209
+ if !json || json['status'] == 404 || json['status'] == 422
210
+ nil
211
+ else
212
+ Channel.new(json, @query)
213
+ end
214
+ end
215
+ end
143
216
  end
@@ -0,0 +1,54 @@
1
+ require 'securerandom'
2
+
3
+ module Twitch
4
+ @query = nil
5
+
6
+ # Configure global settings for interacting with Twitch. Future requests will use these settings.
7
+ # @example
8
+ # Twitch.configure do |config|
9
+ # config.client_id = 'sc2daily-v1.0.0'
10
+ # config.api = Twitch::V2
11
+ # end
12
+ def self.configure(&block)
13
+ @query = instance(&block)
14
+ end
15
+
16
+ def self.instance(&block)
17
+ config = Configuration.new
18
+ config.instance_eval(&block)
19
+ connection = config.create(:Connection, config.client_id)
20
+ return config.create(:Query, connection)
21
+ end
22
+
23
+ def self.method_missing(*args)
24
+ @query ||= create_default_query
25
+ @query.send(*args)
26
+ end
27
+
28
+ # @private
29
+ class Configuration
30
+ def initialize
31
+ @api = Twitch::V2
32
+ end
33
+
34
+ def client_id
35
+ # Generate a random client_id if it's not already set.
36
+ @client_id ||= "Kappa-%s" % SecureRandom.uuid
37
+ @client_id
38
+ end
39
+
40
+ def create(symbol, *args)
41
+ @api.const_get(symbol).new(*args)
42
+ end
43
+
44
+ attr_writer :client_id
45
+ attr_accessor :api
46
+ end
47
+
48
+ private
49
+ def self.create_default_query
50
+ config = Configuration.new
51
+ connection = config.create(:Connection, config.client_id)
52
+ return config.create(:Query, connection)
53
+ end
54
+ end
@@ -1,30 +1,27 @@
1
1
  require 'httparty'
2
2
  require 'addressable/uri'
3
- require 'securerandom'
4
3
  require 'json'
5
- require 'singleton'
6
4
  require 'set'
7
5
 
8
- module Kappa
6
+ module Twitch
9
7
  # @private
10
- class ConnectionBase
8
+ class Connection
11
9
  include HTTParty
12
10
 
13
- def initialize(base_url = DEFAULT_BASE_URL)
11
+ def initialize(client_id, base_url = DEFAULT_BASE_URL)
12
+ @client_id = client_id
14
13
  @base_url = Addressable::URI.parse(base_url)
15
-
16
- uuid = SecureRandom.uuid
17
- @client_id = "Kappa-v1-#{uuid}"
18
14
  end
19
15
 
20
16
  def get(path, query = nil)
21
17
  request_url = @base_url + path
22
18
 
23
- headers = {
19
+ all_headers = {
24
20
  'Client-ID' => @client_id,
25
- }.merge(custom_headers)
21
+ 'Kappa-Version' => Twitch::VERSION
22
+ }.merge(headers())
26
23
 
27
- response = self.class.get(request_url, :headers => headers, :query => query)
24
+ response = self.class.get(request_url, :headers => all_headers, :query => query)
28
25
 
29
26
  json = response.body
30
27
  return JSON.parse(json)
@@ -35,11 +32,16 @@ module Kappa
35
32
  params = options[:params] || {}
36
33
  json = options[:json]
37
34
  sub_json = options[:sub_json]
38
- klass = options[:class]
35
+ create = options[:create]
39
36
 
40
37
  raise ArgumentError, 'json' if json.nil?
41
38
  raise ArgumentError, 'path' if path.nil?
42
- raise ArgumentError, 'class' if klass.nil?
39
+ raise ArgumentError, 'create' if create.nil?
40
+
41
+ if create.is_a? Class
42
+ klass = create
43
+ create = -> hash { klass.new(hash) }
44
+ end
43
45
 
44
46
  total_limit = options[:limit]
45
47
  page_limit = [total_limit || 100, 100].min
@@ -48,11 +50,11 @@ module Kappa
48
50
  objects = []
49
51
  ids = Set.new
50
52
 
51
- paginated(path, page_limit, offset, params) do |response_json|
53
+ paginate(path, page_limit, offset, params) do |response_json|
52
54
  current_objects = response_json[json]
53
55
  current_objects.each do |object_json|
54
56
  object_json = object_json[sub_json] if sub_json
55
- object = klass.new(object_json)
57
+ object = create.call(object_json)
56
58
  if ids.add?(object.id)
57
59
  objects << object
58
60
  if !total_limit.nil? && (objects.count == total_limit)
@@ -67,7 +69,7 @@ module Kappa
67
69
  return objects
68
70
  end
69
71
 
70
- def paginated(path, limit, offset, params = {})
72
+ def paginate(path, limit, offset, params = {})
71
73
  path_uri = Addressable::URI.parse(path)
72
74
  query = { 'limit' => limit, 'offset' => offset }
73
75
  path_uri.query_values ||= {}
@@ -91,7 +93,7 @@ module Kappa
91
93
  break if total && (offset > total)
92
94
 
93
95
  request_url = next_url
94
- json = get(request_url)
96
+ json = get(request_url, params)
95
97
  end
96
98
  end
97
99
 
@@ -100,30 +102,11 @@ module Kappa
100
102
  end
101
103
  end
102
104
 
103
- module Kappa::V2
105
+ module Twitch::V2
104
106
  # @private
105
- module Connection
106
- class Impl < Kappa::ConnectionBase
107
- include Singleton
108
-
109
- private
110
- def custom_headers
107
+ class Connection < Twitch::Connection
108
+ def headers
111
109
  { 'Accept' => 'application/vnd.twitchtv.v2+json' }
112
110
  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
111
  end
129
112
  end