diff --git a/mopidy_iris/core.py b/mopidy_iris/core.py
index 1195bb38..340c340a 100755
--- a/mopidy_iris/core.py
+++ b/mopidy_iris/core.py
@@ -78,15 +78,20 @@ class IrisCore(object):
return {"clientid": clientid, "connection_id": connection_id, "username": username, "generated": generated}
- def send_message(self, to, data):
+ def send_message(self, *args, **kwargs):
+ connection_id = kwargs.get('connection_id', None)
+ data = kwargs.get('data', {})
+
try:
- self.connections[to]['connection'].write_message( json_encode(data) )
+ self.connections[connection_id]['connection'].write_message( json_encode(data) )
except:
self.raven_client.captureException()
- logger.error('Failed to send message to '+ to)
+ logger.error('Failed to send message to '+ connection_id)
- def broadcast(self, data):
+ def broadcast(self, *args, **kwargs):
+ data = kwargs.get('data', None)
+
for connection in self.connections.itervalues():
connection['connection'].write_message( json_encode(data) )
return {
@@ -102,7 +107,7 @@ class IrisCore(object):
# to all current connections
##
- def get_connections(self, data):
+ def get_connections(self, *args, **kwargs):
connections = []
for connection in self.connections.itervalues():
connections.append(connection['client'])
@@ -112,7 +117,11 @@ class IrisCore(object):
'connections': connections
}
- def add_connection(self, connection_id, connection, client):
+ def add_connection(self, *args, **kwargs):
+ connection_id = kwargs.get('connection_id', None)
+ connection = kwargs.get('connection', None)
+ client = kwargs.get('client', None)
+
new_connection = {
'client': client,
'connection': connection
@@ -120,39 +129,48 @@ class IrisCore(object):
self.connections[connection_id] = new_connection
self.send_message(
- connection_id, {
+ connection_id=connection_id,
+ data={
'type': 'connected',
'connection_id': connection_id,
'username': client['username']
}
)
- self.broadcast({
- 'type': 'connection_added',
- 'connection': client
- })
+ self.broadcast(
+ data={
+ 'type': 'connection_added',
+ 'connection': client
+ }
+ )
def remove_connection(self, connection_id):
if connection_id in self.connections:
try:
client = self.connections[connection_id]['client']
del self.connections[connection_id]
- self.broadcast({
- 'type': 'connection_removed',
- 'connection': client
- })
+ self.broadcast(
+ data={
+ 'type': 'connection_removed',
+ 'connection': client
+ }
+ )
except:
self.raven_client.captureException()
logger.error('Failed to close connection to '+ connection_id)
- def set_username(self, data):
+ def set_username(self, *args, **kwargs):
connection_id = data['connection_id']
+ data = kwargs.get('data', None)
+
if connection_id in self.connections:
self.connections[connection_id]['client']['username'] = data['username']
- self.broadcast({
- 'type': 'connection_updated',
- 'connection': self.connections[connection_id]['client']
- })
+ self.broadcast(
+ data={
+ 'type': 'connection_updated',
+ 'connection': self.connections[connection_id]['client']
+ }
+ )
return {
'status': 1,
'connection_id': connection_id,
@@ -168,14 +186,14 @@ class IrisCore(object):
'message': error
}
- def deliver_message(self, data):
- to = data['to']
- if to in self.connections:
-
- self.send_message(to, data['message'])
+ def deliver_message(self, *args, **kwargs):
+ data = kwargs.get('data', "{}")
+ if data['connection_id'] in self.connections:
+ self.send_message(connection_id=data['connection_id'], data=data['message'])
return {
- 'status': 1
+ 'status': 1,
+ 'message': 'Sent message to '+data['connection_id']
}
else:
@@ -196,7 +214,7 @@ class IrisCore(object):
# Faciitates upgrades and configuration fetching
##
- def get_config(self, data):
+ def get_config(self, *args, **kwargs):
# handle config setups where there is no username/password
# Iris won't work properly anyway, but at least we won't get server errors
@@ -215,7 +233,7 @@ class IrisCore(object):
'config': config
}
- def get_version(self, data):
+ def get_version(self, *args, **kwargs):
url = 'https://pypi.python.org/pypi/Mopidy-Iris/json'
req = urllib2.Request(url)
@@ -264,13 +282,14 @@ class IrisCore(object):
# recommendations limit low to avoid timeouts and slow UI
##
- def get_radio(self, data):
+ def get_radio(self, *args, **kwargs):
return {
'status': 1,
'radio': self.radio
}
- def change_radio(self, data):
+ def change_radio(self, *args, **kwargs):
+ data = kwargs.get('data', None)
# figure out if we're starting or updating radio mode
if data['update'] and self.radio['enabled']:
@@ -301,15 +320,19 @@ class IrisCore(object):
if added.get():
if starting:
self.core.playback.play()
- self.broadcast({
- 'type': 'radio_started',
- 'radio': self.radio
- })
+ self.broadcast(
+ data={
+ 'type': 'radio_started',
+ 'radio': self.radio
+ }
+ )
else:
- self.broadcast({
- 'type': 'radio_changed',
- 'radio': self.radio
- })
+ self.broadcast(
+ data={
+ 'type': 'radio_changed',
+ 'radio': self.radio
+ }
+ )
return self.get_radio({})
@@ -322,7 +345,7 @@ class IrisCore(object):
}
- def stop_radio(self, data):
+ def stop_radio(self, *args, **kwargs):
self.radio = {
"enabled": 0,
@@ -336,10 +359,12 @@ class IrisCore(object):
self.core.tracklist.set_consume(self.initial_consume)
self.core.playback.stop()
- self.broadcast({
- 'type': 'radio_stopped',
- 'radio': self.radio
- })
+ self.broadcast(
+ data={
+ 'type': 'radio_stopped',
+ 'radio': self.radio
+ }
+ )
return {
'status': 1
@@ -359,11 +384,13 @@ class IrisCore(object):
error = 'IrisFrontend: access_token missing or invalid'
self.raven_client.captureMessage(error)
logger.error(error)
- self.broadcast({
- 'type': 'error',
- 'message': 'Could not get radio tracks: access_token missing or invalid',
- 'source': 'load_more_tracks'
- })
+ self.broadcast(
+ data={
+ 'type': 'error',
+ 'message': 'Could not get radio tracks: access_token missing or invalid',
+ 'source': 'load_more_tracks'
+ }
+ )
try:
url = 'https://api.spotify.com/v1/recommendations/'
@@ -387,17 +414,19 @@ class IrisCore(object):
except:
self.raven_client.captureException()
logger.error('IrisFrontend: Failed to fetch Spotify recommendations')
- self.broadcast({
- 'type': 'error',
- 'message': 'Could not get radio tracks',
- 'source': 'load_more_tracks'
- })
+ self.broadcast(
+ data={
+ 'type': 'error',
+ 'message': 'Could not get radio tracks',
+ 'source': 'load_more_tracks'
+ }
+ )
return []
def check_for_radio_update( self ):
tracklistLength = self.core.tracklist.length.get()
- if( tracklistLength < 3 and self.radio['enabled'] == 1 ):
+ if (tracklistLength < 3 and self.radio['enabled'] == 1):
# Grab our loaded tracks
uris = self.radio['results']
@@ -423,13 +452,14 @@ class IrisCore(object):
# added_by and from_uri.
##
- def get_queue_metadata(self, data):
+ def get_queue_metadata(self, *args, **kwargs):
return {
'status': 1,
'queue_metadata': self.queue_metadata
}
- def add_queue_metadata(self, data):
+ def add_queue_metadata(self, *args, **kwargs):
+ data = kwargs.get('data', None)
for tlid in data['tlids']:
item = {
@@ -439,10 +469,12 @@ class IrisCore(object):
}
self.queue_metadata['tlid_'+str(tlid)] = item
- self.broadcast({
- 'type': 'queue_metadata_changed',
- 'queue_metadata': self.queue_metadata
- })
+ self.broadcast(
+ data={
+ 'type': 'queue_metadata_changed',
+ 'queue_metadata': self.queue_metadata
+ }
+ )
return {
'status': 1
@@ -459,10 +491,12 @@ class IrisCore(object):
self.queue_metadata = cleaned_queue_metadata
- self.broadcast({
- 'type': 'queue_metadata_changed',
- 'queue_metadata': self.queue_metadata
- })
+ self.broadcast(
+ data={
+ 'type': 'queue_metadata_changed',
+ 'queue_metadata': self.queue_metadata
+ }
+ )
return {
'status': 1
@@ -477,12 +511,12 @@ class IrisCore(object):
# passing token to frontend for javascript requests without use of the Authorization Code Flow.
##
- def get_spotify_token(self, data):
+ def get_spotify_token(self, *args, **kwargs):
return {
'spotify_token': self.spotify_token
}
- def refresh_spotify_token(self, data):
+ def refresh_spotify_token(self, *args, **kwargs):
# Use client_id and client_secret from config
# This was introduced in Mopidy-Spotify 3.1.0
@@ -497,14 +531,16 @@ class IrisCore(object):
req = urllib2.Request(url, data_encoded)
try:
- response = urllib2.urlopen(req, timeout=30).read()
+ response = urllib2.urlopen(req, timeout=15).read()
response_dict = json.loads(response)
self.spotify_token = response_dict
- self.broadcast({
- 'type': 'spotify_token_changed',
- 'spotify_token': self.spotify_token
- })
+ self.broadcast(
+ data={
+ 'type': 'spotify_token_changed',
+ 'spotify_token': self.spotify_token
+ }
+ )
return self.get_spotify_token({})
@@ -517,3 +553,81 @@ class IrisCore(object):
'message': 'Could not refresh token: '+error['error_description'],
'source': 'refresh_spotify_token'
}
+
+
+ ##
+ # Proxy a request to an external provider
+ #
+ # This is required when requesting to non-CORS providers. We simply make the request
+ # server-side and pass that back. All we change is the response's Access-Control-Allow-Origin
+ # to prevent CORS-blocking by the browser.
+ ##
+
+ def proxy_request(self, *args, **kwargs):
+
+ data = kwargs.get('data', None)
+ origin_request = kwargs.get('request', None)
+
+ # Our request includes data, so make sure we POST the data
+ if 'url' not in data:
+ self.raven_client.captureException()
+ return {
+ 'type': 'error',
+ 'message': 'Could not complete proxy request',
+ 'source': 'proxy_request',
+ 'error': "Missing URL property",
+ 'original_request': data
+ }
+
+ # Construct request headers
+ # If we have an original request, pass through it's headers
+ if origin_request:
+ target_request_headers = origin_request.headers
+ else:
+ target_request_headers = {}
+
+ # Adjust headers
+ target_request_headers["Accept-Encoding"] = "deflate"
+ if "Content-Type" in target_request_headers:
+ del target_request_headers["Content-Type"]
+
+ # Our request includes data, so make sure we POST the data
+ if ('data' in data and data['data']):
+ target_request = urllib2.Request(data['url'], data=urllib.urlencode(data['data']), headers=target_request_headers)
+
+ # No data, so just a simple GET request
+ else:
+ target_request = urllib2.Request(data['url'], headers=target_request_headers)
+
+ try:
+ target_response = urllib2.urlopen(target_request, timeout=15)
+ target_response_body = target_response.read()
+
+ try:
+ response = json.loads(target_response_body)
+ return {
+ 'response': response
+ }
+ except:
+ return {
+ 'response': target_response_body
+ }
+
+ except urllib2.HTTPError as e:
+ self.raven_client.captureException()
+ return {
+ 'type': 'error',
+ 'message': 'Could not complete proxy request',
+ 'source': 'proxy_request',
+ 'response': e.read(),
+ 'original_request': data
+ }
+
+ except urllib2.URLError as e:
+ self.raven_client.captureException()
+ return {
+ 'type': 'error',
+ 'message': 'Could not complete proxy request',
+ 'source': 'proxy_request',
+ 'original_request': data
+ }
diff --git a/mopidy_iris/handlers.py b/mopidy_iris/handlers.py
index 488cae76..8f474f61 100755
--- a/mopidy_iris/handlers.py
+++ b/mopidy_iris/handlers.py
@@ -58,7 +58,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
}
# add to connections
- mem.iris.add_connection(connection_id, self, client)
+ mem.iris.add_connection(connection_id=connection_id, connection=self, client=client)
def on_message(self, message):
@@ -87,11 +87,11 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
if hasattr(mem.iris, message['method']):
# make the call, and return it's response
- response = getattr(mem.iris, message['method'])(data)
+ response = getattr(mem.iris, message['method'])(data=data)
if response:
response['request_id'] = request_id
- mem.iris.send_message(self.connection_id, response)
+ mem.iris.send_message(connection_id=self.connection_id, data=response)
else:
mem.iris.raven_client.captureMessage("Method "+message['method']+" does not exist")
response = {
@@ -99,7 +99,7 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
'message': 'Method "'+message['method']+'" does not exist',
'request_id': request_id
}
- mem.iris.send_message(self.connection_id, response)
+ mem.iris.send_message(connection_id=self.connection_id, data=response)
else:
mem.iris.raven_client.captureMessage("Method key missing from request")
response = {
@@ -107,11 +107,11 @@ class WebsocketHandler(tornado.websocket.WebSocketHandler):
'message': 'Method key missing from request',
'request_id': request_id
}
- mem.iris.send_message(self.connection_id, response)
+ mem.iris.send_message(connection_id=self.connection_id, data=response)
def on_close(self):
- mem.iris.remove_connection(self.connection_id)
+ mem.iris.remove_connection(connection_id=self.connection_id)
@@ -133,7 +133,30 @@ class HttpHandler(tornado.web.RequestHandler):
if hasattr(mem.iris, slug):
# make the call, and return it's response
- self.write(getattr(mem.iris, slug)({}))
+ self.write(getattr(mem.iris, slug)(data={}, request=self.request))
+ else:
+ mem.iris.raven_client.captureMessage("Method "+slug+" does not exist")
+ self.write({
+ 'error': 'Method "'+slug+'" does not exist'
+ })
+
+ def post(self, slug=None):
+
+ # make sure the method exists
+ if hasattr(mem.iris, slug):
+
+ try:
+ data = json.loads(self.request.body.decode('utf-8'))
+
+ # make the call, and return it's response
+ self.write(getattr(mem.iris, slug)(data=data, request=self.request))
+
+ except urllib2.HTTPError as e:
+ self.raven_client.captureException()
+ self.write({
+ 'error': 'Invalid JSON payload'
+ })
+
else:
mem.iris.raven_client.captureMessage("Method "+slug+" does not exist")
self.write({
diff --git a/src/js/bootstrap.js b/src/js/bootstrap.js
index be671213..08527ba3 100755
--- a/src/js/bootstrap.js
+++ b/src/js/bootstrap.js
@@ -7,6 +7,7 @@ import pusher from './services/pusher/reducer'
import mopidy from './services/mopidy/reducer'
import lastfm from './services/lastfm/reducer'
import spotify from './services/spotify/reducer'
+import genius from './services/genius/reducer'
import thunk from 'redux-thunk'
import coreMiddleware from './services/core/middleware'
@@ -22,6 +23,7 @@ let reducers = combineReducers({
pusher,
mopidy,
lastfm,
+ genius,
spotify
});
@@ -61,10 +63,10 @@ var initialState = {
}
},
lastfm: {
- connected: false,
- album: {},
- artist: {},
- track: {}
+ connected: false
+ },
+ genius: {
+ connected: false
},
spotify: {
connected: false,
diff --git a/src/js/components/Modal/Modal.js b/src/js/components/Modal/Modal.js
index 05acaebc..86ad28f4 100755
--- a/src/js/components/Modal/Modal.js
+++ b/src/js/components/Modal/Modal.js
@@ -16,12 +16,14 @@ import SearchURISchemesModal from './SearchURISchemesModal'
import VolumeModal from './VolumeModal'
import AuthorizationModal_Send from './AuthorizationModal_Send'
import AuthorizationModal_Receive from './AuthorizationModal_Receive'
+import TrackInfoModal from './TrackInfoModal'
import * as coreActions from '../../services/core/actions'
import * as uiActions from '../../services/ui/actions'
import * as mopidyActions from '../../services/mopidy/actions'
import * as spotifyActions from '../../services/spotify/actions'
import * as pusherActions from '../../services/pusher/actions'
+import * as geniusActions from '../../services/genius/actions'
class Modal extends React.Component{
@@ -58,6 +60,7 @@ class Modal extends React.Component{
{ this.props.modal.name == 'kiosk_mode' ?