Compare commits

...

3 Commits

Author SHA1 Message Date
0bfe341afe Adding some features 2023-06-24 14:25:44 +02:00
4419482152 Adding the request models 2023-06-24 12:15:46 +02:00
e1a0fe68e8 Creating the main files 2023-06-24 12:05:36 +02:00
8 changed files with 1484 additions and 1 deletions

3
.gitignore vendored
View File

@@ -0,0 +1,3 @@
node_modules
.env
package-lock.json

View File

@@ -7,4 +7,10 @@ Ce projet a pour objectif de réaliser des actions via l'API de Spotify afin de
- Récupérer des informations sur les musiques, playlists et podcasts - Récupérer des informations sur les musiques, playlists et podcasts
- Contrôler le lecteur de musique - Contrôler le lecteur de musique
- Visualiser les canvas d'éléments de Spotify - Visualiser les canvas d'éléments de Spotify
- Récupérer des informations supplémentaires non-affichées dans l'application - Récupérer des informations supplémentaires non-affichées dans l'application
## Utilisation
Afin d'utiliser ce module, il est nécessaire de créer un fichier `.env` à la racine du projet contenant les variables d'environnement suivantes :
`SPOTIFY_CLIENT_ID='<your-spotify-client-id>'`

13
main.js Normal file
View File

@@ -0,0 +1,13 @@
const SpotifyController = require('./src/spotify.js')
var spotifyController = new SpotifyController().then(initializingSuccess, printError)
// SpotifyController initialized
function initializingSuccess(result) {
console.log(result)
}
// Print the error to the console
function printError(error) {
console.log(error)
}

1249
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "spotifycontroller",
"version": "0.1.0",
"description": "Get all the spotify data with NodeJS!",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "https://factory.vbprojects.fr/thinkode/SpotifyController.git"
},
"author": "V. Boulanger",
"license": "ISC",
"dependencies": {
"axios": "^1.4.0",
"base64url": "^3.0.1",
"crypto": "^1.0.1",
"express": "^4.18.2",
"opn": "^6.0.0",
"randomstring": "^1.3.0"
}
}

16
src/configuration.js Normal file
View File

@@ -0,0 +1,16 @@
const SpotifyEndpoints = {
GET_USER_TOKEN_ENDPOINT: "https://accounts.spotify.com/api/token",
GET_USER_PROFILE_ENDPOINT: 'https://api.spotify.com/v1/me'
}
const AuthorizationScopes = [
'user-read-private',
'user-read-email',
'user-top-read',
'user-read-currently-playing'
]
module.exports = {
SpotifyEndpoints,
AuthorizationScopes
};

31
src/protobuf/pb/api.proto Normal file
View File

@@ -0,0 +1,31 @@
syntax = "proto3";
package com.spotify.api;
// Request used to get a new token for the current user
message GetUserTokenRequest {
// The client id
string client_id = 1;
// The grant type
string grant_type = 2;
// The code returned by the authorization server
string code = 3;
// The redirect uri
string redirect_uri = 4;
// The client secret
string code_verifier = 5;
}
// Reponse of the GetUserTokenRequest
message GetUserTokenResponse {
// The user access token
string access_token = 1;
// The token type
string token_type = 2;
// The time in seconds before the access token expires
int32 expires_in = 3;
// The token used to refresh the user token
string refresh_token = 4;
// A space-separated list of scopes which have been granted for this access_token
string scope = 5;
}

142
src/spotify.js Normal file
View File

@@ -0,0 +1,142 @@
const axios = require('axios')
const dotenv = require('dotenv')
const configuration = require('./configuration.js')
const randomstring = require('randomstring')
const base64url = require('base64url')
const crypto = require('crypto')
const opn = require('opn')
const express = require('express')
class SpotifyController {
// Creates a new SpotifyController instance with the given clientId
constructor() {
return new Promise((resolve, reject) => {
dotenv.config()
// For connection management
this.clientId = process.env.SPOTIFY_CLIENT_ID
// For user token management
this.userToken = undefined
this.userTokenExpiringDate = undefined
this.refreshToken = undefined
// For canvas token management
this.canvasToken = undefined
this.canvasTokenExpiringDate = undefined
// Check client ID
if (this.clientId === undefined || this.clientId === null || this.clientId === '') {
reject('Spotify client ID not set. Please define this variable in the .env file.')
}
this.checkUserToken().then(resolve("User token got."), reject("Error requesting a user token"))
resolve("SpotifyController initialized.")
})
}
// This function aims to get a valid user token for the Spotify API
async checkUserToken() {
return new Promise((resolve, reject) => {
if (this.userToken === undefined) {
// Get a new token with auth
this.requestAuthentication().then(resolve("User token requested"), reject("Error requesting user token"))
} else if (this.userTokenExpiringDate < new Date()) {
// Refresh the user token
refreshUserToken().then(resolve("User token refreshed"), reject("Error refreshing user token"))
}
resolve("User token is already valid")
})
}
// This function aims to get a user token with authentication
async requestAuthentication() {
let clientId = this.clientId
return new Promise((resolve, reject) => {
//Generate a code verifier
const codeVerifier = randomstring.generate(128);
const codechallenge = base64url.fromBase64(crypto.createHash("sha256").update(codeVerifier).digest("base64"))
// Create the authentication URL
let args = new URLSearchParams({
response_type: 'code',
client_id: clientId,
scope: configuration.AuthorizationScopes.join(' '),
redirect_uri: "http://localhost:3333/callback",
code_challenge_method: 'S256',
code_challenge: codechallenge
});
// Open the authentication page
opn('https://accounts.spotify.com/authorize?' + args);
// Create an express app
let app = express()
// Catch the response
app.get('/callback', async (req, res) => {
if(req.query.code != undefined) {
const params = new URLSearchParams();
params.append("client_id", this.clientId);
params.append("grant_type", "authorization_code");
params.append("code", req.query.code);
params.append("redirect_uri", "http://localhost:3333/callback");
params.append("code_verifier", codeVerifier);
executePostRequest(configuration.SpotifyEndpoints.GET_USER_TOKEN_ENDPOINT, params, { headers: { "Content-Type": "application/x-www-form-urlencoded" } }, "json").then(result => {
console.log(result)
}, error => {
console.log(error)
})
} else {
res.send("Spotify authentication failed! Please close this tab and try again.")
reject("Error when executing request: no code provided.")
}
});
app.use(function(req, res, next) {
res.status(404)
res.send('404: File Not Found')
});
app.listen(3333, function () {
console.log('Callback server is running!')
});
resolve()
}, error => {
reject(error)
})
}
// This function aims to refresh the user token
async refreshUserToken() {
return new Promise((resolve, reject) => {
resolve()
})
}
}
// Execute a GET request to the Spotify API
function executeGetRequest(url, requestObject, options, responseType){
return new Promise((resolve, reject) => {
axios.get(url, requestObject.serializeBinary(), options)
.then(function(response){
if (response.statusText !== 'OK') {
reject(`Request error (${response.status}): ${response.statusText}`)
} else {
resolve(responseType.deserializeBinary(response.data).toObject());
}
})
.catch(function(error){
reject(error)
})
})
}
// Execute a POST request to the Spotify API
function executePostRequest(url, requestObject, options, responseType){
return new Promise((resolve, reject) => {
axios.post(url, requestObject, options)
.then(function(response){
if (response.statusText !== 'OK') {
reject(`Request error (${response.status}): ${response.statusText}`)
} else {
resolve(response.data);
}
})
.catch(function(error){
reject(error)
})
})
}
module.exports = SpotifyController;