require "rest-client" require "base64" require "json" class SpotifyClient SPOTIFY_REDIRECT_URI = "#{ENV['SPOTIFY_REDIRECT_URI']}/spotify_callback".freeze SCOPE = "user-read-recently-played".freeze BASE_URL = "https://api.spotify.com/v1/".freeze attr_reader :login def initialize(login = nil) @login = login end def load_since(load_since) params = { "limit" => "50" } params["after"] = load_since unless load_since.nil? get("me/player/recently-played", params) end def token_response_from_code(code) auth_code_authorize(code) end def auth_redirect_url "https://accounts.spotify.com/authorize?" \ "response_type=code&" \ "client_id=#{ENV['SPOTIFY_CLIENT_ID']}&" \ "scope=#{SCOPE}&" \ "redirect_uri=#{CGI.escape(SPOTIFY_REDIRECT_URI)}" end private def get(path, params = {}) refresh_token! begin call(path, params) rescue RestClient::ExceptionWithResponse => e if e.response.code == 401 Rails.logger.info("token expired, forcing refresh") refresh_token!(force: true) begin call(path, params) rescue RestClient::ExceptionWithResponse Rails.logger.info("token dead") LogLoadFailedWorker.perform_async(login.id) end else Rails.logger.error("SpotifyClient: #{e.message}") raise e end end end def call(path, params) headers = { "Authorization" => "Bearer #{login.credentials['access_token']}" } headers[:params] = params if params.present? JSON.parse RestClient.get("#{BASE_URL}#{path}", headers) end def refresh_token!(force: false) return unless login.about_to_expire? || force request_body = { 'refresh_token': login.credentials["refresh_token"], 'grant_type': "refresh_token" } begin auth_response = RestClient.post( "https://accounts.spotify.com/api/token", request_body, client_auth_headers ) new_credentials = JSON.parse auth_response login.update_access_token!(new_credentials) rescue RestClient::ExceptionWithResponse => e login.update(dead_since: Time.new) raise e end end def client_auth_headers auth_plain = "#{ENV['SPOTIFY_CLIENT_ID']}:#{ENV['SPOTIFY_CLIENT_SECRET']}" auth_string = Base64.strict_encode64 auth_plain { 'Authorization': "Basic #{auth_string}" } end def auth_code_authorize(code) request_body = { 'code': code, 'redirect_uri': SPOTIFY_REDIRECT_URI, 'grant_type': "authorization_code" } begin auth_response = RestClient.post( "https://accounts.spotify.com/api/token", request_body, client_auth_headers ) rescue RestClient::ExceptionWithResponse => e raise e end raise StandardError.new("Authorization failed", auth_response) unless auth_response.code == 200 JSON.parse auth_response.body end end