Vesion 0.1.0 - Initial Version with First Commands
This commit is contained in:
20
src/commands/_template.txt
Normal file
20
src/commands/_template.txt
Normal file
@ -0,0 +1,20 @@
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||
const { __glob } = require("../modules/global-variables");
|
||||
const { LogType } = require("../modules/sub-log");
|
||||
const { List } = require("../modules/sub-list");
|
||||
const subplayer = require(__glob.SUBPLAYER);
|
||||
const packageJson = require(__glob.PACKAGE);
|
||||
|
||||
module.exports = {
|
||||
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("web")
|
||||
.setDescription("[NEW] Donne le lien vers le panel !"),
|
||||
|
||||
async execute(client, interaction) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
29
src/commands/about.js
Normal file
29
src/commands/about.js
Normal file
@ -0,0 +1,29 @@
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||
const { __glob } = require("../modules/global-variables");
|
||||
const packageJson = require(__glob.PACKAGE);
|
||||
|
||||
module.exports = {
|
||||
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("about")
|
||||
.setDescription("[NEW] Affiche les informations principales du bot !"),
|
||||
|
||||
async execute(client, interaction) {
|
||||
|
||||
const uptime = process.uptime();
|
||||
const hours = Math.floor(uptime / 3600);
|
||||
const minutes = Math.floor((uptime % 3600) / 60);
|
||||
const seconds = Math.floor(uptime % 60);
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0xb0f542)
|
||||
.setThumbnail("https://cdn.discordapp.com/avatars/" + client.user.id + "/" + client.user.avatar + ".png")
|
||||
.setTitle('Subsonics - Web')
|
||||
.addFields({name: "Version ", value: packageJson.version},{name:"Uptime", value: hours + " heure(s), " + minutes + " minute(s) et " + seconds + " seconde(s)"},{name: "Ping", value: client.ws.ping + " ms"},{name: "Un bot fait par un fan pour les fans !", value: ":heart:"})
|
||||
.setTimestamp();
|
||||
|
||||
interaction.reply({embeds: [embed]})
|
||||
|
||||
}
|
||||
|
||||
}
|
33
src/commands/help.js
Normal file
33
src/commands/help.js
Normal file
@ -0,0 +1,33 @@
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||
module.exports = {
|
||||
|
||||
data:new SlashCommandBuilder()
|
||||
.setName("help")
|
||||
.setDescription("[NEW] Affiche toutes les commandes disponibles du bot"),
|
||||
|
||||
async execute(client, interaction) {
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0x03ff2d)
|
||||
.setTitle('Comment assister au concert ?')
|
||||
.setDescription("**Eh ! Tu as eu ton ticket ? Tant mieux ! Voici la liste des commandes à utiliser dans le salon <#664355637685256203>**")
|
||||
.addFields({name: "/play <nom/playlist>", value: "Cette commande te permet de lire des titres comme des playlists depuis Youtube / SoundCloud / Vimeo, n'importe quelle musique !"},
|
||||
{name: "/leave", value: "Si tu ne veux plus du meilleur groupe du monde (faire partir le bot), cette commande le fera partir aussi vite qu'il est arrivé !"},
|
||||
{name: "/pause", value: "Besoin d'un entracte ? Cette commande te permettera de mettre en pause ou de remettre le morceau en cours !"},
|
||||
{name: "/state", value: "Donne le titre de la musique"},
|
||||
{name: "/skip", value: "Passer à la chanson suivante."},
|
||||
{name: "/previous", value: "Revenir à la chanson précédente."},
|
||||
{name: "/about", value: "Affiche les informations principales !"},
|
||||
{name: "/web", value: "Donne le lien vers le panel !"})
|
||||
.setTimestamp()
|
||||
.addFields({name: "La queue et la gestion du redémarrage se fait par le site https://subsonics.raphix.fr/", value: ":star:"})
|
||||
.setThumbnail("https://static.wikia.nocookie.net/codelyoko/images/9/95/Subdigitals.jpg/revision/latest/scale-to-width-down/180?cb=20120105180510&path-prefix=fr");
|
||||
|
||||
|
||||
|
||||
interaction.reply({embeds: [embed]})
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
24
src/commands/leave.js
Normal file
24
src/commands/leave.js
Normal file
@ -0,0 +1,24 @@
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||
const { __glob } = require("../modules/global-variables");
|
||||
const subplayer = require(__glob.SUBPLAYER);
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("leave")
|
||||
.setDescription("[NEW] Déconnecte le Bot !"),
|
||||
|
||||
async execute(client, interaction) {
|
||||
|
||||
// CHECK MEMBER IF IN VOICE CHANNEL
|
||||
|
||||
if(!interaction.member.voice.channel) return interaction.reply({content:"Vous devez rejoindre un salon vocal pour contrôler le Bot !", ephemeral: true})
|
||||
|
||||
subplayer.leave(client, interaction)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
22
src/commands/pause.js
Normal file
22
src/commands/pause.js
Normal file
@ -0,0 +1,22 @@
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||
const { __glob } = require("../modules/global-variables");
|
||||
const { LogType } = require("../modules/sub-log");
|
||||
const { List } = require("../modules/sub-list");
|
||||
const subplayer = require(__glob.SUBPLAYER);
|
||||
const packageJson = require(__glob.PACKAGE);
|
||||
|
||||
module.exports = {
|
||||
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("pause")
|
||||
.setDescription("[NEW] Cette commande te permettera de mettre en pause ou de remettre le morceau en cours !"),
|
||||
|
||||
async execute(client, interaction) {
|
||||
|
||||
if(!interaction.member.voice.channel) return interaction.reply({content:"Vous devez rejoindre un salon vocal pour contrôler le Bot !", ephemeral: true})
|
||||
|
||||
subplayer.pause(client, interaction)
|
||||
|
||||
}
|
||||
|
||||
}
|
27
src/commands/play.js
Normal file
27
src/commands/play.js
Normal file
@ -0,0 +1,27 @@
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||
const { __glob } = require("../modules/global-variables");
|
||||
const subplayer = require(__glob.SUBPLAYER);
|
||||
|
||||
|
||||
module.exports = {
|
||||
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("play")
|
||||
.setDescription("[NEW] Permet de lire des titres comme des playlists depuis Youtube / SoundCloud / Vimeo !")
|
||||
.addStringOption(option => option.setName("nom_ou_lien").setDescription("Lien ou nom du titre ou de la playlist").setRequired(true)),
|
||||
|
||||
async execute(client, interaction) {
|
||||
|
||||
// CHECK MEMBER IF IN VOICE CHANNEL
|
||||
|
||||
if(!interaction.member.voice.channel) return interaction.reply({content:"Vous devez rejoindre un salon vocal pour lire un(e) titre / playlist !", ephemeral: true})
|
||||
|
||||
//CHECK OF PLAYER
|
||||
|
||||
subplayer.play(client, interaction)
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
19
src/commands/previous.js
Normal file
19
src/commands/previous.js
Normal file
@ -0,0 +1,19 @@
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||
const { __glob } = require("../modules/global-variables");
|
||||
const { LogType } = require("../modules/sub-log");
|
||||
const { List } = require("../modules/sub-list");
|
||||
const subplayer = require(__glob.SUBPLAYER);
|
||||
const packageJson = require(__glob.PACKAGE);
|
||||
|
||||
module.exports = {
|
||||
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("previous")
|
||||
.setDescription("[NEW] Revenir à la chanson précédente."),
|
||||
|
||||
async execute(client, interaction) {
|
||||
|
||||
subplayer.previous(client, interaction)
|
||||
}
|
||||
|
||||
}
|
20
src/commands/skip.js
Normal file
20
src/commands/skip.js
Normal file
@ -0,0 +1,20 @@
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||
const { __glob } = require("../modules/global-variables");
|
||||
const { LogType } = require("../modules/sub-log");
|
||||
const { List } = require("../modules/sub-list");
|
||||
const subplayer = require(__glob.SUBPLAYER);
|
||||
const packageJson = require(__glob.PACKAGE);
|
||||
|
||||
module.exports = {
|
||||
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("skip")
|
||||
.setDescription("[NEW] Passer à la chanson suivante."),
|
||||
|
||||
async execute(client, interaction) {
|
||||
|
||||
|
||||
subplayer.skip(client, interaction)
|
||||
}
|
||||
|
||||
}
|
19
src/commands/state.js
Normal file
19
src/commands/state.js
Normal file
@ -0,0 +1,19 @@
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||
const { __glob } = require("../modules/global-variables");
|
||||
const { LogType } = require("../modules/sub-log");
|
||||
const { List } = require("../modules/sub-list");
|
||||
const subplayer = require(__glob.SUBPLAYER);
|
||||
const packageJson = require(__glob.PACKAGE);
|
||||
|
||||
module.exports = {
|
||||
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("state")
|
||||
.setDescription("[NEW] Donne le titre de la musique !"),
|
||||
|
||||
async execute(client, interaction) {
|
||||
|
||||
subplayer.getState(client, interaction)
|
||||
}
|
||||
|
||||
}
|
24
src/commands/web.js
Normal file
24
src/commands/web.js
Normal file
@ -0,0 +1,24 @@
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require("discord.js");
|
||||
const { __glob } = require("../modules/global-variables");
|
||||
const packageJson = require(__glob.PACKAGE);
|
||||
|
||||
module.exports = {
|
||||
|
||||
data: new SlashCommandBuilder()
|
||||
.setName("web")
|
||||
.setDescription("[NEW] Donne le lien vers le panel !"),
|
||||
|
||||
async execute(client, interaction) {
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(0xffffff)
|
||||
.setThumbnail("https://cdn.discordapp.com/avatars/" + client.user.id + "/" + client.user.avatar + ".png")
|
||||
.setTitle('Subsonics - Web')
|
||||
.addFields({name: "Lien", value:"https://subsonics.raphix.fr"})
|
||||
.setTimestamp();
|
||||
|
||||
interaction.reply({embeds: [embed]})
|
||||
|
||||
}
|
||||
|
||||
}
|
66
src/main.js
Normal file
66
src/main.js
Normal file
@ -0,0 +1,66 @@
|
||||
const fs = require("node:fs")
|
||||
const path = require("path")
|
||||
const { LogType } = require("./modules/sub-log")
|
||||
const { DiscordBot } = require("./modules/discord-bot")
|
||||
const { __glob } = require("./modules/global-variables")
|
||||
|
||||
setup()
|
||||
|
||||
function setup() {
|
||||
|
||||
//Log - INIT PHASE
|
||||
|
||||
const dlog = new LogType("Discord")
|
||||
const wlog = new LogType("Web")
|
||||
const alog = new LogType("Authentification")
|
||||
|
||||
// Discord Bot - INIT PHASE
|
||||
|
||||
const bot = new DiscordBot(getConfig(dlog), dlog)
|
||||
|
||||
}
|
||||
|
||||
|
||||
// Config GETTER
|
||||
|
||||
function getConfig(dlog) {
|
||||
|
||||
dlog.step.init("getConfig", "Récupération du fichier de configuration")
|
||||
|
||||
if(fs.existsSync(__glob.CONFIG)) {
|
||||
try {
|
||||
|
||||
var config_data = JSON.parse(fs.readFileSync(__glob.CONFIG))
|
||||
|
||||
dlog.log("Fichier de configuration trouvé : TOKEN : " + config_data.token)
|
||||
dlog.step.end("getConfig")
|
||||
|
||||
return config_data
|
||||
} catch(error) {
|
||||
dlog.step.error("getConfig", error)
|
||||
|
||||
}
|
||||
} else {
|
||||
|
||||
dlog.warn("Fichier de configuration introuvable !")
|
||||
|
||||
try {
|
||||
var new_config = {
|
||||
"token":"",
|
||||
"guild_id":"",
|
||||
"voice_channel_id":""
|
||||
}
|
||||
|
||||
|
||||
fs.writeFileSync(__glob.CONFIG, JSON.stringify(new_config, null, 2))
|
||||
dlog.log("Création d'un fichier de configuration ! Redémarrage de l'application nécéssaire !")
|
||||
dlog.step.error("getConfig", "Redémarrage requis pour lire la nouvelle configuration !")
|
||||
process.exit(0)
|
||||
} catch(error) {
|
||||
dlog.step.error("getConfig", "Tentative de création du fichier de configuration échoué !" + error)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
241
src/modules/discord-bot.js
Normal file
241
src/modules/discord-bot.js
Normal 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));
|
||||
}
|
19
src/modules/global-variables.js
Normal file
19
src/modules/global-variables.js
Normal 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 };
|
||||
|
85
src/modules/nodes-finder.js
Normal file
85
src/modules/nodes-finder.js
Normal 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
182
src/modules/sub-list.js
Normal 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
217
src/modules/sub-log.js
Normal 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
305
src/modules/sub-player.js
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user