From 08b444efdd5e521ef9d0466cbbbf56aeded6baee Mon Sep 17 00:00:00 2001 From: Arthur Puechberty Date: Fri, 16 Jan 2026 23:41:39 +0100 Subject: [PATCH] add a major part of user level --- app/bot.js | 136 +++++++++- app/db.js | 32 +++ app/public/guild.html | 344 ++++++++++-------------- app/public/guild/autoroleNewUserForm.js | 29 ++ app/public/guild/autoroleVocalForm.js | 43 +++ app/public/guild/goodbyeForm.js | 32 +++ app/public/guild/guildBase.js | 40 +++ app/public/guild/levelForm.js | 119 ++++++++ app/public/guild/welcomeForm.js | 32 +++ app/routes/api.js | 210 +++++++++++++++ 10 files changed, 810 insertions(+), 207 deletions(-) create mode 100644 app/public/guild/autoroleNewUserForm.js create mode 100644 app/public/guild/autoroleVocalForm.js create mode 100644 app/public/guild/goodbyeForm.js create mode 100644 app/public/guild/guildBase.js create mode 100644 app/public/guild/levelForm.js create mode 100644 app/public/guild/welcomeForm.js diff --git a/app/bot.js b/app/bot.js index 7791ce6..dc35da8 100644 --- a/app/bot.js +++ b/app/bot.js @@ -4,10 +4,11 @@ loadSlashCommands(); const db = require("./db"); const { Client, GatewayIntentBits, ActivityType, Events } = require("discord.js"); +const e = require('express'); const client = new Client({ intents: Object.values(GatewayIntentBits) }); -client.once("clientReady", () => { +client.once(Events.ClientReady, () => { console.log(`Bot connecté en tant que ${client.user.tag}`); client.user.setActivity("LazyBot à votre service !", { type: ActivityType.Custom }); }); @@ -115,6 +116,139 @@ client.on(Events.VoiceStateUpdate, (oldState, newState) => { ); }); + +client.on(Events.MessageCreate, message => { + if (message.author.bot) return; + + const guildId = message.guild.id; + + db.get( + `SELECT + enabled, + level_announcements_enabled, + level_announcements_channel_id, + level_announcements_message, + xp_courbe_type, + multiplier_courbe_for_level, + level_annoncement_every_level, + level_max, + role_with_without_type, + role_with_without_xp, + salon_with_without_type, + salon_with_without_xp, + gain_xp_on_message, + gain_xp_message_lower_bound, + gain_xp_message_upper_bound, + cooldown_xp_message_seconds + FROM levels_config WHERE guild_id = ?`, + [guildId], + (err, row) => { + if (err || !row || !row.enabled || !row.gain_xp_on_message) return; + if (row.role_with_without_type === "with") { + const userRoles = message.member.roles.cache; + const requiredRoles = JSON.parse(row.role_with_without_xp || "[]"); + if (!requiredRoles.some(roleId => userRoles.has(roleId))) { + return; // User has an excluded role + } + } else if (row.role_with_without_type === "without") { + const userRoles = message.member.roles.cache; + const excludedRoles = JSON.parse(row.role_with_without_xp || "[]"); + if (excludedRoles.some(roleId => userRoles.has(roleId))) { + return; // User does not have any of the required roles + } + } else if (row.salon_with_without_type === "with") { + const channelId = message.channel.id; + const requiredChannels = JSON.parse(row.salon_with_without_xp || "[]"); + if (!requiredChannels.includes(channelId)) { + return; // Message not in a required channel + } + } else if (row.salon_with_without_type === "without") { + const channelId = message.channel.id; + const excludedChannels = JSON.parse(row.salon_with_without_xp || "[]"); + if (excludedChannels.includes(channelId)) { + return; // Message in an excluded channel + } + } + // Logic to award XP for message goes here + const now = Date.now(); + db.get( + `SELECT xp, level, last_xp_message_timestamp FROM user_levels WHERE guild_id = ? AND user_id = ?`, + [guildId, message.author.id], + (err, userRow) => { + if (err) return; + + const lastTimestamp = userRow ? userRow.last_xp_message_timestamp || 0 : 0; + if (now - lastTimestamp < row.cooldown_xp_message_seconds * 1000) { + return; // Still in cooldown + } + + const minXp = row.gain_xp_message_lower_bound; + const maxXp = row.gain_xp_message_upper_bound; + const xpToAdd = Math.floor(Math.random() * (maxXp - minXp + 1)) + minXp; + + let newXp; + let newLevel; + + if (userRow) { + newXp = userRow.xp + xpToAdd; + newLevel = userRow.level; + } else { + newXp = xpToAdd; + newLevel = 1; + } + + // Level up logic based on xp_courbe_type and multiplier goes here + const firstLevelXp = 100; // Example base XP for first level ---------------------------------------- + const multiplier = row.multiplier_courbe_for_level; + let fonction_courbe; + + if (row.xp_courbe_type === "constante") { + fonction_courbe = (level) => firstLevelXp * multiplier; + } else if (row.xp_courbe_type === "linear") { + fonction_courbe = (level) => firstLevelXp * (level) * multiplier; + } else if (row.xp_courbe_type === "quadratic") { + fonction_courbe = (level) => firstLevelXp * (level) * (level) * multiplier; + } else if (row.xp_courbe_type === "exponential") { + fonction_courbe = (level) => firstLevelXp * Math.pow(2, (level - 1)) * multiplier; + } + + let xpForNextLevel = fonction_courbe(newLevel); + while (newXp >= xpForNextLevel && (row.level_max === 0 || newLevel < row.level_max)) { + newXp -= xpForNextLevel; + newLevel += 1; + xpForNextLevel = fonction_courbe(newLevel); + + // Announce level up if enabled and meets the criteria + if (row.level_announcements_enabled && (newLevel % row.level_annoncement_every_level === 0)) { + const channel = message.guild.channels.cache.get(row.level_announcements_channel_id); + console.log("Channel for level announcement:", channel); + if (channel) { + let announcementMsg = row.level_announcements_message; + announcementMsg = announcementMsg + .replace("{user}", message.author.username) + .replace("{mention}", `<@${message.author.id}>`) + .replace("{level}", newLevel) + .replace("{level-xp}", xpForNextLevel); + channel.send(announcementMsg); + } + } + } + + db.run( + `INSERT INTO user_levels (guild_id, user_id, xp, level, last_xp_message_timestamp) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT(guild_id, user_id) DO UPDATE SET + xp = excluded.xp, + level = excluded.level, + last_xp_message_timestamp = excluded.last_xp_message_timestamp`, + [guildId, message.author.id, newXp, newLevel, now] + ); + } + ); + } + ); +}); + client.login(process.env.BOT_TOKEN); module.exports = client; diff --git a/app/db.js b/app/db.js index f3323e4..ae3816d 100644 --- a/app/db.js +++ b/app/db.js @@ -37,6 +37,38 @@ db.exec(` exclude_channel_ids TEXT, enabled INTEGER NOT NULL ); + + CREATE TABLE IF NOT EXISTS levels_config ( + guild_id TEXT PRIMARY KEY, + enabled INTEGER NOT NULL, + level_announcements_enabled INTEGER NOT NULL, + level_announcements_channel_id TEXT, + level_announcements_message TEXT NOT NULL, + xp_courbe_type TEXT NOT NULL, + multiplier_courbe_for_level INTEGER NOT NULL, + level_annoncement_every_level INTEGER NOT NULL, + level_max INTEGER NOT NULL, + role_with_without_type TEXT NOT NULL, + role_with_without_xp TEXT NOT NULL, + salon_with_without_type TEXT NOT NULL, + salon_with_without_xp TEXT NOT NULL, + gain_xp_on_message INTEGER NOT NULL, + gain_xp_message_lower_bound INTEGER NOT NULL, + gain_xp_message_upper_bound INTEGER NOT NULL, + cooldown_xp_message_seconds INTEGER NOT NULL, + gain_xp_on_voice INTEGER NOT NULL, + gain_voice_xp_lower_bound INTEGER NOT NULL, + gain_voice_xp_upper_bound INTEGER NOT NULL + ); + + CREATE TABLE IF NOT EXISTS user_levels ( + guild_id TEXT NOT NULL, + user_id TEXT NOT NULL, + xp INTEGER NOT NULL, + level INTEGER NOT NULL, + last_xp_message_timestamp INTEGER, + PRIMARY KEY (guild_id, user_id) + ); `); module.exports = db; diff --git a/app/public/guild.html b/app/public/guild.html index 7e16abe..c1a9088 100644 --- a/app/public/guild.html +++ b/app/public/guild.html @@ -131,213 +131,145 @@
+ + +
+ + + + + + + + + Variables disponibles : +
    +
  • {user} → nom de l'utilisateur
  • +
  • {mention} → mention de l'utilisateur
  • +
  • {level} → niveau atteint
  • +
  • {level-xp} → points d'expérience du niveau
  • +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
+
- + + + + + + diff --git a/app/public/guild/autoroleNewUserForm.js b/app/public/guild/autoroleNewUserForm.js new file mode 100644 index 0000000..fd79a21 --- /dev/null +++ b/app/public/guild/autoroleNewUserForm.js @@ -0,0 +1,29 @@ +const autoroleNewUserForm = document.getElementById("autorole-newuser-form"); +const autoroleEnabled = document.getElementById("autorole-enabled"); +const autoroleRole = document.getElementById("autorole-role"); +const statusAutoroleForm = document.getElementById("status-autorole-form"); + +fetch(`/api/bot/get-autorole-newuser-config/${guildId}`) + .then(res => res.json()) + .then(cfg => { + autoroleEnabled.checked = cfg.enabled; + autoroleRole.value = cfg.roleId; + }); + +autoroleNewUserForm.addEventListener("submit", async e => { + e.preventDefault(); + + const res = await fetch("/api/bot/save-autorole-newuser-config", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + guildId, + enabled: autoroleEnabled.checked, + roleId: autoroleRole.value + }) + }); + + statusAutoroleForm.textContent = (await res.json()).success + ? "Auto-rôle sauvegardé ✅" + : "Erreur ❌"; +}); diff --git a/app/public/guild/autoroleVocalForm.js b/app/public/guild/autoroleVocalForm.js new file mode 100644 index 0000000..e4a97eb --- /dev/null +++ b/app/public/guild/autoroleVocalForm.js @@ -0,0 +1,43 @@ +const autoroleVocalForm = document.getElementById("autorole-vocal-form"); +const autoroleVocalEnabled = document.getElementById("autorole-vocal-enabled"); +const autoroleVocalRole = document.getElementById("autorole-vocal-role"); +const excludeSelect = document.getElementById("autorole-vocal-exclude-channel"); +const statusAutoroleVocalForm = document.getElementById("status-autorole-vocal-form"); + +fetch(`/api/bot/get-voice-channels/${guildId}`) + .then(res => res.json()) + .then(channels => { + channels.forEach(c => { + excludeSelect.appendChild(new Option(`#${c.name}`, c.id)); + }); + return fetch(`/api/bot/get-autorole-vocal-config/${guildId}`); + }) + .then(res => res.json()) + .then(cfg => { + autoroleVocalEnabled.checked = cfg.enabled; + autoroleVocalRole.value = cfg.roleId; + Array.from(excludeSelect.options).forEach(opt => { + opt.selected = cfg.excludeChannelIds?.includes(opt.value); + }); + }); + +autoroleVocalForm.addEventListener("submit", async e => { + e.preventDefault(); + + const exclude = Array.from(excludeSelect.selectedOptions).map(o => o.value); + + const res = await fetch("/api/bot/save-autorole-vocal-config", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + guildId, + enabled: autoroleVocalEnabled.checked, + roleId: autoroleVocalRole.value, + excludeChannelId: exclude + }) + }); + + statusAutoroleVocalForm.textContent = (await res.json()).success + ? "Auto-rôle vocal sauvegardé ✅" + : "Erreur ❌"; +}); diff --git a/app/public/guild/goodbyeForm.js b/app/public/guild/goodbyeForm.js new file mode 100644 index 0000000..98bb9ee --- /dev/null +++ b/app/public/guild/goodbyeForm.js @@ -0,0 +1,32 @@ +const goodbyeForm = document.getElementById("goodbye-form"); +const goodbyeEnabled = document.getElementById("goodbye-enabled"); +const goodbyeChannel = document.getElementById("goodbye-channel"); +const goodbyeMessage = document.getElementById("goodbye-message"); +const statusGoodbyeForm = document.getElementById("status-goodbye-form"); + +fetch(`/api/bot/get-goodbye-config/${guildId}`) + .then(res => res.json()) + .then(cfg => { + goodbyeEnabled.checked = cfg.enabled; + goodbyeChannel.value = cfg.channelId; + goodbyeMessage.value = cfg.message; + }); + +goodbyeForm.addEventListener("submit", async e => { + e.preventDefault(); + + const res = await fetch("/api/bot/save-goodbye-config", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + guildId, + goodbyeEnabled: goodbyeEnabled.checked, + channelId: goodbyeChannel.value, + goodbyeMessage: goodbyeMessage.value + }) + }); + + statusGoodbyeForm.textContent = (await res.json()).success + ? "Config au revoir sauvegardée ✅" + : "Erreur ❌"; +}); diff --git a/app/public/guild/guildBase.js b/app/public/guild/guildBase.js new file mode 100644 index 0000000..e05f79a --- /dev/null +++ b/app/public/guild/guildBase.js @@ -0,0 +1,40 @@ +window.guildId = window.location.pathname.split("/")[2]; + +// Nom du serveur +fetch("/api/guilds") + .then(res => res.json()) + .then(guilds => { + const guild = guilds.find(g => g.id === guildId); + document.getElementById("guild-name").textContent = + guild ? `Dashboard : ${guild.name}` : "Serveur introuvable"; + }); + +// Channels texte +fetch(`/api/bot/get-text-channels/${guildId}`) + .then(res => res.json()) + .then(channels => { + const welcome = document.getElementById("welcome-channel"); + const goodbye = document.getElementById("goodbye-channel"); + const levelAnnouncements = document.getElementById("level-announcements-channel"); + + channels.forEach(c => { + const opt = new Option(`#${c.name}`, c.id); + welcome?.appendChild(opt); + goodbye?.appendChild(opt.cloneNode(true)); + levelAnnouncements?.appendChild(opt.cloneNode(true)); + }); + }); + +// Rôles +fetch(`/api/bot/get-roles/${guildId}`) + .then(res => res.json()) + .then(roles => { + const newUser = document.getElementById("autorole-role"); + const vocal = document.getElementById("autorole-vocal-role"); + + roles.forEach(r => { + const opt = new Option(r.name, r.id); + newUser?.appendChild(opt); + vocal?.appendChild(opt.cloneNode(true)); + }); + }); diff --git a/app/public/guild/levelForm.js b/app/public/guild/levelForm.js new file mode 100644 index 0000000..82cfce8 --- /dev/null +++ b/app/public/guild/levelForm.js @@ -0,0 +1,119 @@ +const levelForm = document.getElementById("level-form"); +const levelEnabled = document.getElementById("level-enabled"); +const levelAnnouncementsEnabled = document.getElementById("level-announcement-enabled"); +const levelAnnouncementsChannel = document.getElementById("level-announcements-channel"); +const levelAnnouncementsMessage = document.getElementById("level-announcements-message"); +const xpCourbeType = document.getElementById("level-xp-curve-type"); +const multiplierCourbeForLevel = document.getElementById("level-xp-multiplier"); +const levelAnnouncementEveryLevel = document.getElementById("level-announcement-every"); +const levelMax = document.getElementById("level-max-level"); +const roleWithWithoutType = document.getElementById("level-role-with-or-without-xp-type"); +const roleWithWithoutXp = document.getElementById("level-role-with-or-without-xp"); +const salonWithWithoutType = document.getElementById("level-channel-with-or-without-xp-type"); +const salonWithWithoutXp = document.getElementById("level-channel-with-or-without-xp"); +const gainXpOnMessage = document.getElementById("message-xp-enabled"); +const gainXpMessageLowerBound = document.getElementById("level-xp-per-message-min"); +const gainXpMessageUpperBound = document.getElementById("level-xp-per-message-max"); +const cooldownXpMessageSeconds = document.getElementById("level-xp-cooldown"); +const gainXpOnVoice = document.getElementById("voice-xp-enabled"); +const gainVoiceXpLowerBound = document.getElementById("level-xp-per-voice-min"); +const gainVoiceXpUpperBound = document.getElementById("level-xp-per-voice-max"); +const statusLevelForm = document.getElementById("status-level-form"); + +// 1️⃣ RÔLES +fetch(`/api/bot/get-roles/${guildId}`) + .then(res => res.json()) + .then(roles => { + roles.forEach(r => { + roleWithWithoutXp?.appendChild(new Option(r.name, r.id)); + }); + + // 2️⃣ SALONS TEXTE + return fetch(`/api/bot/get-text-channels/${guildId}`); + }) + .then(res => res.json()) + .then(textSalons => { + textSalons.forEach(c => { + salonWithWithoutXp?.appendChild(new Option(`#${c.name}`, c.id)); + }); + + // 3️⃣ SALONS VOCAUX + return fetch(`/api/bot/get-voice-channels/${guildId}`); + }) + .then(res => res.json()) + .then(voiceSalons => { + voiceSalons.forEach(c => { + salonWithWithoutXp?.appendChild(new Option(`🔊 ${c.name}`, c.id)); + }); + + // 4️⃣ CONFIG (APRÈS QUE TOUT EST CHARGÉ) + return fetch(`/api/bot/get-level-config/${guildId}`); + }) + .then(res => res.json()) + .then(cfg => { + levelEnabled.checked = cfg.enabled; + levelAnnouncementsEnabled.checked = cfg.levelAnnouncementsEnabled; + levelAnnouncementsChannel.value = cfg.levelAnnouncementsChannelId; + levelAnnouncementsMessage.value = cfg.levelAnnouncementsMessage; + + xpCourbeType.value = cfg.xpCourbeType; + multiplierCourbeForLevel.value = cfg.multiplierCourbeForLevel; + levelAnnouncementEveryLevel.value = cfg.levelAnnouncementEveryLevel; + levelMax.value = cfg.levelMax; + + roleWithWithoutType.value = cfg.roleWithWithoutType; + Array.from(roleWithWithoutXp.options).forEach(opt => { + opt.selected = cfg.roleWithWithoutXp?.includes(opt.value); + }); + + salonWithWithoutType.value = cfg.salonWithWithoutType; + Array.from(salonWithWithoutXp.options).forEach(opt => { + opt.selected = cfg.salonWithWithoutXp?.includes(opt.value); + }); + + gainXpOnMessage.checked = cfg.gainXpOnMessage; + gainXpMessageLowerBound.value = cfg.gainXpMessageLowerBound; + gainXpMessageUpperBound.value = cfg.gainXpMessageUpperBound; + cooldownXpMessageSeconds.value = cfg.cooldownXpMessageSeconds; + + gainXpOnVoice.checked = cfg.gainXpOnVoice; + gainVoiceXpLowerBound.value = cfg.gainVoiceXpLowerBound; + gainVoiceXpUpperBound.value = cfg.gainVoiceXpUpperBound; + }) + .catch(console.error); + + +levelForm.addEventListener("submit", async e => { + e.preventDefault(); + + const res = await fetch("/api/bot/save-level-config", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + guildId, + levelEnabled: levelEnabled.checked, + levelAnnouncementsEnabled: levelAnnouncementsEnabled.checked, + levelAnnouncementsChannelId: levelAnnouncementsChannel.value, + levelAnnouncementsMessage: levelAnnouncementsMessage.value, + xpCourbeType: xpCourbeType.value, + multiplierCourbeForLevel: parseInt(multiplierCourbeForLevel.value, 10), + levelAnnouncementEveryLevel: parseInt(levelAnnouncementEveryLevel.value, 10), + levelMax: parseInt(levelMax.value, 10), + roleWithWithoutType: roleWithWithoutType.value, + roleWithWithoutXp: Array.from(roleWithWithoutXp.selectedOptions).map(opt => opt.value), + salonWithWithoutType: salonWithWithoutType.value, + salonWithWithoutXp: Array.from(salonWithWithoutXp.selectedOptions).map(opt => opt.value), + gainXpOnMessage: gainXpOnMessage.checked, + gainXpMessageLowerBound: parseInt(gainXpMessageLowerBound.value, 10), + gainXpMessageUpperBound: parseInt(gainXpMessageUpperBound.value, 10), + cooldownXpMessageSeconds: parseInt(cooldownXpMessageSeconds.value, 10), + gainXpOnVoice: gainXpOnVoice.checked, + gainVoiceXpLowerBound: parseInt(gainVoiceXpLowerBound.value, 10), + gainVoiceXpUpperBound: parseInt(gainVoiceXpUpperBound.value, 10) + }) + }); + + statusLevelForm.textContent = (await res.json()).success + ? "Config niveaux sauvegardée ✅" + : "Erreur ❌"; +}); \ No newline at end of file diff --git a/app/public/guild/welcomeForm.js b/app/public/guild/welcomeForm.js new file mode 100644 index 0000000..ce44c76 --- /dev/null +++ b/app/public/guild/welcomeForm.js @@ -0,0 +1,32 @@ +const welcomeForm = document.getElementById("welcome-form"); +const welcomeEnabled = document.getElementById("welcome-enabled"); +const welcomeChannel = document.getElementById("welcome-channel"); +const welcomeMessage = document.getElementById("welcome-message"); +const statusWelcomeForm = document.getElementById("status-welcome-form"); + +fetch(`/api/bot/get-welcome-config/${guildId}`) + .then(res => res.json()) + .then(cfg => { + welcomeEnabled.checked = cfg.enabled; + welcomeChannel.value = cfg.channelId; + welcomeMessage.value = cfg.message; + }); + +welcomeForm.addEventListener("submit", async e => { + e.preventDefault(); + + const res = await fetch("/api/bot/save-welcome-config", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + guildId, + welcomeEnabled: welcomeEnabled.checked, + channelId: welcomeChannel.value, + welcomeMessage: welcomeMessage.value + }) + }); + + statusWelcomeForm.textContent = (await res.json()).success + ? "Config bienvenue sauvegardée ✅" + : "Erreur ❌"; +}); diff --git a/app/routes/api.js b/app/routes/api.js index d61221d..54c1c0f 100644 --- a/app/routes/api.js +++ b/app/routes/api.js @@ -335,5 +335,215 @@ module.exports = (app, db, client) => { res.json(roles); }); + + router.get("/bot/get-level-config/:guildId", (req, res) => { + const { guildId } = req.params; + + db.get( + `SELECT + enabled, + level_announcements_enabled, + level_announcements_channel_id, + level_announcements_message, + xp_courbe_type, + multiplier_courbe_for_level, + level_annoncement_every_level, + level_max, + role_with_without_type, + role_with_without_xp, + salon_with_without_type, + salon_with_without_xp, + gain_xp_on_message, + gain_xp_message_lower_bound, + gain_xp_message_upper_bound, + cooldown_xp_message_seconds, + gain_xp_on_voice, + gain_voice_xp_lower_bound, + gain_voice_xp_upper_bound + FROM levels_config WHERE guild_id = ?`, + [guildId], + (err, row) => { + if (err || !row) { + console.error(err); + return res.json({ + enabled: false, + levelAnnouncementsEnabled: false, + levelAnnouncementsChannelId: null, + levelAnnouncementsMessage: "", + xpCourbeType: "exponential", + multiplierCourbeForLevel: 1, + levelAnnouncementEveryLevel: 1, + levelMax: 0, + roleWithWithoutType: "with", + roleWithWithoutXp: [], + salonWithWithoutType: "with", + salonWithWithoutXp: [], + gainXpOnMessage: false, + gainXpMessageLowerBound: 15, + gainXpMessageUpperBound: 25, + cooldownXpMessageSeconds: 2, + gainXpOnVoice: false, + gainVoiceXpLowerBound: 10, + gainVoiceXpUpperBound: 20 + }); + } + res.json({ + enabled: !!row.enabled, + levelAnnouncementsEnabled: !!row.level_announcements_enabled, + levelAnnouncementsChannelId: row.level_announcements_channel_id, + levelAnnouncementsMessage: row.level_announcements_message, + xpCourbeType: row.xp_courbe_type, + multiplierCourbeForLevel: row.multiplier_courbe_for_level, + levelAnnouncementEveryLevel: row.level_annoncement_every_level, + levelMax: row.level_max, + roleWithWithoutType: row.role_with_without_type, + roleWithWithoutXp: JSON.parse(row.role_with_without_xp), + salonWithWithoutType: row.salon_with_without_type, + salonWithWithoutXp: JSON.parse(row.salon_with_without_xp), + gainXpOnMessage: !!row.gain_xp_on_message, + gainXpMessageLowerBound: row.gain_xp_message_lower_bound, + gainXpMessageUpperBound: row.gain_xp_message_upper_bound, + cooldownXpMessageSeconds: row.cooldown_xp_message_seconds, + gainXpOnVoice: !!row.gain_xp_on_voice, + gainVoiceXpLowerBound: row.gain_voice_xp_lower_bound, + gainVoiceXpUpperBound: row.gain_voice_xp_upper_bound + }); + } + ); + }); + + + router.post("/bot/save-level-config", express.json(), (req, res) => { + const { + guildId, + levelEnabled, + levelAnnouncementsEnabled, + levelAnnouncementsChannelId, + levelAnnouncementsMessage, + xpCourbeType, + multiplierCourbeForLevel, + levelAnnouncementEveryLevel, + levelMax, + roleWithWithoutType, + roleWithWithoutXp, + salonWithWithoutType, + salonWithWithoutXp, + gainXpOnMessage, + gainXpMessageLowerBound, + gainXpMessageUpperBound, + cooldownXpMessageSeconds, + gainXpOnVoice, + gainVoiceXpLowerBound, + gainVoiceXpUpperBound + } = 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(401).json({ success: false }); + } + db.run( + ` + INSERT INTO levels_config ( + guild_id, + enabled, + level_announcements_enabled, + level_announcements_channel_id, + level_announcements_message, + xp_courbe_type, + multiplier_courbe_for_level, + level_annoncement_every_level, + level_max, + role_with_without_type, + role_with_without_xp, + salon_with_without_type, + salon_with_without_xp, + gain_xp_on_message, + gain_xp_message_lower_bound, + gain_xp_message_upper_bound, + cooldown_xp_message_seconds, + gain_xp_on_voice, + gain_voice_xp_lower_bound, + gain_voice_xp_upper_bound + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + ON CONFLICT(guild_id) + DO UPDATE SET + enabled = ?, + level_announcements_enabled = ?, + level_announcements_channel_id = ?, + level_announcements_message = ?, + xp_courbe_type = ?, + multiplier_courbe_for_level = ?, + level_annoncement_every_level = ?, + level_max = ?, + role_with_without_type = ?, + role_with_without_xp = ?, + salon_with_without_type = ?, + salon_with_without_xp = ?, + gain_xp_on_message = ?, + gain_xp_message_lower_bound = ?, + gain_xp_message_upper_bound = ?, + cooldown_xp_message_seconds = ?, + gain_xp_on_voice = ?, + gain_voice_xp_lower_bound = ?, + gain_voice_xp_upper_bound = ? + `, + [ + guildId, + levelEnabled ? 1 : 0, + levelAnnouncementsEnabled ? 1 : 0, + levelAnnouncementsChannelId, + levelAnnouncementsMessage, + xpCourbeType, + multiplierCourbeForLevel, + levelAnnouncementEveryLevel, + levelMax, + roleWithWithoutType, + JSON.stringify(Array.isArray(roleWithWithoutXp) ? roleWithWithoutXp : [roleWithWithoutXp]), + salonWithWithoutType, + JSON.stringify(Array.isArray(salonWithWithoutXp) ? salonWithWithoutXp : [salonWithWithoutXp]), + gainXpOnMessage ? 1 : 0, + gainXpMessageLowerBound, + gainXpMessageUpperBound, + cooldownXpMessageSeconds, + gainXpOnVoice ? 1 : 0, + gainVoiceXpLowerBound, + gainVoiceXpUpperBound, + levelEnabled ? 1 : 0, + levelAnnouncementsEnabled ? 1 : 0, + levelAnnouncementsChannelId, + levelAnnouncementsMessage, + xpCourbeType, + multiplierCourbeForLevel, + levelAnnouncementEveryLevel, + levelMax, + roleWithWithoutType, + JSON.stringify(Array.isArray(roleWithWithoutXp) ? roleWithWithoutXp : [roleWithWithoutXp]), + salonWithWithoutType, + JSON.stringify(Array.isArray(salonWithWithoutXp) ? salonWithWithoutXp : [salonWithWithoutXp]), + gainXpOnMessage ? 1 : 0, + gainXpMessageLowerBound, + gainXpMessageUpperBound, + cooldownXpMessageSeconds, + gainXpOnVoice ? 1 : 0, + gainVoiceXpLowerBound, + gainVoiceXpUpperBound + ], + err => { + if (err) { + console.error(err); + return res.status(500).json({ success: false }); + } + res.json({ success: true }); + } + ); + }); + app.use("/api", router); };