Skip to content

Instantly share code, notes, and snippets.

@ericboehs
Last active August 29, 2015 14:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ericboehs/7d4488cbe9e0b9961a06 to your computer and use it in GitHub Desktop.
Save ericboehs/7d4488cbe9e0b9961a06 to your computer and use it in GitHub Desktop.
Example ruby script to retrieve users playlists (including starred)
#!/usr/bin/env ruby
# encoding: utf-8
# Gemfile:
# source "https://rubygems.org"
#
# gem 'spotify'
# gem 'pry'
require "bundler/setup"
require "spotify"
require "logger"
require "pry"
require "io/console"
# Kill main thread if any other thread dies.
Thread.abort_on_exception = true
# We use a logger to print some information on when things are happening.
$stderr.sync = true
$logger = Logger.new($stderr)
$logger.level = Logger::INFO
$logger.formatter = proc do |severity, datetime, progname, msg|
progname = if progname
" (#{progname}) "
else
" "
end
"\n[#{severity} @ #{datetime.strftime("%H:%M:%S")}]#{progname}#{msg}"
end
#
# Some utility.
#
module Support
module_function
DEFAULT_CONFIG = Spotify::SessionConfig.new({
api_version: Spotify::API_VERSION.to_i,
application_key: File.binread("./spotify_appkey.key"),
cache_location: ".spotify/",
settings_location: ".spotify/",
user_agent: "spotify for ruby",
callbacks: Spotify::SessionCallbacks.new
})
def logger
$logger
end
class << self
attr_accessor :silenced
end
# libspotify supports callbacks, but they are not useful for waiting on
# operations (how they fire can be strange at times, and sometimes they
# might not fire at all). As a result, polling is the way to go.
def poll(session, idle_time = 0.05)
until yield
print "." unless silenced
process_events(session)
sleep(idle_time)
end
end
# Process libspotify events once.
def process_events(session)
FFI::MemoryPointer.new(:int) do |ptr|
Spotify.session_process_events(session, ptr)
end
end
# Ask the user for input with a prompt explaining what kind of input.
def prompt(message, default = nil)
if default
print "\n#{message} [#{default}]: "
input = gets.chomp
if input.empty?
default
else
input
end
else
print "\n#{message}: "
gets.chomp
end
end
def initialize_spotify!(config = DEFAULT_CONFIG)
session = FFI::MemoryPointer.new(Spotify::Session) do |ptr|
Spotify.try(:session_create, config, ptr)
break Spotify::Session.new(ptr.read_pointer)
end
remembered_user_length = Spotify.session_remembered_user(session, nil, 0)
if remembered_user_length > 0
username = FFI::MemoryPointer.new(:int, remembered_user_length + 1) do |username_ptr|
Spotify.session_remembered_user(session, username_ptr, username_ptr.size)
break username_ptr.read_string.force_encoding("UTF-8")
end
logger.info "Using remembered login for: #{username}."
Spotify.try(:session_relogin, session)
else
username = prompt("Spotify username, or Facebook e-mail")
password = $stdin.noecho { prompt("Spotify password, or Facebook password") }
logger.info "Attempting login with #{username}."
Spotify.try(:session_login, session, username, password, true, nil)
end
logger.info "Log in requested. Waiting forever until logged in."
Support.poll(session) { Spotify.session_connectionstate(session) == :logged_in }
at_exit do
logger.info "Logging out."
Spotify.session_logout(session)
Support.poll(session) { Spotify.session_connectionstate(session) != :logged_in }
end
session
end
end
#
# Main
#
session = Support.initialize_spotify!
username = Support.prompt("Please enter a username", "burgestrand")
# libspotify has no way of searching for users by name or e-mail, so
# this is the only way I know of to get a handle of a user from nothing,
# and probably won't work for users that do not have a classic Spotify
# username, i.e. users that only signed up through Facebook.
#
# If you can get a handle on a playlist created by that user, you'll be
# able to get the handle for them, but not otherwise!
#
# For users that signed up through Facebook only, I believe their Facebook
# user ID should work with this lookup.
user_link = "spotify:user:#{username}"
link = Spotify.link_create_from_string(user_link)
if link.null?
$logger.error "#{user_link} was apparently not parseable as a Spotify URI. Aborting."
abort
end
user = Spotify.link_as_user(link)
$logger.info "Attempting to load #{user_link}. Waiting forever until successful."
Support.poll(session) { Spotify.user_is_loaded(user) }
display_name = Spotify.user_display_name(user)
canonical_name = Spotify.user_canonical_name(user)
$logger.info "User loaded: #{display_name}."
$logger.info "Loading user playlists by loading their published container: #{canonical_name}."
container = Spotify.session_publishedcontainer_for_user_create(session, canonical_name)
$logger.info "Attempting to load container. Waiting forever until successful."
Support.poll(session) { Spotify.playlistcontainer_is_loaded(container) }
$logger.info "Container loaded. Loading playlists until no more are loaded for three tries!"
container_size = 0
previous_container_size = 0
break_counter = 0
loop do
container_size = Spotify.playlistcontainer_num_playlists(container)
new_playlists = container_size - previous_container_size
previous_container_size = container_size
$logger.info "Loaded #{new_playlists} more playlists."
# If we have loaded no new playlists for 4 tries, we assume we are done.
if new_playlists == 0
break_counter += 1
if break_counter >= 4
break
end
end
$logger.info "Loading…"
5.times do
Support.process_events(session)
sleep 0.2
end
end
$logger.info "#{container_size} published playlists for #{display_name} found. Loading each playlists and printing it."
container_size.times do |index|
type = Spotify.playlistcontainer_playlist_type(container, index)
playlist = Spotify.playlistcontainer_playlist(container, index)
Support.poll(session) { Spotify.playlist_is_loaded(playlist) }
playlist_name = Spotify.playlist_name(playlist)
num_tracks = Spotify.playlist_num_tracks(playlist)
# Retrieving link for playlist:
playlist_link = Spotify.link_create_from_playlist(playlist)
link_string = if playlist_link.nil?
"(no link)"
else
# Spotify.link_as_string(playlist_link)
end
$logger.info " (#{type}) #{playlist_name}: #{link_string} (#{num_tracks} tracks)"
binding.pry
end
$logger.info "All done."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment