turntabler 0.0.1

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.
Files changed (48) hide show
  1. data/.gitignore +7 -0
  2. data/.rspec +2 -0
  3. data/.yardopts +7 -0
  4. data/CHANGELOG.md +5 -0
  5. data/Gemfile +3 -0
  6. data/LICENSE +20 -0
  7. data/README.md +383 -0
  8. data/Rakefile +11 -0
  9. data/examples/Gemfile +3 -0
  10. data/examples/Gemfile.lock +29 -0
  11. data/examples/autobop.rb +13 -0
  12. data/examples/autofan.rb +13 -0
  13. data/examples/blacklist.rb +16 -0
  14. data/examples/bop.rb +15 -0
  15. data/examples/bopcount.rb +20 -0
  16. data/examples/chat_bot.rb +16 -0
  17. data/examples/modlist.rb +19 -0
  18. data/examples/switch.rb +40 -0
  19. data/examples/time_afk_list.rb +46 -0
  20. data/lib/turntabler/assertions.rb +36 -0
  21. data/lib/turntabler/authorized_user.rb +217 -0
  22. data/lib/turntabler/avatar.rb +34 -0
  23. data/lib/turntabler/boot.rb +22 -0
  24. data/lib/turntabler/client.rb +457 -0
  25. data/lib/turntabler/connection.rb +176 -0
  26. data/lib/turntabler/digest_helpers.rb +13 -0
  27. data/lib/turntabler/error.rb +5 -0
  28. data/lib/turntabler/event.rb +239 -0
  29. data/lib/turntabler/handler.rb +67 -0
  30. data/lib/turntabler/loggable.rb +11 -0
  31. data/lib/turntabler/message.rb +24 -0
  32. data/lib/turntabler/playlist.rb +50 -0
  33. data/lib/turntabler/preferences.rb +70 -0
  34. data/lib/turntabler/resource.rb +194 -0
  35. data/lib/turntabler/room.rb +377 -0
  36. data/lib/turntabler/room_directory.rb +133 -0
  37. data/lib/turntabler/snag.rb +16 -0
  38. data/lib/turntabler/song.rb +247 -0
  39. data/lib/turntabler/sticker.rb +48 -0
  40. data/lib/turntabler/sticker_placement.rb +25 -0
  41. data/lib/turntabler/user.rb +274 -0
  42. data/lib/turntabler/version.rb +9 -0
  43. data/lib/turntabler/vote.rb +19 -0
  44. data/lib/turntabler.rb +102 -0
  45. data/spec/spec_helper.rb +7 -0
  46. data/spec/turntabler_spec.rb +4 -0
  47. data/turntable.gemspec +24 -0
  48. metadata +173 -0
