Vesion 0.1.0 - Initial Version with First Commands

This commit is contained in:
Raphix
2023-08-20 17:00:50 +02:00
parent 8739acb402
commit f80e23827a
22 changed files with 4114 additions and 9 deletions

241
src/modules/discord-bot.js Normal file
View File

@ -0,0 +1,241 @@
const { Client, GatewayIntentBits, Collection, ActivityType, REST, Routes } = require("discord.js")
const fs = require("node:fs")
const path = require("path")
const { Manager } = require("erela.js")
const { __glob } = require("./global-variables")
const { LogType } = require("../modules/sub-log")
const { List } = require("./sub-list")
const nodeFinder = require("./nodes-finder")
const { platform } = require("node:os")
const client = new Client({
intents:[GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates, GatewayIntentBits.GuildMembers],
})
const membersVoices = new Map()
module.exports.DiscordBot = class {
constructor(config, dlog) {
dlog.step.init("d_init", "Démarrage du Bot Discord")
init(dlog, config)
}
}
function init(dlog, config) {
client.commands = new Collection()
client.dictator = false;
dlog.step.init("d_get_commands", "Récupération des commandes en local depuis : " + __glob.COMMANDS)
const commands = [];
// Grab all the command files from the commands directory you created earlier
const commandsPath = __glob.COMMANDS
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const command = require(commandsPath + path.sep + file);
client.commands.set(command.data.name, command)
commands.push(command.data.toJSON());
}
dlog.step.end("d_get_commands")
const rest = new REST().setToken(config.token);
(async () => {
try {
dlog.step.init("d_commands_refresh", `Refreshing ${commands.length} application (/) commands.`);
// The put method is used to fully refresh all commands in the guild with the current set
const data = await rest.put(
Routes.applicationGuildCommands("1094727789682380922", "137291455336022018"),
{ body: commands },
);
dlog.log("COMMANDS : Sended to Discord [" + data.length + "]")
dlog.step.end("d_commands_refresh")
} catch (error) {
// And of course, make sure you catch and log any errors!
dlog.error(error)
}
})();
rest.on("rateLimited", (datawarn) => {
dlog.warn("REST - Limite de requête atteinte ! TimeToReset : " + datawarn.timeToReset);
})
client.once("ready", () => {
dlog.log("Connexion au Bot Discord réussi ! Connecté à : " + client.user.username + "#" + client.user.discriminator)
client.user.setPresence({
activities: [{ name: `toutes les musiques possible !`, type: ActivityType.Listening }],
status: 'online',
});
client.manager.init(client.user.id);
const commandManager = client.application.commands;
if (!commandManager) {
dlog.error('Command manager not available.');
} else {
commandManager.set([]);
}
dlog.step.end("d_init")
})
client.on("interactionCreate", (interaction) => {
if(!interaction.isCommand()) return;
const command = client.commands.get(interaction.commandName)
try {
dlog.log(interaction.member.user.username + "-> /" + interaction.commandName)
command.execute(client, interaction)
} catch(error) {
dlog.error(interaction.member.user.username + "-> /" + interaction.commandName + " : ERREUR RENCONTRE")
dlog.error(error)
interaction.reply({content:"Erreur lors de l'éxécution de la commande !", ephemeral: true})
}
})
startErelaManager(dlog, config)
client.login(config.token)
}
function startErelaManager(dlog, config) {
const elog = new LogType("Lavalink-Manager")
client.on("voiceStateUpdate", (oldMember, newMember) => {
membersVoices.set(newMember.id, newMember.channelId)
let player = client.manager.players.get(oldMember.guild.id)
if(player) {
client.channels.fetch(player.options.voiceChannel).then(channel => {
if(channel.members.size <= 1) {
player.destroy()
}
})
}
})
const nodes = nodeFinder.getNodes()
client.manager = new Manager({
// The nodes to connect to, optional if using default lavalink options
nodes,
// Method to send voice data to Discord
send: (id, payload) => {
const guild = client.guilds.cache.get(id);
// NOTE: FOR ERIS YOU NEED JSON.stringify() THE PAYLOAD
if (guild) guild.shard.send(payload);
}
});
const plog = new LogType("Lavalink-Player")
client.on("voiceStateUpdate", (oldMember, newMember) => {
membersVoices.set(newMember.id, newMember.channelId)
let player = client.manager.players.get(oldMember.guild.id)
if(player) {
client.channels.fetch(player.options.voiceChannel).then(channel => {
if(channel.members.size <= 1) {
player.destroy()
plog.log("[Automatic Task] Player supprimé dans : " + channel.name)
}
})
}
})
const list = new List()
client.manager.on("playerCreate", (player) => {
client.channels.fetch(player.options.voiceChannel).then(channel => {
plog.log("Nouveau Player instancié dans : " + channel.name)
})
})
client.manager.on("playerDestroy", (player) => {
list.destroy()
client.channels.fetch(player.options.voiceChannel).then(channel => {
plog.log("Player supprimé dans : " + channel.name)
})
})
client.manager.on("trackStart", (song) => {
plog.log("Lecture de '" + song.queue.current.title + "' de '" + song.queue.current.author + "'")
list.setCurrent(song)
})
client.manager.on("queueEnd", () => {
let player = client.manager.players.get("137291455336022018")
if(player) {
list.passCurrent()
if(list.haveSongs()) {
player.play(list.next())
}
}
})
// Emitted whenever a node connects
client.manager.on("nodeConnect", node => {
elog.log(`Connecté au serveur Lavalink : "${node.options.identifier}"` )
})
// Emitted whenever a node encountered an error
client.manager.on("nodeError", (node, error) => {
elog.warn(`Node "${node.options.identifier}" encountered an error: ${error.message}.`)
})
// THIS IS REQUIRED. Send raw events to Erela.js
client.on("raw", d => client.manager.updateVoiceState(d));
}

