initial commit

This commit is contained in:
Karl 2020-03-06 15:14:23 +00:00
commit 64e5534504
21 changed files with 2831 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
data/accessData.json
data/token.json
data/userDetails.json

25
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,25 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Record",
"skipFiles": [
],
"program": "${workspaceFolder}/bin/www"
},
{
"type": "node",
"request": "launch",
"name": "Player File",
"skipFiles": [
],
"program": "${workspaceFolder}/player.js",
"args":["file"]
}
]
}

41
app.js Normal file
View File

@ -0,0 +1,41 @@
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var indexRouter = require('./routes/index');
var recordRouter = require('./routes/record');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', indexRouter);
app.use('/record', recordRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;

22
bin/callBack.js Normal file
View File

@ -0,0 +1,22 @@
var express = require('express');
var fs = require('fs')
var path = require('path')
var app = express();
var port = process.env.PORT || 8080;
app.get('/callback', function (req, res) {
var token = req.query.code
const userToken = {}
userToken.userToken = token
fs.writeFile(path.resolve(__dirname, "../data/token.json"), JSON.stringify(userToken), function (err) {
if (err) return console.log(err);
console.log(`Token written to file`);
process.exit()
});
res.status(200).send("Request successful")
});
// start the server
app.listen(port, '0.0.0.0');
console.log('Server started! At http://localhost:' + port);

22
bin/generateAccess.js Normal file
View File

@ -0,0 +1,22 @@
var SpotifyWebApi = require('spotify-web-api-node');
var fs = require('fs')
var path = require('path')
var userDetails = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../data/userDetails.json")));
var scopes = ['user-read-private', 'user-read-email', 'user-read-playback-state'],
redirectUri = userDetails.redirectUri,
clientId = userDetails.clientId,
state = 'some-state-of-my-choice';
// Setting credentials can be done in the wrapper's constructor, or using the API object's setters.
var spotifyApi = new SpotifyWebApi({
redirectUri: redirectUri,
clientId: clientId
});
// Create the authorization URL
var authorizeURL = spotifyApi.createAuthorizeURL(scopes, state);
// https://accounts.spotify.com:443/authorize?client_id=5fe01282e44241328a84e7c5cc169165&response_type=code&redirect_uri=https://example.com/callback&scope=user-read-private%20user-read-email&state=some-state-of-my-choice
console.log(authorizeURL);

38
bin/setup.js Normal file
View File

@ -0,0 +1,38 @@
var inquirer = require('inquirer');
var fs = require('fs')
var path = require('path')
var questions = [{
type: 'input',
name: 'clientId',
message: 'Please enter your clientId'
},
{
type: 'input',
name: 'clientSecret',
message: 'Please enter your clientSecret'
},
{
type: 'input',
name: 'redirectUri',
message: 'Please enter your redirectUri'
},
{
type: 'input',
name: 'device',
message: 'Please enter your device name to play music on'
},
{
type: 'input',
name: 'path',
message: 'Where is your floppy drive mounted?'
}
];
inquirer.prompt(questions).then(answers => {
fs.writeFile((path.resolve(__dirname, "../data/userDetails.json")), JSON.stringify(answers), function (err) {
if (err) return console.log(err);
console.log(`User Details Written to File`);
});
console.log(JSON.stringify(answers, null, ' '));
});

39
bin/userToken.js Normal file
View File

@ -0,0 +1,39 @@
var SpotifyWebApi = require('spotify-web-api-node');
var fs = require('fs')
var userDetails = JSON.parse(fs.readFileSync('./data/userDetails.json', 'utf8'));
var path = require('path')
var credentials = {
clientId: userDetails.clientId,
clientSecret: userDetails.clientSecret,
redirectUri: userDetails.redirectUri
};
var spotifyApi = new SpotifyWebApi(credentials);
var token = JSON.parse(fs.readFileSync('./data/token.json', 'utf8'));
// The code that's returned as a query parameter to the redirect URI
var code = token.userToken;
// Retrieve an access token and a refresh token
spotifyApi.authorizationCodeGrant(code).then(
function(data) {
console.log('The token expires in ' + data.body['expires_in']);
console.log('The access token is ' + data.body['access_token']);
console.log('The refresh token is ' + data.body['refresh_token']);
const accessData = {}
accessData.access_token = data.body['access_token'];
accessData.refresh_token = data.body['refresh_token'];
fs.writeFileSync('./data/accessData.json', JSON.stringify(accessData))
// Set the access token on the API object to use it in later calls
spotifyApi.setAccessToken(data.body['access_token']);
spotifyApi.setRefreshToken(data.body['refresh_token']);
},
function(err) {
console.log('Something went wrong!', err);
}
);

90
bin/www Executable file
View File

@ -0,0 +1,90 @@
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('spotifydisk-node:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3001');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}

0
data/.gitkeep Normal file
View File

58
functions/generic.js Normal file
View File

