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 CHANGED
@@ -10,8 +10,9 @@ Kappa is a Ruby library for interfacing with the [Twitch.tv Kraken API](https://
10
10
  ```ruby
11
11
  require 'kappa'
12
12
 
13
- twitch = Kappa::Client.new
14
- grubby = twitch.channel('followgrubby')
13
+ include Kappa::V2
14
+
15
+ grubby = Channel.new('followgrubby')
15
16
  puts grubby.streaming?
16
17
  ```
17
18
 
data/lib/kappa.rb CHANGED
@@ -1,8 +1,15 @@
1
- require 'httparty'
2
- require 'json'
3
- require 'addressable/uri'
4
- require 'securerandom'
5
- require 'set'
1
+ require 'kappa/id_equality'
2
+ require 'kappa/connection'
3
+ require 'kappa/channel'
4
+ require 'kappa/stream'
5
+ require 'kappa/game'
6
+ require 'kappa/video'
7
+ require 'kappa/team'
8
+ require 'kappa/user'
9
+ require 'kappa/images'
10
+ require 'kappa/twitch'
11
+ require 'kappa/base'
12
+ require 'kappa/version'
6
13
 
7
14
  # TODO
8
15
  # https://github.com/justintv/Twitch-API
@@ -81,778 +88,3 @@ require 'set'
81
88
  # t.streams.where(:channel => 'lagtvmaximusblack')
82
89
  # t.streams.where(:channel => [...], :game => '...', :embeddable => t/f, :hls => t/f)
83
90
  # t.stream_summary
84
-
85
- module Kappa
86
- class Connection
87
- include HTTParty
88
- debug_output $stdout
89
-
90
- def initialize(base_url)
91
- @base_url = Addressable::URI.parse(base_url)
92
-
93
- uuid = SecureRandom.uuid
94
- # TODO: Embed current library version.
95
- @client_id = "Kappa-v1-#{uuid}"
96
-
97
- @last_request_time = Time.now - RATE_LIMIT_SEC
98
- end
99
-
100
- def get(path, query = nil)
101
- # TODO: Rate-limiting.
102
-
103
- request_url = @base_url + path
104
-
105
- # Handle non-JSON response
106
- # Handle invalid JSON
107
- # Handle non-200 codes
108
-
109
- headers = {
110
- 'Client-ID' => @client_id,
111
- 'Accept' => 'application/vnd.twitchtv.v2+json'
112
- }
113
-
114
- response = rate_limit do
115
- self.class.get(request_url, :headers => headers, :query => query)
116
- end
117
-
118
- json = response.body
119
- return JSON.parse(json)
120
- end
121
-
122
- def paginated(path, params = {})
123
- limit = [params[:limit] || 100, 100].min
124
- offset = params[:offset] || 0
125
-
126
- path_uri = Addressable::URI.parse(path)
127
- query = { 'limit' => limit, 'offset' => offset }
128
- path_uri.query_values ||= {}
129
- path_uri.query_values = path_uri.query_values.merge(query)
130
-
131
- request_url = path_uri.to_s
132
-
133
- params = params.dup
134
- params.delete(:limit)
135
- params.delete(:offset)
136
-
137
- # TODO: Hande request retry.
138
- loop do
139
- json = get(request_url, params)
140
-
141
- if json['error'] && (json['status'] == 503)
142
- break
143
- end
144
-
145
- break if !yield(json)
146
-
147
- links = json['_links']
148
- next_url = links['next']
149
-
150
- next_uri = Addressable::URI.parse(next_url)
151
- offset = next_uri.query_values['offset'].to_i
152
-
153
- total = json['_total']
154
- break if total && (offset > total)
155
-
156
- request_url = next_url
157
- end
158
- end
159
-
160
- private
161
- def rate_limit
162
- delta = Time.now - @last_request_time
163
- delay = [RATE_LIMIT_SEC - delta, 0].max
164
-
165
- sleep delay if delay > 0
166
-
167
- begin
168
- return yield
169
- ensure
170
- @last_request_time = Time.now
171
- end
172
- end
173
-
174
- RATE_LIMIT_SEC = 1
175
- end
176
-
177
- module IdEquality
178
- def hash
179
- @id.hash
180
- end
181
-
182
- def eql?(other)
183
- other && (self.id == other.id)
184
- end
185
-
186
- def ==(other)
187
- eql?(other)
188
- end
189
- end
190
-
191
- class Client
192
- def initialize(opts = {})
193
- base_url = opts[:base_url] || DEFAULT_BASE_URL
194
- @conn = Connection.new(base_url)
195
- end
196
-
197
- #
198
- # GET /users/:user
199
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/users.md#get-usersuser
200
- #
201
- def user(user_name)
202
- encoded_name = Addressable::URI.encode(user_name)
203
- User.new(@conn.get("users/#{encoded_name}"), @conn)
204
- end
205
-
206
- #
207
- # GET /channels/:channel
208
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/channels.md#get-channelschannel
209
- #
210
- def channel(channel_name)
211
- encoded_name = Addressable::URI.encode(channel_name)
212
- Channel.new(@conn.get("channels/#{encoded_name}"), @conn)
213
- end
214
-
215
- #
216
- # GET /streams/:channel
217
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/streams.md#get-streamschannel
218
- #
219
- def stream(channel_name)
220
- encoded_name = Addressable::URI.encode(channel_name)
221
- json = @conn.get("streams/#{encoded_name}")
222
- stream_json = json['stream']
223
- if stream_json
224
- Stream.new(json['stream'], @conn)
225
- else
226
- nil
227
- end
228
- end
229
-
230
- def streams
231
- @streams ||= StreamQuery.new(@conn)
232
- end
233
-
234
- def games
235
- @games ||= GameQuery.new(@conn)
236
- end
237
-
238
- #
239
- # GET /teams/:team
240
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/teams.md#get-teamsteam
241
- #
242
- def team(team_name)
243
- encoded_name = Addressable::URI.encode(team_name)
244
- json = @conn.get("teams/#{encoded_name}")
245
- Team.new(json, @conn)
246
- end
247
-
248
- def teams
249
- @teams ||= TeamQuery.new(@conn)
250
- end
251
-
252
- #
253
- # GET /videos/:id
254
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/videos.md#get-videosid
255
- #
256
- def video(video_id)
257
- Video.new(@conn.get("videos/#{video_id}"), @conn)
258
- end
259
-
260
- def videos
261
- @videos ||= VideoQuery.new(@conn)
262
- end
263
-
264
- # TODO: Move?
265
- def stats
266
- Stats.new(@conn.get('streams/summary'), @conn)
267
- end
268
-
269
- DEFAULT_BASE_URL = 'https://api.twitch.tv/kraken/'
270
- end
271
-
272
- class Channel
273
- def initialize(json, conn)
274
- @conn = conn
275
-
276
- @id = json['_id']
277
- @background_url = json['background']
278
- @banner_url = json['banner']
279
- @created_at = DateTime.parse(json['created_at'])
280
- @stream_delay_sec = json['delay']
281
- @display_name = json['display_name']
282
- @game = json['game']
283
- @logo_url = json['logo']
284
- @mature = json['mature'] || false
285
- @name = json['name']
286
- @status = json['status']
287
- @updated_at = DateTime.parse(json['updated_at'])
288
- @url = json['url']
289
- @video_banner = json['video_banner']
290
- end
291
-
292
- include IdEquality
293
-
294
- def mature?
295
- @mature
296
- end
297
-
298
- def stream
299
- encoded_name = Addressable::URI.encode(@name)
300
- json = @conn.get("streams/#{encoded_name}")
301
- stream_json = json['stream']
302
- if stream_json
303
- Stream.new(json['stream'], @conn)
304
- else
305
- nil
306
- end
307
- end
308
-
309
- def streaming?
310
- !self.stream.nil?
311
- end
312
-
313
- #
314
- # GET /channels/:channel/editors
315
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/channels.md#get-channelschanneleditors
316
- #
317
- def editors
318
- # TODO: ...
319
- end
320
-
321
- #
322
- # GET /channels/:channels/videos
323
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/videos.md#get-channelschannelvideos
324
- #
325
- def videos(params = {})
326
- # TODO: ...
327
- end
328
-
329
- #
330
- # GET /channels/:channel/follows
331
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/channels.md#get-channelschannelfollows
332
- # TODO: Warning: this set can be very large, this can run for very long time, recommend using :limit/:offset.
333
- #
334
- def followers(params = {})
335
- limit = params[:limit] || 0
336
-
337
- followers = []
338
- ids = Set.new
339
-
340
- @conn.paginated("channels/#{@name}/follows", params) do |json|
341
- current_followers = json['follows']
342
- current_followers.each do |follow_json|
343
- user_json = follow_json['user']
344
- user = User.new(user_json, @conn)
345
- if ids.add?(user.id)
346
- followers << user
347
- if followers.count == limit
348
- return followers
349
- end
350
- end
351
- end
352
-
353
- !current_followers.empty?
354
- end
355
-
356
- followers
357
- end
358
-
359
- # TODO: Requires authentication.
360
- def subscribers
361
- end
362
-
363
- #
364
- # GET /channels/:channel/subscriptions/:user
365
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/subscriptions.md#get-channelschannelsubscriptionsuser
366
- #
367
- # TODO: Requires authentication.
368
- def has_subscriber?(user)
369
- # Support User object or username (string)
370
- end
371
-
372
- # t = Kappa::Client.new
373
- # c = t.channel('lagtvmaximusblack')
374
- # c.followers -> [...]
375
- # c.subscriptions
376
- # c.start_commercial
377
- # c.reset_stream_key
378
- # c... ; c.save!
379
- # TODO: current user channel
380
-
381
- attr_reader :id
382
- attr_reader :background_url
383
- attr_reader :banner_url
384
- attr_reader :created_at
385
- attr_reader :stream_delay_sec
386
- attr_reader :display_name
387
- attr_reader :game
388
- attr_reader :logo_url
389
- attr_reader :name
390
- attr_reader :status
391
- attr_reader :updated_at
392
- attr_reader :url
393
- attr_reader :video_banner
394
- end
395
-
396
- class TeamQuery
397
- def initialize(conn)
398
- @conn = conn
399
- end
400
-
401
- #
402
- # GET /teams
403
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/teams.md#get-teams
404
- #
405
- def all(params = {})
406
- limit = params[:limit] || 0
407
-
408
- teams = []
409
- ids = Set.new
410
-
411
- @conn.paginated('teams', params) do |json|
412
- teams_json = json['teams']
413
- teams_json.each do |team_json|
414
- team = Team.new(team_json, @conn)
415
- if ids.add?(team.id)
416
- teams << team
417
- if teams.count == limit
418
- return teams
419
- end
420
- end
421
- end
422
-
423
- !teams_json.empty?
424
- end
425
-
426
- teams
427
- end
428
- end
429
-
430
- class StreamQuery
431
- def initialize(conn)
432
- @conn = conn
433
- end
434
-
435
- def all
436
- end
437
-
438
- #
439
- # GET /streams
440
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/streams.md
441
- # :game (single, string), :channel (string array), :limit (int), :offset (int), :embeddable (bool), :hls (bool)
442
- #
443
- def where(params = {})
444
- limit = params[:limit] || 0
445
-
446
- params = params.dup
447
- if params[:channel]
448
- params[:channel] = params[:channel].join(',')
449
- end
450
-
451
- streams = []
452
- ids = Set.new
453
-
454
- @conn.paginated('streams', params) do |json|
455
- current_streams = json['streams']
456
- current_streams.each do |stream_json|
457
- stream = Stream.new(stream_json, @conn)
458
- if ids.add?(stream.id)
459
- streams << stream
460
- if streams.count == limit
461
- return streams
462
- end
463
- end
464
- end
465
-
466
- !current_streams.empty?
467
- end
468
-
469
- streams
470
- end
471
-
472
- #
473
- # GET /streams/featured
474
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/streams.md#get-streamsfeatured
475
- #
476
- def featured(params = {})
477
- limit = params[:limit] || 0
478
-
479
- streams = []
480
- ids = Set.new
481
-
482
- @conn.paginated('streams/featured', params) do |json|
483
- current_streams = json['featured']
484
- current_streams.each do |featured_json|
485
- # TODO: Capture more information from the featured_json structure (need a FeaturedStream class?)
486
- stream_json = featured_json['stream']
487
- stream = Stream.new(stream_json, @conn)
488
- if ids.add?(stream.id)
489
- streams << stream
490
- if streams.count == limit
491
- return streams
492
- end
493
- end
494
- end
495
-
496
- !current_streams.empty?
497
- end
498
-
499
- streams
500
- end
501
- end
502
-
503
- class Stream
504
- def initialize(json, conn)
505
- @conn = conn
506
-
507
- @id = json['_id']
508
- @broadcaster = json['broadcaster']
509
- @game_name = json['game']
510
- @name = json['name']
511
- @viewer_count = json['viewers']
512
- @preview_url = json['preview']
513
- @channel = Channel.new(json['channel'], @conn)
514
- end
515
-
516
- include IdEquality
517
-
518
- def channel
519
- end
520
-
521
- attr_reader :id
522
- attr_reader :broadcaster
523
- attr_reader :game_name
524
- attr_reader :name
525
- attr_reader :viewer_count
526
- attr_reader :preview_url
527
- attr_reader :channel
528
- end
529
-
530
- class GameQuery
531
- def initialize(conn)
532
- @conn = conn
533
- end
534
-
535
- #
536
- # GET /games/top
537
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/games.md#get-gamestop
538
- #
539
- def top(params = {})
540
- limit = params[:limit] || 0
541
-
542
- games = []
543
- ids = Set.new
544
-
545
- @conn.paginated('games/top', params) do |json|
546
- current_games = json['top']
547
- current_games.each do |game_json|
548
- game = Game.new(game_json)
549
- if ids.add?(game.id)
550
- games << game
551
- if games.count == limit
552
- return games
553
- end
554
- end
555
- end
556
- end
557
-
558
- games
559
- end
560
-
561
- #
562
- # GET /search/games
563
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/search.md#get-searchgames
564
- #
565
- def search(params = {})
566
- live = params[:live] || false
567
- name = params[:name]
568
-
569
- games = []
570
- ids = Set.new
571
-
572
- json = @conn.get('search/games', :query => name, :type => 'suggest', :live => live)
573
- all_games = json['games']
574
- all_games.each do |game_json|
575
- game = GameSuggestion.new(game_json)
576
- if ids.add?(game.id)
577
- games << game
578
- end
579
- end
580
-
581
- games
582
- end
583
- end
584
-
585
- class VideoQuery
586
- # ...
587
- def top
588
- end
589
- end
590
-
591
- class Game
592
- def initialize(json)
593
- @channel_count = json['channels']
594
- @viewer_count = json['viewers']
595
-
596
- game = json['game']
597
- @id = game['_id']
598
- @name = game['name']
599
- @giantbomb_id = game['giantbomb_id']
600
- @box_images = Images.new(game['box'])
601
- @logo_images = Images.new(game['logo'])
602
- end
603
-
604
- include IdEquality
605
-
606
- attr_reader :id
607
- attr_reader :name
608
- attr_reader :giantbomb_id
609
- attr_reader :box_images
610
- attr_reader :logo_images
611
- attr_reader :channel_count
612
- attr_reader :viewer_count
613
- end
614
-
615
- class GameSuggestion
616
- def initialize(json)
617
- @id = json['_id']
618
- @name = json['name']
619
- @giantbomb_id = json['giantbomb_id']
620
- @popularity = json['popularity']
621
- @box_images = Images.new(json['box'])
622
- @logo_images = Images.new(json['logo'])
623
- end
624
-
625
- include IdEquality
626
-
627
- attr_reader :id
628
- attr_reader :name
629
- attr_reader :giantbomb_id
630
- attr_reader :popularity
631
- attr_reader :box_images
632
- attr_reader :logo_images
633
- end
634
-
635
- class Images
636
- def initialize(json)
637
- @large_url = json['large']
638
- @medium_url = json['medium']
639
- @small_url = json['small']
640
- @template_url = json['template']
641
- end
642
-
643
- def url(width, height)
644
- @template_url.gsub('{width}', width.to_s, '{height}', height.to_s)
645
- end
646
-
647
- attr_reader :large_url
648
- attr_reader :medium_url
649
- attr_reader :small_url
650
- attr_reader :template_url
651
- end
652
-
653
- class User
654
- def initialize(json, conn)
655
- @conn = conn
656
-
657
- # TODO: nil checks
658
- @id = json['_id']
659
- @created_at = DateTime.parse(json['created_at'])
660
- @display_name = json['display_name']
661
- @logo_url = json['logo']
662
- @name = json['name']
663
- @type = json['type']
664
- @updated_at = DateTime.parse(json['updated_at'])
665
- end
666
-
667
- #
668
- # GET /channels/:channel/subscriptions/:user
669
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/subscriptions.md#get-channelschannelsubscriptionsuser
670
- #
671
- # TODO: Requires authentication.
672
- def subscribed_to?(channel_name)
673
- end
674
-
675
- #
676
- # GET /streams/followed
677
- # TODO: Authenticate.
678
- # TODO: Only valid for authenticated user, might not belong here.
679
- #
680
- # GET /users/:user/follows/channels
681
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/follows.md#get-usersuserfollowschannels
682
- #
683
- def following(params = {})
684
- limit = params[:limit] || 0
685
-
686
- channels = []
687
- ids = Set.new
688
-
689
- @conn.paginated("users/#{@name}/follows/channels", params) do |json|
690
- current_channels = json['follows']
691
- current_channels.each do |follow_json|
692
- channel_json = follow_json['channel']
693
- channel = Channel.new(channel_json, @conn)
694
- if ids.add?(channel.id)
695
- channels << channel
696
- if channels.count == limit
697
- return channels
698
- end
699
- end
700
- end
701
-
702
- !current_channels.empty?
703
- end
704
-
705
- channels
706
- end
707
-
708
- #
709
- # GET /users/:user/follows/:channels/:target
710
- # https://github.com/justintv/Twitch-API/blob/master/v2_resources/follows.md#get-usersuserfollowschannelstarget
711
- #
712
- def following?(channel_name)
713
- encoded_name = Addressable::URI.encode(channel_name)
714
- json = @conn.get("users/#{@name}/follows/channels/#{encoded_name}")
715
- status = json['status']
716
- return !status || (status != 404)
717
- end
718
-
719
- attr_reader :id
720
- attr_reader :created_at
721
- attr_reader :display_name
722
- attr_reader :logo_url
723
- attr_reader :name
724
- attr_reader :type
725
- attr_reader :updated_at
726
- end
727
-
728
- class Stats
729
- def initialize(json, conn)
730
- @viewer_count = json['viewers']
731
- @stream_count = json['channels']
732
- end
733
-
734
- attr_reader :viewer_count
735
- attr_reader :stream_count
736
- end
737
-
738
- class Team
739
- def initialize(json, conn)
740
- @id = json['_id']
741
- @info = json['info']
742
- @background_url = json['background']
743
- @banner_url = json['banner']
744
- @logo_url = json['logo']
745
- @name = json['name']
746
- @display_name = json['display_name']
747
- @updated_at = DateTime.parse(json['updated_at'])
748
- @created_at = DateTime.parse(json['created_at'])
749
- end
750
-
751
- include IdEquality
752
-
753
- attr_reader :id
754
- attr_reader :info
755
- attr_reader :background_url
756
- attr_reader :banner_url
757
- attr_reader :logo_url
758
- attr_reader :name
759
- attr_reader :display_name
760
- attr_reader :updated_at
761
- attr_reader :created_at
762
- end
763
-
764
- class Video
765
- def initialize(json, conn)
766
- @conn = conn
767
-
768
- @id = json['id']
769
- @title = json['title']
770
- @recorded_at = DateTime.parse(json['recorded_at'])
771
- @url = json['url']
772
- @view_count = json['views']
773
- @description = json['description']
774
- @length_sec = json['length']
775
- @game_name = json['game']
776
- @preview_url = json['preview']
777
- @channel_name = json['channel']['name']
778
- # @channel_display_name = json['channel']['display_name']
779
- end
780
-
781
- include IdEquality
782
-
783
- def channel
784
- Channel.new(@conn.get("channels/#{@channel_name}"), @conn)
785
- end
786
-
787
- attr_reader :id
788
- attr_reader :title
789
- attr_reader :recorded_at
790
- attr_reader :url
791
- attr_reader :view_count
792
- attr_reader :description
793
- # TODO: Is this actually in seconds? Doesn't seem to match up with video length.
794
- attr_reader :length_sec
795
- attr_reader :game_name
796
- attr_reader :preview_url
797
- # TODO: Move this under "v.channel.name" and force the query if other attributes are requested.
798
- attr_reader :channel_name
799
- end
800
- end
801
-
802
- =begin
803
- k = Kappa::Client.new
804
- v = k.video('a396294648')
805
- =end
806
-
807
- =begin
808
- u.following.each do |channel|
809
- puts channel.display_name
810
- end
811
- =end
812
-
813
- =begin
814
- streams = t.streams.where(:channel => ['psystarcraft', 'destiny', 'combatex', 'eghuk', 'eg_idra', 'day9tv', 'fxoqxc', 'colqxc', 'liquidnony', 'demuslim', 'whitera', 'khaldor', 'acermma'])
815
- streams.each do |stream|
816
- puts "#{stream.channel.display_name}: #{stream.viewer_count}"
817
- puts "#{stream.channel.status}"
818
- end
819
- =end
820
-
821
- =begin
822
- streams = t.streams.where(:game => 'StarCraft II: Heart of the Swarm')
823
- streams.each do |stream|
824
- puts stream.channel.display_name
825
- end
826
- =end
827
-
828
- =begin
829
- puts t.stats.viewer_count
830
- puts t.stats.stream_count
831
- =end
832
-
833
- =begin
834
- s = t.streams.where(:channel => ['lagtvmaximusblack', 'sc2tv_ru'])
835
- s.each do |stream|
836
- puts stream.channel.name
837
- end
838
- =end
839
-
840
- =begin
841
- s = t.streams.featured
842
- s.each do |stream|
843
- puts stream.channel.name
844
- end
845
- =end
846
-
847
- =begin
848
- games = t.games.top(:limit => 150).sort_by(&:viewer_count).reverse
849
- puts games.count
850
-
851
- games.each do |game|
852
- puts "#{game.name}: #{game.viewer_count}"
853
- end
854
- =end
855
-
856
- #c = t.channel('lagtvmaximusblack')
857
- #s = c.stream
858
- #puts s.id