diff --git a/app/db.js b/app/db.js index c92f0bb..5f4faa5 100644 --- a/app/db.js +++ b/app/db.js @@ -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; diff --git a/app/events/guildMemberAdd.js b/app/events/guildMemberAdd.js index 92e2d3e..155c629 100644 --- a/app/events/guildMemberAdd.js +++ b/app/events/guildMemberAdd.js @@ -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; + 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] + ); - let msg = row.message || "Bienvenue {mention} sur {server} !"; + if (!row || !row.enabled) return processAutorole(); - 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) return processAutorole(); - 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(); + // 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); + }; - channel.send({ embeds: [embed] }); + 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( - "SELECT enabled, role_id FROM autorole_newuser_config WHERE guild_id = ?", - [member.guild.id], - (err, row) => { - if (err || !row || !row.enabled) return; + async function processAutorole() { + try { + const row = await db.getAsync( + "SELECT enabled, role_id FROM autorole_newuser_config WHERE guild_id = ?", + [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(); }, }; diff --git a/app/events/guildMemberRemove.js b/app/events/guildMemberRemove.js index 5f006e6..d4db305 100644 --- a/app/events/guildMemberRemove.js +++ b/app/events/guildMemberRemove.js @@ -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; - - let msg = row.message || "Au revoir {user}, tu vas nous manquer !"; - - msg = msg - .replace("{user}", member.user.username) - .replace("{server}", member.guild.name); - - const channel = member.guild.channels.cache.get(row.channel_id); - if (channel) { - 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(); + 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] + ); - channel.send({ embeds: [embed] }); + if (!row || !row.enabled) return; + + const channel = member.guild.channels.cache.get(row.channel_id); + 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(row.embed_color || '#ED4245') + .setTimestamp(); + + 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); + } }, }; diff --git a/app/fonctions/generateWelcomeImage.js b/app/fonctions/generateWelcomeImage.js new file mode 100644 index 0000000..8991696 --- /dev/null +++ b/app/fonctions/generateWelcomeImage.js @@ -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 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 }; diff --git a/app/package-lock.json b/app/package-lock.json index d50f42f..c376139 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -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" } diff --git a/app/package.json b/app/package.json index 371d96e..3678381 100644 --- a/app/package.json +++ b/app/package.json @@ -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" } } diff --git a/app/public/guild.css b/app/public/guild.css index 229b4d8..3b9e329 100644 --- a/app/public/guild.css +++ b/app/public/guild.css @@ -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); +} diff --git a/app/public/guild.html b/app/public/guild.html index b4d098c..c0ecfd3 100644 --- a/app/public/guild.html +++ b/app/public/guild.html @@ -24,8 +24,9 @@ @@ -132,10 +154,123 @@ - + +
- - + + +
+ + +
+ + +
+ + +
+

📦 Configuration de l'embed

+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+
+ Afficher l'avatar + Afficher l'avatar du membre dans l'embed +
+ +
+
+ + +
+

🖼️ Image de bienvenue

+ +
+
+ Générer une image + Créer une belle image personnalisée +
+ +
+ +
@@ -144,6 +279,7 @@ {user} → nom {mention} → mention {server} → serveur + {membercount} → nombre de membres
@@ -172,10 +308,123 @@ - + +
- - + + +
+ + +
+ + +
+ + +
+

📦 Configuration de l'embed

+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+
+ Afficher l'avatar + Afficher l'avatar du membre dans l'embed +
+ +
+
+ + +
+

🖼️ Image d'au revoir

+ +
+
+ Générer une image + Créer une belle image personnalisée +
+ +
+ +
@@ -183,6 +432,7 @@
{user} → nom {server} → serveur + {membercount} → nombre de membres
diff --git a/app/public/guild/goodbyeForm.js b/app/public/guild/goodbyeForm.js index 32f39de..bc1b094 100644 --- a/app/public/guild/goodbyeForm.js +++ b/app/public/guild/goodbyeForm.js @@ -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 = ` +
+
👤
+
+
${escapeHtmlGoodbye(title)}
+
${escapeHtmlGoodbye(subtitle)}
+
NomServeur
+
Utilisateur
+
+
+ `; +} + +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(); diff --git a/app/public/guild/welcomeForm.js b/app/public/guild/welcomeForm.js index eb967dc..da9f31f 100644 --- a/app/public/guild/welcomeForm.js +++ b/app/public/guild/welcomeForm.js @@ -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 = ` +
+
👤
+
+
${escapeHtml(title)}
+
${escapeHtml(subtitle)}
+
NomServeur
+
Utilisateur
+
+
+ `; +} + +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(); diff --git a/app/routes/api.js b/app/routes/api.js index eb458de..df3f9f6 100644 --- a/app/routes/api.js +++ b/app/routes/api.js @@ -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 }); } );