const { __glob, __web } = require("./global-variables"); const { LogType } = require("loguix"); const log = require("loguix"); const auth = require("./sub-auth"); const cook = require("cookie") const wlog = new LogType("Web") const subplayer = require('./sub-player'); const { List } = require("./sub-list") const subplaylist = require("./sub-playlist") const nodesfinder = require("./nodes-finder") const { Server } = require("socket.io") var fs = require("fs") var path = require("path") const { Metric } = require("webmetrik"); const markdownit = require("markdown-it")({ html: true, linkify: true, typographer: true }) const Lyrics = require('song-lyrics-api'); const lyrics = new Lyrics(); module.exports.WebServer = class { constructor() { wlog.step.init("start_server", "Démarrage du serveur Express (Web)") init() } } function init() { const createError = require('http-errors'); const express = require('express'); const cookieParser = require('cookie-parser'); const favicon = require('express-favicon'); const app = express(); const port = normalizePort(process.env.PORT || '4000'); const server = require('http').createServer(app); const io = require('socket.io')(server) const indexRouter = require(__web.ROUTER + "index.js"); const loginRouter = require(__web.ROUTER + "login.js"); const internalRouter = require(__web.ROUTER + "internal.js") IOConnection(io) app.set('views', __web.TEMPLATES); // general config app.set('view engine', 'ejs'); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(__web.PUBLIC)); app.set('port', port); app.use('/', indexRouter); app.use('/login', loginRouter); app.use("/internal", internalRouter) app.use("/userspictures", express.static(__glob.PICTURE_DIR)) app.use(favicon(__web.ICON)); app.use(function (req, res, next) { next(createError(404)); }); 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'); }); server.listen(port); server.on('error', (error) => { if (error.syscall !== 'listen') { throw error; } let 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; } }); server.on('listening', () => { let addr = server.address(); let bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; wlog.log("Serveur démarré sur le port : " + bind ) }); wlog.step.end("start_server") } // EXPRESS FUNCTIONS function normalizePort(val) { let port = parseInt(val, 10); if (isNaN(port)) { // Named pipe return val; } if (port >= 0) { // Port number return port; } return false; } /** * * @param {Server} io */ function IOConnection(io) { /** * @type {LogType} */ const alog = log.getInstance("Authentification") const usersOnline = new Array() process.on("UPDATE_SELF", () => { if(io) { AlwaysRequest("UPDATE_SELF") AlwaysRequest("ALL_CONNECTED_USER", usersOnline) } }) process.on("UPDATE_NODES", () => { if(io) { const data = nodesfinder.getNodesData() AdminRequest("NODES", data) } }) process.on("PLAYLIST_REFRESH", () => { if(io) { AlwaysRequest("PLAYLIST_REFRESH", "OK") } }) process.on("MUSIC_UPDATE_STATE", () => { if(io) { const data = subplayer.updateMusicState() AlwaysRequest("MUSIC_STATE", data) } }) io.on("connection", (socket) => { var token = cook.parse(socket.handshake.headers.cookie).token var user = auth.getUser(token) if(user) { if(user.admin == true) { socket.join("admin") } const online_users_data = auth.getSimpleUser(token) if(online_users_data) { // Check if user is already connected and if not push it if(!usersOnline.find(item => item.username == online_users_data.username)) { usersOnline.push(online_users_data) } alog.log(online_users_data.username + " - Connexion du serveur avec le socket : " + socket.id + " - " + socket.handshake.address + " - " + socket.handshake.headers["user-agent"]) } AlwaysRequest("ALL_CONNECTED_USER", usersOnline) socket.on("disconnect", () => { alog.log(user.user.username + " - Déconnexion du serveur - Socket : " + socket.id) socket.leave("admin") var index = usersOnline.indexOf(online_users_data) if(index > -1) { usersOnline.splice(index, 1) } AlwaysRequest("ALL_CONNECTED_USER", usersOnline) }) GetRequest("USER_INFO", () => { GetAnswer("USER_INFO", user) alog.log("Envoi des informations Discord de '" + user.user.username + "' à '" + socket.id + "'" ) }) GetRequest("USER_LIST", () => { GetAnswer("USER_LIST", auth.getSimpleUsers()) }) GetRequest('MUSIC_STATE', () => { const data = subplayer.updateMusicState() GetAnswer("MUSIC_STATE", "Bienvenue " + user.user.username + " ! " ) AlwaysRequest("MUSIC_STATE", data) }) GetRequest("PLAYLIST", () => { const playlistResult = subplaylist.getUser(user.user.id) GetAnswer("PLAYLIST", playlistResult) }) GetRequest("PAUSE", () => { subplayer.pause() GetAnswer("PAUSE", "OK") }) GetRequest("BACKWARD", () => { subplayer.previous() GetAnswer("BACKWARD", "OK") }) GetRequest("FORWARD", () => { subplayer.skip() GetAnswer("FORWARD", "OK") }) GetRequest("LOOP", () => { subplayer.loop() GetAnswer("LOOP", "OK") }) GetRequest("SHUFFLE", () => { subplayer.changeShuffle() GetAnswer("SHUFFLE", "OK") }) GetRequest("DISCONNECT", () => { subplayer.leave() GetAnswer("DISCONNECT", "OK") }) GetRequest("NODES", () => { process.emit("UPDATE_NODES") GetAnswer("NODES", "OK") }) GetRequest("ALL_CONNECTED_USER", () => { GetAnswer("ALL_CONNECTED_USER", "OK") AlwaysRequest("ALL_CONNECTED_USER", usersOnline) }) GetRequest("README", () => { var content = markdownit.render(fs.readFileSync(__glob.README).toString()) GetAnswer("README", content) }) GetRequest("OOBE_CHECK", () => { GetAnswer("OOBE_CHECK", auth.getOOBE(user.user.id)) }) GetRequest("OOBE_VALID", () => { auth.setOOBE(user.user.id) GetAnswer("OOBE_VALID", "OK") }) GetRequest("MOVEOUT", () => { subplayer.moveOut(user.user.id) GetAnswer("MOVEOUT", "OK") }) PostRequest("SEEK", (data) => { subplayer.seek(data) PostAnswer("SEEK", "OK") }) PostRequest("VOLUME", (data) => { subplayer.setVol(data) PostAnswer("VOLUME", "OK") }) PostRequest("PLAY_QUEUE", (data) => { var sublist = new List() subplayer.addSong(sublist.playQueue(data), user.user.id, true) PostAnswer("PLAY_QUEUE", "OK") }) PostRequest("CHANGE_QUEUE", (data) => { var sublist = new List() sublist.changeQueue(data) PostAnswer("CHANGE_QUEUE", "OK") }) PostRequest("DELETE_QUEUE", (data) => { var sublist = new List() sublist.removeByIndex(data) PostAnswer("DELETE_QUEUE", "OK") }) PostRequest("DELETE_ALL_QUEUE", () => { var sublist = new List() sublist.removeAll() PostAnswer("DELETE_QUEUE", "OK") }) PostRequest("MOVE_QUEUE", (data) => { var sublist = new List() sublist.moveUp(data) PostAnswer("MOVE_QUEUE", "OK") }) PostRequest("MOVE_QUEUE_BY_ENTIRE", (data) => { var sublist = new List() sublist.replaceList(data) PostAnswer("MOVE_QUEUE_BY_ENTIRE", "OK") }) PostRequest("REPORT", (data) => { data.username = user subplayer.report(null, null, data) PostAnswer("REPORT", "OK") }) PostRequest("SEARCH", async (data) => { const results = await subplayer.search(data) PostAnswer("SEARCH", results) }) PostRequest("ADD_SONG", async (data) => { subplayer.addSong(data, user.user.id) PostAnswer("ADD_SONG", "OK") playStats(user.user.username) }) PostRequest("ADD_SONG_NOW", async (data) => { subplayer.addSong(data, user.user.id, true) PostAnswer("ADD_SONG_NOW", "OK") playStats(user.user.username) }) PostRequest("FP_ADD_SONG", async (data) => { subplayer.addSong(data, user.user.id, false, true) PostAnswer("FP_ADD_SONG", "OK") playStats(user.user.username) }) PostRequest("FP_ADD_SONG_NOW", async (data) => { subplayer.addSong(data, user.user.id, true, true) PostAnswer("FP_ADD_SONG_NOW", "OK") playStats(user.user.username) }) PostRequest("PLAY_PLAYLIST", async (data) => { subplaylist.playPlaylist(user.user.id, data) PostAnswer("PLAY_PLAYLIST", "OK") playStats(user.user.username) }) PostRequest("PLAY_PLAYLIST_NOW", async (data) => { subplaylist.playPlaylist(user.user.id, data, true) PostAnswer("PLAY_PLAYLIST_NOW", "OK") playStats(user.user.username) }) PostRequest("CREATE_PLAYLIST", async (data) => { subplaylist.addPlaylist(user.user.id, data) socket.emit("DO_UPDATE_PLAYLIST") PostAnswer("CREATE_PLAYLIST", "OK") }) PostRequest("DELETE_PLAYLIST", async (data) => { subplaylist.removePlaylist(user.user.id, data) socket.emit("DO_UPDATE_PLAYLIST") PostAnswer("DELETE_PLAYLIST", "OK") }) PostRequest("SEND_PLAYLIST", async (data) => { subplaylist.copyPlaylist(user.user.id, data.key, data.dest) socket.emit("DO_UPDATE_PLAYLIST") PostAnswer("SEND_PLAYLIST", "OK") }) PostRequest("RENAME_PLAYLIST", async (data) => { subplaylist.renamePlaylist(user.user.id, data.id, data.name) socket.emit("DO_UPDATE_PLAYLIST") PostAnswer("RENAME_PLAYLIST", "OK") }) PostRequest("ADD_SONG_TO_PLAYLIST", async (data) => { subplaylist.addSong(user.user.id, data.id, data.url) socket.emit("DO_UPDATE_PLAYLIST") PostAnswer("ADD_SONG_TO_PLAYLIST", "OK") }) PostRequest("DELETE_SONG_TO_PLAYLIST", async (data) => { subplaylist.removeSong(user.user.id, data.id, data.identifier) socket.emit("DO_UPDATE_PLAYLIST") PostAnswer("DELETE_SONG_TO_PLAYLIST", "OK") }) PostRequest("FP_PLAY_PLAYLIST", async (data) => { subplayer.addSongsFromPlaylist(data, user.user.id) PostAnswer("FP_PLAY_PLAYLIST", "OK") }) PostRequest("FP_PLAY_PLAYLIST_NOW", async (data) => { subplayer.addSongsFromPlaylist(data, user.user.id, true) PostAnswer("FP_PLAY_PLAYLIST", "OK") }) PostRequest("LYRICS", async (data) => { lyrics.getLyrics(data) .then((response) => { PostAnswer("LYRICS", response[0].lyrics) }) .catch((error) => { PostAnswer("LYRICS", null) }) }) if(user.admin == true) { GetRequest("LOGS", () => { const logs_data = new Array() const logs_folder = fs.readdirSync(__glob.LOGS) for(var log of logs_folder) { logs_data.push({"name":log, "value": fs.readFileSync(__glob.LOGS + path.sep + log).toString()}) } GetAnswer("LOGS", logs_data) }) GetRequest("USERS", () => { const users_data = auth.getUsers() AdminRequest("USERS", users_data) }) PostRequest("NODES/ADD", (data) => { nodesfinder.addNodes(data) PostAnswer("NODES/ADD", "OK") process.emit("UPDATE_NODES") }) PostRequest("NODES/DELETE", (data) => { nodesfinder.deleteNode(data) PostAnswer("NODES/DELETE", "OK") process.emit("UPDATE_NODES") }) PostRequest("NODES/RELOAD", (data) => { nodesfinder.reloadNode(data) PostAnswer("NODES/RELOAD", "OK") process.emit("UPDATE_NODES") }) PostRequest("USERS/ADMIN", (data) => { auth.setAdmin(data) PostAnswer("USERS/ADMIN", "OK") const users_data = auth.getUsers() AdminRequest("USERS", users_data) }) PostRequest("USERS/BAN", (data) => { auth.setBan(data) PostAnswer("USERS/BAN", "OK") const users_data = auth.getUsers() AdminRequest("USERS", users_data) }) PostRequest("USERS/DELETE", (data) => { auth.removeUser(data) PostAnswer("USERS/DELETE", "OK") const users_data = auth.getUsers() AdminRequest("USERS", users_data) }) GetRequest("RESTART", () => { const pm2 = require('pm2'); pm2.restart('Subsonics') GetAnswer("RESTART", "OK") }) } } function GetRequest(GQname, GQcallback) { socket.on("GET/" + GQname, () => { wlog.log(user.user.username + " - Socket : " + socket.id + " - GET/" + GQname + " - [RECIEVED]") GQcallback() }) } function GetAnswer(GRname, GRvalue) { wlog.log(user.user.username + " - Socket : " + socket.id + " - GET/" + GRname + " - [ANSWERED]") socket.emit("ANSWER/GET/" + GRname, GRvalue) } function PostRequest(GQname, GQcallback) { socket.on("POST/" + GQname, (value) => { wlog.log(user.user.username + " - Socket : " + socket.id + " - POST/" + GQname + " - [RECIEVED]") GQcallback(value) }) } function PostAnswer(GRname, GRvalue) { wlog.log(user.user.username + " - Socket : " + socket.id + " - POST/" + GRname + " - [ANSWERED]") socket.emit("ANSWER/POST/" + GRname, GRvalue) } }) function AlwaysRequest(GRname, GRvalue) { io.sockets.emit("ALWAYS/" + GRname, GRvalue) } function AdminRequest(GRname, GRvalue) { io.to("admin").emit("ALWAYS/" + GRname, GRvalue) } function playStats(username) { var userMusicPlayed = new Metric("userMusicPlayed_" + username, "Nombre de musiques jouées par l'utilisateur : " + username) userMusicPlayed.setValue(userMusicPlayed.getValue() + 1) } }