Version 1.1.0 - Refactor + Intergration Backend

This commit is contained in:
2025-07-25 17:56:30 +02:00
parent a59d7a66db
commit 98cdae97c0
58 changed files with 244 additions and 70 deletions

View File

@@ -0,0 +1,87 @@
const { LogType } = require('loguix');
const alog = new LogType("GoogleOAuth2");
const { google } = require('googleapis');
const config = require("../../utils/Database/Configuration");
const Users = require('../../server/auth/User');
const clientId = config.getYoutubeApiClientId();
const clientSecret = config.getYoutubeApiClientSecret();
const redirectUri = config.getWebsiteLink() + "/oauth2callback";
const oAuth2Map = new Map();
function createAuthUrl(userId) {
if(!checkCredientials()) return null;
var oAuth2Client;
const user = Users.getUserById(userId);
if (!user) {
alog.error(`User with ID ${userId} not found.`);
return null;
}
if (!clientId || !clientSecret) {
alog.error("YouTube API client ID or secret is not set in the configuration.");
} else {
oAuth2Client = new google.auth.OAuth2(
clientId,
clientSecret,
redirectUri
);
alog.log("Google OAuth2 client initialized successfully.");
}
if (!oAuth2Client) {
alog.error("OAuth2 client is not initialized. Please check your configuration.");
return null;
}
oAuth2Map.set(userId, oAuth2Client);
alog.log(`OAuth2 client created for user ${userId}.`);
return oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
}
async function getAuthorization(userId, code) {
if(!checkCredientials()) return null;
try {
const user = Users.getUserById(userId);
if (!user) {
alog.error(`User with ID ${userId} not found.`);
return null;
}
oAuth2Client = oAuth2Map.get(userId);
if (!oAuth2Client) {
alog.error(`OAuth2 client for user ${userId} not found. Please create an OAuth2 client first.`);
return null;
}
const { tokens } = await oAuth2Client.getToken(code);
oAuth2Client.setCredentials(tokens);
alog.log(`OAuth2 client credentials set for user ${userId}.`);
return oAuth2Client;
} catch (error) {
alog.error(`Error during OAuth2 authorization for user ${userId}:`, error);
return null;
}
}
function checkCredientials() {
if (!clientId || !clientSecret) {
alog.error("YouTube API client ID or secret is not set in the configuration.");
return false;
}
return true;
}
module.exports = {
createAuthUrl,
getAuthorization,
getOAuth2Client: (userId) => oAuth2Map.get(userId),
oAuth2Map
};
const SCOPES = ['https://www.googleapis.com/auth/youtube.readonly'];

View File

@@ -0,0 +1,62 @@
const { google } = require('googleapis');
const { LogType } = require('loguix');
const alog = new LogType("YoutubeAPI");
const OAuth2 = require('./OAuth2');
const Users = require('../../server/auth/User');
async function getYoutubePlaylists(userId) {
const user = Users.getUserById(userId);
if (!user) {
alog.error(`User with ID ${userId} not found.`);
return null;
}
const oAuth2Client = OAuth2.getOAuth2Client(userId);
if (!oAuth2Client) {
alog.error(`OAuth2 client for user ${userId} not found. Please authenticate first.`);
return null;
}
const youtube = google.youtube({ version: 'v3', auth: oAuth2Client });
try {
const response = await youtube.playlists.list({
part: 'snippet,contentDetails',
mine: true,
maxResults: 50
});
alog.log(`Retrieved playlists for user ${userId}.`);
return response.data.items;
} catch (error) {
alog.error(`Error retrieving playlists for user ${userId}:`, error);
return null;
}
}
function getYoutubePlaylistSongs(playlistId, userId) {
const user = Users.getUserById(userId);
if (!user) {
alog.error(`User with ID ${userId} not found.`);
return null;
}
const oAuth2Client = OAuth2.getOAuth2Client(userId);
if (!oAuth2Client) {
alog.error(`OAuth2 client for user ${userId} not found. Please authenticate first.`);
return null;
}
const youtube = google.youtube({ version: 'v3', auth: oAuth2Client });
return youtube.playlistItems.list({
part: 'snippet',
playlistId: playlistId,
maxResults: 50
});
}
module.exports = {
getYoutubePlaylists,
getYoutubePlaylistSongs
};

