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,457 @@
1
+ require 'fiber'
2
+
3
+ require 'turntabler/authorized_user'
4
+ require 'turntabler/avatar'
5
+ require 'turntabler/connection'
6
+ require 'turntabler/error'
7
+ require 'turntabler/event'
8
+ require 'turntabler/handler'
9
+ require 'turntabler/loggable'
10
+ require 'turntabler/room_directory'
11
+ require 'turntabler/song'
12
+ require 'turntabler/sticker'
13
+ require 'turntabler/user'
14
+
15
+ module Turntabler
16
+ # Provides access to the Turntable API
17
+ class Client
18
+ include Assertions
19
+ include DigestHelpers
20
+ include Loggable
21
+
22
+ # The unique id representing this client
23
+ # @return [String]
24
+ attr_reader :id
25
+
26
+ # Sets the current room the user is in
27
+ # @param [Turntabler::Room] value The new room
28
+ # @api private
29
+ attr_writer :room
30
+
31
+ # The directory for looking up / creating rooms
32
+ # @return [Turntabler::RoomDirectory]
33
+ attr_reader :rooms
34
+
35
+ # The response timeout configured for the connection
36
+ # @return [Fixnum]
37
+ attr_reader :timeout
38
+
39
+ # Creates a new client for communicating with Turntable.fm with the given
40
+ # user id / auth token.
41
+ #
42
+ # @param [String] user_id The user to authenticate with
43
+ # @param [String] auth The authentication token for the user
44
+ # @param [Hash] options The configuration options for the client
45
+ # @option options [String] :id The unique identifier representing this client
46
+ # @option options [String] :room The id of the room to initially enter
47
+ # @option options [Fixnum] :timeout (10) The amount of seconds to allow to elapse for requests before timing out
48
+ # @option options [Boolean] :reconnect (false) Whether to allow the client to automatically reconnect when disconnected either by Turntable or by the network
49
+ # @option options [Fixnum] :reconnect_wait (5) The amount of seconds to wait before reconnecting
50
+ # @raise [Turntabler::Error] if an invalid option is specified
51
+ # @yield Runs the given block within the context if the client (for DSL-type usage)
52
+ def initialize(user_id, auth, options = {}, &block)
53
+ options = {
54
+ :id => "#{Time.now.to_i}-#{rand}",
55
+ :timeout => 10,
56
+ :reconnect => false,
57
+ :reconnect_wait => 5
58
+ }.merge(options)
59
+ assert_valid_keys(options, :id, :room, :url, :timeout, :reconnect, :reconnect_wait)
60
+
61
+ @id = options[:id]
62
+ @user = AuthorizedUser.new(self, :_id => user_id, :auth => auth)
63
+ @rooms = RoomDirectory.new(self)
64
+ @event_handlers = {}
65
+ @timeout = options[:timeout]
66
+ @reconnect = options[:reconnect]
67
+ @reconnect_wait = options[:reconnect_wait]
68
+
69
+ # Setup default event handlers
70
+ on(:heartbeat) { on_heartbeat }
71
+ on(:session_missing) { on_session_missing }
72
+
73
+ # Connect to an initial room / server
74
+ if room_name = options[:room]
75
+ room(room_name).enter
76
+ elsif url = options[:url]
77
+ connect(url)
78
+ else
79
+ connect
80
+ end
81
+
82
+ instance_eval(&block) if block_given?
83
+ end
84
+
85
+ # Initiates a connection with the given url. Once a connection is started,
86
+ # this will also attempt to authenticate the user.
87
+ #
88
+ # @api private
89
+ # @note This wil only open a new connection if the client isn't already connected to the given url
90
+ # @param [String] url The url to open a connection to
91
+ # @return [true]
92
+ # @raise [Turntabler::Error] if the connection cannot be opened
93
+ def connect(url = room(digest(rand)).url)
94
+ if !@connection || !@connection.connected? || @connection.url != url
95
+ # Close any existing connection
96
+ close
97
+
98
+ # Create a new connection to the given url
99
+ @connection = Connection.new(url, :timeout => timeout, :params => {:clientid => id, :userid => user.id, :userauth => user.auth})
100
+ @connection.handler = lambda {|data| on_message(data)}
101
+ @connection.start
102
+
103
+ # Wait until the connection is authenticated
104
+ wait do |fiber|
105
+ on(:session_missing, :once => true) { fiber.resume }
106
+ end
107
+ end
108
+
109
+ true
110
+ end
111
+
112
+ # Closes the current connection to Turntable if one was previously opened.
113
+ #
114
+ # @return [true]
115
+ def close(allow_reconnect = false)
116
+ if @connection
117
+ @update_timer.cancel if @update_timer
118
+ @update_timer = nil
119
+ @connection.close
120
+
121
+ wait do |fiber|
122
+ on(:session_ended, :once => true) { fiber.resume }
123
+ end
124
+
125
+ on_session_ended(allow_reconnect)
126
+ end
127
+
128
+ true
129
+ end
130
+
131
+ # Gets the chat server url currently connected to
132
+ #
133
+ # @api private
134
+ # @return [String]
135
+ def url
136
+ @connection && @connection.url
137
+ end
138
+
139
+ # Runs the given API command.
140
+ #
141
+ # @api private
142
+ # @param [String] command The name of the command to execute
143
+ # @param [Hash] params The parameters to pass into the command
144
+ # @return [Hash] The data returned from the Turntable service
145
+ # @raise [Turntabler::Error] if the connection is not open or the command fails to execute
146
+ def api(command, params = {})
147
+ raise(Turntabler::Error, 'Connection is not open') unless @connection && @connection.connected?
148
+
149
+ message_id = @connection.publish(params.merge(:api => command))
150
+
151
+ # Wait until we get a response for the given message
152
+ data = wait do |fiber|
153
+ on(:response_received, :once => true, :if => {'msgid' => message_id}) {|data| fiber.resume(data)}
154
+ end
155
+
156
+ if data['success']
157
+ data
158
+ else
159
+ error = data['error'] || data['err']
160
+ raise Error, "Command \"#{command}\" failed with message: \"#{error}\""
161
+ end
162
+ end
163
+
164
+ # Registers a handler to invoke when an event occurs in Turntable.
165
+ #
166
+ # @param [Symbol] event The event to register a handler for
167
+ # @param [Hash] options The configuration options for the handler
168
+ # @option options [Hash] :if Specifies a set of key-value pairs that must be matched in the event data in order to run the handler
169
+ # @option options [Boolean] :once (false) Whether to only run the handler once
170
+ # @return [true]
171
+ #
172
+ # == Room Events
173
+ #
174
+ # * +:room_updated+ - Information about the room was updated
175
+ #
176
+ # @example
177
+ # client.on :room_updated do |room| # Room
178
+ # puts room.description
179
+ # # ...
180
+ # end
181
+ #
182
+ # == User Events
183
+ #
184
+ # * +:user_entered+ - A user entered the room
185
+ # * +:user_left+ - A user left the room
186
+ # * +:user_booted+ - A user has been booted from the room
187
+ # * +:user_updated+ - A user's name / profile was updated
188
+ # * +:user_spoke+ - A user spoke in the chat room
189
+ #
190
+ # @example
191
+ # client.on :user_entered do |user| # User
192
+ # puts user.id
193
+ # # ...
194
+ # end
195
+ #
196
+ # client.on :user_left do |user| # User
197
+ # puts user.id
198
+ # # ...
199
+ # end
200
+ #
201
+ # client.on :user_booted do |boot| # Boot
202
+ # puts boot.user.id
203
+ # puts boot.reason
204
+ # # ...
205
+ # end
206
+ #
207
+ # client.on :user_updated do |user| # User
208
+ # puts user.laptop_name
209
+ # # ...
210
+ # end
211
+ #
212
+ # client.on :user_spoke do |message| # Message
213
+ # puts message.content
214
+ # # ...
215
+ # end
216
+ #
217
+ # == DJ Events
218
+ #
219
+ # * +:dj_added+ - A new DJ was added to the booth
220
+ # * +:dj_removed+ - A DJ was removed from the booth
221
+ #
222
+ # @example
223
+ # client.on :dj_added do |user| # User
224
+ # puts user.id
225
+ # # ...
226
+ # end
227
+ #
228
+ # client.on :dj_removed do |user| # User
229
+ # puts user.id
230
+ # # ...
231
+ # end
232
+ #
233
+ # == Moderator Events
234
+ #
235
+ # * +:moderator_added+ - A new moderator was added to the room
236
+ # * +:moderator_removed+ - A moderator was removed from the room
237
+ #
238
+ # @example
239
+ # client.on :moderator_added do |user| # User
240
+ # puts user.id
241
+ # # ...
242
+ # end
243
+ #
244
+ # client.on :moderator_removed do |user| # User
245
+ # puts user.id
246
+ # # ...
247
+ # end
248
+ #
249
+ # == Song Events
250
+ #
251
+ # * +:song_unavailable+ - Indicates that there are no more songs to play in the room
252
+ # * +:song_started+ - A new song has started playing
253
+ # * +:song_ended+ - The current song has ended. This is typically followed by a +:song_started+ or +:song_unavailable+ event.
254
+ # * +:song_voted+ - One or more votes were cast for the song
255
+ # * +:song_snagged+ - A user in the room has queued the current song onto their playlist
256
+ # * +:song_blocked+ - A song was skipped due to a copyright claim
257
+ # * +:song_limited+ - A song was skipped due to a limit on # of plays per hour
258
+ #
259
+ # @example
260
+ # client.on :song_unavailable do
261
+ # # ...
262
+ # end
263
+ #
264
+ # client.on :song_started do |song| # Song
265
+ # puts song.title
266
+ # # ...
267
+ # end
268
+ #
269
+ # client.on :song_ended do |song| # Song
270
+ # puts song.title
271
+ # # ...
272
+ # end
273
+ #
274
+ # client.on :song_voted do |song| # Song
275
+ # puts song.up_votes_count
276
+ # puts song.down_votes_count
277
+ # puts song.votes
278
+ # # ...
279
+ # end
280
+ #
281
+ # client.on :song_snagged do |snag| # Snag
282
+ # puts snag.user.id
283
+ # puts snag.song.id
284
+ # # ...
285
+ # end
286
+ #
287
+ # client.on :song_blocked do |song| # Song
288
+ # puts song.id
289
+ # # ...
290
+ # end
291
+ #
292
+ # client.on :song_limited do |song| # Song
293
+ # puts song.id
294
+ # # ...
295
+ # end
296
+ #
297
+ # == Messaging Events
298
+ #
299
+ # * +:message_received+ - A private message was received from another user in the room
300
+ #
301
+ # @example
302
+ # client.on :message_received do |message| # Message
303
+ # puts message.content
304
+ # # ...
305
+ # end
306
+ def on(event, options = {}, &block)
307
+ event = event.to_sym
308
+ @event_handlers[event] ||= []
309
+ @event_handlers[event] << Handler.new(event, options, &block)
310
+ true
311
+ end
312
+
313
+ # Gets the current room the authorized user is in or builds a new room
314
+ # bound to the given room id.
315
+ #
316
+ # @param [String] room_id The id of the room to build
317
+ # @return [Turntabler::Room]
318
+ # @example
319
+ # client.room # => #<Turntabler::Room id="ab28f..." ...>
320
+ # client.room('50985...') # => #<Turntabler::Room id="50985..." ...>
321
+ def room(room_id = nil)
322
+ room_id ? Room.new(self, :_id => room_id) : @room
323
+ end
324
+
325
+ # Gets the current authorized user or builds a new user bound to the given
326
+ # user id.
327
+ #
328
+ # @param [String] user_id The id of the user to build
329
+ # @return [Turntabler::User]
330
+ # @example
331
+ # client.user # => #<Turntabler::User id="fb129..." ...>
332
+ # client.user('a34bd...') # => #<Turntabler::User id="a34bd..." ...>
333
+ def user(user_id = nil)
334
+ user_id ? User.new(self, :_id => user_id) : @user
335
+ end
336
+
337
+ # Get all avatars availble on Turntable.
338
+ #
339
+ # @return [Array<Turntabler::Avatar>]
340
+ # @raise [Turntabler::Error] if the command fails
341
+ # @example
342
+ # client.avatars # => [#<Turntabler::Avatar ...>, ...]
343
+ def avatars
344
+ data = api('user.available_avatars')
345
+ avatars = []
346
+ data['avatars'].each do |avatar_group|
347
+ avatar_group['avatarids'].each do |avatar_id|
348
+ avatars << Avatar.new(self, :_id => avatar_id, :min => avatar_group['min'], :acl => avatar_group['acl'])
349
+ end
350
+ end
351
+ avatars
352
+ end
353
+
354
+ # Get all stickers available on Turntable.
355
+ #
356
+ # @return [Array<Turntabler::Sticker>]
357
+ # @raise [Turntabler::Error] if the command fails
358
+ # @example
359
+ # client.stickers # => [#<Turntabler::Sticker id="...">, ...]
360
+ def stickers
361
+ data = api('sticker.get')
362
+ data['stickers'].map {|attrs| Sticker.new(self, attrs)}
363
+ end
364
+
365
+ # Builds a new song bound to the given song id.
366
+ #
367
+ # @param [String] song_id The id of the song to build
368
+ # @return [Turntabler::Song]
369
+ # @example
370
+ # client.song('a34bd...') # => #<Turntabler::Song id="a34bd..." ...>
371
+ def song(song_id)
372
+ Song.new(self, :_id => song_id)
373
+ end
374
+
375
+ # Finds songs that match the given query.
376
+ #
377
+ # @param [String] query The query string to search for
378
+ # @param [Hash] options The configuration options for the search
379
+ # @option options [Fixnum] :page The page number to get from the results
380
+ # @return [Array<Turntabler::Song>]
381
+ # @raise [ArgumentError] if an invalid option is specified
382
+ # @raise [Turntabler::Error] if the command fails
383
+ # @example
384
+ # client.search_song('Like a Rolling Stone') # => [#<Turntabler::Sticker ...>, ...]
385
+ def search_song(query, options = {})
386
+ assert_valid_keys(options, :page)
387
+ options = {:page => 1}.merge(options)
388
+
389
+ api('file.search', :query => query, :page => options[:page])
390
+
391
+ # Wait for the async callback
392
+ songs = wait do |fiber|
393
+ on(:search_completed, :once => true, :if => {'query' => query}) {|songs| fiber.resume(songs)}
394
+ on(:search_failed, :once => true, :if => {'query' => query}) { fiber.resume }
395
+ end
396
+
397
+ songs || raise(Error, 'Search failed to complete')
398
+ end
399
+
400
+ private
401
+ # Callback when a message has been received from Turntable. This will run
402
+ # any handlers registered for the event associated with the message.
403
+ def on_message(data)
404
+ if Event.command?(data['command'])
405
+ event = Event.new(self, data)
406
+ handlers = @event_handlers[event.name] || []
407
+ handlers.each do |handler|
408
+ success = handler.run(event)
409
+ handlers.delete(handler) if success && handler.once
410
+ end
411
+ end
412
+ end
413
+
414
+ # Callback when a heartbeat message has been received from Turntable determining
415
+ # whether this client is still alive.
416
+ def on_heartbeat
417
+ user.update(:status => user.status)
418
+ end
419
+
420
+ # Callback when session authentication is missing from the connection. This
421
+ # will automatically authenticate with configured user as well as set up a
422
+ # heartbeat.
423
+ def on_session_missing
424
+ user.authenticate
425
+ user.fan_of
426
+ user.update(:status => user.status)
427
+
428
+ # Periodically update the user's status to remain available
429
+ @update_timer.cancel if @update_timer
430
+ @update_timer = EM::Synchrony.add_periodic_timer(10) { user.update(:status => user.status) }
431
+ end
432
+
433
+ # Callback when the session has ended. This will automatically reconnect if
434
+ # allowed to do so.
435
+ def on_session_ended(allow_reconnect)
436
+ url = @connection.url
437
+ room = @room
438
+ @connection = nil
439
+ @room = nil
440
+
441
+ # Automatically reconnect to the room / server if allowed
442
+ if @reconnect && allow_reconnect
443
+ EM::Synchrony.add_timer(@reconnect_wait) do
444
+ room ? room.enter : connect(url)
445
+ end
446
+ end
447
+ end
448
+
449
+ # Pauses the current fiber until it is resumed with response data. This
450
+ # can only get resumed explicitly by the provided block.
451
+ def wait
452
+ fiber = Fiber.current
453
+ yield(fiber)
454
+ Fiber.yield
455
+ end
456
+ end
457
+ end
@@ -0,0 +1,176 @@
1
+ require 'faye/websocket'
2
+ require 'em-http'
3
+ require 'json'
4
+
5
+ require 'turntabler/assertions'
6
+ require 'turntabler/loggable'
7
+
8
+ module Turntabler
9
+ # Represents the interface for sending and receiving data in Turntable
10
+ # @api private
11
+ class Connection
12
+ include Assertions
13
+ include Loggable
14
+
15
+ # Tracks the list of APIs that don't work through the web socket -- these
16
+ # must be requested through the HTTP channel
17
+ # @return [Array<String>]
18
+ HTTP_APIS = %w(room.directory_rooms user.get_prefs)
19
+
20
+ # The URL that this connection is bound to
21
+ # @return [String]
22
+ attr_reader :url
23
+
24
+ # The callback to run when a message is received from the underlying socket.
25
+ # The data passed to the callback will always be a hash.
26
+ # @return [Proc]
27
+ attr_accessor :handler
28
+
29
+ # Builds a new connection for sending / receiving data via the given url.
30
+ #
31
+ # @note This will *not* open the connection -- #start must be explicitly called in order to do so.
32
+ # @param [String] url The URL to open a conection to
33
+ # @param [Hash] options The connection options
34
+ # @option options [Fixnum] :timeout The amount of time to allow to elapse for requests before timing out
35
+ # @option options [Hash] :params A default set of params that will get included on every message sent
36
+ # @raise [ArgumentError] if an invalid option is specified
37
+ def initialize(url, options = {})
38
+ assert_valid_keys(options, :timeout, :params)
39
+
40
+ @url = url
41
+ @message_id = 0
42
+ @timeout = options[:timeout]
43
+ @default_params = options[:params] || {}
44
+ end
45
+
46
+ # Initiates the connection with turntable
47
+ #
48
+ # @return [true]
49
+ def start
50
+ @socket = Faye::WebSocket::Client.new(url)
51
+ @socket.onopen = lambda {|event| on_open(event)}
52
+ @socket.onclose = lambda {|event| on_close(event)}
53
+ @socket.onmessage = lambda {|event| on_message(event)}
54
+ true
55
+ end
56
+
57
+ # Closes the connection (if one was previously opened)
58
+ #
59
+ # @return [true]
60
+ def close
61
+ @socket.close if @socket
62
+ true
63
+ end
64
+
65
+ # Whether this connection's socket is currently open
66
+ #
67
+ # @return [Boolean] +true+ if the connection is open, otherwise +false+
68
+ def connected?
69
+ @connected
70
+ end
71
+
72
+ # Publishes the given params to the underlying web socket. The defaults
73
+ # initially configured as part of the connection will also be included in
74
+ # the message.
75
+ #
76
+ # @param [Hash] params The parameters to include in the message sent
77
+ # @return [Fixnum] The id of the message delivered
78
+ def publish(params)
79
+ params[:msgid] = message_id = next_message_id
80
+ params = @default_params.merge(params)
81
+
82
+ logger.debug "Message sent: #{params.inspect}"
83
+
84
+ if HTTP_APIS.include?(params[:api])
85
+ publish_to_http(params)
86
+ else
87
+ publish_to_socket(params)
88
+ end
89
+
90
+ # Add timeout handler
91
+ EventMachine.add_timer(@timeout) do
92
+ dispatch('msgid' => message_id, 'command' => 'response_received', 'error' => 'timed out')
93
+ end if @timeout
94
+
95
+ message_id
96
+ end
97
+
98
+ private
99
+ # Publishes the given params to the web socket
100
+ def publish_to_socket(params)
101
+ message = params.to_json
102
+ data = "~m~#{message.length}~m~#{message}"
103
+ @socket.send(data)
104
+ end
105
+
106
+ # Publishes the given params to the HTTP API
107
+ def publish_to_http(params)
108
+ api = params.delete(:api)
109
+ message_id = params[:msgid]
110
+
111
+ http = EventMachine::HttpRequest.new("http://turntable.fm/api/#{api}").get(:query => params)
112
+ if http.response_header.status == 200
113
+ # Command executed properly: parse the results
114
+ success, data = JSON.parse(http.response)
115
+ data = {'result' => data} unless data.is_a?(Hash)
116
+ message = data.merge('success' => success)
117
+ else
118
+ # Command failed to run
119
+ message = {'success' => false, 'error' => http.error}
120
+ end
121
+ message.merge!('msgid' => message_id)
122
+
123
+ # Run the message handler
124
+ event = Faye::WebSocket::API::Event.new('message', :data => "~m~#{Time.now.to_i}~m~#{JSON.generate(message)}")
125
+ on_message(event)
126
+ end
127
+
128
+ # Runs the configured handler with the given message
129
+ def dispatch(message)
130
+ Turntabler.run { @handler.call(message) } if @handler
131
+ end
132
+
133
+ # Callback when the socket is opened.
134
+ def on_open(event)
135
+ logger.debug 'Socket opened'
136
+ @connected = true
137
+ end
138
+
139
+ # Callback when the socket is closed. This will mark the connection as no
140
+ # longer connected.
141
+ def on_close(event)
142
+ logger.debug 'Socket closed'
143
+ @connected = false
144
+ @socket = nil
145
+ dispatch('command' => 'session_ended')
146
+ end
147
+
148
+ # Callback when a message has been received from the remote server on the
149
+ # open socket.
150
+ def on_message(event)
151
+ data = event.data
152
+
153
+ response = data.match(/~m~\d*~m~(.*)/)[1]
154
+ message =
155
+ case response
156
+ when /no_session/
157
+ {'command' => 'no_session'}
158
+ when /~h~([0-9]+)/
159
+ # Send the heartbeat command back to the server
160
+ @socket.send($1)
161
+ {'command' => 'heartbeat'}
162
+ else
163
+ JSON.parse(response)
164
+ end
165
+ message['command'] = 'response_received' if message['msgid']
166
+
167
+ logger.debug "Message received: #{message.inspect}"
168
+ dispatch(message)
169
+ end
170
+
171
+ # Calculates what the next message id should be sent to turntable
172
+ def next_message_id
173
+ @message_id += 1
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,13 @@
1
+ module Turntabler
2
+ # Provides a set of helper functions for dealing with message digests
3
+ # @api private
4
+ module DigestHelpers
5
+ # Generates a SHA1 hash from the given data
6
+ #
7
+ # @param [String] data The data to create a hash from
8
+ # @return [String]
9
+ def digest(data)
10
+ Digest::SHA1.hexdigest(data.to_s)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ module Turntabler
2
+ # Represents an error that occurred while interacting with the Turntable API
3
+ class Error < StandardError
4
+ end
5
+ end