diff --git a/src/js/components/LastfmLoveButton.js b/src/js/components/LastfmLoveButton.js new file mode 100755 index 00000000..7c59122f --- /dev/null +++ b/src/js/components/LastfmLoveButton.js @@ -0,0 +1,62 @@ + +import React, { PropTypes } from 'react' +import { connect } from 'react-redux' +import { Link } from 'react-router' +import { createStore, bindActionCreators } from 'redux' +import FontAwesome from 'react-fontawesome' + +import * as helpers from '../helpers' +import * as uiActions from '../services/ui/actions' +import * as lastfmActions from '../services/lastfm/actions' + +class FollowButton extends React.Component{ + + constructor(props){ + super(props); + } + + remove(){ + this.props.lastfmActions.loveTrack(this.props.uri, this.props.artist, this.props.track); + } + + add(){ + this.props.lastfmActions.unloveTrack(this.props.uri, this.props.artist, this.props.track); + } + + render(){ + if (!this.props.uri){ + return false; + } + + var className = ''; + + // Inherit passed-down classes + if (this.props.className){ + className += ' '+this.props.className; + } + + if (!this.props.lastfm_authorized){ + return + } else if (this.props.loved === true){ + return + } else { + return + } + } +} + +const mapStateToProps = (state, ownProps) => { + return { + load_queue: state.ui.load_queue, + lastfm_authorized: state.lastfm.session + } +} + +const mapDispatchToProps = (dispatch) => { + return { + uiActions: bindActionCreators(uiActions, dispatch), + lastfmActions: bindActionCreators(lastfmActions, dispatch) + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(FollowButton) \ No newline at end of file diff --git a/src/js/services/lastfm/actions.js b/src/js/services/lastfm/actions.js index 4379ce7f..980ce7da 100755 --- a/src/js/services/lastfm/actions.js +++ b/src/js/services/lastfm/actions.js @@ -9,19 +9,26 @@ var helpers = require('../../helpers') * @param dispatch = obj * @param getState = obj * @param params = string, the url params to send - * @param signed = boolean + * @params signed = boolean, whether we've got a signed request with baked-in api_key **/ -const sendRequest = (dispatch, getState, params, signed) => { +const sendRequest = (dispatch, getState, params, signed = false) => { return new Promise((resolve, reject) => { - var loader_key = helpers.generateGuid() - dispatch(uiActions.startLoading(loader_key, 'lastfm_'+params)) + var loader_key = helpers.generateGuid(); + dispatch(uiActions.startLoading(loader_key, 'lastfm_'+params)); var config = { method: 'GET', cache: true, timeout: 30000, - url: '//ws.audioscrobbler.com/2.0/?format=json&api_key=4320a3ef51c9b3d69de552ac083c55e3&'+params + url: '//ws.audioscrobbler.com/2.0/?format=json&'+params + } + + // Signed requests don't need our api_key as the proxy has it's own + if (!signed){ + config.url += '&api_key=4320a3ef51c9b3d69de552ac083c55e3'; + } else { + config.method = 'POST'; } $.ajax(config).then( @@ -46,6 +53,58 @@ const sendRequest = (dispatch, getState, params, signed) => { }) } +/** + * Send a SIGNED ajax request to the LastFM API + * + * @param dispatch = obj + * @param getState = obj + * @param params = string, the url params to send + * @param signed = boolean + **/ +const sendSignedRequest = (dispatch, getState, params) => { + return new Promise((resolve, reject) => { + + var loader_key = helpers.generateGuid() + dispatch(uiActions.startLoading(loader_key, 'lastfm_'+params)) + + var config = { + method: 'GET', + cache: false, + timeout: 30000, + url: getState().lastfm.authorization_url+"?action=sign_request&"+params + } + + $.ajax(config).then( + response => { + var signed_params = ""; + for (var key in response){ + if (response.hasOwnProperty(key)){ + if (signed_params != ""){ + signed_params += "&" + } + signed_params += key+'='+response[key]; + } + } + dispatch(uiActions.stopLoading(loader_key)) + return sendRequest(dispatch, getState, signed_params, true) + }, + (xhr, status, error) => { + dispatch(uiActions.stopLoading(loader_key)) + dispatch(coreActions.handleException( + 'LastFM: '+xhr.responseText, + { + config: config, + error: error, + status: status, + xhr: xhr + } + )); + reject(error) + } + ); + }) +} + export function set(data){ return { @@ -98,16 +157,38 @@ export function connect(){ * TODO **/ -export function loveTrack(artist, track){ +export function loveTrack(uri, artist, track){ return (dispatch, getState) => { - var params = 'method=track.love&artist='+artist+'&track='+track - sendRequest(dispatch, getState, params, true) + artist = encodeURIComponent(artist); + var params = 'method=track.love&track='+track+'&artist='+artist; + sendSignedRequest(dispatch, getState, params) .then( response => { dispatch({ - type: 'LASTFM_TRACK_LOVED', - artist: artist, - track: track + type: 'TRACK_LOADED', + key: uri, + track: { + userloved: true + } + }); + } + ) + } +} + +export function unloveTrack(uri, artist, track){ + return (dispatch, getState) => { + artist = encodeURIComponent(artist); + var params = 'method=track.unlove&track='+track+'&artist='+artist; + sendSignedRequest(dispatch, getState, params) + .then( + response => { + dispatch({ + type: 'TRACK_LOADED', + key: uri, + track: { + userloved: false + } }); } ) @@ -192,19 +273,30 @@ export function getAlbum(artist, album, mbid = false){ } } -export function getTrack(artist, track){ +export function getTrack(track, artist_name = null, track_name = null){ return (dispatch, getState) => { - - dispatch({ type: 'LASTFM_TRACK_LOADED', data: false }); - - artist = encodeURIComponent(artist ); - sendRequest(dispatch, getState, 'method=track.getInfo&track='+track+'&artist='+artist) + if (track){ + artist_name = track.artist[0].name; + track_name = track.name; + } + artist_name = encodeURIComponent(artist_name); + var params = 'method=track.getInfo&track='+track+'&artist='+artist; + if (getState().lastfm.session){ + params += '&username='+getState().lastfm.session.name; + } + sendRequest(dispatch, getState, params) .then( response => { if (response.track){ + var merged_track = Object.assign( + {}, + response.track, + track + ); dispatch({ - type: 'LASTFM_TRACK_LOADED', - data: response.track + type: 'TRACK_LOADED', + key: uri, + track: merged_track }); } } diff --git a/src/js/services/spotify/actions.js b/src/js/services/spotify/actions.js index 4c0cf64a..f56d1080 100755 --- a/src/js/services/spotify/actions.js +++ b/src/js/services/spotify/actions.js @@ -264,11 +264,7 @@ export function getMe(){ **/ export function getTrack(uri){ return (dispatch, getState) => { - - // flush out the previous store value - dispatch({ type: 'SPOTIFY_TRACK_LOADED', data: false }); - - sendRequest(dispatch, getState, 'tracks/'+ helpers.getFromUri('trackid', uri) ) + sendRequest(dispatch, getState, 'tracks/'+ helpers.getFromUri('trackid', uri)) .then( response => { let track = Object.assign( @@ -677,6 +673,13 @@ export function following(uri, method = 'GET'){ var asset_name = helpers.uriType(uri); var endpoint, data switch(asset_name){ + case 'track': + if (method == 'GET'){ + endpoint = 'me/tracks/contains/?ids='+ helpers.getFromUri('trackid', uri) + } else { + endpoint = 'me/tracks/?ids='+ helpers.getFromUri('trackid', uri) + } + break case 'album': if (method == 'GET'){ endpoint = 'me/albums/contains/?ids='+ helpers.getFromUri('albumid', uri) @@ -709,7 +712,7 @@ export function following(uri, method = 'GET'){ break } - sendRequest(dispatch, getState, endpoint, method, data ) + sendRequest(dispatch, getState, endpoint, method, data) .then( response => { if (response ) is_following = response diff --git a/src/js/views/Track.js b/src/js/views/Track.js index bf5e26e6..3213325e 100755 --- a/src/js/views/Track.js +++ b/src/js/views/Track.js @@ -11,6 +11,7 @@ import Thumbnail from '../components/Thumbnail' import ArtistSentence from '../components/ArtistSentence' import ArtistGrid from '../components/ArtistGrid' import FollowButton from '../components/FollowButton' +import LastfmLoveButton from '../components/LastfmLoveButton' import Dater from '../components/Dater' import LazyLoadListener from '../components/LazyLoadListener' import ContextMenuTrigger from '../components/ContextMenuTrigger' @@ -19,6 +20,7 @@ import * as helpers from '../helpers' import * as uiActions from '../services/ui/actions' import * as mopidyActions from '../services/mopidy/actions' import * as spotifyActions from '../services/spotify/actions' +import * as lastfmActions from '../services/lastfm/actions' import * as geniusActions from '../services/genius/actions' class Track extends React.Component{ @@ -39,13 +41,13 @@ class Track extends React.Component{ componentWillReceiveProps(nextProps){ - // if our URI has changed, fetch new album + // if our URI has changed, fetch new track if (nextProps.params.uri != this.props.params.uri){ - this.loadTrack(nextProps ) + this.loadTrack(nextProps) - // if mopidy has just connected AND we're a local album, go get + // if mopidy has just connected AND we're not a Spotify track, go get } else if (!this.props.mopidy_connected && nextProps.mopidy_connected){ - if (helpers.uriSource(this.props.params.uri ) != 'spotify'){ + if (helpers.uriSource(this.props.params.uri) != 'spotify'){ this.loadTrack(nextProps); } } @@ -89,6 +91,11 @@ class Track extends React.Component{ break; } + // Get the LastFM version of this track + if (props.lastfm_authorized){ + //this.props.lastfmActions.getTrack(this.props.track); + } + // We don't have lyrics, but the track (and artists) is already loaded if (props.track && !props.track.lyrics_results && props.track.artists){ this.props.geniusActions.findTrackLyrics(props.track); @@ -211,7 +218,8 @@ class Track extends React.Component{