DAVIDS/src/server.js

271 lines
7.9 KiB
JavaScript

/*
* Copyright (c) 2016 Gabriel Augendre <gabriel@augendre.info>
* Free software under MIT License. See LICENSE file.
*/
/**
* The functions tagged as "From Spotify tutorial"
* are from an example of a basic node.js script that performs
* the Authorization Code oAuth2 flow to authenticate against
* the Spotify Accounts.
*
* It's licensed under Apache 2.0.
* See https://github.com/spotify/web-api-auth-examples for more information.
*
* For more information about the tutorial, read
* https://developer.spotify.com/web-api/authorization-guide/#authorization_code_flow
*/
'use strict';
var express = require('express'); // Express web server framework
var request = require('request'); // "Request" library
var querystring = require('querystring');
var cookieParser = require('cookie-parser');
var enforce = require('express-sslify');
var client_id = process.env.CLIENT_ID; // Your client id
var client_secret = process.env.CLIENT_SECRET; // Your client secret
var redirect_uri = process.env.CALLBACK; // Your redirect uri
var environment = process.env.NODE_ENV || 'prod';
/**
* Generates a random string containing numbers and letters.
* From Spotify tutorial.
* @param {number} length The length of the string
* @return {string} The generated string
*/
var generateRandomString = function (length) {
var text = '';
var possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (var i = 0; i < length; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
};
var stateKey = 'spotify_auth_state';
var app = express();
if (environment == 'prod' || environment == 'production') {
app.use(enforce.HTTPS({trustProtoHeader: true}));
}
app.use(express.static(__dirname + '/public'))
.use(cookieParser());
/**
* From Spotify tutorial.
*/
app.get('/login', function (req, res) {
var state = generateRandomString(16);
res.cookie(stateKey, state);
// your application requests authorization
// Changed scopes from Spotify tytorial
var scope = 'playlist-read-private playlist-read-collaborative';
res.redirect('https://accounts.spotify.com/authorize?' +
querystring.stringify({
response_type: 'code',
client_id: client_id,
scope: scope,
redirect_uri: redirect_uri,
state: state
}));
});
/**
* From Spotify tutorial.
*/
app.get('/callback', function (req, res) {
// your application requests refresh and access tokens
// after checking the state parameter
var code = req.query.code || null;
var state = req.query.state || null;
var storedState = req.cookies ? req.cookies[stateKey] : null;
if (state === null || state !== storedState) {
res.redirect('/#' +
querystring.stringify({
error: 'state_mismatch'
}));
} else {
res.clearCookie(stateKey);
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
form: {
code: code,
redirect_uri: redirect_uri,
grant_type: 'authorization_code'
},
headers: {
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64'))
},
json: true
};
request.post(authOptions, function (error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token,
refresh_token = body.refresh_token;
// we can also pass the token to the browser to make requests from there
res.redirect('/#' +
querystring.stringify({
access_token: access_token,
refresh_token: refresh_token
}));
} else {
res.redirect('/#' +
querystring.stringify({
error: 'invalid_token'
}));
}
});
}
});
/**
* From Spotify tutorial.
*/
app.get('/refresh_token', function (req, res) {
// requesting access token from refresh token
var refresh_token = req.query.refresh_token;
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: {'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64'))},
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token
},
json: true
};
request.post(authOptions, function (error, response, body) {
if (!error && response.statusCode === 200) {
var access_token = body.access_token;
res.send({
'access_token': access_token
});
}
});
});
app.get('/get_playlists', function (req, res) {
// requesting access token from refresh token
var access_token = req.query.access_token;
var next = req.query.next;
var authOptions = {
url: next ? next : 'https://api.spotify.com/v1/me/playlists?' + querystring.stringify({
limit: 50
}),
headers: {'Authorization': 'Bearer ' + access_token},
json: true
};
getAllPages(authOptions, [], function (data) {
res.send({
'data': data
});
});
});
app.get('/pl/:uId/:plId', function (req, res) {
var plId = req.params.plId;
var userId = req.params.uId;
var access_token = req.query.access_token;
var fields = querystring.stringify({
fields: 'items(track(id,name,artists(id,name))),next'
});
var authOptions = {
url: 'https://api.spotify.com/v1/users/' + userId + '/playlists/' + plId + '/tracks?' + fields,
headers: {'Authorization': 'Bearer ' + access_token},
json: true
};
getAllPages(authOptions, [], function (data) {
var dups = [];
data.forEach(function (item, index, array) {
var i = index + 1;
while (i < array.length) {
var other = array[i];
if (areDups(item.track, other.track)) {
dups.push(item);
}
i++;
}
});
res.send({
'data': dups
});
});
});
/**
* @typedef {Object} track
* @property {number|null} id
* @property {string} name
* @property {[artist]} artists
*/
/**
* @typedef {Object} artist
* @property {number} id
* @property {string} name
*/
/**
* Check if two tracks are duplicates
* @param {track} track1
* @param {track} track2
* @return {boolean} True if the two are duplicates. False otherwise
*/
function areDups(track1, track2) {
var name1 = track1.name.toLowerCase();
var name2 = track2.name.toLowerCase();
if (track1.id != null && track2.id != null && track1.id == track2.id) {
return true;
}
else if (haveCommonArtist(track1, track2)) {
if (name1 == name2) {
return true;
}
else if (name1.startsWith(name2) || name2.startsWith(name1)) {
return true;
}
}
return false;
}
/**
* Check if two tracks have at least one artist in common based on artist id.
* @param {track} track1
* @param {track} track2
* @return {boolean} True if they have at least one artist in common. False otherwise.
*/
function haveCommonArtist(track1, track2) {
return track1.artists[0].id == track2.artists[0].id;
}
function getAllPages(authOptions, data, callback) {
request.get(authOptions, function (error, response, body) {
if (!error && response.statusCode === 200) {
if (body.next) {
authOptions.url = body.next;
getAllPages(authOptions, data.concat(body.items), callback);
}
else {
callback(data.concat(body.items));
}
}
});
}
module.exports = app;