Class: Turntabler::Client

Inherits:
Object
  • Object
show all
Includes:
Assertions, DigestHelpers, Loggable
Defined in:
lib/turntabler/client.rb

Overview

Provides access to the Turntable API

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Methods included from DigestHelpers

#digest

Methods included from Assertions

#assert_valid_keys, #assert_valid_values

Constructor Details

- (Client) initialize(email, password, options = {}) { ... }

Creates a new client for communicating with Turntable.fm with the given email / password.

Options Hash (options):

  • :id (String)

    The unique identifier representing this client

  • :room (String)

    The id of the room to initially enter

  • :user_id (String)

    The Turntable id for the authenticating user (required if the user does not have an associated password)

  • :auth (String)

    The authentication token for the user (required if the user does not have an associated password)

  • :timeout (Fixnum) — default: 10

    The amount of seconds to allow to elapse for requests before timing out

  • :reconnect (Boolean) — default: false

    Whether to allow the client to automatically reconnect when disconnected either by Turntable or by the network

  • :reconnect_wait (Fixnum) — default: 5

    The amount of seconds to wait before reconnecting

Yields:

  • Runs the given block within the context if the client (for DSL-type usage)

Raises:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/turntabler/client.rb', line 58

def initialize(email, password, options = {}, &block)
  options = {
    :id => "#{Time.now.to_i}-#{rand}",
    :timeout => 10,
    :reconnect => false,
    :reconnect_wait => 5
  }.merge(options)
  assert_valid_keys(options, :id, :room, :url, :user_id, :auth, :timeout, :reconnect, :reconnect_wait)

  @id = options[:id]
  @user = AuthorizedUser.new(self, :email => email, :password => password, :_id => options[:user_id], :userauth => options[:auth])
  @rooms = RoomDirectory.new(self)
  @event_handlers = {}
  @timeout = options[:timeout]
  @reconnect = options[:reconnect]
  @reconnect_wait = options[:reconnect_wait]
  @clock_delta = 0

  # Setup default event handlers
  on(:heartbeat) { on_heartbeat }
  on(:session_missing) { on_session_missing }
  on(:session_ended) { on_session_ended }

  # Connect to an initial room / server
  reconnect_from(ConnectionError, APIError) do
    if room_name = options[:room]
      room(room_name).enter
    elsif url = options[:url]
      connect(url)
    else
      connect
    end
  end

  instance_eval(&block) if block_given?
end

Instance Attribute Details

- (Fixnum) clock_delta

The difference of time (in seconds) between this client and Turntable servers



41
42
43
# File 'lib/turntabler/client.rb', line 41

def clock_delta
  @clock_delta
end

- (String) id (readonly)

The unique id representing this client



24
25
26
# File 'lib/turntabler/client.rb', line 24

def id
  @id
end

- (Turntabler::Room) room(room_id = nil)

Gets the current room the authorized user is in or builds a new room bound to the given room id.

Examples:

client.room               # => #<Turntabler::Room id="ab28f..." ...>
client.room('50985...')   # => #<Turntabler::Room id="50985..." ...>


409
410
411
# File 'lib/turntabler/client.rb', line 409

def room(room_id = nil)
  room_id ? Room.new(self, :_id => room_id) : @room
end

- (Turntabler::RoomDirectory) rooms (readonly)

The directory for looking up / creating rooms



33
34
35
# File 'lib/turntabler/client.rb', line 33

def rooms
  @rooms
end

- (Fixnum) timeout (readonly)

The response timeout configured for the connection



37
38
39
# File 'lib/turntabler/client.rb', line 37

def timeout
  @timeout
end

Instance Method Details

- (Hash) api(command, params = {})

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Runs the given API command.

Raises:

  • (Turntabler::Error)

    if the connection is not open or the command fails to execute



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/turntabler/client.rb', line 161

def api(command, params = {})
  raise(ConnectionError, 'Connection is not open') unless @connection && @connection.connected?
  
  message_id = @connection.publish(params.merge(:api => command))

  # Wait until we get a response for the given message
  data = wait do |&resume|
    on(:response_received, :once => true, :if => {'msgid' => message_id}) {|data| resume.call(data)}
  end

  if data['success']
    data
  else
    error = data['error'] || data['err']
    raise APIError, "Command \"#{command}\" failed with message: \"#{error}\""
  end