View File

@ -0,0 +1,19 @@
const path = require("path")
const root = path.resolve(__dirname, '../../')
const __glob = {
CONFIG: root + path.sep + "data" + path.sep + "config.json",
ROOT: root,
WEB: root + path.sep + "src" + path.sep + "web",
COMMANDS: root + path.sep + "src" + path.sep + "commands",
SUBLOG: root + path.sep + "src" + path.sep + "modules" + path.sep + "sub-log.js",
SUBPLAYER: root + path.sep + "src" + path.sep + "modules" + path.sep + "sub-player.js",
SUBLIST: root + path.sep + "src" + path.sep + "modules" + path.sep + "sub-list.js",
PACKAGE: root + path.sep + "package.json",
DATA: root + path.sep + "data" + path.sep,
NODES: root + path.sep + "data" + path.sep + "nodes.json",
};
module.exports = { __glob };

View File

@ -0,0 +1,85 @@
const { __glob } = require("./global-variables")
const { LogType } = require("../modules/sub-log")
const fs = require("fs")
const nlog = new LogType("Node-Finder")
module.exports.getNodes = function () {
nlog.step.init("find_nodes", "Récupération des nodes de la base de donnée")
if(fs.existsSync(__glob.NODES)) {
try {
var nodes_data = JSON.parse(fs.readFileSync(__glob.NODES))
const nodes_array = new Array()
for(node of nodes_data) {
nodes_array.push(node)
}
nlog.log("Récupération de " + nodes_array.length + " nodes dans la base de donnée !")
nlog.step.end("find_nodes")
return nodes_array
} catch(error) {
nlog.step.error("find_nodes", error)
}
} else {
nlog.warn("Fichier de configuration introuvable !")
try {
var nodes = {}
fs.writeFileSync(__glob.NODES, JSON.stringify(nodes, null, 2))
nlog.log("Création d'un fichier de base de donnée de nodes ! Redémarrage de l'application nécéssaire !")
nlog.step.error("find_nodes", "Redémarrage requis pour lire la nouvelle base de donnée des nodes !")
process.exit(0)
} catch(error) {
nlog.step.error("find_nodes", "Tentative de création du fichier de base de donnée nodes échoué !" + error)
}
}
}
module.exports.addNodes = function (data) {
nlog.step.init("add_nodes", "Ajout d'un nouveau noeud dans la base de donnée de nodes : " + data.host)
if(fs.existsSync(__glob.NODES)) {
try {
var nodes_data = JSON.parse(fs.readFileSync(__glob.NODES))
nodes_data.push(data)
fs.writeFileSync(__glob.NODES, JSON.stringify(nodes_data, null, 2))
nlog.step.end("add_nodes")
} catch(error) {
nlog.step.error("add_nodes", error)
}
} else {
nlog.warn("Fichier de configuration introuvable !")
try {
var nodes = []
fs.writeFileSync(__glob.NODES, JSON.stringify(nodes, null, 2))
nlog.log("Création d'un fichier de base de donnée de nodes ! Redémarrage de l'application nécéssaire !")
nlog.step.error("add_nodes", "Redémarrage requis pour lire la nouvelle base de donnée des nodes !")
process.exit(0)
} catch(error) {
nlog.step.error("add_nodes", "Tentative de création du fichier de nodes échoué !" + error)
}
}
}

