diff --git a/.env b/.env index 28e018d..5058525 100644 --- a/.env +++ b/.env @@ -11,4 +11,10 @@ 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 +MINECRAFT_WHITELIST_PATH=/opt/minecraft/server/whitelist.json + +USE_SCREEN=true +SERVER_JAR=fabric-server.jar +JAVA_ARGS=-Xmx2G -Xms1G +USE_SCREEN=true +MINECRAFT_SERVER_PATH=/opt/minecraft/server \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..915b73b --- /dev/null +++ b/.env.example @@ -0,0 +1,32 @@ +# Configuration du bot Discord +DISCORD_TOKEN=votre_token_discord +CLIENT_ID=votre_client_id +GUILD_ID=votre_guild_id +OWNER_ID=votre_user_id + +# Configuration des rôles et channels +ROLE_WAITER_ID=id_du_role_attente +ROLE_VERIFIED_ID=id_du_role_verifie +CHANNEL_REQUEST_ACCESS_ID=id_channel_demandes +CHANNEL_LOGS_ID=id_channel_logs + +# Configuration du serveur Minecraft +MINECRAFT_SERVER_HOST=votre_serveur.com +MINECRAFT_SERVER_PORT=25565 +MINECRAFT_WHITELIST_PATH=/chemin/vers/whitelist.json +MINECRAFT_SERVICE_NAME=minecraft + +# Configuration du mode serveur +USE_SCREEN=true +# true = utilise screen (plus flexible, commandes directes) +# false = utilise systemctl (plus robuste, redémarrage pour whitelist) + +# Configuration des chemins serveur (pour VPS) +MINECRAFT_SERVER_PATH=/opt/minecraft/server +MINECRAFT_BACKUP_PATH=/opt/minecraft/backups + +# Configuration Java et serveur +SERVER_JAR=fabric-server.jar +# Autres options: server.jar, paper.jar, spigot.jar, forge-server.jar +JAVA_ARGS=-Xmx2G -Xms1G +# Arguments Java personnalisés \ No newline at end of file diff --git a/README.md b/README.md index 0ce87e9..9cb2836 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,272 @@ -# Bot Discord - Accès Serveur Minecraft +# Bot Discord - Gestion Serveur Minecraft -Ce bot Discord permet de gérer automatiquement les demandes d'accès à un serveur Minecraft via un système de whitelist. +Un bot Discord complet pour gérer la whitelist et le contrôle d'un serveur Minecraft hébergé sur VPS. + +## 🚀 Fonctionnalités + +### 🎯 Système de demandes d'accès automatique +- Message automatique avec bouton de demande d'accès +- Attribution automatique du rôle "En attente" +- Mise à jour du statut serveur toutes les 2 minutes +- Interface avec embeds et emojis personnalisés + +### 👥 Gestion de la whitelist +- Ajout/suppression de joueurs avec récupération automatique d'UUID +- Rechargement automatique de la whitelist sur le serveur +- Liste des joueurs whitelistés +- Formatage correct des UUID pour Minecraft + +### 🎮 Contrôle complet du serveur +- Démarrage/arrêt/redémarrage via systemctl +- Envoi de commandes directes au serveur +- Consultation des logs en temps réel +- Sauvegarde automatique du monde +- Vérification du statut en temps réel + +### 🎨 Interface utilisateur +- Emojis personnalisés configurables +- Embeds Discord avec couleurs et formatage +- Messages d'erreur informatifs +- Gestion des permissions par propriétaire + +## 📋 Prérequis + +- **Node.js** v16+ +- **Serveur Minecraft** configuré avec systemctl +- **Bot Discord** avec permissions appropriées +- **Accès sudo** sur le VPS (pour contrôle serveur) + +## 🛠️ Installation + +### 1. Sur votre VPS +```bash +# Cloner et installer (ou uploader vos fichiers) +cd /home/votre-user/bots/ +# ... copier vos fichiers ici ... +cd mcgestionbot +npm install +``` + +### 2. Configuration automatique +```bash +# Génération automatique du .env +./configure.sh + +# Éditer avec vos tokens Discord +nano .env +``` + +### 3. Lancement +```bash +# Le bot configure automatiquement tout au démarrage ! +node start.js +``` + +**C'est tout ! 🎉** Le bot se charge de : +- ✅ Créer l'utilisateur `minecraft` +- ✅ Configurer les permissions +- ✅ Activer la whitelist dans server.properties +- ✅ Créer les dossiers nécessaires +- ✅ Configurer les sauvegardes + +## 🎮 Utilisation + +### Démarrage du bot +```bash +node start.js +``` + +### Commandes disponibles + +#### `/minecraft` - Gestion du serveur (Owner only) + +**Gestion whitelist :** +- `/minecraft list` - Liste des joueurs whitelistés +- `/minecraft add ` - Ajouter un joueur +- `/minecraft remove ` - Retirer un joueur +- `/minecraft reload` - Recharger la whitelist + +**Contrôle serveur :** +- `/minecraft status` - Statut du serveur +- `/minecraft start` - Démarrer le serveur +- `/minecraft stop` - Arrêter le serveur +- `/minecraft restart` - Redémarrer le serveur +- `/minecraft logs [lines]` - Afficher les logs +- `/minecraft command ` - Envoyer une commande +- `/minecraft backup` - Sauvegarder le monde +- `/minecraft setup` - Configuration automatique du serveur + +### Système de demandes automatique + +Le bot gère automatiquement : +1. **Message persistant** dans le channel configuré +2. **Bouton de demande** toujours disponible +3. **Attribution du rôle** "En attente" automatique +4. **Mise à jour du statut** serveur toutes les 2 minutes + +## 🎨 Personnalisation + +### Emojis personnalisés +Modifiez `emojis.json` pour vos emojis : + +```json +{ + "online": "<:online:123456789>", + "offline": "<:offline:123456789>", + "addedWhitelist": "<:added:123456789>", + "removedWhitelist": "<:removed:123456789>", + "success": "✅", + "error": "❌", + "info": "ℹ️" +} +``` + +## 🔧 Dépannage + +### Problèmes courants + +**1. "Command not found" node** +```bash +# Installer Node.js +curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - +sudo apt-get install -y nodejs +``` + +**2. "Permission denied" systemctl** +```bash +# Configurer sudo +sudo ./setup-sudo.sh +./test-permissions.sh +``` + +**3. "Whitelist not enforced"** +```bash +# Vérifier server.properties +grep "enforce-whitelist" /opt/minecraft/server/server.properties +# Doit être : enforce-whitelist=true +``` + +**4. "UUID format incorrect"** +- Le bot formate automatiquement les UUID +- Utilise l'API Mojang pour récupérer les UUID + +### Logs et debug + +```bash +# Logs du bot +node start.js + +# Logs du serveur Minecraft +sudo journalctl -u minecraft -f + +# Test des permissions +./test-permissions.sh + +# Test de la whitelist +node test-whitelist.js +``` + +## 📁 Structure du projet + +``` +mc-acces-bot/ +├── start.js # Point d'entrée +├── package.json # Dépendances +├── .env # Configuration +├── emojis.json # Emojis personnalisés +├── setup-sudo.sh # Config permissions +├── test-permissions.sh # Test sudo +└── src/ + ├── commands/ + │ └── minecraft.js # Commandes serveur + ├── events/ + │ ├── ready.js # Démarrage bot + │ └── interactionCreate.js # Gestion interactions + ├── handlers/ + │ └── interactionHandler.js # Boutons/modals + └── utils/ + ├── whitelistManager.js # Gestion whitelist + ├── minecraftServer.js # Statut serveur + ├── serverControlManager.js # Contrôle VPS + ├── autoMessageManager.js # Messages auto + └── emojiManager.js # Gestion emojis +``` + +## 🚀 Déploiement en production + +### Avec PM2 (recommandé) +```bash +# Installer PM2 +npm install -g pm2 + +# Démarrer le bot +pm2 start start.js --name "mc-bot" + +# Configurer auto-start +pm2 startup +pm2 save +``` + +### Avec systemctl +```bash +# Créer le service +sudo nano /etc/systemd/system/mc-bot.service + +# Contenu : +[Unit] +Description=Minecraft Discord Bot +After=network.target + +[Service] +Type=simple +User=loicv +WorkingDirectory=/home/loicv/Documents/Wors/mc-acces-bot +ExecStart=/usr/bin/node start.js +Restart=always +Environment=NODE_ENV=production + +[Install] +WantedBy=multi-user.target + +# Activer le service +sudo systemctl enable mc-bot +sudo systemctl start mc-bot +``` + +## 📝 Notes importantes + +- **Sécurité :** Le bot a accès sudo - utilisez uniquement sur un serveur dédié +- **Permissions :** Seul le propriétaire (OWNER_ID) peut utiliser les commandes +- **Backup :** Les sauvegardes sont stockées dans `/opt/minecraft/backups/` +- **Logs :** Tous les logs sont visibles via `journalctl` et dans Discord + +## 🤝 Support + +En cas de problème : +1. Vérifiez les logs : `sudo journalctl -u minecraft -f` +2. Testez les permissions : `./test-permissions.sh` +3. Vérifiez la configuration : fichier `.env` complet +4. Validez le service : `sudo systemctl status minecraft` ## Fonctionnalités ### 🎮 Système de demande d'accès -- Message automatique avec bouton pour demander l'accès +- **Message simple automatique** avec bouton pour demander l'accès +- **Embed de status du serveur** mis à jour automatiquement toutes les 2 minutes - Modal pour saisir le pseudo Minecraft +- **Attribution automatique du rôle "Waiter"** lors de la demande - Vérification automatique si le joueur est déjà whitelisté - Envoi de la demande au canal staff avec boutons Accept/Refuse +- **Gestion automatique des rôles** : Verified (accepté) ou suppression du Waiter (refusé) +- **Emojis personnalisés** pour tous les messages ### ⚙️ 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 +- `/minecraft reload` - Instructions pour recharger la whitelist sur le serveur +- `/setup-access` - Envoie le message de demande d'accès (facultatif) ## Installation @@ -53,10 +304,26 @@ Pour le développement : npm run dev ``` +Pour tester le formatage UUID : +```bash +npm run test-uuid +``` + +Pour tester la connexion au serveur Minecraft : +```bash +npm run test-server +``` + +Pour tester la whitelist : +```bash +npm run test-whitelist +``` + ## Structure du projet ``` ├── start.js # Point d'entrée principal +├── emojis.json # Configuration des emojis personnalisés ├── src/ │ ├── commands/ │ │ ├── minecraft.js # Commandes /minecraft @@ -69,6 +336,8 @@ npm run dev │ │ └── interactionHandler.js # Gestionnaire d'interactions │ └── utils/ │ ├── accessRequestManager.js # Gestionnaire de demandes d'accès +│ ├── emojiManager.js # Gestionnaire d'emojis personnalisés +│ ├── roleManager.js # Gestionnaire de rôles Discord │ ├── minecraftServer.js # Utilitaires serveur Minecraft │ └── whitelistManager.js # Gestionnaire de whitelist ``` @@ -78,17 +347,22 @@ npm run dev ### 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 +2. **Le message de demande d'accès est envoyé automatiquement** au démarrage dans le canal configuré 3. **Configurer les rôles** dans votre serveur Discord selon les IDs dans le `.env` +> ⚠️ **Note :** Il n'est plus nécessaire d'utiliser `/setup-access` manuellement. Le bot gère automatiquement l'envoi et la mise à jour du message de demande d'accès. + ### 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 +2. **Système :** Attribue automatiquement le rôle "Waiter" à l'utilisateur +3. **Utilisateur :** Remplit le modal avec son pseudo Minecraft +4. **Système :** Vérifie si le joueur est déjà whitelisté +5. **Système :** Envoie la demande au canal staff avec emojis personnalisés +6. **Staff :** Clique sur "Accepter" ou "Refuser" +7. **Système (si accepté) :** Ajoute le joueur à la whitelist + donne le rôle "Verified" + retire le rôle "Waiter" +8. **Système (si refusé) :** Retire simplement le rôle "Waiter" +9. **Système :** Notifie l'utilisateur par MP avec les emojis appropriés ### Commandes administrateur @@ -107,6 +381,14 @@ Le bot a besoin des permissions Discord suivantes : - `Embed Links` - Incorporer des liens - `Manage Messages` - Gérer les messages - `Read Message History` - Lire l'historique des messages +- `Manage Roles` - Gérer les rôles (pour attribuer Waiter/Verified) + +## Configuration des rôles + +Assurez-vous que ces rôles existent sur votre serveur Discord : +- **Rôle Waiter** (`ROLE_WAITER_ID`) : Attribué automatiquement lors d'une demande +- **Rôle Verified** (`ROLE_VERIFIED_ID`) : Attribué quand l'accès est accepté +- **Rôle Admin** (`ROLE_ADMIN_ID`) : Requis pour traiter les demandes d'accès ## Notes importantes @@ -127,7 +409,27 @@ Le bot a besoin des permissions Discord suivantes : - Vérifiez les permissions de fichier - Assurez-vous que le format JSON est valide +### Problèmes d'UUID +- Les UUIDs sont automatiquement formatés au bon format (avec tirets) +- Si un joueur n'est pas trouvé, vérifiez que le pseudo existe sur Minecraft +- Utilisez `npm run test-uuid` pour tester le formatage +- Format attendu: `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` + ### 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 +- Vérifiez les paramètres du pare-feu +- Utilisez `npm run test-server` pour tester la connexion +- Le timeout de connexion est de 10 secondes + +### Messages automatiques +- Le message de demande d'accès est envoyé automatiquement au démarrage +- Les anciens messages sont supprimés automatiquement +- Un seul message actif est maintenu par canal +- **Le status du serveur se met à jour automatiquement toutes les 2 minutes** + +### Configuration serveur Minecraft +- Assurez-vous que `white-list=true` dans server.properties +- **IMPORTANT**: `enforce-whitelist=true` doit être activé pour que la whitelist soit appliquée +- Utilisez `/whitelist reload` dans la console serveur après modifications +- Ou utilisez la commande `/minecraft reload` du bot pour avoir les instructions \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..17296ea --- /dev/null +++ b/package-lock.json @@ -0,0 +1,814 @@ +{ + "name": "mc-access-bot", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mc-access-bot", + "version": "1.0.0", + "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" + } + }, + "node_modules/@discordjs/builders": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.12.2.tgz", + "integrity": "sha512-AugKmrgRJOHXEyMkABH/hXpAmIBKbYokCEl9VAM4Kh6FvkdobQ+Zhv7AR6K+y5hS7+VQ7gKXPYCe1JQmV07H1g==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/formatters": "^0.6.1", + "@discordjs/util": "^1.1.1", + "@sapphire/shapeshift": "^4.0.0", + "discord-api-types": "^0.38.26", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/collection": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", + "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.1.tgz", + "integrity": "sha512-5cnX+tASiPCqCWtFcFslxBVUaCetB0thvM/JyavhbXInP1HJIEU+Qv/zMrnuwSsX3yWH2lVXNJZeDK3EiP4HHg==", + "license": "Apache-2.0", + "dependencies": { + "discord-api-types": "^0.38.1" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz", + "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.1", + "@discordjs/util": "^1.1.1", + "@sapphire/async-queue": "^1.5.3", + "@sapphire/snowflake": "^3.5.3", + "@vladfrangu/async_event_emitter": "^2.4.6", + "discord-api-types": "^0.38.16", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/util": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.1.1.tgz", + "integrity": "sha512-eddz6UnOBEB1oITPinyrB2Pttej49M9FZQY8NxgEvc3tq6ZICZ19m70RsmzRdDHk80O9NoYN/25AqJl8vPVf/g==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", + "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.5.1", + "@discordjs/util": "^1.1.0", + "@sapphire/async-queue": "^1.5.2", + "@types/ws": "^8.5.10", + "@vladfrangu/async_event_emitter": "^2.2.4", + "discord-api-types": "^0.38.1", + "tslib": "^2.6.2", + "ws": "^8.17.0" + }, + "engines": { + "node": ">=16.11.0" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws/node_modules/@discordjs/collection": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", + "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", + "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@sapphire/shapeshift": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", + "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v16" + } + }, + "node_modules/@sapphire/snowflake": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", + "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@types/node": { + "version": "24.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", + "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", + "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", + "license": "MIT", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/discord-api-types": { + "version": "0.38.30", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.30.tgz", + "integrity": "sha512-KhAqlBrg+rVK+Ob7INMF5o63yW4/GUzRatG/AjyVsIO8lgcLyR8qCl2HokIVzWwmzkJYG0CEPXsKMOqau3E8NA==", + "license": "MIT", + "workspaces": [ + "scripts/actions/documentation" + ] + }, + "node_modules/discord.js": { + "version": "14.23.2", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.23.2.tgz", + "integrity": "sha512-tU2NFr823X3TXEc8KyR/4m296KLxPai4nirN3q9kHCpY4TKj96n9lHZnyLzRNMui8EbL07jg9hgH2PWWfKMGIg==", + "license": "Apache-2.0", + "dependencies": { + "@discordjs/builders": "^1.12.1", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.6.1", + "@discordjs/rest": "^2.6.0", + "@discordjs/util": "^1.1.1", + "@discordjs/ws": "^1.2.3", + "@sapphire/snowflake": "3.5.3", + "discord-api-types": "^0.38.29", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.21.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lodash.snakecase": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", + "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", + "license": "MIT" + }, + "node_modules/magic-bytes.js": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz", + "integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==", + "license": "MIT" + }, + "node_modules/minecraft-motd-util": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/minecraft-motd-util/-/minecraft-motd-util-1.1.12.tgz", + "integrity": "sha512-5TuTRjrRupSTruea0nRC37r0FdhkS1O4wIJKAYfwJRCQd/X4Zyl/dVIs96h9UVW6N8jhIuz9pNkrDsqyN7VBdA==", + "deprecated": "no longer maintained", + "license": "MIT" + }, + "node_modules/minecraft-server-util": { + "version": "5.4.4", + "resolved": "https://registry.npmjs.org/minecraft-server-util/-/minecraft-server-util-5.4.4.tgz", + "integrity": "sha512-uaE0N9PHPtJudi8BlCvKDD9K30w23ZpeS1I53wEJo9nX0HSmg5qFvwj8gMG0CYCUwWH/g5hjKaGAwXXN/vhMJw==", + "deprecated": "no longer maintained, please use https://mcstatus.io", + "license": "MIT", + "dependencies": { + "minecraft-motd-util": "^1.1.9" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/nodemon": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz", + "integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "6.21.3", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", + "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json index 7707a27..219cf53 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,10 @@ "main": "start.js", "scripts": { "start": "node start.js", - "dev": "nodemon start.js" + "dev": "nodemon start.js", + "test-uuid": "node test-uuid.js", + "test-server": "node test-server.js", + "test-whitelist": "node test-whitelist.js" }, "keywords": [ "discord", diff --git a/src/commands/minecraft.js b/src/commands/minecraft.js index a48006c..d021191 100644 --- a/src/commands/minecraft.js +++ b/src/commands/minecraft.js @@ -1,6 +1,8 @@ const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits } = require('discord.js'); const WhitelistManager = require('../utils/whitelistManager'); const MinecraftServerUtil = require('../utils/minecraftServer'); +const ServerControlManager = require('../utils/serverControlManager'); +const emojiManager = require('../utils/emojiManager'); module.exports = { data: new SlashCommandBuilder() @@ -37,13 +39,66 @@ module.exports = { subcommand .setName('status') .setDescription('Affiche le status du serveur Minecraft') + ) + .addSubcommand(subcommand => + subcommand + .setName('reload') + .setDescription('Recharge la whitelist du serveur') + ) + .addSubcommand(subcommand => + subcommand + .setName('start') + .setDescription('Démarre le serveur Minecraft') + ) + .addSubcommand(subcommand => + subcommand + .setName('stop') + .setDescription('Arrête le serveur Minecraft') + ) + .addSubcommand(subcommand => + subcommand + .setName('restart') + .setDescription('Redémarre le serveur Minecraft') + ) + .addSubcommand(subcommand => + subcommand + .setName('logs') + .setDescription('Affiche les logs du serveur') + .addIntegerOption(option => + option + .setName('lines') + .setDescription('Nombre de lignes à afficher (défaut: 20)') + .setMinValue(1) + .setMaxValue(100) + ) + ) + .addSubcommand(subcommand => + subcommand + .setName('command') + .setDescription('Envoie une commande au serveur') + .addStringOption(option => + option + .setName('cmd') + .setDescription('La commande à envoyer') + .setRequired(true) + ) + ) + .addSubcommand(subcommand => + subcommand + .setName('backup') + .setDescription('Sauvegarde le monde du serveur') + ) + .addSubcommand(subcommand => + subcommand + .setName('setup') + .setDescription('Configure automatiquement le serveur pour le bot') ), 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.', + content: `${emojiManager.error} Vous n'avez pas la permission d'utiliser cette commande.`, ephemeral: true }); } @@ -51,6 +106,7 @@ module.exports = { const subcommand = interaction.options.getSubcommand(); const whitelistManager = new WhitelistManager(); const serverUtil = new MinecraftServerUtil(); + const serverControl = new ServerControlManager(); await interaction.deferReply(); @@ -60,7 +116,7 @@ module.exports = { const players = await whitelistManager.getWhitelistPlayers(); const embed = new EmbedBuilder() - .setTitle('📋 Liste des joueurs whitelistés') + .setTitle(`${emojiManager.info} Liste des joueurs whitelistés`) .setColor('#0099FF') .setTimestamp(); @@ -73,14 +129,12 @@ module.exports = { await interaction.editReply({ embeds: [embed] }); break; - } - - case 'add': { + } case 'add': { const pseudo = interaction.options.getString('pseudo'); const result = await whitelistManager.addPlayer(pseudo); const embed = new EmbedBuilder() - .setTitle(result.success ? '✅ Joueur ajouté' : '❌ Erreur') + .setTitle(result.success ? `${emojiManager.addedWhitelist} Joueur ajouté` : `${emojiManager.error} Erreur`) .setDescription(result.message) .setColor(result.success ? '#00FF00' : '#FF0000') .setTimestamp(); @@ -94,7 +148,7 @@ module.exports = { const result = await whitelistManager.removePlayer(pseudo); const embed = new EmbedBuilder() - .setTitle(result.success ? '✅ Joueur retiré' : '❌ Erreur') + .setTitle(result.success ? `${emojiManager.removedWhitelist} Joueur retiré` : `${emojiManager.error} Erreur`) .setDescription(result.message) .setColor(result.success ? '#00FF00' : '#FF0000') .setTimestamp(); @@ -110,12 +164,149 @@ module.exports = { await interaction.editReply({ embeds: [embed] }); break; } + + case 'reload': { + const result = await serverControl.reloadWhitelist(); + + const embed = new EmbedBuilder() + .setTitle(result.success ? `${emojiManager.success} Whitelist rechargée` : `${emojiManager.error} Erreur`) + .setDescription(result.message) + .setColor(result.success ? '#00FF00' : '#FF0000') + .setTimestamp(); + + await interaction.editReply({ embeds: [embed] }); + break; + } + + case 'start': { + const result = await serverControl.startServer(); + + const embed = new EmbedBuilder() + .setTitle(result.success ? `${emojiManager.success} Serveur démarré` : `${emojiManager.error} Erreur`) + .setDescription(result.message) + .setColor(result.success ? '#00FF00' : '#FF0000') + .setTimestamp(); + + await interaction.editReply({ embeds: [embed] }); + break; + } + + case 'stop': { + const result = await serverControl.stopServer(); + + const embed = new EmbedBuilder() + .setTitle(result.success ? `${emojiManager.success} Serveur arrêté` : `${emojiManager.error} Erreur`) + .setDescription(result.message) + .setColor(result.success ? '#00FF00' : '#FF0000') + .setTimestamp(); + + await interaction.editReply({ embeds: [embed] }); + break; + } + + case 'restart': { + const result = await serverControl.restartServer(); + + const embed = new EmbedBuilder() + .setTitle(result.success ? `${emojiManager.success} Serveur redémarré` : `${emojiManager.error} Erreur`) + .setDescription(result.message) + .setColor(result.success ? '#00FF00' : '#FF0000') + .setTimestamp(); + + await interaction.editReply({ embeds: [embed] }); + break; + } + + case 'logs': { + const lines = interaction.options.getInteger('lines') || 20; + const result = await serverControl.getServerLogs(lines); + + if (!result.success) { + const embed = new EmbedBuilder() + .setTitle(`${emojiManager.error} Erreur`) + .setDescription(`Impossible de récupérer les logs: ${result.error}`) + .setColor('#FF0000') + .setTimestamp(); + + await interaction.editReply({ embeds: [embed] }); + break; + } + + // Limiter la taille des logs pour Discord + let logs = result.logs; + if (logs.length > 4000) { + logs = logs.substring(logs.length - 4000); + logs = '...\n' + logs.substring(logs.indexOf('\n') + 1); + } + + const embed = new EmbedBuilder() + .setTitle(`${emojiManager.info} Logs du serveur (${lines} lignes)`) + .setDescription(`\`\`\`\n${logs}\n\`\`\``) + .setColor('#0099FF') + .setTimestamp(); + + await interaction.editReply({ embeds: [embed] }); + break; + } + + case 'command': { + const cmd = interaction.options.getString('cmd'); + const result = await serverControl.sendServerCommand(cmd); + + const embed = new EmbedBuilder() + .setTitle(result.success ? `${emojiManager.success} Commande envoyée` : `${emojiManager.error} Erreur`) + .setDescription(result.message) + .addFields({ name: 'Commande', value: `\`${cmd}\``, inline: false }) + .setColor(result.success ? '#00FF00' : '#FF0000') + .setTimestamp(); + + await interaction.editReply({ embeds: [embed] }); + break; + } + + case 'backup': { + const result = await serverControl.backupWorld(); + + const embed = new EmbedBuilder() + .setTitle(result.success ? `${emojiManager.success} Sauvegarde créée` : `${emojiManager.error} Erreur`) + .setDescription(result.message) + .setColor(result.success ? '#00FF00' : '#FF0000') + .setTimestamp(); + + if (result.backupPath) { + embed.addFields({ name: 'Fichier', value: `\`${result.backupPath}\``, inline: false }); + } + + await interaction.editReply({ embeds: [embed] }); + break; + } + + case 'setup': { + const result = await serverControl.setupServerForBot(); + + const embed = new EmbedBuilder() + .setTitle(result.success ? `${emojiManager.success} Configuration terminée` : `${emojiManager.error} Erreur`) + .setDescription(result.message) + .setColor(result.success ? '#00FF00' : '#FF0000') + .setTimestamp(); + + if (result.details && result.details.length > 0) { + embed.addFields({ + name: 'Détails de la configuration', + value: result.details.join('\n'), + inline: false + }); + } + + await interaction.editReply({ embeds: [embed] }); + break; + } } } catch (error) { console.error('Erreur dans la commande minecraft:', error); const errorEmbed = new EmbedBuilder() - .setTitle('❌ Erreur') + .setTitle(`${emojiManager.error} Erreur`) .setDescription('Une erreur est survenue lors de l\'exécution de la commande.') .setColor('#FF0000') .setTimestamp(); diff --git a/src/commands/setup-access.js b/src/commands/setup-access.js deleted file mode 100644 index ebde88c..0000000 --- a/src/commands/setup-access.js +++ /dev/null @@ -1,40 +0,0 @@ -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 index 4c4bac7..743fc31 100644 --- a/src/events/ready.js +++ b/src/events/ready.js @@ -1,4 +1,5 @@ const { REST, Routes } = require('discord.js'); +const AutoMessageManager = require('../utils/autoMessageManager'); module.exports = { name: 'ready', @@ -41,7 +42,24 @@ module.exports = { console.error('❌ Erreur lors de l\'enregistrement des commandes slash:', error); } + // Initialiser le gestionnaire de messages automatiques + try { + // Configuration automatique du serveur au démarrage + console.log('🔧 Configuration automatique du serveur...'); + const ServerControlManager = require('../utils/serverControlManager'); + const serverControl = new ServerControlManager(); + await serverControl.setupServerForBot(); + console.log('✅ Configuration du serveur terminée'); + + const autoMessageManager = new AutoMessageManager(client); + await autoMessageManager.initialize(); + } catch (error) { + console.error('❌ Erreur lors de l\'initialisation:', error); + } + // Définir le statut du bot client.user.setActivity(`${process.env.MINECRAFT_SERVER_IP}`, { type: 'WATCHING' }); + + console.log('🎮 Bot prêt et opérationnel !'); }, }; \ No newline at end of file diff --git a/src/handlers/interactionHandler.js b/src/handlers/interactionHandler.js index 9fc7a58..61dcebb 100644 --- a/src/handlers/interactionHandler.js +++ b/src/handlers/interactionHandler.js @@ -1,6 +1,8 @@ const { ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder } = require('discord.js'); const AccessRequestManager = require('../utils/accessRequestManager'); const WhitelistManager = require('../utils/whitelistManager'); +const RoleManager = require('../utils/roleManager'); +const emojiManager = require('../utils/emojiManager'); module.exports = { name: 'interactionCreate', @@ -67,6 +69,10 @@ async function handleModalSubmit(interaction) { await interaction.deferReply({ ephemeral: true }); try { + // Donner le rôle waiter à l'utilisateur + const guildMember = interaction.member; + await RoleManager.addWaiterRole(guildMember); + // Vérifier si le joueur est déjà whitelisté const whitelistManager = new WhitelistManager(); const isAlreadyWhitelisted = await whitelistManager.isPlayerWhitelisted(username); @@ -76,7 +82,7 @@ async function handleModalSubmit(interaction) { if (!staffChannel) { return await interaction.editReply({ - content: '❌ Erreur : Canal du staff introuvable. Contactez un administrateur.' + content: `${emojiManager.error} Erreur : Canal du staff introuvable. Contactez un administrateur.` }); } @@ -89,10 +95,10 @@ async function handleModalSubmit(interaction) { }); // Répondre à l'utilisateur - let responseMessage = '✅ Votre demande d\'accès a été envoyée à l\'équipe administrative !'; + let responseMessage = `${emojiManager.success} 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.'; + responseMessage += `\n\n${emojiManager.error} **Note :** Vous êtes déjà dans la whitelist du serveur.`; } await interaction.editReply({ @@ -102,7 +108,7 @@ async function handleModalSubmit(interaction) { } 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.' + content: `${emojiManager.error} Une erreur est survenue lors du traitement de votre demande. Veuillez réessayer.` }); } } diff --git a/src/utils/accessRequestManager.js b/src/utils/accessRequestManager.js index 1a22d4f..a18e6e9 100644 --- a/src/utils/accessRequestManager.js +++ b/src/utils/accessRequestManager.js @@ -1,9 +1,10 @@ const { EmbedBuilder, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js'); +const emojiManager = require('./emojiManager'); class AccessRequestManager { static createRequestEmbed() { return new EmbedBuilder() - .setTitle('🎮 Accès au serveur Minecraft') + .setTitle(`${emojiManager.info} Accès au serveur Minecraft`) .setDescription( 'Bienvenue ! Pour rejoindre notre serveur Minecraft, vous devez être ajouté à la whitelist.\n\n' + '**Comment procéder :**\n' + @@ -17,7 +18,7 @@ class AccessRequestManager { `**Adresse du serveur :** \`${process.env.MINECRAFT_SERVER_IP}\`` ) .setColor('#00FF00') - .setThumbnail('https://i.imgur.com/YSO5z0H.png') // Image Minecraft + .setThumbnail("https://cdn.discordapp.com/avatars/870707972215550014/b8e97150ca7e455c7f112d37daf1a9ab.webp?size=128") .setFooter({ text: 'Serveur Minecraft • Demande d\'accès' }) .setTimestamp(); } @@ -35,7 +36,7 @@ class AccessRequestManager { static createStaffEmbed(user, username, isAlreadyWhitelisted = false) { const embed = new EmbedBuilder() - .setTitle('📋 Nouvelle demande d\'accès Minecraft') + .setTitle(`${emojiManager.pending} Nouvelle demande d'accès Minecraft`) .setDescription(`${user} souhaite rejoindre le serveur Minecraft.`) .addFields( { name: '👤 Utilisateur Discord', value: `${user.tag} (${user.id})`, inline: true }, @@ -47,7 +48,7 @@ class AccessRequestManager { if (isAlreadyWhitelisted) { embed.setColor('#FFA500') - .addFields({ name: '⚠️ Avertissement', value: 'Ce joueur est déjà dans la whitelist !', inline: false }); + .addFields({ name: `${emojiManager.error} Avertissement`, value: 'Ce joueur est déjà dans la whitelist !', inline: false }); } else { embed.setColor('#0099FF'); } @@ -84,7 +85,7 @@ class AccessRequestManager { .setTimestamp(); if (accepted) { - embed.setTitle('✅ Demande acceptée !') + embed.setTitle(`${emojiManager.addedWhitelist} Demande acceptée !`) .setDescription( `Félicitations ! Votre demande d'accès au serveur Minecraft a été **acceptée**.\n\n` + `**Pseudo ajouté :** \`${username}\`\n` + @@ -93,7 +94,7 @@ class AccessRequestManager { ) .setColor('#00FF00'); } else { - embed.setTitle('❌ Demande refusée') + embed.setTitle(`${emojiManager.refusedWhitelist} Demande refusée`) .setDescription( `Votre demande d'accès au serveur Minecraft a été **refusée**.\n\n` + `**Pseudo demandé :** \`${username}\`\n` + diff --git a/src/utils/autoMessageManager.js b/src/utils/autoMessageManager.js new file mode 100644 index 0000000..4b5d50c --- /dev/null +++ b/src/utils/autoMessageManager.js @@ -0,0 +1,198 @@ +const AccessRequestManager = require('./accessRequestManager'); +const MinecraftServerUtil = require('./minecraftServer'); +const emojiManager = require('./emojiManager'); + +class AutoMessageManager { + constructor(client) { + this.client = client; + this.statusUpdateInterval = null; + this.lastMessageId = null; + } + + async setupAccessRequestMessage() { + try { + const channelId = process.env.CHANNEL_REQUEST_ACCESS_ID; + if (!channelId) { + console.error('❌ CHANNEL_REQUEST_ACCESS_ID non défini dans .env'); + return false; + } + + const channel = this.client.channels.cache.get(channelId); + if (!channel) { + console.error(`❌ Canal non trouvé: ${channelId}`); + return false; + } + + console.log(`🔍 Vérification du canal ${channel.name}...`); + + // Récupérer les messages récents du canal + const messages = await channel.messages.fetch({ limit: 50 }); + + // Chercher un message existant du bot avec le bouton de demande d'accès + const existingMessage = messages.find(msg => + msg.author.id === this.client.user.id && + msg.content?.includes('Cliquez sur le bouton ci-dessous pour demander l\'accès') && + msg.components.length > 0 + ); + + if (existingMessage) { + console.log(`✅ Message de demande d'accès déjà présent (ID: ${existingMessage.id})`); + this.lastMessageId = existingMessage.id; + // Mettre à jour le message avec le status actuel + await this.updateServerStatus(); + return true; + } + + // Si aucun message existant, en créer un nouveau + console.log('📝 Création d\'un nouveau message de demande d\'accès...'); + + const content = `${emojiManager.info} **Demande d'accès au serveur Minecraft**\n\nCliquez sur le bouton ci-dessous pour demander l'accès au serveur Minecraft. Vous devrez fournir votre pseudo Minecraft exact.`; + const button = AccessRequestManager.createRequestButton(); + const statusEmbed = await this.createServerStatusEmbed(); + + const sentMessage = await channel.send({ + content: content, + embeds: [statusEmbed], + components: [button] + }); + + this.lastMessageId = sentMessage.id; + console.log(`${emojiManager.success} Message de demande d'accès créé avec succès (ID: ${sentMessage.id})`); + + // Démarrer la mise à jour automatique du status + this.startStatusUpdates(); + + return true; + + } catch (error) { + console.error('❌ Erreur lors de la configuration du message de demande d\'accès:', error); + return false; + } + } + + async cleanOldAccessMessages() { + try { + const channelId = process.env.CHANNEL_REQUEST_ACCESS_ID; + if (!channelId) return; + + const channel = this.client.channels.cache.get(channelId); + if (!channel) return; + + // Récupérer les messages récents + const messages = await channel.messages.fetch({ limit: 100 }); + + // Trouver tous les messages du bot avec des embeds de demande d'accès + const botAccessMessages = messages.filter(msg => + msg.author.id === this.client.user.id && + msg.embeds.length > 0 && + msg.embeds[0].title?.includes('Accès au serveur Minecraft') + ).sort((a, b) => b.createdTimestamp - a.createdTimestamp); + + // Garder seulement le plus récent, supprimer les autres + if (botAccessMessages.size > 1) { + console.log(`🧹 Suppression de ${botAccessMessages.size - 1} ancien(s) message(s) de demande d'accès...`); + + const messagesToDelete = botAccessMessages.slice(1); + for (const message of messagesToDelete.values()) { + try { + await message.delete(); + console.log(`🗑️ Message supprimé: ${message.id}`); + } catch (error) { + console.error(`Erreur lors de la suppression du message ${message.id}:`, error); + } + } + } + + } catch (error) { + console.error('Erreur lors du nettoyage des anciens messages:', error); + } + } + + async initialize() { + console.log('🚀 Initialisation du gestionnaire de messages automatiques...'); + + // Nettoyer les anciens messages d'abord + await this.cleanOldAccessMessages(); + + // Puis configurer le message actuel + await this.setupAccessRequestMessage(); + } + + async createServerStatusEmbed() { + const serverUtil = new MinecraftServerUtil(); + const statusData = await serverUtil.getServerStatus(); + + const { EmbedBuilder } = require('discord.js'); + + if (!statusData.online) { + return new EmbedBuilder() + .setTitle(`${emojiManager.error} Serveur Hors ligne`) + .setDescription(`Le serveur \`${process.env.MINECRAFT_SERVER_IP}\` est actuellement inaccessible.`) + .setColor('#FF0000') + .setFooter({ text: 'Dernière mise à jour' }) + .setTimestamp(); + } + + return new EmbedBuilder() + .setTitle(`${emojiManager.success} Serveur En ligne`) + .setDescription(`**${process.env.MINECRAFT_SERVER_IP}**`) + .addFields( + { name: '👥 Joueurs connectés', 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') + .setFooter({ text: 'Mise à jour automatique toutes les 2 minutes' }) + .setTimestamp(); + } + + async updateServerStatus() { + try { + if (!this.lastMessageId) return; + + const channelId = process.env.CHANNEL_REQUEST_ACCESS_ID; + const channel = this.client.channels.cache.get(channelId); + if (!channel) return; + + const message = await channel.messages.fetch(this.lastMessageId); + if (!message) return; + + const statusEmbed = await this.createServerStatusEmbed(); + + await message.edit({ + content: message.content, + embeds: [statusEmbed], + components: message.components + }); + + console.log('🔄 Status du serveur mis à jour'); + + } catch (error) { + console.error('Erreur lors de la mise à jour du status:', error); + } + } + + startStatusUpdates() { + // Nettoyer l'ancien interval si il existe + if (this.statusUpdateInterval) { + clearInterval(this.statusUpdateInterval); + } + + // Démarrer la mise à jour toutes les 2 minutes (120000 ms) + this.statusUpdateInterval = setInterval(() => { + this.updateServerStatus(); + }, 120000); + + console.log('🕐 Mise à jour automatique du status démarrée (toutes les 2 minutes)'); + } + + stopStatusUpdates() { + if (this.statusUpdateInterval) { + clearInterval(this.statusUpdateInterval); + this.statusUpdateInterval = null; + console.log('⏹️ Mise à jour automatique du status arrêtée'); + } + } +} + +module.exports = AutoMessageManager; \ No newline at end of file diff --git a/src/utils/emojiManager.js b/src/utils/emojiManager.js new file mode 100644 index 0000000..b0aa0ed --- /dev/null +++ b/src/utils/emojiManager.js @@ -0,0 +1,46 @@ +const fs = require('fs'); +const path = require('path'); + +class EmojiManager { + constructor() { + this.emojis = this.loadEmojis(); + } + + loadEmojis() { + try { + const emojiPath = path.join(__dirname, '../../emojis.json'); + const data = fs.readFileSync(emojiPath, 'utf8'); + return JSON.parse(data); + } catch (error) { + console.error('Erreur lors du chargement des emojis:', error); + // Emojis de fallback si le fichier n'est pas trouvé + return { + added_whitelist_emoji: "✅", + removed_whitelist_emoji: "❌", + refused_whitelist_emoji: "❌", + pending_whitelist_emoji: "⏳", + error_emoji: "❌", + info_emoji: "ℹ️", + success_emoji: "✅" + }; + } + } + + get(emojiName) { + return this.emojis[emojiName] || "❓"; + } + + // Méthodes de raccourci pour les emojis fréquemment utilisés + get success() { return this.get('success_emoji'); } + get error() { return this.get('error_emoji'); } + get info() { return this.get('info_emoji'); } + get pending() { return this.get('pending_whitelist_emoji'); } + get addedWhitelist() { return this.get('added_whitelist_emoji'); } + get removedWhitelist() { return this.get('removed_whitelist_emoji'); } + get refusedWhitelist() { return this.get('refused_whitelist_emoji'); } +} + +// Instance singleton +const emojiManager = new EmojiManager(); + +module.exports = emojiManager; \ No newline at end of file diff --git a/src/utils/minecraftServer.js b/src/utils/minecraftServer.js index db04906..efe9e96 100644 --- a/src/utils/minecraftServer.js +++ b/src/utils/minecraftServer.js @@ -9,23 +9,39 @@ class MinecraftServerUtil { async getServerStatus() { try { - const result = await status(this.host, this.port); + console.log(`🔍 Vérification du serveur ${this.host}:${this.port}...`); + const result = await status(this.host, this.port, { timeout: 10000 }); + console.log('✅ Réponse du serveur reçue'); + + // Gérer la description qui peut être un objet complexe + let description = 'Serveur Minecraft'; + if (result.description) { + if (typeof result.description === 'string') { + description = result.description; + } else if (result.description.text) { + description = result.description.text; + } else if (result.description.extra) { + description = result.description.extra.map(part => part.text || '').join(''); + } else { + description = JSON.stringify(result.description); + } + } return { online: true, - version: result.version.name, - protocol: result.version.protocol, + version: result.version?.name || 'Inconnu', + protocol: result.version?.protocol || 0, players: { - online: result.players.online, - max: result.players.max, - list: result.players.sample || [] + online: result.players?.online || 0, + max: result.players?.max || 0, + list: result.players?.sample || [] }, - description: result.description.text || result.description, - latency: result.roundTripLatency, + description: description, + latency: result.roundTripLatency || 0, favicon: result.favicon }; } catch (error) { - console.error('Erreur lors de la vérification du serveur:', error); + console.error('❌ Erreur lors de la vérification du serveur:', error); return { online: false, error: error.message @@ -40,10 +56,11 @@ class MinecraftServerUtil { formatStatusEmbed(statusData) { const { EmbedBuilder } = require('discord.js'); + const emojiManager = require('./emojiManager'); if (!statusData.online) { return new EmbedBuilder() - .setTitle('🔴 Serveur Minecraft - Hors ligne') + .setTitle(`${emojiManager.error} Serveur Minecraft - Hors ligne`) .setDescription(`Le serveur \`${this.serverIP}\` est actuellement hors ligne.`) .addFields( { name: 'Erreur', value: statusData.error || 'Serveur inaccessible', inline: true } @@ -53,7 +70,7 @@ class MinecraftServerUtil { } const embed = new EmbedBuilder() - .setTitle('🟢 Serveur Minecraft - En ligne') + .setTitle(`${emojiManager.success} Serveur Minecraft - En ligne`) .setDescription(`Le serveur \`${this.serverIP}\` est en ligne !`) .addFields( { name: '👥 Joueurs', value: `${statusData.players.online}/${statusData.players.max}`, inline: true }, @@ -63,8 +80,12 @@ class MinecraftServerUtil { .setColor('#00FF00') .setTimestamp(); - if (statusData.description) { - embed.addFields({ name: '📝 Description', value: statusData.description, inline: false }); + if (statusData.description && statusData.description !== 'Serveur Minecraft') { + // Limiter la taille de la description pour éviter les erreurs Discord + const description = statusData.description.length > 1024 + ? statusData.description.substring(0, 1021) + '...' + : statusData.description; + embed.addFields({ name: '📝 Description', value: description, inline: false }); } if (statusData.players.list && statusData.players.list.length > 0) { diff --git a/src/utils/roleManager.js b/src/utils/roleManager.js new file mode 100644 index 0000000..458ab31 --- /dev/null +++ b/src/utils/roleManager.js @@ -0,0 +1,103 @@ +class RoleManager { + static async addWaiterRole(member) { + try { + const waiterRoleId = process.env.ROLE_WAITER_ID; + if (!waiterRoleId) { + console.error('ROLE_WAITER_ID non défini dans .env'); + return false; + } + + const waiterRole = member.guild.roles.cache.get(waiterRoleId); + if (!waiterRole) { + console.error(`Rôle waiter non trouvé: ${waiterRoleId}`); + return false; + } + + if (!member.roles.cache.has(waiterRoleId)) { + await member.roles.add(waiterRole); + console.log(`✅ Rôle waiter ajouté à ${member.user.tag}`); + } + return true; + } catch (error) { + console.error('Erreur lors de l\'ajout du rôle waiter:', error); + return false; + } + } + + static async addVerifiedRole(member) { + try { + const verifiedRoleId = process.env.ROLE_VERIFIED_ID; + if (!verifiedRoleId) { + console.error('ROLE_VERIFIED_ID non défini dans .env'); + return false; + } + + const verifiedRole = member.guild.roles.cache.get(verifiedRoleId); + if (!verifiedRole) { + console.error(`Rôle verified non trouvé: ${verifiedRoleId}`); + return false; + } + + if (!member.roles.cache.has(verifiedRoleId)) { + await member.roles.add(verifiedRole); + console.log(`✅ Rôle verified ajouté à ${member.user.tag}`); + } + return true; + } catch (error) { + console.error('Erreur lors de l\'ajout du rôle verified:', error); + return false; + } + } + + static async removeWaiterRole(member) { + try { + const waiterRoleId = process.env.ROLE_WAITER_ID; + if (!waiterRoleId) { + console.error('ROLE_WAITER_ID non défini dans .env'); + return false; + } + + if (member.roles.cache.has(waiterRoleId)) { + await member.roles.remove(waiterRoleId); + console.log(`🗑️ Rôle waiter retiré de ${member.user.tag}`); + } + return true; + } catch (error) { + console.error('Erreur lors de la suppression du rôle waiter:', error); + return false; + } + } + + static async removeVerifiedRole(member) { + try { + const verifiedRoleId = process.env.ROLE_VERIFIED_ID; + if (!verifiedRoleId) { + console.error('ROLE_VERIFIED_ID non défini dans .env'); + return false; + } + + if (member.roles.cache.has(verifiedRoleId)) { + await member.roles.remove(verifiedRoleId); + console.log(`🗑️ Rôle verified retiré de ${member.user.tag}`); + } + return true; + } catch (error) { + console.error('Erreur lors de la suppression du rôle verified:', error); + return false; + } + } + + static async handleAccessAccepted(member) { + // Ajouter le rôle verified et retirer le rôle waiter + const addedVerified = await this.addVerifiedRole(member); + const removedWaiter = await this.removeWaiterRole(member); + return addedVerified && removedWaiter; + } + + static async handleAccessDenied(member) { + // Retirer seulement le rôle waiter + return await this.removeWaiterRole(member); + } +} + +module.exports = RoleManager; \ No newline at end of file diff --git a/src/utils/serverControlManager.js b/src/utils/serverControlManager.js new file mode 100644 index 0000000..c14b786 --- /dev/null +++ b/src/utils/serverControlManager.js @@ -0,0 +1,426 @@ +const { exec } = require('child_process'); +const { promisify } = require('util'); +const execAsync = promisify(exec); +const fs = require('fs').promises; + +class ServerControlManager { + constructor() { + this.serverPath = process.env.MINECRAFT_SERVER_PATH || '/opt/minecraft/server'; + this.serviceName = process.env.MINECRAFT_SERVICE_NAME || 'minecraft'; + this.screenName = 'minecraft'; // Nom de la session screen + this.serverUser = 'minecraft'; // Utilisateur qui lance le serveur + this.whitelistPath = process.env.MINECRAFT_WHITELIST_PATH; + this.useScreen = process.env.USE_SCREEN === 'true'; // true pour screen, false pour systemctl + + // Détecter le type de serveur et le fichier JAR + this.serverJar = this.detectServerJar(); + this.javaArgs = process.env.JAVA_ARGS || '-Xmx2G -Xms1G'; + } + + detectServerJar() { + // Ordre de priorité pour détecter le fichier JAR + const possibleJars = [ + 'fabric-server.jar', + 'fabric-server-launch.jar', + 'server.jar', + 'forge-server.jar', + 'paper.jar', + 'spigot.jar' + ]; + + // Pour l'instant, on retourne fabric-server.jar puisque c'est ce que vous utilisez + // Plus tard, on pourrait faire une détection automatique + return process.env.SERVER_JAR || 'fabric-server.jar'; + } + + async executeCommand(command, description = '') { + try { + console.log(`🔧 Exécution: ${description || command}`); + const { stdout, stderr } = await execAsync(command); + + // Log des détails pour debug + if (stdout) console.log(`📤 Sortie: ${stdout.trim()}`); + if (stderr && !stderr.includes('Warning')) { + console.warn(`⚠️ Erreur stderr: ${stderr.trim()}`); + return { success: false, error: stderr.trim(), output: stdout }; + } + + console.log(`✅ Succès: ${description || command}`); + return { success: true, output: stdout, error: stderr }; + } catch (error) { + console.error(`❌ Erreur: ${description || command} - ${error.message}`); + return { success: false, error: error.message, output: '' }; + } + } + + async reloadWhitelist() { + console.log('🔄 Rechargement de la whitelist...'); + + if (this.useScreen) { + // Méthode screen : commande directe + const result = await this.executeCommand( + `sudo -u ${this.serverUser} screen -S ${this.screenName} -p 0 -X stuff "whitelist reload\n"`, + 'Rechargement whitelist via commande' + ); + + if (result.success) { + return { success: true, message: 'Whitelist rechargée via commande serveur' }; + } + } + + // Fallback systemctl : redémarrage + const restartResult = await this.executeCommand( + `sudo systemctl restart ${this.serviceName}`, + 'Redémarrage pour appliquer la whitelist' + ); + + if (restartResult.success) { + return { success: true, message: 'Whitelist rechargée (serveur redémarré)' }; + } + + return { success: false, message: 'Impossible de recharger la whitelist' }; + } + + async getServerStatus() { + if (this.useScreen) { + // Vérifier si la session screen existe (nom exact ou suffixé) + const screenResult = await this.executeCommand( + `screen -ls | grep -w "${this.screenName}"`, + 'Vérification session screen' + ); + const screenRunning = screenResult.success && screenResult.output.trim() !== ''; + + // Double vérification : chercher le processus Java avec le bon jar + const javaResult = await this.executeCommand( + `pgrep -f "${this.serverJar}" | wc -l`, + 'Vérification processus Java Fabric' + ); + const javaRunning = javaResult.success && parseInt(javaResult.output.trim()) > 0; + + return { + running: screenRunning && javaRunning, + enabled: true, + status: screenRunning ? + (javaRunning ? 'Running in screen with Java process' : 'Screen session exists but no Java process') : + 'No screen session found', + details: { + screen: screenRunning, + java: javaRunning, + screenOutput: screenResult.output.trim(), + javaOutput: javaResult.output.trim() + } + }; + } + + // Méthode systemctl avec vérification plus précise + const result = await this.executeCommand( + `sudo systemctl is-active ${this.serviceName}`, + 'Vérification active systemctl' + ); + + const isActive = result.success && result.output.trim() === 'active'; + + const enabledResult = await this.executeCommand( + `sudo systemctl is-enabled ${this.serviceName}`, + 'Vérification enabled systemctl' + ); + const isEnabled = enabledResult.success && enabledResult.output.trim() === 'enabled'; + + return { + running: isActive, + enabled: isEnabled, + status: `Active: ${isActive ? 'active' : result.output.trim()}, Enabled: ${isEnabled ? 'enabled' : enabledResult.output.trim()}` + }; + } + + async startServer() { + if (this.useScreen) { + // Vérifier d'abord si le serveur n'est pas déjà lancé + const status = await this.getServerStatus(); + if (status.running) { + return { + success: false, + message: 'Le serveur est déjà en cours d\'exécution' + }; + } + + // Forcer le nom de la session screen à "minecraft" + const result = await this.executeCommand( + `sudo -u ${this.serverUser} screen -dmS ${this.screenName} bash -c "cd ${this.serverPath} && java ${this.javaArgs} -jar ${this.serverJar} nogui"`, + 'Démarrage du serveur avec screen (nom forcé)' + ); + + if (result.success) { + // Attendre un peu et vérifier que ça a vraiment démarré + await new Promise(resolve => setTimeout(resolve, 3000)); + const newStatus = await this.getServerStatus(); + + return { + success: newStatus.running, + message: newStatus.running ? 'Serveur démarré avec screen' : `Échec démarrage: ${result.error || 'Processus non trouvé'}` + }; + } + + return { + success: false, + message: `Erreur: ${result.error}` + }; + } + + // Méthode systemctl avec vérification + const result = await this.executeCommand( + `sudo systemctl start ${this.serviceName}`, + 'Démarrage du serveur' + ); + + if (result.success) { + // Vérifier que ça a vraiment démarré + await new Promise(resolve => setTimeout(resolve, 2000)); + const status = await this.getServerStatus(); + + return { + success: status.running, + message: status.running ? 'Serveur démarré avec succès' : `Échec: ${status.status}` + }; + } + + return { + success: false, + message: `Erreur: ${result.error}` + }; + } + + async stopServer() { + if (this.useScreen) { + // Arrêter proprement avec la commande stop + const stopResult = await this.executeCommand( + `sudo -u ${this.serverUser} screen -S ${this.screenName} -p 0 -X stuff "stop\n"`, + 'Arrêt propre du serveur' + ); + + // Attendre un peu puis forcer si nécessaire + await new Promise(resolve => setTimeout(resolve, 5000)); + + const killResult = await this.executeCommand( + `sudo -u ${this.serverUser} screen -S ${this.screenName} -X quit`, + 'Fermeture session screen' + ); + + return { + success: true, + message: 'Serveur arrêté' + }; + } + + // Méthode systemctl + const result = await this.executeCommand( + `sudo systemctl stop ${this.serviceName}`, + 'Arrêt du serveur' + ); + + return { + success: result.success, + message: result.success ? 'Serveur arrêté avec succès' : `Erreur: ${result.error}` + }; + } + + async restartServer() { + if (this.useScreen) { + // Arrêter puis redémarrer avec screen + console.log('🔄 Arrêt du serveur...'); + await this.stopServer(); + + // Attendre un peu + await new Promise(resolve => setTimeout(resolve, 3000)); + + console.log('🔄 Redémarrage du serveur...'); + return await this.startServer(); + } + + // Méthode systemctl + const result = await this.executeCommand( + `sudo systemctl restart ${this.serviceName}`, + 'Redémarrage du serveur' + ); + + return { + success: result.success, + message: result.success ? 'Serveur redémarré avec succès' : `Erreur: ${result.error}` + }; + } + + async getServerLogs(lines = 50) { + const result = await this.executeCommand( + `sudo journalctl -u ${this.serviceName} -n ${lines} --no-pager`, + `Récupération des ${lines} dernières lignes de logs` + ); + + return { + success: result.success, + logs: result.output, + error: result.error + }; + } + + async sendServerCommand(command) { + if (this.useScreen) { + // Envoyer directement via screen + const result = await this.executeCommand( + `sudo -u ${this.serverUser} screen -S ${this.screenName} -p 0 -X stuff "${command}\n"`, + `Envoi commande: ${command}` + ); + + return { + success: result.success, + message: result.success ? `Commande "${command}" envoyée` : `Erreur: ${result.error}` + }; + } + + // Pour systemctl, impossible d'envoyer des commandes directes + return { + success: false, + message: 'Envoi de commandes non supporté avec systemctl (utilisez screen)' + }; + } + + async backupWorld() { + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); + const backupPath = `/opt/minecraft/backups/world_${timestamp}.tar.gz`; + + const result = await this.executeCommand( + `sudo mkdir -p /opt/minecraft/backups && sudo tar -czf ${backupPath} -C ${this.serverPath} world`, + 'Sauvegarde du monde' + ); + + return { + success: result.success, + message: result.success ? `Monde sauvegardé: ${backupPath}` : `Erreur: ${result.error}`, + backupPath: result.success ? backupPath : null + }; + } + + async getWhitelistFromFile() { + try { + const data = await fs.readFile(this.whitelistPath, 'utf8'); + return JSON.parse(data); + } catch (error) { + console.error('Erreur lecture whitelist:', error); + return []; + } + } + + async saveWhitelistToFile(whitelist) { + try { + await fs.writeFile(this.whitelistPath, JSON.stringify(whitelist, null, 2)); + return true; + } catch (error) { + console.error('Erreur écriture whitelist:', error); + return false; + } + } + + async getServerProperties() { + try { + const propsPath = `${this.serverPath}/server.properties`; + const data = await fs.readFile(propsPath, 'utf8'); + + const properties = {}; + data.split('\n').forEach(line => { + if (line.trim() && !line.startsWith('#')) { + const [key, value] = line.split('='); + if (key && value !== undefined) { + properties[key.trim()] = value.trim(); + } + } + }); + + return { success: true, properties }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + async updateServerProperty(key, value) { + try { + const propsPath = `${this.serverPath}/server.properties`; + let data = await fs.readFile(propsPath, 'utf8'); + + // Remplacer ou ajouter la propriété + const regex = new RegExp(`^${key}=.*$`, 'm'); + if (regex.test(data)) { + data = data.replace(regex, `${key}=${value}`); + } else { + data += `\n${key}=${value}\n`; + } + + await fs.writeFile(propsPath, data); + return { success: true, message: `Propriété ${key} mise à jour: ${value}` }; + } catch (error) { + return { success: false, error: error.message }; + } + } + + async setupServerForBot() { + console.log('🔧 Configuration automatique du serveur pour le bot...'); + const results = []; + + // Vérifier si l'utilisateur minecraft existe déjà + const userCheck = await this.executeCommand( + 'id minecraft 2>/dev/null', + 'Vérification utilisateur minecraft' + ); + + if (!userCheck.success && this.useScreen) { + const userResult = await this.executeCommand( + 'sudo useradd -r -s /bin/bash minecraft', + 'Création utilisateur minecraft' + ); + results.push(userResult.success ? '✅ Utilisateur minecraft créé' : '❌ Échec création utilisateur'); + } else if (this.useScreen) { + results.push('✅ Utilisateur minecraft existe déjà'); + } + + // Vérifier et créer les dossiers + try { + await fs.access(this.serverPath); + results.push('✅ Dossier serveur existe'); + } catch (error) { + const dirResult = await this.executeCommand( + `sudo mkdir -p ${this.serverPath}`, + 'Création dossier serveur' + ); + results.push(dirResult.success ? '✅ Dossier serveur créé' : '❌ Échec création dossier'); + } + + // Configuration permissions si screen + if (this.useScreen) { + const permResult = await this.executeCommand( + `sudo chown -R minecraft:minecraft /opt/minecraft`, + 'Configuration permissions' + ); + results.push(permResult.success ? '✅ Permissions configurées' : '⚠️ Échec permissions'); + } + + // Configuration server.properties + const whitelistResult = await this.updateServerProperty('white-list', 'true'); + const enforceResult = await this.updateServerProperty('enforce-whitelist', 'true'); + + results.push(whitelistResult.success ? '✅ Whitelist activée' : '⚠️ Échec whitelist'); + results.push(enforceResult.success ? '✅ Enforce-whitelist activé' : '⚠️ Échec enforce-whitelist'); + + // Création dossier backups + const backupResult = await this.executeCommand( + 'sudo mkdir -p /opt/minecraft/backups', + 'Création dossier backups' + ); + results.push(backupResult.success ? '✅ Dossier backups créé' : '⚠️ Échec dossier backups'); + + return { + success: true, + message: 'Configuration terminée', + details: results + }; + } +} + +module.exports = ServerControlManager; \ No newline at end of file diff --git a/src/utils/whitelistManager.js b/src/utils/whitelistManager.js index fde95b9..cf93bcc 100644 --- a/src/utils/whitelistManager.js +++ b/src/utils/whitelistManager.js @@ -4,6 +4,26 @@ const path = require('path'); class WhitelistManager { constructor() { this.whitelistPath = process.env.MINECRAFT_WHITELIST_PATH; + // Import dynamique pour éviter les dépendances circulaires + this.ServerControlManager = null; + } + + async getServerControlManager() { + if (!this.ServerControlManager) { + const ServerControlManager = require('./serverControlManager'); + this.ServerControlManager = new ServerControlManager(); + } + return this.ServerControlManager; + } + + async reloadMinecraftWhitelist() { + try { + const serverControl = await this.getServerControlManager(); + return await serverControl.reloadWhitelist(); + } catch (error) { + console.error('Erreur lors du rechargement de la whitelist:', error); + return false; + } } async readWhitelist() { @@ -44,20 +64,36 @@ class WhitelistManager { // Si pas d'UUID fourni, essayer de le récupérer via l'API Mojang if (!uuid) { + console.log(`🔍 Récupération de l'UUID pour ${username}...`); uuid = await this.getUUIDFromUsername(username); if (!uuid) { - return { success: false, message: 'Impossible de trouver l\'UUID pour ce joueur.' }; + return { success: false, message: 'Impossible de trouver l\'UUID pour ce joueur. Vérifiez que le pseudo existe.' }; } + console.log(`✅ UUID trouvé pour ${username}: ${uuid}`); } // Ajouter le joueur - whitelist.push({ + const newPlayer = { uuid: uuid, name: username - }); + }; + + whitelist.push(newPlayer); + console.log(`📝 Ajout du joueur à la whitelist:`, newPlayer); const success = await this.writeWhitelist(whitelist); if (success) { + console.log(`✅ ${username} ajouté avec succès à la whitelist`); + + // Recharger automatiquement la whitelist sur le serveur + console.log('🔄 Rechargement de la whitelist sur le serveur...'); + const reloadSuccess = await this.reloadMinecraftWhitelist(); + if (reloadSuccess) { + console.log('✅ Whitelist rechargée avec succès sur le serveur'); + } else { + console.log('⚠️ Impossible de recharger la whitelist automatiquement'); + } + return { success: true, message: `${username} a été ajouté à la whitelist.` }; } else { return { success: false, message: 'Erreur lors de l\'ajout du joueur.' }; @@ -66,9 +102,7 @@ class WhitelistManager { 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) { + } async removePlayer(username) { try { const whitelist = await this.readWhitelist(); const initialLength = whitelist.length; @@ -83,6 +117,17 @@ class WhitelistManager { const success = await this.writeWhitelist(newWhitelist); if (success) { + console.log(`✅ ${username} retiré avec succès de la whitelist`); + + // Recharger automatiquement la whitelist sur le serveur + console.log('🔄 Rechargement de la whitelist sur le serveur...'); + const reloadSuccess = await this.reloadMinecraftWhitelist(); + if (reloadSuccess) { + console.log('✅ Whitelist rechargée avec succès sur le serveur'); + } else { + console.log('⚠️ Impossible de recharger la whitelist automatiquement'); + } + return { success: true, message: `${username} a été retiré de la whitelist.` }; } else { return { success: false, message: 'Erreur lors de la suppression du joueur.' }; @@ -108,12 +153,29 @@ class WhitelistManager { } const data = await response.json(); - return data.id; + // Convertir l'UUID compact en UUID avec tirets + return this.formatUUID(data.id); } catch (error) { console.error('Erreur lors de la récupération de l\'UUID:', error); return null; } } + + // Formate un UUID compact (32 caractères) en UUID avec tirets (36 caractères) + formatUUID(compactUUID) { + if (!compactUUID || compactUUID.length !== 32) { + return null; + } + + // Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + return [ + compactUUID.substring(0, 8), + compactUUID.substring(8, 12), + compactUUID.substring(12, 16), + compactUUID.substring(16, 20), + compactUUID.substring(20, 32) + ].join('-'); + } } module.exports = WhitelistManager; \ No newline at end of file