Skip to content

Commit

Permalink
working auth and current album art
Browse files Browse the repository at this point in the history
  • Loading branch information
ajtadeo committed Nov 16, 2023
1 parent 0d05411 commit d560484
Show file tree
Hide file tree
Showing 10 changed files with 179 additions and 76 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ SPOTIFY_REDIRECT_URI /* http://[HOSTNAME]:[PORT]/callback/ */

Note: HOSTNAME should not contain "http://"

3. `pm2 start app.js`
3. `npm start app.js`

## Running the App
1. Open `[HOSTNAME]:[PORT]` in a web browser
Expand Down
173 changes: 100 additions & 73 deletions app.js
Original file line number Diff line number Diff line change
@@ -1,70 +1,99 @@
const { assert } = require("console");
// const { assert, log } = require("console");
const express = require("express");
const http = require("http");
const { Server } = require("socket.io");
const dotenv = require("dotenv")
var cookieParser = require('cookie-parser');
var request = require('request');
const { access } = require("fs");

// https://stackoverflow.com/questions/68329418/in-javascript-how-can-i-throw-an-error-if-an-environmental-variable-is-missing
function getEnv(name){
let val = process.env[name];
if ((val === undefined) || (val === null)) {
throw ("Spotipi: Error: Missing "+ name +" in ./.env");
}
return val;
}
const cookieParser = require('cookie-parser');
const request = require('request');
// const { access } = require("fs");
const { Liquid } = require('liquidjs');
const { generateRandomString, getEnv } = require("./helpers");

/* dotenv setup */
const result = dotenv.config();
if (result.error){
if (result.error) {
console.log("Spotipi: Error: dotenv not configured correctly.")
throw result.error;
}

const port = getEnv("PORT");
const hostname = getEnv("HOSTNAME");
const port = 8888;
const hostname = "localhost";
const client_id = getEnv("SPOTIFY_CLIENT_ID");
const redirect_uri = getEnv("SPOTIFY_REDIRECT_URI");
const client_secret = getEnv("SPOTIFY_CLIENT_SECRET");
var stateKey = 'spotify_auth_state';

/* Express.js and socket.io setup */
/* Express.js setup with LiquidJS */
const app = express();
const engine = new Liquid();
app.engine("liquid", engine.express());
app.set("views", __dirname + "/views");
app.set("view engine", "liquid");
app.use(express.static(__dirname + "/static"))
app.use(cookieParser());

/* Socket.io setup */
const server = http.createServer(app);
const io = new Server(server);

/* Cookie methods */
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';

/* Routing methods */
app.use(cookieParser());

// home page
app.get("/", (req, res) => {
res.sendFile(__dirname + "/public/index.html");
res.render('home');
});

// dashboard - login required
app.get("/dashboard", (req, res) => {
if (req.cookies['access_token']) {
var currently_playing_params = {
url: 'https://api.spotify.com/v1/me/player/currently-playing',
headers: { 'Authorization': 'Bearer ' + req.cookies["access_token"] },
json: true
};

request.get(currently_playing_params, (error, response, body) => {
if (response.statusCode == 200) {
// music currently playing
var current_image_url = body.item.album.images[0].url;
var current_progress = body.progress_ms;
var current_duration = body.item.duration_ms;
var next_refresh = current_duration - current_progress;
console.log(current_image_url, next_refresh)
res.render('dashboard', {
current_image_url: current_image_url
});
} else if (response.statusCode == 204) {
// no music currently playing
res.render('dashboard', {
error: "Play some music to start!"
})
} else if (response.statusCode == 401) {
// bad or expired token
res.redirect("/refresh_token?next=/dashboard")
} else {
// invalid HTTP code received
res.render('dashboard', {
error: response.statusCode + ": " + response.body
});
}
});
} else {
res.redirect('/')
}
})

// request authorization from Spotify
app.get("/login", (req, res) => {
var state = generateRandomString(16);
res.cookie(stateKey, state);
var scope = "user-read-currently-playing";
var params = new URLSearchParams([
['response_type', 'code'],
['client_id', client_id],
['scope', scope],
['redirect_uri', redirect_uri],
['state', state]
]);
var params = new URLSearchParams({
'response_type': 'code',
'client_id': client_id,
'scope': scope,
'redirect_uri': redirect_uri,
'state': state,
'show_dialog': true
});
res.redirect("https://accounts.spotify.com/authorize?" + params.toString());
});

Expand All @@ -75,9 +104,9 @@ app.get("/callback", (req, res) => {
var storedState = req.cookies ? req.cookies[stateKey] : null;
if (state == null || state !== storedState) {
var params = new URLSearchParams({ 'error': 'state_mismatch' });
res.redirect('/#' + params.toString());
res.redirect('/#?' + params.toString());
} else {
// get api token
// generate token request
res.clearCookie(stateKey);
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
Expand All @@ -92,57 +121,55 @@ app.get("/callback", (req, res) => {
json: true
};

// post response
var status = "";
// post token request
request.post(authOptions, (error, response, body) => {
if (!error && response.statusCode === 200) {
var access_token = body.access_token;
var refresh_token = body.refresh_token;
var options = {
url: 'https://api.spotify.com/v1/me',
headers: { 'Authorization': 'Bearer ' + access_token },
json: true
};

request.get(options, (error, response, body) => {
console.log(body);
});

// pass token to the browser
var params = new URLSearchParams([
["access_token", access_token],
["refresh_token", refresh_token]
]);
status = "Authorized"
res.redirect('/#' + params.toString());
// store access token and refresh token in session cookie
const cookieAttributes = {
httpOnly: true,
secure: true,
}
res.cookie("access_token", body.access_token, cookieAttributes)
res.cookie("refresh_token", body.refresh_token, cookieAttributes);

// redirect to dashboard page
res.redirect('/dashboard');
} else {
var params = new URLSearchParams({ "error": "invalid_token" });
status = "Invalid Token"
res.redirect('/#', params.toString());
res.redirect('/#?' + params.toString());
}
});
}
});

// refresh access token
app.get("/refresh_token", (req, res) => {
var refresh_token = req.query.refresh_token;
var next = req.query.next
console.log(next)
var refresh_token = req.cookies['refresh_token']
var authOptions = {
url: 'https://accounts.spotify.com/api/token',
headers: { 'Authorization': 'Basic ' + Buffer.from(client_id + ':' + client_secret).toString('base64') },
headers: {
'content-type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + Buffer.from(client_id + ':' + client_secret).toString('base64')
},
form: {
grant_type: 'refresh_token',
refresh_token: refresh_token
},
json: true
};

request.post(authOptions, (error, response, body) =>{
if (!error && response.statusCode == 200){
var access_token = body.access_token;
res.send({
"access_token": access_token
});
request.post(authOptions, (error, response, body) => {
if (!error && response.statusCode == 200) {
res.cookie('access_token', body.access_token)
res.cookie('refresh_token', body.refresh_token)
res.redirect(next)
} else {
console.error('Token refresh error:', error);
console.log('Response:', response.statusCode, response.body);
console.log('Request:', authOptions);
res.redirect("/");
}
});
});
Expand Down
18 changes: 18 additions & 0 deletions helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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;
};

function getEnv(name) {
if (!process.env[name]) {
throw ("Spotipi: Error: Missing " + name + " in ./.env");
}
return process.env[name];
}

module.exports = { generateRandomString, getEnv }
41 changes: 41 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"is-extglob": "^2.1.1",
"is-glob": "^4.0.3",
"is-number": "^7.0.0",
"liquidjs": "^10.9.4",
"minimatch": "^3.1.2",
"ms": "^2.1.3",
"nopt": "^1.0.10",
Expand Down
File renamed without changes
File renamed without changes
9 changes: 9 additions & 0 deletions views/dashboard.liquid
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{% layout "index.liquid" %}
{% block content %}
<h1>Dashboard</h1>
{% if error %}
<p>{{error}}</p>
{% else %}
<img src="{{current_image_url}}" width="400px" height="400px">
{% endif %}
{% endblock %}
5 changes: 5 additions & 0 deletions views/home.liquid
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{% layout "index.liquid" %}
{% block content %}
<h1>Login</h1>
<a href="/login"><button>Login</button></a>
{% endblock %}
6 changes: 4 additions & 2 deletions public/index.html → views/index.liquid
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
<script>
var socket = io();
</script>

{% block content %}Default{% endblock %}

<p>Status:</p>
<!-- <p>Status:</p>
<div id="status"></div>
<p>Currently playing song:</p>
Expand All @@ -21,6 +23,6 @@
<img id="albumImage" src="">
<div id="trackTitle"></div>
<div id="trackArtist"></div>
</div>
</div> -->
</body>
</html>

0 comments on commit d560484

Please sign in to comment.