diff --git a/app/events/guildMemberAdd.js b/app/events/guildMemberAdd.js
index a5e74b8..8134f6e 100644
--- a/app/events/guildMemberAdd.js
+++ b/app/events/guildMemberAdd.js
@@ -1,4 +1,4 @@
-const { Events } = require("discord.js");
+const { Events, EmbedBuilder } = require("discord.js");
const db = require("../db");
module.exports = {
@@ -10,7 +10,7 @@ module.exports = {
(err, row) => {
if (err || !row || !row.enabled) return;
- let msg = row.message;
+ let msg = row.message || "Bienvenue {mention} sur {server} !";
msg = msg
.replace("{user}", member.user.username)
@@ -19,7 +19,15 @@ module.exports = {
const channel = member.guild.channels.cache.get(row.channel_id);
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] });
}
}
);
diff --git a/app/events/guildMemberRemove.js b/app/events/guildMemberRemove.js
index 18ba1ac..4546ec0 100644
--- a/app/events/guildMemberRemove.js
+++ b/app/events/guildMemberRemove.js
@@ -1,4 +1,4 @@
-const { Events } = require("discord.js");
+const { Events, EmbedBuilder } = require("discord.js");
const db = require("../db");
module.exports = {
@@ -10,7 +10,7 @@ module.exports = {
(err, row) => {
if (err || !row || !row.enabled) return;
- let msg = row.message;
+ let msg = row.message || "Au revoir {user}, tu vas nous manquer !";
msg = msg
.replace("{user}", member.user.username)
@@ -18,7 +18,15 @@ module.exports = {
const channel = member.guild.channels.cache.get(row.channel_id);
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] });
}
}
);
diff --git a/app/events/messageCreate.js b/app/events/messageCreate.js
index f9727cd..3cf78dc 100644
--- a/app/events/messageCreate.js
+++ b/app/events/messageCreate.js
@@ -1,4 +1,4 @@
-const { Events } = require("discord.js");
+const { Events, EmbedBuilder } = require("discord.js");
const db = require("../db");
module.exports = {
@@ -31,25 +31,31 @@ module.exports = {
[guildId],
(err, row) => {
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 requiredRoles = JSON.parse(row.role_with_without_xp || "[]");
if (!requiredRoles.some(roleId => userRoles.has(roleId))) {
return;
}
- } else if (row.role_with_without_type === "without") {
+ } else if (roleWithWithoutType === "without") {
const userRoles = message.member.roles.cache;
const excludedRoles = JSON.parse(row.role_with_without_xp || "[]");
if (excludedRoles.some(roleId => userRoles.has(roleId))) {
return;
}
- } else if (row.salon_with_without_type === "with") {
+ }
+
+ if (salonWithWithoutType === "with") {
const channelId = message.channel.id;
const requiredChannels = JSON.parse(row.salon_with_without_xp || "[]");
if (!requiredChannels.includes(channelId)) {
return;
}
- } else if (row.salon_with_without_type === "without") {
+ } else if (salonWithWithoutType === "without") {
const channelId = message.channel.id;
const excludedChannels = JSON.parse(row.salon_with_without_xp || "[]");
if (excludedChannels.includes(channelId)) {
@@ -64,13 +70,14 @@ module.exports = {
(err, userRow) => {
if (err) return;
+ const cooldownSeconds = row.cooldown_xp_message_seconds ?? 60;
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;
}
- const minXp = row.gain_xp_message_lower_bound;
- const maxXp = row.gain_xp_message_upper_bound;
+ const minXp = row.gain_xp_message_lower_bound ?? 15;
+ const maxXp = row.gain_xp_message_upper_bound ?? 25;
const xpToAdd = Math.floor(Math.random() * (maxXp - minXp + 1)) + minXp;
let newXp;
@@ -84,17 +91,21 @@ module.exports = {
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;
- if (row.xp_courbe_type === "constante") {
+ if (courbeType === "constante") {
fonction_courbe = (level) => multiplier;
- } else if (row.xp_courbe_type === "linear") {
+ } else if (courbeType === "linear") {
fonction_courbe = (level) => (level) * multiplier;
- } else if (row.xp_courbe_type === "quadratic") {
+ } else if (courbeType === "quadratic") {
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;
+ } else {
+ // Fallback au cas où
+ fonction_courbe = (level) => (level) * multiplier;
}
let xpForNextLevel = fonction_courbe(newLevel);
@@ -106,13 +117,27 @@ module.exports = {
if (row.level_announcements_enabled && (newLevel % row.level_annoncement_every_level === 0)) {
const channel = message.guild.channels.cache.get(row.level_announcements_channel_id);
if (channel) {
- let announcementMsg = row.level_announcements_message;
+ let announcementMsg = row.level_announcements_message || "🎉 {mention} a atteint le niveau {level} !";
announcementMsg = announcementMsg
.replace("{user}", message.author.username)
.replace("{mention}", `<@${message.author.id}>`)
.replace("{level}", newLevel)
.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] });
}
}
}
diff --git a/app/public/dashboard.css b/app/public/dashboard.css
index 6a2b6e0..f72fff5 100644
--- a/app/public/dashboard.css
+++ b/app/public/dashboard.css
@@ -1,137 +1,280 @@
-/* ===== Reset minimal ===== */
-* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
+@import url('global.css');
+
+/* ===== Navigation ===== */
+.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;
+ 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 {
- background-color: #1e1f29; /* fond sombre type Discord */
- color: #ffffff;
+ padding-top: 80px;
+ 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;
flex-direction: column;
align-items: center;
- padding: 20px;
+ justify-content: center;
+ padding: var(--spacing-2xl);
}
-/* ===== Navigation ===== */
-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 {
+.spinner {
width: 40px;
height: 40px;
+ border: 3px solid var(--border-color);
+ border-top-color: var(--primary);
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 ===== */
-@media (max-width: 600px) {
- body {
- padding: 10px;
- }
-
- nav {
+@media (max-width: 768px) {
+ .dashboard-header {
flex-direction: column;
- align-items: center;
- gap: 10px;
+ align-items: flex-start;
}
-
- ul#guilds-list li {
- flex-direction: column;
- align-items: center;
- text-align: center;
+
+ .user-avatar {
+ width: 48px;
+ height: 48px;
}
-
- ul#guilds-list li img {
- margin-bottom: 5px;
+
+ .user-details h1 {
+ font-size: 1.25rem;
+ }
+
+ .guilds-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (max-width: 480px) {
+ .navbar-nav .navbar-link {
+ display: none;
}
}
diff --git a/app/public/dashboard.html b/app/public/dashboard.html
index ec12da7..1a6c656 100644
--- a/app/public/dashboard.html
+++ b/app/public/dashboard.html
@@ -1,108 +1,155 @@
-
+
- Tableau de bord
+ Dashboard - LazyBot
-