mirror of
https://github.com/arthur-pbty/LazyBot.git
synced 2026-06-03 15:07:29 +02:00
feat: Enhance goodbye and welcome forms with new message types and image options
- Added support for different message types (text, embed, both) in goodbye and welcome forms. - Implemented embed options including title, description, color, thumbnail, and footer for both forms. - Introduced image options with gradient selection, title, subtitle, and member count display for goodbye and welcome messages. - Updated API routes to handle new configuration parameters for saving and retrieving goodbye and welcome settings. - Created a new function to generate welcome images with customizable gradients and text.
This commit is contained in:
@@ -32,15 +32,37 @@ db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS welcome_config (
|
||||
guild_id TEXT PRIMARY KEY,
|
||||
channel_id TEXT,
|
||||
enabled INTEGER NOT NULL,
|
||||
message TEXT NOT NULL
|
||||
enabled INTEGER NOT NULL DEFAULT 0,
|
||||
message TEXT,
|
||||
message_type TEXT NOT NULL DEFAULT 'embed',
|
||||
embed_title TEXT,
|
||||
embed_description TEXT,
|
||||
embed_color TEXT DEFAULT '#57F287',
|
||||
embed_thumbnail INTEGER NOT NULL DEFAULT 1,
|
||||
embed_footer TEXT,
|
||||
image_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
image_gradient TEXT DEFAULT 'purple',
|
||||
image_title TEXT,
|
||||
image_subtitle TEXT,
|
||||
image_show_member_count INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS goodbye_config (
|
||||
guild_id TEXT PRIMARY KEY,
|
||||
channel_id TEXT,
|
||||
enabled INTEGER NOT NULL,
|
||||
message TEXT NOT NULL
|
||||
enabled INTEGER NOT NULL DEFAULT 0,
|
||||
message TEXT,
|
||||
message_type TEXT NOT NULL DEFAULT 'embed',
|
||||
embed_title TEXT,
|
||||
embed_description TEXT,
|
||||
embed_color TEXT DEFAULT '#ED4245',
|
||||
embed_thumbnail INTEGER NOT NULL DEFAULT 1,
|
||||
embed_footer TEXT,
|
||||
image_enabled INTEGER NOT NULL DEFAULT 0,
|
||||
image_gradient TEXT DEFAULT 'red',
|
||||
image_title TEXT,
|
||||
image_subtitle TEXT,
|
||||
image_show_member_count INTEGER NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS autorole_newuser_config (
|
||||
@@ -443,4 +465,58 @@ db.exec(`
|
||||
);
|
||||
`);
|
||||
|
||||
// Migration: Ajouter les nouvelles colonnes pour welcome/goodbye si elles n'existent pas
|
||||
const migrateWelcomeGoodbye = () => {
|
||||
// Colonnes à ajouter pour welcome_config
|
||||
const welcomeColumns = [
|
||||
{ name: 'message_type', sql: "ALTER TABLE welcome_config ADD COLUMN message_type TEXT NOT NULL DEFAULT 'embed'" },
|
||||
{ name: 'embed_title', sql: "ALTER TABLE welcome_config ADD COLUMN embed_title TEXT" },
|
||||
{ name: 'embed_description', sql: "ALTER TABLE welcome_config ADD COLUMN embed_description TEXT" },
|
||||
{ name: 'embed_color', sql: "ALTER TABLE welcome_config ADD COLUMN embed_color TEXT DEFAULT '#57F287'" },
|
||||
{ name: 'embed_thumbnail', sql: "ALTER TABLE welcome_config ADD COLUMN embed_thumbnail INTEGER NOT NULL DEFAULT 1" },
|
||||
{ name: 'embed_footer', sql: "ALTER TABLE welcome_config ADD COLUMN embed_footer TEXT" },
|
||||
{ name: 'image_enabled', sql: "ALTER TABLE welcome_config ADD COLUMN image_enabled INTEGER NOT NULL DEFAULT 0" },
|
||||
{ name: 'image_gradient', sql: "ALTER TABLE welcome_config ADD COLUMN image_gradient TEXT DEFAULT 'purple'" },
|
||||
{ name: 'image_title', sql: "ALTER TABLE welcome_config ADD COLUMN image_title TEXT" },
|
||||
{ name: 'image_subtitle', sql: "ALTER TABLE welcome_config ADD COLUMN image_subtitle TEXT" },
|
||||
{ name: 'image_show_member_count', sql: "ALTER TABLE welcome_config ADD COLUMN image_show_member_count INTEGER NOT NULL DEFAULT 1" }
|
||||
];
|
||||
|
||||
// Colonnes à ajouter pour goodbye_config
|
||||
const goodbyeColumns = [
|
||||
{ name: 'message_type', sql: "ALTER TABLE goodbye_config ADD COLUMN message_type TEXT NOT NULL DEFAULT 'embed'" },
|
||||
{ name: 'embed_title', sql: "ALTER TABLE goodbye_config ADD COLUMN embed_title TEXT" },
|
||||
{ name: 'embed_description', sql: "ALTER TABLE goodbye_config ADD COLUMN embed_description TEXT" },
|
||||
{ name: 'embed_color', sql: "ALTER TABLE goodbye_config ADD COLUMN embed_color TEXT DEFAULT '#ED4245'" },
|
||||
{ name: 'embed_thumbnail', sql: "ALTER TABLE goodbye_config ADD COLUMN embed_thumbnail INTEGER NOT NULL DEFAULT 1" },
|
||||
{ name: 'embed_footer', sql: "ALTER TABLE goodbye_config ADD COLUMN embed_footer TEXT" },
|
||||
{ name: 'image_enabled', sql: "ALTER TABLE goodbye_config ADD COLUMN image_enabled INTEGER NOT NULL DEFAULT 0" },
|
||||
{ name: 'image_gradient', sql: "ALTER TABLE goodbye_config ADD COLUMN image_gradient TEXT DEFAULT 'red'" },
|
||||
{ name: 'image_title', sql: "ALTER TABLE goodbye_config ADD COLUMN image_title TEXT" },
|
||||
{ name: 'image_subtitle', sql: "ALTER TABLE goodbye_config ADD COLUMN image_subtitle TEXT" },
|
||||
{ name: 'image_show_member_count', sql: "ALTER TABLE goodbye_config ADD COLUMN image_show_member_count INTEGER NOT NULL DEFAULT 1" }
|
||||
];
|
||||
|
||||
// Exécuter les migrations pour welcome_config
|
||||
welcomeColumns.forEach(col => {
|
||||
db.run(col.sql, (err) => {
|
||||
if (err && !err.message.includes('duplicate column')) {
|
||||
// Ignorer les erreurs de colonnes dupliquées
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Exécuter les migrations pour goodbye_config
|
||||
goodbyeColumns.forEach(col => {
|
||||
db.run(col.sql, (err) => {
|
||||
if (err && !err.message.includes('duplicate column')) {
|
||||
// Ignorer les erreurs de colonnes dupliquées
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
// Exécuter la migration
|
||||
migrateWelcomeGoodbye();
|
||||
|
||||
module.exports = db;
|
||||
|
||||
+121
-33
@@ -1,7 +1,8 @@
|
||||
const { Events, EmbedBuilder } = require("discord.js");
|
||||
const { Events, EmbedBuilder, AttachmentBuilder } = require("discord.js");
|
||||
const db = require("../db");
|
||||
const { sendLog } = require("../fonctions/sendLog");
|
||||
const antiraid = require("../fonctions/antiraid");
|
||||
const { generateWelcomeImage } = require("../fonctions/generateWelcomeImage");
|
||||
|
||||
module.exports = {
|
||||
name: Events.GuildMemberAdd,
|
||||
@@ -32,46 +33,133 @@ module.exports = {
|
||||
});
|
||||
|
||||
// ===== MESSAGE DE BIENVENUE =====
|
||||
db.get(
|
||||
"SELECT enabled, channel_id, message FROM welcome_config WHERE guild_id = ?",
|
||||
[member.guild.id],
|
||||
(err, row) => {
|
||||
if (err || !row || !row.enabled) return;
|
||||
|
||||
let msg = row.message || "Bienvenue {mention} sur {server} !";
|
||||
|
||||
msg = msg
|
||||
.replace("{user}", member.user.username)
|
||||
.replace("{mention}", `<@${member.id}>`)
|
||||
.replace("{server}", member.guild.name);
|
||||
|
||||
const channel = member.guild.channels.cache.get(row.channel_id);
|
||||
if (channel) {
|
||||
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] });
|
||||
}
|
||||
}
|
||||
try {
|
||||
const row = await db.getAsync(
|
||||
`SELECT enabled, channel_id, message, message_type,
|
||||
embed_title, embed_description, embed_color, embed_thumbnail, embed_footer,
|
||||
image_enabled, image_gradient, image_title, image_subtitle, image_show_member_count
|
||||
FROM welcome_config WHERE guild_id = ?`,
|
||||
[member.guild.id]
|
||||
);
|
||||
|
||||
if (!row || !row.enabled) return processAutorole();
|
||||
|
||||
const channel = member.guild.channels.cache.get(row.channel_id);
|
||||
if (!channel) return processAutorole();
|
||||
|
||||
// Variables de remplacement
|
||||
const replaceVariables = (text) => {
|
||||
if (!text) return text;
|
||||
return text
|
||||
.replace(/{user}/g, member.user.username)
|
||||
.replace(/{mention}/g, `<@${member.id}>`)
|
||||
.replace(/{server}/g, member.guild.name)
|
||||
.replace(/{membercount}/g, member.guild.memberCount.toString())
|
||||
.replace(/{userid}/g, member.id);
|
||||
};
|
||||
|
||||
const messagePayload = { };
|
||||
|
||||
// Message texte simple
|
||||
if (row.message_type === 'text' || row.message_type === 'both') {
|
||||
const textMsg = replaceVariables(row.message || "Bienvenue {mention} sur {server} !");
|
||||
messagePayload.content = textMsg;
|
||||
}
|
||||
|
||||
// Embed
|
||||
if (row.message_type === 'embed' || row.message_type === 'both') {
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(row.embed_color || '#57F287')
|
||||
.setTimestamp();
|
||||
|
||||
if (row.embed_title) {
|
||||
embed.setTitle(replaceVariables(row.embed_title));
|
||||
} else {
|
||||
embed.setTitle('👋 Bienvenue !');
|
||||
}
|
||||
|
||||
if (row.embed_description) {
|
||||
embed.setDescription(replaceVariables(row.embed_description));
|
||||
} else if (row.message) {
|
||||
embed.setDescription(replaceVariables(row.message));
|
||||
} else {
|
||||
embed.setDescription(`Bienvenue ${member} sur **${member.guild.name}** !`);
|
||||
}
|
||||
|
||||
if (row.embed_thumbnail) {
|
||||
embed.setThumbnail(member.user.displayAvatarURL({ dynamic: true, size: 256 }));
|
||||
}
|
||||
|
||||
if (row.embed_footer) {
|
||||
embed.setFooter({
|
||||
text: replaceVariables(row.embed_footer),
|
||||
iconURL: member.guild.iconURL({ dynamic: true })
|
||||
});
|
||||
} else {
|
||||
embed.setFooter({
|
||||
text: member.guild.name,
|
||||
iconURL: member.guild.iconURL({ dynamic: true })
|
||||
});
|
||||
}
|
||||
|
||||
messagePayload.embeds = [embed];
|
||||
}
|
||||
|
||||
// Image générée
|
||||
if (row.image_enabled) {
|
||||
try {
|
||||
const imageBuffer = await generateWelcomeImage({
|
||||
type: 'welcome',
|
||||
username: member.user.username,
|
||||
discriminator: member.user.discriminator,
|
||||
avatarURL: member.user.displayAvatarURL({ extension: 'png', size: 256 }),
|
||||
serverName: member.guild.name,
|
||||
memberCount: row.image_show_member_count ? member.guild.memberCount : null,
|
||||
gradient: row.image_gradient || 'purple',
|
||||
title: row.image_title ? replaceVariables(row.image_title) : null,
|
||||
subtitle: row.image_subtitle ? replaceVariables(row.image_subtitle) : null
|
||||
});
|
||||
|
||||
const attachment = new AttachmentBuilder(imageBuffer, { name: 'welcome.png' });
|
||||
messagePayload.files = [attachment];
|
||||
|
||||
// Si on a un embed, mettre l'image dans l'embed
|
||||
if (messagePayload.embeds && messagePayload.embeds.length > 0) {
|
||||
messagePayload.embeds[0].setImage('attachment://welcome.png');
|
||||
}
|
||||
} catch (imgErr) {
|
||||
console.error('Erreur génération image bienvenue:', imgErr);
|
||||
}
|
||||
}
|
||||
|
||||
// Envoyer le message si on a quelque chose à envoyer
|
||||
if (messagePayload.content || messagePayload.embeds || messagePayload.files) {
|
||||
await channel.send(messagePayload);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Erreur message bienvenue:', err);
|
||||
}
|
||||
|
||||
// ===== AUTOROLE =====
|
||||
db.get(
|
||||
async function processAutorole() {
|
||||
try {
|
||||
const row = await db.getAsync(
|
||||
"SELECT enabled, role_id FROM autorole_newuser_config WHERE guild_id = ?",
|
||||
[member.guild.id],
|
||||
(err, row) => {
|
||||
if (err || !row || !row.enabled) return;
|
||||
[member.guild.id]
|
||||
);
|
||||
|
||||
if (!row || !row.enabled) return;
|
||||
|
||||
const role = member.guild.roles.cache.get(row.role_id);
|
||||
if (role) {
|
||||
member.roles.add(role);
|
||||
await member.roles.add(role);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Erreur autorole:', err);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
processAutorole();
|
||||
},
|
||||
};
|
||||
|
||||
+100
-19
@@ -1,6 +1,7 @@
|
||||
const { Events, EmbedBuilder, AuditLogEvent } = require("discord.js");
|
||||
const { Events, EmbedBuilder, AuditLogEvent, AttachmentBuilder } = require("discord.js");
|
||||
const db = require("../db");
|
||||
const { sendLog } = require("../fonctions/sendLog");
|
||||
const { generateWelcomeImage } = require("../fonctions/generateWelcomeImage");
|
||||
|
||||
module.exports = {
|
||||
name: Events.GuildMemberRemove,
|
||||
@@ -60,31 +61,111 @@ module.exports = {
|
||||
}
|
||||
|
||||
// ===== MESSAGE D'AU REVOIR =====
|
||||
db.get(
|
||||
"SELECT enabled, channel_id, message FROM goodbye_config WHERE guild_id = ?",
|
||||
[member.guild.id],
|
||||
(err, row) => {
|
||||
if (err || !row || !row.enabled) return;
|
||||
try {
|
||||
const row = await db.getAsync(
|
||||
`SELECT enabled, channel_id, message, message_type,
|
||||
embed_title, embed_description, embed_color, embed_thumbnail, embed_footer,
|
||||
image_enabled, image_gradient, image_title, image_subtitle, image_show_member_count
|
||||
FROM goodbye_config WHERE guild_id = ?`,
|
||||
[member.guild.id]
|
||||
);
|
||||
|
||||
let msg = row.message || "Au revoir {user}, tu vas nous manquer !";
|
||||
|
||||
msg = msg
|
||||
.replace("{user}", member.user.username)
|
||||
.replace("{server}", member.guild.name);
|
||||
if (!row || !row.enabled) return;
|
||||
|
||||
const channel = member.guild.channels.cache.get(row.channel_id);
|
||||
if (channel) {
|
||||
if (!channel) return;
|
||||
|
||||
// Variables de remplacement
|
||||
const replaceVariables = (text) => {
|
||||
if (!text) return text;
|
||||
return text
|
||||
.replace(/{user}/g, member.user.username)
|
||||
.replace(/{server}/g, member.guild.name)
|
||||
.replace(/{membercount}/g, member.guild.memberCount.toString())
|
||||
.replace(/{userid}/g, member.id);
|
||||
};
|
||||
|
||||
const messagePayload = { };
|
||||
|
||||
// Message texte simple
|
||||
if (row.message_type === 'text' || row.message_type === 'both') {
|
||||
const textMsg = replaceVariables(row.message || "Au revoir {user}, tu vas nous manquer !");
|
||||
messagePayload.content = textMsg;
|
||||
}
|
||||
|
||||
// Embed
|
||||
if (row.message_type === 'embed' || row.message_type === 'both') {
|
||||
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 }) })
|
||||
.setColor(row.embed_color || '#ED4245')
|
||||
.setTimestamp();
|
||||
|
||||
channel.send({ embeds: [embed] });
|
||||
if (row.embed_title) {
|
||||
embed.setTitle(replaceVariables(row.embed_title));
|
||||
} else {
|
||||
embed.setTitle('👋 Au revoir...');
|
||||
}
|
||||
|
||||
if (row.embed_description) {
|
||||
embed.setDescription(replaceVariables(row.embed_description));
|
||||
} else if (row.message) {
|
||||
embed.setDescription(replaceVariables(row.message));
|
||||
} else {
|
||||
embed.setDescription(`**${member.user.username}** a quitté le serveur.`);
|
||||
}
|
||||
|
||||
if (row.embed_thumbnail) {
|
||||
embed.setThumbnail(member.user.displayAvatarURL({ dynamic: true, size: 256 }));
|
||||
}
|
||||
|
||||
if (row.embed_footer) {
|
||||
embed.setFooter({
|
||||
text: replaceVariables(row.embed_footer),
|
||||
iconURL: member.guild.iconURL({ dynamic: true })
|
||||
});
|
||||
} else {
|
||||
embed.setFooter({
|
||||
text: member.guild.name,
|
||||
iconURL: member.guild.iconURL({ dynamic: true })
|
||||
});
|
||||
}
|
||||
|
||||
messagePayload.embeds = [embed];
|
||||
}
|
||||
|
||||
// Image générée
|
||||
if (row.image_enabled) {
|
||||
try {
|
||||
const imageBuffer = await generateWelcomeImage({
|
||||
type: 'goodbye',
|
||||
username: member.user.username,
|
||||
discriminator: member.user.discriminator,
|
||||
avatarURL: member.user.displayAvatarURL({ extension: 'png', size: 256 }),
|
||||
serverName: member.guild.name,
|
||||
memberCount: row.image_show_member_count ? member.guild.memberCount : null,
|
||||
gradient: row.image_gradient || 'red',
|
||||
title: row.image_title ? replaceVariables(row.image_title) : null,
|
||||
subtitle: row.image_subtitle ? replaceVariables(row.image_subtitle) : null
|
||||
});
|
||||
|
||||
const attachment = new AttachmentBuilder(imageBuffer, { name: 'goodbye.png' });
|
||||
messagePayload.files = [attachment];
|
||||
|
||||
// Si on a un embed, mettre l'image dans l'embed
|
||||
if (messagePayload.embeds && messagePayload.embeds.length > 0) {
|
||||
messagePayload.embeds[0].setImage('attachment://goodbye.png');
|
||||
}
|
||||
} catch (imgErr) {
|
||||
console.error('Erreur génération image au revoir:', imgErr);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Envoyer le message si on a quelque chose à envoyer
|
||||
if (messagePayload.content || messagePayload.embeds || messagePayload.files) {
|
||||
await channel.send(messagePayload);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error('Erreur message au revoir:', err);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
const { createCanvas, loadImage, registerFont } = require('canvas');
|
||||
const path = require('path');
|
||||
|
||||
// Couleurs par défaut pour les dégradés
|
||||
const GRADIENTS = {
|
||||
purple: ['#667eea', '#764ba2'],
|
||||
blue: ['#4facfe', '#00f2fe'],
|
||||
green: ['#11998e', '#38ef7d'],
|
||||
red: ['#ff416c', '#ff4b2b'],
|
||||
orange: ['#f12711', '#f5af19'],
|
||||
pink: ['#ee0979', '#ff6a00'],
|
||||
dark: ['#232526', '#414345'],
|
||||
sunset: ['#fa709a', '#fee140'],
|
||||
ocean: ['#2193b0', '#6dd5ed'],
|
||||
forest: ['#134e5e', '#71b280']
|
||||
};
|
||||
|
||||
/**
|
||||
* Génère une image de bienvenue/au revoir
|
||||
* @param {Object} options - Options de génération
|
||||
* @param {string} options.type - 'welcome' ou 'goodbye'
|
||||
* @param {string} options.username - Nom d'utilisateur
|
||||
* @param {string} options.discriminator - Discriminateur (ou pseudo global)
|
||||
* @param {string} options.avatarURL - URL de l'avatar
|
||||
* @param {string} options.serverName - Nom du serveur
|
||||
* @param {string} options.memberCount - Nombre de membres (optionnel)
|
||||
* @param {string} options.gradient - Nom du dégradé ou couleurs custom
|
||||
* @param {string} options.title - Titre personnalisé (optionnel)
|
||||
* @param {string} options.subtitle - Sous-titre personnalisé (optionnel)
|
||||
* @returns {Promise<Buffer>} - Buffer de l'image PNG
|
||||
*/
|
||||
async function generateWelcomeImage(options) {
|
||||
const {
|
||||
type = 'welcome',
|
||||
username,
|
||||
discriminator = '',
|
||||
avatarURL,
|
||||
serverName,
|
||||
memberCount = null,
|
||||
gradient = 'purple',
|
||||
title = null,
|
||||
subtitle = null
|
||||
} = options;
|
||||
|
||||
// Dimensions de l'image
|
||||
const width = 800;
|
||||
const height = 250;
|
||||
|
||||
const canvas = createCanvas(width, height);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Créer le dégradé de fond
|
||||
let colors = GRADIENTS[gradient] || GRADIENTS.purple;
|
||||
if (Array.isArray(gradient) && gradient.length >= 2) {
|
||||
colors = gradient;
|
||||
}
|
||||
|
||||
const grd = ctx.createLinearGradient(0, 0, width, height);
|
||||
grd.addColorStop(0, colors[0]);
|
||||
grd.addColorStop(1, colors[1]);
|
||||
|
||||
// Fond avec coins arrondis
|
||||
roundRect(ctx, 0, 0, width, height, 20);
|
||||
ctx.fillStyle = grd;
|
||||
ctx.fill();
|
||||
|
||||
// Ajouter un effet de vague/pattern subtil
|
||||
ctx.globalAlpha = 0.1;
|
||||
for (let i = 0; i < 5; i++) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, height - 50 + i * 20);
|
||||
ctx.quadraticCurveTo(width / 4, height - 80 + i * 20, width / 2, height - 50 + i * 20);
|
||||
ctx.quadraticCurveTo(width * 3 / 4, height - 20 + i * 20, width, height - 50 + i * 20);
|
||||
ctx.lineTo(width, height);
|
||||
ctx.lineTo(0, height);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.globalAlpha = 1;
|
||||
|
||||
// Charger et dessiner l'avatar avec bordure circulaire
|
||||
try {
|
||||
const avatar = await loadImage(avatarURL);
|
||||
const avatarSize = 130;
|
||||
const avatarX = 50;
|
||||
const avatarY = (height - avatarSize) / 2;
|
||||
|
||||
// Ombre de l'avatar
|
||||
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
|
||||
ctx.shadowBlur = 15;
|
||||
ctx.shadowOffsetX = 0;
|
||||
ctx.shadowOffsetY = 5;
|
||||
|
||||
// Bordure blanche de l'avatar
|
||||
ctx.beginPath();
|
||||
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2 + 5, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fill();
|
||||
|
||||
ctx.shadowColor = 'transparent';
|
||||
ctx.shadowBlur = 0;
|
||||
|
||||
// Dessiner l'avatar en cercle
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
|
||||
ctx.closePath();
|
||||
ctx.clip();
|
||||
ctx.drawImage(avatar, avatarX, avatarY, avatarSize, avatarSize);
|
||||
ctx.restore();
|
||||
|
||||
} catch (err) {
|
||||
// Si l'avatar ne charge pas, dessiner un cercle par défaut
|
||||
const avatarSize = 130;
|
||||
const avatarX = 50;
|
||||
const avatarY = (height - avatarSize) / 2;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(avatarX + avatarSize / 2, avatarY + avatarSize / 2, avatarSize / 2, 0, Math.PI * 2);
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
// Zone de texte
|
||||
const textX = 210;
|
||||
|
||||
// Titre principal (Bienvenue / Au revoir)
|
||||
const defaultTitle = type === 'welcome' ? 'Bienvenue' : 'Au revoir';
|
||||
const displayTitle = title || defaultTitle;
|
||||
|
||||
ctx.font = 'bold 42px Arial, sans-serif';
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
|
||||
ctx.shadowBlur = 5;
|
||||
ctx.shadowOffsetX = 2;
|
||||
ctx.shadowOffsetY = 2;
|
||||
ctx.fillText(displayTitle, textX, 75);
|
||||
|
||||
// Sous-titre (sur le serveur Discord / a quitté le serveur)
|
||||
const defaultSubtitle = type === 'welcome' ? 'sur le serveur Discord' : 'a quitté le serveur';
|
||||
const displaySubtitle = subtitle || defaultSubtitle;
|
||||
|
||||
ctx.font = '22px Arial, sans-serif';
|
||||
ctx.shadowBlur = 3;
|
||||
ctx.fillText(displaySubtitle, textX, 110);
|
||||
|
||||
// Nom du serveur (en italique)
|
||||
ctx.font = 'italic 28px Arial, sans-serif';
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillText(serverName, textX, 150);
|
||||
|
||||
// Reset shadow
|
||||
ctx.shadowColor = 'transparent';
|
||||
ctx.shadowBlur = 0;
|
||||
|
||||
// Nom d'utilisateur
|
||||
ctx.font = 'bold 26px Arial, sans-serif';
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
|
||||
|
||||
let userDisplay = username;
|
||||
if (discriminator && discriminator !== '0') {
|
||||
userDisplay += `#${discriminator}`;
|
||||
}
|
||||
|
||||
// Tronquer si trop long
|
||||
if (ctx.measureText(userDisplay).width > 350) {
|
||||
while (ctx.measureText(userDisplay + '...').width > 350 && userDisplay.length > 0) {
|
||||
userDisplay = userDisplay.slice(0, -1);
|
||||
}
|
||||
userDisplay += '...';
|
||||
}
|
||||
|
||||
ctx.fillText(userDisplay, textX, 195);
|
||||
|
||||
// Nombre de membres (optionnel)
|
||||
if (memberCount) {
|
||||
ctx.font = '16px Arial, sans-serif';
|
||||
ctx.fillStyle = 'rgba(255, 255, 255, 0.7)';
|
||||
const memberText = type === 'welcome'
|
||||
? `Tu es le ${memberCount}ème membre !`
|
||||
: `Il reste ${memberCount} membres`;
|
||||
ctx.fillText(memberText, textX, 225);
|
||||
}
|
||||
|
||||
return canvas.toBuffer('image/png');
|
||||
}
|
||||
|
||||
/**
|
||||
* Dessine un rectangle aux coins arrondis
|
||||
*/
|
||||
function roundRect(ctx, x, y, width, height, radius) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x + radius, y);
|
||||
ctx.lineTo(x + width - radius, y);
|
||||
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
||||
ctx.lineTo(x + width, y + height - radius);
|
||||
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
||||
ctx.lineTo(x + radius, y + height);
|
||||
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
||||
ctx.lineTo(x, y + radius);
|
||||
ctx.quadraticCurveTo(x, y, x + radius, y);
|
||||
ctx.closePath();
|
||||
}
|
||||
|
||||
// Liste des dégradés disponibles pour le frontend
|
||||
const availableGradients = Object.keys(GRADIENTS);
|
||||
|
||||
module.exports = { generateWelcomeImage, availableGradients, GRADIENTS };
|
||||
Generated
+227
-37
@@ -9,6 +9,8 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"canvas": "^3.2.1",
|
||||
"chartjs-node-canvas": "^4.1.6",
|
||||
"connect-sqlite3": "^0.9.16",
|
||||
"cross-fetch": "^4.1.0",
|
||||
"discord.js": "^14.25.1",
|
||||
@@ -155,6 +157,74 @@
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
|
||||
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.0",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"make-dir": "^3.1.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"nopt": "^5.0.0",
|
||||
"npmlog": "^5.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.5",
|
||||
"tar": "^6.1.11"
|
||||
},
|
||||
"bin": {
|
||||
"node-pre-gyp": "bin/node-pre-gyp"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/are-we-there-yet": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
|
||||
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"delegates": "^1.0.0",
|
||||
"readable-stream": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/gauge": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
|
||||
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"aproba": "^1.0.3 || ^2.0.0",
|
||||
"color-support": "^1.1.2",
|
||||
"console-control-strings": "^1.0.0",
|
||||
"has-unicode": "^2.0.1",
|
||||
"object-assign": "^4.1.1",
|
||||
"signal-exit": "^3.0.0",
|
||||
"string-width": "^4.2.3",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wide-align": "^1.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@mapbox/node-pre-gyp/node_modules/npmlog": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
|
||||
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
|
||||
"deprecated": "This package is no longer supported.",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"are-we-there-yet": "^2.0.0",
|
||||
"console-control-strings": "^1.1.0",
|
||||
"gauge": "^3.0.0",
|
||||
"set-blocking": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@npmcli/fs": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz",
|
||||
@@ -256,8 +326,7 @@
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
|
||||
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "2.0.0",
|
||||
@@ -277,7 +346,6 @@
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
@@ -317,7 +385,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -326,8 +393,7 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz",
|
||||
"integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/are-we-there-yet": {
|
||||
"version": "3.0.1",
|
||||
@@ -348,8 +414,7 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/base64-js": {
|
||||
"version": "1.5.1",
|
||||
@@ -420,7 +485,6 @@
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
|
||||
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -518,6 +582,90 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/canvas": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.1.tgz",
|
||||
"integrity": "sha512-ej1sPFR5+0YWtaVp6S1N1FVz69TQCqmrkGeRvQxZeAB1nAIcjNTHVwrZtYtWFFBmQsF40/uDLehsW5KuYC99mg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7.0.0",
|
||||
"prebuild-install": "^7.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.12.0 || >= 20.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chart.js": {
|
||||
"version": "3.9.1",
|
||||
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.9.1.tgz",
|
||||
"integrity": "sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/chartjs-node-canvas": {
|
||||
"version": "4.1.6",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-node-canvas/-/chartjs-node-canvas-4.1.6.tgz",
|
||||
"integrity": "sha512-UQJbPWrvqB/FoLclGA9BaLQmZbzSYlujF4w8NZd6Xzb+sqgACBb2owDX6m7ifCXLjUW5Nz0Qx0qqrTtQkkSoYw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"canvas": "^2.8.0",
|
||||
"tslib": "^2.3.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"chart.js": "^3.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-node-canvas/node_modules/canvas": {
|
||||
"version": "2.11.2",
|
||||
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz",
|
||||
"integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@mapbox/node-pre-gyp": "^1.0.0",
|
||||
"nan": "^2.17.0",
|
||||
"simple-get": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-node-canvas/node_modules/decompress-response": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
|
||||
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-response": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-node-canvas/node_modules/mimic-response": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
|
||||
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-node-canvas/node_modules/simple-get": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
|
||||
"integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"decompress-response": "^4.2.0",
|
||||
"once": "^1.3.1",
|
||||
"simple-concat": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chownr": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
|
||||
@@ -542,7 +690,6 @@
|
||||
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
|
||||
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"bin": {
|
||||
"color-support": "bin.js"
|
||||
}
|
||||
@@ -551,8 +698,7 @@
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/connect-sqlite3": {
|
||||
"version": "0.9.16",
|
||||
@@ -569,8 +715,7 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
|
||||
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "1.0.1",
|
||||
@@ -666,8 +811,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
|
||||
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
@@ -759,8 +903,7 @@
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
"version": "2.0.0",
|
||||
@@ -771,6 +914,29 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"iconv-lite": "^0.6.2"
|
||||
}
|
||||
},
|
||||
"node_modules/encoding/node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/end-of-stream": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||
@@ -1007,8 +1173,7 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
@@ -1089,7 +1254,6 @@
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
@@ -1140,8 +1304,7 @@
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
|
||||
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/hasown": {
|
||||
"version": "2.0.2",
|
||||
@@ -1202,7 +1365,6 @@
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
|
||||
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"agent-base": "6",
|
||||
"debug": "4"
|
||||
@@ -1290,7 +1452,6 @@
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
@@ -1332,7 +1493,6 @@
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -1388,6 +1548,30 @@
|
||||
"integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/make-dir": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
|
||||
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"semver": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir/node_modules/semver": {
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/make-fetch-happen": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz",
|
||||
@@ -1498,7 +1682,6 @@
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
@@ -1634,6 +1817,12 @@
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nan": {
|
||||
"version": "2.24.0",
|
||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.24.0.tgz",
|
||||
"integrity": "sha512-Vpf9qnVW1RaDkoNKFUvfxqAbtI8ncb8OJlqZ9wwpXzWPEsvsB1nvdUi6oYrHIkQ1Y/tMDnr1h4nczS0VB9Xykg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
|
||||
@@ -1717,7 +1906,6 @@
|
||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
|
||||
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"abbrev": "1"
|
||||
},
|
||||
@@ -1745,6 +1933,15 @@
|
||||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||
@@ -1817,7 +2014,6 @@
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -1995,7 +2191,6 @@
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"glob": "^7.1.3"
|
||||
},
|
||||
@@ -2109,8 +2304,7 @@
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
|
||||
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/setprototypeof": {
|
||||
"version": "1.2.0",
|
||||
@@ -2194,8 +2388,7 @@
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/simple-concat": {
|
||||
"version": "1.0.1",
|
||||
@@ -2343,7 +2536,6 @@
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
@@ -2358,7 +2550,6 @@
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
@@ -2596,7 +2787,6 @@
|
||||
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
|
||||
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"string-width": "^1.0.2 || 2 || 3 || 4"
|
||||
}
|
||||
|
||||
+3
-2
@@ -23,13 +23,14 @@
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"canvas": "^3.2.1",
|
||||
"chartjs-node-canvas": "^4.1.6",
|
||||
"connect-sqlite3": "^0.9.16",
|
||||
"cross-fetch": "^4.1.0",
|
||||
"discord.js": "^14.25.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"express": "^5.2.1",
|
||||
"express-session": "^1.18.2",
|
||||
"sqlite3": "^5.1.7",
|
||||
"chartjs-node-canvas": "^4.1.6"
|
||||
"sqlite3": "^5.1.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1528,3 +1528,81 @@ body {
|
||||
padding: var(--spacing-lg);
|
||||
}
|
||||
|
||||
/* ===== Gradient Picker ===== */
|
||||
.gradient-picker {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--spacing-sm);
|
||||
margin-top: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.gradient-option {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
border: 3px solid transparent;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gradient-option:hover {
|
||||
transform: scale(1.1);
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
.gradient-option.selected {
|
||||
border-color: var(--primary);
|
||||
box-shadow: 0 0 0 2px var(--primary), var(--shadow-md);
|
||||
}
|
||||
|
||||
.gradient-option.selected::after {
|
||||
content: '✓';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* ===== Image Preview ===== */
|
||||
.image-preview {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius-md);
|
||||
min-height: 150px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image-preview img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.preview-placeholder {
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* ===== Color Input ===== */
|
||||
.form-color {
|
||||
height: 45px;
|
||||
padding: 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.form-color::-webkit-color-swatch-wrapper {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.form-color::-webkit-color-swatch {
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
+270
-20
@@ -24,8 +24,9 @@
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav">
|
||||
<!-- Accueil & Membres -->
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">Configuration</div>
|
||||
<div class="nav-section-title">👥 Membres</div>
|
||||
<a class="nav-item active" data-section="welcome">
|
||||
<span class="nav-item-icon">👋</span>
|
||||
Bienvenue
|
||||
@@ -38,10 +39,15 @@
|
||||
<span class="nav-item-icon">🎭</span>
|
||||
Rôles automatiques
|
||||
</a>
|
||||
<a class="nav-item" data-section="rolepanels">
|
||||
<span class="nav-item-icon">🏷️</span>
|
||||
Rôles par boutons
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Progression -->
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">Systèmes</div>
|
||||
<div class="nav-section-title">🎮 Progression</div>
|
||||
<a class="nav-item" data-section="levels">
|
||||
<span class="nav-item-icon">📈</span>
|
||||
Niveaux
|
||||
@@ -50,6 +56,11 @@
|
||||
<span class="nav-item-icon">💰</span>
|
||||
Économie
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Fonctionnalités -->
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">⚡ Fonctionnalités</div>
|
||||
<a class="nav-item" data-section="privateroom">
|
||||
<span class="nav-item-icon">🔊</span>
|
||||
Salons temporaires
|
||||
@@ -62,29 +73,40 @@
|
||||
<span class="nav-item-icon">📊</span>
|
||||
Salons de stats
|
||||
</a>
|
||||
<a class="nav-item" data-section="scheduledmessages">
|
||||
<span class="nav-item-icon">⏰</span>
|
||||
Messages programmés
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Communication -->
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">💬 Communication</div>
|
||||
<a class="nav-item" data-section="sendmessage">
|
||||
<span class="nav-item-icon">✉️</span>
|
||||
Envoyer un message
|
||||
</a>
|
||||
<a class="nav-item" data-section="botappearance">
|
||||
<span class="nav-item-icon">🤖</span>
|
||||
Apparence du bot
|
||||
<a class="nav-item" data-section="scheduledmessages">
|
||||
<span class="nav-item-icon">⏰</span>
|
||||
Messages programmés
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Modération & Sécurité -->
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">🛡️ Sécurité</div>
|
||||
<a class="nav-item" data-section="antiraid">
|
||||
<span class="nav-item-icon">⚔️</span>
|
||||
Anti-Raid
|
||||
</a>
|
||||
<a class="nav-item" data-section="logs">
|
||||
<span class="nav-item-icon">📜</span>
|
||||
Logs
|
||||
</a>
|
||||
<a class="nav-item" data-section="antiraid">
|
||||
<span class="nav-item-icon">🛡️</span>
|
||||
Anti-Raid
|
||||
</a>
|
||||
<a class="nav-item" data-section="rolepanels">
|
||||
<span class="nav-item-icon">🎭</span>
|
||||
Rôles par boutons
|
||||
</div>
|
||||
|
||||
<!-- Personnalisation -->
|
||||
<div class="nav-section">
|
||||
<div class="nav-section-title">🎨 Personnalisation</div>
|
||||
<a class="nav-item" data-section="botappearance">
|
||||
<span class="nav-item-icon">🤖</span>
|
||||
Apparence du bot
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -133,9 +155,122 @@
|
||||
<select class="form-select" id="welcome-channel"></select>
|
||||
</div>
|
||||
|
||||
<!-- Type de message -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">Message</label>
|
||||
<textarea class="form-textarea" id="welcome-message" rows="4" placeholder="Ex: Bienvenue {user} sur {server} 🎉"></textarea>
|
||||
<label class="form-label">Type de message</label>
|
||||
<select class="form-select" id="welcome-message-type">
|
||||
<option value="text">📝 Texte simple</option>
|
||||
<option value="embed" selected>📦 Embed</option>
|
||||
<option value="both">📝📦 Texte + Embed</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Message texte -->
|
||||
<div class="form-group" id="welcome-text-group">
|
||||
<label class="form-label">Message texte</label>
|
||||
<textarea class="form-textarea" id="welcome-message" rows="3" placeholder="Ex: Bienvenue {mention} sur {server} 🎉"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Configuration Embed -->
|
||||
<div class="sub-section" id="welcome-embed-section">
|
||||
<h4 class="sub-section-title">📦 Configuration de l'embed</h4>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Titre de l'embed</label>
|
||||
<input type="text" class="form-input" id="welcome-embed-title" placeholder="👋 Bienvenue !">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Couleur</label>
|
||||
<input type="color" class="form-input form-color" id="welcome-embed-color" value="#57F287">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Description de l'embed</label>
|
||||
<textarea class="form-textarea" id="welcome-embed-description" rows="3" placeholder="Bienvenue {mention} sur **{server}** ! 🎉"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Footer (pied de page)</label>
|
||||
<input type="text" class="form-input" id="welcome-embed-footer" placeholder="Laissez vide pour le nom du serveur">
|
||||
</div>
|
||||
|
||||
<div class="inline-toggle">
|
||||
<div class="inline-toggle-label">
|
||||
<span class="inline-toggle-title">Afficher l'avatar</span>
|
||||
<span class="inline-toggle-desc">Afficher l'avatar du membre dans l'embed</span>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="welcome-embed-thumbnail" checked>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image générée -->
|
||||
<div class="sub-section">
|
||||
<h4 class="sub-section-title">🖼️ Image de bienvenue</h4>
|
||||
|
||||
<div class="inline-toggle">
|
||||
<div class="inline-toggle-label">
|
||||
<span class="inline-toggle-title">Générer une image</span>
|
||||
<span class="inline-toggle-desc">Créer une belle image personnalisée</span>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="welcome-image-enabled">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="welcome-image-options" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Style de dégradé</label>
|
||||
<div class="gradient-picker" id="welcome-gradient-picker">
|
||||
<div class="gradient-option selected" data-gradient="purple" style="background: linear-gradient(135deg, #667eea, #764ba2);" title="Violet"></div>
|
||||
<div class="gradient-option" data-gradient="blue" style="background: linear-gradient(135deg, #4facfe, #00f2fe);" title="Bleu"></div>
|
||||
<div class="gradient-option" data-gradient="green" style="background: linear-gradient(135deg, #11998e, #38ef7d);" title="Vert"></div>
|
||||
<div class="gradient-option" data-gradient="red" style="background: linear-gradient(135deg, #ff416c, #ff4b2b);" title="Rouge"></div>
|
||||
<div class="gradient-option" data-gradient="orange" style="background: linear-gradient(135deg, #f12711, #f5af19);" title="Orange"></div>
|
||||
<div class="gradient-option" data-gradient="pink" style="background: linear-gradient(135deg, #ee0979, #ff6a00);" title="Rose"></div>
|
||||
<div class="gradient-option" data-gradient="dark" style="background: linear-gradient(135deg, #232526, #414345);" title="Sombre"></div>
|
||||
<div class="gradient-option" data-gradient="sunset" style="background: linear-gradient(135deg, #fa709a, #fee140);" title="Coucher de soleil"></div>
|
||||
<div class="gradient-option" data-gradient="ocean" style="background: linear-gradient(135deg, #2193b0, #6dd5ed);" title="Océan"></div>
|
||||
<div class="gradient-option" data-gradient="forest" style="background: linear-gradient(135deg, #134e5e, #71b280);" title="Forêt"></div>
|
||||
</div>
|
||||
<input type="hidden" id="welcome-image-gradient" value="purple">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Titre personnalisé</label>
|
||||
<input type="text" class="form-input" id="welcome-image-title" placeholder="Bienvenue">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Sous-titre personnalisé</label>
|
||||
<input type="text" class="form-input" id="welcome-image-subtitle" placeholder="sur le serveur Discord">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inline-toggle">
|
||||
<div class="inline-toggle-label">
|
||||
<span class="inline-toggle-title">Afficher le nombre de membres</span>
|
||||
<span class="inline-toggle-desc">Affiche "Tu es le Xème membre !"</span>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="welcome-image-member-count" checked>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Aperçu de l'image -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">👁️ Aperçu de l'image</label>
|
||||
<div class="image-preview" id="welcome-image-preview">
|
||||
<div class="preview-placeholder">L'aperçu s'affichera ici</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="variables-box">
|
||||
@@ -144,6 +279,7 @@
|
||||
<span class="variable-tag"><code>{user}</code> <span>→ nom</span></span>
|
||||
<span class="variable-tag"><code>{mention}</code> <span>→ mention</span></span>
|
||||
<span class="variable-tag"><code>{server}</code> <span>→ serveur</span></span>
|
||||
<span class="variable-tag"><code>{membercount}</code> <span>→ nombre de membres</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -173,9 +309,122 @@
|
||||
<select class="form-select" id="goodbye-channel"></select>
|
||||
</div>
|
||||
|
||||
<!-- Type de message -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">Message</label>
|
||||
<textarea class="form-textarea" id="goodbye-message" rows="4" placeholder="Ex: Au revoir {user}, on espère te revoir sur {server} 👋"></textarea>
|
||||
<label class="form-label">Type de message</label>
|
||||
<select class="form-select" id="goodbye-message-type">
|
||||
<option value="text">📝 Texte simple</option>
|
||||
<option value="embed" selected>📦 Embed</option>
|
||||
<option value="both">📝📦 Texte + Embed</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Message texte -->
|
||||
<div class="form-group" id="goodbye-text-group">
|
||||
<label class="form-label">Message texte</label>
|
||||
<textarea class="form-textarea" id="goodbye-message" rows="3" placeholder="Ex: Au revoir {user}, on espère te revoir sur {server} 👋"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Configuration Embed -->
|
||||
<div class="sub-section" id="goodbye-embed-section">
|
||||
<h4 class="sub-section-title">📦 Configuration de l'embed</h4>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Titre de l'embed</label>
|
||||
<input type="text" class="form-input" id="goodbye-embed-title" placeholder="👋 Au revoir...">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Couleur</label>
|
||||
<input type="color" class="form-input form-color" id="goodbye-embed-color" value="#ED4245">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Description de l'embed</label>
|
||||
<textarea class="form-textarea" id="goodbye-embed-description" rows="3" placeholder="**{user}** a quitté le serveur. À bientôt ! 👋"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">Footer (pied de page)</label>
|
||||
<input type="text" class="form-input" id="goodbye-embed-footer" placeholder="Laissez vide pour le nom du serveur">
|
||||
</div>
|
||||
|
||||
<div class="inline-toggle">
|
||||
<div class="inline-toggle-label">
|
||||
<span class="inline-toggle-title">Afficher l'avatar</span>
|
||||
<span class="inline-toggle-desc">Afficher l'avatar du membre dans l'embed</span>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="goodbye-embed-thumbnail" checked>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image générée -->
|
||||
<div class="sub-section">
|
||||
<h4 class="sub-section-title">🖼️ Image d'au revoir</h4>
|
||||
|
||||
<div class="inline-toggle">
|
||||
<div class="inline-toggle-label">
|
||||
<span class="inline-toggle-title">Générer une image</span>
|
||||
<span class="inline-toggle-desc">Créer une belle image personnalisée</span>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="goodbye-image-enabled">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="goodbye-image-options" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Style de dégradé</label>
|
||||
<div class="gradient-picker" id="goodbye-gradient-picker">
|
||||
<div class="gradient-option" data-gradient="purple" style="background: linear-gradient(135deg, #667eea, #764ba2);" title="Violet"></div>
|
||||
<div class="gradient-option" data-gradient="blue" style="background: linear-gradient(135deg, #4facfe, #00f2fe);" title="Bleu"></div>
|
||||
<div class="gradient-option" data-gradient="green" style="background: linear-gradient(135deg, #11998e, #38ef7d);" title="Vert"></div>
|
||||
<div class="gradient-option selected" data-gradient="red" style="background: linear-gradient(135deg, #ff416c, #ff4b2b);" title="Rouge"></div>
|
||||
<div class="gradient-option" data-gradient="orange" style="background: linear-gradient(135deg, #f12711, #f5af19);" title="Orange"></div>
|
||||
<div class="gradient-option" data-gradient="pink" style="background: linear-gradient(135deg, #ee0979, #ff6a00);" title="Rose"></div>
|
||||
<div class="gradient-option" data-gradient="dark" style="background: linear-gradient(135deg, #232526, #414345);" title="Sombre"></div>
|
||||
<div class="gradient-option" data-gradient="sunset" style="background: linear-gradient(135deg, #fa709a, #fee140);" title="Coucher de soleil"></div>
|
||||
<div class="gradient-option" data-gradient="ocean" style="background: linear-gradient(135deg, #2193b0, #6dd5ed);" title="Océan"></div>
|
||||
<div class="gradient-option" data-gradient="forest" style="background: linear-gradient(135deg, #134e5e, #71b280);" title="Forêt"></div>
|
||||
</div>
|
||||
<input type="hidden" id="goodbye-image-gradient" value="red">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Titre personnalisé</label>
|
||||
<input type="text" class="form-input" id="goodbye-image-title" placeholder="Au revoir">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Sous-titre personnalisé</label>
|
||||
<input type="text" class="form-input" id="goodbye-image-subtitle" placeholder="a quitté le serveur">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="inline-toggle">
|
||||
<div class="inline-toggle-label">
|
||||
<span class="inline-toggle-title">Afficher le nombre de membres</span>
|
||||
<span class="inline-toggle-desc">Affiche "Il reste X membres"</span>
|
||||
</div>
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="goodbye-image-member-count" checked>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Aperçu de l'image -->
|
||||
<div class="form-group">
|
||||
<label class="form-label">👁️ Aperçu de l'image</label>
|
||||
<div class="image-preview" id="goodbye-image-preview">
|
||||
<div class="preview-placeholder">L'aperçu s'affichera ici</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="variables-box">
|
||||
@@ -183,6 +432,7 @@
|
||||
<div class="variables-list">
|
||||
<span class="variable-tag"><code>{user}</code> <span>→ nom</span></span>
|
||||
<span class="variable-tag"><code>{server}</code> <span>→ serveur</span></span>
|
||||
<span class="variable-tag"><code>{membercount}</code> <span>→ nombre de membres</span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,158 @@
|
||||
// ========== GOODBYE FORM ==========
|
||||
const goodbyeEnabled = document.getElementById("goodbye-enabled");
|
||||
const goodbyeChannel = document.getElementById("goodbye-channel");
|
||||
const goodbyeMessage = document.getElementById("goodbye-message");
|
||||
const goodbyeMessageType = document.getElementById("goodbye-message-type");
|
||||
const goodbyeEmbedTitle = document.getElementById("goodbye-embed-title");
|
||||
const goodbyeEmbedDescription = document.getElementById("goodbye-embed-description");
|
||||
const goodbyeEmbedColor = document.getElementById("goodbye-embed-color");
|
||||
const goodbyeEmbedThumbnail = document.getElementById("goodbye-embed-thumbnail");
|
||||
const goodbyeEmbedFooter = document.getElementById("goodbye-embed-footer");
|
||||
const goodbyeImageEnabled = document.getElementById("goodbye-image-enabled");
|
||||
const goodbyeImageGradient = document.getElementById("goodbye-image-gradient");
|
||||
const goodbyeImageTitle = document.getElementById("goodbye-image-title");
|
||||
const goodbyeImageSubtitle = document.getElementById("goodbye-image-subtitle");
|
||||
const goodbyeImageMemberCount = document.getElementById("goodbye-image-member-count");
|
||||
const saveGoodbye = document.getElementById("save-goodbye");
|
||||
|
||||
// Message par défaut
|
||||
const defaultGoodbyeMessage = "Au revoir **{user}**, on espère te revoir sur **{server}** ! 👋";
|
||||
const goodbyeTextGroup = document.getElementById("goodbye-text-group");
|
||||
const goodbyeEmbedSection = document.getElementById("goodbye-embed-section");
|
||||
const goodbyeImageOptions = document.getElementById("goodbye-image-options");
|
||||
const goodbyeGradientPicker = document.getElementById("goodbye-gradient-picker");
|
||||
const goodbyeImagePreview = document.getElementById("goodbye-image-preview");
|
||||
|
||||
// Afficher/masquer les sections selon le type de message
|
||||
function updateGoodbyeVisibility() {
|
||||
const type = goodbyeMessageType.value;
|
||||
|
||||
// Text group visible si text ou both
|
||||
goodbyeTextGroup.style.display = (type === 'text' || type === 'both') ? 'block' : 'none';
|
||||
|
||||
// Embed section visible si embed ou both
|
||||
goodbyeEmbedSection.style.display = (type === 'embed' || type === 'both') ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// Gestion du gradient picker
|
||||
if (goodbyeGradientPicker) {
|
||||
goodbyeGradientPicker.addEventListener('click', (e) => {
|
||||
const option = e.target.closest('.gradient-option');
|
||||
if (!option) return;
|
||||
|
||||
goodbyeGradientPicker.querySelectorAll('.gradient-option').forEach(opt => opt.classList.remove('selected'));
|
||||
option.classList.add('selected');
|
||||
goodbyeImageGradient.value = option.dataset.gradient;
|
||||
updateGoodbyePreview();
|
||||
});
|
||||
}
|
||||
|
||||
// Afficher/masquer les options d'image
|
||||
if (goodbyeImageEnabled) {
|
||||
goodbyeImageEnabled.addEventListener('change', () => {
|
||||
goodbyeImageOptions.style.display = goodbyeImageEnabled.checked ? 'block' : 'none';
|
||||
if (goodbyeImageEnabled.checked) {
|
||||
updateGoodbyePreview();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Mise à jour de l'aperçu de l'image
|
||||
function updateGoodbyePreview() {
|
||||
if (!goodbyeImagePreview) return;
|
||||
|
||||
const gradient = goodbyeImageGradient.value || 'red';
|
||||
const title = goodbyeImageTitle.value || 'Au revoir';
|
||||
const subtitle = goodbyeImageSubtitle.value || 'a quitté le serveur';
|
||||
|
||||
// Utiliser les mêmes fonctions que welcome (définies globalement)
|
||||
const colors = typeof getGradientColors === 'function' ? getGradientColors(gradient) : ['#ff416c', '#ff4b2b'];
|
||||
|
||||
// Créer un aperçu simplifié avec CSS
|
||||
goodbyeImagePreview.innerHTML = `
|
||||
<div class="preview-card" style="
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
height: 150px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, ${colors[0]}, ${colors[1]});
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
gap: 20px;
|
||||
color: white;
|
||||
font-family: Arial, sans-serif;
|
||||
">
|
||||
<div style="
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.3);
|
||||
border: 3px solid white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
">👤</div>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-size: 24px; font-weight: bold; text-shadow: 0 2px 4px rgba(0,0,0,0.3);">${escapeHtmlGoodbye(title)}</div>
|
||||
<div style="font-size: 14px; opacity: 0.9;">${escapeHtmlGoodbye(subtitle)}</div>
|
||||
<div style="font-size: 16px; font-style: italic; margin-top: 8px;">NomServeur</div>
|
||||
<div style="font-size: 14px; font-weight: bold; opacity: 0.8; margin-top: 4px;">Utilisateur</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function escapeHtmlGoodbye(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Event listeners pour la mise à jour de l'aperçu
|
||||
if (goodbyeImageTitle) goodbyeImageTitle.addEventListener('input', updateGoodbyePreview);
|
||||
if (goodbyeImageSubtitle) goodbyeImageSubtitle.addEventListener('input', updateGoodbyePreview);
|
||||
|
||||
if (goodbyeMessageType) {
|
||||
goodbyeMessageType.addEventListener('change', updateGoodbyeVisibility);
|
||||
}
|
||||
|
||||
// Charger la config
|
||||
fetch(`/api/bot/get-goodbye-config/${guildId}`)
|
||||
.then(res => res.json())
|
||||
.then(cfg => {
|
||||
goodbyeEnabled.checked = cfg.enabled;
|
||||
goodbyeChannel.value = cfg.channelId;
|
||||
goodbyeMessage.value = cfg.message || defaultGoodbyeMessage;
|
||||
goodbyeChannel.value = cfg.channelId || '';
|
||||
goodbyeMessage.value = cfg.message || '';
|
||||
goodbyeMessageType.value = cfg.messageType || 'embed';
|
||||
goodbyeEmbedTitle.value = cfg.embedTitle || '';
|
||||
goodbyeEmbedDescription.value = cfg.embedDescription || '';
|
||||
goodbyeEmbedColor.value = cfg.embedColor || '#ED4245';
|
||||
goodbyeEmbedThumbnail.checked = cfg.embedThumbnail !== false;
|
||||
goodbyeEmbedFooter.value = cfg.embedFooter || '';
|
||||
goodbyeImageEnabled.checked = cfg.imageEnabled || false;
|
||||
goodbyeImageGradient.value = cfg.imageGradient || 'red';
|
||||
goodbyeImageTitle.value = cfg.imageTitle || '';
|
||||
goodbyeImageSubtitle.value = cfg.imageSubtitle || '';
|
||||
goodbyeImageMemberCount.checked = cfg.imageShowMemberCount !== false;
|
||||
|
||||
// Update visibility
|
||||
updateGoodbyeVisibility();
|
||||
|
||||
// Update gradient picker selection
|
||||
if (goodbyeGradientPicker) {
|
||||
goodbyeGradientPicker.querySelectorAll('.gradient-option').forEach(opt => {
|
||||
opt.classList.toggle('selected', opt.dataset.gradient === cfg.imageGradient);
|
||||
});
|
||||
}
|
||||
|
||||
// Show image options if enabled
|
||||
if (goodbyeImageOptions) {
|
||||
goodbyeImageOptions.style.display = cfg.imageEnabled ? 'block' : 'none';
|
||||
}
|
||||
|
||||
if (cfg.imageEnabled) {
|
||||
updateGoodbyePreview();
|
||||
}
|
||||
});
|
||||
|
||||
// Sauvegarder
|
||||
@@ -28,7 +168,18 @@ saveGoodbye.addEventListener("click", async () => {
|
||||
guildId,
|
||||
goodbyeEnabled: goodbyeEnabled.checked,
|
||||
channelId: goodbyeChannel.value,
|
||||
goodbyeMessage: goodbyeMessage.value
|
||||
goodbyeMessage: goodbyeMessage.value,
|
||||
messageType: goodbyeMessageType.value,
|
||||
embedTitle: goodbyeEmbedTitle.value,
|
||||
embedDescription: goodbyeEmbedDescription.value,
|
||||
embedColor: goodbyeEmbedColor.value,
|
||||
embedThumbnail: goodbyeEmbedThumbnail.checked,
|
||||
embedFooter: goodbyeEmbedFooter.value,
|
||||
imageEnabled: goodbyeImageEnabled.checked,
|
||||
imageGradient: goodbyeImageGradient.value,
|
||||
imageTitle: goodbyeImageTitle.value,
|
||||
imageSubtitle: goodbyeImageSubtitle.value,
|
||||
imageShowMemberCount: goodbyeImageMemberCount.checked
|
||||
})
|
||||
});
|
||||
|
||||
@@ -45,3 +196,6 @@ saveGoodbye.addEventListener("click", async () => {
|
||||
saveGoodbye.disabled = false;
|
||||
saveGoodbye.textContent = "Sauvegarder";
|
||||
});
|
||||
|
||||
// Initialisation
|
||||
updateGoodbyeVisibility();
|
||||
|
||||
@@ -1,18 +1,171 @@
|
||||
// ========== WELCOME FORM ==========
|
||||
const welcomeEnabled = document.getElementById("welcome-enabled");
|
||||
const welcomeChannel = document.getElementById("welcome-channel");
|
||||
const welcomeMessage = document.getElementById("welcome-message");
|
||||
const welcomeMessageType = document.getElementById("welcome-message-type");
|
||||
const welcomeEmbedTitle = document.getElementById("welcome-embed-title");
|
||||
const welcomeEmbedDescription = document.getElementById("welcome-embed-description");
|
||||
const welcomeEmbedColor = document.getElementById("welcome-embed-color");
|
||||
const welcomeEmbedThumbnail = document.getElementById("welcome-embed-thumbnail");
|
||||
const welcomeEmbedFooter = document.getElementById("welcome-embed-footer");
|
||||
const welcomeImageEnabled = document.getElementById("welcome-image-enabled");
|
||||
const welcomeImageGradient = document.getElementById("welcome-image-gradient");
|
||||
const welcomeImageTitle = document.getElementById("welcome-image-title");
|
||||
const welcomeImageSubtitle = document.getElementById("welcome-image-subtitle");
|
||||
const welcomeImageMemberCount = document.getElementById("welcome-image-member-count");
|
||||
const saveWelcome = document.getElementById("save-welcome");
|
||||
|
||||
// Message par défaut
|
||||
const defaultWelcomeMessage = "Bienvenue {mention} sur **{server}** ! 🎉";
|
||||
const welcomeTextGroup = document.getElementById("welcome-text-group");
|
||||
const welcomeEmbedSection = document.getElementById("welcome-embed-section");
|
||||
const welcomeImageOptions = document.getElementById("welcome-image-options");
|
||||
const welcomeGradientPicker = document.getElementById("welcome-gradient-picker");
|
||||
const welcomeImagePreview = document.getElementById("welcome-image-preview");
|
||||
|
||||
// Afficher/masquer les sections selon le type de message
|
||||
function updateWelcomeVisibility() {
|
||||
const type = welcomeMessageType.value;
|
||||
|
||||
// Text group visible si text ou both
|
||||
welcomeTextGroup.style.display = (type === 'text' || type === 'both') ? 'block' : 'none';
|
||||
|
||||
// Embed section visible si embed ou both
|
||||
welcomeEmbedSection.style.display = (type === 'embed' || type === 'both') ? 'block' : 'none';
|
||||
}
|
||||
|
||||
// Gestion du gradient picker
|
||||
if (welcomeGradientPicker) {
|
||||
welcomeGradientPicker.addEventListener('click', (e) => {
|
||||
const option = e.target.closest('.gradient-option');
|
||||
if (!option) return;
|
||||
|
||||
welcomeGradientPicker.querySelectorAll('.gradient-option').forEach(opt => opt.classList.remove('selected'));
|
||||
option.classList.add('selected');
|
||||
welcomeImageGradient.value = option.dataset.gradient;
|
||||
updateWelcomePreview();
|
||||
});
|
||||
}
|
||||
|
||||
// Afficher/masquer les options d'image
|
||||
if (welcomeImageEnabled) {
|
||||
welcomeImageEnabled.addEventListener('change', () => {
|
||||
welcomeImageOptions.style.display = welcomeImageEnabled.checked ? 'block' : 'none';
|
||||
if (welcomeImageEnabled.checked) {
|
||||
updateWelcomePreview();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Mise à jour de l'aperçu de l'image
|
||||
function updateWelcomePreview() {
|
||||
if (!welcomeImagePreview) return;
|
||||
|
||||
const gradient = welcomeImageGradient.value || 'purple';
|
||||
const title = welcomeImageTitle.value || 'Bienvenue';
|
||||
const subtitle = welcomeImageSubtitle.value || 'sur le serveur Discord';
|
||||
|
||||
// Créer un aperçu simplifié avec CSS
|
||||
welcomeImagePreview.innerHTML = `
|
||||
<div class="preview-card" style="
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
height: 150px;
|
||||
border-radius: 12px;
|
||||
background: linear-gradient(135deg, ${getGradientColors(gradient)[0]}, ${getGradientColors(gradient)[1]});
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
gap: 20px;
|
||||
color: white;
|
||||
font-family: Arial, sans-serif;
|
||||
">
|
||||
<div style="
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255,255,255,0.3);
|
||||
border: 3px solid white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 24px;
|
||||
">👤</div>
|
||||
<div style="flex: 1;">
|
||||
<div style="font-size: 24px; font-weight: bold; text-shadow: 0 2px 4px rgba(0,0,0,0.3);">${escapeHtml(title)}</div>
|
||||
<div style="font-size: 14px; opacity: 0.9;">${escapeHtml(subtitle)}</div>
|
||||
<div style="font-size: 16px; font-style: italic; margin-top: 8px;">NomServeur</div>
|
||||
<div style="font-size: 14px; font-weight: bold; opacity: 0.8; margin-top: 4px;">Utilisateur</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function getGradientColors(gradient) {
|
||||
const gradients = {
|
||||
purple: ['#667eea', '#764ba2'],
|
||||
blue: ['#4facfe', '#00f2fe'],
|
||||
green: ['#11998e', '#38ef7d'],
|
||||
red: ['#ff416c', '#ff4b2b'],
|
||||
orange: ['#f12711', '#f5af19'],
|
||||
pink: ['#ee0979', '#ff6a00'],
|
||||
dark: ['#232526', '#414345'],
|
||||
sunset: ['#fa709a', '#fee140'],
|
||||
ocean: ['#2193b0', '#6dd5ed'],
|
||||
forest: ['#134e5e', '#71b280']
|
||||
};
|
||||
return gradients[gradient] || gradients.purple;
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Event listeners pour la mise à jour de l'aperçu
|
||||
if (welcomeImageTitle) welcomeImageTitle.addEventListener('input', updateWelcomePreview);
|
||||
if (welcomeImageSubtitle) welcomeImageSubtitle.addEventListener('input', updateWelcomePreview);
|
||||
|
||||
if (welcomeMessageType) {
|
||||
welcomeMessageType.addEventListener('change', updateWelcomeVisibility);
|
||||
}
|
||||
|
||||
// Charger la config
|
||||
fetch(`/api/bot/get-welcome-config/${guildId}`)
|
||||
.then(res => res.json())
|
||||
.then(cfg => {
|
||||
welcomeEnabled.checked = cfg.enabled;
|
||||
welcomeChannel.value = cfg.channelId;
|
||||
welcomeMessage.value = cfg.message || defaultWelcomeMessage;
|
||||
welcomeChannel.value = cfg.channelId || '';
|
||||
welcomeMessage.value = cfg.message || '';
|
||||
welcomeMessageType.value = cfg.messageType || 'embed';
|
||||
welcomeEmbedTitle.value = cfg.embedTitle || '';
|
||||
welcomeEmbedDescription.value = cfg.embedDescription || '';
|
||||
welcomeEmbedColor.value = cfg.embedColor || '#57F287';
|
||||
welcomeEmbedThumbnail.checked = cfg.embedThumbnail !== false;
|
||||
welcomeEmbedFooter.value = cfg.embedFooter || '';
|
||||
welcomeImageEnabled.checked = cfg.imageEnabled || false;
|
||||
welcomeImageGradient.value = cfg.imageGradient || 'purple';
|
||||
welcomeImageTitle.value = cfg.imageTitle || '';
|
||||
welcomeImageSubtitle.value = cfg.imageSubtitle || '';
|
||||
welcomeImageMemberCount.checked = cfg.imageShowMemberCount !== false;
|
||||
|
||||
// Update visibility
|
||||
updateWelcomeVisibility();
|
||||
|
||||
// Update gradient picker selection
|
||||
if (welcomeGradientPicker) {
|
||||
welcomeGradientPicker.querySelectorAll('.gradient-option').forEach(opt => {
|
||||
opt.classList.toggle('selected', opt.dataset.gradient === cfg.imageGradient);
|
||||
});
|
||||
}
|
||||
|
||||
// Show image options if enabled
|
||||
if (welcomeImageOptions) {
|
||||
welcomeImageOptions.style.display = cfg.imageEnabled ? 'block' : 'none';
|
||||
}
|
||||
|
||||
if (cfg.imageEnabled) {
|
||||
updateWelcomePreview();
|
||||
}
|
||||
});
|
||||
|
||||
// Sauvegarder
|
||||
@@ -28,7 +181,18 @@ saveWelcome.addEventListener("click", async () => {
|
||||
guildId,
|
||||
welcomeEnabled: welcomeEnabled.checked,
|
||||
channelId: welcomeChannel.value,
|
||||
welcomeMessage: welcomeMessage.value
|
||||
welcomeMessage: welcomeMessage.value,
|
||||
messageType: welcomeMessageType.value,
|
||||
embedTitle: welcomeEmbedTitle.value,
|
||||
embedDescription: welcomeEmbedDescription.value,
|
||||
embedColor: welcomeEmbedColor.value,
|
||||
embedThumbnail: welcomeEmbedThumbnail.checked,
|
||||
embedFooter: welcomeEmbedFooter.value,
|
||||
imageEnabled: welcomeImageEnabled.checked,
|
||||
imageGradient: welcomeImageGradient.value,
|
||||
imageTitle: welcomeImageTitle.value,
|
||||
imageSubtitle: welcomeImageSubtitle.value,
|
||||
imageShowMemberCount: welcomeImageMemberCount.checked
|
||||
})
|
||||
});
|
||||
|
||||
@@ -45,3 +209,6 @@ saveWelcome.addEventListener("click", async () => {
|
||||
saveWelcome.disabled = false;
|
||||
saveWelcome.textContent = "Sauvegarder";
|
||||
});
|
||||
|
||||
// Initialisation
|
||||
updateWelcomeVisibility();
|
||||
|
||||
+108
-28
@@ -46,7 +46,11 @@ module.exports = (app, db, client) => {
|
||||
|
||||
// API pour sauvegarder la configuration de bienvenue
|
||||
router.post("/bot/save-welcome-config", express.json(), (req, res) => {
|
||||
const { guildId, channelId, welcomeEnabled, welcomeMessage } = req.body;
|
||||
const {
|
||||
guildId, channelId, welcomeEnabled, welcomeMessage,
|
||||
messageType, embedTitle, embedDescription, embedColor, embedThumbnail, embedFooter,
|
||||
imageEnabled, imageGradient, imageTitle, imageSubtitle, imageShowMemberCount
|
||||
} = req.body;
|
||||
|
||||
if (!req.session.guilds) {
|
||||
return res.status(401).json({ success: false });
|
||||
@@ -62,19 +66,26 @@ module.exports = (app, db, client) => {
|
||||
|
||||
db.run(
|
||||
`
|
||||
INSERT INTO welcome_config (guild_id, channel_id, enabled, message)
|
||||
VALUES (?, ?, ?, ?)
|
||||
INSERT INTO welcome_config (
|
||||
guild_id, channel_id, enabled, message, message_type,
|
||||
embed_title, embed_description, embed_color, embed_thumbnail, embed_footer,
|
||||
image_enabled, image_gradient, image_title, image_subtitle, image_show_member_count
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(guild_id)
|
||||
DO UPDATE SET channel_id = ?, enabled = ?, message = ?
|
||||
DO UPDATE SET
|
||||
channel_id = ?, enabled = ?, message = ?, message_type = ?,
|
||||
embed_title = ?, embed_description = ?, embed_color = ?, embed_thumbnail = ?, embed_footer = ?,
|
||||
image_enabled = ?, image_gradient = ?, image_title = ?, image_subtitle = ?, image_show_member_count = ?
|
||||
`,
|
||||
[
|
||||
guildId,
|
||||
channelId,
|
||||
welcomeEnabled ? 1 : 0,
|
||||
welcomeMessage,
|
||||
channelId,
|
||||
welcomeEnabled ? 1 : 0,
|
||||
welcomeMessage
|
||||
guildId, channelId, welcomeEnabled ? 1 : 0, welcomeMessage, messageType || 'embed',
|
||||
embedTitle || null, embedDescription || null, embedColor || '#57F287', embedThumbnail ? 1 : 0, embedFooter || null,
|
||||
imageEnabled ? 1 : 0, imageGradient || 'purple', imageTitle || null, imageSubtitle || null, imageShowMemberCount ? 1 : 0,
|
||||
// UPDATE values
|
||||
channelId, welcomeEnabled ? 1 : 0, welcomeMessage, messageType || 'embed',
|
||||
embedTitle || null, embedDescription || null, embedColor || '#57F287', embedThumbnail ? 1 : 0, embedFooter || null,
|
||||
imageEnabled ? 1 : 0, imageGradient || 'purple', imageTitle || null, imageSubtitle || null, imageShowMemberCount ? 1 : 0
|
||||
],
|
||||
err => {
|
||||
if (err) {
|
||||
@@ -91,16 +102,45 @@ module.exports = (app, db, client) => {
|
||||
const { guildId } = req.params;
|
||||
|
||||
db.get(
|
||||
"SELECT enabled, channel_id, message FROM welcome_config WHERE guild_id = ?",
|
||||
`SELECT enabled, channel_id, message, message_type,
|
||||
embed_title, embed_description, embed_color, embed_thumbnail, embed_footer,
|
||||
image_enabled, image_gradient, image_title, image_subtitle, image_show_member_count
|
||||
FROM welcome_config WHERE guild_id = ?`,
|
||||
[guildId],
|
||||
(err, row) => {
|
||||
if (err || !row) {
|
||||
return res.json({ enabled: false, channelId: null, message: "" });
|
||||
return res.json({
|
||||
enabled: false,
|
||||
channelId: null,
|
||||
message: "",
|
||||
messageType: "embed",
|
||||
embedTitle: "",
|
||||
embedDescription: "",
|
||||
embedColor: "#57F287",
|
||||
embedThumbnail: true,
|
||||
embedFooter: "",
|
||||
imageEnabled: false,
|
||||
imageGradient: "purple",
|
||||
imageTitle: "",
|
||||
imageSubtitle: "",
|
||||
imageShowMemberCount: true
|
||||
});
|
||||
}
|
||||
res.json({
|
||||
enabled: !!row.enabled,
|
||||
channelId: row.channel_id,
|
||||
message: row.message
|
||||
message: row.message || "",
|
||||
messageType: row.message_type || "embed",
|
||||
embedTitle: row.embed_title || "",
|
||||
embedDescription: row.embed_description || "",
|
||||
embedColor: row.embed_color || "#57F287",
|
||||
embedThumbnail: !!row.embed_thumbnail,
|
||||
embedFooter: row.embed_footer || "",
|
||||
imageEnabled: !!row.image_enabled,
|
||||
imageGradient: row.image_gradient || "purple",
|
||||
imageTitle: row.image_title || "",
|
||||
imageSubtitle: row.image_subtitle || "",
|
||||
imageShowMemberCount: row.image_show_member_count !== 0
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -108,7 +148,11 @@ module.exports = (app, db, client) => {
|
||||
|
||||
|
||||
router.post("/bot/save-goodbye-config", express.json(), (req, res) => {
|
||||
const { guildId, channelId, goodbyeEnabled, goodbyeMessage } = req.body;
|
||||
const {
|
||||
guildId, channelId, goodbyeEnabled, goodbyeMessage,
|
||||
messageType, embedTitle, embedDescription, embedColor, embedThumbnail, embedFooter,
|
||||
imageEnabled, imageGradient, imageTitle, imageSubtitle, imageShowMemberCount
|
||||
} = req.body;
|
||||
|
||||
if (!req.session.guilds) {
|
||||
return res.status(401).json({ success: false });
|
||||
@@ -124,19 +168,26 @@ module.exports = (app, db, client) => {
|
||||
|
||||
db.run(
|
||||
`
|
||||
INSERT INTO goodbye_config (guild_id, channel_id, enabled, message)
|
||||
VALUES (?, ?, ?, ?)
|
||||
INSERT INTO goodbye_config (
|
||||
guild_id, channel_id, enabled, message, message_type,
|
||||
embed_title, embed_description, embed_color, embed_thumbnail, embed_footer,
|
||||
image_enabled, image_gradient, image_title, image_subtitle, image_show_member_count
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(guild_id)
|
||||
DO UPDATE SET channel_id = ?, enabled = ?, message = ?
|
||||
DO UPDATE SET
|
||||
channel_id = ?, enabled = ?, message = ?, message_type = ?,
|
||||
embed_title = ?, embed_description = ?, embed_color = ?, embed_thumbnail = ?, embed_footer = ?,
|
||||
image_enabled = ?, image_gradient = ?, image_title = ?, image_subtitle = ?, image_show_member_count = ?
|
||||
`,
|
||||
[
|
||||
guildId,
|
||||
channelId,
|
||||
goodbyeEnabled ? 1 : 0,
|
||||
goodbyeMessage,
|
||||
channelId,
|
||||
goodbyeEnabled ? 1 : 0,
|
||||
goodbyeMessage
|
||||
guildId, channelId, goodbyeEnabled ? 1 : 0, goodbyeMessage, messageType || 'embed',
|
||||
embedTitle || null, embedDescription || null, embedColor || '#ED4245', embedThumbnail ? 1 : 0, embedFooter || null,
|
||||
imageEnabled ? 1 : 0, imageGradient || 'red', imageTitle || null, imageSubtitle || null, imageShowMemberCount ? 1 : 0,
|
||||
// UPDATE values
|
||||
channelId, goodbyeEnabled ? 1 : 0, goodbyeMessage, messageType || 'embed',
|
||||
embedTitle || null, embedDescription || null, embedColor || '#ED4245', embedThumbnail ? 1 : 0, embedFooter || null,
|
||||
imageEnabled ? 1 : 0, imageGradient || 'red', imageTitle || null, imageSubtitle || null, imageShowMemberCount ? 1 : 0
|
||||
],
|
||||
err => {
|
||||
if (err) {
|
||||
@@ -153,16 +204,45 @@ module.exports = (app, db, client) => {
|
||||
const { guildId } = req.params;
|
||||
|
||||
db.get(
|
||||
"SELECT enabled, channel_id, message FROM goodbye_config WHERE guild_id = ?",
|
||||
`SELECT enabled, channel_id, message, message_type,
|
||||
embed_title, embed_description, embed_color, embed_thumbnail, embed_footer,
|
||||
image_enabled, image_gradient, image_title, image_subtitle, image_show_member_count
|
||||
FROM goodbye_config WHERE guild_id = ?`,
|
||||
[guildId],
|
||||
(err, row) => {
|
||||
if (err || !row) {
|
||||
return res.json({ enabled: false, channelId: null, message: "" });
|
||||
return res.json({
|
||||
enabled: false,
|
||||
channelId: null,
|
||||
message: "",
|
||||
messageType: "embed",
|
||||
embedTitle: "",
|
||||
embedDescription: "",
|
||||
embedColor: "#ED4245",
|
||||
embedThumbnail: true,
|
||||
embedFooter: "",
|
||||
imageEnabled: false,
|
||||
imageGradient: "red",
|
||||
imageTitle: "",
|
||||
imageSubtitle: "",
|
||||
imageShowMemberCount: true
|
||||
});
|
||||
}
|
||||
res.json({
|
||||
enabled: !!row.enabled,
|
||||
channelId: row.channel_id,
|
||||
message: row.message
|
||||
message: row.message || "",
|
||||
messageType: row.message_type || "embed",
|
||||
embedTitle: row.embed_title || "",
|
||||
embedDescription: row.embed_description || "",
|
||||
embedColor: row.embed_color || "#ED4245",
|
||||
embedThumbnail: !!row.embed_thumbnail,
|
||||
embedFooter: row.embed_footer || "",
|
||||
imageEnabled: !!row.image_enabled,
|
||||
imageGradient: row.image_gradient || "red",
|
||||
imageTitle: row.image_title || "",
|
||||
imageSubtitle: row.image_subtitle || "",
|
||||
imageShowMemberCount: row.image_show_member_count !== 0
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user