@ -0,0 +1,58 @@
const drivelist = require('drivelist');
const fileSize = require('filesize')
const { exec } = require("child_process");
async function getDrives() {
const drives = await drivelist.list();
drives.forEach(function (item) {
item.size = fileSize(item.size)
if (item.mountpoints.length > 0) {
item.path = item.mountpoints[0].path
} else {
item.path = 'N/A'
}
})
return drives
}
async function mount(path1, path2) {
return new Promise(function (resolve, reject) {
exec(`sudo mount ${path1} ${path2}`, (error, stdout, stderr) => {
if (error) {
if (error.message.includes('already mounted')) {
resolve(true)
return;
} else {
resolve(false)
return;
}
} if (stderr) {
resolve(false)
return;
}
resolve(true)
})
})
}
async function unMount(path1) {
return new Promise(function (resolve, reject) {
exec(`sudo umount ${path1}`, (error, stdout, stderr) => {
if (error) {
resolve(false)
return;
} if (stderr) {
resolve(false)
return;
}
resolve(true)
})
})
}
module.exports = {
getDrives,
mount,
unMount
}

2089
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

27
package.json Normal file
View File

@ -0,0 +1,27 @@
{
"name": "spotifydisk-node",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www",
"setup": "node ./bin/setup.js && node ./bin/generateAccess.js && node ./bin/callBack.js && node ./bin/userToken.js"
},
"dependencies": {
"body-parser": "^1.19.0",
"cookie-parser": "~1.4.4",
"debug": "~2.6.9",
"drivelist": "^8.0.10",
"express": "~4.16.1",
"filesize": "^6.1.0",
"fs": "0.0.1-security",
"http-errors": "~1.6.3",
"inquirer": "^7.0.6",
"morgan": "~1.9.1",
"path": "^0.12.7",
"path-exists": "^4.0.0",
"pug": "2.0.0-beta11",
"request": "^2.88.2",
"request-promise-native": "^1.0.8",
"spotify-web-api-node": "^4.0.0"
}
}

240
player.js Normal file
View File

