From 7d6bc8e12fda7c0e4e543d2710fed0aa3d196b1e Mon Sep 17 00:00:00 2001 From: cut0x Date: Tue, 21 Oct 2025 02:13:56 +0200 Subject: [PATCH] Push structure --- .env | 14 +++ .gitignore | 1 + README.md | 134 +++++++++++++++++++- emojis.json | 9 ++ package.json | 27 ++++ src/commands/minecraft.js | 126 +++++++++++++++++++ src/commands/setup-access.js | 40 ++++++ src/events/ready.js | 47 +++++++ src/handlers/commandHandler.js | 26 ++++ src/handlers/eventHandler.js | 35 ++++++ src/handlers/interactionHandler.js | 191 +++++++++++++++++++++++++++++ src/utils/accessRequestManager.js | 110 +++++++++++++++++ src/utils/minecraftServer.js | 82 +++++++++++++ src/utils/whitelistManager.js | 119 ++++++++++++++++++ start.js | 33 +++++ 15 files changed, 992 insertions(+), 2 deletions(-) create mode 100644 .env create mode 100644 .gitignore create mode 100644 emojis.json create mode 100644 package.json create mode 100644 src/commands/minecraft.js create mode 100644 src/commands/setup-access.js create mode 100644 src/events/ready.js create mode 100644 src/handlers/commandHandler.js create mode 100644 src/handlers/eventHandler.js create mode 100644 src/handlers/interactionHandler.js create mode 100644 src/utils/accessRequestManager.js create mode 100644 src/utils/minecraftServer.js create mode 100644 src/utils/whitelistManager.js create mode 100644 start.js diff --git a/.env b/.env new file mode 100644 index 0000000..28e018d --- /dev/null +++ b/.env @@ -0,0 +1,14 @@ +TOKEN=ODcwNzA3OTcyMjE1NTUwMDE0.GNCXuj.kPaJfU-QHKZk7uBvzblOmJHSv9QllNRIIp5dGg +CLIENT_ID=870707972215550014 +OWNER_ID=574544938440851466 + +SUPPORT_SERVER_ID=840770323988873227 +CHANNEL_REQUEST_ACCESS_ID=1402029493258031114 +CHANNEL_ACCESS_STAFF_REQUESTS_ID=1429968603905786000 + +ROLE_WAITER_ID=1429968069450530987 +ROLE_VERIFIED_ID=1429968133304615012 +ROLE_ADMIN_ID=1164572260607205486 + +MINECRAFT_SERVER_IP=valloic.dev:25565 +MINECRAFT_WHITELIST_PATH=/opt/minecraft/server/whitelist.json \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b512c09 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/README.md b/README.md index 341b566..0ce87e9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,133 @@ -# mc-acces-bot +# Bot Discord - Accès Serveur Minecraft -Bot privé pour gérer mon serveur Minecraft. \ No newline at end of file +Ce bot Discord permet de gérer automatiquement les demandes d'accès à un serveur Minecraft via un système de whitelist. + +## Fonctionnalités + +### 🎮 Système de demande d'accès +- Message automatique avec bouton pour demander l'accès +- Modal pour saisir le pseudo Minecraft +- Vérification automatique si le joueur est déjà whitelisté +- Envoi de la demande au canal staff avec boutons Accept/Refuse + +### ⚙️ Commandes administrateur +- `/minecraft list` - Affiche la liste des joueurs whitelistés +- `/minecraft add ` - Ajoute un joueur à la whitelist +- `/minecraft remove ` - Retire un joueur de la whitelist +- `/minecraft status` - Affiche le statut du serveur Minecraft +- `/setup-access` - Envoie le message de demande d'accès + +## Installation + +1. **Cloner le projet et installer les dépendances :** +```bash +npm install +``` + +2. **Configurer le fichier .env :** +Assurez-vous que votre fichier `.env` contient toutes les variables nécessaires : +```env +TOKEN=votre_token_bot_discord +CLIENT_ID=id_du_bot +OWNER_ID=votre_id_discord + +SUPPORT_SERVER_ID=id_du_serveur_discord +CHANNEL_REQUEST_ACCESS_ID=id_canal_demandes_publiques +CHANNEL_ACCESS_STAFF_REQUESTS_ID=id_canal_staff_demandes + +ROLE_WAITER_ID=id_role_attente +ROLE_VERIFIED_ID=id_role_verifie +ROLE_ADMIN_ID=id_role_admin + +MINECRAFT_SERVER_IP=adresse:port +MINECRAFT_WHITELIST_PATH=/chemin/vers/whitelist.json +``` + +3. **Lancer le bot :** +```bash +npm start +``` + +Pour le développement : +```bash +npm run dev +``` + +## Structure du projet + +``` +├── start.js # Point d'entrée principal +├── src/ +│ ├── commands/ +│ │ ├── minecraft.js # Commandes /minecraft +│ │ └── setup-access.js # Commande /setup-access +│ ├── events/ +│ │ └── ready.js # Événement de démarrage +│ ├── handlers/ +│ │ ├── commandHandler.js # Chargeur de commandes +│ │ ├── eventHandler.js # Chargeur d'événements +│ │ └── interactionHandler.js # Gestionnaire d'interactions +│ └── utils/ +│ ├── accessRequestManager.js # Gestionnaire de demandes d'accès +│ ├── minecraftServer.js # Utilitaires serveur Minecraft +│ └── whitelistManager.js # Gestionnaire de whitelist +``` + +## Utilisation + +### Configuration initiale + +1. **Inviter le bot sur votre serveur** avec les permissions nécessaires +2. **Utiliser `/setup-access`** dans le canal où vous voulez le message de demande d'accès +3. **Configurer les rôles** dans votre serveur Discord selon les IDs dans le `.env` + +### Workflow des demandes + +1. **Utilisateur :** Clique sur le bouton "Demander l'accès" +2. **Utilisateur :** Remplit le modal avec son pseudo Minecraft +3. **Système :** Vérifie si le joueur est déjà whitelisté +4. **Système :** Envoie la demande au canal staff +5. **Staff :** Clique sur "Accepter" ou "Refuser" +6. **Système :** Ajoute le joueur à la whitelist (si accepté) et notifie l'utilisateur + +### Commandes administrateur + +Seul l'OWNER_ID peut utiliser les commandes `/minecraft` : + +- **Lister les joueurs :** `/minecraft list` +- **Ajouter un joueur :** `/minecraft add pseudo:NomDuJoueur` +- **Retirer un joueur :** `/minecraft remove pseudo:NomDuJoueur` +- **Status du serveur :** `/minecraft status` + +## Permissions requises + +Le bot a besoin des permissions Discord suivantes : +- `Send Messages` - Envoyer des messages +- `Use Slash Commands` - Utiliser les commandes slash +- `Embed Links` - Incorporer des liens +- `Manage Messages` - Gérer les messages +- `Read Message History` - Lire l'historique des messages + +## Notes importantes + +- Le fichier `whitelist.json` doit être accessible en lecture/écriture +- Le serveur Minecraft doit être accessible pour la vérification du statut +- Les IDs Discord dans le `.env` doivent être valides +- Le bot vérifie automatiquement les pseudos via l'API Mojang + +## Dépannage + +### Le bot ne répond pas aux commandes +- Vérifiez que le TOKEN est correct +- Vérifiez que les permissions sont accordées +- Consultez les logs dans la console + +### Erreurs de whitelist +- Vérifiez le chemin vers `whitelist.json` +- Vérifiez les permissions de fichier +- Assurez-vous que le format JSON est valide + +### Problèmes de serveur Minecraft +- Vérifiez que l'adresse IP et le port sont corrects +- Assurez-vous que le serveur est en ligne +- Vérifiez les paramètres du pare-feu \ No newline at end of file diff --git a/emojis.json b/emojis.json new file mode 100644 index 0000000..e5ae6e4 --- /dev/null +++ b/emojis.json @@ -0,0 +1,9 @@ +{ + "added_whitelist_emoji": "<:securityenable:1429980226351534090>", + "removed_whitelist_emoji": "<:securitydisable:1429980224984055960>", + "refused_whitelist_emoji": "<:securitydisable:1429980224984055960>", + "pending_whitelist_emoji": "<:pending:1429980711481245768>", + "error_emoji": "<:error:1429980784076521614>", + "info_emoji": "<:info:1429981238130774016>", + "success_emoji": "" +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..7707a27 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "mc-access-bot", + "version": "1.0.0", + "description": "Bot Discord pour gérer l'accès au serveur Minecraft", + "main": "start.js", + "scripts": { + "start": "node start.js", + "dev": "nodemon start.js" + }, + "keywords": [ + "discord", + "minecraft", + "bot", + "whitelist" + ], + "author": "", + "license": "MIT", + "dependencies": { + "discord.js": "^14.14.1", + "dotenv": "^16.3.1", + "minecraft-server-util": "^5.4.3", + "node-fetch": "^3.3.2" + }, + "devDependencies": { + "nodemon": "^3.0.2" + } +} \ No newline at end of file diff --git a/src/commands/minecraft.js b/src/commands/minecraft.js new file mode 100644 index 0000000..a48006c --- /dev/null +++ b/src/commands/minecraft.js @@ -0,0 +1,126 @@ +const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js'); +const WhitelistManager = require('../utils/whitelistManager'); +const MinecraftServerUtil = require('../utils/minecraftServer'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('minecraft') + .setDescription('Commandes de gestion du serveur Minecraft') + .addSubcommand(subcommand => + subcommand + .setName('list') + .setDescription('Affiche la liste des joueurs whitelistés') + ) + .addSubcommand(subcommand => + subcommand + .setName('add') + .setDescription('Ajoute un joueur à la whitelist') + .addStringOption(option => + option + .setName('pseudo') + .setDescription('Le pseudo Minecraft du joueur à ajouter') + .setRequired(true) + ) + ) + .addSubcommand(subcommand => + subcommand + .setName('remove') + .setDescription('Retire un joueur de la whitelist') + .addStringOption(option => + option + .setName('pseudo') + .setDescription('Le pseudo Minecraft du joueur à retirer') + .setRequired(true) + ) + ) + .addSubcommand(subcommand => + subcommand + .setName('status') + .setDescription('Affiche le status du serveur Minecraft') + ), + + async execute(interaction) { + // Vérifier si l'utilisateur est le propriétaire + if (interaction.user.id !== process.env.OWNER_ID) { + return await interaction.reply({ + content: '❌ Vous n\'avez pas la permission d\'utiliser cette commande.', + ephemeral: true + }); + } + + const subcommand = interaction.options.getSubcommand(); + const whitelistManager = new WhitelistManager(); + const serverUtil = new MinecraftServerUtil(); + + await interaction.deferReply(); + + try { + switch (subcommand) { + case 'list': { + const players = await whitelistManager.getWhitelistPlayers(); + + const embed = new EmbedBuilder() + .setTitle('📋 Liste des joueurs whitelistés') + .setColor('#0099FF') + .setTimestamp(); + + if (players.length === 0) { + embed.setDescription('Aucun joueur dans la whitelist.'); + } else { + const playerList = players.join('\n'); + embed.setDescription(`**${players.length} joueur(s) whitelisté(s) :**\n\`\`\`\n${playerList}\n\`\`\``); + } + + await interaction.editReply({ embeds: [embed] }); + break; + } + + case 'add': { + const pseudo = interaction.options.getString('pseudo'); + const result = await whitelistManager.addPlayer(pseudo); + + const embed = new EmbedBuilder() + .setTitle(result.success ? '✅ Joueur ajouté' : '❌ Erreur') + .setDescription(result.message) + .setColor(result.success ? '#00FF00' : '#FF0000') + .setTimestamp(); + + await interaction.editReply({ embeds: [embed] }); + break; + } + + case 'remove': { + const pseudo = interaction.options.getString('pseudo'); + const result = await whitelistManager.removePlayer(pseudo); + + const embed = new EmbedBuilder() + .setTitle(result.success ? '✅ Joueur retiré' : '❌ Erreur') + .setDescription(result.message) + .setColor(result.success ? '#00FF00' : '#FF0000') + .setTimestamp(); + + await interaction.editReply({ embeds: [embed] }); + break; + } + + case 'status': { + const statusData = await serverUtil.getServerStatus(); + const embed = serverUtil.formatStatusEmbed(statusData); + + await interaction.editReply({ embeds: [embed] }); + break; + } + } + } catch (error) { + console.error('Erreur dans la commande minecraft:', error); + + const errorEmbed = new EmbedBuilder() + .setTitle('❌ Erreur') + .setDescription('Une erreur est survenue lors de l\'exécution de la commande.') + .setColor('#FF0000') + .setTimestamp(); + + await interaction.editReply({ embeds: [errorEmbed] }); + } + } +}; \ No newline at end of file diff --git a/src/commands/setup-access.js b/src/commands/setup-access.js new file mode 100644 index 0000000..ebde88c --- /dev/null +++ b/src/commands/setup-access.js @@ -0,0 +1,40 @@ +const { SlashCommandBuilder } = require('discord.js'); +const AccessRequestManager = require('../utils/accessRequestManager'); + +module.exports = { + data: new SlashCommandBuilder() + .setName('setup-access') + .setDescription('Envoie le message de demande d\'accès dans le canal actuel'), + + async execute(interaction) { + // Vérifier si l'utilisateur est le propriétaire + if (interaction.user.id !== process.env.OWNER_ID) { + return await interaction.reply({ + content: '❌ Vous n\'avez pas la permission d\'utiliser cette commande.', + ephemeral: true + }); + } + + try { + const embed = AccessRequestManager.createRequestEmbed(); + const button = AccessRequestManager.createRequestButton(); + + await interaction.reply({ + content: '✅ Message de demande d\'accès envoyé !', + ephemeral: true + }); + + await interaction.followUp({ + embeds: [embed], + components: [button] + }); + + } catch (error) { + console.error('Erreur lors de l\'envoi du message de demande d\'accès:', error); + await interaction.reply({ + content: '❌ Erreur lors de l\'envoi du message.', + ephemeral: true + }); + } + } +}; \ No newline at end of file diff --git a/src/events/ready.js b/src/events/ready.js new file mode 100644 index 0000000..4c4bac7 --- /dev/null +++ b/src/events/ready.js @@ -0,0 +1,47 @@ +const { REST, Routes } = require('discord.js'); + +module.exports = { + name: 'ready', + once: true, + async execute(client) { + console.log(`🚀 ${client.user.tag} est maintenant en ligne !`); + console.log(`📊 Connecté à ${client.guilds.cache.size} serveur(s)`); + console.log(`👥 ${client.users.cache.size} utilisateur(s) dans le cache`); + + // Enregistrer les commandes slash + try { + console.log('🔄 Début de l\'actualisation des commandes slash...'); + + const commands = []; + for (const command of client.commands.values()) { + commands.push(command.data.toJSON()); + } + + const rest = new REST().setToken(process.env.TOKEN); + + // Pour un serveur spécifique (plus rapide pendant le développement) + if (process.env.SUPPORT_SERVER_ID) { + await rest.put( + Routes.applicationGuildCommands(process.env.CLIENT_ID, process.env.SUPPORT_SERVER_ID), + { body: commands } + ); + console.log(`✅ ${commands.length} commande(s) slash enregistrée(s) pour le serveur de support.`); + } + + // Pour tous les serveurs (global) - décommentez si nécessaire + /* + await rest.put( + Routes.applicationCommands(process.env.CLIENT_ID), + { body: commands } + ); + console.log(`✅ ${commands.length} commande(s) slash enregistrée(s) globalement.`); + */ + + } catch (error) { + console.error('❌ Erreur lors de l\'enregistrement des commandes slash:', error); + } + + // Définir le statut du bot + client.user.setActivity(`${process.env.MINECRAFT_SERVER_IP}`, { type: 'WATCHING' }); + }, +}; \ No newline at end of file diff --git a/src/handlers/commandHandler.js b/src/handlers/commandHandler.js new file mode 100644 index 0000000..dddfae5 --- /dev/null +++ b/src/handlers/commandHandler.js @@ -0,0 +1,26 @@ +const fs = require('fs'); +const path = require('path'); +const { Collection } = require('discord.js'); + +async function loadCommands(client) { + client.commands = new Collection(); + + const commandsPath = path.join(__dirname, '../commands'); + const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js')); + + for (const file of commandFiles) { + const filePath = path.join(commandsPath, file); + const command = require(filePath); + + if ('data' in command && 'execute' in command) { + client.commands.set(command.data.name, command); + console.log(`✅ Commande chargée: ${command.data.name}`); + } else { + console.log(`⚠️ La commande ${filePath} n'a pas les propriétés "data" ou "execute" requises.`); + } + } + + console.log(`📝 ${commandFiles.length} commande(s) chargée(s).`); +} + +module.exports = { loadCommands }; \ No newline at end of file diff --git a/src/handlers/eventHandler.js b/src/handlers/eventHandler.js new file mode 100644 index 0000000..c2eb7cb --- /dev/null +++ b/src/handlers/eventHandler.js @@ -0,0 +1,35 @@ +const fs = require('fs'); +const path = require('path'); + +async function loadEvents(client) { + const eventsPath = path.join(__dirname, '../events'); + + // Créer le dossier events s'il n'existe pas + if (!fs.existsSync(eventsPath)) { + fs.mkdirSync(eventsPath, { recursive: true }); + } + + const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js')); + + for (const file of eventFiles) { + const filePath = path.join(eventsPath, file); + const event = require(filePath); + + if (event.once) { + client.once(event.name, (...args) => event.execute(...args)); + } else { + client.on(event.name, (...args) => event.execute(...args)); + } + + console.log(`✅ Événement chargé: ${event.name}`); + } + + // Charger directement l'handler d'interactions + const interactionHandler = require('./interactionHandler'); + client.on('interactionCreate', (...args) => interactionHandler.execute(...args)); + console.log('✅ Handler d\'interactions chargé'); + + console.log(`📝 ${eventFiles.length + 1} événement(s) chargé(s).`); +} + +module.exports = { loadEvents }; \ No newline at end of file diff --git a/src/handlers/interactionHandler.js b/src/handlers/interactionHandler.js new file mode 100644 index 0000000..9fc7a58 --- /dev/null +++ b/src/handlers/interactionHandler.js @@ -0,0 +1,191 @@ +const { ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder } = require('discord.js'); +const AccessRequestManager = require('../utils/accessRequestManager'); +const WhitelistManager = require('../utils/whitelistManager'); + +module.exports = { + name: 'interactionCreate', + async execute(interaction) { + if (interaction.isChatInputCommand()) { + const command = interaction.client.commands.get(interaction.commandName); + + if (!command) { + console.error(`Aucune commande correspondant à ${interaction.commandName} n'a été trouvée.`); + return; + } + + try { + await command.execute(interaction); + } catch (error) { + console.error('Erreur lors de l\'exécution de la commande:', error); + const reply = { + content: 'Il y a eu une erreur lors de l\'exécution de cette commande !', + ephemeral: true + }; + if (interaction.replied || interaction.deferred) { + await interaction.followUp(reply); + } else { + await interaction.reply(reply); + } + } + } else if (interaction.isButton()) { + await handleButtonInteraction(interaction); + } else if (interaction.isModalSubmit()) { + await handleModalSubmit(interaction); + } + }, +}; + +async function handleButtonInteraction(interaction) { + if (interaction.customId === 'request_access') { + // Créer le modal pour demander le pseudo Minecraft + const modal = new ModalBuilder() + .setCustomId('minecraft_access_modal') + .setTitle('Demande d\'accès Minecraft'); + + const usernameInput = new TextInputBuilder() + .setCustomId('minecraft_username') + .setLabel('Votre pseudo Minecraft') + .setStyle(TextInputStyle.Short) + .setPlaceholder('Entrez votre pseudo Minecraft exact...') + .setRequired(true) + .setMaxLength(16) + .setMinLength(3); + + const firstActionRow = new ActionRowBuilder().addComponents(usernameInput); + modal.addComponents(firstActionRow); + + await interaction.showModal(modal); + } else if (interaction.customId.startsWith('accept_request_') || interaction.customId.startsWith('deny_request_')) { + await handleStaffResponse(interaction); + } +} + +async function handleModalSubmit(interaction) { + if (interaction.customId === 'minecraft_access_modal') { + const username = interaction.fields.getTextInputValue('minecraft_username'); + + await interaction.deferReply({ ephemeral: true }); + + try { + // Vérifier si le joueur est déjà whitelisté + const whitelistManager = new WhitelistManager(); + const isAlreadyWhitelisted = await whitelistManager.isPlayerWhitelisted(username); + + // Envoyer la demande au canal du staff + const staffChannel = interaction.client.channels.cache.get(process.env.CHANNEL_ACCESS_STAFF_REQUESTS_ID); + + if (!staffChannel) { + return await interaction.editReply({ + content: '❌ Erreur : Canal du staff introuvable. Contactez un administrateur.' + }); + } + + const embed = AccessRequestManager.createStaffEmbed(interaction.user, username, isAlreadyWhitelisted); + const buttons = AccessRequestManager.createStaffButtons(interaction.user.id, username, isAlreadyWhitelisted); + + await staffChannel.send({ + embeds: [embed], + components: [buttons] + }); + + // Répondre à l'utilisateur + let responseMessage = '✅ Votre demande d\'accès a été envoyée à l\'équipe administrative !'; + + if (isAlreadyWhitelisted) { + responseMessage += '\n\n⚠️ **Note :** Vous êtes déjà dans la whitelist du serveur.'; + } + + await interaction.editReply({ + content: responseMessage + }); + + } catch (error) { + console.error('Erreur lors du traitement de la demande d\'accès:', error); + await interaction.editReply({ + content: '❌ Une erreur est survenue lors du traitement de votre demande. Veuillez réessayer.' + }); + } + } +} + +async function handleStaffResponse(interaction) { + // Vérifier si l'utilisateur a le rôle admin + if (!interaction.member.roles.cache.has(process.env.ROLE_ADMIN_ID)) { + return await interaction.reply({ + content: '❌ Vous n\'avez pas la permission de traiter les demandes d\'accès.', + ephemeral: true + }); + } + + const [action, , userId, username] = interaction.customId.split('_'); + const isAccepted = action === 'accept'; + + await interaction.deferReply({ ephemeral: true }); + + try { + const whitelistManager = new WhitelistManager(); + let result = { success: true, message: '' }; + + if (isAccepted) { + // Ajouter le joueur à la whitelist + result = await whitelistManager.addPlayer(username); + } + + // Envoyer une réponse à l'utilisateur qui a fait la demande + try { + const user = await interaction.client.users.fetch(userId); + const responseEmbed = AccessRequestManager.createResponseEmbed( + isAccepted && result.success, + username, + isAccepted && !result.success ? result.message : null + ); + + await user.send({ embeds: [responseEmbed] }); + } catch (error) { + console.error('Impossible d\'envoyer un MP à l\'utilisateur:', error); + } + + // Mettre à jour le message original + const originalEmbed = interaction.message.embeds[0]; + const updatedEmbed = { + ...originalEmbed.toJSON(), + color: isAccepted && result.success ? 0x00FF00 : 0xFF0000, + fields: [ + ...originalEmbed.fields, + { + name: isAccepted ? '✅ Accepté par' : '❌ Refusé par', + value: `${interaction.user.tag} ()`, + inline: false + } + ] + }; + + if (isAccepted && !result.success) { + updatedEmbed.fields.push({ + name: '⚠️ Erreur', + value: result.message, + inline: false + }); + } + + await interaction.message.edit({ + embeds: [updatedEmbed], + components: [] // Retirer les boutons + }); + + // Répondre au staff member + const statusMessage = isAccepted + ? (result.success ? `✅ Demande acceptée et ${username} ajouté à la whitelist.` : `❌ Erreur lors de l'ajout : ${result.message}`) + : `❌ Demande refusée pour ${username}.`; + + await interaction.editReply({ + content: statusMessage + }); + + } catch (error) { + console.error('Erreur lors du traitement de la réponse du staff:', error); + await interaction.editReply({ + content: '❌ Une erreur est survenue lors du traitement de la demande.' + }); + } +} \ No newline at end of file diff --git a/src/utils/accessRequestManager.js b/src/utils/accessRequestManager.js new file mode 100644 index 0000000..1a22d4f --- /dev/null +++ b/src/utils/accessRequestManager.js @@ -0,0 +1,110 @@ +const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); + +class AccessRequestManager { + static createRequestEmbed() { + return new EmbedBuilder() + .setTitle('🎮 Accès au serveur Minecraft') + .setDescription( + 'Bienvenue ! Pour rejoindre notre serveur Minecraft, vous devez être ajouté à la whitelist.\n\n' + + '**Comment procéder :**\n' + + '1️⃣ Cliquez sur le bouton "Demander l\'accès" ci-dessous\n' + + '2️⃣ Entrez votre pseudo Minecraft exact\n' + + '3️⃣ Attendez l\'approbation d\'un administrateur\n\n' + + '**Informations importantes :**\n' + + '• Votre pseudo doit être valide et correspondre à votre compte Minecraft\n' + + '• Si vous êtes déjà whitelisté, le système vous en informera\n' + + '• Les demandes sont traitées par l\'équipe administrative\n\n' + + `**Adresse du serveur :** \`${process.env.MINECRAFT_SERVER_IP}\`` + ) + .setColor('#00FF00') + .setThumbnail('https://i.imgur.com/YSO5z0H.png') // Image Minecraft + .setFooter({ text: 'Serveur Minecraft • Demande d\'accès' }) + .setTimestamp(); + } + + static createRequestButton() { + return new ActionRowBuilder() + .addComponents( + new ButtonBuilder() + .setCustomId('request_access') + .setLabel('Demander l\'accès') + .setStyle(ButtonStyle.Primary) + .setEmoji('🎮') + ); + } + + static createStaffEmbed(user, username, isAlreadyWhitelisted = false) { + const embed = new EmbedBuilder() + .setTitle('📋 Nouvelle demande d\'accès Minecraft') + .setDescription(`${user} souhaite rejoindre le serveur Minecraft.`) + .addFields( + { name: '👤 Utilisateur Discord', value: `${user.tag} (${user.id})`, inline: true }, + { name: '🎮 Pseudo Minecraft', value: `\`${username}\``, inline: true }, + { name: '📅 Date de la demande', value: ``, inline: false } + ) + .setThumbnail(user.displayAvatarURL({ dynamic: true })) + .setTimestamp(); + + if (isAlreadyWhitelisted) { + embed.setColor('#FFA500') + .addFields({ name: '⚠️ Avertissement', value: 'Ce joueur est déjà dans la whitelist !', inline: false }); + } else { + embed.setColor('#0099FF'); + } + + return embed; + } + + static createStaffButtons(userId, username, isAlreadyWhitelisted = false) { + const row = new ActionRowBuilder(); + + if (!isAlreadyWhitelisted) { + row.addComponents( + new ButtonBuilder() + .setCustomId(`accept_request_${userId}_${username}`) + .setLabel('Accepter') + .setStyle(ButtonStyle.Success) + .setEmoji('✅') + ); + } + + row.addComponents( + new ButtonBuilder() + .setCustomId(`deny_request_${userId}_${username}`) + .setLabel('Refuser') + .setStyle(ButtonStyle.Danger) + .setEmoji('❌') + ); + + return row; + } + + static createResponseEmbed(accepted, username, reason = null) { + const embed = new EmbedBuilder() + .setTimestamp(); + + if (accepted) { + embed.setTitle('✅ Demande acceptée !') + .setDescription( + `Félicitations ! Votre demande d'accès au serveur Minecraft a été **acceptée**.\n\n` + + `**Pseudo ajouté :** \`${username}\`\n` + + `**Adresse du serveur :** \`${process.env.MINECRAFT_SERVER_IP}\`\n\n` + + `Vous pouvez maintenant vous connecter au serveur !` + ) + .setColor('#00FF00'); + } else { + embed.setTitle('❌ Demande refusée') + .setDescription( + `Votre demande d'accès au serveur Minecraft a été **refusée**.\n\n` + + `**Pseudo demandé :** \`${username}\`\n` + + (reason ? `**Raison :** ${reason}\n\n` : '') + + `Vous pouvez faire une nouvelle demande si nécessaire.` + ) + .setColor('#FF0000'); + } + + return embed; + } +} + +module.exports = AccessRequestManager; \ No newline at end of file diff --git a/src/utils/minecraftServer.js b/src/utils/minecraftServer.js new file mode 100644 index 0000000..db04906 --- /dev/null +++ b/src/utils/minecraftServer.js @@ -0,0 +1,82 @@ +const { status } = require('minecraft-server-util'); + +class MinecraftServerUtil { + constructor() { + this.serverIP = process.env.MINECRAFT_SERVER_IP; + this.host = this.serverIP.split(':')[0]; + this.port = parseInt(this.serverIP.split(':')[1]) || 25565; + } + + async getServerStatus() { + try { + const result = await status(this.host, this.port); + + return { + online: true, + version: result.version.name, + protocol: result.version.protocol, + players: { + online: result.players.online, + max: result.players.max, + list: result.players.sample || [] + }, + description: result.description.text || result.description, + latency: result.roundTripLatency, + favicon: result.favicon + }; + } catch (error) { + console.error('Erreur lors de la vérification du serveur:', error); + return { + online: false, + error: error.message + }; + } + } + + async isServerOnline() { + const status = await this.getServerStatus(); + return status.online; + } + + formatStatusEmbed(statusData) { + const { EmbedBuilder } = require('discord.js'); + + if (!statusData.online) { + return new EmbedBuilder() + .setTitle('🔴 Serveur Minecraft - Hors ligne') + .setDescription(`Le serveur \`${this.serverIP}\` est actuellement hors ligne.`) + .addFields( + { name: 'Erreur', value: statusData.error || 'Serveur inaccessible', inline: true } + ) + .setColor('#FF0000') + .setTimestamp(); + } + + const embed = new EmbedBuilder() + .setTitle('🟢 Serveur Minecraft - En ligne') + .setDescription(`Le serveur \`${this.serverIP}\` est en ligne !`) + .addFields( + { name: '👥 Joueurs', value: `${statusData.players.online}/${statusData.players.max}`, inline: true }, + { name: '📋 Version', value: statusData.version, inline: true }, + { name: '📡 Latence', value: `${statusData.latency}ms`, inline: true } + ) + .setColor('#00FF00') + .setTimestamp(); + + if (statusData.description) { + embed.addFields({ name: '📝 Description', value: statusData.description, inline: false }); + } + + if (statusData.players.list && statusData.players.list.length > 0) { + const playerList = statusData.players.list + .slice(0, 10) // Limiter à 10 joueurs + .map(player => player.name) + .join(', '); + embed.addFields({ name: '🎮 Joueurs connectés', value: playerList, inline: false }); + } + + return embed; + } +} + +module.exports = MinecraftServerUtil; \ No newline at end of file diff --git a/src/utils/whitelistManager.js b/src/utils/whitelistManager.js new file mode 100644 index 0000000..fde95b9 --- /dev/null +++ b/src/utils/whitelistManager.js @@ -0,0 +1,119 @@ +const fs = require('fs').promises; +const path = require('path'); + +class WhitelistManager { + constructor() { + this.whitelistPath = process.env.MINECRAFT_WHITELIST_PATH; + } + + async readWhitelist() { + try { + const data = await fs.readFile(this.whitelistPath, 'utf8'); + return JSON.parse(data); + } catch (error) { + console.error('Erreur lors de la lecture de la whitelist:', error); + return []; + } + } + + async writeWhitelist(whitelist) { + try { + await fs.writeFile(this.whitelistPath, JSON.stringify(whitelist, null, 2)); + return true; + } catch (error) { + console.error('Erreur lors de l\'écriture de la whitelist:', error); + return false; + } + } + + async isPlayerWhitelisted(username) { + const whitelist = await this.readWhitelist(); + return whitelist.some(player => + player.name.toLowerCase() === username.toLowerCase() + ); + } + + async addPlayer(username, uuid = null) { + try { + const whitelist = await this.readWhitelist(); + + // Vérifier si le joueur existe déjà + if (await this.isPlayerWhitelisted(username)) { + return { success: false, message: 'Le joueur est déjà dans la whitelist.' }; + } + + // Si pas d'UUID fourni, essayer de le récupérer via l'API Mojang + if (!uuid) { + uuid = await this.getUUIDFromUsername(username); + if (!uuid) { + return { success: false, message: 'Impossible de trouver l\'UUID pour ce joueur.' }; + } + } + + // Ajouter le joueur + whitelist.push({ + uuid: uuid, + name: username + }); + + const success = await this.writeWhitelist(whitelist); + if (success) { + return { success: true, message: `${username} a été ajouté à la whitelist.` }; + } else { + return { success: false, message: 'Erreur lors de l\'ajout du joueur.' }; + } + } catch (error) { + console.error('Erreur lors de l\'ajout du joueur:', error); + return { success: false, message: 'Erreur interne lors de l\'ajout du joueur.' }; + } + } + + async removePlayer(username) { + try { + const whitelist = await this.readWhitelist(); + const initialLength = whitelist.length; + + const newWhitelist = whitelist.filter(player => + player.name.toLowerCase() !== username.toLowerCase() + ); + + if (newWhitelist.length === initialLength) { + return { success: false, message: 'Le joueur n\'est pas dans la whitelist.' }; + } + + const success = await this.writeWhitelist(newWhitelist); + if (success) { + return { success: true, message: `${username} a été retiré de la whitelist.` }; + } else { + return { success: false, message: 'Erreur lors de la suppression du joueur.' }; + } + } catch (error) { + console.error('Erreur lors de la suppression du joueur:', error); + return { success: false, message: 'Erreur interne lors de la suppression du joueur.' }; + } + } + + async getWhitelistPlayers() { + const whitelist = await this.readWhitelist(); + return whitelist.map(player => player.name); + } + + async getUUIDFromUsername(username) { + try { + const fetch = (await import('node-fetch')).default; + const response = await fetch(`https://api.mojang.com/users/profiles/minecraft/${username}`); + + if (!response.ok) { + return null; + } + + const data = await response.json(); + return data.id; + } catch (error) { + console.error('Erreur lors de la récupération de l\'UUID:', error); + return null; + } + } +} + +module.exports = WhitelistManager; \ No newline at end of file diff --git a/start.js b/start.js new file mode 100644 index 0000000..f0f2429 --- /dev/null +++ b/start.js @@ -0,0 +1,33 @@ +require('dotenv').config(); +const { Client, GatewayIntentBits } = require('discord.js'); +const { loadCommands } = require('./src/handlers/commandHandler'); +const { loadEvents } = require('./src/handlers/eventHandler'); + +const client = new Client({ + intents: [ + GatewayIntentBits.Guilds, + GatewayIntentBits.GuildMessages, + GatewayIntentBits.MessageContent, + GatewayIntentBits.GuildMembers + ] +}); + +async function startBot() { + try { + console.log('🚀 Démarrage du bot...'); + + // Charger les commandes et événements + await loadCommands(client); + await loadEvents(client); + + // Connexion du bot + await client.login(process.env.TOKEN); + + console.log('✅ Bot connecté avec succès !'); + } catch (error) { + console.error('❌ Erreur lors du démarrage du bot:', error); + process.exit(1); + } +} + +startBot(); \ No newline at end of file