54
src/playlists/History.js Normal file
View File

@@ -0,0 +1,54 @@
const {LogType} = require("loguix")
const hlog = new LogType("PersonalHistory")
const {__glob} = require("../utils/GlobalVars")
const { Database } = require("../utils/Database/Database")
const historyDb = new Database("history", __glob.HISTORY_DB, {})
historyDb.load()
/**
* @param {string} userId
* @returns {Array<Object>}
* @description Renvoie l'historique personnel de l'utilisateur
*/
function getPersonalHistory(userId) {
if (historyDb.data[userId]) {
return historyDb.data[userId];
} else {
hlog.log(`Création d'une clé pour l'utilisateur : ${userId}`);
historyDb.data[userId] = [];
historyDb.save();
return historyDb.data[userId];
}
}
/**
* @param {string} userId
* @param {Object} entry
* @description Ajoute une entrée à l'historique personnel de l'utilisateur
*/
function addToPersonalHistory(userId, entry) {
hlog.log(`Ajout d'une entrée à l'historique personnel de l'utilisateur : ${userId}`);
const history = getPersonalHistory(userId);
// Limit to 25 entries
if (history.length >= 25) {
history.shift();
}
history.push(entry)
historyDb.save();
}
/**
* @param {string} userId
* @description Vide l'historique personnel de l'utilisateur
*/
function clearPersonalHistory(userId) {
hlog.log(`Vidage de l'historique personnel de l'utilisateur : ${userId}`);
historyDb.data[userId] = [];
historyDb.save();
}
module.exports = {
getPersonalHistory,
addToPersonalHistory,
clearPersonalHistory
};

36
src/playlists/Playlist.js Normal file
View File

@@ -0,0 +1,36 @@
const { getReadableDuration } = require("../utils/TimeConverter");
class Playlist {
title = "Aucun titre";
id;
url;
author = "Auteur inconnu";
authorId;
songs = new Array();
thumbnail = "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png" ;
duration = 0;
readduration;
description;
form = "PLAYLIST";
type;
constructor(title, url, author, authorId, songs, thumbnail, duration, readduration, description) {
this.title = title;
this.url = url;
this.author = author;
this.authorId = authorId;
this.songs = songs || new Array();
this.thumbnail = thumbnail;
// Make the some of durations of the songs
if(this.songs.length > 0) {
this.duration = this.songs.reduce((acc, song) => acc + song.duration, 0);
this.readduration = getReadableDuration(this.duration);
}
this.description = description;
if(!this.url) {
this.type = "playlist";
}
}
}
module.exports = {Playlist};

View File