182
src/modules/sub-list.js Normal file
View File

@ -0,0 +1,182 @@
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
const { __glob } = require("../modules/global-variables");
const { LogType } = require('./sub-log');
const dlog = new LogType("Queue-List")
var next = new Array()
var previous = new Array()
var current = null;
module.exports.List = class {
constructor() {
}
destroy() {
next = new Array()
previous = new Array()
current = null
}
next() {
const song = next[0]
next.splice(0, 1)
return song
}
previous() {
const song = previous[0]
previous.splice(0, 1)
return song
}
setCurrent(song) {
current = song.queue.current
}
passCurrent() {
previous.unshift(current)
}
haveSongs() {
if(next.length == 0) {
return false
} else{
return true
}
}
havePreviousSongs() {
if(previous.length == 0) {
return false
} else{
return true
}
}
getNextSong() {
const song = next[0]
return song
}
async add(song, interaction) {
dlog.log("Ajout d'un titre dans la liste de lecture : '" + song.title + "' de '" + song.author + "'")
next.push(song)
if(interaction) {
const embed = await new EmbedBuilder()
.setColor(0x15e6ed)
.setTitle('**Ajout dans la liste de lecture **: ' + song.title)
.setDescription('**Demandé par **' + interaction.member.user.username)
.addFields({name: "Auteur", value: song.author},
{name: "URL", value: song.uri})
.setThumbnail(song.thumbnail)
.setTimestamp();
interaction.reply({embeds: [embed]})
}
}
remove(song) {
dlog.log("Supression d'un titre dans la liste de lecture : '" + song.title + "' de '" + song.author + "'")
const index = next.indexOf(song)
next.splice(index, 1)
}
__previous_add(song) {
previous.unshift(song)
}
__previous_remove(song) {
const index = next.indexOf(song)
previous.splice(index, 1)
}
__next_add(song) {
next.unshift(song)
}
async playlistAdd(playlist, interaction) {
if(interaction) {
const client = interaction.client
var author = "Artiste inconnu !"
if(typeof playlist.author != "undefined" ) {
author == playlist.author.name
}
const embed = await new EmbedBuilder()
.setColor(0x15e6ed)
.setTitle('**Lecture de la playlist : **' + playlist.title)
.setDescription('**Demandé par **' + interaction.member.user.username)
.addFields({name: "Auteur", value: author},
{name: "URL", value:playlist.url},
{name: "Nombre de videos", value:playlist.video_count + " vidéos"})
.setThumbnail(playlist.thumbnail_url)
.setTimestamp();
interaction.reply({embeds: [embed]})
let player = client.manager.players.get(interaction.guild.id)
for(var song of playlist.videos) {
const song_finded = await client.manager.search(song.url)
next.push(song_finded.tracks[0])
}
if(!player.playing) {
player.play(next[0])
this.remove(next[0])
}
} else {
// [A FINIR POUR WEB]
}
}
}

