mirror of
https://github.com/arthur-pbty/LazyBot.git
synced 2026-06-03 15:07:29 +02:00
add vocal stats system
This commit is contained in:
+271
@@ -140,6 +140,277 @@ setInterval(() => {
|
||||
}, 60 * 1000); // Toutes les minutes
|
||||
|
||||
|
||||
// ===== STATS CHANNELS UPDATE =====
|
||||
// Met à jour les noms des salons de statistiques toutes les 5 minutes
|
||||
async function updateStatsChannels() {
|
||||
try {
|
||||
const statsChannels = await db.allAsync(`SELECT * FROM stats_channels`);
|
||||
|
||||
for (const config of statsChannels) {
|
||||
const guild = client.guilds.cache.get(config.guild_id);
|
||||
if (!guild) continue;
|
||||
|
||||
const channel = guild.channels.cache.get(config.channel_id);
|
||||
if (!channel) continue;
|
||||
|
||||
let statValue;
|
||||
|
||||
switch (config.stat_type) {
|
||||
case "members":
|
||||
// Total des membres
|
||||
statValue = guild.memberCount;
|
||||
break;
|
||||
|
||||
case "humans":
|
||||
// Membres sans les bots
|
||||
await guild.members.fetch();
|
||||
statValue = guild.members.cache.filter(m => !m.user.bot).size;
|
||||
break;
|
||||
|
||||
case "bots":
|
||||
// Nombre de bots
|
||||
await guild.members.fetch();
|
||||
statValue = guild.members.cache.filter(m => m.user.bot).size;
|
||||
break;
|
||||
|
||||
case "online":
|
||||
// Membres en ligne (online, idle, dnd)
|
||||
await guild.members.fetch({ withPresences: true });
|
||||
statValue = guild.members.cache.filter(m =>
|
||||
m.presence && ["online", "idle", "dnd"].includes(m.presence.status)
|
||||
).size;
|
||||
break;
|
||||
|
||||
case "voice":
|
||||
// Membres en vocal
|
||||
statValue = guild.members.cache.filter(m => m.voice.channelId).size;
|
||||
break;
|
||||
|
||||
case "roles":
|
||||
// Nombre de rôles
|
||||
statValue = guild.roles.cache.size;
|
||||
break;
|
||||
|
||||
case "channels":
|
||||
// Nombre de salons
|
||||
statValue = guild.channels.cache.size;
|
||||
break;
|
||||
|
||||
case "boosts":
|
||||
// Nombre de boosts
|
||||
statValue = guild.premiumSubscriptionCount || 0;
|
||||
break;
|
||||
|
||||
case "boost_level":
|
||||
// Niveau de boost
|
||||
statValue = guild.premiumTier;
|
||||
break;
|
||||
|
||||
case "role_members":
|
||||
// Membres ayant un rôle spécifique
|
||||
if (config.role_id) {
|
||||
await guild.members.fetch();
|
||||
const role = guild.roles.cache.get(config.role_id);
|
||||
statValue = role ? role.members.size : 0;
|
||||
} else {
|
||||
statValue = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
statValue = "?";
|
||||
}
|
||||
|
||||
// Construire le nouveau nom
|
||||
const newName = config.format.replace("{stat}", statValue);
|
||||
|
||||
// Ne mettre à jour que si le nom a changé
|
||||
if (channel.name !== newName) {
|
||||
try {
|
||||
await channel.setName(newName);
|
||||
} catch (err) {
|
||||
console.error(`Erreur lors de la mise à jour du salon ${config.channel_id}:`, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Erreur updateStatsChannels:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Met à jour uniquement les stats d'un type spécifique pour un serveur
|
||||
async function updateGuildStats(guildId, statTypes) {
|
||||
try {
|
||||
const statsChannels = await db.allAsync(
|
||||
`SELECT * FROM stats_channels WHERE guild_id = ? AND stat_type IN (${statTypes.map(() => '?').join(',')})`,
|
||||
[guildId, ...statTypes]
|
||||
);
|
||||
|
||||
const guild = client.guilds.cache.get(guildId);
|
||||
if (!guild) return;
|
||||
|
||||
for (const config of statsChannels) {
|
||||
const channel = guild.channels.cache.get(config.channel_id);
|
||||
if (!channel) continue;
|
||||
|
||||
let statValue;
|
||||
|
||||
switch (config.stat_type) {
|
||||
case "members":
|
||||
statValue = guild.memberCount;
|
||||
break;
|
||||
case "humans":
|
||||
statValue = guild.members.cache.filter(m => !m.user.bot).size;
|
||||
break;
|
||||
case "bots":
|
||||
statValue = guild.members.cache.filter(m => m.user.bot).size;
|
||||
break;
|
||||
case "online":
|
||||
statValue = guild.members.cache.filter(m =>
|
||||
m.presence && ["online", "idle", "dnd"].includes(m.presence.status)
|
||||
).size;
|
||||
break;
|
||||
case "voice":
|
||||
statValue = guild.members.cache.filter(m => m.voice.channelId).size;
|
||||
break;
|
||||
case "roles":
|
||||
statValue = guild.roles.cache.size;
|
||||
break;
|
||||
case "channels":
|
||||
statValue = guild.channels.cache.size;
|
||||
break;
|
||||
case "boosts":
|
||||
statValue = guild.premiumSubscriptionCount || 0;
|
||||
break;
|
||||
case "boost_level":
|
||||
statValue = guild.premiumTier;
|
||||
break;
|
||||
case "role_members":
|
||||
if (config.role_id) {
|
||||
const role = guild.roles.cache.get(config.role_id);
|
||||
statValue = role ? role.members.size : 0;
|
||||
} else {
|
||||
statValue = 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
statValue = "?";
|
||||
}
|
||||
|
||||
const newName = config.format.replace("{stat}", statValue);
|
||||
|
||||
if (channel.name !== newName) {
|
||||
try {
|
||||
await channel.setName(newName);
|
||||
} catch (err) {
|
||||
console.error(`Erreur mise à jour salon ${config.channel_id}:`, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Erreur updateGuildStats:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Debounce pour éviter le rate limiting (Discord limite le renommage de salon à 2 fois par 10 minutes)
|
||||
const statsDebounceTimers = new Map();
|
||||
function debounceStatsUpdate(guildId, statTypes, delay = 10000) {
|
||||
const key = `${guildId}-${statTypes.sort().join(",")}`;
|
||||
|
||||
if (statsDebounceTimers.has(key)) {
|
||||
clearTimeout(statsDebounceTimers.get(key));
|
||||
}
|
||||
|
||||
statsDebounceTimers.set(key, setTimeout(() => {
|
||||
updateGuildStats(guildId, statTypes);
|
||||
statsDebounceTimers.delete(key);
|
||||
}, delay));
|
||||
}
|
||||
|
||||
// ===== ÉVÉNEMENTS POUR LES STATS =====
|
||||
|
||||
// Membre rejoint/quitte -> members, humans, bots
|
||||
client.on("guildMemberAdd", (member) => {
|
||||
const types = ["members", "humans"];
|
||||
if (member.user.bot) types.push("bots");
|
||||
debounceStatsUpdate(member.guild.id, types);
|
||||
});
|
||||
|
||||
client.on("guildMemberRemove", (member) => {
|
||||
const types = ["members", "humans"];
|
||||
if (member.user.bot) types.push("bots");
|
||||
debounceStatsUpdate(member.guild.id, types);
|
||||
});
|
||||
|
||||
// Changement de présence -> online
|
||||
client.on("presenceUpdate", (oldPresence, newPresence) => {
|
||||
if (!newPresence || !newPresence.guild) return;
|
||||
const wasOnline = oldPresence && ["online", "idle", "dnd"].includes(oldPresence.status);
|
||||
const isOnline = ["online", "idle", "dnd"].includes(newPresence.status);
|
||||
if (wasOnline !== isOnline) {
|
||||
debounceStatsUpdate(newPresence.guild.id, ["online"]);
|
||||
}
|
||||
});
|
||||
|
||||
// Changement vocal -> voice (géré dans voiceStateUpdate.js mais on ajoute ici pour les stats)
|
||||
client.on("voiceStateUpdate", (oldState, newState) => {
|
||||
const guildId = newState.guild?.id || oldState.guild?.id;
|
||||
if (!guildId) return;
|
||||
// Si rejoint ou quitte un vocal
|
||||
if (oldState.channelId !== newState.channelId) {
|
||||
debounceStatsUpdate(guildId, ["voice"]);
|
||||
}
|
||||
});
|
||||
|
||||
// Rôle créé/supprimé -> roles
|
||||
client.on("roleCreate", (role) => {
|
||||
debounceStatsUpdate(role.guild.id, ["roles"]);
|
||||
});
|
||||
|
||||
client.on("roleDelete", (role) => {
|
||||
debounceStatsUpdate(role.guild.id, ["roles"]);
|
||||
});
|
||||
|
||||
// Salon créé/supprimé -> channels
|
||||
client.on("channelCreate", (channel) => {
|
||||
if (channel.guild) debounceStatsUpdate(channel.guild.id, ["channels"]);
|
||||
});
|
||||
|
||||
client.on("channelDelete", (channel) => {
|
||||
if (channel.guild) debounceStatsUpdate(channel.guild.id, ["channels"]);
|
||||
});
|
||||
|
||||
// Mise à jour du serveur -> boosts, boost_level
|
||||
client.on("guildUpdate", (oldGuild, newGuild) => {
|
||||
const types = [];
|
||||
if (oldGuild.premiumSubscriptionCount !== newGuild.premiumSubscriptionCount) {
|
||||
types.push("boosts");
|
||||
}
|
||||
if (oldGuild.premiumTier !== newGuild.premiumTier) {
|
||||
types.push("boost_level");
|
||||
}
|
||||
if (types.length > 0) {
|
||||
debounceStatsUpdate(newGuild.id, types);
|
||||
}
|
||||
});
|
||||
|
||||
// Mise à jour membre (rôle ajouté/retiré) -> role_members
|
||||
client.on("guildMemberUpdate", (oldMember, newMember) => {
|
||||
const oldRoles = oldMember.roles.cache;
|
||||
const newRoles = newMember.roles.cache;
|
||||
if (oldRoles.size !== newRoles.size || !oldRoles.every((r, id) => newRoles.has(id))) {
|
||||
debounceStatsUpdate(newMember.guild.id, ["role_members"]);
|
||||
}
|
||||
});
|
||||
|
||||
// Au démarrage du bot -> toutes les stats
|
||||
client.once("clientReady", async () => {
|
||||
console.log("📊 Mise à jour initiale des salons de statistiques...");
|
||||
await updateStatsChannels();
|
||||
});
|
||||
|
||||
|
||||
client.login(process.env.BOT_TOKEN);
|
||||
|
||||
module.exports = client;
|
||||
module.exports.updateGuildStats = updateGuildStats;
|
||||
|
||||
@@ -184,6 +184,16 @@ db.exec(`
|
||||
current_count INTEGER NOT NULL DEFAULT 0,
|
||||
last_user_id TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS stats_channels (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
guild_id TEXT NOT NULL,
|
||||
channel_id TEXT NOT NULL,
|
||||
stat_type TEXT NOT NULL,
|
||||
role_id TEXT,
|
||||
format TEXT NOT NULL DEFAULT '{stat}',
|
||||
UNIQUE(guild_id, channel_id)
|
||||
);
|
||||
`);
|
||||
|
||||
module.exports = db;
|
||||
|
||||
@@ -146,7 +146,7 @@ body {
|
||||
background-color: var(--bg-card);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-lg);
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
transition: transform var(--transition-normal), border-color var(--transition-normal), box-shadow var(--transition-normal);
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -161,7 +161,7 @@ body {
|
||||
height: 80px;
|
||||
background: linear-gradient(135deg, var(--primary), #7289da);
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
border-radius: var(--border-radius-lg) var(--border-radius-lg) 0 0;
|
||||
}
|
||||
|
||||
.guild-card-avatar {
|
||||
|
||||
@@ -117,8 +117,9 @@
|
||||
: `https://cdn.discordapp.com/embed/avatars/0.png`;
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="guild-card-header"></div>
|
||||
<img class="guild-card-avatar" src="${iconUrl}" alt="${g.name}">
|
||||
<div class="guild-card-header">
|
||||
<img class="guild-card-avatar" src="${iconUrl}" alt="${g.name}">
|
||||
</div>
|
||||
<div class="guild-card-body">
|
||||
<h3 class="guild-card-name">${g.name}</h3>
|
||||
<p class="guild-card-info">Cliquez pour configurer</p>
|
||||
|
||||
@@ -552,6 +552,43 @@ body {
|
||||
z-index: 200;
|
||||
}
|
||||
|
||||
/* ===== Stats Channels List ===== */
|
||||
.stats-channel-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--spacing-md);
|
||||
background: var(--bg-secondary);
|
||||
border-radius: var(--radius-md);
|
||||
margin-bottom: var(--spacing-sm);
|
||||
gap: var(--spacing-md);
|
||||
}
|
||||
|
||||
.stats-channel-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-xs);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.stats-channel-info strong {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.stats-channel-type {
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.stats-channel-format {
|
||||
background: var(--bg-card);
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-secondary);
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
/* ===== Responsive ===== */
|
||||
@media (max-width: 900px) {
|
||||
.sidebar {
|
||||
|
||||
@@ -58,6 +58,10 @@
|
||||
<span class="nav-item-icon">🔢</span>
|
||||
Comptage
|
||||
</a>
|
||||
<a class="nav-item" data-section="statschannels">
|
||||
<span class="nav-item-icon">📊</span>
|
||||
Salons de stats
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -698,6 +702,80 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Section: Salons de statistiques -->
|
||||
<section class="config-section" id="section-statschannels">
|
||||
<div class="config-card">
|
||||
<div class="config-card-header">
|
||||
<div class="config-card-title">
|
||||
<span class="icon">📊</span>
|
||||
<h3>Salons de statistiques</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div class="config-card-body">
|
||||
<div class="info-box">
|
||||
<strong>💡 Comment ça marche ?</strong><br>
|
||||
Créez des salons vocaux dont le nom affiche des statistiques du serveur en temps réel.
|
||||
Les noms sont mis à jour automatiquement toutes les 5 minutes.
|
||||
</div>
|
||||
|
||||
<!-- Formulaire d'ajout -->
|
||||
<div class="sub-section">
|
||||
<h4 class="sub-section-title">➕ Ajouter un salon de stats</h4>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Salon vocal</label>
|
||||
<select class="form-select" id="stats-channel-select"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Type de statistique</label>
|
||||
<select class="form-select" id="stats-type-select">
|
||||
<option value="members">👥 Membres (total)</option>
|
||||
<option value="humans">👤 Membres (sans bots)</option>
|
||||
<option value="bots">🤖 Bots</option>
|
||||
<option value="online">🟢 Membres en ligne</option>
|
||||
<option value="voice">🎤 Membres en vocal</option>
|
||||
<option value="roles">🎭 Nombre de rôles</option>
|
||||
<option value="channels">📺 Nombre de salons</option>
|
||||
<option value="boosts">🚀 Boosts</option>
|
||||
<option value="boost_level">💎 Niveau de boost</option>
|
||||
<option value="role_members">🏷️ Membres avec un rôle</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="stats-role-group" style="display: none;">
|
||||
<label class="form-label">Rôle à compter</label>
|
||||
<select class="form-select" id="stats-role-select"></select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Format du nom</label>
|
||||
<input type="text" class="form-input" id="stats-format-input" value="📊 Membres: {stat}" placeholder="📊 Membres: {stat}">
|
||||
</div>
|
||||
|
||||
<div class="variables-box">
|
||||
<div class="variables-box-title">Variables disponibles</div>
|
||||
<div class="variables-list">
|
||||
<span class="variable-tag"><code>{stat}</code> <span>→ valeur de la statistique</span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="btn btn-primary" id="add-stats-channel" style="margin-top: var(--spacing-md);">
|
||||
➕ Ajouter le salon
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Liste des salons configurés -->
|
||||
<div class="sub-section">
|
||||
<h4 class="sub-section-title">📋 Salons configurés</h4>
|
||||
<div id="stats-channels-list">
|
||||
<p class="text-muted">Aucun salon configuré.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</main>
|
||||
|
||||
@@ -714,5 +792,6 @@
|
||||
<script src="/guild/economyForm.js"></script>
|
||||
<script src="/guild/privateroomForm.js"></script>
|
||||
<script src="/guild/countingForm.js"></script>
|
||||
<script src="/guild/statsChannelsForm.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
// ===== STATS CHANNELS FORM =====
|
||||
(async function () {
|
||||
const channelSelect = document.getElementById("stats-channel-select");
|
||||
const typeSelect = document.getElementById("stats-type-select");
|
||||
const roleGroup = document.getElementById("stats-role-group");
|
||||
const roleSelect = document.getElementById("stats-role-select");
|
||||
const formatInput = document.getElementById("stats-format-input");
|
||||
const addBtn = document.getElementById("add-stats-channel");
|
||||
const listContainer = document.getElementById("stats-channels-list");
|
||||
|
||||
const statTypeNames = {
|
||||
members: "👥 Membres (total)",
|
||||
humans: "👤 Membres (sans bots)",
|
||||
bots: "🤖 Bots",
|
||||
online: "🟢 En ligne",
|
||||
voice: "🎤 En vocal",
|
||||
roles: "🎭 Rôles",
|
||||
channels: "📺 Salons",
|
||||
boosts: "🚀 Boosts",
|
||||
boost_level: "💎 Niveau boost",
|
||||
role_members: "🏷️ Membres avec rôle"
|
||||
};
|
||||
|
||||
// Afficher/masquer le sélecteur de rôle
|
||||
typeSelect.addEventListener("change", () => {
|
||||
if (typeSelect.value === "role_members") {
|
||||
roleGroup.style.display = "block";
|
||||
} else {
|
||||
roleGroup.style.display = "none";
|
||||
}
|
||||
|
||||
// Mettre à jour le format par défaut
|
||||
const formats = {
|
||||
members: "👥 Membres: {stat}",
|
||||
humans: "👤 Humains: {stat}",
|
||||
bots: "🤖 Bots: {stat}",
|
||||
online: "🟢 En ligne: {stat}",
|
||||
voice: "🎤 En vocal: {stat}",
|
||||
roles: "🎭 Rôles: {stat}",
|
||||
channels: "📺 Salons: {stat}",
|
||||
boosts: "🚀 Boosts: {stat}",
|
||||
boost_level: "💎 Niveau: {stat}",
|
||||
role_members: "🏷️ Rôle: {stat}"
|
||||
};
|
||||
formatInput.value = formats[typeSelect.value] || "📊 {stat}";
|
||||
});
|
||||
|
||||
// Charger les salons vocaux
|
||||
async function loadVoiceChannels() {
|
||||
try {
|
||||
const res = await fetch(`/api/bot/get-voice-channels/${guildId}`);
|
||||
const channels = await res.json();
|
||||
channelSelect.innerHTML = '<option value="">-- Sélectionner un salon --</option>';
|
||||
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 vocaux:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Charger les rôles
|
||||
async function loadRoles() {
|
||||
try {
|
||||
const res = await fetch(`/api/bot/get-roles/${guildId}`);
|
||||
const roles = await res.json();
|
||||
roleSelect.innerHTML = '<option value="">-- Sélectionner un rôle --</option>';
|
||||
roles.forEach(role => {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = role.id;
|
||||
opt.textContent = role.name;
|
||||
roleSelect.appendChild(opt);
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Erreur chargement rôles:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Charger la liste des salons configurés
|
||||
async function loadStatsChannels() {
|
||||
try {
|
||||
const res = await fetch(`/api/bot/get-stats-channels/${guildId}`);
|
||||
const channels = await res.json();
|
||||
|
||||
if (channels.length === 0) {
|
||||
listContainer.innerHTML = '<p class="text-muted">Aucun salon configuré.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
// Récupérer les infos des salons vocaux pour afficher les noms
|
||||
const voiceRes = await fetch(`/api/bot/get-voice-channels/${guildId}`);
|
||||
const voiceChannels = await voiceRes.json();
|
||||
const voiceMap = {};
|
||||
voiceChannels.forEach(ch => voiceMap[ch.id] = ch.name);
|
||||
|
||||
// Récupérer les rôles
|
||||
const rolesRes = await fetch(`/api/bot/get-roles/${guildId}`);
|
||||
const roles = await rolesRes.json();
|
||||
const rolesMap = {};
|
||||
roles.forEach(r => rolesMap[r.id] = r.name);
|
||||
|
||||
listContainer.innerHTML = channels.map(ch => {
|
||||
const channelName = voiceMap[ch.channel_id] || "Salon inconnu";
|
||||
const typeName = statTypeNames[ch.stat_type] || ch.stat_type;
|
||||
const roleInfo = ch.stat_type === "role_members" && ch.role_id
|
||||
? ` (${rolesMap[ch.role_id] || "Rôle inconnu"})`
|
||||
: "";
|
||||
|
||||
return `
|
||||
<div class="stats-channel-item" data-id="${ch.id}">
|
||||
<div class="stats-channel-info">
|
||||
<strong>🔊 ${channelName}</strong>
|
||||
<span class="stats-channel-type">${typeName}${roleInfo}</span>
|
||||
<code class="stats-channel-format">${ch.format}</code>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-danger delete-stats-channel" data-id="${ch.id}">
|
||||
🗑️ Supprimer
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}).join("");
|
||||
|
||||
// Ajouter les événements de suppression
|
||||
document.querySelectorAll(".delete-stats-channel").forEach(btn => {
|
||||
btn.addEventListener("click", async () => {
|
||||
const id = btn.dataset.id;
|
||||
if (!confirm("Supprimer ce salon de statistiques ?")) return;
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/bot/delete-stats-channel/${id}`, {
|
||||
method: "DELETE"
|
||||
});
|
||||
const result = await res.json();
|
||||
if (result.success) {
|
||||
loadStatsChannels();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Erreur suppression:", err);
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("Erreur chargement stats channels:", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Ajouter un salon
|
||||
addBtn.addEventListener("click", async () => {
|
||||
const channelId = channelSelect.value;
|
||||
const statType = typeSelect.value;
|
||||
const roleId = typeSelect.value === "role_members" ? roleSelect.value : null;
|
||||
const format = formatInput.value || "📊 {stat}";
|
||||
|
||||
if (!channelId) {
|
||||
alert("Veuillez sélectionner un salon.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (statType === "role_members" && !roleId) {
|
||||
alert("Veuillez sélectionner un rôle.");
|
||||
return;
|
||||
}
|
||||
|
||||
addBtn.disabled = true;
|
||||
addBtn.textContent = "Ajout...";
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/bot/add-stats-channel", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ guildId, channelId, statType, roleId, format })
|
||||
});
|
||||
const result = await res.json();
|
||||
|
||||
if (result.success) {
|
||||
channelSelect.value = "";
|
||||
loadStatsChannels();
|
||||
} else {
|
||||
alert("Erreur lors de l'ajout.");
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("Erreur ajout:", err);
|
||||
alert("Erreur réseau.");
|
||||
}
|
||||
|
||||
addBtn.disabled = false;
|
||||
addBtn.textContent = "➕ Ajouter le salon";
|
||||
});
|
||||
|
||||
// Init
|
||||
await loadVoiceChannels();
|
||||
await loadRoles();
|
||||
await loadStatsChannels();
|
||||
})();
|
||||
@@ -82,6 +82,11 @@
|
||||
<h3>Jeu de Comptage</h3>
|
||||
<p>Un mini-jeu collaboratif où les membres comptent à l'infini. Ils doivent alterner et ne pas se tromper sinon le compteur repart à 0 !</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">📊</div>
|
||||
<h3>Salons de Statistiques</h3>
|
||||
<p>Affichez les stats de votre serveur en temps réel dans des salons vocaux : membres, bots, en ligne, boosts, rôles et plus encore.</p>
|
||||
</div>
|
||||
<div class="feature-card">
|
||||
<div class="feature-icon">⚙️</div>
|
||||
<h3>Dashboard Intuitif</h3>
|
||||
@@ -110,7 +115,7 @@
|
||||
<span class="stat-label">Utilisateurs</span>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span id="stat-commands" class="stat-number">20+</span>
|
||||
<span id="stat-commands" class="stat-number">30+</span>
|
||||
<span class="stat-label">Commandes</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -890,5 +890,80 @@ module.exports = (app, db, client) => {
|
||||
);
|
||||
});
|
||||
|
||||
// ===== STATS CHANNELS =====
|
||||
router.get("/bot/get-stats-channels/:guildId", (req, res) => {
|
||||
const { guildId } = req.params;
|
||||
|
||||
db.all(
|
||||
"SELECT id, channel_id, stat_type, role_id, format FROM stats_channels WHERE guild_id = ?",
|
||||
[guildId],
|
||||
(err, rows) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.json([]);
|
||||
}
|
||||
res.json(rows || []);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
router.post("/bot/add-stats-channel", express.json(), (req, res) => {
|
||||
const { guildId, channelId, statType, roleId, format } = 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 stats_channels (guild_id, channel_id, stat_type, role_id, format)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(guild_id, channel_id) DO UPDATE SET
|
||||
stat_type = ?, role_id = ?, format = ?`,
|
||||
[guildId, channelId, statType, roleId || null, format || '{stat}', statType, roleId || null, format || '{stat}'],
|
||||
async function(err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.status(500).json({ success: false });
|
||||
}
|
||||
|
||||
// Mettre à jour le salon immédiatement
|
||||
try {
|
||||
const client = require("../bot");
|
||||
if (client.updateGuildStats) {
|
||||
await client.updateGuildStats(guildId, [statType]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Erreur mise à jour stats:", e);
|
||||
}
|
||||
|
||||
res.json({ success: true, id: this.lastID });
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
router.delete("/bot/delete-stats-channel/:id", (req, res) => {
|
||||
const { id } = req.params;
|
||||
|
||||
if (!req.session.guilds) {
|
||||
return res.status(401).json({ success: false });
|
||||
}
|
||||
|
||||
db.run("DELETE FROM stats_channels WHERE id = ?", [id], (err) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return res.status(500).json({ success: false });
|
||||
}
|
||||
res.json({ success: true });
|
||||
});
|
||||
});
|
||||
|
||||
app.use("/api", router);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user