finish organization event/command & add guild command in folder commands

This commit is contained in:
Arthur Puechberty
2026-01-17 17:23:49 +01:00
parent d5f0f4c30b
commit 6c9241f349
12 changed files with 654 additions and 599 deletions
+1 -287
View File
@@ -1,298 +1,12 @@
const loadSlashCommands = require('./slash_commands.js');
const db = require("./db");
const { Client, GatewayIntentBits, ActivityType, Events } = require("discord.js");
const e = require('express');
const { Client, GatewayIntentBits, Events } = require("discord.js");
const client = new Client({ intents: Object.values(GatewayIntentBits) });
require("./loader/events.js")(client);
require("./loader/commands.js")(client);
client.once(Events.ClientReady, async () => {
console.log(`Bot connecté en tant que ${client.user.tag}`);
await loadSlashCommands(client);
client.user.setActivity("LazyBot à votre service !", { type: ActivityType.Custom });
});
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isChatInputCommand()) return;
else if (interaction.commandName === 'level') {
const guildId = interaction.guild.id;
const userId = interaction.user.id;
db.get(
`SELECT xp, level FROM user_levels WHERE guild_id = ? AND user_id = ?`,
[guildId, userId],
(err, row) => {
if (err) {
console.error("DB error fetching user level", err);
return interaction.reply("Une erreur est survenue en récupérant votre niveau.");
}
if (!row) {
return interaction.reply("Vous n'avez pas encore de niveau dans ce serveur.");
}
return interaction.reply(`Vous êtes au niveau ${row.level} avec ${row.xp} XP.`);
}
);
} else if (interaction.commandName === 'leveltop') {
const guildId = interaction.guild.id;
db.all(
`SELECT user_id, xp, level FROM user_levels WHERE guild_id = ? ORDER BY level DESC, xp DESC LIMIT 10`,
[guildId],
(err, rows) => {
if (err) {
console.error("DB error fetching level top", err);
return interaction.reply("Une erreur est survenue en récupérant le classement des niveaux.");
}
if (rows.length === 0) {
return interaction.reply("Aucun utilisateur n'a encore de niveau dans ce serveur.");
}
let replyMessage = "🏆 **Top 10 des niveaux :**\n";
rows.forEach((row, index) => {
replyMessage += `${index + 1}. <@${row.user_id}> - Niveau ${row.level} (${row.xp} XP)\n`;
});
return interaction.reply(replyMessage);
}
);
}
});
client.on(Events.GuildMemberAdd, member => {
db.get(
"SELECT enabled, channel_id, message FROM welcome_config WHERE guild_id = ?",
[member.guild.id],
(err, row) => {
if (err || !row || !row.enabled) return;
let msg = row.message;
msg = msg
.replace("{user}", member.user.username)
.replace("{mention}", `<@${member.id}>`)
.replace("{server}", member.guild.name);
const channel = member.guild.channels.cache.get(row.channel_id);
if (channel) {
channel.send(msg);
}
}
);
db.get(
"SELECT enabled, role_id FROM autorole_newuser_config WHERE guild_id = ?",
[member.guild.id],
(err, row) => {
if (err || !row || !row.enabled) return;
const role = member.guild.roles.cache.get(row.role_id);
if (role) {
member.roles.add(role);
}
}
);
});
client.on(Events.GuildMemberRemove, member => {
db.get(
"SELECT enabled, channel_id, message FROM goodbye_config WHERE guild_id = ?",
[member.guild.id],
(err, row) => {
if (err || !row || !row.enabled) return;
let msg = row.message;
msg = msg
.replace("{user}", member.user.username)
.replace("{server}", member.guild.name);
const channel = member.guild.channels.cache.get(row.channel_id);
if (channel) {
channel.send(msg);
}
}
);
});
client.on(Events.VoiceStateUpdate, (oldState, newState) => {
if (newState.member.user.bot) return;
const guildId = newState.guild.id;
db.get(
"SELECT enabled, role_id, exclude_channel_ids FROM autorole_vocal_config WHERE guild_id = ?",
[guildId],
(err, row) => {
if (err || !row || !row.enabled) return;
let excludeChannelIds = [];
try {
excludeChannelIds = row.exclude_channel_ids
? JSON.parse(row.exclude_channel_ids)
: [];
} catch (err) {
console.error("Erreur parsing exclude_channel_ids", err);
excludeChannelIds = [];
}
const role = newState.guild.roles.cache.get(row.role_id);
if (!role) return;
// User joins a voice channel and it's not excluded et a pas déjà le rôle
if (newState.channelId && !excludeChannelIds.includes(newState.channelId) && !newState.member.roles.cache.has(role.id)) {
newState.member.roles.add(role);
}
// User leaves a voice channel or joins an excluded one
else if (!newState.channelId || excludeChannelIds.includes(newState.channelId)) {
newState.member.roles.remove(role);
}
}
);
});
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 multiplier = row.multiplier_courbe_for_level;
let fonction_courbe;
if (row.xp_courbe_type === "constante") {
fonction_courbe = (level) => multiplier;
} else if (row.xp_courbe_type === "linear") {
fonction_courbe = (level) => (level) * multiplier;
} else if (row.xp_courbe_type === "quadratic") {
fonction_courbe = (level) => (level) * (level) * multiplier;
} else if (row.xp_courbe_type === "exponential") {
fonction_courbe = (level) => 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]
);
}
);
}
);
});
setInterval(() => {
// vérification des membres vocaux pour leur faire gagner de l'xp
+91
View File
@@ -0,0 +1,91 @@
const addCommand = require("../fonctions/addCommand");
const { EmbedBuilder } = require("discord.js");
const db = require("../db");
module.exports = addCommand({
name: "level",
description: "Affiche votre niveau et XP sur ce serveur.",
aliases: ["lvl", "xp", "niveau"],
permissions: [],
botOwnerOnly: false,
dm: false,
scope: "guild",
guildCondition: async (guildId) => {
return new Promise((resolve) => {
db.get(
"SELECT enabled FROM levels_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);
}
);
});
},
executePrefix: async (client, message, args) => {
const guildId = message.guild.id;
const userId = message.author.id;
db.get(
`SELECT xp, level FROM user_levels WHERE guild_id = ? AND user_id = ?`,
[guildId, userId],
async (err, row) => {
if (err) {
console.error("DB error fetching user level", err);
return message.reply("Une erreur est survenue en récupérant votre niveau.");
}
if (!row) {
return message.reply("Vous n'avez pas encore de niveau dans ce serveur.");
}
const embed = new EmbedBuilder()
.setTitle(`${message.author.username} — Niveau`)
.setDescription(`Vous êtes au **niveau ${row.level}** avec **${row.xp} XP**.`)
.setColor("#00FF00")
.setTimestamp()
.setFooter({ text: `ID: ${userId}` });
await message.reply({ embeds: [embed] });
}
);
},
executeSlash: async (client, interaction) => {
const guildId = interaction.guild.id;
const userId = interaction.user.id;
db.get(
`SELECT xp, level FROM user_levels WHERE guild_id = ? AND user_id = ?`,
[guildId, userId],
async (err, row) => {
if (err) {
console.error("DB error fetching user level", err);
return interaction.reply({ content: "Une erreur est survenue en récupérant votre niveau.", ephemeral: true });
}
if (!row) {
return interaction.reply({ content: "Vous n'avez pas encore de niveau dans ce serveur.", ephemeral: true });
}
const embed = new EmbedBuilder()
.setTitle(`${interaction.user.username} — Niveau`)
.setDescription(`Vous êtes au **niveau ${row.level}** avec **${row.xp} XP**.`)
.setColor("#00FF00")
.setTimestamp()
.setFooter({ text: `ID: ${userId}` });
await interaction.reply({ embeds: [embed] });
}
);
},
slashData: new (require("discord.js").SlashCommandBuilder)()
.setName("level")
.setDescription("Affiche votre niveau et XP sur ce serveur."),
});
+101
View File
@@ -0,0 +1,101 @@
const addCommand = require("../fonctions/addCommand");
const { EmbedBuilder, SlashCommandBuilder } = require("discord.js");
const db = require("../db");
module.exports = addCommand({
name: "leveltop",
description: "Affiche le top 10 des niveaux du serveur.",
aliases: ["toplevel", "topxp", "leaderboard"],
permissions: [],
botOwnerOnly: false,
dm: false,
scope: "guild",
guildCondition: async (guildId) => {
return new Promise((resolve) => {
db.get(
"SELECT enabled FROM levels_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);
}
);
});
},
executePrefix: async (client, message, args) => {
const guildId = message.guild.id;
db.all(
`SELECT user_id, xp, level FROM user_levels WHERE guild_id = ? ORDER BY level DESC, xp DESC LIMIT 10`,
[guildId],
async (err, rows) => {
if (err) {
console.error("DB error fetching level top", err);
return message.reply("Une erreur est survenue en récupérant le classement des niveaux.");
}
if (rows.length === 0) {
return message.reply("Aucun utilisateur n'a encore de niveau dans ce serveur.");
}
const embed = new EmbedBuilder()
.setTitle("🏆 Top 10 des niveaux")
.setColor("#FFD700")
.setTimestamp()
.setDescription(
rows
.map(
(row, index) =>
`**${index + 1}.** <@${row.user_id}> - Niveau **${row.level}** (${row.xp} XP)`
)
.join("\n")
);
await message.reply({ embeds: [embed] });
}
);
},
executeSlash: async (client, interaction) => {
const guildId = interaction.guild.id;
db.all(
`SELECT user_id, xp, level FROM user_levels WHERE guild_id = ? ORDER BY level DESC, xp DESC LIMIT 10`,
[guildId],
async (err, rows) => {
if (err) {
console.error("DB error fetching level top", err);
return interaction.reply({ content: "Une erreur est survenue en récupérant le classement des niveaux.", ephemeral: true });
}
if (rows.length === 0) {
return interaction.reply({ content: "Aucun utilisateur n'a encore de niveau dans ce serveur.", ephemeral: true });
}
const embed = new EmbedBuilder()
.setTitle("🏆 Top 10 des niveaux")
.setColor("#FFD700")
.setTimestamp()
.setDescription(
rows
.map(
(row, index) =>
`**${index + 1}.** <@${row.user_id}> - Niveau **${row.level}** (${row.xp} XP)`
)
.join("\n")
);
await interaction.reply({ embeds: [embed] });
}
);
},
slashData: new SlashCommandBuilder()
.setName("leveltop")
.setDescription("Affiche le top 10 des niveaux du serveur."),
});
+53 -113
View File
@@ -7,14 +7,26 @@ const {
EmbedBuilder,
} = require("discord.js");
module.exports = addCommand(
(this.name = "ping"),
(this.description = "Cette commande permet de vérifier la latence du bot."),
(this.aliases = ["latency", "lag", "responseTime"]),
(this.permissions = []),
(this.botOwnerOnly = false),
(this.dm = true),
(this.executePrefix = async (client, message, args) => {
module.exports = addCommand({
name: "ping",
description: "Cette commande permet de vérifier la latence du bot.",
aliases: ["latency", "lag", "responseTime"],
permissions: [],
botOwnerOnly: false,
dm: true,
scope: "global",
slashOptions: [
{
type: "BOOLEAN",
name: "actualiser",
description:
"Actualiser automatiquement la latence du bot toutes les 10 secondes pendant 2 minutes.",
required: false,
},
],
executePrefix: async (client, message, args) => {
const pingBtn = new ButtonBuilder()
.setCustomId("pingBtn")
.setLabel("🔄")
@@ -22,61 +34,35 @@ module.exports = addCommand(
const row = new ActionRowBuilder().addComponents(pingBtn);
const embed = new EmbedBuilder()
const embed = (user) =>
new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${message.author.tag}`,
iconURL: message.author.displayAvatarURL(),
});
.setFooter({ text: `Demandé par ${user.tag}`, iconURL: user.displayAvatarURL() });
const sendMessage = await message.reply({
embeds: [embed],
components: [row],
});
const sendMessage = await message.reply({ embeds: [embed(message.author)], components: [row] });
const filter = (i) => i.customId === "pingBtn";
const collector = sendMessage.createMessageComponentCollector({
filter,
time: 120000,
});
const collector = sendMessage.createMessageComponentCollector({ filter, time: 120_000 });
collector.on("collect", async (i) => {
if (i.user.id !== message.author.id)
return i.reply({
content: "Vous n'êtes pas l'auteur du message.",
ephemeral: true,
});
const embed = new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${message.author.tag}`,
iconURL: message.author.displayAvatarURL(),
});
return i.reply({ content: "Vous n'êtes pas l'auteur du message.", ephemeral: true });
sendMessage.edit({ embeds: [embed], components: [row] });
i.reply({ content: "La latence a été rafraichie.", ephemeral: true });
await sendMessage.edit({ embeds: [embed(i.user)], components: [row] });
await i.reply({ content: "La latence a été rafraichie.", ephemeral: true });
});
collector.on("end", async () => {
const embed = new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${message.author.tag}`,
iconURL: message.author.displayAvatarURL(),
await sendMessage.edit({ embeds: [embed(message.author)], components: [] });
});
},
executeSlash: async (client, interaction) => {
const actualiser = interaction.options.getBoolean("actualiser") ?? false;
sendMessage.edit({ embeds: [embed], components: [] });
});
}),
(this.executeSlash = async (client, interaction) => {
const pingBtn = new ButtonBuilder()
.setCustomId("pingBtn")
.setLabel("🔄")
@@ -84,87 +70,41 @@ module.exports = addCommand(
const row = new ActionRowBuilder().addComponents(pingBtn);
const embed = new EmbedBuilder()
const embed = (user) =>
new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${interaction.user.tag}`,
iconURL: interaction.user.displayAvatarURL(),
});
.setFooter({ text: `Demandé par ${user.tag}`, iconURL: user.displayAvatarURL() });
if (interaction.options.getBoolean("actualiser") === false) {
const sendMessage = await interaction.reply({
embeds: [embed],
components: [row],
embeds: [embed(interaction.user)],
components: actualiser ? [] : [row],
fetchReply: true,
});
if (!actualiser) {
const filter = (i) => i.customId === "pingBtn";
const collector = sendMessage.createMessageComponentCollector({
filter,
time: 120000,
});
const collector = sendMessage.createMessageComponentCollector({ filter, time: 120_000 });
collector.on("collect", async (i) => {
if (i.user.id !== interaction.user.id)
return i.reply({
content: "Vous n'êtes pas l'auteur du message.",
ephemeral: true,
});
const embed = new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${interaction.user.tag}`,
iconURL: interaction.user.displayAvatarURL(),
});
return i.reply({ content: "Vous n'êtes pas l'auteur du message.", ephemeral: true });
sendMessage.edit({ embeds: [embed], components: [row] });
i.reply({ content: "La latence a été rafraichie.", ephemeral: true });
await sendMessage.edit({ embeds: [embed(i.user)], components: [row] });
await i.reply({ content: "La latence a été rafraichie.", ephemeral: true });
});
collector.on("end", async () => {
const embed = new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${interaction.user.tag}`,
iconURL: interaction.user.displayAvatarURL(),
});
sendMessage.edit({ embeds: [embed], components: [] });
await sendMessage.edit({ embeds: [embed(interaction.user)], components: [] });
});
} else {
const sendMessage = await interaction.reply({ embeds: [embed] });
const interval = setInterval(async () => {
await sendMessage.edit({ embeds: [embed(interaction.user)] });
}, 10_000);
const interval = setInterval(() => {
const embed = new EmbedBuilder()
.setTitle("Pong !")
.setDescription(`La latence du bot est de \`${client.ws.ping}\`ms.`)
.setColor("#0099FF")
.setTimestamp()
.setFooter({
text: `Demandé par ${interaction.user.tag}`,
iconURL: interaction.user.displayAvatarURL(),
});
sendMessage.edit({ embeds: [embed] });
}, 10000);
setTimeout(() => {
clearInterval(interval);
}, 120000);
setTimeout(() => clearInterval(interval), 120_000);
}
}),
(this.slashOptions = new SlashCommandBuilder().addBooleanOption((option) =>
option
.setName("actualiser")
.setDescription(
"Actualiser automatiquement la latence du bot toute les 10 secondes pandant 2 minutes.",
)
.setRequired(false),
)),
);
},
});
+18
View File
@@ -9,6 +9,24 @@ const db = new sqlite3.Database(
}
);
db.getAsync = (sql, params = []) => {
return new Promise((resolve, reject) => {
db.get(sql, params, (err, row) => {
if (err) reject(err);
else resolve(row);
});
});
};
db.allAsync = (sql, params = []) => {
return new Promise((resolve, reject) => {
db.all(sql, params, (err, rows) => {
if (err) reject(err);
else resolve(rows);
});
});
};
// Création de la table si elle n'existe pas
db.exec(`
CREATE TABLE IF NOT EXISTS welcome_config (
+11
View File
@@ -0,0 +1,11 @@
const { Events, ActivityType } = require("discord.js");
const loadSlashCommands = require('../slash_commands.js');
module.exports = {
name: Events.ClientReady,
async execute(client) {
console.log(`[READY] ${client.user.tag} est prêt | ${client.guilds.cache.size} serveurs | ${client.guilds.cache.reduce((acc, guild) => acc + guild.memberCount, 0)} utilisateurs`);
await loadSlashCommands(client);
client.user.setActivity("LazyBot à votre service !", { type: ActivityType.Custom });
}
};
+39
View File
@@ -0,0 +1,39 @@
const { Events } = require("discord.js");
const db = require("../db");
module.exports = {
name: Events.GuildMemberAdd,
async execute(client, member) {
db.get(
"SELECT enabled, channel_id, message FROM welcome_config WHERE guild_id = ?",
[member.guild.id],
(err, row) => {
if (err || !row || !row.enabled) return;
let msg = row.message;
msg = msg
.replace("{user}", member.user.username)
.replace("{mention}", `<@${member.id}>`)
.replace("{server}", member.guild.name);
const channel = member.guild.channels.cache.get(row.channel_id);
if (channel) {
channel.send(msg);
}
}
);
db.get(
"SELECT enabled, role_id FROM autorole_newuser_config WHERE guild_id = ?",
[member.guild.id],
(err, row) => {
if (err || !row || !row.enabled) return;
const role = member.guild.roles.cache.get(row.role_id);
if (role) {
member.roles.add(role);
}
}
);
},
};
+26
View File
@@ -0,0 +1,26 @@
const { Events } = require("discord.js");
const db = require("../db");
module.exports = {
name: Events.GuildMemberRemove,
async execute(client, member) {
db.get(
"SELECT enabled, channel_id, message FROM goodbye_config WHERE guild_id = ?",
[member.guild.id],
(err, row) => {
if (err || !row || !row.enabled) return;
let msg = row.message;
msg = msg
.replace("{user}", member.user.username)
.replace("{server}", member.guild.name);
const channel = member.guild.channels.cache.get(row.channel_id);
if (channel) {
channel.send(msg);
}
}
);
},
};
+136
View File
@@ -0,0 +1,136 @@
const { Events } = require("discord.js");
const db = require("../db");
module.exports = {
name: Events.MessageCreate,
async execute(client, 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 multiplier = row.multiplier_courbe_for_level;
let fonction_courbe;
if (row.xp_courbe_type === "constante") {
fonction_courbe = (level) => multiplier;
} else if (row.xp_courbe_type === "linear") {
fonction_courbe = (level) => (level) * multiplier;
} else if (row.xp_courbe_type === "quadratic") {
fonction_courbe = (level) => (level) * (level) * multiplier;
} else if (row.xp_courbe_type === "exponential") {
fonction_courbe = (level) => 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]
);
}
);
}
);
},
};
+41
View File
@@ -0,0 +1,41 @@
const { Events } = require("discord.js");
const db = require("../db");
module.exports = {
name: Events.VoiceStateUpdate,
async execute(client, oldState, newState) {
if (newState.member.user.bot) return;
const guildId = newState.guild.id;
db.get(
"SELECT enabled, role_id, exclude_channel_ids FROM autorole_vocal_config WHERE guild_id = ?",
[guildId],
(err, row) => {
if (err || !row || !row.enabled) return;
let excludeChannelIds = [];
try {
excludeChannelIds = row.exclude_channel_ids
? JSON.parse(row.exclude_channel_ids)
: [];
} catch (err) {
console.error("Erreur parsing exclude_channel_ids", err);
excludeChannelIds = [];
}
const role = newState.guild.roles.cache.get(row.role_id);
if (!role) return;
// User joins a voice channel and it's not excluded et a pas déjà le rôle
if (newState.channelId && !excludeChannelIds.includes(newState.channelId) && !newState.member.roles.cache.has(role.id)) {
newState.member.roles.add(role);
}
// User leaves a voice channel or joins an excluded one
else if (!newState.channelId || excludeChannelIds.includes(newState.channelId)) {
newState.member.roles.remove(role);
}
}
);
},
};
+88 -150
View File
@@ -3,170 +3,108 @@ const { SlashCommandBuilder, PermissionsBitField } = require("discord.js");
/**
* Ajoute une nouvelle commande au bot.
*
* @param {string} name - Le nom de la commande.
* @param {string} description - La description de la commande.
* @param {Array<string>} aliases - Les alias de la commande.
* @param {Array<PermissionsBitField>} permissions - Les permissions nécessaires pour exécuter la commande.
* @param {boolean} botOwnerOnly - Si la commande est réservée au propriétaire du bot.
* @param {boolean} dm - Si la commande peut être exécutée en message privé.
* @param {Function} executePrefix - La fonction à exécuter avec un préfixe.
* @param {Function} executeSlash - La fonction à exécuter comme commande slash.
* @param {Array<Object>} slashOptions - Les options pour la commande slash.
* @param {Object} options
* @param {string} options.name - Nom de la commande
* @param {string} options.description - Description de la commande
* @param {Array<string>} [options.aliases=[]] - Alias
* @param {Array<number>} [options.permissions=[]] - Permissions requises
* @param {boolean} [options.botOwnerOnly=false] - Réservé au propriétaire du bot
* @param {boolean} [options.dm=false] - Disponible en DM
* @param {"global"|"guild"} [options.scope="global"] - Portée de la commande
* @param {(guildId: string) => Promise<boolean> | boolean} [options.guildCondition=null] - Condition pour les commandes de guild
* @param {Function} options.executePrefix - Fonction à exécuter en préfixe
* @param {Function} options.executeSlash - Fonction à exécuter en slash
* @param {Array<Object>} [options.slashOptions=[]] - Options pour la commande slash
*
* @returns {void} Ne retourne rien.
* @returns {Object} Commande prête à être ajoutée au bot
*/
function addCommand(
function addCommand({
name,
description,
aliases,
permissions,
botOwnerOnly,
dm,
aliases = [],
permissions = [],
botOwnerOnly = false,
dm = false,
scope = "global",
guildCondition = null,
executePrefix,
executeSlash,
slashOptions,
) {
if (!name) return console.error("Le nom de la commande est requis.");
name = name.toString();
name = name.toLowerCase();
name = name.replace(/ /g, "_");
if (!description)
return console.error("La description de la commande est requise.");
description = description.toString();
if (!aliases) aliases = [];
if (!Array.isArray(aliases)) aliases = [aliases];
aliases = aliases.map((alias) => alias.toString());
if (!permissions) permissions = [];
if (!Array.isArray(permissions)) permissions = [permissions];
if (!botOwnerOnly) botOwnerOnly = false;
botOwnerOnly = Boolean(botOwnerOnly);
if (!dm) dm = false;
dm = Boolean(dm);
if (!executePrefix)
return console.error("La fonction executePrefix est requise.");
if (!executeSlash)
return console.error("La fonction executeSlash est requise.");
if (
typeof executePrefix !== "function" ||
executePrefix.constructor.name !== "AsyncFunction"
) {
return console.error(
"La fonction executePrefix doit être une fonction asynchrone.",
slashOptions = []
}) {
if (!name || !description) throw new Error("name et description requis");
if (scope === "guild" && typeof guildCondition !== "function") {
throw new Error("guildCondition requise pour scope=guild");
}
if (typeof executePrefix !== "function") throw new Error("executePrefix requis et doit être une fonction");
if (typeof executeSlash !== "function") throw new Error("executeSlash requis et doit être une fonction");
// Permissions
let defaultMemberPermissions = null;
if (permissions.length > 0) {
defaultMemberPermissions = new PermissionsBitField();
permissions.forEach(p => defaultMemberPermissions.add(p));
}
// Création du SlashCommandBuilder
const slashData = new SlashCommandBuilder()
.setName(name.toLowerCase())
.setDescription(description)
.setDMPermission(dm)
.setDefaultMemberPermissions(defaultMemberPermissions);
// Ajouter les options
slashOptions.forEach(opt => {
switch (opt.type) {
case "STRING":
slashData.addStringOption(o =>
o.setName(opt.name).setDescription(opt.description || "No description").setRequired(!!opt.required)
);
}
if (
typeof executeSlash !== "function" ||
executeSlash.constructor.name !== "AsyncFunction"
) {
return console.error(
"La fonction executeSlash doit être une fonction asynchrone.",
break;
case "INTEGER":
slashData.addIntegerOption(o =>
o.setName(opt.name).setDescription(opt.description || "No description").setRequired(!!opt.required)
);
}
const executePrefixParams = executePrefix
.toString()
.match(/\(([^)]+)\)/)[1]
.split(",")
.map((param) => param.trim());
if (
executePrefixParams.length !== 3 ||
executePrefixParams[0] !== "client" ||
executePrefixParams[1] !== "message" ||
executePrefixParams[2] !== "args"
) {
return console.error(
'La fonction executePrefix doit avoir les paramètres "client", "message" et "args".',
break;
case "BOOLEAN":
slashData.addBooleanOption(o =>
o.setName(opt.name).setDescription(opt.description || "No description").setRequired(!!opt.required)
);
}
const executeSlashParams = executeSlash
.toString()
.match(/\(([^)]+)\)/)[1]
.split(",")
.map((param) => param.trim());
if (
executeSlashParams.length !== 2 ||
executeSlashParams[0] !== "client" ||
executeSlashParams[1] !== "interaction"
) {
return console.error(
'La fonction executeSlash doit avoir les paramètres "client" et "interaction".',
break;
case "USER":
slashData.addUserOption(o =>
o.setName(opt.name).setDescription(opt.description || "No description").setRequired(!!opt.required)
);
}
const command = {
name,
description,
aliases,
permissions,
botOwnerOnly,
dm,
executePrefix,
executeSlash,
};
let default_member_permissions;
if (command.permissions.length === 0) {
default_member_permissions = null;
} else {
default_member_permissions = new PermissionsBitField();
command.permissions.forEach(
(permission) => (default_member_permissions += BigInt(permission)),
break;
case "CHANNEL":
slashData.addChannelOption(o =>
o.setName(opt.name).setDescription(opt.description || "No description").setRequired(!!opt.required)
);
break;
case "ROLE":
slashData.addRoleOption(o =>
o.setName(opt.name).setDescription(opt.description || "No description").setRequired(!!opt.required)
);
break;
default:
console.warn(`Option type inconnu pour ${opt.name}: ${opt.type}`);
}
command.data = new SlashCommandBuilder()
.setName(command.name)
.setDescription(command.description)
.setDMPermission(command.dm)
.setDefaultMemberPermissions(default_member_permissions);
for (const key in command.data) {
if (command.data.hasOwnProperty(key)) {
const value = command.data[key];
if (value !== undefined && key !== "options") {
slashOptions[key] = value;
}
}
}
command.data = slashOptions;
let utilisation = "";
command.data.options.forEach((option) => {
let optionUsage = "";
if (option.choices) {
optionUsage = option.required
? `<${option.choices.map((choice) => choice.name).join("|")}>`
: `[${option.choices.map((choice) => choice.name).join("|")}]`;
} else {
if (option.type === 3) {
optionUsage = option.required ? `<${option.name}>` : `[${option.name}]`;
} else if (option.type === 4) {
optionUsage = option.required ? `<${option.name}>` : `[${option.name}]`;
} else if (option.type === 5) {
optionUsage = option.required ? `<True|False>` : `[True|False]`;
} else if (option.type === 6) {
optionUsage = option.required ? `<@member>` : `[@member]`;
} else if (option.type === 7) {
optionUsage = option.required ? `<#channel>` : `[#channel]`;
} else if (option.type === 8) {
optionUsage = option.required ? `<@role>` : `[@role]`;
} else if (option.type === 9) {
optionUsage = option.required ? `<@mention>` : `[@mention]`;
} else if (option.type === 10) {
optionUsage = option.required ? `<${option.name}>` : `[${option.name}]`;
} else if (option.type === 11) {
optionUsage = option.required ? `<${option.name}>` : `[${option.name}]`;
}
}
utilisation += ` ${optionUsage}`;
});
utilisation = utilisation.trim();
command.utilisation = utilisation;
return command;
return {
name,
description,
aliases,
permissions,
botOwnerOnly,
dm,
scope,
guildCondition,
executePrefix,
executeSlash,
data: slashData
};
}
module.exports = addCommand;
+33 -33
View File
@@ -1,11 +1,10 @@
const { REST, Routes } = require('discord.js');
const db = require('./db');
const { REST, Routes } = require("discord.js");
module.exports = async (client, guildId = null) => {
module.exports = async function loadSlashCommands(client, guildId = null) {
const TOKEN = process.env.BOT_TOKEN;
const CLIENT_ID = process.env.CLIENT_ID;
const rest = new REST({ version: '10' }).setToken(TOKEN);
const rest = new REST({ version: "10" }).setToken(TOKEN);
/* =========================
1️⃣ COMMANDES GLOBALES
@@ -14,19 +13,21 @@ module.exports = async (client, guildId = null) => {
if (!guildId) {
const globalCommands = [];
client.commands.forEach((command) => {
for (const command of client.commands.values()) {
if (command.scope === "guild") continue;
globalCommands.push(command.data.toJSON());
});
}
try {
console.log('Refreshing GLOBAL slash commands...');
console.log("Refreshing GLOBAL slash commands...");
await rest.put(
Routes.applicationCommands(client.user.id),
Routes.applicationCommands(CLIENT_ID),
{ body: globalCommands }
);
console.log('Global slash commands loaded');
console.log("Global slash commands loaded");
} catch (err) {
console.error('Global commands error', err);
console.error("Global commands error", err);
}
}
@@ -36,38 +37,39 @@ module.exports = async (client, guildId = null) => {
const guildsToProcess = guildId
? [client.guilds.cache.get(guildId)]
: client.guilds.cache.values();
: [...client.guilds.cache.values()];
for (const guild of guildsToProcess) {
if (!guild) continue;
db.get(
"SELECT enabled FROM levels_config WHERE guild_id = ?",
[guild.id],
async (err, row) => {
if (err) {
console.error(`DB error ${guild.name}`, err);
return;
}
const guildCommands = [];
if (row?.enabled) {
guildCommands.push(
{
name: 'level',
description: 'Check your level and XP',
},
{
name: 'leveltop',
description: 'Show the top levels',
}
for (const command of client.commands.values()) {
if (command.scope === "global") continue;
if (command.guildCondition) {
let conditionMet = false;
try {
conditionMet = await command.guildCondition(guild.id);
} catch (err) {
console.error(
`Guild condition error for command ${command.name} in guild ${guild.name}`,
err
);
}
if (!conditionMet) continue;
}
guildCommands.push(command.data.toJSON());
}
console.log(
`Refreshing GUILD slash commands for ${guild.name} (${guildCommands.length})`
);
try {
await rest.put(
Routes.applicationGuildCommands(client.user.id, guild.id),
Routes.applicationGuildCommands(CLIENT_ID, guild.id),
{ body: guildCommands }
);
console.log(`Guild commands updated for ${guild.name}`);
@@ -75,6 +77,4 @@ module.exports = async (client, guildId = null) => {
console.error(`Guild commands error ${guild.name}`, err);
}
}
);
}
};