217
src/modules/sub-log.js Normal file
View File

@ -0,0 +1,217 @@
const fs = require("fs")
const path = require("path")
var logStream = null
var logInstance = new Map()
setup()
function getDate(formated) {
var date = new Date()
// [Date Format] - Format de la date
var gmonth = date.getMonth()
var gday = date.getDate()
var gHour = date.getHours()
var gMinute = date.getMinutes()
var gSecondes = date.getSeconds()
if(date.getMonth() + 1 <= 10) {
gmonth = "0" + (date.getMonth() + 1)
}
if(date.getDate() + 1 <= 10) {
gday = "0" + date.getDate()
}
if(date.getHours() + 1 <= 10) {
gHour = "0" + date.getHours()
}
if(date.getMinutes() + 1 <= 10) {
gMinute = "0" + date.getMinutes()
}
if(date.getSeconds() + 1 <= 10) {
gSecondes = "0" + date.getSeconds()
}
if(!formated) {
return gday + "/" + gmonth + " - " + gHour + "h" + "-" + gMinute + "m" + "-" + gSecondes + "s"
} else {
return date.getFullYear() + "-" + gmonth + "-" + gday + "-" + gHour + "h" + "-" + gMinute + "m" + "-" + gSecondes + "s"
}
}
function setup() {
if(!fs.existsSync(__dirname + path.sep + "logs" + path.sep)) {
fs.mkdir(__dirname + path.sep + "logs", (err) => {
if(!err) {
console.log("[Logs] - Dossier de logs crée ! !")
} else {
console.log("[Logs] -Erreur d'écriture par manque de permission ")
console.log(err)
}
})
}
logStream = fs.createWriteStream(__dirname + path.sep + "logs" + path.sep + getDate(true) + ".log", {
flags: 'a'
});
logStream.write("[" + require("../../package.json").name + "@"+ require("../../package.json").version + "] - [" + getDate(true) + "]" + "\n")
logStream.write("Subsonics-Web by Raphix" + "\n")
logStream.write("----------------------------------------------------------------" + "\n")
process.on('uncaughtException', (err) => {
console.error("["+ "FATAL" + "] - The application has encountered an error ! Please Restart ! #E = " + err + "\n")
console.error(err)
logStream.write("[" + getDate() + "] - ["+ "FATAL" + "] - The application has encountered an error ! Please Restart ! #E = " + err + "\n")
logStream.end( "["+ "UNCAUGHT_EXCEPTION" + "]" + " - [END OF LOGS] - [" + getDate() + ']')
logStream.close()
});
process.on('beforeExit', (err) => {
logStream.end( "["+ "BEFORE_EXIT" + "]" + " - [END OF LOGS] - [" + getDate(true) + ']')
logStream.close()
});
}
module.exports.getInstance = function (name) {
if(logInstance.has(name)) {
return logInstance.get(name)
} else {
var logtext = "[Logs] - [ERROR] - '" + name + "' n'est pas enregistré en tant qu'instance de log !"
logStream.write("[" + getDate() + "] - " + logtext + "\n")
console.log(logtext)
return null
}
}
module.exports.LogType = class {
constructor(typeName) {
this.type = typeName;
this.steps = new Map()
this.step = this.initializeStep()
logInstance.set(typeName, this)
}
log(txt) {
var logtext = "[" + this.type + "] - [INFO] - " + txt
logStream.write("[" + getDate() + "] - " + logtext + "\n")
console.log(logtext)
}
warn(txt) {
var logtext = "[" + this.type + "] - [WARN] - " + txt
logStream.write("[" + getDate() + "] - " + logtext + "\n")
console.log(logtext)
}
error(txt) {
var logtext = "[" + this.type + "] - [ERROR] - " + txt
logStream.write("[" + getDate() + "] - " + logtext + "\n")
console.log(logtext)
}
initializeStep() {
const parent = this;
return {
init: function(id, desc) {
parent.steps.set(id, desc)
var logtext = "[" + parent.type + "] - [INFO] - [STEP] - " + desc + " - En cours ..."
logStream.write("[" + getDate() + "] - " + logtext + "\n")
console.log(logtext)
},
end: function(id) {
if(parent.steps.has(id)) {
var logtext = "[" + parent.type + "] - [INFO] - [STEP] - " + parent.steps.get(id) + " - Terminé !"
logStream.write("[" + getDate() + "] - " + logtext + "\n")
console.log(logtext)
parent.steps.delete(id)
} else {
var logtext = "[" + parent.type + "] - [WARN] - [STEP] - '" + id + "' n'est pas enregistré en tant qu'étape !"
logStream.write("[" + getDate() + "] - " + logtext + "\n")
console.log(logtext)
}
},
error: function(id, errorDesc) {
if(parent.steps.has(id)) {
var logtext = "[" + parent.type + "] - [ERROR] - [STEP] - " + parent.steps.get(id) + " - Une erreur a été rencontré dans l'étape ! : #E = " + errorDesc
logStream.write("[" + getDate() + "] - " + logtext + "\n")
console.log(logtext)
parent.steps.delete(id)
} else {
var logtext = "[" + parent.type + "] - [WARN] - [STEP] - '" + id + "' n'est pas enregistré en tant qu'étape !"
logStream.write("[" + getDate() + "] - " + logtext + "\n")
console.log(logtext)
}
}
}
}
}