@ -0,0 +1,240 @@
var SpotifyWebApi = require('spotify-web-api-node');
var _ = require('lodash')
var fs = require('fs')
var rpn = require('request-promise-native')
var myArgs = process.argv.slice(2);
var accessData = JSON.parse(fs.readFileSync('./data/accessData.json', 'utf8'));
var userDetails = JSON.parse(fs.readFileSync('./data/userDetails.json', 'utf8'));
let spotify = new SpotifyWebApi({
clientId: userDetails.clientId,
clientSecret: userDetails.clientSecret,
redirectUri: userDetails.redirectUri,
})
const Device = userDetails.device
async function initialize() {
const token = await getToken()
spotify.setAccessToken(token)
}
async function refreshToken() {
spotify.setRefreshToken(accessData.refresh_token);
const token = await getRefreshToken()
spotify.setAccessToken(token)
}
async function getToken() {
const result = await spotify.clientCredentialsGrant()
return result.body.access_token
}
async function getRefreshToken() {
const result = await spotify.refreshAccessToken()
return result.body.access_token
}
async function getDevice() {
await initialize()
await refreshToken()
let response;
try {
response = await rpn({
method: 'GET',
url: 'https://api.spotify.com/v1/me/player/devices',
simple: false,
// body,
resolveWithFullResponse: true,
json: true,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${spotify._credentials.accessToken}`
},
});
} catch (e) {
throw Error(`Request error: ${e}`);
}
this.response = response;
this.response.body = response.body
this.response.devices = response.body.devices
const DiskPlayer = _.find(this.response.devices, ['name', Device])
if (DiskPlayer != undefined) {
DiskPlayerId = DiskPlayer.id
return DiskPlayerId
} else {
console.log(`${Device} Not found in device list`)
}
}
async function playFile() {
await initialize()
await refreshToken()
const DiskPlayerId = await getDevice()
const album = fs.readFileSync(`${userDetails.path}/diskplayer.contents`, 'utf8');
const body = {
"context_uri": album,
"offset": {
"position": 0
},
"position_ms": 0
}
let response;
try {
response = await rpn({
method: 'PUT',
url: ' https://api.spotify.com/v1/me/player/play',
qs: {
device_id: DiskPlayerId
},
simple: false,
body,
resolveWithFullResponse: true,
json: true,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${spotify._credentials.accessToken}`
},
});
} catch (e) {
throw Error(`Request error: ${e}`);
}
this.response = response;
this.response.body = response.body
}
async function playURL() {
await initialize()
await refreshToken()
const DiskPlayerId = await getDevice()
let album = myArgs[1]
const i = album.lastIndexOf('/')
album = album.substring(i + 1)
album = 'spotify:album:' + album
const body = {
"context_uri": album,
"offset": {
"position": 0
},
"position_ms": 0
}
let response;
try {
response = await rpn({
method: 'PUT',
url: ' https://api.spotify.com/v1/me/player/play',
qs: {
device_id: DiskPlayerId
},
simple: false,
body,
resolveWithFullResponse: true,
json: true,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${spotify._credentials.accessToken}`
},
});
} catch (e) {
throw Error(`Request error: ${e}`);
}
this.response = response;
this.response.body = response.body
}
async function playPlaylist() {
await initialize()
await refreshToken()
const DiskPlayerId = await getDevice()
let playlist = myArgs[1]
const i = playlist.lastIndexOf('/')
playlist = playlist.substring(i + 1)
playlist = 'spotify:playlist:' + playlist
const body = {
"context_uri": playlist,
"offset": {
"position": 0
},
"position_ms": 0
}
let response;
try {
response = await rpn({
method: 'PUT',
url: ' https://api.spotify.com/v1/me/player/play',
qs: {
device_id: DiskPlayerId
},
simple: false,
body,
resolveWithFullResponse: true,
json: true,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${spotify._credentials.accessToken}`
},
});
} catch (e) {
throw Error(`Request error: ${e}`);
}
this.response = response;
this.response.body = response.body
}
async function pausePlayer() {
await initialize()
await refreshToken()
const DiskPlayerId = await getDevice()
let response;
try {
response = await rpn({
method: 'PUT',
url: ' https://api.spotify.com/v1/me/player/pause',
qs: {
device_id: DiskPlayerId
},
simple: false,
resolveWithFullResponse: true,
json: true,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${spotify._credentials.accessToken}`
},
});
} catch (e) {
throw Error(`Request error: ${e}`);
}
this.response = response;
this.response.body = response.body
}
switch (myArgs[0]) {
case 'file':
playFile()
break;
case 'url':
playURL()
break;
case 'playlist':
playPlaylist()
break;
case 'pause':
pausePlayer()
break;
default:
// code block
}

View File

@ -0,0 +1,8 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}
a {
color: #00B7FF;
}

17
routes/index.js Normal file
View File

@ -0,0 +1,17 @@
var express = require('express');
var router = express.Router();
const genericFunctions = require('../functions/generic')
async function buildPage() {
const drives = await genericFunctions.getDrives()
/* GET home page. */
router.get('/', function (req, res, next) {
res.render('record', {
drives: drives
})
});
}
buildPage()
module.exports = router;

60
routes/record.js Normal file
View File

@ -0,0 +1,60 @@
const bodyParser = require('body-parser');
const pathExists = require('path-exists');
const fs = require('fs')
const genericFunctions = require('../functions/generic')
const path = require("path");
var myArgs = process.argv.slice(2);
var userDetails = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../data/userDetails.json")));
var express = require('express');
var router = express.Router();
router.use(bodyParser.urlencoded({
extended: true
}));
/* GET users listing. */
async function buildPage() {
router.post('/', async (req, res) => {
const pExists = pathExists.sync(req.body.path)
const i = req.body.url
const test = i.lastIndexOf('/')
const id = i.substring(test + 1)
if (i.includes("/album/")) {
sid = `spotify:album:${id}`
} else if (i.includes("/playlist/")) {
sid = `spotify:playlist:${id}`
} else {
console.log('failed')
}
if (pExists === true) {
let isMounted = await genericFunctions.mount(req.body.path, userDetails.path)
console.log(isMounted)
if (isMounted === true) {
try {
fs.writeFileSync(`${userDetails.path}/diskplayer.contents`, sid)
res.render('success', {
path: req.body.path,
url: req.body.url
})
await genericFunctions.unMount(userDetails.path)
} catch (err) {
res.render('failed', {
error: err
})
await genericFunctions.unMount(userDetails.path)
}
} else {
res.render('failed', {
error: 'Path does not exist'
})
}
}
})
};
buildPage()
module.exports = router;

12
views/default.pug Normal file
View File

@ -0,0 +1,12 @@
doctype html
html
head
title #{title}
link(rel='stylesheet', href='/css/style.css')
meta(name="viewport" content="width=device-width, initial-scale=1")
body
main
block header
header.header
h1 #{title}
block content

3
views/failed.pug Normal file
View File

@ -0,0 +1,3 @@
h1 Failed!
p #{error}
a(href='/') Try Again

5
views/index.pug Normal file
View File

@ -0,0 +1,5 @@
extends layout
block content
h1= title
p Welcome to #{title}

27
views/record.pug Normal file
View File

@ -0,0 +1,27 @@
extends default
block content
table#desc
thead
tr
th Name
th Path
th Size
tbody
each n in drives
tr
td= n.device
td= n.path
td= n.size
.main.container
.row
.col-md-6.col-md-offset-3
h1.display-4.m-b-2 Node-DiskPlayer - Record
form(method='POST' action='/record')
div.form-group
label(for='path') Path:
input#path.form-control(type='text', placeholder='/dev/sda' name='path')
div.form-group
label(for='url') Spotify Url:
input#url.form-control(type='text', placeholder='https://open.spotify.com/track/1zv2bVCoZL8RyjBhna1NdV' name='url')
button.btn.btn-primary(type='submit') Record

4
views/success.pug Normal file
View File

@ -0,0 +1,4 @@
h1 Success!
p Recorded Path - #{path}
p Spotify URL - #{url}
a(href='/') Record Another Disk