Hacky, but working spotify skipping.
This commit is contained in:
parent
fecd3db3bc
commit
64f1d76778
|
@ -13,6 +13,7 @@
|
|||
"express": "^4.18.2",
|
||||
"nanoid": "^3.3.6",
|
||||
"pkg": "^5.8.1",
|
||||
"spotify-web-api-node": "^5.0.2",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"bin": {
|
||||
|
@ -434,6 +435,11 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
|
||||
|
@ -648,6 +654,22 @@
|
|||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
|
||||
|
@ -699,6 +721,11 @@
|
|||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/cookiejar": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz",
|
||||
"integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw=="
|
||||
},
|
||||
"node_modules/core-util-is": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||
|
@ -755,6 +782,14 @@
|
|||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
|
@ -955,6 +990,11 @@
|
|||
"node": ">=8.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-safe-stringify": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
|
||||
|
@ -1018,6 +1058,28 @@
|
|||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
|
||||
"integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/formidable": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz",
|
||||
"integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==",
|
||||
"deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau",
|
||||
"funding": {
|
||||
"url": "https://ko-fi.com/tunnckoCore/commissions"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
|
@ -2143,6 +2205,14 @@
|
|||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/spotify-web-api-node": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/spotify-web-api-node/-/spotify-web-api-node-5.0.2.tgz",
|
||||
"integrity": "sha512-r82dRWU9PMimHvHEzL0DwEJrzFk+SMCVfq249SLt3I7EFez7R+jeoKQd+M1//QcnjqlXPs2am4DFsGk8/GCsrA==",
|
||||
"dependencies": {
|
||||
"superagent": "^6.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/statuses": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||
|
@ -2199,6 +2269,52 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/superagent/-/superagent-6.1.0.tgz",
|
||||
"integrity": "sha512-OUDHEssirmplo3F+1HWKUrUjvnQuA+nZI6i/JJBdXb5eq9IyEQwPyPpqND+SSsxf6TygpBEkUjISVRN4/VOpeg==",
|
||||
"deprecated": "Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at <https://github.com/visionmedia/superagent/releases>.",
|
||||
"dependencies": {
|
||||
"component-emitter": "^1.3.0",
|
||||
"cookiejar": "^2.1.2",
|
||||
"debug": "^4.1.1",
|
||||
"fast-safe-stringify": "^2.0.7",
|
||||
"form-data": "^3.0.0",
|
||||
"formidable": "^1.2.2",
|
||||
"methods": "^1.1.2",
|
||||
"mime": "^2.4.6",
|
||||
"qs": "^6.9.4",
|
||||
"readable-stream": "^3.6.0",
|
||||
"semver": "^7.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/mime": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
|
||||
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
|
||||
"bin": {
|
||||
"mime": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/superagent/node_modules/readable-stream": {
|
||||
"version": "3.6.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"string_decoder": "^1.1.1",
|
||||
"util-deprecate": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"express": "^4.18.2",
|
||||
"nanoid": "^3.3.6",
|
||||
"pkg": "^5.8.1",
|
||||
"spotify-web-api-node": "^5.0.2",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -3,31 +3,36 @@ const {input} = require("@inquirer/prompts");
|
|||
const querystring = require("querystring");
|
||||
const {nanoid} = require("nanoid");
|
||||
const express = require('express')
|
||||
const {setAuth, hasAuth, authFile} = require("../utilities/authentication");
|
||||
const {spotify, spotifyCredentialsFile, hasSpotifyCredentials, saveSpotifyCredentials} = require("../utilities/spotify");
|
||||
|
||||
exports.command = 'authenticate'
|
||||
exports.description = 'starts the Spotity authentication process'
|
||||
exports.builder = function (argv) {
|
||||
return argv.option('client-id')
|
||||
}
|
||||
;
|
||||
exports.builder = (argv) => argv
|
||||
.option('client-id', {
|
||||
description: 'The Spotify app client id',
|
||||
type: 'string'
|
||||
})
|
||||
.option('client-secret', {
|
||||
description: 'The Spotify app client secret',
|
||||
type: 'string'
|
||||
})
|
||||
exports.handler = async function (argv) {
|
||||
if (hasAuth()) {
|
||||
console.log(`spotify.local is already authenticated, remove "${authFile}" is you want to log out.`);
|
||||
if (hasSpotifyCredentials()) {
|
||||
console.log(`spotify.local is already authenticated, remove "${spotifyCredentialsFile}" is you want to log out.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const port = await findAvailablePort();
|
||||
|
||||
const authenticateRoute = 'authenticate';
|
||||
const authenticateUrl = `http://localhost:${port}/${authenticateRoute}`;
|
||||
const redirectUri = `http://localhost:${port}/${authenticateRoute}`;
|
||||
|
||||
const tutorial = [
|
||||
'Visit https://developer.spotify.com/dashboard and click "Create app"',
|
||||
'Set the "App name" as "spotify.local"',
|
||||
'Set the "App description" as "My spotify.local"',
|
||||
'Set the "Website" as "https://code.tmp.dev/ian/spotify.local"',
|
||||
`Set the "Redirect URI" as "${authenticateUrl}"`,
|
||||
`Set the "Redirect URI" as "${redirectUri}"`,
|
||||
'Select under "Which API/SDKs are you planning to use?" the "Web API"',
|
||||
'Sign your life away by agreeing with the "Developer Terms of Service and Design Guidelines"',
|
||||
'Click on the "Save" button',
|
||||
|
@ -36,12 +41,18 @@ exports.handler = async function (argv) {
|
|||
|
||||
tutorial.forEach((string, index) => console.log(`${index+1} ${string}`));
|
||||
|
||||
const clientId = argv.clientId ?? await input({ message: `${tutorial.length}. Paste the clientId here` })
|
||||
const clientId = argv.clientId ?? await input({ message: `${tutorial.length}. Paste the client id here` })
|
||||
const clientSecret = argv.clientSecret ?? await input({ message: `${tutorial.length}. Paste the client secret here` })
|
||||
const originalState = nanoid();
|
||||
const redirectUrl = new URL('https://accounts.spotify.com/authorize');
|
||||
redirectUrl.search = querystring.stringify({
|
||||
|
||||
spotify.setClientId(clientId);
|
||||
spotify.setClientSecret(clientSecret);
|
||||
spotify.setRedirectURI(redirectUri);
|
||||
|
||||
const authUrl = new URL('https://accounts.spotify.com/authorize');
|
||||
authUrl.search = querystring.stringify({
|
||||
response_type: 'code',
|
||||
client_id: clientId,
|
||||
client_id: spotify.getClientId(),
|
||||
scope: [
|
||||
'user-library-read',
|
||||
'user-read-private',
|
||||
|
@ -59,28 +70,35 @@ exports.handler = async function (argv) {
|
|||
'user-read-currently-playing',
|
||||
'user-read-playback-position'
|
||||
].join(' '),
|
||||
redirect_uri: authenticateUrl,
|
||||
redirect_uri: spotify.getRedirectURI(),
|
||||
state: originalState
|
||||
});
|
||||
|
||||
const app = express();
|
||||
|
||||
let server;
|
||||
app.get(`/${authenticateRoute}`, (req, res) => {
|
||||
const {state, code} = req.query;
|
||||
if (originalState === state) {
|
||||
setAuth(code);
|
||||
app.get(`/${authenticateRoute}`, async (req, res) => {
|
||||
const {state, code, error = null} = req.query;
|
||||
if (!error && originalState === state) {
|
||||
const { body } = await spotify.authorizationCodeGrant(code)
|
||||
const {access_token, refresh_token, expires_in} = body;
|
||||
spotify.setExpired((new Date).getTime() + expires_in);
|
||||
spotify.setAccessToken(access_token);
|
||||
spotify.setRefreshToken(refresh_token);
|
||||
saveSpotifyCredentials();
|
||||
|
||||
res.send('<script>(() => close())()</script>');
|
||||
|
||||
server?.close();
|
||||
|
||||
console.log(`${tutorial.length+2}. Spotify.local has been authenticated`);
|
||||
} else {
|
||||
res.status(400);
|
||||
res.send('NO')
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(400);
|
||||
res.send('NO');
|
||||
});
|
||||
|
||||
server = app.listen(port, () => console.log(`${tutorial.length+1}. Follow the following URL to authenticate: ${redirectUrl}`));
|
||||
server = app.listen(port, () => console.log(`${tutorial.length+1}. Follow the following URL to authenticate: ${authUrl}`));
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
const { spotify} = require('../utilities/spotify')
|
||||
|
||||
exports.command = 'next'
|
||||
exports.handler = async () => {
|
||||
spotify.ensure()
|
||||
.then(spotify => spotify.skipToNext())
|
||||
.catch(e => console.error(e.message))
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
const { existsSync, mkdirSync, writeFileSync, readFileSync } = require('fs');
|
||||
const { homedir } = require('os');
|
||||
|
||||
const configDir = `${homedir()}/.spotify-local`
|
||||
const authenticationFile = `${configDir}/authentication`
|
||||
|
||||
const ensure = () => {
|
||||
if (!existsSync(configDir)) mkdirSync(configDir);
|
||||
if (!existsSync(authenticationFile)) writeFileSync(authenticationFile, '');
|
||||
return true;
|
||||
};
|
||||
|
||||
exports.authFile = authenticationFile;
|
||||
exports.setAuth = (code) => ensure() && writeFileSync(authenticationFile, code);
|
||||
exports.getAuth = () => ensure() && readFileSync(authenticationFile).toString();
|
||||
exports.hasAuth = () => ensure() && readFileSync(authenticationFile).toString() !== '';
|
|
@ -0,0 +1,56 @@
|
|||
const SpotifyWebApi = require('spotify-web-api-node');
|
||||
const {homedir} = require("os");
|
||||
const {existsSync, mkdirSync, writeFileSync, readFileSync} = require("fs");
|
||||
|
||||
const configDir = `${homedir()}/.spotify-local`
|
||||
const spotifyCredentialsFile = `${configDir}/credentials.json`
|
||||
|
||||
const ensure = () => {
|
||||
if (!existsSync(configDir)) mkdirSync(configDir);
|
||||
if (!existsSync(spotifyCredentialsFile)) writeFileSync(spotifyCredentialsFile, '{}');
|
||||
return true;
|
||||
};
|
||||
|
||||
SpotifyWebApi.prototype.setExpired = function (expiresIn) {
|
||||
const expired = (new Date).getTime() + expiresIn;
|
||||
|
||||
this._setCredential('expired', expired);
|
||||
}
|
||||
|
||||
SpotifyWebApi.prototype.getExpired = function () {
|
||||
return this._getCredential('expired') ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<SpotifyWebApi>}
|
||||
*/
|
||||
SpotifyWebApi.prototype.ensure = async function () {
|
||||
const timeExpired = this.getExpired();
|
||||
const time = (new Date).getTime();
|
||||
if (time > timeExpired) {
|
||||
const { body } = await this.refreshAccessToken();
|
||||
const { access_token, expires_in } = body;
|
||||
this.setAccessToken(access_token);
|
||||
this.setExpired((new Date).getTime() + expires_in);
|
||||
saveSpotifyCredentials();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
const spotify = new SpotifyWebApi();
|
||||
|
||||
const saveSpotifyCredentials = () => ensure() && writeFileSync(spotifyCredentialsFile, JSON.stringify(spotify.getCredentials()));
|
||||
const restoreSpotifyCredentials = () => ensure() && spotify.setCredentials(JSON.parse(readFileSync(spotifyCredentialsFile).toString()));
|
||||
const hasSpotifyCredentials = () => existsSync(spotifyCredentialsFile);
|
||||
|
||||
if (existsSync(spotifyCredentialsFile)) restoreSpotifyCredentials();
|
||||
|
||||
|
||||
module.exports = {
|
||||
spotify,
|
||||
spotifyCredentialsFile,
|
||||
saveSpotifyCredentials,
|
||||
restoreSpotifyCredentials,
|
||||
hasSpotifyCredentials,
|
||||
}
|
Loading…
Reference in New Issue