diff --git a/app/commands/countingAdd.js b/app/commands/countingAdd.js new file mode 100644 index 0000000..a9ce403 --- /dev/null +++ b/app/commands/countingAdd.js @@ -0,0 +1,122 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); +const db = require("../db"); + +module.exports = addCommand({ + name: "counting-add", + description: "Ajoute un nombre au compteur actuel.", + aliases: ["countadd", "addcount"], + permissions: [PermissionFlagsBits.ManageGuild], + botOwnerOnly: false, + dm: false, + scope: "guild", + + guildCondition: async (guildId) => { + return new Promise((resolve) => { + db.get( + "SELECT enabled FROM counting_config WHERE guild_id = ?", + [guildId], + (err, row) => { + if (err) { + console.error(`DB error in guildCondition for guild ${guildId}`, err); + return resolve(false); + } + resolve(!!row?.enabled); + } + ); + }); + }, + + slashOptions: [ + { + type: "INTEGER", + name: "nombre", + description: "Le nombre à ajouter au compteur", + required: true, + }, + ], + + executePrefix: async (client, message, args) => { + const number = parseInt(args[0], 10); + if (isNaN(number) || number <= 0) { + return message.reply({ + embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Veuillez spécifier un nombre valide (> 0).")] + }); + } + + await addCount(message.guild, number, message.author, message); + }, + + executeSlash: async (client, interaction) => { + const number = interaction.options.getInteger("nombre"); + if (number <= 0) { + return interaction.reply({ + embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Le nombre doit être supérieur à 0.")], + ephemeral: true + }); + } + + await addCount(interaction.guild, number, interaction.user, interaction); + }, +}); + +async function addCount(guild, amount, moderator, context) { + try { + const config = await db.getAsync( + "SELECT enabled, channel_id, current_count FROM counting_config WHERE guild_id = ?", + [guild.id] + ); + + if (!config || !config.enabled) { + const embed = new EmbedBuilder() + .setColor(0xED4245) + .setDescription("❌ Le système de comptage n'est pas activé sur ce serveur."); + return context.reply({ embeds: [embed], ephemeral: true }); + } + + const oldCount = config.current_count || 0; + const newCount = oldCount + amount; + + // Mettre à jour le compteur + await new Promise((resolve, reject) => { + db.run( + "UPDATE counting_config SET current_count = ?, last_user_id = NULL WHERE guild_id = ?", + [newCount, guild.id], + (err) => { + if (err) reject(err); + else resolve(); + } + ); + }); + + const embed = new EmbedBuilder() + .setColor(0x57F287) + .setTitle("🔢 Compteur augmenté") + .setDescription(`**+${amount}** ajouté au compteur !`) + .addFields( + { name: "📊 Ancien", value: `${oldCount}`, inline: true }, + { name: "📊 Nouveau", value: `${newCount}`, inline: true }, + { name: "➡️ Prochain", value: `${newCount + 1}`, inline: true } + ) + .setFooter({ text: `Modifié par ${moderator.username}`, iconURL: moderator.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); + + await context.reply({ embeds: [embed] }); + + // Envoyer un message dans le salon de comptage + if (config.channel_id) { + const channel = guild.channels.cache.get(config.channel_id); + if (channel) { + const announceEmbed = new EmbedBuilder() + .setColor(0x57F287) + .setDescription(`🔢 Un administrateur a ajouté **+${amount}** au compteur !\n\n📊 Compteur actuel : **${newCount}**\n➡️ Le prochain nombre est **${newCount + 1}**`); + + await channel.send({ embeds: [announceEmbed] }); + } + } + } catch (error) { + console.error(error); + const errorEmbed = new EmbedBuilder().setColor(0xED4245).setDescription("❌ Une erreur est survenue."); + await context.reply({ embeds: [errorEmbed], ephemeral: true }); + } +} diff --git a/app/commands/countingRemove.js b/app/commands/countingRemove.js new file mode 100644 index 0000000..69e3a40 --- /dev/null +++ b/app/commands/countingRemove.js @@ -0,0 +1,122 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); +const db = require("../db"); + +module.exports = addCommand({ + name: "counting-remove", + description: "Retire un nombre du compteur actuel.", + aliases: ["countremove", "removecount"], + permissions: [PermissionFlagsBits.ManageGuild], + botOwnerOnly: false, + dm: false, + scope: "guild", + + guildCondition: async (guildId) => { + return new Promise((resolve) => { + db.get( + "SELECT enabled FROM counting_config WHERE guild_id = ?", + [guildId], + (err, row) => { + if (err) { + console.error(`DB error in guildCondition for guild ${guildId}`, err); + return resolve(false); + } + resolve(!!row?.enabled); + } + ); + }); + }, + + slashOptions: [ + { + type: "INTEGER", + name: "nombre", + description: "Le nombre à retirer du compteur", + required: true, + }, + ], + + executePrefix: async (client, message, args) => { + const number = parseInt(args[0], 10); + if (isNaN(number) || number <= 0) { + return message.reply({ + embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Veuillez spécifier un nombre valide (> 0).")] + }); + } + + await removeCount(message.guild, number, message.author, message); + }, + + executeSlash: async (client, interaction) => { + const number = interaction.options.getInteger("nombre"); + if (number <= 0) { + return interaction.reply({ + embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Le nombre doit être supérieur à 0.")], + ephemeral: true + }); + } + + await removeCount(interaction.guild, number, interaction.user, interaction); + }, +}); + +async function removeCount(guild, amount, moderator, context) { + try { + const config = await db.getAsync( + "SELECT enabled, channel_id, current_count FROM counting_config WHERE guild_id = ?", + [guild.id] + ); + + if (!config || !config.enabled) { + const embed = new EmbedBuilder() + .setColor(0xED4245) + .setDescription("❌ Le système de comptage n'est pas activé sur ce serveur."); + return context.reply({ embeds: [embed], ephemeral: true }); + } + + const oldCount = config.current_count || 0; + const newCount = Math.max(0, oldCount - amount); // Ne pas aller en dessous de 0 + + // Mettre à jour le compteur + await new Promise((resolve, reject) => { + db.run( + "UPDATE counting_config SET current_count = ?, last_user_id = NULL WHERE guild_id = ?", + [newCount, guild.id], + (err) => { + if (err) reject(err); + else resolve(); + } + ); + }); + + const embed = new EmbedBuilder() + .setColor(0xFEE75C) + .setTitle("🔢 Compteur diminué") + .setDescription(`**-${amount}** retiré du compteur !`) + .addFields( + { name: "📊 Ancien", value: `${oldCount}`, inline: true }, + { name: "📊 Nouveau", value: `${newCount}`, inline: true }, + { name: "➡️ Prochain", value: `${newCount + 1}`, inline: true } + ) + .setFooter({ text: `Modifié par ${moderator.username}`, iconURL: moderator.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); + + await context.reply({ embeds: [embed] }); + + // Envoyer un message dans le salon de comptage + if (config.channel_id) { + const channel = guild.channels.cache.get(config.channel_id); + if (channel) { + const announceEmbed = new EmbedBuilder() + .setColor(0xFEE75C) + .setDescription(`🔢 Un administrateur a retiré **-${amount}** du compteur !\n\n📊 Compteur actuel : **${newCount}**\n➡️ Le prochain nombre est **${newCount + 1}**`); + + await channel.send({ embeds: [announceEmbed] }); + } + } + } catch (error) { + console.error(error); + const errorEmbed = new EmbedBuilder().setColor(0xED4245).setDescription("❌ Une erreur est survenue."); + await context.reply({ embeds: [errorEmbed], ephemeral: true }); + } +} diff --git a/app/commands/countingSet.js b/app/commands/countingSet.js new file mode 100644 index 0000000..eb5331d --- /dev/null +++ b/app/commands/countingSet.js @@ -0,0 +1,118 @@ +const addCommand = require("../fonctions/addCommand"); +const { EmbedBuilder, PermissionFlagsBits } = require("discord.js"); +const db = require("../db"); + +module.exports = addCommand({ + name: "counting-set", + description: "Définit le compteur à une valeur spécifique.", + aliases: ["countset", "setcount"], + permissions: [PermissionFlagsBits.ManageGuild], + botOwnerOnly: false, + dm: false, + scope: "guild", + + guildCondition: async (guildId) => { + return new Promise((resolve) => { + db.get( + "SELECT enabled FROM counting_config WHERE guild_id = ?", + [guildId], + (err, row) => { + if (err) { + console.error(`DB error in guildCondition for guild ${guildId}`, err); + return resolve(false); + } + resolve(!!row?.enabled); + } + ); + }); + }, + + slashOptions: [ + { + type: "INTEGER", + name: "nombre", + description: "La nouvelle valeur du compteur", + required: true, + }, + ], + + executePrefix: async (client, message, args) => { + const number = parseInt(args[0], 10); + if (isNaN(number) || number < 0) { + return message.reply({ + embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Veuillez spécifier un nombre valide (>= 0).")] + }); + } + + await setCount(message.guild, number, message.author, message); + }, + + executeSlash: async (client, interaction) => { + const number = interaction.options.getInteger("nombre"); + if (number < 0) { + return interaction.reply({ + embeds: [new EmbedBuilder().setColor(0xED4245).setDescription("❌ Le nombre doit être supérieur ou égal à 0.")], + ephemeral: true + }); + } + + await setCount(interaction.guild, number, interaction.user, interaction); + }, +}); + +async function setCount(guild, number, moderator, context) { + try { + const config = await db.getAsync( + "SELECT enabled, channel_id FROM counting_config WHERE guild_id = ?", + [guild.id] + ); + + if (!config || !config.enabled) { + const embed = new EmbedBuilder() + .setColor(0xED4245) + .setDescription("❌ Le système de comptage n'est pas activé sur ce serveur."); + return context.reply({ embeds: [embed], ephemeral: true }); + } + + // Mettre à jour le compteur + await new Promise((resolve, reject) => { + db.run( + "UPDATE counting_config SET current_count = ?, last_user_id = NULL WHERE guild_id = ?", + [number, guild.id], + (err) => { + if (err) reject(err); + else resolve(); + } + ); + }); + + const embed = new EmbedBuilder() + .setColor(0x57F287) + .setTitle("🔢 Compteur modifié") + .setDescription(`Le compteur a été défini à **${number}**.`) + .addFields( + { name: "👮 Modérateur", value: moderator.tag, inline: true }, + { name: "➡️ Prochain nombre", value: `${number + 1}`, inline: true } + ) + .setFooter({ text: `Modifié par ${moderator.username}`, iconURL: moderator.displayAvatarURL({ dynamic: true }) }) + .setTimestamp(); + + await context.reply({ embeds: [embed] }); + + // Envoyer un message dans le salon de comptage + if (config.channel_id) { + const channel = guild.channels.cache.get(config.channel_id); + if (channel) { + const announceEmbed = new EmbedBuilder() + .setColor(0x5865F2) + .setDescription(`🔢 Le compteur a été défini à **${number}** par un administrateur.\n\n➡️ Le prochain nombre est **${number + 1}**`); + + await channel.send({ embeds: [announceEmbed] }); + } + } + } catch (error) { + console.error(error); + const errorEmbed = new EmbedBuilder().setColor(0xED4245).setDescription("❌ Une erreur est survenue."); + await context.reply({ embeds: [errorEmbed], ephemeral: true }); + } +} diff --git a/app/db.js b/app/db.js index 111c4a7..88a8466 100644 --- a/app/db.js +++ b/app/db.js @@ -176,6 +176,14 @@ db.exec(` owner_id TEXT NOT NULL, created_at INTEGER NOT NULL ); + + CREATE TABLE IF NOT EXISTS counting_config ( + guild_id TEXT PRIMARY KEY, + enabled INTEGER NOT NULL DEFAULT 0, + channel_id TEXT, + current_count INTEGER NOT NULL DEFAULT 0, + last_user_id TEXT + ); `); module.exports = db; diff --git a/app/events/messageCreate.js b/app/events/messageCreate.js index 3cf78dc..2f97bb4 100644 --- a/app/events/messageCreate.js +++ b/app/events/messageCreate.js @@ -193,5 +193,70 @@ module.exports = { ); } ); + + // ===== COUNTING SYSTEM ===== + handleCounting(message, guildId); }, }; + +// ===== COUNTING HANDLER ===== +async function handleCounting(message, guildId) { + try { + const config = await db.getAsync( + "SELECT enabled, channel_id, current_count, last_user_id FROM counting_config WHERE guild_id = ?", + [guildId] + ); + + if (!config || !config.enabled || config.channel_id !== message.channel.id) return; + + const content = message.content.trim(); + const number = parseInt(content, 10); + + // Supprimer les messages qui ne sont pas des nombres valides + if (isNaN(number) || content !== number.toString()) { + await message.delete().catch(() => {}); + return; + } + + const expectedNumber = config.current_count + 1; + + // Vérifier que l'utilisateur n'est pas le même que le précédent + if (config.last_user_id === message.author.id) { + await message.delete().catch(() => {}); + const errorMsg = await message.channel.send({ + content: `❌ **${message.author.username}**, tu ne peux pas compter deux fois de suite ! Le prochain nombre est toujours **${expectedNumber}**.` + }); + setTimeout(() => errorMsg.delete().catch(() => {}), 5000); + return; + } + + // Vérifier que le nombre est correct + if (number !== expectedNumber) { + await message.delete().catch(() => {}); + const errorMsg = await message.channel.send({ + content: `❌ **${message.author.username}**, mauvais nombre ! Le prochain nombre est **${expectedNumber}**.` + }); + setTimeout(() => errorMsg.delete().catch(() => {}), 5000); + return; + } + + // Nombre correct ! + await message.react("✅"); + + // Mettre à jour le compteur + db.run( + "UPDATE counting_config SET current_count = ?, last_user_id = ? WHERE guild_id = ?", + [number, message.author.id, guildId] + ); + + // Milestones pour les nombres ronds + if (number % 100 === 0) { + await message.reply({ + content: `🎉 **${number}** atteint ! Bien joué à tous !`, + allowedMentions: { repliedUser: false } + }); + } + } catch (err) { + console.error("Erreur counting:", err); + } +} diff --git a/app/public/guild.html b/app/public/guild.html index 4876129..f2576be 100644 --- a/app/public/guild.html +++ b/app/public/guild.html @@ -54,6 +54,10 @@ 🔊 Salons temporaires + + 🔢 + Comptage + @@ -655,6 +659,45 @@ + +
+
+
+
+ 🔢 +

Système de comptage

+
+ +
+
+
+ 💡 Comment ça marche ?
+ Les membres comptent à l'infini dans le salon dédié. Un membre ne peut pas compter deux fois de suite, + ils doivent alterner. Si quelqu'un se trompe, le compteur repart à 0 ! +
+ +
+ + Le salon textuel où les membres peuvent compter + +
+ +
+ + + Utilisez les commandes /counting-set, /counting-add, /counting-remove pour modifier +
+
+ +
+
+ @@ -670,5 +713,6 @@ + diff --git a/app/public/guild/countingForm.js b/app/public/guild/countingForm.js new file mode 100644 index 0000000..747c35f --- /dev/null +++ b/app/public/guild/countingForm.js @@ -0,0 +1,79 @@ +// ===== COUNTING FORM ===== +(async function () { + const enabledCheckbox = document.getElementById("counting-enabled"); + const channelSelect = document.getElementById("counting-channel"); + const currentCountInput = document.getElementById("counting-current"); + const saveBtn = document.getElementById("save-counting"); + + // Charger les salons textuels + async function loadTextChannels() { + try { + const res = await fetch(`/api/bot/get-text-channels/${guildId}`); + const channels = await res.json(); + channelSelect.innerHTML = ''; + channels.forEach(ch => { + const opt = document.createElement("option"); + opt.value = ch.id; + opt.textContent = "#" + ch.name; + channelSelect.appendChild(opt); + }); + } catch (err) { + console.error("Erreur chargement salons textuels:", err); + } + } + + // Charger la configuration + async function loadConfig() { + try { + await loadTextChannels(); + + const res = await fetch(`/api/bot/get-counting-config/${guildId}`); + const data = await res.json(); + + enabledCheckbox.checked = data.enabled; + currentCountInput.value = data.currentCount || 0; + + if (data.channelId) { + channelSelect.value = data.channelId; + } + } catch (err) { + console.error("Erreur chargement config counting:", err); + } + } + + // Sauvegarder + saveBtn.addEventListener("click", async () => { + saveBtn.disabled = true; + saveBtn.textContent = "Sauvegarde..."; + + const data = { + guildId, + enabled: enabledCheckbox.checked, + channelId: channelSelect.value || null + }; + + try { + const res = await fetch("/api/bot/save-counting-config", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(data) + }); + const result = await res.json(); + + if (result.success) { + showStatus("status-counting-form", "Configuration sauvegardée ✅", "success"); + } else { + showStatus("status-counting-form", "Erreur lors de la sauvegarde ❌", "error"); + } + } catch (err) { + console.error("Erreur sauvegarde:", err); + showStatus("status-counting-form", "Erreur de connexion ❌", "error"); + } + + saveBtn.disabled = false; + saveBtn.textContent = "Sauvegarder"; + }); + + // Init + loadConfig(); +})(); diff --git a/app/public/index.html b/app/public/index.html index fe0888c..529cdee 100644 --- a/app/public/index.html +++ b/app/public/index.html @@ -77,6 +77,11 @@

Salons Temporaires

Créez des salons vocaux privés à la demande. Quand un membre rejoint le salon créateur, un salon personnel est créé automatiquement.

+
+
🔢
+

Jeu de Comptage

+

Un mini-jeu collaboratif où les membres comptent à l'infini. Ils doivent alterner et ne pas se tromper sinon le compteur repart à 0 !

+
⚙️

Dashboard Intuitif

diff --git a/app/routes/api.js b/app/routes/api.js index 715c063..620516d 100644 --- a/app/routes/api.js +++ b/app/routes/api.js @@ -829,5 +829,66 @@ module.exports = (app, db, client) => { res.json(categories); }); + // ===== COUNTING CONFIG ===== + router.post("/bot/save-counting-config", express.json(), (req, res) => { + const { guildId, enabled, channelId } = req.body; + + if (!req.session.guilds) { + return res.status(401).json({ success: false }); + } + + const isAdmin = req.session.guilds.find( + g => g.id === guildId && (BigInt(g.permissions) & 0x8n) === 0x8n + ); + + if (!isAdmin) { + return res.status(403).json({ success: false }); + } + + db.run( + `INSERT INTO counting_config (guild_id, enabled, channel_id, current_count, last_user_id) + VALUES (?, ?, ?, 0, NULL) + ON CONFLICT(guild_id) DO UPDATE SET + enabled = ?, channel_id = ?`, + [ + guildId, + enabled ? 1 : 0, + channelId, + enabled ? 1 : 0, + channelId + ], + err => { + if (err) { + console.error(err); + return res.status(500).json({ success: false }); + } + res.json({ success: true }); + } + ); + }); + + router.get("/bot/get-counting-config/:guildId", (req, res) => { + const { guildId } = req.params; + + db.get( + "SELECT enabled, channel_id, current_count FROM counting_config WHERE guild_id = ?", + [guildId], + (err, row) => { + if (err || !row) { + return res.json({ + enabled: false, + channelId: null, + currentCount: 0 + }); + } + res.json({ + enabled: !!row.enabled, + channelId: row.channel_id, + currentCount: row.current_count || 0 + }); + } + ); + }); + app.use("/api", router); };