end

- (Array<Turntabler::Avatar>) avatars

Get all avatars availble on Turntable.

Examples:

client.avatars    # => [#<Turntabler::Avatar ...>, ...]

Raises:



446
447
448
449
450
451
452
453
454
455
# File 'lib/turntabler/client.rb', line 446

def avatars
  data = api('user.available_avatars')
  avatars = []
  data['avatars'].each do |avatar_group|
    avatar_group['avatarids'].each do |avatar_id|
      avatars << Avatar.new(self, :_id => avatar_id, :min => avatar_group['min'], :acl => avatar_group['acl'])
    end
  end
  avatars
end

- (true) close(allow_reconnect = false)

Closes the current connection to Turntable if one was previously opened.



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/turntabler/client.rb', line 125

def close(allow_reconnect = false)
  if @connection
    # Disable reconnects if specified
    reconnect = @reconnect
    @reconnect = reconnect && allow_reconnect

    # Clean up timers / connections
    @keepalive_timer.cancel if @keepalive_timer
    @keepalive_timer = nil
    @connection.close

    # Revert change to reconnect config once the final signal is received
    wait do |&resume|
      on(:session_ended, :once => true) { resume.call }
    end
    @reconnect = reconnect
  end
  
  true
end

- (true) connect(url = room(digest(rand)).url)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Note:

This will only open a new connection if the client isn't already connected to the given url

Initiates a connection with the given url. Once a connection is started, this will also attempt to authenticate the user.

Raises:



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/turntabler/client.rb', line 103

def connect(url = room(digest(rand)).url)
  if !@connection || !@connection.connected? || @connection.url != url
    # Close any existing connection
    close

    # Create a new connection to the given url
    @connection = Connection.new(url, :timeout => timeout, :params => {:clientid => id, :userid => user.id, :userauth => user.auth})
    @connection.handler = lambda {|data| trigger(data.delete('command'), data)}
    @connection.start

    # Wait until the connection is authenticated
    wait do |&resume|
      on(:session_missing, :once => true) { resume.call }
    end
  end

  true
end

- (true) on(event, options = {}, &block)

Registers a handler to invoke when an event occurs in Turntable.

Client Events

  • :reconnected - The client reconnected (and re-entered any room that the user was previously in)

Room Events

  • :room_updated - Information about the room was updated

  • :room_description_updated - The room's description was updated

User Events

  • :user_entered - A user entered the room

  • :user_left - A user left the room

  • :user_booted - A user has been booted from the room

  • :user_updated - A user's profile was updated

  • :user_name_updated - A user's name was updated

  • :user_avatar_updated - A user's avatar was updated

  • :user_spoke - A user spoke in the chat room

DJ Events

  • :fan_added - A new fan was added by a user in the room

  • :fan_removed - A fan was removed from a user in the room

DJ Events

  • :dj_added - A new DJ was added to the stage

  • :dj_removed - A DJ was removed from the stage

  • :dj_escorted_off - A DJ was escorted off the stage by a moderator

  • :dj_booed_off - A DJ was booed off the stage

Moderator Events

  • :moderator_added - A new moderator was added to the room

  • :moderator_removed - A moderator was removed from the room

Song Events

  • :song_unavailable - Indicates that there are no more songs to play in the room

  • :song_started - A new song has started playing

  • :song_ended - The current song has ended. This is typically followed by a :song_started or :song_unavailable event.

  • :song_voted - One or more votes were cast for the song

  • :song_snagged - A user in the room has queued the current song onto their playlist

  • :song_skipped - A song was skipped due to either the dj skipping it or too many downvotes

  • :song_moderated - A song was forcefully skipped by a moderator

  • :song_blocked - A song was prevented from playing due to a copyright claim

Messaging Events

  • :message_received - A private message was received from another user in the room

Examples:

client.on :reconnected do
  client.room.dj
  # ...
end
client.on :room_updated do |room| # Room
  puts room.description
  # ...
end

client.on :room_description_updated do |room| # Room
  puts room.description
  # ...
