Adding POC lastfm authorization button
This commit is contained in:
@ -13,7 +13,7 @@ from core import IrisCore
|
||||
from raven import Client
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
__version__ = '3.6.1'
|
||||
__version__ = '3.6.1'
|
||||
|
||||
##
|
||||
# Core extension class
|
||||
@ -35,7 +35,8 @@ class Extension( ext.Extension ):
|
||||
schema['enabled'] = config.Boolean()
|
||||
schema['country'] = config.String()
|
||||
schema['locale'] = config.String()
|
||||
schema['authorization_url'] = config.String()
|
||||
schema['spotify_authorization_url'] = config.String()
|
||||
schema['lastfm_authorization_url'] = config.String()
|
||||
return schema
|
||||
|
||||
def setup(self, registry):
|
||||
|
||||
@ -259,7 +259,8 @@ class IrisCore(object):
|
||||
"spotify_username": spotify_username,
|
||||
"country": self.config['iris']['country'],
|
||||
"locale": self.config['iris']['locale'],
|
||||
"authorization_url": self.config['iris']['authorization_url']
|
||||
"spotify_authorization_url": self.config['iris']['spotify_authorization_url'],
|
||||
"lastfm_authorization_url": self.config['iris']['lastfm_authorization_url']
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,4 +2,5 @@
|
||||
enabled = true
|
||||
country = NZ
|
||||
locale = en_NZ
|
||||
authorization_url = https://jamesbarnsley.co.nz/auth_v2.php
|
||||
spotify_authorization_url = https://jamesbarnsley.co.nz/auth_spotify.php
|
||||
lastfm_authorization_url = https://jamesbarnsley.co.nz/auth_lastfm.php
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
154
mopidy_iris/static/auth_lastfm.php
Executable file
154
mopidy_iris/static/auth_lastfm.php
Executable file
@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* LastFM Authentication proxy
|
||||
*
|
||||
* To use:
|
||||
* 1. Create a LastFM App (https://www.last.fm/api/account/create), and paste in your credentials below
|
||||
* 2. Save this script to a publicly-accessible server
|
||||
* 3. Set config in mopidy.conf to point to this script
|
||||
**/
|
||||
|
||||
// LastFM app credentials
|
||||
define('API_URL','http://ws.audioscrobbler.com/2.0/?format=json');
|
||||
define('API_KEY','269cc2b1dfb1059a89a2db21ceb81d2a');
|
||||
define('API_SECRET','1551231f57eb3b73801e31bc0e89ab09');
|
||||
define('REDIRECT_URI','https://jamesbarnsley.nz/auth_lastfm.php?action=start_session');
|
||||
|
||||
// Allow cross-domain requests
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
|
||||
// Set our cookies
|
||||
if (isset($_GET['app'])){
|
||||
setcookie('mopidy_iris', $_GET['app'], time()+3600);
|
||||
}
|
||||
|
||||
|
||||
/* ================================================================================= INIT ================ */
|
||||
/* ======================================================================================================= */
|
||||
|
||||
if (isset($_GET['action'])){
|
||||
switch ($_GET['action']){
|
||||
|
||||
case 'authorize':
|
||||
header('Location: http://www.last.fm/api/auth/?api_key='.API_KEY.'&cb='.REDIRECT_URI);
|
||||
exit;
|
||||
|
||||
case 'start_session':
|
||||
$session = startSession($_GET['token']);
|
||||
|
||||
// Pass our error back to the popup opener
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
window.opener.postMessage( '<?php echo json_encode($session) ?>', "*");
|
||||
window.close();
|
||||
</script>
|
||||
<?php
|
||||
|
||||
break;
|
||||
|
||||
case 'sign_request':
|
||||
$data = $_GET;
|
||||
$signed = signRequest($data);
|
||||
echo json_encode($signed);
|
||||
exit;
|
||||
|
||||
default:
|
||||
echo 'Invalid action specified';
|
||||
die();
|
||||
|
||||
}
|
||||
} else {
|
||||
echo "No action specified";
|
||||
die();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* ================================================================================= GETTERS ============= */
|
||||
/* ======================================================================================================= */
|
||||
|
||||
/**
|
||||
* Start a session
|
||||
*
|
||||
* @param $data = array
|
||||
* @param $post = boolean (POST request)
|
||||
**/
|
||||
function startSession($token){
|
||||
|
||||
$ch = curl_init();
|
||||
|
||||
if (FALSE === $ch){
|
||||
throw new Exception('Failed to initialize');
|
||||
}
|
||||
|
||||
$data = signRequest(
|
||||
array(
|
||||
"method" => "auth.getSession",
|
||||
"token" => $token
|
||||
),
|
||||
false
|
||||
);
|
||||
|
||||
curl_setopt($ch, CURLOPT_URL,API_URL);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
|
||||
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
|
||||
curl_setopt($ch, CURLOPT_VERBOSE, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
$response = curl_exec($ch);
|
||||
|
||||
if (curl_errno($ch)){
|
||||
echo 'CURL Error: '. curl_error($ch);
|
||||
}
|
||||
curl_close($ch);
|
||||
|
||||
$response = json_decode($response, true);
|
||||
|
||||
// Append our other important values to successful signings
|
||||
if (!$response['error']){
|
||||
$response['api_key'] = API_KEY;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Perform a signed request
|
||||
*
|
||||
* @param $data = array
|
||||
* @param $post = boolean (POST request)
|
||||
**/
|
||||
function signRequest($data = array(), $post = true){
|
||||
unset($data['action']);
|
||||
|
||||
$ch = curl_init();
|
||||
|
||||
if (FALSE === $ch){
|
||||
throw new Exception('Failed to initialize');
|
||||
}
|
||||
|
||||
// Make sure we've got our API key included
|
||||
$request = array_merge(array("api_key" => API_KEY), $data);
|
||||
|
||||
// Loop all the values in our request and add to our signature
|
||||
$signature = "";
|
||||
ksort($request);
|
||||
foreach ($request as $key => $value){
|
||||
$signature.= $key.$value;
|
||||
}
|
||||
|
||||
// Finalize the signature
|
||||
$signature.= API_SECRET;
|
||||
$signature = md5($signature);
|
||||
$request["api_sig"] = $signature;
|
||||
|
||||
return $request;
|
||||
}
|
||||
@ -38,7 +38,7 @@
|
||||
|
||||
// Release details
|
||||
// These are automatically injected by build.sh
|
||||
var build = "1509392542";
|
||||
var build = "1509609058";
|
||||
var version = "3.6.1";
|
||||
|
||||
// Construct the script tag
|
||||
|
||||
6
src/js/bootstrap.js
vendored
6
src/js/bootstrap.js
vendored
@ -63,7 +63,9 @@ var initialState = {
|
||||
}
|
||||
},
|
||||
lastfm: {
|
||||
connected: false
|
||||
connected: false,
|
||||
me: false,
|
||||
authorization_url: 'https://jamesbarnsley.co.nz/auth_lastfm.php'
|
||||
},
|
||||
genius: {
|
||||
connected: false
|
||||
@ -72,7 +74,7 @@ var initialState = {
|
||||
connected: false,
|
||||
me: false,
|
||||
autocomplete_results: {},
|
||||
authorization_url: 'https://jamesbarnsley.co.nz/auth_v2.php'
|
||||
authorization_url: 'https://jamesbarnsley.co.nz/auth_spotify.php'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
123
src/js/components/LastfmAuthenticationFrame.js
Executable file
123
src/js/components/LastfmAuthenticationFrame.js
Executable file
@ -0,0 +1,123 @@
|
||||
|
||||
import React, { PropTypes } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { createStore, bindActionCreators } from 'redux'
|
||||
import ReactGA from 'react-ga'
|
||||
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
import Thumbnail from './Thumbnail'
|
||||
|
||||
import * as uiActions from '../services/ui/actions'
|
||||
import * as lastfmActions from '../services/lastfm/actions'
|
||||
|
||||
class LastfmAuthenticationFrame extends React.Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
authorizing: false
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
|
||||
let self = this;
|
||||
|
||||
// Listen for incoming messages from the authorization iframe
|
||||
// This is triggered when the popup posts a message, which is then passed to
|
||||
// the iframe, and then passed on to the parent frame (our application)
|
||||
window.addEventListener('message', function(event){
|
||||
self.handleMessage(event)
|
||||
}, false);
|
||||
}
|
||||
|
||||
handleMessage(event){
|
||||
|
||||
var data = JSON.parse(event.data)
|
||||
|
||||
// Only allow incoming data from our authorized authenticator proxy
|
||||
var authorization_domain = this.props.authorization_url.substring(0,this.props.authorization_url.indexOf('/',8))
|
||||
if (event.origin != authorization_domain){
|
||||
this.props.uiActions.createNotification('Authorization failed. '+event.origin+' is not the configured authorization_url.','bad')
|
||||
return false
|
||||
}
|
||||
|
||||
// Bounced with an error
|
||||
if (typeof(data.error) !== 'undefined'){
|
||||
this.props.uiActions.createNotification(data.error,'bad')
|
||||
|
||||
// No errors? We're in!
|
||||
} else {
|
||||
this.props.lastfmActions.authorizationGranted(data)
|
||||
//this.props.lastfmActions.getMe()
|
||||
}
|
||||
|
||||
// Turn off our authorizing switch
|
||||
this.setState({authorizing: false})
|
||||
}
|
||||
|
||||
startAuthorization(){
|
||||
|
||||
var self = this
|
||||
this.setState({authorizing: true})
|
||||
|
||||
// Open an authentication request window
|
||||
var url = this.props.authorization_url+'?action=authorize'
|
||||
var popup = window.open(url,"popup","height=500,width=350");
|
||||
|
||||
// Start timer to check our popup's state
|
||||
var timer = setInterval(checkPopup, 1000);
|
||||
function checkPopup(){
|
||||
|
||||
// Popup has been closed
|
||||
if (typeof(popup) !== 'undefined' && popup){
|
||||
if (popup.closed){
|
||||
self.setState({authorizing: false})
|
||||
clearInterval(timer);
|
||||
}
|
||||
|
||||
// Popup does not exist, so must have been blocked
|
||||
} else {
|
||||
self.props.uiActions.createNotification('Popup blocked. Please allow popups and try again.','bad')
|
||||
self.setState({authorizing: false})
|
||||
clearInterval(timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
if (this.state.authorizing){
|
||||
return (
|
||||
<button className="working">
|
||||
Authorizing...
|
||||
</button>
|
||||
)
|
||||
} else if (this.props.authorized){
|
||||
return (
|
||||
<button className="destructive" onClick={() => this.props.lastfmActions.revokeAuthorization()}>Log out</button>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<button className="primary" onClick={() => this.startAuthorization()}>Log in</button>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
return {
|
||||
authorization_url: state.lastfm.authorization_url,
|
||||
authorized: state.lastfm.authorization,
|
||||
authorizing: state.lastfm.authorizing
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
uiActions: bindActionCreators(uiActions, dispatch),
|
||||
lastfmActions: bindActionCreators(lastfmActions, dispatch)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(LastfmAuthenticationFrame)
|
||||
@ -45,6 +45,28 @@ const sendRequest = (dispatch, getState, params ) => {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function set(data){
|
||||
return {
|
||||
type: 'LASTFM_SET',
|
||||
data: data
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle authorization process
|
||||
**/
|
||||
|
||||
export function authorizationGranted(data){
|
||||
data.token_expiry = new Date().getTime() + data.expires_in;
|
||||
return { type: 'LASTFM_AUTHORIZATION_GRANTED', data: data }
|
||||
}
|
||||
|
||||
export function revokeAuthorization(){
|
||||
return { type: 'LASTFM_AUTHORIZATION_REVOKED' }
|
||||
}
|
||||
|
||||
export function connect(){
|
||||
return (dispatch, getState) => {
|
||||
|
||||
|
||||
@ -9,6 +9,27 @@ export default function reducer(lastfm = {}, action){
|
||||
case 'LASTFM_CONNECTED':
|
||||
return Object.assign({}, lastfm, { connected: true, connecting: false });
|
||||
|
||||
case 'LASTFM_SET':
|
||||
return Object.assign({}, lastfm, action.data)
|
||||
|
||||
case 'LASTFM_AUTHORIZATION_GRANTED':
|
||||
return Object.assign({}, lastfm, {
|
||||
enabled: true,
|
||||
authorizing: false,
|
||||
authorization: action.data,
|
||||
api_key: action.data.api_key,
|
||||
token_expiry: action.data.token_expiry
|
||||
})
|
||||
|
||||
case 'LASTFM_AUTHORIZATION_REVOKED':
|
||||
return Object.assign({}, lastfm, {
|
||||
authorizing: false,
|
||||
authorization: false,
|
||||
api_key: false,
|
||||
token_expiry: 0,
|
||||
me: false
|
||||
})
|
||||
|
||||
default:
|
||||
return lastfm
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ var helpers = require('../../helpers.js')
|
||||
var coreActions = require('../core/actions.js')
|
||||
var uiActions = require('../ui/actions.js')
|
||||
var pusherActions = require('./actions.js')
|
||||
var lastfmActions = require('../lastfm/actions.js')
|
||||
var spotifyActions = require('../spotify/actions.js')
|
||||
|
||||
const PusherMiddleware = (function(){
|
||||
@ -431,7 +432,10 @@ const PusherMiddleware = (function(){
|
||||
store.dispatch(spotifyActions.set({
|
||||
locale: (action.config.locale ? action.config.locale : null),
|
||||
country: (action.config.country ? action.config.country : null),
|
||||
authorization_url: (action.config.authorization_url ? action.config.authorization_url : null)
|
||||
authorization_url: (action.config.spotify_authorization_url ? action.config.spotify_authorization_url : null)
|
||||
}))
|
||||
store.dispatch(lastfmActions.set({
|
||||
authorization_url: (action.config.lastfm_authorization_url ? action.config.lastfm_authorization_url : null)
|
||||
}))
|
||||
|
||||
next(action )
|
||||
|
||||
@ -6,6 +6,7 @@ import { bindActionCreators } from 'redux'
|
||||
import FontAwesome from 'react-fontawesome'
|
||||
|
||||
import SpotifyAuthenticationFrame from '../components/SpotifyAuthenticationFrame'
|
||||
import LastfmAuthenticationFrame from '../components/LastfmAuthenticationFrame'
|
||||
import ConfirmationButton from '../components/ConfirmationButton'
|
||||
import PusherConnectionList from '../components/PusherConnectionList'
|
||||
import URISchemesList from '../components/URISchemesList'
|
||||
@ -310,6 +311,15 @@ class Settings extends React.Component {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 className="underline">LastFM</h4>
|
||||
|
||||
<div className="field">
|
||||
<div className="name">Authorization</div>
|
||||
<div className="input">
|
||||
<LastfmAuthenticationFrame />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h4 className="underline">Advanced</h4>
|
||||
|
||||
<div className="field checkbox">
|
||||
|
||||
Reference in New Issue
Block a user