305
src/modules/sub-player.js Normal file
View File

@ -0,0 +1,305 @@
const { SlashCommandBuilder, EmbedBuilder, DefaultWebSocketManagerOptions } = require("discord.js");
const { __glob } = require("../modules/global-variables");
const { LogType } = require("./sub-log");
const { List } = require("./sub-list")
var ytfps = require("ytfps")
const list = new List()
module.exports.play = async function (client, interaction) {
if(interaction) {
let player = client.manager.players.get(interaction.guild.id)
const song_name = interaction.options.getString("nom_ou_lien")
if(!player) {
player = client.manager.create({
guild: interaction.guild.id,
voiceChannel: interaction.member.voice.channel.id,
textChannel: interaction.channel.id,
});
player.connect();
}
// CHECK OF PLAYLIST
var playlist = await checkPlaylist(song_name)
if(playlist) {
list.playlistAdd(playlist, interaction)
} else {
const songs = await client.manager.search(song_name)
if(!player.playing) {
player.play(songs.tracks[0])
const embed = await new EmbedBuilder()
.setColor(0x15e6ed)
.setTitle('**Lecture de : **' + songs.tracks[0].title)
.setDescription('**Demandé par **' + interaction.member.user.username)
.addFields({name: "Auteur", value: songs.tracks[0].author},
{name: "URL", value:songs.tracks[0].uri})
.setThumbnail(songs.tracks[0].thumbnail)
.setTimestamp();
interaction.reply({embeds: [embed]})
} else {
list.add(songs.tracks[0], interaction)
}
}
} else {
// [A FINIR POUR WEB]
}
}
module.exports.pause = function (client, interaction) {
if(interaction) {
let player = client.manager.players.get(interaction.guild.id)
if(player) {
if(player.playing) {
const embed = new EmbedBuilder()
.setColor(0x03ff2d)
.setTitle('Pause !')
.setDescription("**Ok, une entracte est demandée par " + interaction.member.user.username + "**")
.setTimestamp();
interaction.reply({embeds: [embed]})
player.pause(true)
} else {
const embed = new EmbedBuilder()
.setColor(0x03ff2d)
.setTitle('C\'est reparti !')
.setDescription("**Ok, Fin de l'entracte, c'est reparti et c'est demandée par " + interaction.member.user.username + "**")
.setTimestamp();
interaction.reply({embeds: [embed]})
player.pause(false)
}
} else {
interaction.reply("**Aucune musique n'est actuellement jouée !**")
}
} else {
// [A FINIR POUR WEB]
}
}
module.exports.getState = function(client, interaction) {
if(interaction) {
let player = client.manager.players.get(interaction.guild.id)
if(player) {
let embed = new EmbedBuilder()
.setColor(0x32a875)
.setTitle('Information sur la musique')
.addFields({name:"Titre", value: player.queue.current.title},
{name:"Auteur", value: player.queue.current.author},
{name:"URL", value: player.queue.current.uri})
.setTimestamp()
.setThumbnail(player.queue.current.thumbnail);
interaction.reply({embeds: [embed]})
} else {
interaction.reply("**Aucune musique n'est actuellement jouée !**")
}
} else {
// [A FINIR POUR WEB]
}
}
module.exports.skip = function (client, interaction) {
if(interaction) {
let player = client.manager.players.get(interaction.guild.id)
if(player) {
if(!list.haveSongs()) {
var embed = new EmbedBuilder()
.setColor(0xff0303)
.setTitle('Erreur : Skip')
.setTimestamp();
const song_show = {name: "Aucune chanson n'est dans la queue", value: "Changement impossible !"}
embed.addFields(song_show)
interaction.reply({embeds: [embed]})
} else {
var embed = new EmbedBuilder()
.setColor(0x03ff2d)
.setTitle('On change de morceau !!!')
.setDescription("**Ok, On est reparti avec "+ list.getNextSong().title + " et c'est demandée par " + interaction.member.user.username + "**")
.setThumbnail(list.getNextSong().thumbnail)
.setTimestamp();
player.stop()
interaction.reply({embeds: [embed]})
}
} else {
interaction.reply("**Aucune musique n'est actuellement jouée !**")
}
} else {
// [A FINIR POUR WEB]
}
}
module.exports.previous = function (client, interaction) {
if(interaction) {
let player = client.manager.players.get(interaction.guild.id)
if(player) {
if(!list.havePreviousSongs()){
embed = new EmbedBuilder()
.setColor(0xff0303)
.setTitle('Erreur : Back')
.setTimestamp();
const song_show = {name: "Aucune chanson n'a été joué précédemment !", value: "Changement impossible !"}
embed.addFields(song_show)
interaction.reply({embeds: [embed]})
} else {
list.__next_add(player.queue.current)
player.play(list.previous())
embed = new EmbedBuilder()
.setColor(0x03ff2d)
.setTitle('Retour vers le passé !!!')
.setDescription("**Ok, On est reparti avec "+ player.queue.current.title +" et c'est demandée par " + interaction.member.user.username + "**")
.setTimestamp();
interaction.reply({embeds: [embed]})
}
}
}
}
// FINI
module.exports.leave = function (client, interaction) {
var player = null
if(interaction) {
player = client.manager.players.get(interaction.guild.id)
}
if(player) {
player.destroy()
if(interaction) {
const embed = new EmbedBuilder()
.setColor(0xff0000)
.setTitle('C\'est tout pour nous !')
.setDescription("**Le meilleur groupe du monde est parti ... !**")
.setTimestamp();
interaction.reply({embeds: [embed]})
}
} else {
if(interaction) {
interaction.reply("**Aucune musique n'est actuellement jouée !**")
}
}
}
// FINI
async function checkPlaylist (song_name) {
try {
return await ytfps(song_name)
} catch(err) {
return false
}
}