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/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