Build a Spotify duplicate finder
This commit is contained in:
parent
c38989b8bd
commit
ad28b92bf9
12 changed files with 557 additions and 500 deletions
82
.gitignore
vendored
82
.gitignore
vendored
|
@ -1 +1,83 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
|
|
||||||
|
# Created by https://www.gitignore.io/api/webstorm,node
|
||||||
|
|
||||||
|
### WebStorm ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff:
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Sensitive or high-churn files:
|
||||||
|
.idea/dataSources.ids
|
||||||
|
.idea/dataSources.xml
|
||||||
|
.idea/dataSources.local.xml
|
||||||
|
.idea/sqlDataSources.xml
|
||||||
|
.idea/dynamic.xml
|
||||||
|
.idea/uiDesigner.xml
|
||||||
|
|
||||||
|
# Gradle:
|
||||||
|
.idea/gradle.xml
|
||||||
|
.idea/libraries
|
||||||
|
|
||||||
|
# Mongo Explorer plugin:
|
||||||
|
.idea/mongoSettings.xml
|
||||||
|
|
||||||
|
## File-based project format:
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
## Plugin-specific files:
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
|
||||||
|
### Node ###
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules
|
||||||
|
jspm_packages
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
1
Procfile
Normal file
1
Procfile
Normal file
|
@ -0,0 +1 @@
|
||||||
|
web: node bin/www
|
200
app.js
Normal file
200
app.js
Normal file
|
@ -0,0 +1,200 @@
|
||||||
|
/**
|
||||||
|
* This is an example of a basic node.js script that performs
|
||||||
|
* the Authorization Code oAuth2 flow to authenticate against
|
||||||
|
* the Spotify Accounts.
|
||||||
|
*
|
||||||
|
* For more information, read
|
||||||
|
* https://developer.spotify.com/web-api/authorization-guide/#authorization_code_flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
var express = require('express'); // Express web server framework
|
||||||
|
var request = require('request'); // "Request" library
|
||||||
|
var querystring = require('querystring');
|
||||||
|
var cookieParser = require('cookie-parser');
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a random string containing numbers and letters
|
||||||
|
* @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();
|
||||||
|
|
||||||
|
app.use(express.static(__dirname + '/public'))
|
||||||
|
.use(cookieParser());
|
||||||
|
|
||||||
|
app.get('/login', function (req, res) {
|
||||||
|
|
||||||
|
var state = generateRandomString(16);
|
||||||
|
res.cookie(stateKey, state);
|
||||||
|
|
||||||
|
// your application requests authorization
|
||||||
|
var scope = 'user-read-private user-read-email playlist-modify-private 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
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
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'
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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',
|
||||||
|
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 (item.track.id == other.track.id) {
|
||||||
|
dups.push(item);
|
||||||
|
}
|
||||||
|
else if (item.track.name.toLowerCase() == other.track.name.toLowerCase() && item.track.artists[0].id == other.track.artists[0].id) {
|
||||||
|
dups.push(item);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
array.forEach(function (other, otherIndex) {
|
||||||
|
});
|
||||||
|
});
|
||||||
|
res.send({
|
||||||
|
'data': dups
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
|
@ -1,145 +0,0 @@
|
||||||
/**
|
|
||||||
* This is an example of a basic node.js script that performs
|
|
||||||
* the Authorization Code oAuth2 flow to authenticate against
|
|
||||||
* the Spotify Accounts.
|
|
||||||
*
|
|
||||||
* For more information, read
|
|
||||||
* https://developer.spotify.com/web-api/authorization-guide/#authorization_code_flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
var express = require('express'); // Express web server framework
|
|
||||||
var request = require('request'); // "Request" library
|
|
||||||
var querystring = require('querystring');
|
|
||||||
var cookieParser = require('cookie-parser');
|
|
||||||
|
|
||||||
var client_id = '03ffe0cac0a0401aa6673c3cf6d02ced'; // Your client id
|
|
||||||
var client_secret = 'a57c43efb9644574a96d6623fb8bfbc2'; // Your client secret
|
|
||||||
var redirect_uri = 'http://localhost:8888/callback'; // Your redirect uri
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a random string containing numbers and letters
|
|
||||||
* @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();
|
|
||||||
|
|
||||||
app.use(express.static(__dirname + '/public'))
|
|
||||||
.use(cookieParser());
|
|
||||||
|
|
||||||
app.get('/login', function(req, res) {
|
|
||||||
|
|
||||||
var state = generateRandomString(16);
|
|
||||||
res.cookie(stateKey, state);
|
|
||||||
|
|
||||||
// your application requests authorization
|
|
||||||
var scope = 'user-read-private user-read-email';
|
|
||||||
res.redirect('https://accounts.spotify.com/authorize?' +
|
|
||||||
querystring.stringify({
|
|
||||||
response_type: 'code',
|
|
||||||
client_id: client_id,
|
|
||||||
scope: scope,
|
|
||||||
redirect_uri: redirect_uri,
|
|
||||||
state: state
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
var options = {
|
|
||||||
url: 'https://api.spotify.com/v1/me',
|
|
||||||
headers: { 'Authorization': 'Bearer ' + access_token },
|
|
||||||
json: true
|
|
||||||
};
|
|
||||||
|
|
||||||
// use the access token to access the Spotify Web API
|
|
||||||
request.get(options, function(error, response, body) {
|
|
||||||
console.log(body);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 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'
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Listening on 8888');
|
|
||||||
app.listen(8888);
|
|
|
@ -1,142 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Example of the Authorization Code flow with Spotify</title>
|
|
||||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
|
|
||||||
<style type="text/css">
|
|
||||||
#login, #loggedin {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.text-overflow {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
width: 500px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div id="login">
|
|
||||||
<h1>This is an example of the Authorization Code flow</h1>
|
|
||||||
<a href="/login" class="btn btn-primary">Log in with Spotify</a>
|
|
||||||
</div>
|
|
||||||
<div id="loggedin">
|
|
||||||
<div id="user-profile">
|
|
||||||
</div>
|
|
||||||
<div id="oauth">
|
|
||||||
</div>
|
|
||||||
<button class="btn btn-default" id="obtain-new-token">Obtain new token using the refresh token</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script id="user-profile-template" type="text/x-handlebars-template">
|
|
||||||
<h1>Logged in as {{display_name}}</h1>
|
|
||||||
<div class="media">
|
|
||||||
<div class="pull-left">
|
|
||||||
<img class="media-object" width="150" src="{{images.0.url}}" />
|
|
||||||
</div>
|
|
||||||
<div class="media-body">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>Display name</dt><dd class="clearfix">{{display_name}}</dd>
|
|
||||||
<dt>Id</dt><dd>{{id}}</dd>
|
|
||||||
<dt>Email</dt><dd>{{email}}</dd>
|
|
||||||
<dt>Spotify URI</dt><dd><a href="{{external_urls.spotify}}">{{external_urls.spotify}}</a></dd>
|
|
||||||
<dt>Link</dt><dd><a href="{{href}}">{{href}}</a></dd>
|
|
||||||
<dt>Profile Image</dt><dd class="clearfix"><a href="{{images.0.url}}">{{images.0.url}}</a></dd>
|
|
||||||
<dt>Country</dt><dd>{{country}}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id="oauth-template" type="text/x-handlebars-template">
|
|
||||||
<h2>oAuth info</h2>
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>Access token</dt><dd class="text-overflow">{{access_token}}</dd>
|
|
||||||
<dt>Refresh token</dt><dd class="text-overflow">{{refresh_token}}></dd>
|
|
||||||
</dl>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0-alpha.1/handlebars.min.js"></script>
|
|
||||||
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
|
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains parameters from the hash of the URL
|
|
||||||
* @return Object
|
|
||||||
*/
|
|
||||||
function getHashParams() {
|
|
||||||
var hashParams = {};
|
|
||||||
var e, r = /([^&;=]+)=?([^&;]*)/g,
|
|
||||||
q = window.location.hash.substring(1);
|
|
||||||
while ( e = r.exec(q)) {
|
|
||||||
hashParams[e[1]] = decodeURIComponent(e[2]);
|
|
||||||
}
|
|
||||||
return hashParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
var userProfileSource = document.getElementById('user-profile-template').innerHTML,
|
|
||||||
userProfileTemplate = Handlebars.compile(userProfileSource),
|
|
||||||
userProfilePlaceholder = document.getElementById('user-profile');
|
|
||||||
|
|
||||||
var oauthSource = document.getElementById('oauth-template').innerHTML,
|
|
||||||
oauthTemplate = Handlebars.compile(oauthSource),
|
|
||||||
oauthPlaceholder = document.getElementById('oauth');
|
|
||||||
|
|
||||||
var params = getHashParams();
|
|
||||||
|
|
||||||
var access_token = params.access_token,
|
|
||||||
refresh_token = params.refresh_token,
|
|
||||||
error = params.error;
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
alert('There was an error during the authentication');
|
|
||||||
} else {
|
|
||||||
if (access_token) {
|
|
||||||
// render oauth info
|
|
||||||
oauthPlaceholder.innerHTML = oauthTemplate({
|
|
||||||
access_token: access_token,
|
|
||||||
refresh_token: refresh_token
|
|
||||||
});
|
|
||||||
|
|
||||||
$.ajax({
|
|
||||||
url: 'https://api.spotify.com/v1/me',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + access_token
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
userProfilePlaceholder.innerHTML = userProfileTemplate(response);
|
|
||||||
|
|
||||||
$('#login').hide();
|
|
||||||
$('#loggedin').show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// render initial screen
|
|
||||||
$('#login').show();
|
|
||||||
$('#loggedin').hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('obtain-new-token').addEventListener('click', function() {
|
|
||||||
$.ajax({
|
|
||||||
url: '/refresh_token',
|
|
||||||
data: {
|
|
||||||
'refresh_token': refresh_token
|
|
||||||
}
|
|
||||||
}).done(function(data) {
|
|
||||||
access_token = data.access_token;
|
|
||||||
oauthPlaceholder.innerHTML = oauthTemplate({
|
|
||||||
access_token: access_token,
|
|
||||||
refresh_token: refresh_token
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, false);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
24
bin/www
Normal file
24
bin/www
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
'use strict';
|
||||||
|
require('dotenv').config({silent: true});
|
||||||
|
var throng = require('throng');
|
||||||
|
var WORKERS = process.env.WEB_CONCURRENCY || 1;
|
||||||
|
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
var app = require('../app');
|
||||||
|
// Specify listen port
|
||||||
|
app.set('port', process.env.PORT || 3000);
|
||||||
|
|
||||||
|
// Start listening
|
||||||
|
var server = app.listen(app.get('port'), function () {
|
||||||
|
var port = server.address().port;
|
||||||
|
console.log('Listening on %s', port);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
throng({
|
||||||
|
workers: WORKERS,
|
||||||
|
lifetime: Infinity,
|
||||||
|
start: start
|
||||||
|
});
|
|
@ -1,44 +0,0 @@
|
||||||
/**
|
|
||||||
* This is an example of a basic node.js script that performs
|
|
||||||
* the Client Credentials oAuth2 flow to authenticate against
|
|
||||||
* the Spotify Accounts.
|
|
||||||
*
|
|
||||||
* For more information, read
|
|
||||||
* https://developer.spotify.com/web-api/authorization-guide/#client_credentials_flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
var request = require('request'); // "Request" library
|
|
||||||
|
|
||||||
var client_id = '03ffe0cac0a0401aa6673c3cf6d02ced'; // Your client id
|
|
||||||
var client_secret = 'a57c43efb9644574a96d6623fb8bfbc2'; // Your client secret
|
|
||||||
var redirect_uri = 'http://localhost:8888/callback'; // Your redirect uri
|
|
||||||
|
|
||||||
// your application requests authorization
|
|
||||||
var authOptions = {
|
|
||||||
url: 'https://accounts.spotify.com/api/token',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Basic ' + (new Buffer(client_id + ':' + client_secret).toString('base64'))
|
|
||||||
},
|
|
||||||
form: {
|
|
||||||
grant_type: 'client_credentials'
|
|
||||||
},
|
|
||||||
json: true
|
|
||||||
};
|
|
||||||
|
|
||||||
request.post(authOptions, function(error, response, body) {
|
|
||||||
if (!error && response.statusCode === 200) {
|
|
||||||
|
|
||||||
// use the access token to access the Spotify Web API
|
|
||||||
var token = body.access_token;
|
|
||||||
var options = {
|
|
||||||
url: 'https://api.spotify.com/v1/users/jmperezperez',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + token
|
|
||||||
},
|
|
||||||
json: true
|
|
||||||
};
|
|
||||||
request.get(options, function(error, response, body) {
|
|
||||||
console.log(body);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,14 +0,0 @@
|
||||||
/**
|
|
||||||
* This is an example of a basic node.js script that performs
|
|
||||||
* the Implicit Grant oAuth2 flow to authenticate against
|
|
||||||
* the Spotify Accounts.
|
|
||||||
*
|
|
||||||
* For more information, read
|
|
||||||
* https://developer.spotify.com/web-api/authorization-guide/#implicit_grant_flow
|
|
||||||
*/
|
|
||||||
|
|
||||||
var express = require('express'); // Express web server framework
|
|
||||||
var app = express();
|
|
||||||
app.use(express.static(__dirname + '/public'));
|
|
||||||
console.log('Listening on 8888');
|
|
||||||
app.listen(8888);
|
|
|
@ -1,154 +0,0 @@
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Example of the Implicit Grant flow with Spotify</title>
|
|
||||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
|
|
||||||
<style type="text/css">
|
|
||||||
#login, #loggedin {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.text-overflow {
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
width: 500px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div id="login">
|
|
||||||
<h1>This is an example of the Implicit Grant flow</h1>
|
|
||||||
<button id="login-button" class="btn btn-primary">Log in with Spotify</button>
|
|
||||||
</div>
|
|
||||||
<div id="loggedin">
|
|
||||||
<div id="user-profile">
|
|
||||||
</div>
|
|
||||||
<div id="oauth">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script id="user-profile-template" type="text/x-handlebars-template">
|
|
||||||
<h1>Logged in as {{display_name}}</h1>
|
|
||||||
<div class="media">
|
|
||||||
<div class="pull-left">
|
|
||||||
<img class="media-object" width="150" src="{{images.0.url}}" />
|
|
||||||
</div>
|
|
||||||
<div class="media-body">
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>Display name</dt><dd class="clearfix">{{display_name}}</dd>
|
|
||||||
<dt>Id</dt><dd>{{id}}</dd>
|
|
||||||
<dt>Email</dt><dd>{{email}}</dd>
|
|
||||||
<dt>Spotify URI</dt><dd><a href="{{external_urls.spotify}}">{{external_urls.spotify}}</a></dd>
|
|
||||||
<dt>Link</dt><dd><a href="{{href}}">{{href}}</a></dd>
|
|
||||||
<dt>Profile Image</dt><dd class="clearfix"><a href="{{images.0.url}}">{{images.0.url}}</a></dd>
|
|
||||||
<dt>Country</dt><dd>{{country}}</dd>
|
|
||||||
</dl>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script id="oauth-template" type="text/x-handlebars-template">
|
|
||||||
<h2>oAuth info</h2>
|
|
||||||
<dl class="dl-horizontal">
|
|
||||||
<dt>Access token</dt><dd class="text-overflow">{{access_token}}</dd>
|
|
||||||
</dl>
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0-alpha.1/handlebars.min.js"></script>
|
|
||||||
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
|
|
||||||
<script>
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
var stateKey = 'spotify_auth_state';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtains parameters from the hash of the URL
|
|
||||||
* @return Object
|
|
||||||
*/
|
|
||||||
function getHashParams() {
|
|
||||||
var hashParams = {};
|
|
||||||
var e, r = /([^&;=]+)=?([^&;]*)/g,
|
|
||||||
q = window.location.hash.substring(1);
|
|
||||||
while ( e = r.exec(q)) {
|
|
||||||
hashParams[e[1]] = decodeURIComponent(e[2]);
|
|
||||||
}
|
|
||||||
return hashParams;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a random string containing numbers and letters
|
|
||||||
* @param {number} length The length of the string
|
|
||||||
* @return {string} The generated string
|
|
||||||
*/
|
|
||||||
function generateRandomString(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 userProfileSource = document.getElementById('user-profile-template').innerHTML,
|
|
||||||
userProfileTemplate = Handlebars.compile(userProfileSource),
|
|
||||||
userProfilePlaceholder = document.getElementById('user-profile');
|
|
||||||
|
|
||||||
oauthSource = document.getElementById('oauth-template').innerHTML,
|
|
||||||
oauthTemplate = Handlebars.compile(oauthSource),
|
|
||||||
oauthPlaceholder = document.getElementById('oauth');
|
|
||||||
|
|
||||||
var params = getHashParams();
|
|
||||||
|
|
||||||
var access_token = params.access_token,
|
|
||||||
state = params.state,
|
|
||||||
storedState = localStorage.getItem(stateKey);
|
|
||||||
|
|
||||||
if (access_token && (state == null || state !== storedState)) {
|
|
||||||
alert('There was an error during the authentication');
|
|
||||||
} else {
|
|
||||||
localStorage.removeItem(stateKey);
|
|
||||||
if (access_token) {
|
|
||||||
$.ajax({
|
|
||||||
url: 'https://api.spotify.com/v1/me',
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer ' + access_token
|
|
||||||
},
|
|
||||||
success: function(response) {
|
|
||||||
userProfilePlaceholder.innerHTML = userProfileTemplate(response);
|
|
||||||
|
|
||||||
$('#login').hide();
|
|
||||||
$('#loggedin').show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
$('#login').show();
|
|
||||||
$('#loggedin').hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('login-button').addEventListener('click', function() {
|
|
||||||
|
|
||||||
var client_id = '03ffe0cac0a0401aa6673c3cf6d02ced'; // Your client id
|
|
||||||
var redirect_uri = 'http://localhost:8888/'; // Your redirect uri
|
|
||||||
|
|
||||||
var state = generateRandomString(16);
|
|
||||||
|
|
||||||
localStorage.setItem(stateKey, state);
|
|
||||||
var scope = 'user-read-private user-read-email';
|
|
||||||
|
|
||||||
var url = 'https://accounts.spotify.com/authorize';
|
|
||||||
url += '?response_type=token';
|
|
||||||
url += '&client_id=' + encodeURIComponent(client_id);
|
|
||||||
url += '&scope=' + encodeURIComponent(scope);
|
|
||||||
url += '&redirect_uri=' + encodeURIComponent(redirect_uri);
|
|
||||||
url += '&state=' + encodeURIComponent(state);
|
|
||||||
|
|
||||||
window.location = url;
|
|
||||||
}, false);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</html>
|
|
|
@ -6,8 +6,10 @@
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cookie-parser": "1.3.2",
|
"cookie-parser": "1.3.2",
|
||||||
|
"dotenv": "^2.0.0",
|
||||||
"express": "~4.0.0",
|
"express": "~4.0.0",
|
||||||
"querystring": "~0.2.0",
|
"querystring": "~0.2.0",
|
||||||
"request": "~2.34.0"
|
"request": "~2.34.0",
|
||||||
|
"throng": "^4.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
241
public/index.html
Normal file
241
public/index.html
Normal file
|
@ -0,0 +1,241 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Spotify Duplicate Finder</title>
|
||||||
|
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
|
||||||
|
<style type="text/css">
|
||||||
|
#login, #loggedin {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-overflow {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 500px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div id="login">
|
||||||
|
<h1>This is an example of the Authorization Code flow</h1>
|
||||||
|
<a href="/login" class="btn btn-primary">Log in with Spotify</a>
|
||||||
|
</div>
|
||||||
|
<div id="loggedin">
|
||||||
|
<div id="user-profile">
|
||||||
|
</div>
|
||||||
|
<div id="oauth">
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-default" id="obtain-new-token">Obtain new token using the refresh token</button>
|
||||||
|
|
||||||
|
<h1>Duplicates finder</h1>
|
||||||
|
<div id="dups">
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-default" id="get-playlists">Get playlists</button>
|
||||||
|
<div id="playlists">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script id="user-profile-template" type="text/x-handlebars-template">
|
||||||
|
<h1>Logged in as {{display_name}}</h1>
|
||||||
|
<div class="media">
|
||||||
|
<div class="pull-left">
|
||||||
|
<img class="media-object" width="150" src="{{images.0.url}}"/>
|
||||||
|
</div>
|
||||||
|
<div class="media-body">
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>Display name</dt>
|
||||||
|
<dd class="clearfix">{{display_name}}</dd>
|
||||||
|
<dt>Id</dt>
|
||||||
|
<dd>{{id}}</dd>
|
||||||
|
<dt>Spotify URI</dt>
|
||||||
|
<dd><a href="{{external_urls.spotify}}">{{external_urls.spotify}}</a></dd>
|
||||||
|
<dt>Link</dt>
|
||||||
|
<dd><a href="{{href}}">{{href}}</a></dd>
|
||||||
|
<dt>Profile Image</dt>
|
||||||
|
<dd class="clearfix"><a href="{{images.0.url}}">{{images.0.url}}</a></dd>
|
||||||
|
<dt>Country</dt>
|
||||||
|
<dd>{{country}}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="oauth-template" type="text/x-handlebars-template">
|
||||||
|
<h2>oAuth info</h2>
|
||||||
|
<dl class="dl-horizontal">
|
||||||
|
<dt>Access token</dt>
|
||||||
|
<dd class="text-overflow">{{access_token}}</dd>
|
||||||
|
<dt>Refresh token</dt>
|
||||||
|
<dd class="text-overflow">{{refresh_token}}></dd>
|
||||||
|
</dl>
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="dups-template" type="text/x-handlebars-template">
|
||||||
|
<h2>Duplicates in {{pl_name}}</h2>
|
||||||
|
{{message}}
|
||||||
|
{{#list dups}}{{dup_trackname}} - {{dup_artist}}{{/list}}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script id="playlists-template" type="text/x-handlebars-template">
|
||||||
|
<h2>Playlists</h2>
|
||||||
|
{{#list playlists}}<a class="pl_item" href="/pl/{{pl_uid}}/{{pl_id}}">{{pl_name}}</a>{{/list}}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script src="//cdnjs.cloudflare.com/ajax/libs/handlebars.js/2.0.0-alpha.1/handlebars.min.js"></script>
|
||||||
|
<script src="https://code.jquery.com/jquery-1.12.3.min.js"></script>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains parameters from the hash of the URL
|
||||||
|
* @return Object
|
||||||
|
*/
|
||||||
|
function getHashParams() {
|
||||||
|
var hashParams = {};
|
||||||
|
var e, r = /([^&;=]+)=?([^&;]*)/g,
|
||||||
|
q = window.location.hash.substring(1);
|
||||||
|
while (e = r.exec(q)) {
|
||||||
|
hashParams[e[1]] = decodeURIComponent(e[2]);
|
||||||
|
}
|
||||||
|
return hashParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
Handlebars.registerHelper('list', function (items, options) {
|
||||||
|
var out = "<ul>";
|
||||||
|
|
||||||
|
for (var i = 0, l = items.length; i < l; i++) {
|
||||||
|
out = out + "<li>" + options.fn(items[i]) + "</li>";
|
||||||
|
}
|
||||||
|
|
||||||
|
return out + "</ul>";
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
var userProfileSource = document.getElementById('user-profile-template').innerHTML,
|
||||||
|
userProfileTemplate = Handlebars.compile(userProfileSource),
|
||||||
|
userProfilePlaceholder = document.getElementById('user-profile');
|
||||||
|
|
||||||
|
var oauthSource = document.getElementById('oauth-template').innerHTML,
|
||||||
|
oauthTemplate = Handlebars.compile(oauthSource),
|
||||||
|
oauthPlaceholder = document.getElementById('oauth');
|
||||||
|
|
||||||
|
var playlistsSource = document.getElementById('playlists-template').innerHTML,
|
||||||
|
playlistsTemplate = Handlebars.compile(playlistsSource),
|
||||||
|
playlistsPlaceholder = document.getElementById('playlists');
|
||||||
|
|
||||||
|
var dupsSource = document.getElementById('dups-template').innerHTML,
|
||||||
|
dupsTemplate = Handlebars.compile(dupsSource),
|
||||||
|
dupsPlaceholder = document.getElementById('dups');
|
||||||
|
|
||||||
|
var params = getHashParams();
|
||||||
|
|
||||||
|
var access_token = params.access_token,
|
||||||
|
refresh_token = params.refresh_token,
|
||||||
|
error = params.error;
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
alert('There was an error during the authentication');
|
||||||
|
} else {
|
||||||
|
if (access_token) {
|
||||||
|
// render oauth info
|
||||||
|
oauthPlaceholder.innerHTML = oauthTemplate({
|
||||||
|
access_token: access_token,
|
||||||
|
refresh_token: refresh_token
|
||||||
|
});
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: 'https://api.spotify.com/v1/me',
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + access_token
|
||||||
|
},
|
||||||
|
success: function (response) {
|
||||||
|
userProfilePlaceholder.innerHTML = userProfileTemplate(response);
|
||||||
|
|
||||||
|
$('#login').hide();
|
||||||
|
$('#loggedin').show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// render initial screen
|
||||||
|
$('#login').show();
|
||||||
|
$('#loggedin').hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('get-playlists').addEventListener('click', function () {
|
||||||
|
$.ajax({
|
||||||
|
url: '/get_playlists',
|
||||||
|
data: {
|
||||||
|
'access_token': access_token
|
||||||
|
}
|
||||||
|
}).done(function (data) {
|
||||||
|
var pl = data.data.map(function (item, index, array) {
|
||||||
|
return {
|
||||||
|
pl_uid : item.owner.id,
|
||||||
|
pl_name : item.name,
|
||||||
|
pl_id : item.id
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
playlistsPlaceholder.innerHTML = playlistsTemplate({
|
||||||
|
playlists: pl
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
$(document).on('click', '.pl_item', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
var pl_name = $(this).text();
|
||||||
|
$.ajax({
|
||||||
|
url: $(this).attr('href'),
|
||||||
|
data: {
|
||||||
|
'access_token': access_token
|
||||||
|
}
|
||||||
|
}).done(function (data) {
|
||||||
|
var dups = data.data.map(function (item) {
|
||||||
|
return {
|
||||||
|
dup_trackname: item.track.name,
|
||||||
|
dup_artist: item.track.artists[0].name
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (data.data.length > 0) {
|
||||||
|
dupsPlaceholder.innerHTML = dupsTemplate({
|
||||||
|
dups : dups,
|
||||||
|
pl_name : pl_name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
dupsPlaceholder.innerHTML = dupsTemplate({
|
||||||
|
dups : [],
|
||||||
|
message: "No duplicate found.",
|
||||||
|
pl_name: pl_name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('obtain-new-token').addEventListener('click', function () {
|
||||||
|
$.ajax({
|
||||||
|
url: '/refresh_token',
|
||||||
|
data: {
|
||||||
|
'refresh_token': refresh_token
|
||||||
|
}
|
||||||
|
}).done(function (data) {
|
||||||
|
access_token = data.access_token;
|
||||||
|
oauthPlaceholder.innerHTML = oauthTemplate({
|
||||||
|
access_token: access_token,
|
||||||
|
refresh_token: refresh_token
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue