diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..88f4cd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.idea/ +.vscode/ +.fleet/ +node_modules/ +dist/ +*.iml +tsconfig.tsbuildinfo + +data/*.json +!data/*.example.json \ No newline at end of file diff --git a/data/config.example.json b/data/config.example.json new file mode 100644 index 0000000..4822252 --- /dev/null +++ b/data/config.example.json @@ -0,0 +1,3 @@ +{ + "token": "YOUR_DISCORD_TOKEN" +} \ No newline at end of file diff --git a/index.js b/old/index.js similarity index 97% rename from index.js rename to old/index.js index 7cc06ef..83579b5 100644 --- a/index.js +++ b/old/index.js @@ -1,528 +1,528 @@ -const Discord = require('discord.js'); -const sqlite3 = require('sqlite3').verbose(); -const client = new Discord.Client(); - -const db = new sqlite3.Database('./userdata.db'); - -// Messages de bienvenue avec leurs probabilités, les rôles associés et les images -const welcomeMessages = [ - { message: "{mention} vient d'être invoqué sur **Camélia Studio** ! Souhaitons-lui la bienvenue ! Il s'agit d'un personnage de rareté **B**. Dommage, nous aurons plus de chance la prochaine fois...", role: "Rang B", probability: 80, image: "https://concepts.esenjin.xyz/cyla/v2/file/11C108.png" }, - { message: "{mention} vient d'être invoqué sur **Camélia Studio** ! Souhaitons-lui la bienvenue ! Il s'agit d'un personnage de rareté **A**. C'est plutôt une bonne pioche !", role: "Rang A", probability: 15, image: "https://concepts.esenjin.xyz/cyla/v2/file/732316.png" }, - { message: "{mention} vient d'être invoqué sur **Camélia Studio** ! Souhaitons-lui la bienvenue ! Il s'agit d'un personnage de rareté **S**. On a vraiment de la chance aujourd'hui !!", role: "Rang S", probability: 4, image: "https://concepts.esenjin.xyz/cyla/v2/file/D6E3E1.png" }, - { message: "{mention} vient d'être invoqué sur **Camélia Studio** ! Souhaitons-lui la bienvenue ! Il s'agit d'un personnage de rareté **S+**. Incroyable ! On vient de tomber sur la perle rare !", role: "Rang S+", probability: 1, image: "https://concepts.esenjin.xyz/cyla/v2/file/6B6CE3.png" } -]; - -let welcomeChannel; // Canal où publier les messages de bienvenue -let farewellChannel; // Canal où publier les messages d'adieu - -// Création de la table dans la base de données -db.serialize(() => { - db.run(`CREATE TABLE IF NOT EXISTS userdata ( - userid TEXT PRIMARY KEY, - message TEXT, - role TEXT, - image TEXT - )`); -}); - -// Fonction pour choisir un message de bienvenue selon les probabilités -function chooseWelcomeMessage() { - const random = Math.random() * 100; // Random entre 0 et 100 - let cumulativeProbability = 0; - for (const { message, role, image } of welcomeMessages) { - cumulativeProbability += probability; - if (random < cumulativeProbability) { - return { message, role, image }; - } - } -} - -// Commande "/gachaoptions" -client.on('message', message => { - if (message.member.hasPermission('ADMINISTRATOR') && message.content.startsWith('/gachaoptions')) { - const args = message.content.split(' '); - if (args.length === 1) { - // Afficher les options actuelles - message.channel.send(`Options actuelles : - - Canal de bienvenue : ${welcomeChannel ? welcomeChannel : "non défini"} - - Canal d'adieu : ${farewellChannel ? farewellChannel : "non défini"} - `); - } else if (args.length === 3 && args[1] === 'bienvenue') { - // Définir le canal de bienvenue - const channelID = args[2].replace(/<#|>/g, ''); - welcomeChannel = message.guild.channels.cache.get(channelID); - message.channel.send(`Canal de bienvenue défini sur : ${welcomeChannel}`); - } else if (args.length === 3 && args[1] === 'adieu') { - // Définir le canal d'adieu - const channelID = args[2].replace(/<#|>/g, ''); - farewellChannel = message.guild.channels.cache.get(channelID); - message.channel.send(`Canal d'adieu défini sur : ${farewellChannel}`); - } else if (args.length === 3 && args[1] === 'messageadieu') { - // Définir le message d'adieu - farewellMessage = args.slice(2).join(' '); - message.channel.send(`Message d'adieu défini sur : ${farewellMessage}`); - } else if (args.length === 3 && args[1] === 'imageadieu') { - // Définir l'image d'adieu - farewellImage = args[2]; - message.channel.send(`Image d'adieu définie sur : ${farewellImage}`); - } else if (args.length === 3 && args[1] === 'bienvenueimage') { - // Définir l'image de bienvenue - welcomeImage = args[2]; - message.channel.send(`Image de bienvenue définie sur : ${welcomeImage}`); - } else if (args.length === 3 && args[1] === 'rangauto') { - // Définir la commande pour le rang automatique - autorangCommand = args[2]; - message.channel.send(`Commande pour le rang automatique définie sur : ${autorangCommand}`); - } else if (args.length === 3 && args[1] === 'rangs') { - // Afficher les rôles et leurs messages associés - message.channel.send(`Messages de bienvenue et rôles associés : - - Rang B : ${welcomeMessages.find(msg => msg.role === "Rang B").message} - - Rang A : ${welcomeMessages.find(msg => msg.role === "Rang A").message} - - Rang S : ${welcomeMessages.find(msg => msg.role === "Rang S").message} - - Rang S+ : ${welcomeMessages.find(msg => msg.role === "Rang S+").message} - `); - } else if (args.length === 5 && args[1] === 'ajoutermessage') { - // Ajouter un nouveau message de bienvenue - const newRole = args[2]; - const newProbability = parseInt(args[3]); - const newMessage = args.slice(4).join(' '); - welcomeMessages.push({ message: newMessage, role: newRole, probability: newProbability }); - message.channel.send(`Nouveau message de bienvenue ajouté pour le rôle ${newRole} avec une probabilité de ${newProbability}%.`); - } else if (args.length === 4 && args[1] === 'modifierprobabilite') { - // Modifier la probabilité d'un message de bienvenue existant - const roleName = args[2]; - const newProbability = parseInt(args[3]); - const index = welcomeMessages.findIndex(msg => msg.role === roleName); - if (index !== -1) { - // Calculer la somme des probabilités actuelles - let currentTotal = welcomeMessages.reduce((total, msg) => total + msg.probability, 0); - // Calculer la différence entre la nouvelle probabilité et l'ancienne - let difference = newProbability - welcomeMessages[index].probability; - // Mettre à jour la probabilité - welcomeMessages[index].probability = newProbability; - // Vérifier si le total est égal à 100% - if (currentTotal + difference !== 100) { - message.channel.send(`La somme des probabilités n'est pas égale à 100%. Veuillez modifier une autre probabilité pour ajuster.`); - } else { - message.channel.send(`Probabilité du message de bienvenue pour le rôle ${roleName} modifiée à ${newProbability}%.`); - } - } else { - message.channel.send(`Le rôle ${roleName} n'existe pas.`); - } - } else if (args.length === 4 && args[1] === 'changerrole') { - // Changer le rôle associé à un message de bienvenue existant - const roleName = args[2]; - const newRoleName = args[3]; - const index = welcomeMessages.findIndex(msg => msg.role === roleName); - if (index !== -1) { - welcomeMessages[index].role = newRoleName; - message.channel.send(`Rôle associé au message de bienvenue pour le rôle ${roleName} changé en ${newRoleName}.`); - } else { - message.channel.send(`Le rôle ${roleName} n'existe pas.`); - } - } else { - message.channel.send("Options disponibles : \n- `/gachaoptions bienvenue ` pour définir le canal de bienvenue\n- `/gachaoptions adieu ` pour définir le canal d'adieu\n- `/gachaoptions messageadieu ` pour définir le message d'adieu\n- `/gachaoptions imageadieu ` pour définir l'image d'adieu\n- `/gachaoptions bienvenueimage ` pour définir l'image de bienvenue\n- `/gachaoptions rangauto ` pour définir la commande pour le rang automatique\n- `/gachaoptions rangs` pour afficher les rôles et leurs messages associés\n- `/gachaoptions ajoutermessage ` pour ajouter un nouveau message de bienvenue\n- `/gachaoptions modifierprobabilite ` pour modifier la probabilité d'un message de bienvenue\n- `/gachaoptions changerrole ` pour changer le rôle associé à un message de bienvenue"); - } - } -}); - -client.on('guildMemberAdd', member => { - // Vérifier si l'utilisateur est déjà dans la base de données - db.get(`SELECT * FROM userdata WHERE userid = ?`, member.id, (err, row) => { - if (err) { - console.error(err); - return; - } - if (row) { - // Utiliser les données stockées - const { message, role, image } = row; - // Envoi du message de bienvenue dans le canal approprié - const welcomeMessage = message.replace("{mention}", member.user); - const embed = new Discord.MessageEmbed() - .setColor('#0099ff') - .setDescription(welcomeMessage) - .setImage(image); - if (welcomeChannel) { - welcomeChannel.send(embed); - } - // Attribution automatique du rôle - const guildRole = member.guild.roles.cache.find(guildRole => guildRole.name === role); - if (guildRole) { - member.roles.add(guildRole); - } - } else { - // Choix du message de bienvenue, du rôle associé et de l'image - const { message, role, image } = chooseWelcomeMessage(); - // Envoi du message de bienvenue dans le canal approprié - const welcomeMessage = message.replace("{mention}", member.user); - const embed = new Discord.MessageEmbed() - .setColor('#0099ff') - .setDescription(welcomeMessage) - .setImage(image); - if (welcomeChannel) { - welcomeChannel.send(embed); - } - // Attribution automatique du rôle - const guildRole = member.guild.roles.cache.find(guildRole => guildRole.name === role); - if (guildRole) { - member.roles.add(guildRole); - } - // Stocker les données de bienvenue dans la base de données - db.run(`INSERT INTO userdata (userid, message, role, image) VALUES (?, ?, ?, ?)`, [member.id, message, role, image], err => { - if (err) { - console.error(err); - } - }); - } - }); -}); - -client.on('guildMemberRemove', member => { - // Message d'adieu - const farewellMessage = `Oh non ! ${member.user} n'est plus utile dans la méta actuelle et quitte notre équipe. Espérons qu'une prochaine mise à jour lui soit favorable !`; - const embed = new Discord.MessageEmbed() - .setColor('#0099ff') - .setDescription(farewellMessage) - .setImage("https://concepts.esenjin.xyz/cyla/v2/file/EDF7B4.gif"); - if (farewellChannel) { - farewellChannel.send(embed); - } - // Supprimer les données de bienvenue de la base de données - db.run(`DELETE FROM userdata WHERE userid = ?`, member.id, err => { - if (err) { - console.error(err); - } - }); -}); - -// Connexion du bot à Discord -client.login('TOKEN_DU_BOT'); - -const sqlite3 = require('sqlite3').verbose(); - -// Connexion à la base de données SQLite -const db = new sqlite3.Database('./userXP.db', (err) => { - if (err) { - console.error('Erreur lors de la connexion à la base de données :', err.message); - } else { - console.log('Connexion à la base de données réussie.'); - // Créer une table pour stocker l'XP des utilisateurs si elle n'existe pas déjà - db.run(`CREATE TABLE IF NOT EXISTS userXP ( - userId TEXT PRIMARY KEY, - xp INTEGER DEFAULT 0, - dailyXP INTEGER DEFAULT 0 - )`); - } -}); - -// Fonction pour récupérer l'XP d'un utilisateur depuis la base de données -function getUserXP(user) { - return new Promise((resolve, reject) => { - db.get('SELECT xp FROM userXP WHERE userId = ?', [user.id], (err, row) => { - if (err) { - reject(err); - } else { - resolve(row ? row.xp : 0); - } - }); - }); -} - -// Fonction pour mettre à jour l'XP d'un utilisateur dans la base de données -function setUserXP(user, xp) { - db.run('INSERT OR REPLACE INTO userXP (userId, xp) VALUES (?, ?)', [user.id, xp], (err) => { - if (err) { - console.error('Erreur lors de la mise à jour de l\'XP de l\'utilisateur :', err.message); - } - }); -} - -// Fonction pour récupérer l'XP quotidien d'un utilisateur depuis la base de données -function getUserDailyXP(user) { - return new Promise((resolve, reject) => { - db.get('SELECT dailyXP FROM userXP WHERE userId = ?', [user.id], (err, row) => { - if (err) { - reject(err); - } else { - resolve(row ? row.dailyXP : 0); - } - }); - }); -} - -// Fonction pour mettre à jour l'XP quotidien d'un utilisateur dans la base de données -function updateUserDailyXP(user, amount) { - db.run('INSERT OR REPLACE INTO userXP (userId, dailyXP) VALUES (?, ?)', [user.id, amount], (err) => { - if (err) { - console.error('Erreur lors de la mise à jour de l\'XP quotidienne de l\'utilisateur :', err.message); - } - }); -} - -// Supprimer l'utilisateur de la base de données lorsque celui-ci quitte le serveur -client.on('guildMemberRemove', member => { - db.run('DELETE FROM userXP WHERE userId = ?', [member.id], (err) => { - if (err) { - console.error('Erreur lors de la suppression de l\'utilisateur de la base de données :', err.message); - } - }); -}); - -// Variables globales pour stocker les paramètres XP et les canaux exclus -let excludedChannels = []; -let xpMultiplierChannels = []; -let xpCooldowns = {}; - -client.on('message', message => { - // Vérifier si le message est dans un salon exclu ou provenant du bot lui-même - if (excludedChannels.includes(message.channel.id) || message.author.bot) return; - - // Vérifier si l'utilisateur est un booster - let xpMultiplier = 1; - if (message.member.roles.cache.some(role => role.name === 'Booster')) { - xpMultiplier = 2; - } - - // Vérifier si le salon a un multiplicateur d'XP - if (xpMultiplierChannels.includes(message.channel.id)) { - xpMultiplier *= 2; - } - - // Vérifier si l'utilisateur est déjà en cooldown - if (xpCooldowns[message.author.id]) return; - - // Ajouter l'XP à l'utilisateur - addXP(message.author, 1 * xpMultiplier); - - // Mettre l'utilisateur en cooldown pour 2 minutes - xpCooldowns[message.author.id] = true; - setTimeout(() => { - delete xpCooldowns[message.author.id]; - }, 120000); -}); - -// Fonction pour ajouter de l'XP à un utilisateur -function addXP(user, amount) { - // Récupérer l'XP actuelle de l'utilisateur depuis la base de données - getUserXP(user) - .then(currentXP => { - // Ajouter la quantité spécifiée à l'XP actuelle - const newXP = currentXP + amount; - - // Mettre à jour l'XP de l'utilisateur dans la base de données - setUserXP(user, newXP); - - // Vérifier si l'utilisateur a atteint un nouveau rang - checkAndUpdateUserRank(user, newXP); - }) - .catch(err => { - console.error('Erreur lors de l\'ajout de l\'XP à l\'utilisateur :', err); - }); - - // Vérifier si l'utilisateur a atteint un nouveau rang - const currentXP = getUserXP(user); - const newRank = calculateRank(currentXP + amount); - if (newRank !== getUserRank(user)) { - const newRole = getRoleFromRank(newRank); - const notificationMessage = `Héhé ! C'est que tu as bien xp dit donc, tu peux désormais évoluer au ${newRole} ! Pour cela, rien de plus simple, utilise la commande /gachajaiunegrosseépée.`; - user.send(notificationMessage); - } -} - -// Fonction pour calculer le nouveau rang à partir de l'XP -function calculateRank(xp) { - if (xp >= 66666) return "Rang S++"; - if (xp >= 10000) return "Rang S+"; - if (xp >= 1000) return "Rang S"; - if (xp >= 100) return "Rang A"; - return "Rang B"; -} - -// Fonction pour obtenir le rang d'un utilisateur -function getUserRank(user) { - const xp = getUserXP(user); - return calculateRank(xp); -} - -// Fonction pour obtenir l'XP d'un utilisateur -function getUserXP(user) { - return new Promise((resolve, reject) => { - db.get('SELECT xp FROM userXP WHERE userId = ?', [user.id], (err, row) => { - if (err) { - reject(err); - } else { - resolve(row ? row.xp : 0); - } - }); - }); -} - -// Fonction pour obtenir le rôle associé à un rang -function getRoleFromRank(rank) { - // Tableau associant les rangs aux noms de rôles - const rankRoles = { - 'Rang B': 'Role B', - 'Rang A': 'Role A', - 'Rang S': 'Role S', - 'Rang S+': 'Role S+' - // Ajouter d'autres rangs et rôles au besoin - }; - - // Renvoyer le nom du rôle associé au rang spécifié - return rankRoles[rank]; -} - -// Commande pour obtenir le nouveau rang -client.on('message', message => { - if (message.content === '/gachajaiunegrosseépée') { - const user = message.author; - const currentXP = getUserXP(user); - const newRank = calculateRank(currentXP); - const roles = ['Rang B', 'Rang A', 'Rang S', 'Rang S+', 'Rang S++']; - const index = roles.indexOf(newRank); - if (index !== -1) { - const newRole = roles[index + 1]; - const guildRole = message.guild.roles.cache.find(role => role.name === newRole); - if (guildRole) { - const currentRole = message.guild.roles.cache.find(role => role.name === getUserRank(user)); - if (currentRole) { - message.member.roles.remove(currentRole); - } - message.member.roles.add(guildRole); - message.reply(`Félicitations ! Vous avez maintenant atteint le ${newRole}.`); - } - } - } -}); - -// Commande pour exclure des salons du décompte de l'XP -client.on('message', message => { - if (message.member.hasPermission('ADMINISTRATOR') && message.content.startsWith('/excludesalon')) { - const channelID = message.content.split(' ')[1]; - excludedChannels.push(channelID); - message.channel.send(`Le salon avec l'ID ${channelID} a été exclu du décompte de l'XP.`); - } -}); - -// Commande pour multiplier l'XP dans certains salons -client.on('message', message => { - if (message.member.hasPermission('ADMINISTRATOR') && message.content.startsWith('/multiplierxp')) { - const channelID = message.content.split(' ')[1]; - xpMultiplierChannels.push(channelID); - message.channel.send(`Le salon avec l'ID ${channelID} a maintenant un multiplicateur d'XP.`); - } -}); - -// Variable globale pour stocker le rôle déclenchant le Rang ULTRA -let ultraRole = null; - -// Fonction pour vérifier si l'utilisateur a le rôle déclenchant le Rang ULTRA -function hasUltraRole(user) { - return user.roles.cache.some(role => role === ultraRole); -} - -// Fonction pour obtenir le rôle "Rang ULTRA" et l'assigner à la variable globale -function setUltraRole(roleName) { - ultraRole = message.guild.roles.cache.find(role => role.name === roleName); -} - -// Commande pour définir le rôle déclenchant le Rang ULTRA -client.on('message', message => { - if (message.member.hasPermission('ADMINISTRATOR') && message.content.startsWith('/gachaoptions ultrarole')) { - const roleName = message.content.split(' ')[1]; - setUltraRole(roleName); - message.channel.send(`Le rôle déclenchant le Rang ULTRA a été défini sur "${roleName}".`); - } -}); - -// Variable globale pour stocker les temps de connexion des membres des salons vocaux -const voiceChannelConnections = {}; -// Variable globale pour stocker les temps de connexion des membres des salons vocaux en cooldown -const xpCooldowns = {}; -// Variable globale pour stocker les XP quotidiennes des utilisateurs -const userDailyXP = {}; - -// Fonction pour surveiller la connexion des membres aux salons vocaux -client.on('voiceStateUpdate', (oldState, newState) => { - const user = newState.member; - // Vérifier si l'utilisateur existe et n'est pas un bot - if (!user || user.user.bot) return; - - const oldChannel = oldState.channel; - const newChannel = newState.channel; - - // L'utilisateur est entré dans un salon vocal - if (!oldChannel && newChannel) { - voiceChannelConnections[user.id] = Date.now(); // Enregistrer le moment de la connexion - } - // L'utilisateur est sorti d'un salon vocal - else if (oldChannel && !newChannel) { - // Vérifier si l'utilisateur est enregistré comme étant connecté à un salon vocal - if (voiceChannelConnections[user.id]) { - const timeSpentInVoiceChannel = Math.floor((Date.now() - voiceChannelConnections[user.id]) / 1000); // Calculer la durée en secondes - const xpGained = calculateXPFromTime(timeSpentInVoiceChannel, user); // Convertir la durée en XP - addXP(user, xpGained); // Ajouter l'XP à l'utilisateur - delete voiceChannelConnections[user.id]; // Supprimer l'enregistrement de la connexion - } - } -}); - -// Fonction pour calculer l'XP à partir du temps passé dans un salon vocal -function calculateXPFromTime(timeSpentInVoiceChannel, user) { - // Déterminer le multiplicateur d'XP en fonction du boost Nitro - let xpMultiplier = 1; - if (user.roles.cache.some(role => role.name === 'Booster')) { - xpMultiplier = 2; - } - - // Vérifier si l'utilisateur est en cooldown pour le salon vocal - if (!xpCooldowns[user.id]) { - // Appliquer le multiplicateur d'XP - const xpPerMinute = 1 * xpMultiplier; - const xpGained = Math.floor(timeSpentInVoiceChannel / 120) * xpPerMinute; // 1 XP pour 2 minutes - // Appliquer le plafond d'XP quotidien - if (xpGained > 0) { - if (xpMultiplier === 1 && getUserDailyXP(user) + xpGained > 200) { - xpGained = 200 - getUserDailyXP(user); - } else if (xpMultiplier === 2 && getUserDailyXP(user) + xpGained > 500) { - xpGained = 500 - getUserDailyXP(user); - } - updateUserDailyXP(user, xpGained); - } - return xpGained; - } else { - return 0; // Aucune XP gagnée pendant le cooldown - } -} - -// Fonction pour ajouter de l'XP à un utilisateur -function addXP(user, amount) { - // Récupérer l'XP actuelle de l'utilisateur - let currentXP = getUserXP(user); - // Ajouter la quantité spécifiée à l'XP actuelle - currentXP += amount; - // Mettre à jour l'XP de l'utilisateur - setUserXP(user, currentXP); -} - -// Fonction pour récupérer l'XP quotidien d'un utilisateur -function getUserDailyXP(user) { - // Récupérer l'XP quotidien de l'utilisateur depuis le stockage approprié - return userDailyXP[user.id] || 0; // Si l'XP quotidien n'est pas défini, retourner 0 -} - -// Fonction pour mettre à jour l'XP quotidien d'un utilisateur -function updateUserDailyXP(user, amount) { - // Mettre à jour l'XP quotidien de l'utilisateur dans le stockage approprié - userDailyXP[user.id] = amount; -} - -// Fonction pour exclure des salons vocaux du décompte de l'XP -client.on('message', message => { - if (message.member.hasPermission('ADMINISTRATOR') && message.content.startsWith('/gachaoptions excludevoice')) { - const channelID = message.content.split(' ')[1]; - // Ajouter le salon vocal à la liste des exclusions - excludedVoiceChannels.push(channelID); - message.channel.send(`Le salon vocal avec l'ID ${channelID} a été exclu du décompte de l'XP.`); - } -}); +const Discord = require('discord.js'); +const sqlite3 = require('sqlite3').verbose(); +const client = new Discord.Client(); + +const db = new sqlite3.Database('./userdata.db'); + +// Messages de bienvenue avec leurs probabilités, les rôles associés et les images +const welcomeMessages = [ + { message: "{mention} vient d'être invoqué sur **Camélia Studio** ! Souhaitons-lui la bienvenue ! Il s'agit d'un personnage de rareté **B**. Dommage, nous aurons plus de chance la prochaine fois...", role: "Rang B", probability: 80, image: "https://concepts.esenjin.xyz/cyla/v2/file/11C108.png" }, + { message: "{mention} vient d'être invoqué sur **Camélia Studio** ! Souhaitons-lui la bienvenue ! Il s'agit d'un personnage de rareté **A**. C'est plutôt une bonne pioche !", role: "Rang A", probability: 15, image: "https://concepts.esenjin.xyz/cyla/v2/file/732316.png" }, + { message: "{mention} vient d'être invoqué sur **Camélia Studio** ! Souhaitons-lui la bienvenue ! Il s'agit d'un personnage de rareté **S**. On a vraiment de la chance aujourd'hui !!", role: "Rang S", probability: 4, image: "https://concepts.esenjin.xyz/cyla/v2/file/D6E3E1.png" }, + { message: "{mention} vient d'être invoqué sur **Camélia Studio** ! Souhaitons-lui la bienvenue ! Il s'agit d'un personnage de rareté **S+**. Incroyable ! On vient de tomber sur la perle rare !", role: "Rang S+", probability: 1, image: "https://concepts.esenjin.xyz/cyla/v2/file/6B6CE3.png" } +]; + +let welcomeChannel; // Canal où publier les messages de bienvenue +let farewellChannel; // Canal où publier les messages d'adieu + +// Création de la table dans la base de données +db.serialize(() => { + db.run(`CREATE TABLE IF NOT EXISTS userdata ( + userid TEXT PRIMARY KEY, + message TEXT, + role TEXT, + image TEXT + )`); +}); + +// Fonction pour choisir un message de bienvenue selon les probabilités +function chooseWelcomeMessage() { + const random = Math.random() * 100; // Random entre 0 et 100 + let cumulativeProbability = 0; + for (const { message, role, image } of welcomeMessages) { + cumulativeProbability += probability; + if (random < cumulativeProbability) { + return { message, role, image }; + } + } +} + +// Commande "/gachaoptions" +client.on('message', message => { + if (message.member.hasPermission('ADMINISTRATOR') && message.content.startsWith('/gachaoptions')) { + const args = message.content.split(' '); + if (args.length === 1) { + // Afficher les options actuelles + message.channel.send(`Options actuelles : + - Canal de bienvenue : ${welcomeChannel ? welcomeChannel : "non défini"} + - Canal d'adieu : ${farewellChannel ? farewellChannel : "non défini"} + `); + } else if (args.length === 3 && args[1] === 'bienvenue') { + // Définir le canal de bienvenue + const channelID = args[2].replace(/<#|>/g, ''); + welcomeChannel = message.guild.channels.cache.get(channelID); + message.channel.send(`Canal de bienvenue défini sur : ${welcomeChannel}`); + } else if (args.length === 3 && args[1] === 'adieu') { + // Définir le canal d'adieu + const channelID = args[2].replace(/<#|>/g, ''); + farewellChannel = message.guild.channels.cache.get(channelID); + message.channel.send(`Canal d'adieu défini sur : ${farewellChannel}`); + } else if (args.length === 3 && args[1] === 'messageadieu') { + // Définir le message d'adieu + farewellMessage = args.slice(2).join(' '); + message.channel.send(`Message d'adieu défini sur : ${farewellMessage}`); + } else if (args.length === 3 && args[1] === 'imageadieu') { + // Définir l'image d'adieu + farewellImage = args[2]; + message.channel.send(`Image d'adieu définie sur : ${farewellImage}`); + } else if (args.length === 3 && args[1] === 'bienvenueimage') { + // Définir l'image de bienvenue + welcomeImage = args[2]; + message.channel.send(`Image de bienvenue définie sur : ${welcomeImage}`); + } else if (args.length === 3 && args[1] === 'rangauto') { + // Définir la commande pour le rang automatique + autorangCommand = args[2]; + message.channel.send(`Commande pour le rang automatique définie sur : ${autorangCommand}`); + } else if (args.length === 3 && args[1] === 'rangs') { + // Afficher les rôles et leurs messages associés + message.channel.send(`Messages de bienvenue et rôles associés : + - Rang B : ${welcomeMessages.find(msg => msg.role === "Rang B").message} + - Rang A : ${welcomeMessages.find(msg => msg.role === "Rang A").message} + - Rang S : ${welcomeMessages.find(msg => msg.role === "Rang S").message} + - Rang S+ : ${welcomeMessages.find(msg => msg.role === "Rang S+").message} + `); + } else if (args.length === 5 && args[1] === 'ajoutermessage') { + // Ajouter un nouveau message de bienvenue + const newRole = args[2]; + const newProbability = parseInt(args[3]); + const newMessage = args.slice(4).join(' '); + welcomeMessages.push({ message: newMessage, role: newRole, probability: newProbability }); + message.channel.send(`Nouveau message de bienvenue ajouté pour le rôle ${newRole} avec une probabilité de ${newProbability}%.`); + } else if (args.length === 4 && args[1] === 'modifierprobabilite') { + // Modifier la probabilité d'un message de bienvenue existant + const roleName = args[2]; + const newProbability = parseInt(args[3]); + const index = welcomeMessages.findIndex(msg => msg.role === roleName); + if (index !== -1) { + // Calculer la somme des probabilités actuelles + let currentTotal = welcomeMessages.reduce((total, msg) => total + msg.probability, 0); + // Calculer la différence entre la nouvelle probabilité et l'ancienne + let difference = newProbability - welcomeMessages[index].probability; + // Mettre à jour la probabilité + welcomeMessages[index].probability = newProbability; + // Vérifier si le total est égal à 100% + if (currentTotal + difference !== 100) { + message.channel.send(`La somme des probabilités n'est pas égale à 100%. Veuillez modifier une autre probabilité pour ajuster.`); + } else { + message.channel.send(`Probabilité du message de bienvenue pour le rôle ${roleName} modifiée à ${newProbability}%.`); + } + } else { + message.channel.send(`Le rôle ${roleName} n'existe pas.`); + } + } else if (args.length === 4 && args[1] === 'changerrole') { + // Changer le rôle associé à un message de bienvenue existant + const roleName = args[2]; + const newRoleName = args[3]; + const index = welcomeMessages.findIndex(msg => msg.role === roleName); + if (index !== -1) { + welcomeMessages[index].role = newRoleName; + message.channel.send(`Rôle associé au message de bienvenue pour le rôle ${roleName} changé en ${newRoleName}.`); + } else { + message.channel.send(`Le rôle ${roleName} n'existe pas.`); + } + } else { + message.channel.send("Options disponibles : \n- `/gachaoptions bienvenue ` pour définir le canal de bienvenue\n- `/gachaoptions adieu ` pour définir le canal d'adieu\n- `/gachaoptions messageadieu ` pour définir le message d'adieu\n- `/gachaoptions imageadieu ` pour définir l'image d'adieu\n- `/gachaoptions bienvenueimage ` pour définir l'image de bienvenue\n- `/gachaoptions rangauto ` pour définir la commande pour le rang automatique\n- `/gachaoptions rangs` pour afficher les rôles et leurs messages associés\n- `/gachaoptions ajoutermessage ` pour ajouter un nouveau message de bienvenue\n- `/gachaoptions modifierprobabilite ` pour modifier la probabilité d'un message de bienvenue\n- `/gachaoptions changerrole ` pour changer le rôle associé à un message de bienvenue"); + } + } +}); + +client.on('guildMemberAdd', member => { + // Vérifier si l'utilisateur est déjà dans la base de données + db.get(`SELECT * FROM userdata WHERE userid = ?`, member.id, (err, row) => { + if (err) { + console.error(err); + return; + } + if (row) { + // Utiliser les données stockées + const { message, role, image } = row; + // Envoi du message de bienvenue dans le canal approprié + const welcomeMessage = message.replace("{mention}", member.user); + const embed = new Discord.MessageEmbed() + .setColor('#0099ff') + .setDescription(welcomeMessage) + .setImage(image); + if (welcomeChannel) { + welcomeChannel.send(embed); + } + // Attribution automatique du rôle + const guildRole = member.guild.roles.cache.find(guildRole => guildRole.name === role); + if (guildRole) { + member.roles.add(guildRole); + } + } else { + // Choix du message de bienvenue, du rôle associé et de l'image + const { message, role, image } = chooseWelcomeMessage(); + // Envoi du message de bienvenue dans le canal approprié + const welcomeMessage = message.replace("{mention}", member.user); + const embed = new Discord.MessageEmbed() + .setColor('#0099ff') + .setDescription(welcomeMessage) + .setImage(image); + if (welcomeChannel) { + welcomeChannel.send(embed); + } + // Attribution automatique du rôle + const guildRole = member.guild.roles.cache.find(guildRole => guildRole.name === role); + if (guildRole) { + member.roles.add(guildRole); + } + // Stocker les données de bienvenue dans la base de données + db.run(`INSERT INTO userdata (userid, message, role, image) VALUES (?, ?, ?, ?)`, [member.id, message, role, image], err => { + if (err) { + console.error(err); + } + }); + } + }); +}); + +client.on('guildMemberRemove', member => { + // Message d'adieu + const farewellMessage = `Oh non ! ${member.user} n'est plus utile dans la méta actuelle et quitte notre équipe. Espérons qu'une prochaine mise à jour lui soit favorable !`; + const embed = new Discord.MessageEmbed() + .setColor('#0099ff') + .setDescription(farewellMessage) + .setImage("https://concepts.esenjin.xyz/cyla/v2/file/EDF7B4.gif"); + if (farewellChannel) { + farewellChannel.send(embed); + } + // Supprimer les données de bienvenue de la base de données + db.run(`DELETE FROM userdata WHERE userid = ?`, member.id, err => { + if (err) { + console.error(err); + } + }); +}); + +// Connexion du bot à Discord +client.login('TOKEN_DU_BOT'); + +const sqlite3 = require('sqlite3').verbose(); + +// Connexion à la base de données SQLite +const db = new sqlite3.Database('./userXP.db', (err) => { + if (err) { + console.error('Erreur lors de la connexion à la base de données :', err.message); + } else { + console.log('Connexion à la base de données réussie.'); + // Créer une table pour stocker l'XP des utilisateurs si elle n'existe pas déjà + db.run(`CREATE TABLE IF NOT EXISTS userXP ( + userId TEXT PRIMARY KEY, + xp INTEGER DEFAULT 0, + dailyXP INTEGER DEFAULT 0 + )`); + } +}); + +// Fonction pour récupérer l'XP d'un utilisateur depuis la base de données +function getUserXP(user) { + return new Promise((resolve, reject) => { + db.get('SELECT xp FROM userXP WHERE userId = ?', [user.id], (err, row) => { + if (err) { + reject(err); + } else { + resolve(row ? row.xp : 0); + } + }); + }); +} + +// Fonction pour mettre à jour l'XP d'un utilisateur dans la base de données +function setUserXP(user, xp) { + db.run('INSERT OR REPLACE INTO userXP (userId, xp) VALUES (?, ?)', [user.id, xp], (err) => { + if (err) { + console.error('Erreur lors de la mise à jour de l\'XP de l\'utilisateur :', err.message); + } + }); +} + +// Fonction pour récupérer l'XP quotidien d'un utilisateur depuis la base de données +function getUserDailyXP(user) { + return new Promise((resolve, reject) => { + db.get('SELECT dailyXP FROM userXP WHERE userId = ?', [user.id], (err, row) => { + if (err) { + reject(err); + } else { + resolve(row ? row.dailyXP : 0); + } + }); + }); +} + +// Fonction pour mettre à jour l'XP quotidien d'un utilisateur dans la base de données +function updateUserDailyXP(user, amount) { + db.run('INSERT OR REPLACE INTO userXP (userId, dailyXP) VALUES (?, ?)', [user.id, amount], (err) => { + if (err) { + console.error('Erreur lors de la mise à jour de l\'XP quotidienne de l\'utilisateur :', err.message); + } + }); +} + +// Supprimer l'utilisateur de la base de données lorsque celui-ci quitte le serveur +client.on('guildMemberRemove', member => { + db.run('DELETE FROM userXP WHERE userId = ?', [member.id], (err) => { + if (err) { + console.error('Erreur lors de la suppression de l\'utilisateur de la base de données :', err.message); + } + }); +}); + +// Variables globales pour stocker les paramètres XP et les canaux exclus +let excludedChannels = []; +let xpMultiplierChannels = []; +let xpCooldowns = {}; + +client.on('message', message => { + // Vérifier si le message est dans un salon exclu ou provenant du bot lui-même + if (excludedChannels.includes(message.channel.id) || message.author.bot) return; + + // Vérifier si l'utilisateur est un booster + let xpMultiplier = 1; + if (message.member.roles.cache.some(role => role.name === 'Booster')) { + xpMultiplier = 2; + } + + // Vérifier si le salon a un multiplicateur d'XP + if (xpMultiplierChannels.includes(message.channel.id)) { + xpMultiplier *= 2; + } + + // Vérifier si l'utilisateur est déjà en cooldown + if (xpCooldowns[message.author.id]) return; + + // Ajouter l'XP à l'utilisateur + addXP(message.author, 1 * xpMultiplier); + + // Mettre l'utilisateur en cooldown pour 2 minutes + xpCooldowns[message.author.id] = true; + setTimeout(() => { + delete xpCooldowns[message.author.id]; + }, 120000); +}); + +// Fonction pour ajouter de l'XP à un utilisateur +function addXP(user, amount) { + // Récupérer l'XP actuelle de l'utilisateur depuis la base de données + getUserXP(user) + .then(currentXP => { + // Ajouter la quantité spécifiée à l'XP actuelle + const newXP = currentXP + amount; + + // Mettre à jour l'XP de l'utilisateur dans la base de données + setUserXP(user, newXP); + + // Vérifier si l'utilisateur a atteint un nouveau rang + checkAndUpdateUserRank(user, newXP); + }) + .catch(err => { + console.error('Erreur lors de l\'ajout de l\'XP à l\'utilisateur :', err); + }); + + // Vérifier si l'utilisateur a atteint un nouveau rang + const currentXP = getUserXP(user); + const newRank = calculateRank(currentXP + amount); + if (newRank !== getUserRank(user)) { + const newRole = getRoleFromRank(newRank); + const notificationMessage = `Héhé ! C'est que tu as bien xp dit donc, tu peux désormais évoluer au ${newRole} ! Pour cela, rien de plus simple, utilise la commande /gachajaiunegrosseépée.`; + user.send(notificationMessage); + } +} + +// Fonction pour calculer le nouveau rang à partir de l'XP +function calculateRank(xp) { + if (xp >= 66666) return "Rang S++"; + if (xp >= 10000) return "Rang S+"; + if (xp >= 1000) return "Rang S"; + if (xp >= 100) return "Rang A"; + return "Rang B"; +} + +// Fonction pour obtenir le rang d'un utilisateur +function getUserRank(user) { + const xp = getUserXP(user); + return calculateRank(xp); +} + +// Fonction pour obtenir l'XP d'un utilisateur +function getUserXP(user) { + return new Promise((resolve, reject) => { + db.get('SELECT xp FROM userXP WHERE userId = ?', [user.id], (err, row) => { + if (err) { + reject(err); + } else { + resolve(row ? row.xp : 0); + } + }); + }); +} + +// Fonction pour obtenir le rôle associé à un rang +function getRoleFromRank(rank) { + // Tableau associant les rangs aux noms de rôles + const rankRoles = { + 'Rang B': 'Role B', + 'Rang A': 'Role A', + 'Rang S': 'Role S', + 'Rang S+': 'Role S+' + // Ajouter d'autres rangs et rôles au besoin + }; + + // Renvoyer le nom du rôle associé au rang spécifié + return rankRoles[rank]; +} + +// Commande pour obtenir le nouveau rang +client.on('message', message => { + if (message.content === '/gachajaiunegrosseépée') { + const user = message.author; + const currentXP = getUserXP(user); + const newRank = calculateRank(currentXP); + const roles = ['Rang B', 'Rang A', 'Rang S', 'Rang S+', 'Rang S++']; + const index = roles.indexOf(newRank); + if (index !== -1) { + const newRole = roles[index + 1]; + const guildRole = message.guild.roles.cache.find(role => role.name === newRole); + if (guildRole) { + const currentRole = message.guild.roles.cache.find(role => role.name === getUserRank(user)); + if (currentRole) { + message.member.roles.remove(currentRole); + } + message.member.roles.add(guildRole); + message.reply(`Félicitations ! Vous avez maintenant atteint le ${newRole}.`); + } + } + } +}); + +// Commande pour exclure des salons du décompte de l'XP +client.on('message', message => { + if (message.member.hasPermission('ADMINISTRATOR') && message.content.startsWith('/excludesalon')) { + const channelID = message.content.split(' ')[1]; + excludedChannels.push(channelID); + message.channel.send(`Le salon avec l'ID ${channelID} a été exclu du décompte de l'XP.`); + } +}); + +// Commande pour multiplier l'XP dans certains salons +client.on('message', message => { + if (message.member.hasPermission('ADMINISTRATOR') && message.content.startsWith('/multiplierxp')) { + const channelID = message.content.split(' ')[1]; + xpMultiplierChannels.push(channelID); + message.channel.send(`Le salon avec l'ID ${channelID} a maintenant un multiplicateur d'XP.`); + } +}); + +// Variable globale pour stocker le rôle déclenchant le Rang ULTRA +let ultraRole = null; + +// Fonction pour vérifier si l'utilisateur a le rôle déclenchant le Rang ULTRA +function hasUltraRole(user) { + return user.roles.cache.some(role => role === ultraRole); +} + +// Fonction pour obtenir le rôle "Rang ULTRA" et l'assigner à la variable globale +function setUltraRole(roleName) { + ultraRole = message.guild.roles.cache.find(role => role.name === roleName); +} + +// Commande pour définir le rôle déclenchant le Rang ULTRA +client.on('message', message => { + if (message.member.hasPermission('ADMINISTRATOR') && message.content.startsWith('/gachaoptions ultrarole')) { + const roleName = message.content.split(' ')[1]; + setUltraRole(roleName); + message.channel.send(`Le rôle déclenchant le Rang ULTRA a été défini sur "${roleName}".`); + } +}); + +// Variable globale pour stocker les temps de connexion des membres des salons vocaux +const voiceChannelConnections = {}; +// Variable globale pour stocker les temps de connexion des membres des salons vocaux en cooldown +const xpCooldowns = {}; +// Variable globale pour stocker les XP quotidiennes des utilisateurs +const userDailyXP = {}; + +// Fonction pour surveiller la connexion des membres aux salons vocaux +client.on('voiceStateUpdate', (oldState, newState) => { + const user = newState.member; + // Vérifier si l'utilisateur existe et n'est pas un bot + if (!user || user.user.bot) return; + + const oldChannel = oldState.channel; + const newChannel = newState.channel; + + // L'utilisateur est entré dans un salon vocal + if (!oldChannel && newChannel) { + voiceChannelConnections[user.id] = Date.now(); // Enregistrer le moment de la connexion + } + // L'utilisateur est sorti d'un salon vocal + else if (oldChannel && !newChannel) { + // Vérifier si l'utilisateur est enregistré comme étant connecté à un salon vocal + if (voiceChannelConnections[user.id]) { + const timeSpentInVoiceChannel = Math.floor((Date.now() - voiceChannelConnections[user.id]) / 1000); // Calculer la durée en secondes + const xpGained = calculateXPFromTime(timeSpentInVoiceChannel, user); // Convertir la durée en XP + addXP(user, xpGained); // Ajouter l'XP à l'utilisateur + delete voiceChannelConnections[user.id]; // Supprimer l'enregistrement de la connexion + } + } +}); + +// Fonction pour calculer l'XP à partir du temps passé dans un salon vocal +function calculateXPFromTime(timeSpentInVoiceChannel, user) { + // Déterminer le multiplicateur d'XP en fonction du boost Nitro + let xpMultiplier = 1; + if (user.roles.cache.some(role => role.name === 'Booster')) { + xpMultiplier = 2; + } + + // Vérifier si l'utilisateur est en cooldown pour le salon vocal + if (!xpCooldowns[user.id]) { + // Appliquer le multiplicateur d'XP + const xpPerMinute = 1 * xpMultiplier; + const xpGained = Math.floor(timeSpentInVoiceChannel / 120) * xpPerMinute; // 1 XP pour 2 minutes + // Appliquer le plafond d'XP quotidien + if (xpGained > 0) { + if (xpMultiplier === 1 && getUserDailyXP(user) + xpGained > 200) { + xpGained = 200 - getUserDailyXP(user); + } else if (xpMultiplier === 2 && getUserDailyXP(user) + xpGained > 500) { + xpGained = 500 - getUserDailyXP(user); + } + updateUserDailyXP(user, xpGained); + } + return xpGained; + } else { + return 0; // Aucune XP gagnée pendant le cooldown + } +} + +// Fonction pour ajouter de l'XP à un utilisateur +function addXP(user, amount) { + // Récupérer l'XP actuelle de l'utilisateur + let currentXP = getUserXP(user); + // Ajouter la quantité spécifiée à l'XP actuelle + currentXP += amount; + // Mettre à jour l'XP de l'utilisateur + setUserXP(user, currentXP); +} + +// Fonction pour récupérer l'XP quotidien d'un utilisateur +function getUserDailyXP(user) { + // Récupérer l'XP quotidien de l'utilisateur depuis le stockage approprié + return userDailyXP[user.id] || 0; // Si l'XP quotidien n'est pas défini, retourner 0 +} + +// Fonction pour mettre à jour l'XP quotidien d'un utilisateur +function updateUserDailyXP(user, amount) { + // Mettre à jour l'XP quotidien de l'utilisateur dans le stockage approprié + userDailyXP[user.id] = amount; +} + +// Fonction pour exclure des salons vocaux du décompte de l'XP +client.on('message', message => { + if (message.member.hasPermission('ADMINISTRATOR') && message.content.startsWith('/gachaoptions excludevoice')) { + const channelID = message.content.split(' ')[1]; + // Ajouter le salon vocal à la liste des exclusions + excludedVoiceChannels.push(channelID); + message.channel.send(`Le salon vocal avec l'ID ${channelID} a été exclu du décompte de l'XP.`); + } +}); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6bc3dc9 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,742 @@ +{ + "name": "gachamelia", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "gachamelia", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "discord.js": "^14.16.2", + "glob": "^11.0.0", + "typescript": "^5.6.2" + } + }, + "node_modules/@discordjs/builders": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.9.0.tgz", + "integrity": "sha512-0zx8DePNVvQibh5ly5kCEei5wtPBIUbSoE9n+91Rlladz4tgtFbJ36PZMxxZrTEOQ7AHMZ/b0crT/0fCy6FTKg==", + "dependencies": { + "@discordjs/formatters": "^0.5.0", + "@discordjs/util": "^1.1.1", + "@sapphire/shapeshift": "^4.0.0", + "discord-api-types": "0.37.97", + "fast-deep-equal": "^3.1.3", + "ts-mixer": "^6.0.4", + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=18" + }, + "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==", + "engines": { + "node": ">=16.11.0" + } + }, + "node_modules/@discordjs/formatters": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.5.0.tgz", + "integrity": "sha512-98b3i+Y19RFq1Xke4NkVY46x8KjJQjldHUuEbCqMvp1F5Iq9HgnGpu91jOi/Ufazhty32eRsKnnzS8n4c+L93g==", + "dependencies": { + "discord-api-types": "0.37.97" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/rest": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.4.0.tgz", + "integrity": "sha512-Xb2irDqNcq+O8F0/k/NaDp7+t091p+acb51iA4bCKfIn+WFWd6HrNvcsSbMMxIR9NjcMZS6NReTKygqiQN+ntw==", + "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.37.97", + "magic-bytes.js": "^1.10.0", + "tslib": "^2.6.3", + "undici": "6.19.8" + }, + "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==", + "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==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.1.1.tgz", + "integrity": "sha512-PZ+vLpxGCRtmr2RMkqh8Zp+BenUaJqlS6xhgWKEZcgC/vfHLEzpHtKkB0sl3nZWpwtcKk6YWy+pU3okL2I97FA==", + "dependencies": { + "@discordjs/collection": "^2.1.0", + "@discordjs/rest": "^2.3.0", + "@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.37.83", + "tslib": "^2.6.2", + "ws": "^8.16.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==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/@discordjs/ws/node_modules/discord-api-types": { + "version": "0.37.83", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.83.tgz", + "integrity": "sha512-urGGYeWtWNYMKnYlZnOnDHm8fVRffQs3U0SpE8RHeiuLKb/u92APS8HoQnPTFbnXmY1vVnXjXO4dOxcAn3J+DA==" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@sapphire/async-queue": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.3.tgz", + "integrity": "sha512-x7zadcfJGxFka1Q3f8gCts1F0xMwCKbZweM85xECGI0hBTeIZJGGCrHgLggihBoprlQ/hBmDR5LKfIPqnmHM3w==", + "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==", + "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==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/@types/node": { + "version": "22.5.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.5.tgz", + "integrity": "sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA==", + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@vladfrangu/async_event_emitter": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.6.tgz", + "integrity": "sha512-RaI5qZo6D2CVS6sTHFKg1v5Ohq/+Bo2LZ5gzUEwZ/WkHhwtGTCB/sVLw8ijOkAUxasZ+WshN/Rzj4ywsABJ5ZA==", + "engines": { + "node": ">=v14.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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==" + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/discord-api-types": { + "version": "0.37.97", + "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.97.tgz", + "integrity": "sha512-No1BXPcVkyVD4ZVmbNgDKaBoqgeQ+FJpzZ8wqHkfmBnTZig1FcH3iPPersiK1TUIAzgClh2IvOuVUYfcWLQAOA==" + }, + "node_modules/discord.js": { + "version": "14.16.2", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-14.16.2.tgz", + "integrity": "sha512-VGNi9WE2dZIxYM8/r/iatQQ+3LT8STW4hhczJOwm+DBeHq66vsKDCk8trChNCB01sMO9crslYuEMeZl2d7r3xw==", + "dependencies": { + "@discordjs/builders": "^1.9.0", + "@discordjs/collection": "1.5.3", + "@discordjs/formatters": "^0.5.0", + "@discordjs/rest": "^2.4.0", + "@discordjs/util": "^1.1.1", + "@discordjs/ws": "1.1.1", + "@sapphire/snowflake": "3.5.3", + "discord-api-types": "0.37.97", + "fast-deep-equal": "3.1.3", + "lodash.snakecase": "4.1.1", + "tslib": "^2.6.3", + "undici": "6.19.8" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/discordjs/discord.js?sponsor" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "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==" + }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/jackspeak": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "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==" + }, + "node_modules/lru-cache": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.1.tgz", + "integrity": "sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-bytes.js": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.10.0.tgz", + "integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==" + }, + "node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "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==" + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/typescript": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz", + "integrity": "sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", + "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "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 new file mode 100644 index 0000000..ad1c673 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "gachamelia", + "version": "1.0.0", + "description": "Transforme ton serveur Discord en *gacha* géant !", + "main": "old/index.js", + "scripts": { + "clean": "rm -rf dist", + "build": "tsc --build", + "start": "npm run build && node dist/index.js", + "start:clean": "npm run clean && npm run build && node dist/index.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "discord.js": "^14.16.2", + "glob": "^11.0.0", + "typescript": "^5.6.2" + } +} diff --git a/src/base/classes/CustomClient.ts b/src/base/classes/CustomClient.ts new file mode 100644 index 0000000..0c85bb2 --- /dev/null +++ b/src/base/classes/CustomClient.ts @@ -0,0 +1,26 @@ +import IConfig from "../interfaces/IConfig"; +import ICustomClient from "../interfaces/ICustomClient"; +import {Client} from "discord.js"; +import Handler from "./Handler"; + +export default class CustomClient extends Client implements ICustomClient { + config: IConfig; + handler: Handler; + constructor() { + super({ + intents: [], + }) + + this.config = require(`${process.cwd()}/data/config.json`); + this.handler = new Handler(this); + } + async init(): Promise { + await this.LoadHandlers(); + this.login(this.config.token).catch(console.error); + } + + async LoadHandlers(): Promise { + await this.handler.loadEvents(); + } + +} \ No newline at end of file diff --git a/src/base/classes/Event.ts b/src/base/classes/Event.ts new file mode 100644 index 0000000..f0695dd --- /dev/null +++ b/src/base/classes/Event.ts @@ -0,0 +1,21 @@ +import { Events } from "discord.js"; +import IEvent from "../interfaces/IEvent"; +import CustomClient from "./CustomClient"; +import IEventOptions from "../interfaces/IEventOptions"; + +export default class Event implements IEvent { + client: CustomClient; + name: Events; + description: string; + once: boolean; + + constructor(client: CustomClient, options: IEventOptions) { + this.client = client; + this.name = options.name; + this.description = options.description; + this.once = options.once; + } + execute(...args: any[]): void {}; + + +} \ No newline at end of file diff --git a/src/base/classes/Handler.ts b/src/base/classes/Handler.ts new file mode 100644 index 0000000..10adb91 --- /dev/null +++ b/src/base/classes/Handler.ts @@ -0,0 +1,37 @@ +import IHandler from "../interfaces/IHandler"; +import path from "node:path"; +import {glob} from "glob"; +import CustomClient from "./CustomClient"; +import Event from "./Event"; + +export default class Handler implements IHandler { + client: CustomClient; + constructor(client: CustomClient) { + this.client = client; + } + async loadEvents(): Promise { + const files = (await glob(`dist/events/**/*.js`)).map(filePath => path.resolve(filePath)); + + files.map(async (file: string) => { + const event : Event = new (await import(file)).default(this.client); + + if (!event.name) + return delete require.cache[require.resolve(file)] && console.log(`Event ${file.split('/').pop()} n'a pas de nom`); + + const execute = (...args: any[]) => event.execute(...args); + + if (event.once) { + // @ts-ignore + this.client.once(event.name, execute); + } else { + // @ts-ignore + this.client.on(event.name, execute); + } + + console.log(`Event ${file.split('/').pop()} chargé`); + + return delete require.cache[require.resolve(file)]; + }); + } + +} \ No newline at end of file diff --git a/src/base/interfaces/IConfig.ts b/src/base/interfaces/IConfig.ts new file mode 100644 index 0000000..c4a1977 --- /dev/null +++ b/src/base/interfaces/IConfig.ts @@ -0,0 +1,3 @@ +export default interface IConfig { + token: string; +} \ No newline at end of file diff --git a/src/base/interfaces/ICustomClient.ts b/src/base/interfaces/ICustomClient.ts new file mode 100644 index 0000000..0ee782c --- /dev/null +++ b/src/base/interfaces/ICustomClient.ts @@ -0,0 +1,7 @@ +import IConfig from "./IConfig"; + +export default interface ICustomClient { + config: IConfig; + init(): void; + LoadHandlers(): void; +} \ No newline at end of file diff --git a/src/base/interfaces/IEvent.ts b/src/base/interfaces/IEvent.ts new file mode 100644 index 0000000..e8f8717 --- /dev/null +++ b/src/base/interfaces/IEvent.ts @@ -0,0 +1,11 @@ +import CustomClient from "../classes/CustomClient"; +import {Events} from "discord.js"; + +export default interface IEvent { + client: CustomClient; + name: Events; + description: string; + once: boolean; + + execute(...args: any[]): void; +} \ No newline at end of file diff --git a/src/base/interfaces/IEventOptions.ts b/src/base/interfaces/IEventOptions.ts new file mode 100644 index 0000000..c6cf583 --- /dev/null +++ b/src/base/interfaces/IEventOptions.ts @@ -0,0 +1,7 @@ +import {Events} from "discord.js"; + +export default interface IEventOptions { + name: Events; + description: string; + once: boolean; +} \ No newline at end of file diff --git a/src/base/interfaces/IHandler.ts b/src/base/interfaces/IHandler.ts new file mode 100644 index 0000000..a488665 --- /dev/null +++ b/src/base/interfaces/IHandler.ts @@ -0,0 +1,3 @@ +export default interface IHandler { + loadEvents(): void; +} \ No newline at end of file diff --git a/src/events/client/Ready.ts b/src/events/client/Ready.ts new file mode 100644 index 0000000..5949f6d --- /dev/null +++ b/src/events/client/Ready.ts @@ -0,0 +1,16 @@ +import Event from '../../base/classes/Event'; +import CustomClient from "../../base/classes/CustomClient"; +import {Events} from "discord.js"; +export default class Ready extends Event { + constructor(client: CustomClient) { + super(client, { + name: Events.ClientReady, + once: true, + description: 'Event se déclenchant lorsque le bot est prêt' + }); + } + + execute(...args: any[]): void { + console.log(`Connecté en tant que ${this.client.user?.tag}!`); + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..75dfa4e --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +import CustomClient from "./base/classes/CustomClient"; +(async () => await new CustomClient().init())(); \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..bc3961d --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "commonjs", + "rootDir": "./src", + "outDir": "./dist", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +}