Overhaul of websocket messages
This commit is contained in:
@ -4,10 +4,13 @@ from __future__ import unicode_literals
|
||||
import logging, os, json
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
import handlers
|
||||
|
||||
from mopidy import config, ext
|
||||
from frontend import IrisFrontend, make_iris_factory
|
||||
from frontend import IrisFrontend
|
||||
from http import HttpHandler
|
||||
from websocket import WebsocketHandler
|
||||
from core import IrisCore
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
__version__ = '2.12.1'
|
||||
@ -40,8 +43,35 @@ class Extension( ext.Extension ):
|
||||
# Add web extension
|
||||
registry.add('http:app', {
|
||||
'name': self.ext_name,
|
||||
'factory': make_iris_factory(
|
||||
registry['http:app'],
|
||||
registry['http:static']
|
||||
)
|
||||
'factory': iris_factory
|
||||
})
|
||||
|
||||
# create our core instance
|
||||
mem.iris = IrisCore()
|
||||
mem.iris.version = self.version
|
||||
|
||||
# Add our frontend
|
||||
registry.add('frontend', IrisFrontend)
|
||||
|
||||
|
||||
def iris_factory(config, core):
|
||||
|
||||
path = os.path.join( os.path.dirname(__file__), 'static')
|
||||
|
||||
return [
|
||||
(r"/images/(.*)", tornado.web.StaticFileHandler, {
|
||||
'path': config['local-images']['image_dir']
|
||||
}),
|
||||
(r'/http/([^/]*)', handlers.HttpHandler, {
|
||||
'core': core,
|
||||
'config': config
|
||||
}),
|
||||
(r'/ws/?', handlers.WebsocketHandler, {
|
||||
'core': core,
|
||||
'config': config
|
||||
}),
|
||||
(r'/(.*)', tornado.web.StaticFileHandler, {
|
||||
'path': path,
|
||||
'default_filename': 'index.html'
|
||||
}),
|
||||
]
|
||||
200
mopidy_iris/core.py
Executable file
200
mopidy_iris/core.py
Executable file
@ -0,0 +1,200 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import random, string, logging, json, pykka, pylast, urllib, urllib2, os, sys, mopidy_iris, subprocess
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
import tornado.ioloop
|
||||
from mopidy import config, ext
|
||||
from mopidy.core import CoreListener
|
||||
from pkg_resources import parse_version
|
||||
from tornado.escape import json_encode, json_decode
|
||||
from spotipy import Spotify
|
||||
|
||||
# import logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class IrisCore(object):
|
||||
|
||||
version = 0
|
||||
is_root = ( os.geteuid() == 0 )
|
||||
spotify_token = False
|
||||
queue_metadata = {}
|
||||
connections = {}
|
||||
radio = {
|
||||
"enabled": 0,
|
||||
"seed_artists": [],
|
||||
"seed_genres": [],
|
||||
"seed_tracks": []
|
||||
}
|
||||
|
||||
|
||||
def on_start(self):
|
||||
logger.info('--- Starting Iris core '+self.version)
|
||||
|
||||
|
||||
##
|
||||
# Generate a random string
|
||||
#
|
||||
# Used for connection_ids where none is provided by client
|
||||
# @return string
|
||||
##
|
||||
def generateGuid(self, length):
|
||||
return ''.join(random.choice(string.lowercase) for i in range(length))
|
||||
|
||||
|
||||
##
|
||||
# Digest a protocol header into it's id/name parts
|
||||
#
|
||||
# @return dict
|
||||
##
|
||||
def digest_protocol(self, protocol):
|
||||
|
||||
# if we're a string, split into list
|
||||
# this handles the different ways we get this passed (select_subprotocols gives string, headers.get gives list)
|
||||
if isinstance(protocol, basestring):
|
||||
|
||||
# make sure we strip any spaces (IE gives "element,element", proper browsers give "element, element")
|
||||
protocol = [i.strip() for i in protocol.split(',')]
|
||||
|
||||
# if we've been given a valid array
|
||||
try:
|
||||
clientid = protocol[0]
|
||||
connection_id = protocol[1]
|
||||
username = protocol[2]
|
||||
generated = False
|
||||
|
||||
# invalid, so just create a default connection, and auto-generate an ID
|
||||
except:
|
||||
clientid = self.generateGuid(12)
|
||||
connection_id = self.generateGuid(12)
|
||||
username = 'Anonymous'
|
||||
generated = True
|
||||
|
||||
# construct our protocol object, and return
|
||||
return {"clientid": clientid, "connection_id": connection_id, "username": username, "generated": generated}
|
||||
|
||||
##
|
||||
# Send a message to an individual connection
|
||||
#
|
||||
# @param to = recipient's connection_id
|
||||
# @param data = array (any data required to include in our message)
|
||||
##
|
||||
def send_message(self, to, data):
|
||||
self.connections[to]['connection'].write_message( json_encode(data) )
|
||||
|
||||
|
||||
def broadcast(self, data):
|
||||
for connection in self.connections.itervalues():
|
||||
connection['connection'].write_message( json_encode(data) )
|
||||
return {}
|
||||
|
||||
##
|
||||
# Add a new connection
|
||||
##
|
||||
def add_connection(self, connection_id, connection, client):
|
||||
new_connection = {
|
||||
'client': client,
|
||||
'connection': connection
|
||||
}
|
||||
self.connections[connection_id] = new_connection
|
||||
|
||||
self.broadcast({
|
||||
'action': 'client_connected',
|
||||
'client': client
|
||||
})
|
||||
|
||||
##
|
||||
# Add a new connection
|
||||
##
|
||||
def remove_connection(self, connection_id):
|
||||
if connection_id in self.connections:
|
||||
try:
|
||||
del self.connections[connection_id]
|
||||
self.broadcast(self.get_connections())
|
||||
except:
|
||||
print 'Failed to close connection to '+ connection_id
|
||||
|
||||
self.broadcast({
|
||||
'action': 'client_disconnected',
|
||||
'client': client
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
def get_config(self, data):
|
||||
config = {
|
||||
"spotify_username": self.config['spotify']['username'],
|
||||
"country": self.config['iris']['country'],
|
||||
"locale": self.config['iris']['locale']
|
||||
}
|
||||
return {
|
||||
'config': config
|
||||
}
|
||||
|
||||
|
||||
def get_version(self, data):
|
||||
|
||||
url = 'https://pypi.python.org/pypi/Mopidy-Iris/json'
|
||||
req = urllib2.Request(url)
|
||||
|
||||
try:
|
||||
response = urllib2.urlopen(req, timeout=30).read()
|
||||
response = json.loads(response)
|
||||
latest_version = response['info']['version']
|
||||
|
||||
# compare our versions, and convert result to boolean
|
||||
upgrade_available = cmp( parse_version( latest_version ), parse_version( self.version ) )
|
||||
upgrade_available = ( upgrade_available == 1 )
|
||||
|
||||
except urllib2.HTTPError as e:
|
||||
latest_version = '0.0.0'
|
||||
upgrade_available = False
|
||||
|
||||
return {
|
||||
'version': {
|
||||
'current': self.version,
|
||||
'latest': latest_version,
|
||||
'is_root': self.is_root,
|
||||
'upgrade_available': upgrade_available
|
||||
}
|
||||
}
|
||||
|
||||
def get_connections(self, data):
|
||||
connections = []
|
||||
for connection in self.connections.itervalues():
|
||||
connections.append(connection['client'])
|
||||
|
||||
return {
|
||||
'connections': connections
|
||||
}
|
||||
|
||||
def get_radio(self, data):
|
||||
return {
|
||||
'radio': self.radio
|
||||
}
|
||||
|
||||
def stop_radio(self, data):
|
||||
|
||||
self.radio = {
|
||||
"enabled": 0,
|
||||
"seed_artists": [],
|
||||
"seed_genres": [],
|
||||
"seed_tracks": []
|
||||
}
|
||||
|
||||
self.core.playback.stop()
|
||||
|
||||
self.broadcast({
|
||||
'action': 'radio_stopped',
|
||||
'radio': self.radio
|
||||
})
|
||||
|
||||
return {}
|
||||
|
||||
def get_queue_metadata(self, data):
|
||||
return {
|
||||
'queue_metadata': self.queue_metadata
|
||||
}
|
||||
|
||||
@ -1,322 +1,22 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging, json, pykka, pylast, urllib, urllib2, os, sys, mopidy_iris, subprocess
|
||||
import tornado.web
|
||||
import tornado.websocket
|
||||
import tornado.ioloop
|
||||
from mopidy import config, ext
|
||||
from mopidy.core import CoreListener
|
||||
from pkg_resources import parse_version
|
||||
from spotipy import Spotify
|
||||
|
||||
from websocket import WebsocketHandler
|
||||
from http import HttpHandler
|
||||
import mem
|
||||
import pykka
|
||||
|
||||
# import logger
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
###
|
||||
# Create our factory
|
||||
#
|
||||
# This hooks all our components into the Mopidy registry. Called from __init__.py
|
||||
##
|
||||
def make_iris_factory(apps, statics):
|
||||
def iris_factory(config, core):
|
||||
|
||||
path = os.path.join( os.path.dirname(__file__), 'static')
|
||||
frontend = IrisFrontend(config, core)
|
||||
|
||||
return [
|
||||
(r"/images/(.*)", tornado.web.StaticFileHandler, {
|
||||
"path": config['local-images']['image_dir']
|
||||
}),
|
||||
(r'/http/([^/]*)', HttpHandler, {
|
||||
"frontend": frontend
|
||||
}),
|
||||
(r'/ws/?', WebsocketHandler, {
|
||||
"frontend": frontend
|
||||
}),
|
||||
(r'/(.*)', tornado.web.StaticFileHandler, {
|
||||
"path": path,
|
||||
"default_filename": "index.html"
|
||||
}),
|
||||
]
|
||||
return iris_factory
|
||||
|
||||
###
|
||||
# Spotmop supporting frontend
|
||||
#
|
||||
# This provides a wrapping thread for the Pusher websocket, as well as the radio infrastructure
|
||||
##
|
||||
class IrisFrontend(pykka.ThreadingActor, CoreListener):
|
||||
|
||||
def __init__(self, config, core):
|
||||
super(IrisFrontend, self).__init__()
|
||||
self.config = config
|
||||
self.core = core
|
||||
self.version = mopidy_iris.__version__
|
||||
self.is_root = ( os.geteuid() == 0 )
|
||||
self.spotify_token = False
|
||||
self.queue_metadata = {}
|
||||
self.connections = {}
|
||||
self.radio = {
|
||||
"enabled": 0,
|
||||
"seed_artists": [],
|
||||
"seed_genres": [],
|
||||
"seed_tracks": []
|
||||
}
|
||||
mem.iris.core = core
|
||||
mem.iris.config = config
|
||||
|
||||
def on_start(self):
|
||||
logger.info('Starting Iris '+self.version)
|
||||
print '--- Starting IrisFrontend'
|
||||
|
||||
|
||||
##
|
||||
# Get a new spotify authentication token for server-side use
|
||||
#
|
||||
# Uses the Client Credentials Flow, so is invisible to the user. We need this token for
|
||||
# any backend spotify requests (we don't tap in to Mopidy-Spotify, yet). Also used for
|
||||
# passing token to frontend for javascript requests without use of the Authorization Code Flow.
|
||||
##
|
||||
def refresh_spotify_token( self ):
|
||||
|
||||
url = 'https://accounts.spotify.com/api/token'
|
||||
authorization = 'YTg3ZmI0ZGJlZDMwNDc1YjhjZWMzODUyM2RmZjUzZTI6ZDdjODlkMDc1M2VmNDA2OGJiYTE2NzhjNmNmMjZlZDY='
|
||||
|
||||
headers = {'Authorization' : 'Basic ' + authorization}
|
||||
data = {'grant_type': 'client_credentials'}
|
||||
data_encoded = urllib.urlencode( data )
|
||||
req = urllib2.Request(url, data_encoded, headers)
|
||||
|
||||
try:
|
||||
response = urllib2.urlopen(req, timeout=30).read()
|
||||
response_dict = json.loads(response)
|
||||
self.spotify_token = response_dict
|
||||
return response_dict
|
||||
except urllib2.HTTPError as e:
|
||||
return e
|
||||
|
||||
|
||||
##
|
||||
# Listen for core events, and update our frontend as required
|
||||
##
|
||||
def track_playback_ended( self, tl_track, time_position ):
|
||||
self.check_for_radio_update()
|
||||
|
||||
def tracklist_changed( self ):
|
||||
self.clean_queue_metadata()
|
||||
|
||||
|
||||
##
|
||||
# See if we need to perform updates to our radio
|
||||
#
|
||||
# We see if we've got one or two tracks left, if so, go get some more
|
||||
##
|
||||
def check_for_radio_update( self ):
|
||||
try:
|
||||
tracklistLength = self.core.tracklist.length.get()
|
||||
if( tracklistLength <= 5 and self.radio['enabled'] == 1 ):
|
||||
self.load_more_tracks()
|
||||
|
||||
except RuntimeError:
|
||||
self.websocket.broadcast('error', {'source': 'check_for_radio_update', 'message': 'Could not fetch tracklist length'})
|
||||
logger.warning('IrisFrontend: Could not fetch tracklist length')
|
||||
pass
|
||||
|
||||
|
||||
##
|
||||
# Load some more radio tracks
|
||||
#
|
||||
# We need to build a Spotify authentication token first, and then fetch recommendations
|
||||
##
|
||||
def load_more_tracks( self ):
|
||||
|
||||
# this is crude, but it means we don't need to handle expired tokens
|
||||
# TODO: address this when it's clear what Jodal and the team want to do with Pyspotify
|
||||
self.refresh_spotify_token()
|
||||
|
||||
try:
|
||||
token = self.spotify_token
|
||||
token = token['access_token']
|
||||
except:
|
||||
logger.error('IrisFrontend: access_token missing or invalid')
|
||||
self.websocket.broadcast('error', {'source': 'load_more_tracks', 'message': 'access_token missing or invalid'})
|
||||
|
||||
try:
|
||||
spotify = Spotify( auth = token )
|
||||
response = spotify.recommendations(seed_artists = self.radio['seed_artists'], seed_genres = self.radio['seed_genres'], seed_tracks = self.radio['seed_tracks'], limit = 5)
|
||||
|
||||
uris = []
|
||||
for track in response['tracks']:
|
||||
uris.append( track['uri'] )
|
||||
|
||||
self.core.tracklist.add( uris = uris )
|
||||
except:
|
||||
self.websocket.broadcast('error', {'source': 'load_more_tracks', 'message': 'Failed to fetch Spotify recommendations'})
|
||||
logger.error('IrisFrontend: Failed to fetch Spotify recommendations')
|
||||
|
||||
|
||||
##
|
||||
# Start radio
|
||||
#
|
||||
# Take the provided radio details, and start a new radio process
|
||||
##
|
||||
def start_radio( self, new_state ):
|
||||
|
||||
# set our new radio state
|
||||
self.radio = new_state
|
||||
self.radio['enabled'] = 1;
|
||||
|
||||
# clear all tracks
|
||||
self.core.tracklist.clear()
|
||||
|
||||
# explicitly set consume, to ensure we don't end up with a huge tracklist (and it's how a radio should 'feel')
|
||||
self.core.tracklist.set_consume( True )
|
||||
|
||||
# load me some tracks, and start playing!
|
||||
self.load_more_tracks()
|
||||
self.core.playback.play()
|
||||
|
||||
# notify clients
|
||||
self.websocket.broadcast('radio', { 'radio': self.radio })
|
||||
|
||||
# return new radio state to initial call
|
||||
return self.radio
|
||||
|
||||
##
|
||||
# Stop radio
|
||||
##
|
||||
def stop_radio( self ):
|
||||
|
||||
# reset radio
|
||||
self.radio = {
|
||||
"enabled": 0,
|
||||
"seed_artists": [],
|
||||
"seed_genres": [],
|
||||
"seed_tracks": []
|
||||
}
|
||||
|
||||
# stop track playback
|
||||
self.core.playback.stop()
|
||||
|
||||
# notify clients
|
||||
self.websocket.broadcast( 'radio', { 'radio': self.radio })
|
||||
|
||||
# return new radio state to initial call
|
||||
return self.radio
|
||||
|
||||
|
||||
# get our spotify token
|
||||
def get_spotify_token( self ):
|
||||
return self.spotify_token
|
||||
|
||||
|
||||
##
|
||||
# Queue metadata
|
||||
##
|
||||
|
||||
def get_queue_metadata( self ):
|
||||
return self.queue_metadata
|
||||
|
||||
def add_queue_metadata( self, tlids, added_from, added_by ):
|
||||
|
||||
for tlid in tlids:
|
||||
item = {
|
||||
'tlid': tlid,
|
||||
'added_from': added_from,
|
||||
'added_by': added_by
|
||||
}
|
||||
self.queue_metadata['tlid_'+str(tlid)] = item
|
||||
|
||||
# broadcast to all clients
|
||||
self.websocket.broadcast('queue_metadata', {'queue_metadata': self.queue_metadata})
|
||||
|
||||
return self.queue_metadata
|
||||
|
||||
# fetch our tracklist, and remove any metadata for tlids that don't exist anymore
|
||||
def clean_queue_metadata( self ):
|
||||
|
||||
cleaned_queue_metadata = {}
|
||||
|
||||
# get and loop all tltracks
|
||||
for tltrack in self.core.tracklist.get_tl_tracks().get():
|
||||
|
||||
# if we have metadata for this track, push it through to cleaned dictionary
|
||||
if 'tlid_'+str(tltrack.tlid) in self.queue_metadata:
|
||||
cleaned_queue_metadata['tlid_'+str(tltrack.tlid)] = self.queue_metadata['tlid_'+str(tltrack.tlid)]
|
||||
|
||||
# update our cleaned store
|
||||
self.queue_metadata = cleaned_queue_metadata
|
||||
|
||||
# broadcast to all clients
|
||||
self.websocket.broadcast('queue_metadata', {'queue_metadata': self.queue_metadata})
|
||||
|
||||
|
||||
##
|
||||
# System configuration
|
||||
#
|
||||
# This enables Iris to respect system config
|
||||
##
|
||||
def get_config( self ):
|
||||
all_config = self.config
|
||||
config = {
|
||||
"spotify_username": all_config['spotify']['username'],
|
||||
"country": all_config['iris']['country'],
|
||||
"locale": all_config['iris']['locale']
|
||||
}
|
||||
return config
|
||||
|
||||
|
||||
##
|
||||
# Get Spotmop version, and check for updates
|
||||
#
|
||||
# We compare our version with the latest available on PyPi
|
||||
##
|
||||
def get_version( self ):
|
||||
|
||||
url = 'https://pypi.python.org/pypi/Mopidy-Iris/json'
|
||||
req = urllib2.Request(url)
|
||||
|
||||
try:
|
||||
response = urllib2.urlopen(req, timeout=30).read()
|
||||
response = json.loads(response)
|
||||
latest_version = response['info']['version']
|
||||
|
||||
# compare our versions, and convert result to boolean
|
||||
upgrade_available = cmp( parse_version( latest_version ), parse_version( self.version ) )
|
||||
upgrade_available = ( upgrade_available == 1 )
|
||||
|
||||
except urllib2.HTTPError as e:
|
||||
latest_version = '0.0.0'
|
||||
upgrade_available = False
|
||||
|
||||
# prepare our response
|
||||
data = {
|
||||
'current': self.version,
|
||||
'latest': latest_version,
|
||||
'is_root': self.is_root,
|
||||
'upgrade_available': upgrade_available
|
||||
}
|
||||
return data
|
||||
|
||||
|
||||
##
|
||||
# Upgrade Spotmop module
|
||||
#
|
||||
# Upgrade myself to the latest version available on PyPi
|
||||
##
|
||||
def perform_upgrade( self ):
|
||||
try:
|
||||
subprocess.check_call(["pip", "install", "--upgrade", "Mopidy-Iris"])
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
##
|
||||
# Restart Mopidy
|
||||
#
|
||||
# This is untested and may require installation of an upstart script to properly restart
|
||||
##
|
||||
def restart( self ):
|
||||
os.execl(sys.executable, *([sys.executable]+sys.argv))
|
||||
def track_playback_started(self, tl_track):
|
||||
mem.iris.broadcast({
|
||||
'action': 'started_playback'
|
||||
})
|
||||
|
||||
127
mopidy_iris/handlers.py
Executable file
127
mopidy_iris/handlers.py
Executable file
@ -0,0 +1,127 @@
|
||||
|
||||
from __future__ import unicode_literals
|
||||
import tornado.ioloop, tornado.web, tornado.websocket, tornado.template
|
||||
import random, string, logging, uuid, subprocess, pykka
|
||||
from datetime import datetime
|
||||
from tornado.escape import json_encode, json_decode
|
||||
import logging, json, urllib, urllib2
|
||||
import tornado.web
|
||||
from spotipy import Spotify
|
||||
|
||||
import mem
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WebsocketHandler(tornado.websocket.WebSocketHandler):
|
||||
|
||||
# initiate (not the actual object __init__, but run shortly after)
|
||||
def initialize(self, core, config):
|
||||
self.core = core
|
||||
self.config = config
|
||||
|
||||
def check_origin(self, origin):
|
||||
return True
|
||||
|
||||
def select_subprotocol(self, subprotocols):
|
||||
|
||||
# select one of our subprotocol elements and return it. This confirms the connection has been accepted.
|
||||
protocols = mem.iris.digest_protocol( subprotocols )
|
||||
|
||||
# if we've auto-generated some ids, the provided subprotocols was a string, so just return it right back
|
||||
# this allows a connection to be completed
|
||||
if protocols['generated']:
|
||||
return subprotocols[0]
|
||||
|
||||
# otherwise, just return one of the supplied subprotocols
|
||||
else:
|
||||
return protocols['clientid']
|
||||
|
||||
def open(self):
|
||||
|
||||
# decode our connection protocol value (which is a payload of id/name from javascript)
|
||||
protocolElements = mem.iris.digest_protocol(self.request.headers.get('Sec-Websocket-Protocol', []))
|
||||
|
||||
connection_id = protocolElements['connection_id']
|
||||
clientid = protocolElements['clientid']
|
||||
self.connection_id = connection_id
|
||||
username = protocolElements['username']
|
||||
created = datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# construct our client object, and add to our list of connections
|
||||
client = {
|
||||
'clientid': clientid,
|
||||
'connection_id': connection_id,
|
||||
'username': username,
|
||||
'ip': self.request.remote_ip,
|
||||
'created': created
|
||||
}
|
||||
|
||||
# add to connections
|
||||
mem.iris.add_connection(connection_id, self, client)
|
||||
|
||||
|
||||
def on_message(self, message):
|
||||
message = json_decode(message)
|
||||
|
||||
if 'data' in message:
|
||||
data = message['data']
|
||||
else:
|
||||
data = {}
|
||||
|
||||
if 'request_id' in message:
|
||||
request_id = message['request_id']
|
||||
else:
|
||||
request_id = False
|
||||
|
||||
# call the method, as specified in payload
|
||||
if 'method' in message:
|
||||
|
||||
# make sure the method exists
|
||||
if hasattr(mem.iris, message['method']):
|
||||
|
||||
# make the call, and return it's response
|
||||
response = getattr(mem.iris, message['method'])(data)
|
||||
response['request_id'] = request_id
|
||||
mem.iris.send_message(self.connection_id, response)
|
||||
else:
|
||||
response = {
|
||||
'error': 'Method "'+message['method']+'" does not exist',
|
||||
'request_id': request_id
|
||||
}
|
||||
mem.iris.send_message(self.connection_id, response)
|
||||
else:
|
||||
response = {
|
||||
'error': 'Method key missing from request',
|
||||
'request_id': request_id
|
||||
}
|
||||
mem.iris.send_message(self.connection_id, response)
|
||||
|
||||
|
||||
def on_close(self):
|
||||
mem.iris.remove_connection(self.connection_id)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class HttpHandler(tornado.web.RequestHandler):
|
||||
|
||||
def set_default_headers(self):
|
||||
self.set_header("Access-Control-Allow-Origin", "*")
|
||||
|
||||
def initialize(self, core, config):
|
||||
self.core = core
|
||||
self.config = config
|
||||
|
||||
def get(self, slug=None):
|
||||
|
||||
if( slug == 'refresh_spotify_token' ):
|
||||
self.write( mem.iriscore.refresh_spotify_token() )
|
||||
return
|
||||
|
||||
else:
|
||||
self.write('Invalid request')
|
||||
return
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
queuemanager = None
|
||||
localfiles = None
|
||||
|
||||
iris = None
|
||||
|
||||
|
||||
Reference in New Issue
Block a user