@@ -0,0 +1,377 @@
1
+ require 'set'
2
+ require 'em-synchrony/em-http'
3
+ require 'turntabler/resource'
4
+
5
+ module Turntabler
6
+ # Represents an individual room in Turntable. The room must be explicitly
7
+ # entered before being able to DJ.
8
+ class Room < Resource
9
+ # Allow the id to be set via the "roomid" attribute
10
+ # @return [String]
11
+ attribute :id, :roomid, :load => false
12
+
13
+ # The section of the room that the user is in. This only applies to
14
+ # overflow rooms.
15
+ # @return [String]
16
+ attribute :section, :load => false
17
+
18
+ # The human-readable name for the room
19
+ # @return [String]
20
+ attribute :name
21
+
22
+ # A longer description of the room (sometimes includes rules, guidelines, etc.)
23
+ # @return [String]
24
+ attribute :description
25
+
26
+ # The path which can be used in the url to load the room
27
+ # @return [String]
28
+ attribute :shortcut
29
+
30
+ # The privacy level for the room (either "public" or "unlisted")
31
+ # @return [String]
32
+ attribute :privacy
33
+
34
+ # The maximum number of listeners that can be in the room (including DJs)
35
+ # @return [Fixnum]
36
+ attribute :listener_capacity, :max_size
37
+
38
+ # The maximum number of users that can DJ
39
+ # @return [Fixnum]
40
+ attribute :dj_capacity, :max_djs
41
+
42
+ # The minimum number of points required to DJ
43
+ # @return [Fixnum]
44
+ attribute :dj_minimum_points, :djthreshold
45
+
46
+ # The type of music being played in the room
47
+ # @return [String]
48
+ attribute :genre
49
+
50
+ # The time at which this room was created
51
+ # @return [Time]
52
+ attribute :created_at, :created do |value|
53
+ Time.at(value)
54
+ end
55
+
56
+ # The host to connect to for joining this room
57
+ # @return [String]
58
+ attribute :host, :chatserver do |value|
59
+ value[0]
60
+ end
61
+
62
+ # Whether this room is being featured by Turntable
63
+ # @return [Boolean]
64
+ attribute :featured
65
+
66
+ # The user that created the room
67
+ # @return [Turntabler::User]
68
+ attribute :creator do |attrs|
69
+ build_user(attrs)
70
+ end
71
+
72
+ # The listeners currently in the rom
73
+ # @return [Array<Turntabler::User>]
74
+ attribute :listeners, :users do |users|
75
+ Set.new(users.map {|attrs| build_user(attrs)})
76
+ end
77
+
78
+ # The users that are currently DJ'ing in the room
79
+ # @return [Array<Turntabler::User>]
80
+ attribute :djs do |ids|
81
+ Set.new(ids.map {|id| build_user(:_id => id)})
82
+ end
83
+
84
+ # The users that are appointed to moderate the room
85
+ # @return [Array<Turntabler::User>]
86
+ attribute :moderators, :moderator_id do |ids|
87
+ Set.new(ids.map {|id| build_user(:_id => id)})
88
+ end
89
+
90
+ # The current user's friends who are also known to be in the room. These
91
+ # friends must be connected through a separate network like Facebook or Twitter.
92
+ #
93
+ # @note This is only available when the room is discovered via Turntabler::RoomDirectory#with_friends
94
+ # @return [Array<Turntabler::User>]
95
+ attribute :friends, :load => false do |users|
96
+ Set.new(users.map {|attrs| build_user(attrs)})
97
+ end
98
+
99
+ # The current song being played
100
+ # @return [Turntabler::Song]
101
+ attribute :current_song do |attrs|
102
+ Song.new(client, attrs)
103
+ end
104
+
105
+ # The current DJ playing
106
+ # @return [Turntabler::User]
107
+ attribute :current_dj do |id|
108
+ build_user(:_id => id)
109
+ end
110
+
111
+ # The list of songs that have been played in this room.
112
+ # @note This is not an exhaustive list
113
+ # @return [Array<Turntabler::Song>]
114
+ attribute :songs_played, :songlog, :load => false do |songs|
115
+ songs.map {|attrs| Song.new(client, attrs)}
116
+ end
117
+
118
+ # @api private
119
+ def initialize(*)
120
+ @friends = Set.new
121
+ @songs_played = []
122
+ super
123
+ end
124
+
125
+ # Uses the configured chat host or attempts to look it up based on the room id
126
+ #
127
+ # @return [String]
128
+ # @raise [Turntabler::Error] if the host lookup fails
129
+ def host
130
+ @host ||= begin
131
+ response = EventMachine::HttpRequest.new("http://turntable.fm/api/room.which_chatserver?roomid=#{id}").get.response
132
+ JSON.parse(response)[1]['chatserver'][0]
133
+ end
134
+ end
135
+
136
+ # Gets the configured chat url
137
+ #
138
+ # @return [String]
139
+ def url
140
+ "ws://#{host}/socket.io/websocket"
141
+ end
142
+
143
+ # Loads the attributes for this room. Attributes will automatically load
144
+ # when accessed, but this allows data to be forcefully loaded upfront.
145
+ #
146
+ # @note This will open a connection to the chat server the room is hosted on if the client is not already connected to it
147
+ # @param [Hash] options The configuration options
148
+ # @option options [Boolean] :song_log (false) Whether to include the song log
149
+ # @return [true]
150
+ # @raise [Turntabler::Error] if the command fails
151
+ # @example
152
+ # room.load # => true
153
+ # room.load(:song_log => true) # => true
154
+ def load(options = {})
155
+ assert_valid_keys(options, :song_log)
156
+ options = {:song_log => false}.merge(options)
157
+
158
+ # Use a client that is connected on the same url this room is hosted on
159
+ client = @client.url == url ? @client : Turntabler::Client.new(@client.user.id, @client.user.auth, :url => url, :timeout => @client.timeout)
160
+
161
+ begin
162
+ data = client.api('room.info', :roomid => id, :section => section, :extended => options[:song_log])
163
+ self.attributes = data['room'].merge('users' => data['users'])
164
+ super()
165
+ ensure
166
+ # Close the client if it was only opened for use in this API call
167
+ client.close if client != @client
168
+ end
169
+ end
170
+
171
+ # Sets the current attributes for this room, ensures that the full list of
172
+ # listeners gets set first so that we can use those built users to then fill
173
+ # out the collection of djs, moderators, etc.
174
+ #
175
+ # @api private
176
+ def attributes=(attrs)
177
+ if attrs
178
+ super('users' => attrs.delete('users')) if attrs['users']
179
+ super
180
+
181
+ # Set room-level attributes that are specific to the song
182
+ song_attributes = attrs['metadata'] && attrs['metadata'].select {|key, value| %w(upvotes downvotes votelog).include?(key)}
183
+ current_song.attributes = song_attributes if @current_song
184
+ end
185
+ end
186
+
187
+ # Updates this room's information.
188
+ #
189
+ # @param [Hash] attributes The attributes to update
190
+ # @option attributes [String] :description
191
+ # @return [true]
192
+ # @raise [Turntabler::Error] if the command fails
193
+ # @example
194
+ # room.update(:description => '...') # => true
195
+ def update(attributes = {})
196
+ assert_valid_keys(attributes, :description)
197
+
198
+ api('room.modify', attributes)
199
+ self.attributes = attributes
200
+ true
201
+ end
202
+
203
+ # Enters the current room.
204
+ #
205
+ # @return [true]
206
+ # @raise [Turntabler::Error] if the command fails
207
+ # @example
208
+ # room.enter # => true
209
+ def enter
210
+ if client.room != self
211
+ # Leave the old room
212
+ client.room.leave if client.room
213
+
214
+ # Connect and register with this room
215
+ client.connect(url)
216
+ begin
217
+ client.room = self
218
+ data = api('room.register', :roomid => id, :section => nil)
219
+ self.attributes = {'section' => data['section']}
220
+ rescue Exception
221
+ client.room = nil
222
+ raise
223
+ end
224
+ end
225
+
226
+ true
227
+ end
228
+
229
+ # Leaves from the current room.
230
+ #
231
+ # @return [true]
232
+ # @raise [Turntabler::Error] if the command fails
233
+ # @example
234
+ # room.leave # => true
235
+ def leave
236
+ api('room.deregister', :roomid => id, :section => section)
237
+ true
238
+ end
239
+
240
+ # Add this room to the current user's favorites.
241
+ #
242
+ # @return [true]
243
+ # @raise [Turntabler::Error] if the command fails
244
+ # @example
245
+ # room.add_as_favorite # => true
246
+ def add_as_favorite
247
+ api('room.add_favorite', :roomid => id, :section => section)
248
+ true
249
+ end
250
+
251
+ # Remove this room from current user's favorites.
252
+ #
253
+ # @return [true]
254
+ # @raise [Turntabler::Error] if the command fails
255
+ # @example
256
+ # room.remove_as_favorite # => true
257
+ def remove_as_favorite
258
+ api('room.rem_favorite', :roomid => id, :section => section)
259
+ true
260
+ end
261
+
262
+ # Gets the user represented by the given attributes. This can either pull
263
+ # the user from:
264
+ # * The currently authorized user
265
+ # * The room's creator
266
+ # * The room's listeners
267
+ # * The room's moderators
268
+ #
269
+ # If the user isn't present in any of those, then a new User instance will
270
+ # get created.
271
+ #
272
+ # @api private
273
+ def build_user(attrs)
274
+ user = User.new(client, attrs)
275
+ user = if client.user == user
276
+ client.user
277
+ elsif @creator == user
278
+ creator
279
+ elsif result = @listeners && listener(user.id) || @moderators && moderator(user.id) || friend(user.id)
280
+ result
281
+ else
282
+ user
283
+ end
284
+ user.attributes = attrs
285
+ user
286
+ end
287
+
288
+ # Determines whether the current user can dj based on the minimum points
289
+ # required and spot availability
290
+ def can_dj?
291
+ dj_capacity > djs.length && dj_minimum_points <= client.user.points
292
+ end
293
+
294
+ # Adds the current user to the list of DJs.
295
+ #
296
+ # @note This will cause the user to enter the current room if that isn't already the case
297
+ # @return [true]
298
+ # @raise [Turntabler::Error] if the command fails
299
+ # @example
300
+ # room.become_dj # => true
301
+ def become_dj
302
+ enter
303
+ api('room.add_dj', :roomid => id, :section => section)
304
+ true
305
+ end
306
+
307
+ # Gets the dj with the given user id.
308
+ #
309
+ # @return [Turntabler::User, nil]
310
+ # @example
311
+ # room.dj('4fd8...') # => #<Turntabler::User ...>
312
+ def dj(user_id)
313
+ djs.detect {|dj| dj.id == user_id}
314
+ end
315
+
316
+ # Gets the listener with the given user id.
317
+ #
318
+ # @return [Turntabler::User, nil]
319
+ # @example
320
+ # room.listener('4fd8...') # => #<Turntabler::User ...>
321
+ def listener(user_id)
322
+ listeners.detect {|listener| listener.id == user_id}
323
+ end
324
+
325
+ # Gets the moderator with the given user id.
326
+ #
327
+ # @return [Turntabler::User, nil]
328
+ # @example
329
+ # room.moderator('4fd8...') # => #<Turntabler::User ...>
330
+ def moderator(user_id)
331
+ moderators.detect {|moderator| moderator.id == user_id}
332
+ end
333
+
334
+ # Gets the friend in the room with the given user id.
335
+ #
336
+ # @note This is only available when the room is discovered via Turntabler::RoomDirectory#with_friends
337
+ # @return [Turntabler::User, nil]
338
+ # @example
339
+ # room.friend('4fd8...') # => #<Turntabler::User ...>
340
+ def friend(user_id)
341
+ friends.detect {|friend| friend.id == user_id}
342
+ end
343
+
344
+ # Braodcasts a message in the chat.
345
+ #
346
+ # @param [String] text The text to send to the chat
347
+ # @return [true]
348
+ # @raise [Turntabler::Error] if the command fails
349
+ # @example
350
+ # room.say("What's up guys?") # => true
351
+ def say(text)
352
+ enter
353
+ api('room.speak', :text => text)
354
+ true
355
+ end
356
+
357
+ # Reports abuse by a room.
358
+ #
359
+ # @param [String] reason The reason the room is being reported
360
+ # @return [true]
361
+ # @raise [Turntabler::Error] if the command fails
362
+ # @example
363
+ # room.report('Name abuse ...') # => true
364
+ def report(reason = '')
365
+ api('room.report', :roomid => id, :section => section, :reason => reason)
366
+ true
367
+ end
368
+
369
+ private
370
+ # Sets the sticker placements for each dj
371
+ def sticker_placements=(user_placements)
372
+ user_placements.each do |user_id, placements|
373
+ listener(user_id).attributes = {'placements' => placements}
374
+ end
375
+ end
376
+ end
377
+ end
@@ -0,0 +1,133 @@
1
+ require 'turntabler/room'
2
+
3
+ module Turntabler
4
+ # Provides a set of helper methods for interacting with Turntable's directory
5
+ # of rooms.
6
+ class RoomDirectory
7
+ include Assertions
8
+
9
+ # @api private
10
+ def initialize(client)
11
+ @client = client
12
+ end
13
+
14
+ # Creates a new room with the given name and configuration. This should
15
+ # only be used if the room doesn't already exist.
16
+ #
17
+ # @note This will automatically enter the room when it is created
18
+ # @param [String] name The name of the room
19
+ # @param [Hash] attributes The initial attributes for the room
20
+ # @option attributes [String] :privacy ("public") TheThe level which the room will be made available to others ("public" or "unlisted")
21
+ # @option attributes [Fixnum] :dj_capacity (5) The maximum number of DJs allowed
22
+ # @option attributes [Fixnum] :dj_minimum_points (0) The minimum number of points required for a user to DJ
23
+ # @return [Turntabler::Room]
24
+ # @raise [ArgumentError] if an invalid attribute is specified
25
+ # @raise [Turntabler::Error] if the command fails
26
+ # @example
27
+ # rooms.create("Rock Awesomeness") # => #<Turntabler::Room ...>
28
+ def create(name, attributes = {})
29
+ assert_valid_keys(attributes, :privacy, :dj_capacity, :dj_minimum_points)
30
+ attributes = {:privacy => 'public', :dj_capacity => 5, :dj_minimum_points => 0}.merge(attributes)
31
+
32
+ # Convert attribute names over to their Turntable equivalent
33
+ {:dj_capacity => :max_djs, :dj_minimum_points => :djthreshold}.each do |from, to|
34
+ attributes[to] = attributes.delete(from) if attributes[from]
35
+ end
36
+
37
+ data = api('room.create', attributes.merge(:room_name => name))
38
+ room = Room.new(client, attributes.merge(:_id => data['roomid'], :shortcut => data['shortcut'], :name => name))
39
+ room.enter
40
+ room
41
+ end
42
+
43
+ # Gets the list of available rooms.
44
+ #
45
+ # @param [Hash] options The search options
46
+ # @option options [Fixnum] :limit (20) The total number of rooms to list
47
+ # @option options [Fixnum] :skip (0) The number of rooms to skip when loading the list
48
+ # @option options [Fixnum] :favorites (false) Whether to only include rooms marked as favorites
49
+ # @option options [Fixnum] :available_djs (false) Whether to only include rooms that have dj spots available
50
+ # @option options [Fixnum] :genre The genre of music being played in the room, . Possible values are +:rock+, +:electronica+, +:indie+, +:hiphop+, +:pop+, and +:dubstep+.
51
+ # @option options [Fixnum] :minimum_listeners (1) The minimum number of listeners in the room
52
+ # @option options [Fixnum] :sort (:listeners) The order to list rooms in. Possible values are +:created+, +:listeners+, and +:random+.
53
+ # @return [Array<Turntabler::Room>]
54
+ # @raise [ArgumentError] if an invalid option is specified
55
+ # @raise [Turntabler::Error] if the command fails
56
+ # @example
57
+ # rooms.list # => [#<Turntabler::Room ...>, ...]
58
+ # rooms.list(:favorites => true) # => [#<Turntabler::Room ...>, ...]
59
+ # rooms.list(:available_djs => true, :genre => :rock) # => [#<Turntabler::Room ...>, ...]
60
+ # rooms.list(:sort => :random) # => [#<Turntabler::Room ...>, ...]
61
+ def list(options = {})
62
+ assert_valid_keys(options, :limit, :skip, :favorites, :available_djs, :genre, :minimum_listeners, :sort)
63
+ assert_valid_values(options[:genre], :rock, :electronic, :indie, :hiphop, :pop, :dubstep) if options[:genre]
64
+ assert_valid_values(options[:sort], :created, :listeners, :random) if options[:sort]
65
+ options = {
66
+ :limit => 20,
67
+ :skip => 0,
68
+ :favorites => false,
69
+ :available_djs => false,
70
+ :minimum_listeners => 1,
71
+ :sort => :listeners
72
+ }.merge(options)
73
+
74
+ constraints = []
75
+ constraints << :favorites if options[:favorites]
76
+ constraints << :available_djs if options[:available_djs]
77
+ constraints << :"has_people=#{options[:minimum_listeners]}"
78
+ if options[:genre]
79
+ constraints << :"genre=#{options[:genre]}"
80
+ options[:sort] = "#{options[:sort]},genre:#{options[:genre]}"
81
+ end
82
+
83
+ data = api('room.directory_rooms',
84
+ :section_aware => true,
85
+ :limit => options[:limit],
86
+ :skip => options[:skip],
87
+ :constraints => constraints * ',',
88
+ :sort => options[:sort]
89
+ )
90
+ data['rooms'].map {|attrs| Room.new(client, attrs)}
91
+ end
92
+
93
+ # Get the rooms where your friends are currently in.
94
+ #
95
+ # @return [Array<Turntabler::Room>]
96
+ # @raise [Turntabler::Error] if the command fails
97
+ # @example
98
+ # rooms.with_friends # => [#<Turntabler::Room ...>, ...]
99
+ def with_friends
100
+ data = api('room.directory_graph')
101
+ data['rooms'].map do |(attrs, friends)|
102
+ Room.new(client, attrs.merge(:friends => friends))
103
+ end
104
+ end
105
+
106
+ # Finds rooms that match the given query string.
107
+ #
108
+ # @param [String] query The query string to search with
109
+ # @param [Hash] options The search options
110
+ # @option options [Fixnum] :limit (20) The maximum number of rooms to query for
111
+ # @option options [Fixnum] :skip (0) The number of rooms to skip when loading the results
112
+ # @return [Array<Turntabler::Room>]
113
+ # @raise [ArgumentError] if an invalid option is specified
114
+ # @raise [Turntabler::Error] if the command fails
115
+ # rooms.find('indie') # => [#<Turntabler::Room ...>, ...]
116
+ def find(query, options = {})
117
+ assert_valid_keys(options, :limit, :skip)
118
+ options = {:limit => 20, :skip => 0}.merge(options)
119
+
120
+ data = api('room.search', :query => query, :skip => options[:skip])
121
+ data['rooms'].map {|(attrs, *)| Room.new(client, attrs)}
122
+ end
123
+
124
+ private
125
+ # The client that all APIs filter through
126
+ attr_reader :client
127
+
128
+ # Runs the given API command on the client.
129
+ def api(command, options = {})
130
+ client.api(command, options)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,16 @@
1
+ require 'turntabler/resource'
2
+
3
+ module Turntabler
4
+ # Represents a song that was snagged
5
+ class Snag < Resource
6
+ # The user who snagged the song
7
+ # @return [Turntabler::User]
8
+ attribute :user, :userid do |value|
9
+ room.build_user(:_id => value)
10
+ end
11
+
12
+ # The song that was snagged
13
+ # @return [Turntabler::Song]
14
+ attribute :song
15
+ end
16
+ end