Class: SpotifyWeb::Client

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

Overview

Provides access to the Spotify Web API

Constant Summary collapse

KEEPALIVE_INTERVAL =

The interval with which to send keepalives

180
SEARCH_TYPES =

The resource types that can be searched (and their respective values)

{
  :songs => {:value => 1, :resource => Song},
  :albums => {:value => 2, :resource => Album},
  :artists => {:value => 4, :resource => Artist},
  :playlists => {:value => 8, :resource => Playlist},
  :all => {:value => 15}
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Assertions

#assert_valid_keys, #assert_valid_values

Constructor Details

#initialize(username, password, options = {}) { ... } ⇒ Client

Creates a new client for communicating with Spotify with the given username / password.

Options Hash (options):

  • :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 Spotify 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:


53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/spotify_web/client.rb', line 53

def initialize(username, password, options = {}, &block)
  options = {
    :timeout => 10,
    :reconnect => false,
    :reconnect_wait => 5
  }.merge(options)
  assert_valid_keys(options, :timeout, :reconnect, :reconnect_wait)

  @user = AuthorizedUser.new(self, :username => username, :password => password)
  @event_handlers = {}
  @timeout = options[:timeout]
  @reconnect = options[:reconnect]
  @reconnect_wait = options[:reconnect_wait]

  # Setup default event handlers
  on(:work_requested) {|work| on_work_requested(work) }
  on(:session_ended)  { on_session_ended }

  reconnect_from(ConnectionError, APIError) do
    connect
  end

  instance_eval(&block) if block_given?
end

Instance Attribute Details

#timeoutFixnum (readonly)

The response timeout configured for the connection


40
41
42
# File 'lib/spotify_web/client.rb', line 40

def timeout
  @timeout
end

#user(username = nil) ⇒ SpotifyWeb::User (readonly)

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

Examples:

client.user               # => #<SpotifyWeb::User username="benze..." ...>
client.user('johnd...')   # => #<SpotifyWeb::User username="johnd..." ...>

36
37
38
# File 'lib/spotify_web/client.rb', line 36

def user
  @user
end

Instance Method Details

#album(attributes) ⇒ SpotifyWeb::Album

Builds a new album bound to the given id / attributes.

Examples:

client.album("\x03\xC0...")   # => #<SpotifyWeb::Album id="\x03\xC0..." ...>

240
241
242
243
# File 'lib/spotify_web/client.rb', line 240

def album(attributes)
  attributes = {:gid => attributes} unless attributes.is_a?(Hash)
  Album.new(self, attributes)
end

#api(command, args = nil) ⇒ Hash

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:

  • (SpotifyWeb::Error)

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


147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/spotify_web/client.rb', line 147

def api(command, args = nil)
  raise(ConnectionError, 'Connection is not open') unless @connection && @connection.connected?

  if command == 'request' && args.delete(:batch)
    batch(command, args) do |batch_command, batch_args|
      api(batch_command, batch_args)
    end
  else
    # Process this as a mercury request
    if command == 'request'
      response_schema = args.delete(:response_schema)
    end

    message_id = @connection.publish(command, args)

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

    if command == 'request' && !data['error']
      # Parse the response bsed on the schema provided
      header, body = data['result']
      request = Schema::Mercury::MercuryRequest.decode(Base64.decode64(header))

      if (400..599).include?(request.status_code)
        data['error'] = "Failed response: #{request.status_code}"
      else
        data['result'] = response_schema.decode(Base64.decode64(body))
      end
    end

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

#artist(attributes) ⇒ SpotifyWeb::Artist

Builds a new artist bound to the given id.

Examples:

client.artist("\xC1\x8Fr...")   # => #<SpotifyWeb::Artist gid="\xC1\x8Fr..." ...>

229
230
231
232
# File 'lib/spotify_web/client.rb', line 229

def artist(attributes)
  attributes = {:gid => attributes} unless attributes.is_a?(Hash)
  Artist.new(self, attributes)
end

#close(allow_reconnect = false) ⇒ true

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


119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/spotify_web/client.rb', line 119

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

#connecttrue

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:


86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/spotify_web/client.rb', line 86

def connect
  if !@connection || !@connection.connected?
    # Close any existing connection
    close

    # Create a new connection to the given url
    @connection = Connection.new(access_point_url, :timeout => timeout)
    @connection.handler = lambda {|data| trigger(data.delete('command'), data)}
    @connection.start

    # Wait for connection to open
    wait do |&resume|
      on(:session_started, :once => true) { resume.call }
    end

    # Send the user's credentials
    creds = user.settings['credentials'][0].split(':')
    message = [creds[0], creds[1], creds[2..-1] * ':']
    api('connect', message)

    wait do |&resume|
      on(:session_authenticated, :once => true) { resume.call }
    end

    start_keepalives
  end

  true
end

#search(query, options = {}) ⇒ Hash, Array<SpotifyWeb::Resource>

Finds songs / albums / artists / playlists that match the given query.

Examples:

# General query search
client.search('Like a Rolling Stone')                   # => {:songs => [...], :albums => [...], ...}

# More accurate, explicit title / artist search
client.search('Like a Rolling Stone', :only => :songs)  # => [#<SpotifyWeb::Song ...>, ...]

Options Hash (options):

  • :only (Symbol)

    The type of resource being looked up. Default is all resource types. Valid values include :songs, :albums, :artists, and :playlists.

  • :limit (Fixnum) — default: 50

    The total number of each type of resource to get

  • :skip (Fixnum) — default: 0

    The number of resources to skip when loading the list

Raises:

  • (ArgumentError)

    if an invalid option is specified


260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/spotify_web/client.rb', line 260

def search(query, options = {})
  assert_valid_keys(options, :only, :limit, :skip)
  options = {:limit => 50, :skip => 0}.merge(options)
  assert_valid_values(options[:only], *(SEARCH_TYPES.keys - [:all] + [nil]))

  options[:type] = options.delete(:only) || :all

  search_value = SEARCH_TYPES[options[:type]][:value]
  response = api('sp/search', [query, search_value, options[:limit], options[:skip]])
  data = MultiXml.parse(response['result'])['result']

  # Typecast data results to resources
  results = {}
  SEARCH_TYPES.each do |type, config|
    next unless resource_class = config[:resource]
    resource_name = resource_class.resource_name

    resources_data = data["#{resource_name}s"] ? [data["#{resource_name}s"][resource_name]].flatten : []
    results[type] = resources_data.map do |resource_data|
      resource_class.from_search_result(self, resource_data)
    end
  end

  if options[:type] == :all
    results
  else
    results[options[:type]]
  end
end

#song(attributes) ⇒ SpotifyWeb::Song

Builds a new song bound to the given id.

Examples:

client.song("\x92\xA9...")   # => #<SpotifyWeb::Song id="\x92\xA9..." ...>

218
219
220
221
# File 'lib/spotify_web/client.rb', line 218

def song(attributes)
  attributes = {:gid => attributes} unless attributes.is_a?(Hash)
  Song.new(self, attributes)
end

#start_keepalivesObject

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.

Starts the keepalive timer for ensure the connection remains open.


189
190
191
192
193
194
# File 'lib/spotify_web/client.rb', line 189

def start_keepalives
  @keepalive_timer.cancel if @keepalive_timer
  @keepalive_timer = EM::Synchrony.add_periodic_timer(KEEPALIVE_INTERVAL) do
    SpotifyWeb.run { api('sp/echo', 'h') }
  end
end