end
client.on :user_entered do |user| # User
  puts user.id
  # ...
end

client.on :user_left do |user| # User
  puts user.id
  # ...
end

client.on :user_booted do |boot| # Boot
  puts boot.user.id
  puts boot.reason
  # ...
end

client.on :user_updated do |user| # User
  puts user.name
  # ...
end

client.on :user_name_updated do |user| # User
  puts user.name
  # ...
end

client.on :user_avatar_updated do |user| # User
  puts user.avatar.id
  # ...
end

client.on :user_stickers_updated do |user| # User
  puts user.stickers.map {|sticker| sticker.id}
  # ...
end

client.on :user_spoke do |message| # Message
 puts message.content
  # ...
end
client.on :fan_added do |user, fan_of| # User, User
  puts user.id
  # ...
end

client.on :fan_removed do |user, count| # User, Fixnum
  puts user.id
  # ...
end
client.on :dj_added do |user| # User
  puts user.id
  # ...
end

client.on :dj_removed do |user| # User
  puts user.id
  # ...
end

client.on :dj_escorted_off do |user, moderator| # User, User
  puts user.id
  # ...
end

client.on :dj_booed_off do |user| # User
  puts user.id
  # ...
end
client.on :moderator_added do |user| # User
  puts user.id
  # ...
end

client.on :moderator_removed do |user| # User
  puts user.id
  # ...
end
client.on :song_unavailable do
  # ...
end

client.on :song_started do |song| # Song
  puts song.title
  # ...
end

client.on :song_ended do |song| # Song
  puts song.title
  # ...
end

client.on :song_voted do |song| # Song
  puts song.up_votes_count
  puts song.down_votes_count
  puts song.votes
  # ...
end

client.on :song_snagged do |snag| # Snag
  puts snag.user.id
  puts snag.song.id
  # ...
end

client.on :song_skipped do |song| # Song
  puts song.title
  # ...
end

client.on :song_moderated do |song, moderator| # Song, User
  puts song.title
  puts moderator.id
  # ...
end

client.on :song_blocked do |song| # Song
  puts song.id
  # ...
end

client.on :song_limited do |song| # Song
  puts song.id
  # ...
end
client.on :message_received do |message| # Message
  puts message.content
  # ...
end

Options Hash (options):

  • :if (Hash)

    Specifies a set of key-value pairs that must be matched in the event data in order to run the handler

  • :once (Boolean) — default: false

    Whether to only run the handler once



394
395
396
397
398
399
# File 'lib/turntabler/client.rb', line 394

def on(event, options = {}, &block)
  event = event.to_sym
  @event_handlers[event] ||= []
  @event_handlers[event] << Handler.new(event, options, &block)
  true
end

- (Object) reset_keepalive(interval = 10)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Resets the keepalive timer to run at the given interval.



579
580
581
582
583
584
585
586
587
588
589
# File 'lib/turntabler/client.rb', line 579

def reset_keepalive(interval = 10)
  if !@keepalive_timer || @keepalive_interval != interval
    @keepalive_interval = interval

    # Periodically update the user's status to remain available
    @keepalive_timer.cancel if @keepalive_timer
    @keepalive_timer = EM::Synchrony.add_periodic_timer(interval) do
      Turntabler.run { user.update(:status => user.status) }
    end
  end
end

- (Array<Turntabler::Song>) search_song(query, options = {})

Note:

The user must be entered in a room to search for songs

Finds songs that match the given query.

Examples:

# Less accurate, general query search
client.search_song('Like a Rolling Stone by Bob Dylan')             # => [#<Turntabler::Song ...>, ...]

# More accurate, explicit title / artist search
client.search_song('Like a Rolling Stone', :artist => 'Bob Dylan')  # => [#<Turntabler::Song ...>, ...]

Options Hash (options):

  • :artist (String)

    The name of the artist for the song

  • :duration (Fixnum)

    The length, in minutes, of the song

  • :page (Fixnum)

    The page number to get from the results

Raises:

  • (ArgumentError)

    if an invalid option is specified

  • (Turntabler::Error)

    if the user is not in a room or the command fails



495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
# File 'lib/turntabler/client.rb', line 495

