refonte css of site

This commit is contained in:
Arthur Puechberty
2026-01-17 20:59:52 +01:00
parent 90339f9323
commit 95cd796839
20 changed files with 2951 additions and 1109 deletions
+11 -3
View File
@@ -1,4 +1,4 @@
const { Events } = require("discord.js"); const { Events, EmbedBuilder } = require("discord.js");
const db = require("../db"); const db = require("../db");
module.exports = { module.exports = {
@@ -10,7 +10,7 @@ module.exports = {
(err, row) => { (err, row) => {
if (err || !row || !row.enabled) return; if (err || !row || !row.enabled) return;
let msg = row.message; let msg = row.message || "Bienvenue {mention} sur {server} !";
msg = msg msg = msg
.replace("{user}", member.user.username) .replace("{user}", member.user.username)
@@ -19,7 +19,15 @@ module.exports = {
const channel = member.guild.channels.cache.get(row.channel_id); const channel = member.guild.channels.cache.get(row.channel_id);
if (channel) { if (channel) {
channel.send(msg); const embed = new EmbedBuilder()
.setColor(0x57F287)
.setTitle("👋 Bienvenue !")
.setDescription(msg)
.setThumbnail(member.user.displayAvatarURL({ dynamic: true, size: 256 }))
.setFooter({ text: member.guild.name, iconURL: member.guild.iconURL({ dynamic: true }) })
.setTimestamp();
channel.send({ embeds: [embed] });
} }
} }
); );
+11 -3
View File
@@ -1,4 +1,4 @@
const { Events } = require("discord.js"); const { Events, EmbedBuilder } = require("discord.js");
const db = require("../db"); const db = require("../db");
module.exports = { module.exports = {
@@ -10,7 +10,7 @@ module.exports = {
(err, row) => { (err, row) => {
if (err || !row || !row.enabled) return; if (err || !row || !row.enabled) return;
let msg = row.message; let msg = row.message || "Au revoir {user}, tu vas nous manquer !";
msg = msg msg = msg
.replace("{user}", member.user.username) .replace("{user}", member.user.username)
@@ -18,7 +18,15 @@ module.exports = {
const channel = member.guild.channels.cache.get(row.channel_id); const channel = member.guild.channels.cache.get(row.channel_id);
if (channel) { if (channel) {
channel.send(msg); const embed = new EmbedBuilder()
.setColor(0xED4245)
.setTitle("👋 Au revoir...")
.setDescription(msg)
.setThumbnail(member.user.displayAvatarURL({ dynamic: true, size: 256 }))
.setFooter({ text: member.guild.name, iconURL: member.guild.iconURL({ dynamic: true }) })
.setTimestamp();
channel.send({ embeds: [embed] });
} }
} }
); );
+40 -15
View File
@@ -1,4 +1,4 @@
const { Events } = require("discord.js"); const { Events, EmbedBuilder } = require("discord.js");
const db = require("../db"); const db = require("../db");
module.exports = { module.exports = {
@@ -31,25 +31,31 @@ module.exports = {
[guildId], [guildId],
(err, row) => { (err, row) => {
if (err || !row || !row.enabled || !row.gain_xp_on_message) return; if (err || !row || !row.enabled || !row.gain_xp_on_message) return;
if (row.role_with_without_type === "with") {
const roleWithWithoutType = row.role_with_without_type || "without";
const salonWithWithoutType = row.salon_with_without_type || "without";
if (roleWithWithoutType === "with") {
const userRoles = message.member.roles.cache; const userRoles = message.member.roles.cache;
const requiredRoles = JSON.parse(row.role_with_without_xp || "[]"); const requiredRoles = JSON.parse(row.role_with_without_xp || "[]");
if (!requiredRoles.some(roleId => userRoles.has(roleId))) { if (!requiredRoles.some(roleId => userRoles.has(roleId))) {
return; return;
} }
} else if (row.role_with_without_type === "without") { } else if (roleWithWithoutType === "without") {
const userRoles = message.member.roles.cache; const userRoles = message.member.roles.cache;
const excludedRoles = JSON.parse(row.role_with_without_xp || "[]"); const excludedRoles = JSON.parse(row.role_with_without_xp || "[]");
if (excludedRoles.some(roleId => userRoles.has(roleId))) { if (excludedRoles.some(roleId => userRoles.has(roleId))) {
return; return;
} }
} else if (row.salon_with_without_type === "with") { }
if (salonWithWithoutType === "with") {
const channelId = message.channel.id; const channelId = message.channel.id;
const requiredChannels = JSON.parse(row.salon_with_without_xp || "[]"); const requiredChannels = JSON.parse(row.salon_with_without_xp || "[]");
if (!requiredChannels.includes(channelId)) { if (!requiredChannels.includes(channelId)) {
return; return;
} }
} else if (row.salon_with_without_type === "without") { } else if (salonWithWithoutType === "without") {
const channelId = message.channel.id; const channelId = message.channel.id;
const excludedChannels = JSON.parse(row.salon_with_without_xp || "[]"); const excludedChannels = JSON.parse(row.salon_with_without_xp || "[]");
if (excludedChannels.includes(channelId)) { if (excludedChannels.includes(channelId)) {
@@ -64,13 +70,14 @@ module.exports = {
(err, userRow) => { (err, userRow) => {
if (err) return; if (err) return;
const cooldownSeconds = row.cooldown_xp_message_seconds ?? 60;
const lastTimestamp = userRow ? userRow.last_xp_message_timestamp || 0 : 0; const lastTimestamp = userRow ? userRow.last_xp_message_timestamp || 0 : 0;
if (now - lastTimestamp < row.cooldown_xp_message_seconds * 1000) { if (now - lastTimestamp < cooldownSeconds * 1000) {
return; return;
} }
const minXp = row.gain_xp_message_lower_bound; const minXp = row.gain_xp_message_lower_bound ?? 15;
const maxXp = row.gain_xp_message_upper_bound; const maxXp = row.gain_xp_message_upper_bound ?? 25;
const xpToAdd = Math.floor(Math.random() * (maxXp - minXp + 1)) + minXp; const xpToAdd = Math.floor(Math.random() * (maxXp - minXp + 1)) + minXp;
let newXp; let newXp;
@@ -84,17 +91,21 @@ module.exports = {
newLevel = 1; newLevel = 1;
} }
const multiplier = row.multiplier_courbe_for_level; const multiplier = row.multiplier_courbe_for_level ?? 100;
const courbeType = row.xp_courbe_type || "linear";
let fonction_courbe; let fonction_courbe;
if (row.xp_courbe_type === "constante") { if (courbeType === "constante") {
fonction_courbe = (level) => multiplier; fonction_courbe = (level) => multiplier;
} else if (row.xp_courbe_type === "linear") { } else if (courbeType === "linear") {
fonction_courbe = (level) => (level) * multiplier; fonction_courbe = (level) => (level) * multiplier;
} else if (row.xp_courbe_type === "quadratic") { } else if (courbeType === "quadratic") {
fonction_courbe = (level) => (level) * (level) * multiplier; fonction_courbe = (level) => (level) * (level) * multiplier;
} else if (row.xp_courbe_type === "exponential") { } else if (courbeType === "exponential") {
fonction_courbe = (level) => Math.pow(2, (level - 1)) * multiplier; fonction_courbe = (level) => Math.pow(2, (level - 1)) * multiplier;
} else {
// Fallback au cas où
fonction_courbe = (level) => (level) * multiplier;
} }
let xpForNextLevel = fonction_courbe(newLevel); let xpForNextLevel = fonction_courbe(newLevel);
@@ -106,13 +117,27 @@ module.exports = {
if (row.level_announcements_enabled && (newLevel % row.level_annoncement_every_level === 0)) { if (row.level_announcements_enabled && (newLevel % row.level_annoncement_every_level === 0)) {
const channel = message.guild.channels.cache.get(row.level_announcements_channel_id); const channel = message.guild.channels.cache.get(row.level_announcements_channel_id);
if (channel) { if (channel) {
let announcementMsg = row.level_announcements_message; let announcementMsg = row.level_announcements_message || "🎉 {mention} a atteint le niveau {level} !";
announcementMsg = announcementMsg announcementMsg = announcementMsg
.replace("{user}", message.author.username) .replace("{user}", message.author.username)
.replace("{mention}", `<@${message.author.id}>`) .replace("{mention}", `<@${message.author.id}>`)
.replace("{level}", newLevel) .replace("{level}", newLevel)
.replace("{level-xp}", xpForNextLevel); .replace("{level-xp}", xpForNextLevel);
channel.send(announcementMsg);
const embed = new EmbedBuilder()
.setColor(0xFEE75C)
.setTitle("🎉 Level Up !")
.setDescription(announcementMsg)
.setThumbnail(message.author.displayAvatarURL({ dynamic: true, size: 256 }))
.addFields(
{ name: "Nouveau niveau", value: `${newLevel}`, inline: true },
{ name: "XP pour le prochain", value: `${xpForNextLevel}`, inline: true }
)
.setFooter({ text: message.guild.name, iconURL: message.guild.iconURL({ dynamic: true }) })
.setTimestamp();
// @ mention car c'est une notification importante
channel.send({ content: `<@${message.author.id}>`, embeds: [embed] });
} }
} }
} }
+261 -118
View File
@@ -1,137 +1,280 @@
/* ===== Reset minimal ===== */ @import url('global.css');
* {
margin: 0; /* ===== Navigation ===== */
padding: 0; .navbar {
box-sizing: border-box; position: fixed;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; top: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: rgba(22, 27, 34, 0.95);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border-color);
padding: var(--spacing-md) 0;
} }
.navbar-container {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-md);
}
.navbar-brand {
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-size: 1.25rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
}
.navbar-brand img {
width: 32px;
height: 32px;
border-radius: 50%;
}
.navbar-nav {
display: flex;
align-items: center;
gap: var(--spacing-lg);
}
.navbar-link {
color: var(--text-secondary);
font-weight: 500;
text-decoration: none;
transition: color var(--transition-fast);
}
.navbar-link:hover {
color: var(--text-primary);
}
.navbar-user {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.navbar-user img {
width: 32px;
height: 32px;
border-radius: 50%;
border: 2px solid var(--primary);
}
.navbar-user span {
color: var(--text-primary);
font-weight: 500;
}
.navbar-actions {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
/* ===== Dashboard Layout ===== */
body { body {
background-color: #1e1f29; /* fond sombre type Discord */ padding-top: 80px;
color: #ffffff; min-height: 100vh;
}
.dashboard-container {
max-width: 1200px;
margin: 0 auto;
padding: var(--spacing-xl) var(--spacing-md);
}
/* ===== Dashboard Header ===== */
.dashboard-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--spacing-xl);
flex-wrap: wrap;
gap: var(--spacing-md);
}
.user-info {
display: flex;
align-items: center;
gap: var(--spacing-md);
}
.user-avatar {
width: 64px;
height: 64px;
border-radius: 50%;
border: 3px solid var(--primary);
box-shadow: 0 0 20px rgba(88, 101, 242, 0.3);
}
.user-details h1 {
font-size: 1.5rem;
margin-bottom: var(--spacing-xs);
color: var(--text-primary);
}
.user-details p {
color: var(--text-secondary);
font-size: 0.95rem;
}
/* ===== Section Title ===== */
.section-title {
font-size: 1.25rem;
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
padding-bottom: var(--spacing-sm);
border-bottom: 1px solid var(--border-color);
}
/* ===== Guilds Grid ===== */
.guilds-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--spacing-lg);
}
.guild-card {
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-lg);
overflow: hidden;
transition: transform var(--transition-normal), border-color var(--transition-normal), box-shadow var(--transition-normal);
cursor: pointer;
}
.guild-card:hover {
transform: translateY(-4px);
border-color: var(--primary);
box-shadow: 0 8px 30px rgba(88, 101, 242, 0.15);
}
.guild-card-header {
height: 80px;
background: linear-gradient(135deg, var(--primary), #7289da);
position: relative;
overflow: visible;
}
.guild-card-avatar {
position: absolute;
bottom: -30px;
left: var(--spacing-md);
width: 60px;
height: 60px;
border-radius: 50%;
border: 4px solid var(--bg-card);
background-color: var(--bg-dark);
object-fit: cover;
}
.guild-card-body {
padding: var(--spacing-lg);
padding-top: calc(var(--spacing-lg) + 20px);
}
.guild-card-name {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
}
.guild-card-info {
color: var(--text-secondary);
font-size: 0.85rem;
}
.guild-card-footer {
padding: var(--spacing-md);
border-top: 1px solid var(--border-color);
display: flex;
justify-content: flex-end;
}
/* ===== Empty State ===== */
.empty-state {
text-align: center;
padding: var(--spacing-2xl);
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-lg);
}
.empty-state-icon {
font-size: 48px;
margin-bottom: var(--spacing-md);
}
.empty-state h3 {
font-size: 1.25rem;
color: var(--text-primary);
margin-bottom: var(--spacing-sm);
}
.empty-state p {
color: var(--text-secondary);
margin-bottom: var(--spacing-lg);
}
/* ===== Loading State ===== */
.loading {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 20px; justify-content: center;
padding: var(--spacing-2xl);
} }
/* ===== Navigation ===== */ .spinner {
nav {
width: 100%;
max-width: 800px;
display: flex;
justify-content: flex-start;
background-color: #2f3136;
padding: 10px 20px;
border-radius: 10px;
margin-bottom: 30px;
}
nav a {
color: #ffffff;
text-decoration: none;
font-weight: 500;
transition: color 0.2s;
}
nav a:hover {
color: #5865f2;
}
/* ===== Titres ===== */
h1, h2 {
text-align: center;
}
h1 {
font-size: 2.5rem;
margin-bottom: 15px;
}
h2 {
font-size: 1.8rem;
margin: 25px 0 10px 0;
border-bottom: 2px solid #5865f2;
display: inline-block;
padding-bottom: 5px;
}
/* ===== Avatar ===== */
#avatar {
width: 80px;
height: 80px;
border-radius: 50%;
margin: 10px 0 20px 0;
border: 2px solid #5865f2;
}
/* ===== Bouton d'invitation ===== */
#invite-link {
display: inline-block;
background-color: #5865f2;
color: white;
padding: 10px 20px;
border-radius: 8px;
text-decoration: none;
font-weight: 600;
margin-bottom: 30px;
transition: background 0.2s;
}
#invite-link:hover {
background-color: #4752c4;
}
/* ===== Liste des guilds ===== */
ul#guilds-list {
list-style: none;
width: 100%;
max-width: 800px;
padding: 0;
}
ul#guilds-list li {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 15px;
margin-bottom: 10px;
border-radius: 10px;
background-color: #161a22;
cursor: pointer;
transition: background 0.2s, color 0.2s;
}
ul#guilds-list li:hover {
background-color: #5865f2;
color: white;
}
ul#guilds-list li img {
width: 40px; width: 40px;
height: 40px; height: 40px;
border: 3px solid var(--border-color);
border-top-color: var(--primary);
border-radius: 50%; border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading p {
margin-top: var(--spacing-md);
color: var(--text-secondary);
} }
/* ===== Responsive ===== */ /* ===== Responsive ===== */
@media (max-width: 600px) { @media (max-width: 768px) {
body { .dashboard-header {
padding: 10px;
}
nav {
flex-direction: column; flex-direction: column;
align-items: center; align-items: flex-start;
gap: 10px;
} }
ul#guilds-list li { .user-avatar {
flex-direction: column; width: 48px;
align-items: center; height: 48px;
text-align: center;
} }
ul#guilds-list li img { .user-details h1 {
margin-bottom: 5px; font-size: 1.25rem;
}
.guilds-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 480px) {
.navbar-nav .navbar-link {
display: none;
} }
} }
+118 -71
View File
@@ -1,108 +1,155 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="fr">
<head> <head>
<title>Tableau de bord</title> <title>Dashboard - LazyBot</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/dashboard.css"> <link rel="stylesheet" href="/dashboard.css">
</head> </head>
<body> <body>
<nav> <!-- Navigation -->
<a href="/">Accueil</a> <nav class="navbar">
<div class="navbar-container">
<a href="/" class="navbar-brand">
<img id="bot-avatar" src="https://cdn.discordapp.com/embed/avatars/0.png" alt="LazyBot">
<span>LazyBot</span>
</a>
<div class="navbar-nav">
<a href="/" class="navbar-link">Accueil</a>
<a href="/dashboard" class="navbar-link">Dashboard</a>
<div class="navbar-actions">
<div class="navbar-user">
<img id="nav-avatar" src="" alt="Avatar">
<span id="nav-username"></span>
</div>
<a href="/auth/logout" class="btn btn-secondary btn-sm">Déconnexion</a>
</div>
</div>
</div>
</nav> </nav>
<h1 id="greeting">Chargement...</h1> <!-- Dashboard Content -->
<img id="avatar" src="" alt="Avatar"> <div class="dashboard-container">
<!-- Dashboard Header -->
<div class="dashboard-header">
<div class="user-info">
<img id="user-avatar" class="user-avatar" src="" alt="Avatar">
<div class="user-details">
<h1 id="greeting">Chargement...</h1>
<p>Sélectionnez un serveur pour le configurer</p>
</div>
</div>
<a id="invite-link" href="#" class="btn btn-primary">
Ajouter le bot à un serveur
</a>
</div>
<a id="invite-link" href="#">Ajouter le bot à votre serveur</a> <!-- Guilds Section -->
<h2 class="section-title">🏠 Mes serveurs</h2>
<h2>Mes serveurs qui ont le bot :</h2>
<ul id="guilds-list"></ul> <!-- Ici on va lister les guilds --> <div id="guilds-container">
<div class="loading">
<div class="spinner"></div>
<p>Chargement des serveurs...</p>
</div>
</div>
</div>
<script> <script>
// --- Affichage des infos utilisateur --- // Charger les infos du bot
fetch("/api/user") fetch("/api/bot-info")
.then(res => res.json()) .then(res => res.json())
.then(bot => {
document.getElementById("bot-avatar").src = `https://cdn.discordapp.com/avatars/${bot.id}/${bot.avatar}.png`;
})
.catch(() => {});
// Charger les infos utilisateur
fetch("/api/user")
.then(res => {
if (!res.ok) throw new Error("Non connecté");
return res.json();
})
.then(user => { .then(user => {
document.getElementById("greeting").textContent = `Salut ${user.username}#${user.discriminator} !`; const avatarUrl = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`;
document.getElementById("avatar").src = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`; document.getElementById("greeting").textContent = `Bienvenue, ${user.username} !`;
document.getElementById("user-avatar").src = avatarUrl;
document.getElementById("nav-avatar").src = avatarUrl;
document.getElementById("nav-username").textContent = user.username;
}) })
.catch(() => { .catch(() => {
document.getElementById("greeting").textContent = "Utilisateur non connecté."; window.location.href = "/auth/login";
}); });
// Charger les guilds
// --- Affichage des guilds de l'utilisateur ---
fetch("/api/guilds") fetch("/api/guilds")
.then(res => res.json()) .then(res => res.json())
.then(guilds => { .then(guilds => {
const list = document.getElementById("guilds-list"); const container = document.getElementById("guilds-container");
if (guilds.length === 0) { if (guilds.length === 0) {
list.innerHTML = "<li>Aucun serveur disponible</li>"; container.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">🤖</div>
<h3>Aucun serveur trouvé</h3>
<p>Ajoutez LazyBot à un serveur pour commencer à le configurer.</p>
<a id="invite-empty" href="#" class="btn btn-primary">Ajouter à un serveur</a>
</div>
`;
// Récupérer le lien pour le bouton empty state
fetch("/invite-bot")
.then(res => res.json())
.then(data => {
const btn = document.getElementById("invite-empty");
if (btn) btn.href = data.url;
});
} else { } else {
container.innerHTML = '<div class="guilds-grid"></div>';
const grid = container.querySelector('.guilds-grid');
guilds.forEach(g => { guilds.forEach(g => {
const li = document.createElement("li"); const card = document.createElement("div");
li.style.cursor = "pointer"; card.className = "guild-card";
li.style.padding = "8px"; card.onclick = () => window.location.href = `/guild/${g.id}`;
li.style.border = "1px solid #ccc";
li.style.margin = "5px 0"; const iconUrl = g.icon
li.style.display = "flex"; ? `https://cdn.discordapp.com/icons/${g.id}/${g.icon}.png`
li.style.alignItems = "center"; : `https://cdn.discordapp.com/embed/avatars/0.png`;
li.style.gap = "10px";
li.style.borderRadius = "8px"; card.innerHTML = `
li.style.backgroundColor = "#161a22"; // fond sombre pour chaque item <div class="guild-card-header"></div>
li.style.transition = "background 0.2s"; <img class="guild-card-avatar" src="${iconUrl}" alt="${g.name}">
<div class="guild-card-body">
li.addEventListener("click", () => { <h3 class="guild-card-name">${g.name}</h3>
window.location.href = `/guild/${g.id}`; <p class="guild-card-info">Cliquez pour configurer</p>
}); </div>
<div class="guild-card-footer">
li.addEventListener("mouseover", () => { <span class="btn btn-sm btn-primary">Configurer →</span>
li.style.backgroundColor = "#5865F2"; // couleur Discord au hover </div>
li.style.color = "white"; `;
});
grid.appendChild(card);
li.addEventListener("mouseout", () => {
li.style.backgroundColor = "#161a22";
li.style.color = "";
});
// Logo de la guild
if (g.icon) {
const img = document.createElement("img");
img.src = `https://cdn.discordapp.com/icons/${g.id}/${g.icon}.png`;
img.alt = `${g.name} logo`;
img.style.width = "40px";
img.style.height = "40px";
img.style.borderRadius = "50%";
li.appendChild(img);
}
// Nom de la guild
const span = document.createElement("span");
span.textContent = g.name;
li.appendChild(span);
list.appendChild(li);
}); });
} }
}) })
.catch(() => { .catch(() => {
document.getElementById("guilds-list").innerHTML = "<li>Impossible de récupérer les guilds.</li>"; document.getElementById("guilds-container").innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">❌</div>
<h3>Erreur de chargement</h3>
<p>Impossible de récupérer la liste des serveurs.</p>
<button class="btn btn-primary" onclick="location.reload()">Réessayer</button>
</div>
`;
}); });
// Lien d'invitation
fetch("/invite-bot") fetch("/invite-bot")
.then(res => res.json()) .then(res => res.json())
.then(data => { .then(data => {
const link = document.getElementById("invite-link"); document.getElementById("invite-link").href = data.url;
link.href = data.url; // met le lien dynamique
}) })
.catch(() => { .catch(() => console.log("Impossible de récupérer le lien du bot."));
console.log("Impossible de récupérer le lien du bot.");
});
</script> </script>
</body> </body>
</html> </html>
+406
View File
@@ -0,0 +1,406 @@
/* ===== Variables CSS ===== */
:root {
/* Couleurs principales */
--primary: #5865f2;
--primary-hover: #4752c4;
--primary-light: rgba(88, 101, 242, 0.1);
/* Couleurs de fond */
--bg-dark: #0d1117;
--bg-card: #161b22;
--bg-card-hover: #1c2128;
--bg-input: #0d1117;
--bg-nav: #161b22;
/* Couleurs de texte */
--text-primary: #e6edf3;
--text-secondary: #8b949e;
--text-muted: #6e7681;
/* Couleurs d'état */
--success: #238636;
--success-light: rgba(35, 134, 54, 0.15);
--danger: #da3633;
--danger-light: rgba(218, 54, 51, 0.15);
--warning: #d29922;
--warning-light: rgba(210, 153, 34, 0.15);
/* Bordures */
--border-color: #30363d;
--border-radius: 6px;
--border-radius-lg: 12px;
/* Ombres */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.5);
/* Transitions */
--transition-fast: 150ms ease;
--transition-normal: 250ms ease;
/* Espacements */
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 16px;
--spacing-lg: 24px;
--spacing-xl: 32px;
--spacing-2xl: 48px;
}
/* ===== Reset ===== */
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.5;
color: var(--text-primary);
background-color: var(--bg-dark);
min-height: 100vh;
}
a {
color: var(--primary);
text-decoration: none;
transition: color var(--transition-fast);
}
a:hover {
color: var(--primary-hover);
text-decoration: none;
}
img {
max-width: 100%;
height: auto;
}
/* ===== Typography ===== */
h1, h2, h3, h4, h5, h6 {
font-weight: 600;
line-height: 1.25;
color: var(--text-primary);
}
h1 { font-size: 2rem; }
h2 { font-size: 1.5rem; }
h3 { font-size: 1.25rem; }
h4 { font-size: 1rem; }
p {
color: var(--text-secondary);
}
/* ===== Buttons ===== */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
font-size: 14px;
font-weight: 500;
line-height: 20px;
border-radius: var(--border-radius);
border: 1px solid transparent;
cursor: pointer;
transition: all var(--transition-fast);
text-decoration: none;
}
.btn:hover {
text-decoration: none;
}
.btn-primary {
background-color: var(--primary);
color: white;
border-color: var(--primary);
}
.btn-primary:hover {
background-color: var(--primary-hover);
border-color: var(--primary-hover);
color: white;
}
.btn-secondary {
background-color: var(--bg-card);
color: var(--text-primary);
border-color: var(--border-color);
}
.btn-secondary:hover {
background-color: var(--bg-card-hover);
border-color: var(--text-muted);
}
.btn-success {
background-color: var(--success);
color: white;
border-color: var(--success);
}
.btn-success:hover {
background-color: #2ea043;
border-color: #2ea043;
color: white;
}
.btn-danger {
background-color: var(--danger);
color: white;
border-color: var(--danger);
}
.btn-danger:hover {
background-color: #f85149;
border-color: #f85149;
color: white;
}
.btn-lg {
padding: var(--spacing-md) var(--spacing-lg);
font-size: 16px;
}
.btn-sm {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: 12px;
}
/* ===== Form Elements ===== */
.form-group {
margin-bottom: var(--spacing-md);
}
.form-label {
display: block;
margin-bottom: var(--spacing-sm);
font-weight: 500;
color: var(--text-primary);
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
padding: var(--spacing-sm) var(--spacing-md);
font-size: 14px;
line-height: 20px;
color: var(--text-primary);
background-color: var(--bg-input);
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px var(--primary-light);
}
.form-input::placeholder {
color: var(--text-muted);
}
.form-textarea {
resize: vertical;
min-height: 100px;
}
.form-select {
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%238b949e' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10l-5 5z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 12px center;
padding-right: 36px;
}
.form-select[multiple] {
background-image: none;
padding-right: var(--spacing-md);
height: auto;
min-height: 120px;
}
.form-select option {
padding: var(--spacing-sm);
}
.form-checkbox {
display: flex;
align-items: center;
gap: var(--spacing-sm);
cursor: pointer;
}
.form-checkbox input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: var(--primary);
cursor: pointer;
}
.form-hint {
margin-top: var(--spacing-xs);
font-size: 12px;
color: var(--text-muted);
}
/* ===== Cards ===== */
.card {
background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-lg);
overflow: hidden;
}
.card-header {
padding: var(--spacing-md) var(--spacing-lg);
border-bottom: 1px solid var(--border-color);
background-color: var(--bg-card-hover);
}
.card-header h2,
.card-header h3 {
margin: 0;
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.card-body {
padding: var(--spacing-lg);
}
.card-footer {
padding: var(--spacing-md) var(--spacing-lg);
border-top: 1px solid var(--border-color);
background-color: var(--bg-card-hover);
}
/* ===== Alerts ===== */
.alert {
padding: var(--spacing-md);
border-radius: var(--border-radius);
margin-bottom: var(--spacing-md);
display: flex;
align-items: flex-start;
gap: var(--spacing-sm);
}
.alert-success {
background-color: var(--success-light);
border: 1px solid var(--success);
color: #3fb950;
}
.alert-danger {
background-color: var(--danger-light);
border: 1px solid var(--danger);
color: #f85149;
}
.alert-warning {
background-color: var(--warning-light);
border: 1px solid var(--warning);
color: #d29922;
}
/* ===== Badge ===== */
.badge {
display: inline-flex;
align-items: center;
padding: 2px 8px;
font-size: 12px;
font-weight: 500;
border-radius: 20px;
}
.badge-primary {
background-color: var(--primary-light);
color: var(--primary);
}
.badge-success {
background-color: var(--success-light);
color: #3fb950;
}
.badge-danger {
background-color: var(--danger-light);
color: #f85149;
}
/* ===== Container ===== */
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
padding: 0 var(--spacing-md);
}
.container-sm {
max-width: 800px;
}
.container-lg {
max-width: 1400px;
}
/* ===== Grid ===== */
.grid {
display: grid;
gap: var(--spacing-lg);
}
.grid-2 {
grid-template-columns: repeat(2, 1fr);
}
.grid-3 {
grid-template-columns: repeat(3, 1fr);
}
@media (max-width: 768px) {
.grid-2,
.grid-3 {
grid-template-columns: 1fr;
}
}
/* ===== Utilities ===== */
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-muted { color: var(--text-muted); }
.text-success { color: #3fb950; }
.text-danger { color: #f85149; }
.mt-sm { margin-top: var(--spacing-sm); }
.mt-md { margin-top: var(--spacing-md); }
.mt-lg { margin-top: var(--spacing-lg); }
.mb-sm { margin-bottom: var(--spacing-sm); }
.mb-md { margin-bottom: var(--spacing-md); }
.mb-lg { margin-bottom: var(--spacing-lg); }
.flex { display: flex; }
.flex-center { align-items: center; justify-content: center; }
.flex-between { justify-content: space-between; }
.gap-sm { gap: var(--spacing-sm); }
.gap-md { gap: var(--spacing-md); }
+534 -104
View File
@@ -1,143 +1,573 @@
/* ===== Reset minimal ===== */ @import url('global.css');
* {
margin: 0; /* ===== Layout ===== */
padding: 0; body {
box-sizing: border-box; display: flex;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; min-height: 100vh;
overflow-x: hidden;
} }
body { /* ===== Sidebar ===== */
background-color: #1e1f29; /* fond sombre type Discord */ .sidebar {
color: #ffffff; width: 260px;
background-color: var(--bg-card);
border-right: 1px solid var(--border-color);
position: fixed;
top: 0;
left: 0;
bottom: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; z-index: 100;
padding: 20px;
} }
/* ===== Navigation ===== */ .sidebar-header {
nav { padding: var(--spacing-lg);
width: 100%; border-bottom: 1px solid var(--border-color);
max-width: 900px; }
.sidebar-brand {
display: flex; display: flex;
justify-content: flex-start; align-items: center;
background-color: #2f3136; gap: var(--spacing-sm);
padding: 10px 20px;
border-radius: 10px;
margin-bottom: 30px;
}
nav a {
color: #ffffff;
text-decoration: none; text-decoration: none;
font-weight: 500; color: var(--text-primary);
margin-right: 15px; font-weight: 700;
transition: color 0.2s; font-size: 1.1rem;
margin-bottom: var(--spacing-md);
} }
nav a:hover { .sidebar-brand img {
color: #5865f2; width: 32px;
height: 32px;
border-radius: 50%;
} }
/* ===== Titres ===== */ .guild-info {
h1 { display: flex;
text-align: center; align-items: center;
font-size: 2.5rem; gap: var(--spacing-sm);
margin-bottom: 20px; padding: var(--spacing-sm);
background-color: var(--bg-dark);
border-radius: var(--border-radius);
} }
h2 { .guild-info img {
font-size: 1.8rem; width: 40px;
margin: 25px 0 10px 0; height: 40px;
border-bottom: 2px solid #5865f2; border-radius: 50%;
display: inline-block;
padding-bottom: 5px;
text-align: center;
} }
/* ===== Formulaires ===== */ .guild-info-text {
form { flex: 1;
background-color: #2f3136; min-width: 0;
padding: 20px;
border-radius: 12px;
margin-bottom: 30px;
width: 100%;
max-width: 700px;
} }
form label { .guild-info-name {
display: block;
margin-bottom: 15px;
font-weight: 500;
}
form input[type="checkbox"] {
margin-right: 10px;
}
form select,
form textarea {
width: 100%;
padding: 8px;
border-radius: 6px;
border: 1px solid #5865f2;
background-color: #161a22;
color: #ffffff;
margin-top: 5px;
font-size: 1rem;
}
form textarea {
resize: vertical;
}
form button {
background-color: #5865f2;
color: white;
padding: 10px 20px;
border: none;
border-radius: 8px;
font-weight: 600; font-weight: 600;
color: var(--text-primary);
font-size: 0.9rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.guild-info-id {
color: var(--text-muted);
font-size: 0.75rem;
}
/* ===== Sidebar Navigation ===== */
.sidebar-nav {
flex: 1;
padding: var(--spacing-md);
overflow-y: auto;
}
.nav-section {
margin-bottom: var(--spacing-lg);
}
.nav-section-title {
font-size: 0.7rem;
font-weight: 700;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.05em;
padding: var(--spacing-xs) var(--spacing-sm);
margin-bottom: var(--spacing-xs);
}
.nav-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius);
color: var(--text-secondary);
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
transition: all var(--transition-fast);
cursor: pointer; cursor: pointer;
transition: background 0.2s;
} }
form button:hover { .nav-item:hover {
background-color: #4752c4; background-color: var(--bg-dark);
color: var(--text-primary);
} }
form small { .nav-item.active {
background-color: var(--primary-light);
color: var(--primary);
}
.nav-item-icon {
font-size: 1.1rem;
width: 24px;
text-align: center;
}
/* ===== Sidebar Footer ===== */
.sidebar-footer {
padding: var(--spacing-md);
border-top: 1px solid var(--border-color);
}
.sidebar-footer a {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius);
color: var(--text-secondary);
text-decoration: none;
font-size: 0.9rem;
transition: all var(--transition-fast);
}
.sidebar-footer a:hover {
background-color: var(--bg-dark);
color: var(--text-primary);
}
/* ===== Main Content ===== */
.main-content {
flex: 1;
margin-left: 260px;
min-height: 100vh;
display: flex;
flex-direction: column;
}
/* ===== Top Header ===== */
.top-header {
background-color: var(--bg-card);
border-bottom: 1px solid var(--border-color);
padding: var(--spacing-md) var(--spacing-xl);
display: flex;
align-items: center;
justify-content: space-between;
position: sticky;
top: 0;
z-index: 50;
}
.breadcrumb {
display: flex;
align-items: center;
gap: var(--spacing-sm);
color: var(--text-secondary);
font-size: 0.9rem;
}
.breadcrumb a {
color: var(--text-secondary);
text-decoration: none;
}
.breadcrumb a:hover {
color: var(--text-primary);
}
.breadcrumb-separator {
color: var(--text-muted);
}
.breadcrumb-current {
color: var(--text-primary);
font-weight: 500;
}
.header-user {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.header-user img {
width: 32px;
height: 32px;
border-radius: 50%;
}
.header-user span {
color: var(--text-primary);
font-weight: 500;
}
/* ===== Content Area ===== */
.content-area {
flex: 1;
padding: var(--spacing-xl);
max-width: 900px;
}
/* ===== Config Sections ===== */
.config-section {
display: none;
}
.config-section.active {
display: block; display: block;
margin-top: 10px;
font-size: 0.85rem;
color: #b9bbbe;
} }
form small ul { /* ===== Config Card ===== */
list-style: disc inside; .config-card {
margin-top: 5px; background-color: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-lg);
margin-bottom: var(--spacing-lg);
overflow: hidden;
} }
#status-welcome-form, .config-card-header {
#status-goodbye-form, padding: var(--spacing-lg);
#status-autorole-form, border-bottom: 1px solid var(--border-color);
#status-autorole-vocal-form { display: flex;
margin-top: 10px; align-items: center;
justify-content: space-between;
}
.config-card-title {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
.config-card-title h3 {
font-size: 1.1rem;
font-weight: 600; font-weight: 600;
color: var(--text-primary);
} }
/* ===== Select multiple pour vocal ===== */ .config-card-title .icon {
select[multiple] { font-size: 1.25rem;
}
.config-card-body {
padding: var(--spacing-lg);
}
.config-card-footer {
padding: var(--spacing-md) var(--spacing-lg);
border-top: 1px solid var(--border-color);
background-color: var(--bg-dark);
display: flex;
align-items: center;
justify-content: space-between;
}
/* ===== Toggle Switch ===== */
.toggle-switch {
position: relative;
width: 48px;
height: 24px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.toggle-slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: var(--bg-dark);
border: 1px solid var(--border-color);
transition: var(--transition-fast);
border-radius: 24px;
}
.toggle-slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 2px;
bottom: 2px;
background-color: var(--text-muted);
transition: var(--transition-fast);
border-radius: 50%;
}
.toggle-switch input:checked + .toggle-slider {
background-color: var(--primary);
border-color: var(--primary);
}
.toggle-switch input:checked + .toggle-slider:before {
background-color: white;
transform: translateX(24px);
}
/* ===== Form Styles ===== */
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: var(--spacing-md);
}
.form-group {
margin-bottom: var(--spacing-md);
}
.form-group:last-child {
margin-bottom: 0;
}
.form-label {
display: block;
font-weight: 500;
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
font-size: 0.9rem;
}
.form-sublabel {
display: block;
color: var(--text-muted);
font-size: 0.8rem;
margin-top: 2px;
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
padding: var(--spacing-sm) var(--spacing-md);
background-color: var(--bg-dark);
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
color: var(--text-primary);
font-size: 0.9rem;
transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px var(--primary-light);
}
.form-textarea {
resize: vertical;
min-height: 100px;
}
.form-select[multiple] {
height: auto; height: auto;
min-height: 120px;
}
.form-hint {
margin-top: var(--spacing-xs);
font-size: 0.8rem;
color: var(--text-muted);
}
.form-hint code {
background-color: var(--bg-dark);
padding: 2px 6px;
border-radius: 4px;
font-family: monospace;
font-size: 0.85em;
}
/* ===== Inline Toggle ===== */
.inline-toggle {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--spacing-md);
background-color: var(--bg-dark);
border-radius: var(--border-radius);
margin-bottom: var(--spacing-md);
}
.inline-toggle:last-child {
margin-bottom: 0;
}
.inline-toggle-label {
display: flex;
flex-direction: column;
}
.inline-toggle-title {
font-weight: 500;
color: var(--text-primary);
font-size: 0.9rem;
}
.inline-toggle-desc {
color: var(--text-muted);
font-size: 0.8rem;
margin-top: 2px;
}
/* ===== Status Messages ===== */
.status-message {
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--border-radius);
font-size: 0.9rem;
font-weight: 500;
display: none;
}
.status-message.show {
display: block;
}
.status-message.success {
background-color: rgba(35, 134, 54, 0.15);
color: var(--success);
border: 1px solid var(--success);
}
.status-message.error {
background-color: rgba(248, 81, 73, 0.15);
color: var(--danger);
border: 1px solid var(--danger);
}
/* ===== Sub-sections ===== */
.sub-section {
margin-top: var(--spacing-lg);
padding-top: var(--spacing-lg);
border-top: 1px solid var(--border-color);
}
.sub-section-title {
font-size: 0.95rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-md);
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
/* ===== Variables Info Box ===== */
.variables-box {
background-color: var(--bg-dark);
border: 1px solid var(--border-color);
border-radius: var(--border-radius);
padding: var(--spacing-md);
margin-top: var(--spacing-md);
}
.variables-box-title {
font-size: 0.85rem;
font-weight: 600;
color: var(--text-secondary);
margin-bottom: var(--spacing-sm);
}
.variables-list {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
}
.variable-tag {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
background-color: var(--bg-card);
border-radius: 4px;
font-size: 0.8rem;
}
.variable-tag code {
color: var(--primary);
font-family: monospace;
}
.variable-tag span {
color: var(--text-muted);
}
/* ===== Mobile Sidebar Toggle ===== */
.mobile-toggle {
display: none;
position: fixed;
bottom: var(--spacing-lg);
right: var(--spacing-lg);
width: 56px;
height: 56px;
background-color: var(--primary);
border: none;
border-radius: 50%;
color: white;
font-size: 24px;
cursor: pointer;
box-shadow: var(--shadow-lg);
z-index: 200;
} }
/* ===== Responsive ===== */ /* ===== Responsive ===== */
@media (max-width: 600px) { @media (max-width: 900px) {
body { .sidebar {
padding: 10px; transform: translateX(-100%);
transition: transform var(--transition-normal);
} }
form { .sidebar.open {
padding: 15px; transform: translateX(0);
}
.main-content {
margin-left: 0;
}
.mobile-toggle {
display: flex;
align-items: center;
justify-content: center;
}
.content-area {
padding: var(--spacing-md);
}
}
@media (max-width: 600px) {
.form-row {
grid-template-columns: 1fr;
}
.config-card-header {
flex-direction: column;
align-items: flex-start;
gap: var(--spacing-md);
} }
} }
+574 -417
View File
File diff suppressed because it is too large Load Diff
+28 -16
View File
@@ -1,8 +1,8 @@
const autoroleNewUserForm = document.getElementById("autorole-newuser-form");
const autoroleEnabled = document.getElementById("autorole-enabled"); const autoroleEnabled = document.getElementById("autorole-enabled");
const autoroleRole = document.getElementById("autorole-role"); const autoroleRole = document.getElementById("autorole-role");
const statusAutoroleForm = document.getElementById("status-autorole-form"); const saveAutorole = document.getElementById("save-autorole");
// Charger la config
fetch(`/api/bot/get-autorole-newuser-config/${guildId}`) fetch(`/api/bot/get-autorole-newuser-config/${guildId}`)
.then(res => res.json()) .then(res => res.json())
.then(cfg => { .then(cfg => {
@@ -10,20 +10,32 @@ fetch(`/api/bot/get-autorole-newuser-config/${guildId}`)
autoroleRole.value = cfg.roleId; autoroleRole.value = cfg.roleId;
}); });
autoroleNewUserForm.addEventListener("submit", async e => { // Sauvegarder
e.preventDefault(); saveAutorole.addEventListener("click", async () => {
saveAutorole.disabled = true;
saveAutorole.textContent = "Sauvegarde...";
const res = await fetch("/api/bot/save-autorole-newuser-config", { try {
method: "POST", const res = await fetch("/api/bot/save-autorole-newuser-config", {
headers: { "Content-Type": "application/json" }, method: "POST",
body: JSON.stringify({ headers: { "Content-Type": "application/json" },
guildId, body: JSON.stringify({
enabled: autoroleEnabled.checked, guildId,
roleId: autoroleRole.value enabled: autoroleEnabled.checked,
}) roleId: autoroleRole.value
}); })
});
statusAutoroleForm.textContent = (await res.json()).success const data = await res.json();
? "Auto-rôle sauvegardé ✅" if (data.success) {
: "Erreur ❌"; showStatus("status-autorole-form", "Configuration sauvegardée ✅", "success");
} else {
showStatus("status-autorole-form", "Erreur lors de la sauvegarde ❌", "error");
}
} catch (error) {
showStatus("status-autorole-form", "Erreur de connexion ❌", "error");
}
saveAutorole.disabled = false;
saveAutorole.textContent = "Sauvegarder";
}); });
+41 -34
View File
@@ -1,43 +1,50 @@
const autoroleVocalForm = document.getElementById("autorole-vocal-form");
const autoroleVocalEnabled = document.getElementById("autorole-vocal-enabled"); const autoroleVocalEnabled = document.getElementById("autorole-vocal-enabled");
const autoroleVocalRole = document.getElementById("autorole-vocal-role"); const autoroleVocalRole = document.getElementById("autorole-vocal-role");
const excludeSelect = document.getElementById("autorole-vocal-exclude-channel"); const excludeSelect = document.getElementById("autorole-vocal-exclude-channel");
const statusAutoroleVocalForm = document.getElementById("status-autorole-vocal-form"); const saveAutoroleVocal = document.getElementById("save-autorole-vocal");
fetch(`/api/bot/get-voice-channels/${guildId}`) // Charger la config après que les channels soient chargés
.then(res => res.json()) setTimeout(() => {
.then(channels => { fetch(`/api/bot/get-autorole-vocal-config/${guildId}`)
channels.forEach(c => { .then(res => res.json())
excludeSelect.appendChild(new Option(`#${c.name}`, c.id)); .then(cfg => {
autoroleVocalEnabled.checked = cfg.enabled;
autoroleVocalRole.value = cfg.roleId;
Array.from(excludeSelect.options).forEach(opt => {
opt.selected = cfg.excludeChannelIds?.includes(opt.value);
});
}); });
return fetch(`/api/bot/get-autorole-vocal-config/${guildId}`); }, 500);
})
.then(res => res.json()) // Sauvegarder
.then(cfg => { saveAutoroleVocal.addEventListener("click", async () => {
autoroleVocalEnabled.checked = cfg.enabled; saveAutoroleVocal.disabled = true;
autoroleVocalRole.value = cfg.roleId; saveAutoroleVocal.textContent = "Sauvegarde...";
Array.from(excludeSelect.options).forEach(opt => {
opt.selected = cfg.excludeChannelIds?.includes(opt.value); try {
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
})
}); });
});
autoroleVocalForm.addEventListener("submit", async e => { const data = await res.json();
e.preventDefault(); if (data.success) {
showStatus("status-autorole-vocal-form", "Configuration sauvegardée ✅", "success");
} else {
showStatus("status-autorole-vocal-form", "Erreur lors de la sauvegarde ❌", "error");
}
} catch (error) {
showStatus("status-autorole-vocal-form", "Erreur de connexion ❌", "error");
}
const exclude = Array.from(excludeSelect.selectedOptions).map(o => o.value); saveAutoroleVocal.disabled = false;
saveAutoroleVocal.textContent = "Sauvegarder";
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 ❌";
}); });
+60 -50
View File
@@ -1,4 +1,3 @@
const economyForm = document.getElementById("economy-form");
const economyEnabled = document.getElementById("economy-enabled"); const economyEnabled = document.getElementById("economy-enabled");
const currencyName = document.getElementById("economy-currency-name"); const currencyName = document.getElementById("economy-currency-name");
const currencySymbol = document.getElementById("economy-currency-symbol"); const currencySymbol = document.getElementById("economy-currency-symbol");
@@ -42,7 +41,7 @@ const voiceMoneyMin = document.getElementById("economy-voice-money-min");
const voiceMoneyMax = document.getElementById("economy-voice-money-max"); const voiceMoneyMax = document.getElementById("economy-voice-money-max");
const voiceMoneyInterval = document.getElementById("economy-voice-money-interval"); const voiceMoneyInterval = document.getElementById("economy-voice-money-interval");
const statusEconomyForm = document.getElementById("status-economy-form"); const saveEconomy = document.getElementById("save-economy");
// Charger la config existante // Charger la config existante
fetch(`/api/bot/get-economy-config/${guildId}`) fetch(`/api/bot/get-economy-config/${guildId}`)
@@ -94,60 +93,71 @@ fetch(`/api/bot/get-economy-config/${guildId}`)
.catch(console.error); .catch(console.error);
// Sauvegarder la config // Sauvegarder la config
economyForm.addEventListener("submit", async e => { saveEconomy.addEventListener("click", async () => {
e.preventDefault(); saveEconomy.disabled = true;
saveEconomy.textContent = "Sauvegarde...";
const res = await fetch("/api/bot/save-economy-config", { try {
method: "POST", const res = await fetch("/api/bot/save-economy-config", {
headers: { "Content-Type": "application/json" }, method: "POST",
body: JSON.stringify({ headers: { "Content-Type": "application/json" },
guildId, body: JSON.stringify({
economyEnabled: economyEnabled.checked, guildId,
currencyName: currencyName.value, economyEnabled: economyEnabled.checked,
currencySymbol: currencySymbol.value, currencyName: currencyName.value,
startingBalance: parseInt(startingBalance.value, 10), currencySymbol: currencySymbol.value,
startingBalance: parseInt(startingBalance.value, 10),
// Daily // Daily
dailyEnabled: dailyEnabled.checked, dailyEnabled: dailyEnabled.checked,
dailyAmount: parseInt(dailyAmount.value, 10), dailyAmount: parseInt(dailyAmount.value, 10),
dailyCooldownHours: parseInt(dailyCooldown.value, 10), dailyCooldownHours: parseInt(dailyCooldown.value, 10),
// Work // Work
workEnabled: workEnabled.checked, workEnabled: workEnabled.checked,
workMinAmount: parseInt(workMin.value, 10), workMinAmount: parseInt(workMin.value, 10),
workMaxAmount: parseInt(workMax.value, 10), workMaxAmount: parseInt(workMax.value, 10),
workCooldownMinutes: parseInt(workCooldown.value, 10), workCooldownMinutes: parseInt(workCooldown.value, 10),
// Crime // Crime
crimeEnabled: crimeEnabled.checked, crimeEnabled: crimeEnabled.checked,
crimeMinAmount: parseInt(crimeMin.value, 10), crimeMinAmount: parseInt(crimeMin.value, 10),
crimeMaxAmount: parseInt(crimeMax.value, 10), crimeMaxAmount: parseInt(crimeMax.value, 10),
crimeSuccessRate: parseInt(crimeSuccess.value, 10), crimeSuccessRate: parseInt(crimeSuccess.value, 10),
crimeFinePercent: parseInt(crimeFine.value, 10), crimeFinePercent: parseInt(crimeFine.value, 10),
crimeCooldownMinutes: parseInt(crimeCooldown.value, 10), crimeCooldownMinutes: parseInt(crimeCooldown.value, 10),
// Steal // Steal
stealEnabled: stealEnabled.checked, stealEnabled: stealEnabled.checked,
stealSuccessRate: parseInt(stealSuccess.value, 10), stealSuccessRate: parseInt(stealSuccess.value, 10),
stealMaxPercent: parseInt(stealMaxPercent.value, 10), stealMaxPercent: parseInt(stealMaxPercent.value, 10),
stealFinePercent: parseInt(stealFine.value, 10), stealFinePercent: parseInt(stealFine.value, 10),
stealCooldownMinutes: parseInt(stealCooldown.value, 10), stealCooldownMinutes: parseInt(stealCooldown.value, 10),
// Message Money // Message Money
messageMoneyEnabled: messageMoneyEnabled.checked, messageMoneyEnabled: messageMoneyEnabled.checked,
messageMoneyMin: parseInt(messageMoneyMin.value, 10), messageMoneyMin: parseInt(messageMoneyMin.value, 10),
messageMoneyMax: parseInt(messageMoneyMax.value, 10), messageMoneyMax: parseInt(messageMoneyMax.value, 10),
messageMoneyCooldownSeconds: parseInt(messageMoneyCooldown.value, 10), messageMoneyCooldownSeconds: parseInt(messageMoneyCooldown.value, 10),
// Voice Money // Voice Money
voiceMoneyEnabled: voiceMoneyEnabled.checked, voiceMoneyEnabled: voiceMoneyEnabled.checked,
voiceMoneyMin: parseInt(voiceMoneyMin.value, 10), voiceMoneyMin: parseInt(voiceMoneyMin.value, 10),
voiceMoneyMax: parseInt(voiceMoneyMax.value, 10), voiceMoneyMax: parseInt(voiceMoneyMax.value, 10),
voiceMoneyIntervalMinutes: parseInt(voiceMoneyInterval.value, 10) voiceMoneyIntervalMinutes: parseInt(voiceMoneyInterval.value, 10)
}) })
}); });
statusEconomyForm.textContent = (await res.json()).success const data = await res.json();
? "Config économie sauvegardée ✅" if (data.success) {
: "Erreur ❌"; showStatus("status-economy-form", "Configuration sauvegardée ✅", "success");
} else {
showStatus("status-economy-form", "Erreur lors de la sauvegarde ❌", "error");
}
} catch (error) {
showStatus("status-economy-form", "Erreur de connexion ❌", "error");
}
saveEconomy.disabled = false;
saveEconomy.textContent = "Sauvegarder";
}); });
+33 -18
View File
@@ -1,32 +1,47 @@
const goodbyeForm = document.getElementById("goodbye-form");
const goodbyeEnabled = document.getElementById("goodbye-enabled"); const goodbyeEnabled = document.getElementById("goodbye-enabled");
const goodbyeChannel = document.getElementById("goodbye-channel"); const goodbyeChannel = document.getElementById("goodbye-channel");
const goodbyeMessage = document.getElementById("goodbye-message"); const goodbyeMessage = document.getElementById("goodbye-message");
const statusGoodbyeForm = document.getElementById("status-goodbye-form"); const saveGoodbye = document.getElementById("save-goodbye");
// Message par défaut
const defaultGoodbyeMessage = "Au revoir **{user}**, on espère te revoir sur **{server}** ! 👋";
// Charger la config
fetch(`/api/bot/get-goodbye-config/${guildId}`) fetch(`/api/bot/get-goodbye-config/${guildId}`)
.then(res => res.json()) .then(res => res.json())
.then(cfg => { .then(cfg => {
goodbyeEnabled.checked = cfg.enabled; goodbyeEnabled.checked = cfg.enabled;
goodbyeChannel.value = cfg.channelId; goodbyeChannel.value = cfg.channelId;
goodbyeMessage.value = cfg.message; goodbyeMessage.value = cfg.message || defaultGoodbyeMessage;
}); });
goodbyeForm.addEventListener("submit", async e => { // Sauvegarder
e.preventDefault(); saveGoodbye.addEventListener("click", async () => {
saveGoodbye.disabled = true;
saveGoodbye.textContent = "Sauvegarde...";
const res = await fetch("/api/bot/save-goodbye-config", { try {
method: "POST", const res = await fetch("/api/bot/save-goodbye-config", {
headers: { "Content-Type": "application/json" }, method: "POST",
body: JSON.stringify({ headers: { "Content-Type": "application/json" },
guildId, body: JSON.stringify({
goodbyeEnabled: goodbyeEnabled.checked, guildId,
channelId: goodbyeChannel.value, goodbyeEnabled: goodbyeEnabled.checked,
goodbyeMessage: goodbyeMessage.value channelId: goodbyeChannel.value,
}) goodbyeMessage: goodbyeMessage.value
}); })
});
statusGoodbyeForm.textContent = (await res.json()).success const data = await res.json();
? "Config au revoir sauvegardée ✅" if (data.success) {
: "Erreur ❌"; showStatus("status-goodbye-form", "Configuration sauvegardée ✅", "success");
} else {
showStatus("status-goodbye-form", "Erreur lors de la sauvegarde ❌", "error");
}
} catch (error) {
showStatus("status-goodbye-form", "Erreur de connexion ❌", "error");
}
saveGoodbye.disabled = false;
saveGoodbye.textContent = "Sauvegarder";
}); });
+62 -3
View File
@@ -1,12 +1,55 @@
window.guildId = window.location.pathname.split("/")[2]; window.guildId = window.location.pathname.split("/")[2];
// Nom du serveur // Charger les infos du bot
fetch("/api/bot-info")
.then(res => res.json())
.then(bot => {
const botAvatar = document.getElementById("bot-avatar");
if (botAvatar) {
botAvatar.src = `https://cdn.discordapp.com/avatars/${bot.id}/${bot.avatar}.png`;
}
})
.catch(() => {});
// Charger les infos utilisateur
fetch("/api/user")
.then(res => {
if (!res.ok) throw new Error("Non connecté");
return res.json();
})
.then(user => {
const avatarUrl = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`;
const userAvatar = document.getElementById("user-avatar");
const userName = document.getElementById("user-name");
if (userAvatar) userAvatar.src = avatarUrl;
if (userName) userName.textContent = user.username;
})
.catch(() => {
window.location.href = "/auth/login";
});
// Nom et icône du serveur
fetch("/api/guilds") fetch("/api/guilds")
.then(res => res.json()) .then(res => res.json())
.then(guilds => { .then(guilds => {
const guild = guilds.find(g => g.id === guildId); const guild = guilds.find(g => g.id === guildId);
document.getElementById("guild-name").textContent = const guildName = document.getElementById("guild-name");
guild ? `Dashboard : ${guild.name}` : "Serveur introuvable"; const guildIcon = document.getElementById("guild-icon");
const guildIdEl = document.getElementById("guild-id");
const breadcrumbGuild = document.getElementById("breadcrumb-guild");
if (guild) {
if (guildName) guildName.textContent = guild.name;
if (breadcrumbGuild) breadcrumbGuild.textContent = guild.name;
if (guildIdEl) guildIdEl.textContent = `ID: ${guild.id}`;
if (guildIcon && guild.icon) {
guildIcon.src = `https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png`;
}
document.title = `${guild.name} - LazyBot`;
} else {
if (guildName) guildName.textContent = "Serveur introuvable";
}
}); });
// Channels texte // Channels texte
@@ -16,12 +59,26 @@ fetch(`/api/bot/get-text-channels/${guildId}`)
const welcome = document.getElementById("welcome-channel"); const welcome = document.getElementById("welcome-channel");
const goodbye = document.getElementById("goodbye-channel"); const goodbye = document.getElementById("goodbye-channel");
const levelAnnouncements = document.getElementById("level-announcements-channel"); const levelAnnouncements = document.getElementById("level-announcements-channel");
const levelChannelRestrict = document.getElementById("level-channel-with-or-without-xp");
channels.forEach(c => { channels.forEach(c => {
const opt = new Option(`#${c.name}`, c.id); const opt = new Option(`#${c.name}`, c.id);
welcome?.appendChild(opt); welcome?.appendChild(opt);
goodbye?.appendChild(opt.cloneNode(true)); goodbye?.appendChild(opt.cloneNode(true));
levelAnnouncements?.appendChild(opt.cloneNode(true)); levelAnnouncements?.appendChild(opt.cloneNode(true));
levelChannelRestrict?.appendChild(opt.cloneNode(true));
});
});
// Channels vocaux
fetch(`/api/bot/get-voice-channels/${guildId}`)
.then(res => res.json())
.then(channels => {
const vocalExclude = document.getElementById("autorole-vocal-exclude-channel");
channels.forEach(c => {
const opt = new Option(`🔊 ${c.name}`, c.id);
vocalExclude?.appendChild(opt);
}); });
}); });
@@ -31,10 +88,12 @@ fetch(`/api/bot/get-roles/${guildId}`)
.then(roles => { .then(roles => {
const newUser = document.getElementById("autorole-role"); const newUser = document.getElementById("autorole-role");
const vocal = document.getElementById("autorole-vocal-role"); const vocal = document.getElementById("autorole-vocal-role");
const levelRoleRestrict = document.getElementById("level-role-with-or-without-xp");
roles.forEach(r => { roles.forEach(r => {
const opt = new Option(r.name, r.id); const opt = new Option(r.name, r.id);
newUser?.appendChild(opt); newUser?.appendChild(opt);
vocal?.appendChild(opt.cloneNode(true)); vocal?.appendChild(opt.cloneNode(true));
levelRoleRestrict?.appendChild(opt.cloneNode(true));
}); });
}); });
+78 -90
View File
@@ -1,4 +1,3 @@
const levelForm = document.getElementById("level-form");
const levelEnabled = document.getElementById("level-enabled"); const levelEnabled = document.getElementById("level-enabled");
const levelAnnouncementsEnabled = document.getElementById("level-announcement-enabled"); const levelAnnouncementsEnabled = document.getElementById("level-announcement-enabled");
const levelAnnouncementsChannel = document.getElementById("level-announcements-channel"); const levelAnnouncementsChannel = document.getElementById("level-announcements-channel");
@@ -18,102 +17,91 @@ const cooldownXpMessageSeconds = document.getElementById("level-xp-cooldown");
const gainXpOnVoice = document.getElementById("voice-xp-enabled"); const gainXpOnVoice = document.getElementById("voice-xp-enabled");
const gainVoiceXpLowerBound = document.getElementById("level-xp-per-voice-min"); const gainVoiceXpLowerBound = document.getElementById("level-xp-per-voice-min");
const gainVoiceXpUpperBound = document.getElementById("level-xp-per-voice-max"); const gainVoiceXpUpperBound = document.getElementById("level-xp-per-voice-max");
const statusLevelForm = document.getElementById("status-level-form"); const saveLevel = document.getElementById("save-level");
// 1️⃣ RÔLES // Message par défaut
fetch(`/api/bot/get-roles/${guildId}`) const defaultLevelMessage = "Félicitations {mention}, tu es maintenant niveau **{level}** ! 🎉";
.then(res => res.json())
.then(roles => {
roles.forEach(r => {
roleWithWithoutXp?.appendChild(new Option(r.name, r.id));
});
// 2️⃣ SALONS TEXTE // Charger la config après que les channels/roles soient chargés
return fetch(`/api/bot/get-text-channels/${guildId}`); setTimeout(() => {
}) fetch(`/api/bot/get-level-config/${guildId}`)
.then(res => res.json()) .then(res => res.json())
.then(textSalons => { .then(cfg => {
textSalons.forEach(c => { levelEnabled.checked = cfg.enabled;
salonWithWithoutXp?.appendChild(new Option(`#${c.name}`, c.id)); levelAnnouncementsEnabled.checked = cfg.levelAnnouncementsEnabled;
}); levelAnnouncementsChannel.value = cfg.levelAnnouncementsChannelId;
levelAnnouncementsMessage.value = cfg.levelAnnouncementsMessage || defaultLevelMessage;
// 3️⃣ SALONS VOCAUX xpCourbeType.value = cfg.xpCourbeType;
return fetch(`/api/bot/get-voice-channels/${guildId}`); multiplierCourbeForLevel.value = cfg.multiplierCourbeForLevel;
}) levelAnnouncementEveryLevel.value = cfg.levelAnnouncementEveryLevel;
.then(res => res.json()) levelMax.value = cfg.levelMax;
.then(voiceSalons => {
voiceSalons.forEach(c => {
salonWithWithoutXp?.appendChild(new Option(`🔊 ${c.name}`, c.id));
});
// 4️⃣ CONFIG (APRÈS QUE TOUT EST CHARGÉ) roleWithWithoutType.value = cfg.roleWithWithoutType;
return fetch(`/api/bot/get-level-config/${guildId}`); Array.from(roleWithWithoutXp.options).forEach(opt => {
}) opt.selected = cfg.roleWithWithoutXp?.includes(opt.value);
.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; salonWithWithoutType.value = cfg.salonWithWithoutType;
multiplierCourbeForLevel.value = cfg.multiplierCourbeForLevel; Array.from(salonWithWithoutXp.options).forEach(opt => {
levelAnnouncementEveryLevel.value = cfg.levelAnnouncementEveryLevel; opt.selected = cfg.salonWithWithoutXp?.includes(opt.value);
levelMax.value = cfg.levelMax; });
roleWithWithoutType.value = cfg.roleWithWithoutType; gainXpOnMessage.checked = cfg.gainXpOnMessage;
Array.from(roleWithWithoutXp.options).forEach(opt => { gainXpMessageLowerBound.value = cfg.gainXpMessageLowerBound;
opt.selected = cfg.roleWithWithoutXp?.includes(opt.value); gainXpMessageUpperBound.value = cfg.gainXpMessageUpperBound;
}); cooldownXpMessageSeconds.value = cfg.cooldownXpMessageSeconds;
salonWithWithoutType.value = cfg.salonWithWithoutType; gainXpOnVoice.checked = cfg.gainXpOnVoice;
Array.from(salonWithWithoutXp.options).forEach(opt => { gainVoiceXpLowerBound.value = cfg.gainVoiceXpLowerBound;
opt.selected = cfg.salonWithWithoutXp?.includes(opt.value); gainVoiceXpUpperBound.value = cfg.gainVoiceXpUpperBound;
});
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)
}) })
}); .catch(console.error);
}, 500);
statusLevelForm.textContent = (await res.json()).success // Sauvegarder
? "Config niveaux sauvegardée ✅" saveLevel.addEventListener("click", async () => {
: "Erreur ❌"; saveLevel.disabled = true;
saveLevel.textContent = "Sauvegarde...";
try {
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)
})
});
const data = await res.json();
if (data.success) {
showStatus("status-level-form", "Configuration sauvegardée ✅", "success");
} else {
showStatus("status-level-form", "Erreur lors de la sauvegarde ❌", "error");
}
} catch (error) {
showStatus("status-level-form", "Erreur de connexion ❌", "error");
}
saveLevel.disabled = false;
saveLevel.textContent = "Sauvegarder";
}); });
+100
View File
@@ -0,0 +1,100 @@
// Navigation sidebar avec hash routing
document.addEventListener('DOMContentLoaded', () => {
const navItems = document.querySelectorAll('.nav-item[data-section]');
const sections = document.querySelectorAll('.config-section');
const sidebar = document.getElementById('sidebar');
const mobileToggle = document.getElementById('mobile-toggle');
// Fonction pour changer de section
function showSection(sectionId) {
// Masquer toutes les sections
sections.forEach(section => {
section.classList.remove('active');
});
// Retirer la classe active de tous les nav items
navItems.forEach(item => {
item.classList.remove('active');
});
// Afficher la section cible
const targetSection = document.getElementById(`section-${sectionId}`);
if (targetSection) {
targetSection.classList.add('active');
}
// Activer le nav item correspondant
const targetNav = document.querySelector(`.nav-item[data-section="${sectionId}"]`);
if (targetNav) {
targetNav.classList.add('active');
}
// Fermer la sidebar sur mobile
if (window.innerWidth <= 900) {
sidebar.classList.remove('open');
}
// Mettre à jour l'URL
window.location.hash = sectionId;
}
// Ajouter les listeners sur les nav items
navItems.forEach(item => {
item.addEventListener('click', (e) => {
e.preventDefault();
const sectionId = item.dataset.section;
showSection(sectionId);
});
});
// Gérer le hash au chargement de la page
function handleHash() {
const hash = window.location.hash.slice(1); // Enlever le #
if (hash && document.getElementById(`section-${hash}`)) {
showSection(hash);
} else {
// Par défaut, afficher la première section
const firstNavItem = navItems[0];
if (firstNavItem) {
showSection(firstNavItem.dataset.section);
}
}
}
// Écouter les changements de hash
window.addEventListener('hashchange', handleHash);
// Charger la section initiale
handleHash();
// Toggle sidebar sur mobile
if (mobileToggle) {
mobileToggle.addEventListener('click', () => {
sidebar.classList.toggle('open');
});
}
// Fermer la sidebar en cliquant à l'extérieur sur mobile
document.addEventListener('click', (e) => {
if (window.innerWidth <= 900) {
if (!sidebar.contains(e.target) && !mobileToggle.contains(e.target)) {
sidebar.classList.remove('open');
}
}
});
});
// Helper function pour afficher les messages de statut
window.showStatus = function(elementId, message, type = 'success') {
const element = document.getElementById(elementId);
if (element) {
element.textContent = message;
element.className = `status-message show ${type}`;
// Masquer après 5 secondes
setTimeout(() => {
element.classList.remove('show');
}, 5000);
}
};
+33 -18
View File
@@ -1,32 +1,47 @@
const welcomeForm = document.getElementById("welcome-form");
const welcomeEnabled = document.getElementById("welcome-enabled"); const welcomeEnabled = document.getElementById("welcome-enabled");
const welcomeChannel = document.getElementById("welcome-channel"); const welcomeChannel = document.getElementById("welcome-channel");
const welcomeMessage = document.getElementById("welcome-message"); const welcomeMessage = document.getElementById("welcome-message");
const statusWelcomeForm = document.getElementById("status-welcome-form"); const saveWelcome = document.getElementById("save-welcome");
// Message par défaut
const defaultWelcomeMessage = "Bienvenue {mention} sur **{server}** ! 🎉";
// Charger la config
fetch(`/api/bot/get-welcome-config/${guildId}`) fetch(`/api/bot/get-welcome-config/${guildId}`)
.then(res => res.json()) .then(res => res.json())
.then(cfg => { .then(cfg => {
welcomeEnabled.checked = cfg.enabled; welcomeEnabled.checked = cfg.enabled;
welcomeChannel.value = cfg.channelId; welcomeChannel.value = cfg.channelId;
welcomeMessage.value = cfg.message; welcomeMessage.value = cfg.message || defaultWelcomeMessage;
}); });
welcomeForm.addEventListener("submit", async e => { // Sauvegarder
e.preventDefault(); saveWelcome.addEventListener("click", async () => {
saveWelcome.disabled = true;
saveWelcome.textContent = "Sauvegarde...";
const res = await fetch("/api/bot/save-welcome-config", { try {
method: "POST", const res = await fetch("/api/bot/save-welcome-config", {
headers: { "Content-Type": "application/json" }, method: "POST",
body: JSON.stringify({ headers: { "Content-Type": "application/json" },
guildId, body: JSON.stringify({
welcomeEnabled: welcomeEnabled.checked, guildId,
channelId: welcomeChannel.value, welcomeEnabled: welcomeEnabled.checked,
welcomeMessage: welcomeMessage.value channelId: welcomeChannel.value,
}) welcomeMessage: welcomeMessage.value
}); })
});
statusWelcomeForm.textContent = (await res.json()).success const data = await res.json();
? "Config bienvenue sauvegardée ✅" if (data.success) {
: "Erreur ❌"; showStatus("status-welcome-form", "Configuration sauvegardée ✅", "success");
} else {
showStatus("status-welcome-form", "Erreur lors de la sauvegarde ❌", "error");
}
} catch (error) {
showStatus("status-welcome-form", "Erreur de connexion ❌", "error");
}
saveWelcome.disabled = false;
saveWelcome.textContent = "Sauvegarder";
}); });
+362 -99
View File
@@ -1,130 +1,393 @@
/* ===== Reset minimal ===== */ @import url('global.css');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}
body { /* ===== Hero Section ===== */
background-color: #1e1f29; /* fond sombre type Discord */ .hero {
color: #ffffff;
line-height: 1.6;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; /* centre horizontalement */ align-items: center;
justify-content: flex-start; /* commencer depuis le haut */ justify-content: center;
padding: 20px; text-align: center;
padding: var(--spacing-2xl) var(--spacing-md);
min-height: 70vh;
background: linear-gradient(135deg, var(--bg-dark) 0%, #0a1628 50%, var(--bg-dark) 100%);
position: relative;
overflow: hidden;
}
.hero::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(circle at 50% 50%, rgba(88, 101, 242, 0.1) 0%, transparent 50%);
pointer-events: none;
}
.hero-content {
position: relative;
z-index: 1;
max-width: 800px;
}
.hero-logo {
width: 120px;
height: 120px;
border-radius: 50%;
margin-bottom: var(--spacing-lg);
border: 4px solid var(--primary);
box-shadow: var(--shadow-lg), 0 0 40px rgba(88, 101, 242, 0.3);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { box-shadow: var(--shadow-lg), 0 0 40px rgba(88, 101, 242, 0.3); }
50% { box-shadow: var(--shadow-lg), 0 0 60px rgba(88, 101, 242, 0.5); }
}
.hero-title {
font-size: 3.5rem;
font-weight: 800;
margin-bottom: var(--spacing-md);
background: linear-gradient(135deg, var(--text-primary) 0%, var(--primary) 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-subtitle {
font-size: 1.25rem;
color: var(--text-secondary);
margin-bottom: var(--spacing-xl);
max-width: 600px;
line-height: 1.6;
}
.hero-buttons {
display: flex;
gap: var(--spacing-md);
flex-wrap: wrap;
justify-content: center;
} }
/* ===== Navigation ===== */ /* ===== Navigation ===== */
nav { .navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
background-color: rgba(22, 27, 34, 0.95);
backdrop-filter: blur(10px);
border-bottom: 1px solid var(--border-color);
padding: var(--spacing-md) 0;
}
.navbar-container {
display: flex; display: flex;
align-items: center;
justify-content: space-between; justify-content: space-between;
align-items: center; max-width: 1200px;
background-color: #2f3136; margin: 0 auto;
padding: 10px 20px; padding: 0 var(--spacing-md);
border-radius: 10px;
margin-bottom: 30px;
width: 100%;
max-width: 800px; /* limite largeur nav */
} }
nav a { .navbar-brand {
color: #ffffff;
text-decoration: none;
margin-right: 15px;
font-weight: 500;
transition: color 0.2s;
}
nav a:hover {
color: #5865f2;
}
#profil {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: var(--spacing-sm);
font-size: 1.25rem;
font-weight: 700;
color: var(--text-primary);
text-decoration: none;
} }
#profil img { .navbar-brand:hover {
width: 40px; color: var(--text-primary);
height: 40px; text-decoration: none;
}
.navbar-brand img {
width: 32px;
height: 32px;
border-radius: 50%; border-radius: 50%;
} }
#logout { .navbar-nav {
background-color: #5865f2; display: flex;
color: white; align-items: center;
padding: 5px 12px; gap: var(--spacing-lg);
border-radius: 6px; }
.navbar-link {
color: var(--text-secondary);
font-weight: 500;
text-decoration: none;
transition: color var(--transition-fast);
}
.navbar-link:hover {
color: var(--text-primary);
text-decoration: none;
}
.navbar-user {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-xs) var(--spacing-sm);
background-color: var(--bg-card);
border-radius: var(--border-radius);
border: 1px solid var(--border-color);
}
.navbar-user img {
width: 28px;
height: 28px;
border-radius: 50%;
}
.navbar-user span {
color: var(--text-primary);
font-weight: 500;
}
.navbar-actions {
display: flex;
align-items: center;
gap: var(--spacing-sm);
}
/* ===== Features Section ===== */
.features {
padding: var(--spacing-2xl) var(--spacing-md);
background-color: var(--bg-card);
}
.section-header {
text-align: center;
margin-bottom: var(--spacing-2xl);
}
.section-header h2 {
font-size: 2.25rem;
margin-bottom: var(--spacing-sm);
color: var(--text-primary);
}
.section-header p {
font-size: 1.1rem;
color: var(--text-secondary);
max-width: 600px;
margin: 0 auto;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: var(--spacing-lg);
max-width: 1200px;
margin: 0 auto;
}
.feature-card {
background-color: var(--bg-dark);
border: 1px solid var(--border-color);
border-radius: var(--border-radius-lg);
padding: var(--spacing-xl);
transition: transform var(--transition-normal), border-color var(--transition-normal), box-shadow var(--transition-normal);
}
.feature-card:hover {
transform: translateY(-4px);
border-color: var(--primary);
box-shadow: 0 8px 30px rgba(88, 101, 242, 0.15);
}
.feature-icon {
width: 56px;
height: 56px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, var(--primary), #7289da);
border-radius: var(--border-radius);
font-size: 28px;
margin-bottom: var(--spacing-md);
}
.feature-card h3 {
font-size: 1.25rem;
margin-bottom: var(--spacing-sm);
color: var(--text-primary);
}
.feature-card p {
font-size: 0.95rem;
color: var(--text-secondary);
line-height: 1.6;
}
/* ===== Stats Section ===== */
.stats {
padding: var(--spacing-2xl) var(--spacing-md);
text-align: center;
background-color: var(--bg-dark);
}
.stats-grid {
display: flex;
justify-content: center;
gap: var(--spacing-2xl);
flex-wrap: wrap;
max-width: 800px;
margin: 0 auto;
}
.stat-item {
display: flex;
flex-direction: column;
align-items: center;
padding: var(--spacing-lg);
}
.stat-number {
font-size: 3.5rem;
font-weight: 800;
background: linear-gradient(135deg, var(--primary), #7289da);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
line-height: 1;
}
.stat-label {
color: var(--text-secondary);
font-size: 1.1rem;
margin-top: var(--spacing-sm);
font-weight: 500;
}
/* ===== CTA Section ===== */
.cta {
padding: var(--spacing-2xl) var(--spacing-md);
text-align: center;
background: linear-gradient(135deg, rgba(88, 101, 242, 0.1) 0%, transparent 100%);
}
.cta h2 {
font-size: 2rem;
margin-bottom: var(--spacing-md);
color: var(--text-primary);
}
.cta p {
font-size: 1.1rem;
color: var(--text-secondary);
margin-bottom: var(--spacing-xl);
}
/* ===== Footer ===== */
.footer {
padding: var(--spacing-xl) var(--spacing-md);
border-top: 1px solid var(--border-color);
text-align: center;
background-color: var(--bg-card);
}
.footer-content {
max-width: 1200px;
margin: 0 auto;
display: flex;
flex-direction: column;
align-items: center;
gap: var(--spacing-md);
}
.footer-links {
display: flex;
gap: var(--spacing-lg);
}
.footer-links a {
color: var(--text-secondary);
text-decoration: none;
transition: color var(--transition-fast);
}
.footer-links a:hover {
color: var(--primary);
}
.footer-copyright {
color: var(--text-muted);
font-size: 0.9rem; font-size: 0.9rem;
transition: background 0.2s;
} }
#logout:hover { /* ===== Login Button ===== */
background-color: #4752c4; .btn-login {
} display: inline-flex;
align-items: center;
/* ===== Titres ===== */ gap: var(--spacing-sm);
h1, h2 { background-color: var(--primary);
text-align: center; /* centre le texte */
}
h1 {
font-size: 3rem;
margin-bottom: 20px;
color: #ffffff;
}
h2 {
font-size: 1.8rem;
margin: 25px 0 10px 0;
color: #ffffff;
border-bottom: 2px solid #5865f2;
display: inline-block;
padding-bottom: 5px;
}
/* ===== Bouton d'invitation ===== */
#invite-link {
display: inline-block;
background-color: #5865f2;
color: white; color: white;
padding: 10px 20px; padding: var(--spacing-sm) var(--spacing-md);
border-radius: 8px; border-radius: var(--border-radius);
text-decoration: none; text-decoration: none;
font-weight: 600; font-weight: 600;
transition: background 0.2s; transition: background-color var(--transition-fast);
margin-bottom: 20px;
} }
#invite-link:hover { .btn-login:hover {
background-color: #4752c4; background-color: var(--primary-hover);
} color: white;
text-decoration: none;
/* ===== Listes ===== */
ul {
list-style: disc inside;
margin: 10px 0 20px 0;
max-width: 700px;
text-align: left;
}
li {
margin-bottom: 10px;
} }
/* ===== Responsive ===== */ /* ===== Responsive ===== */
@media (max-width: 600px) { @media (max-width: 768px) {
nav { .hero-title {
flex-direction: column; font-size: 2.5rem;
align-items: center;
gap: 10px;
} }
#profil { .hero-subtitle {
gap: 5px; font-size: 1rem;
}
.navbar-nav {
gap: var(--spacing-md);
}
.stats-grid {
gap: var(--spacing-xl);
}
.stat-number {
font-size: 2.5rem;
}
.section-header h2 {
font-size: 1.75rem;
} }
} }
@media (max-width: 480px) {
.hero-buttons {
flex-direction: column;
width: 100%;
}
.hero-buttons .btn {
width: 100%;
}
.navbar-nav .navbar-link {
display: none;
}
}
/* Body padding for fixed navbar */
body {
padding-top: 65px;
}
+174 -50
View File
@@ -1,62 +1,186 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html lang="fr">
<head> <head>
<title>LazyBot - Bot Discord</title> <title>LazyBot - Bot Discord Multifonction</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="LazyBot est un bot Discord puissant avec système de niveaux, économie, messages personnalisés et bien plus encore.">
<link rel="stylesheet" href="/index.css"> <link rel="stylesheet" href="/index.css">
</head> </head>
<body> <body>
<nav> <!-- Navigation -->
<a href="/">Accueil</a> <nav class="navbar">
<a href="/dashboard">Tableau de bord</a> <div class="navbar-container">
<div id="profil"> <a href="/" class="navbar-brand">
<img id="avatar" src="" alt="Avatar"> <img id="bot-avatar" src="" alt="LazyBot">
<span id="username"></span> <span>LazyBot</span>
<a id="logout" href="/auth/logout">Se déconnecter</a> </a>
<div class="navbar-nav">
<a href="/" class="navbar-link">Accueil</a>
<a href="/dashboard" class="navbar-link">Dashboard</a>
<div id="auth-section">
<a href="/auth/login" class="btn btn-primary btn-sm">Connexion</a>
</div>
</div>
</div> </div>
</nav> </nav>
<h1>LazyBot</h1>
<a id="invite-link" href="#">Ajouter à Discord</a>
<br><br>
<h2>Ajout du bot</h2>
<ul>
<li>Ajoutez le bot à votre serveur Discord en cliquant sur le lien ci-dessus.</li>
<li>Utilisez le tableau de bord pour configurer les paramètres du bot sur vos serveurs.</li>
<li>Profitez des fonctionnalités offertes par LazyBot pour améliorer votre expérience Discord !</li>
</ul>
<h2>Fonctionnalités</h2> <!-- Hero Section -->
<ul> <section class="hero">
<li>Messages de bienvenue et d'au revoir personnalisables</li> <div class="hero-content">
<li>Rôles automatiques pour les nouveaux membres et les utilisateurs en vocal</li> <img id="hero-logo" class="hero-logo" src="" alt="LazyBot Logo">
<li>Et bien plus encore...</li> <h1 class="hero-title">LazyBot</h1>
<p class="hero-subtitle">
Un bot Discord complet avec système de niveaux, économie, messages de bienvenue personnalisés,
rôles automatiques et un dashboard intuitif pour gérer votre serveur.
</p>
<div class="hero-buttons">
<a id="invite-link" href="#" class="btn btn-primary btn-lg">
Ajouter à Discord
</a>
<a href="/dashboard" class="btn btn-secondary btn-lg">
⚙️ Dashboard
</a>
</div>
</div>
</section>
<!-- Features Section -->
<section class="features">
<div class="section-header">
<h2>Fonctionnalités</h2>
<p>Tout ce dont vous avez besoin pour votre serveur Discord</p>
</div>
<div class="features-grid">
<div class="feature-card">
<div class="feature-icon">📈</div>
<h3>Système de Niveaux</h3>
<p>Récompensez l'activité de vos membres avec un système d'XP complet. Messages, vocal, courbes personnalisables et annonces automatiques.</p>
</div>
<div class="feature-card">
<div class="feature-icon">💰</div>
<h3>Économie</h3>
<p>Un système économique complet avec monnaie personnalisable, commandes daily, work, crime, steal, et transactions entre membres.</p>
</div>
<div class="feature-card">
<div class="feature-icon">👋</div>
<h3>Messages Personnalisés</h3>
<p>Accueillez les nouveaux membres et dites au revoir avec des messages entièrement personnalisables avec variables.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🎭</div>
<h3>Rôles Automatiques</h3>
<p>Attribuez automatiquement des rôles aux nouveaux membres ou aux utilisateurs en vocal. Configurez les salons à exclure.</p>
</div>
<div class="feature-card">
<div class="feature-icon">⚙️</div>
<h3>Dashboard Intuitif</h3>
<p>Configurez tout depuis un dashboard web moderne et facile à utiliser. Pas besoin de commandes compliquées.</p>
</div>
<div class="feature-card">
<div class="feature-icon">🛡️</div>
<h3>Commandes Admin</h3>
<p>Gérez les niveaux et l'économie de vos membres avec des commandes d'administration dédiées.</p>
</div>
</div>
</section>
<!-- Stats Section -->
<section class="stats">
<div class="section-header">
<h2>Statistiques</h2>
</div>
<div class="stats-grid">
<div class="stat-item">
<span id="stat-servers" class="stat-number">-</span>
<span class="stat-label">Serveurs</span>
</div>
<div class="stat-item">
<span id="stat-users" class="stat-number">-</span>
<span class="stat-label">Utilisateurs</span>
</div>
<div class="stat-item">
<span id="stat-commands" class="stat-number">20+</span>
<span class="stat-label">Commandes</span>
</div>
</div>
</section>
<!-- CTA Section -->
<section class="cta">
<h2>Prêt à améliorer votre serveur ?</h2>
<p>Ajoutez LazyBot dès maintenant et découvrez toutes ses fonctionnalités.</p>
<a id="invite-link-2" href="#" class="btn btn-primary btn-lg">
Ajouter à Discord
</a>
</section>
<!-- Footer -->
<footer class="footer">
<div class="footer-content">
<div class="footer-links">
<a href="/dashboard">Dashboard</a>
<a href="https://discord.com" target="_blank">Support</a>
</div>
<p class="footer-copyright">
© 2024 LazyBot. Fait avec ❤️ pour Discord.
</p>
</div>
</footer>
<script>
// Charger le lien d'invitation
fetch("/invite-bot")
.then(res => res.json())
.then(data => {
document.getElementById("invite-link").href = data.url;
document.getElementById("invite-link-2").href = data.url;
})
.catch(() => console.log("Impossible de récupérer le lien du bot."));
// Charger les infos du bot (avatar)
fetch("/api/bot-info")
.then(res => res.json())
.then(bot => {
const avatarUrl = `https://cdn.discordapp.com/avatars/${bot.id}/${bot.avatar}.png`;
document.getElementById("bot-avatar").src = avatarUrl;
document.getElementById("hero-logo").src = avatarUrl;
// Stats
if (bot.guildCount) {
document.getElementById("stat-servers").textContent = bot.guildCount;
}
if (bot.userCount) {
document.getElementById("stat-users").textContent = bot.userCount;
}
})
.catch(() => {
// Utiliser un placeholder si pas d'info bot
document.getElementById("bot-avatar").src = "https://cdn.discordapp.com/embed/avatars/0.png";
document.getElementById("hero-logo").src = "https://cdn.discordapp.com/embed/avatars/0.png";
});
// Vérifier si l'utilisateur est connecté
fetch("/api/user")
.then(res => {
if (!res.ok) throw new Error("Non connecté");
return res.json();
})
.then(user => {
const authSection = document.getElementById("auth-section");
authSection.innerHTML = `
<div class="navbar-actions">
<div class="navbar-user">
<img src="https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png" alt="Avatar">
<span>${user.username}</span>
</div>
<a href="/auth/logout" class="btn btn-secondary btn-sm">Déconnexion</a>
</div>
`;
})
.catch(() => {
// Garder le bouton de connexion par défaut
});
</script>
</body> </body>
<script>
fetch("/invite-bot")
.then(res => res.json())
.then(data => {
const link = document.getElementById("invite-link");
link.href = data.url; // met le lien dynamique
})
.catch(() => {
console.log("Impossible de récupérer le lien du bot.");
});
fetch("/api/user")
.then(res => {
if (!res.ok) throw new Error("Utilisateur non connecté"); // gère les erreurs HTTP
return res.json();
})
.then(user => {
document.getElementById("username").textContent = `${user.username}`;
document.getElementById("avatar").src = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`;
})
.catch(() => {
const profilDiv = document.getElementById("profil");
profilDiv.style.display = "none";
});
</script>
</html> </html>
+20
View File
@@ -4,6 +4,26 @@ const loadSlashCommands = require("../slash_commands");
module.exports = (app, db, client) => { module.exports = (app, db, client) => {
// --- Bot info ---
router.get("/bot-info", (req, res) => {
const bot = client.user;
if (!bot) return res.status(500).json({ error: "Bot non connecté" });
let userCount = 0;
client.guilds.cache.forEach(g => {
userCount += g.memberCount;
});
res.json({
id: bot.id,
username: bot.username,
discriminator: bot.discriminator,
avatar: bot.avatar,
guildCount: client.guilds.cache.size,
userCount: userCount
});
});
// --- User info --- // --- User info ---
router.get("/user", (req, res) => { router.get("/user", (req, res) => {
if (req.session.user) res.json(req.session.user); if (req.session.user) res.json(req.session.user);
+5
View File
@@ -8,6 +8,11 @@ const CLIENT_SECRET = process.env.CLIENT_SECRET;
const REDIRECT_URI = process.env.REDIRECT_URI; const REDIRECT_URI = process.env.REDIRECT_URI;
module.exports = (app, db, client) => { module.exports = (app, db, client) => {
// --- Redirection login ---
router.get("/login", (req, res) => {
res.redirect("/auth/discord");
});
// --- Connexion Discord --- // --- Connexion Discord ---
router.get("/discord", (req, res) => { router.get("/discord", (req, res) => {
const url = `https://discord.com/api/oauth2/authorize?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&response_type=code&scope=identify%20guilds`; const url = `https://discord.com/api/oauth2/authorize?client_id=${CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&response_type=code&scope=identify%20guilds`;