// Configuration const config = require('./config'); // Driver SQL const Sequelize = require('sequelize'); const { Op } = require("sequelize"); // API externe const express = require('express') const app = express() var XMLHttpRequest = require('xhr2'); // Couleurs de la console var colors = require('colors'); // API discord const { REST } = require('@discordjs/rest'); const { Routes } = require('discord-api-types/v9'); // Moment JS var moment = require('moment'); // Schedule const schedule = require('node-schedule'); //////////////////////////////////////////////////////////////// // MODELES DES TABLES //////////////////////////////////////////////////////////////// // Connexion à la base de données const sequelize = new Sequelize(config.get("BDD_NAME"), config.get("BDD_USER"), config.get("BDD_PASSWORD"), { host: config.get("BDD_HOST"), dialect: 'mariadb', logging: false, }); // Paramètres du bot const botSettings = sequelize.define('discord_settings', { name: { type: Sequelize.STRING(128), primaryKey: true }, value: Sequelize.STRING(512), }, { timestamps: false }); // Entrées et sorties des membres const entries = sequelize.define('discord_entries', { id: { type: Sequelize.INTEGER, autoIncrement: true, primaryKey: true }, memberId: Sequelize.BIGINT(255), isJoin: Sequelize.BOOLEAN, date: Sequelize.DATE, }, { timestamps: false }); // Paramètres des membres const memberSettings = sequelize.define('discord_memberSettings', { memberId: { type: Sequelize.BIGINT(255), primaryKey: true }, name: { type: Sequelize.STRING(128), primaryKey: true }, value: Sequelize.STRING(512), }, { timestamps: false }); // Paramètres des joueurs MC const minecraftPlayerSetting = sequelize.define('site_userSetting', { uuid: { type: Sequelize.STRING(36), primaryKey: true }, name: { type: Sequelize.STRING(128), primaryKey: true }, value: Sequelize.TEXT, }, { timestamps: false, freezeTableName: true }); // Codes de liaison du serveur MC const mcLinkCode = sequelize.define('site_linkCode', { uuid: { type: Sequelize.STRING(36), primaryKey: true }, code: { type: Sequelize.STRING(8), unique: true }, time: { type: 'TIMESTAMP', defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), allowNull: false }, used: { type: Sequelize.BOOLEAN, defaultValue: false }, }, { timestamps: false, freezeTableName: true }); //////////////////////////////////////////////////////////////// const commands = [{ name: 'ping', description: 'Répond avec pong!' }, { name: 'setanniv', description: 'Permet de définir ta date d\'anniversaire (usage unique).', options: [{ name: "date", // no uppercase as well description: "Date au format DD/MM/YYYY - 31/12/2001", type: 3, required: true }] }, { name: 'delanniv', description: '[Admin] Supprime la date d\'anniversaire d\'un membre.', options: [{ name: "membre", // no uppercase as well description: "Membre à supprimer la date d'anniversaire.", type: 6, required: true }] }, { name: 'linkmc', description: 'Te permet de lier ton compte Minecraft au serveur Discord.', options: [{ name: "code", // no uppercase as well description: "Code généré par la commande /getlinkcode.", type: 3, required: true }] }]; //////////////////////////////////////////////////////////////// // INITIALISATION DES COMMANDES ET CONNEXION À LA BASE DE DONNÉES //////////////////////////////////////////////////////////////// const rest = new REST({ version: '9' }).setToken(config.get("DISCORD_BOT_TOKEN")); (async () => { console.log('[' + 'INFO'.yellow + '] Connexion à la base de donnée...'.brightWhite); try { await sequelize.authenticate(); console.log('[' + 'SUCCES'.brightGreen + '] Connexion à la base de donnée réussie.'.brightWhite); await initialiseDatabaseTables(); // On va initialiser les tables de la base de donnée try { console.log('[' + 'INFO'.yellow + '] Actualisation des commandes...'.brightWhite); //console.log(JSON.stringify(commands)); await rest.put( Routes.applicationGuildCommands(config.get("CLIENT_ID"), config.get("GUILD_ID")), { body: commands }, ); console.log('[' + 'SUCCES'.brightGreen + '] Toutes les commandes ont été actualisées.'); } catch (error) { console.error('[' + 'ERREUR'.brightRed + '] Erreur lors de l\'actualisation des commandes:'.brightWhite + error); } } catch (error) { console.log('[' + 'ERREUR'.brightRed + '] Erreur lors de la connexion à la base de donnée:'.brightWhite); console.log('[' + 'DEBUG'.yellow + '] '.brightWhite + config.get("BDD_USER") + ":" + config.get("BDD_PASSWORD") + "@" + config.get("BDD_HOST") + " db:" + config.get("BDD_NAME") + '\n'); console.error(error); } })(); async function initialiseDatabaseTables() { console.log('[' + 'INFO'.yellow + '] Initialisation des tables...'.brightWhite); try { // On synchronise les modèles de sequlize await botSettings.sync(); await entries.sync(); await memberSettings.sync(); await minecraftPlayerSetting.sync(); await mcLinkCode.sync(); // Basiquement on regarde si l'entrée existe, puis on agit en conséquence let token = await botSettings.findOne({ where: { name: "token" } }); if (token == null) { // INSERT si elle n'existe pas console.log('[' + 'INSERT'.brightMagenta + '] Insertion de token'.brightWhite); let token = botSettings.create({ name: "token", value: config.get("DISCORD_BOT_TOKEN") }); } else { // UPDATE si différente if (token.value != config.get("DISCORD_BOT_TOKEN")) { token.update({ value: config.get("DISCORD_BOT_TOKEN") }) .then(updatedRecord => { console.log('[' + 'UPDATE'.brightMagenta + '] Mise à jour du token dans la base de donnée'.brightWhite); }).catch(err => { console.log('[' + 'ERREUR'.brightRed + '] Erreur lors de la màj de token dans la base de donnée: '.brightWhite + '\n'); throw new Error(err); }); } } // Et c'est pareil à chaque fois let clientId = await botSettings.findOne({ where: { name: "clientId" } }); if (clientId == null) { console.log('[' + 'INSERT'.brightMagenta + '] Insertion de clientId'.brightWhite); let clientId = botSettings.create({ name: "clientId", value: config.get("CLIENT_ID") }); } else { if (clientId.value != config.get("CLIENT_ID")) { clientId.update({ value: config.get("CLIENT_ID") }) .then(updatedRecord => { console.log('[' + 'UPDATE'.brightMagenta + '] Mise à jour du clientId dans la base de donnée'.brightWhite); }).catch(err => { console.log('[' + 'ERREUR'.brightRed + '] Erreur lors de la màj de clientId dans la base de donnée: '.brightWhite + '\n'); throw new Error(err); }); } } let guildId = await botSettings.findOne({ where: { name: "guildId" } }); if (guildId == null) { console.log('[' + 'INSERT'.brightMagenta + '] Insertion de guildId'.brightWhite); let guildId = botSettings.create({ name: "guildId", value: config.get("GUILD_ID") }); } else { if (guildId.value != config.get("GUILD_ID")) { guildId.update({ value: config.get("GUILD_ID") }) .then(updatedRecord => { console.log('[' + 'UPDATE'.brightMagenta + '] Mise à jour du guildId dans la base de donnée'.brightWhite); }).catch(err => { console.log('[' + 'ERREUR'.brightRed + '] Erreur lors de la màj de guildId dans la base de donnée: '.brightWhite + '\n'); throw new Error(err); }); } } console.log('[' + 'SUCCES'.brightGreen + '] Tables initialisées avec succès.'.brightWhite); } catch (error) { console.error('[' + 'ERREUR'.brightRed + '] Erreur lors de l\'initialisation des tables:'.brightWhite + '\n', error); } } //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// // CONSOLE //////////////////////////////////////////////////////////////// // Console Input/Output var readline = require('readline'); var rl = readline.createInterface({ input: process.stdin, output: process.stdout }); // Ce code est a améliorer, je l'ai vulgairement recopié d'un ancien bot (lui même pas très bien conçu) var recursiveAsyncReadLine = function () { rl.question('Commande: ', function (answer) { //if (answer == 'exit') // return rl.close(); const args = answer.match(/(".*?"|[^"\s]+)+(?=\s*|\s*$)/g); const command = args.shift().toLowerCase(); switch (command) { case "say": if (!args[0] || !args[1]) console.log('\n' + '[' + 'ERREUR'.brightRed + "] Tu n'as pas mis d'arguments :p" + '\n' + '[' + 'INFO'.yellow + "] Usage: say <Numéro du canal> <\"Texte\">"); else { var message = args[1].substring(1, args[1].length - 1); client.channels.cache.get(args[0]).send(message); console.log('\n' + '[' + 'SUCCES'.brightGreen + '] Le message a été envoyé dans le canal n°' + args[0]); } break; default: console.log('\n' + "Commande inconnue. :p"); break; } recursiveAsyncReadLine(); //Calling this function again to ask new question }); }; recursiveAsyncReadLine(); //we have to actually start our recursion somehow //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// // BOT DISCORD //////////////////////////////////////////////////////////////// // require the needed discord.js classes const { Client, GatewayIntentBits, MessageActionRow, MessageButton, WebhookClient } = require('discord.js'); const { verify } = require('crypto'); // create a new Discord client const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMembers, GatewayIntentBits.DirectMessages ] }) var mcChatWebhook, devMcChatWebhook; client.on("ready", async function () { console.log('\n' + "SL-Projects Bot".brightCyan); console.log(""); console.log("Bot démarré".brightGreen); var guild = client.guilds.cache.get(config.get("GUILD_ID")); var memberCount = guild.memberCount.toLocaleString(); console.log('\n' + "Nous sommes " + memberCount + " membres."); client.user.setActivity("Regarde " + memberCount + " membres"); checkAnniv(); verifyMcChatWebhook(); }); // Actions lorsqu'un membre rejoins ou part client.on('guildMemberAdd', async (member) => { let guild = client.guilds.cache.get(config.get("GUILD_ID")); var memberCount = guild.memberCount.toLocaleString(); console.log('\n' + "Nouveau membre - " + member.user.username + " - " + memberCount + " membres"); member.guild.channels.cache.get(config.get("CANAL_LOG")).send("Nouveau membre - " + member.user.username + " - " + memberCount + " membres"); client.user.setActivity("Regarde " + memberCount + " membres"); var currentdate = new Date(); var datetime = currentdate.getFullYear() + "-" + (currentdate.getMonth() + 1) + "-" + currentdate.getDate() + " " + (currentdate.getHours() + 1) + ":" + currentdate.getMinutes() + ":" + currentdate.getSeconds(); await entries.create({ id: null, memberId: member.user.id, isJoin: 1, date: datetime, }); }); client.on('guildMemberRemove', async (member) => { let guild = client.guilds.cache.get(config.get("GUILD_ID")); var memberCount = guild.memberCount.toLocaleString(); console.log('\n' + "Un membre a quitté le serveur - " + member.user.username + " - " + memberCount + " membres"); member.guild.channels.cache.get(config.get("CANAL_LOG")).send("Un membre a quitté le serveur - " + member.user.username + " - " + memberCount + " membres"); client.user.setActivity("Regarde " + memberCount + " membres"); var currentdate = new Date(); var datetime = currentdate.getFullYear() + "-" + (currentdate.getMonth() + 1) + "-" + currentdate.getDate() + " " + (currentdate.getHours() + 1) + ":" + currentdate.getMinutes() + ":" + currentdate.getSeconds(); await entries.create({ id: null, memberId: member.user.id, isJoin: 0, date: datetime, }); }); // Lorsqu'un message est envoyé client.on('messageCreate', async message => { // On va regarder si le message vient du canal de tchat du serveur MC if (message.channel.id == config.get("CANAL_CHAT_MC") && !message.author.bot) { var playerName = message.author.username; var messageContent = message.content; // On va vérifier si l'utilisateur a associé son compte discord avec le serveur MC var memberAccountAssoc = await minecraftPlayerSetting.findOne({ where: { name: 'discordAccountId', value: message.author.id } }); if (memberAccountAssoc != null) { // L'utilisateur a bien associé son compte discord avec le serveur MC let userMcUsername = await minecraftPlayerSetting.findOne({ where: { uuid: memberAccountAssoc.uuid, name: 'playerName' } }); if(userMcUsername != null) { playerName = userMcUsername.value; } } // On encode ça en JSON var jsonEncodedMessage = JSON.stringify({ "message": messageContent, "playerName": playerName }); // Maintenant on va appeler le serveur pour envoyer le message var xmlHttp = new XMLHttpRequest(); xmlHttp.open("GET", "http://node.sl-projects.com:" + config.get("PORT_MSG_SRVMC") + "/discordMsg/" + encodeURIComponent(jsonEncodedMessage), true); xmlHttp.send(); } }); // Commandes client.on('interactionCreate', async interaction => { if (interaction.isCommand()) { console.log('[' + 'COMMANDE'.brightMagenta + '] '.brightWhite + interaction.user.username.brightBlue + ' a lancé la commande '.brightWhite + interaction.commandName.yellow); if (interaction.commandName === 'ping') { await interaction.reply('Pong!'); } else if (interaction.commandName === 'setanniv') { // Je check si le membre a déjà enregistré sa date d'anniversaire let userAnniv = await memberSettings.findOne({ where: { memberId: interaction.user.id, name: 'birthday' } }); // S'il ne l'a pas déjà fait if (userAnniv == null) { // On va checker que ce qu'il a envoyé est bien une date valide let memberBirthday = moment(interaction.options.getString('date'), "DD/MM/YYYY"); if (isNaN(memberBirthday)) { console.log('\n' + '[' + 'ERREUR'.brightRed + "] Date illisible: " + interaction.options.getString('date')); await interaction.reply('J\'ai du mal à lire la date que tu m\'as donné. Est-elle bien dans ce format **DD/MM/YYYY**? :thinking:'); } else { try { memberBirthday = memberBirthday.toDate(); console.log('[' + 'INSERT'.brightMagenta + '] '.brightWhite + interaction.user.username.brightBlue + " a renseigné sa date d'anniversaire. ".brightWhite + interaction.options.getString('date').yellow); var dd = memberBirthday.getDate(); var mm = memberBirthday.getMonth() + 1; var yyyy = memberBirthday.getFullYear(); if (dd < 10) { dd = '0' + dd; } if (mm < 10) { mm = '0' + mm; } let birthday = mm + '/' + dd + '/' + yyyy; let insetMemberBirthday = memberSettings.create({ memberId: interaction.user.id, name: "birthday", value: birthday }); await interaction.reply({ content: 'Je m\'en souviendrai. :thumbup:', ephemeral: true }); checkAnniv(); } catch (error) { console.error('[' + 'ERREUR'.brightRed + '] Erreur lors de l\'insertion de la date d\'anniversaire: '.brightWhite + '\n', error); await interaction.reply("J'ai eu un petit problème pour enregistrer ta date d'anniversaire, re-essaie plus-tard. :p"); } } } else { await interaction.reply('Tu ne peux pas redéfinir ta date d\'anniversaire. Demande au staff si besoin. :p'); } } else if (interaction.commandName === 'delanniv') { // On check les perms if (interaction.member.roles.cache.has(config.get("ROLE_ANNIV"))) { try { console.log('\n' + '[' + 'DELETE'.brightMagenta + "] Suppression de la date d'anniversaire de " + interaction.options.getMember('membre')); await memberSettings.destroy({ where: { name: "birthday", memberId: interaction.options.getMember('membre').id } }); } catch (error) { console.error('[' + 'ERREUR'.brightRed + '] Erreur lors de la supression de la date d\'anniversaire: '.brightWhite + '\n', error); await interaction.reply("J'ai eu un petit problème pour supprimer la date d'anniversaire, re-essaie plus-tard. :p"); } await interaction.reply({ content: 'La date d\'anniversaire de <@' + interaction.options.getMember('membre') + '> a été supprimée.', ephemeral: true }); checkAnniv(); } else { await interaction.reply({ content: "Tu n'as pas le droit d'exécuter cette commande. :p", ephemeral: true }); } } else if (interaction.commandName === 'linkmc') { console.log('[' + 'INFO'.yellow + '] L\'utilisateur '.brightWhite + interaction.user.username.brightBlue + " a demandé une association de son compte Minecraft avec le code ".brightWhite + interaction.options.getString('code').yellow); // On va vérifier si le membre a déjà associé son compte minecraft let memberAccountAssoc = await minecraftPlayerSetting.findOne({ where: { name: 'discordAccountId', value: interaction.user.id } }); if (memberAccountAssoc == null) { // Il ne l'a pas déjà associé // On va donc checker la validité du code let code = interaction.options.getString('code'); // On va le chercher dans la bdd let isCodeValid = await mcLinkCode.findOne({ where: { code: code, used: false } }); if (isCodeValid != null) { // Le code est valide // On va le mettre dans la bdd await minecraftPlayerSetting.create({ name: 'discordAccountId', value: interaction.user.id, uuid: isCodeValid.uuid }); // On va mettre à jour le code await mcLinkCode.update({ used: true }, { where: { code: code } }); // On va récupérer le pseudo du joueur pour la réponse let playerName = await minecraftPlayerSetting.findOne({ where: { uuid: isCodeValid.uuid, name: 'playerName' } }); // On va envoyer le message await interaction.reply({ content: 'Ton compte a été associé avec le compte Minecraft de **' + playerName.value + '** !', ephemeral: true }); } else { // Le code n'est pas valide await interaction.reply({ content: 'Le code que tu as entré est invalide. :p', ephemeral: true }); } } else { // Il l'a déjà associé await interaction.reply({ content: 'Tu as déjà associé ton compte minecraft avec le serveur Discord. :thinking:', ephemeral: true }); } } } }); async function checkAnniv() { console.log('[' + 'INFO'.yellow + '] Vérification des anniversaires.'.brightWhite); let today = new Date(); var dd = today.getDate(); var mm = today.getMonth() + 1; var yyyy = today.getFullYear(); if (dd < 10) { dd = '0' + dd; } if (mm < 10) { mm = '0' + mm; } today = mm + '/' + dd; let { count, rows } = await memberSettings.findAndCountAll({ where: { name: "birthday", value: { [Op.like]: today + '%' } } }); const guild = client.guilds.cache.get(config.get("GUILD_ID")); //console.log(guild); var membersWithAnnivRole = await guild.roles.cache.get(config.get("ROLE_ANNIV")).members; //console.log(membersWithAnnivRole); // On va vérifier que c'est bien l'anniv des membres ayant le rôle. :p for await (var memberWithAnnivRole of membersWithAnnivRole) { var isMemberBirthday = false; for await (const member of rows) { if (memberWithAnnivRole[0] === member.memberId.toString()) { isMemberBirthday = true; } } if (!isMemberBirthday) { console.log('[' + 'INFO'.yellow + '] Suppression du rôle anniversaire pour '.brightWhite + memberWithAnnivRole[1].user.username); memberWithAnnivRole[1].roles.remove(config.get("ROLE_ANNIV")).catch(console.error); } } console.log('[' + 'SUCCES'.brightGreen + '] C\'est l\'anniversaire de ' + count + ' personne(s).'); for await (var member of rows) { let memberFetch = await guild.members.fetch(member.memberId.toString()); //console.log(memberFetch); if (memberFetch) { console.log(" 🎂 " + memberFetch.user.username); if (!memberFetch.roles.cache.has(config.get("ROLE_ANNIV"))) { let annivRole = await memberFetch.guild.roles.cache.find(role => role.id === config.get("ROLE_ANNIV")); if (annivRole) { memberFetch.roles.add(annivRole); console.log('[' + 'INFO'.yellow + '] Le rôle '.brightWhite + annivRole.name.yellow + " a été donné à " + memberFetch.user.username.brightBlue); client.channels.cache.get(config.get("CANAL_GENERAL")).send("Fêtons l'anniversaire de <@" + memberFetch.id + "> ! :partying_face:").catch(console.error); } } } } } async function verifyMcChatWebhook() { let mcChatWebhookId = await botSettings.findOne({ where: { name: "mcChatWebhookId" } }); let devMcChatWebhookId = await botSettings.findOne({ where: { name: "devMcChatWebhookId" } }); if (mcChatWebhookId != null && devMcChatWebhookId != null) { console.log("[" + 'INFO'.yellow + "] Le Webhook du serveur MC est déjà configuré."); let mcChatWebhookToken = await botSettings.findOne({ where: { name: "mcChatWebhookToken" } }); let devMcChatWebhookToken = await botSettings.findOne({ where: { name: "devMcChatWebhookToken" } }); if (mcChatWebhookToken != null && devMcChatWebhookToken != null) { mcChatWebhook = new WebhookClient({ id: mcChatWebhookId.value, token: mcChatWebhookToken.value }); devMcChatWebhook = new WebhookClient({ id: devMcChatWebhookId.value, token: devMcChatWebhookToken.value }); } else { console.log("[" + 'ERREUR'.brightRed + "] Impossible de trouver le token du webhook dans la base de donnée."); createMcChatWebhook(); } } else { createMcChatWebhook(); } } async function createMcChatWebhook() { console.log("[" + 'INFO'.yellow + "] Création du Webhook du serveur MC."); client.channels.cache.get(config.get("CANAL_CHAT_MC")).createWebhook('Chat Serveur Minecraft', { avatar: 'https://sl-craft.fr/data/images/logo/short-color.png', }) .then(webhook => { botSettings.create({ name: "mcChatWebhookId", value: webhook.id }); botSettings.create({ name: "mcChatWebhookToken", value: webhook.token }); mcChatWebhook = new WebhookClient({ id: webhook.id, token: webhook.token }); console.log("[" + 'INFO'.yellow + "] Le Webhook du serveur MC a été configuré avec succès."); }) .catch(console.error); // Dev client.channels.cache.get(config.get("CANAL_CHAT_MC_DEV")).createWebhook('Chat Serveur Minecraft Dev', { avatar: 'https://sl-craft.fr/data/images/logo/short-color.png', }) .then(webhook => { botSettings.create({ name: "devMcChatWebhookId", value: webhook.id }); botSettings.create({ name: "devMcChatWebhookToken", value: webhook.token }); devMcChatWebhook = new WebhookClient({ id: webhook.id, token: webhook.token }); console.log("[" + 'INFO'.yellow + "] Le Webhook du serveur MC Dev a été configuré avec succès."); }) .catch(console.error); } function sendMessageFromMcChat(username, message, serverType) { if(username == "SL-Craft"){ if(serverType == "dev"){ devMcChatWebhook.send({ content: message, username: username, avatarURL: 'https://sl-craft.fr/data/images/logo/favicon-color.png', }); }else{ mcChatWebhook.send({ content: message, username: username, avatarURL: 'https://sl-craft.fr/data/images/logo/favicon-color.png', }); } }else{ // On va vérifier que le joueur ne fait pas de @everyone ou de @here message = message.replace("<@everyone>", "**everyone**"); message = message.replace("<@here>", "**here**"); message = message.replace("@everyone", "**everyone**"); message = message.replace("@here", "**here**"); // On va regarder si le joueur souhaite tagger quelqu'un avec un @ var regex = /@(.*?\s)/g; var match = regex.exec(message); if (match){ // console.log("ça a matché"); var member = match[1].trim(); // console.log(member+"flag"); var memberFetch = client.users.cache.find(user => user.username == member).id; if (memberFetch){ // console.log("Le membre existe"); message = message.replace("@"+member, "<@" + memberFetch + ">"); } } if(serverType == "dev"){ devMcChatWebhook.send({ content: message, username: username, avatarURL: 'https://live.mc.sl-projects.com/tiles/faces/32x32/' + username + '.png', }); }else{ mcChatWebhook.send({ content: message, username: username, avatarURL: 'https://live.mc.sl-projects.com/tiles/faces/32x32/' + username + '.png', }); } } } const job = schedule.scheduleJob('0 0 * * *', function () { console.log('[' + 'INFO'.yellow + '] Éxecution des tâches de fonds quotidiennes...'.brightWhite); checkAnniv(); }); // login to Discord with your app's token client.login(config.get("DISCORD_BOT_TOKEN")); //////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////// // API //////////////////////////////////////////////////////////////// app.get('/ping', (req, res) => { res.send("Pong!") }) app.get('/channels/:id', (req, res) => { var id = req.params.id; res.send(client.channels.cache.get(id)); }) app.get('/mc/chat/:detail', (req, res) => { var detail = JSON.parse(decodeURI(req.params.detail)); sendMessageFromMcChat(detail.username, detail.message, req.headers['server-type']); res.send("Envoyé!") }) app.listen(27001, () => { console.log('[' + 'INFO'.yellow + '] Écoute sur '.brightWhite + 'node.sl-projects.com:27001'.yellow); }); ////////////////////////////////////////////////////////////////