@@ -0,0 +1,278 @@
const {Database} = require('../utils/Database/Database');
const {__glob} = require('../utils/GlobalVars');
const {Playlist} = require('./Playlist');
const {LogType} = require('loguix');
const clog = new LogType("PlaylistManager");
const Finder = require('../player/Finder');
const spotify = require('../media/SpotifyInformation');
const { getYoutubePlaylistSongs } = require('./Google/YoutubeList');
const { auth } = require('googleapis/build/src/apis/abusiveexperiencereport');
const { getReadableDuration } = require('../utils/TimeConverter');
const { getSecondsFromUrl } = require('../media/YoutubeInformation');
const playlistDB = new Database("Playlists", __glob.PLAYLISTFILE, {});
/**
* @param {string} id
* @param {string} name
* @returns {Array<Playlist>}
* @description Renvoie la liste des playlists de l'utilisateur
*/
function getPlaylistsOfUser(id) {
if (playlistDB.data[id]) {
return playlistDB.data[id];
} else {
// Creaete a key with the user id and an empty array
playlistDB.data[id] = new Array();
clog.log(`Création d'une clé pour l'utilisateur : ${id}`);
playlistDB.save();
return playlistDB.data[id];
}
}
/**
* @param {string} id
* @param {string} name
* @returns {Playlist}
*/
function getPlaylistOfUser(id, name) {
const playlists = getPlaylistsOfUser(id);
const playlist = playlists.find(p => p.title === name);
if (!playlist) {
clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${id}`);
return null;
}
return playlist;
}
async function addPlaylist(id, name, url) {
const playlists = getPlaylistsOfUser(id);
var playlist = new Playlist(name, url);
if (playlists.find(p => p.title === name)) {
clog.warn(`La playlist ${name} existe déjà pour l'utilisateur ${id}`);
return;
}
var failed;
if(url) {
await Finder.search(url, false, "PLAYLIST").then(async (playlistFounded) => {
if(!playlistFounded) {
failed = true;
}
if(playlistFounded instanceof Playlist) {
playlist = playlistFounded;
}
if(playlist.type === "spotify") {
playlist.songs = await spotify.getTracks(playlist);
}
})
}
if(failed) {
clog.error(`Impossible de trouver la playlist ${name} pour l'utilisateur ${id}`);
return null;
}
playlists.push(playlist);
playlistDB.save();
clog.log(`Ajout de la playlist ${name} pour l'utilisateur ${id}`);
return playlist;
}
function removePlaylist(id, name) {
const playlists = getPlaylistsOfUser(id);
const index = playlists.findIndex(p => p.title === name);
if (index === -1) {
clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${id}`);
return;
}
playlists.splice(index, 1);
playlistDB.save();
clog.log(`Suppression de la playlist ${name} pour l'utilisateur ${id}`);
}
function getPlaylist(id, name) {
const playlists = getPlaylistsOfUser(id);
const playlist = playlists.find(p => p.title === name);
if (!playlist) {
clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${id}`);
return null;
}
return playlist;
}
function copyPlaylist(fromId, toId, name) {
const playlists = getPlaylistsOfUser(fromId);
const playlist = playlists.find(p => p.title === name);
if (!playlist) {
clog.warn(`La playlist ${name} n'existe pas pour l'utilisateur ${fromId}`);
return null;
}
const toPlaylists = getPlaylistsOfUser(toId);
// Check if the playlist already exists in the target user
if (toPlaylists.find(p => p.title === name)) {
clog.warn(`La playlist ${name} existe déjà pour l'utilisateur ${toId}`);
return null;
}
toPlaylists.push(playlist);
playlistDB.save();
clog.log(`Copie de la playlist ${name} de l'utilisateur ${fromId} vers l'utilisateur ${toId}`);
return false;
}
function renamePlaylist(id, oldName, newName) {
const playlists = getPlaylistsOfUser(id);
const playlist = playlists.find(p => p.title === oldName);
if (!playlist) {
clog.warn(`La playlist ${oldName} n'existe pas pour l'utilisateur ${id}`);
return null;
}
// Check if the new name already exists
if (playlists.find(p => p.title === newName)) {
clog.warn(`La playlist ${newName} existe déjà pour l'utilisateur ${id}`);
return null;
}
playlist.title = newName;
playlistDB.save();
clog.log(`Renommage de la playlist ${oldName} en ${newName} pour l'utilisateur ${id}`);
}
function addSong(id, playlistName, song) {
if(typeof song === "string") {
try {
song = JSON.parse(song)
} catch (e) {
clog.error(`La chanson ${song} n'est pas valide`);
return null;
}
}
// Check if the song is a valid object
if (typeof song !== 'object' || !song) {
clog.error(`La chanson ${song} n'est pas valide`);
return null;
}
const playlists = getPlaylistsOfUser(id);
const playlist = playlists.find(p => p.title === playlistName);
if (!playlist) {
clog.warn(`La playlist ${playlistName} n'existe pas pour l'utilisateur ${id}`);
return null;
}
// Check the integrity of the song
if (!song.id || !song.title || !song.url) {
clog.error(`La chanson ${song.title} n'est pas valide`);
return null;
}
playlist.songs.push(song);
playlistDB.save();
clog.log(`Ajout de la chanson ${song.title} à la playlist ${playlistName} pour l'utilisateur ${id}`);
}
function removeSong(id, playlistName, songId) {
const playlists = getPlaylistsOfUser(id);
const playlist = playlists.find(p => p.title === playlistName);
if (!playlist) {
clog.warn(`La playlist ${playlistName} n'existe pas pour l'utilisateur ${id}`);
return null;
}
const index = playlist.songs.findIndex(s => s.id === songId);
if (index === -1) {
clog.warn(`La chanson ${songId} n'existe pas dans la playlist ${playlistName} pour l'utilisateur ${id}`);
return null;
}
playlist.songs.splice(index, 1);
playlistDB.save();
clog.log(`Suppression de la chanson ${songId} de la playlist ${playlistName} pour l'utilisateur ${id}`);
}
async function processYoutubeData(userId, data) {
if (!data || data.length === 0) {
clog.warn(`Aucune donnée YouTube trouvée pour l'utilisateur ${userId}`);
return [];
}
const playlists = [];
for (const item of data) {
if (item.snippet && item.contentDetails) {
const playlist = new Playlist();
playlist.id = item.id;
playlist.title = item.snippet.title;
playlist.url = `https://www.youtube.com/playlist?list=${item.id}`;
playlist.description = item.snippet.description || "Aucune description disponible";
playlist.author = item.snippet.channelTitle;
playlist.thumbnail = item.snippet.thumbnails.default.url;
playlist.authorId = `https://www.youtube.com/channel/${item.snippet.channelId}`;
playlist.songs = []; // You can fetch songs later if needed
await getYoutubePlaylistSongs(item.id, userId).then(songsData => {
if (songsData && songsData.data && songsData.data.items) {
playlist.songs = songsData.data.items.map(song => ({
id: song.snippet.resourceId.videoId,
title: song.snippet.title,
author: song.snippet.videoOwnerChannelTitle,
authorId: `https://www.youtube.com/channel/${song.snippet.videoOwnerChannelId}`,
url: `https://www.youtube.com/watch?v=${song.snippet.resourceId.videoId}`,
thumbnail: song.snippet?.thumbnails?.default?.url || "https://radomisol.fr/wp-content/uploads/2016/08/cropped-note-radomisol-musique.png",
}));
// Add readduration for every items in songs
} else {
clog.warn(`Aucune chanson trouvée pour la playlist ${item.id}`);
}
}).catch(err => {
clog.error(`Erreur lors de la récupération des chansons pour la playlist ${item.id}:`, err);
});
for (const song of playlist.songs) {
// If authorId is not defined, delete the song
if (song.authorId == "https://www.youtube.com/channel/undefined") {
clog.warn(`L'auteur de la chanson ${song.title} (${song.id}) n'est pas défini. Suppression de la chanson.`);
playlist.songs.splice(playlist.songs.indexOf(song), 1);
continue; // Skip this song
}
song.duration = await getSecondsFromUrl(song.url);
if (song.duration === null) {
clog.warn(`Impossible de récupérer la durée de la chanson ${song.title} (${song.id})`);
song.duration = 0; // Set to 0 if duration cannot be fetched
} else {
song.readduration = getReadableDuration(song.duration);
playlist.duration += song.duration; // Initialize duration if not set
}
}
playlist.readduration = getReadableDuration(playlist.duration);
playlist.type = "youtube";
playlists.push(playlist);
} else {
clog.warn(`Données YouTube manquantes pour l'élément ${item.id}`);
}
};
clog.log(`Traitement des données YouTube pour l'utilisateur ${userId} terminé. Nombre de playlists trouvées : ${playlists.length}`);
// Save the playlists to the user's playlist collection
const userPlaylists = getPlaylistsOfUser(userId);
// Remove existing playlists with the same IDs to avoid duplicates
for (const playlist of playlists) {
const existingIndex = userPlaylists.findIndex(p => p.id === playlist.id);
if (existingIndex !== -1) {
userPlaylists.splice(existingIndex, 1); // Remove existing playlist with the same ID
}
}
userPlaylists.push(...playlists);
playlistDB.save();
clog.log(`Playlists ajoutées pour l'utilisateur ${userId}. Nombre total de playlists : ${userPlaylists.length}`);
return playlists;
}
module.exports = {
getPlaylistsOfUser,
getPlaylistOfUser,
addPlaylist,
removePlaylist,
getPlaylist,
copyPlaylist,
renamePlaylist,
addSong,
removeSong,
processYoutubeData
}