def search_song(query, options = {})
  assert_valid_keys(options, :artist, :duration, :page)
  options = {:page => 1}.merge(options)

  raise(APIError, 'User must be in a room to search for songs') unless room

  if artist = options[:artist]
    query = "title: #{query}"
    query << " artist: #{artist}"
  end
  query << " duration: #{options[:duration]}" if options[:duration]

  api('file.search', :query => query, :page => options[:page])

  conditions = {'query' => query}

  # Time out if the response takes too long
  EventMachine.add_timer(@timeout) do
    trigger(:search_failed, conditions)
  end if @timeout

  # Wait for the async callback
  songs = wait do |&resume|
    on(:search_completed, :once => true, :if => conditions) {|songs| resume.call(songs)}
    on(:search_failed, :once => true, :if => conditions) { resume.call }
  end

  # Clean up any leftover handlers
  @event_handlers[:search_completed].delete_if {|handler| handler.conditions == conditions}
  @event_handlers[:search_failed].delete_if {|handler| handler.conditions == conditions}

  songs || raise(APIError, 'Search failed to complete')
end

- (Turntabler::Song) song(song_id)

Builds a new song bound to the given song id.

Examples:

client.song('a34bd...')   # => #<Turntabler::Song id="a34bd..." ...>


474
475
476
# File 'lib/turntabler/client.rb', line 474

def song(song_id)
  Song.new(self, :_id => song_id)
end

- (Array<Turntabler::Sticker>) stickers

Get all stickers available on Turntable.

Examples:

client.stickers   # => [#<Turntabler::Sticker id="...">, ...]

Raises:



463
464
465
466
# File 'lib/turntabler/client.rb', line 463

def stickers
  data = api('sticker.get')
  data['stickers'].map {|attrs| Sticker.new(self, attrs)}
end

- (true) trigger(command, *args)

Note:

If the command is unknown, it will simply get skipped and not raise an exception

Triggers callback handlers for the given Turntable command. This should either be invoked when responses are received for Turntable or when triggering custom events.

Triggering custom events

After defining custom events, `trigger` can be used to invoke any handler that's been registered for that event. The argument list passed into `trigger` will be passed, exactly as specified, to the registered handlers.

Examples:

# Define names of events
Turntabler.events(:no_args, :one_arg, :multiple_args)

# ...

# Register handlers
client.on(:no_args) { }
client.on(:one_arg) {|arg| }
client.on(:multiple_args) {|arg1, arg2| }

# Trigger handlers registered for events
client.trigger(:no_args)              # => true
client.trigger(:one_arg, 1)           # => true
client.trigger(:multiple_args, 1, 2)  # => true


560
561
562
563
564
565
566
567
568
569
570
571
572
573
# File 'lib/turntabler/client.rb', line 560

def trigger(command, *args)
  command = command.to_sym if command

  if Event.command?(command)
    event = Event.new(self, command, args)
    handlers = @event_handlers[event.name] || []
    handlers.each do |handler|
      success = handler.run(event)
      handlers.delete(handler) if success && handler.once
    end
  end

  true
end

- (String) url

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Gets the chat server url currently connected to



150
151
152
# File 'lib/turntabler/client.rb', line 150

def url
  @connection && @connection.url
end

- (Turntabler::User) user(user_id = nil)

Gets the current authorized user or builds a new user bound to the given user id.

Examples:

client.user               # => #<Turntabler::User id="fb129..." ...>
client.user('a34bd...')   # => #<Turntabler::User id="a34bd..." ...>


421
422
423
# File 'lib/turntabler/client.rb', line 421

def user(user_id = nil)
  user_id ? User.new(self, :_id => user_id) : @user
end

- (Turntabler::User) user_by_name(name)

Gets the user with the given DJ name. This should only be used if the id of the user is unknown.

Examples:

client.user_by_name('DJSpinster')   # => #<Turntabler::User id="a34bd..." ...>

Raises:



433
434
435
436
437
438
# File 'lib/turntabler/client.rb', line 433

def user_by_name(name)
  data = api('user.get_id', :name => name)
  user = self.user(data['userid'])
  user.attributes = {'name' => name}
  user
end