diff --git a/app/bot.js b/app/bot.js index def4d6d..622e6cf 100644 --- a/app/bot.js +++ b/app/bot.js @@ -410,6 +410,148 @@ client.once("clientReady", async () => { }); +// ===== SCHEDULED MESSAGES SYSTEM ===== +const { EmbedBuilder } = require("discord.js"); + +// Track last channel activity +const channelLastActivity = new Map(); + +// Update last activity on message +client.on("messageCreate", (message) => { + if (!message.guild || message.author.bot) return; + channelLastActivity.set(message.channel.id, Date.now()); +}); + +// Process scheduled messages +async function processScheduledMessages() { + try { + const messages = await db.allAsync( + "SELECT * FROM scheduled_messages WHERE enabled = 1" + ); + + // Utiliser le fuseau horaire français + const now = new Date(); + const parisTime = new Date(now.toLocaleString("en-US", { timeZone: "Europe/Paris" })); + const currentDay = parisTime.getDay(); // 0-6 (Sunday-Saturday) + const currentHour = parisTime.getHours().toString().padStart(2, '0'); + const currentMinute = parisTime.getMinutes().toString().padStart(2, '0'); + const currentTime = `${currentHour}:${currentMinute}`; + const currentTimestamp = Date.now(); + + for (const msg of messages) { + try { + const guild = client.guilds.cache.get(msg.guild_id); + if (!guild) continue; + + const channel = guild.channels.cache.get(msg.channel_id); + if (!channel) continue; + + let shouldSend = false; + + if (msg.schedule_type === "weekly") { + // Check day and time + const days = JSON.parse(msg.days_of_week || "[]").map(d => parseInt(d)); + const times = JSON.parse(msg.times_of_day || "[]"); + + if (days.includes(currentDay) && times.includes(currentTime)) { + // Check if already sent this minute + const lastSent = msg.last_sent_at || 0; + const oneMinuteAgo = currentTimestamp - 60000; + + if (lastSent < oneMinuteAgo) { + shouldSend = true; + } + } + } else if (msg.schedule_type === "interval") { + // Check interval + const intervalMs = msg.interval_unit === "hours" + ? msg.interval_value * 60 * 60 * 1000 + : msg.interval_value * 60 * 1000; + + const lastSent = msg.last_sent_at || 0; + + if (currentTimestamp - lastSent >= intervalMs) { + shouldSend = true; + } + } + + if (!shouldSend) continue; + + // Check force_send option + if (!msg.force_send) { + const lastActivity = channelLastActivity.get(msg.channel_id) || msg.last_channel_activity || 0; + const lastSent = msg.last_sent_at || 0; + + // If no activity since last send, skip + if (lastActivity <= lastSent) { + continue; + } + } + + // Delete previous message if option enabled + if (msg.delete_previous && msg.last_message_id) { + try { + const oldMessage = await channel.messages.fetch(msg.last_message_id); + if (oldMessage) await oldMessage.delete(); + } catch (err) { + // Message may have been deleted already + } + } + + // Build message content + const messageOptions = {}; + + if (msg.message_content && msg.message_content.trim()) { + messageOptions.content = msg.message_content; + } + + if (msg.embed_enabled) { + const embed = new EmbedBuilder(); + + if (msg.embed_title) embed.setTitle(msg.embed_title); + if (msg.embed_description) embed.setDescription(msg.embed_description); + if (msg.embed_color) { + const color = msg.embed_color.startsWith('#') + ? parseInt(msg.embed_color.slice(1), 16) + : parseInt(msg.embed_color, 16); + embed.setColor(color); + } + embed.setTimestamp(); + + messageOptions.embeds = [embed]; + } + + // Send message + const sentMessage = await channel.send(messageOptions); + + // Update database + db.run( + `UPDATE scheduled_messages SET + last_sent_at = ?, + last_message_id = ?, + last_channel_activity = ? + WHERE id = ?`, + [currentTimestamp, sentMessage.id, channelLastActivity.get(msg.channel_id) || currentTimestamp, msg.id] + ); + + console.log(`📨 Message programmé envoyé: ${msg.id} dans ${channel.name}`); + + } catch (err) { + console.error(`Erreur envoi message programmé ${msg.id}:`, err); + } + } + } catch (err) { + console.error("Erreur processScheduledMessages:", err); + } +} + +// Run every minute to check scheduled messages +setInterval(processScheduledMessages, 60 * 1000); + +// Initial run after 10 seconds +setTimeout(processScheduledMessages, 10 * 1000); + + client.login(process.env.BOT_TOKEN); module.exports = client; diff --git a/app/db.js b/app/db.js index ce3b3a1..8f18bb0 100644 --- a/app/db.js +++ b/app/db.js @@ -213,6 +213,29 @@ db.exec(` ); CREATE INDEX IF NOT EXISTS idx_user_activity_stats_date ON user_activity_stats(guild_id, user_id, date); + + CREATE TABLE IF NOT EXISTS scheduled_messages ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + guild_id TEXT NOT NULL, + channel_id TEXT NOT NULL, + message_content TEXT NOT NULL, + embed_enabled INTEGER NOT NULL DEFAULT 0, + embed_title TEXT, + embed_description TEXT, + embed_color TEXT DEFAULT '#5865F2', + schedule_type TEXT NOT NULL DEFAULT 'weekly', + days_of_week TEXT DEFAULT '[]', + times_of_day TEXT DEFAULT '[]', + interval_value INTEGER DEFAULT 60, + interval_unit TEXT DEFAULT 'minutes', + force_send INTEGER NOT NULL DEFAULT 1, + delete_previous INTEGER NOT NULL DEFAULT 0, + enabled INTEGER NOT NULL DEFAULT 1, + last_sent_at INTEGER, + last_message_id TEXT, + last_channel_activity INTEGER, + created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) + ); `); module.exports = db; diff --git a/app/public/guild.css b/app/public/guild.css index 6b927ac..1f9661c 100644 --- a/app/public/guild.css +++ b/app/public/guild.css @@ -626,3 +626,176 @@ body { gap: var(--spacing-md); } } + +/* ===== Scheduled Messages ===== */ +.checkbox-group { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-sm); +} + +.checkbox-item { + display: flex; + align-items: center; + gap: var(--spacing-xs); + padding: var(--spacing-xs) var(--spacing-sm); + background: var(--bg-secondary); + border-radius: var(--radius-sm); + cursor: pointer; + user-select: none; +} + +.checkbox-item:hover { + background: var(--bg-card); +} + +.checkbox-item input { + cursor: pointer; +} + +.checkbox-group-vertical { + display: flex; + flex-direction: column; + gap: var(--spacing-sm); +} + +.checkbox-group-vertical .checkbox-item { + width: fit-content; +} + +.times-input-container { + display: flex; + gap: var(--spacing-sm); + align-items: center; + margin-bottom: var(--spacing-sm); +} + +.times-list { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-xs); + min-height: 32px; +} + +.time-tag { + display: inline-flex; + align-items: center; + gap: var(--spacing-xs); + padding: 4px 10px; + background: var(--primary-color); + color: white; + border-radius: var(--radius-sm); + font-size: 0.85rem; +} + +.time-remove { + background: none; + border: none; + color: white; + cursor: pointer; + font-size: 1.1rem; + line-height: 1; + opacity: 0.7; +} + +.time-remove:hover { + opacity: 1; +} + +.form-actions { + display: flex; + gap: var(--spacing-sm); + margin-top: var(--spacing-md); +} + +.scheduled-message-item { + background: var(--bg-secondary); + border-radius: var(--radius-md); + padding: var(--spacing-md); + margin-bottom: var(--spacing-sm); +} + +.scheduled-message-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: var(--spacing-sm); +} + +.scheduled-message-info strong { + color: var(--text-primary); + display: block; +} + +.scheduled-message-schedule { + font-size: 0.85rem; + color: var(--text-muted); +} + +.status-badge { + padding: 2px 8px; + border-radius: var(--radius-sm); + font-size: 0.75rem; + font-weight: 600; +} + +.status-active { + background: rgba(87, 242, 135, 0.2); + color: #57F287; +} + +.status-inactive { + background: rgba(237, 66, 69, 0.2); + color: #ED4245; +} + +.scheduled-message-preview { + background: var(--bg-card); + padding: var(--spacing-sm); + border-radius: var(--radius-sm); + font-size: 0.9rem; + color: var(--text-secondary); + margin-bottom: var(--spacing-sm); + word-break: break-word; +} + +.embed-badge { + display: inline-block; + padding: 2px 6px; + background: var(--primary-color); + color: white; + border-radius: var(--radius-sm); + font-size: 0.75rem; + margin-left: var(--spacing-xs); +} + +.scheduled-message-options { + display: flex; + flex-wrap: wrap; + gap: var(--spacing-xs); + margin-bottom: var(--spacing-sm); +} + +.option-tag { + padding: 2px 8px; + background: var(--bg-card); + border-radius: var(--radius-sm); + font-size: 0.75rem; + color: var(--text-muted); +} + +.scheduled-message-actions { + display: flex; + gap: var(--spacing-sm); + flex-wrap: wrap; +} + +.btn-warning { + background: #F0B232; + color: #000; +} + +.btn-warning:hover { + background: #d9a02a; +} + diff --git a/app/public/guild.html b/app/public/guild.html index a00e743..4e4c04c 100644 --- a/app/public/guild.html +++ b/app/public/guild.html @@ -62,6 +62,10 @@ 📊 Salons de stats + + ⏰ + Messages programmés + @@ -776,6 +780,158 @@ + + + + + + ⏰ Messages Programmés + Programmez l'envoi automatique de messages dans vos salons + + + + + + + ➕ Nouveau message programmé + + + + + 📺 Salon + + -- Sélectionner un salon -- + + + + + 💬 Contenu du message + + + + + + + + 📦 Utiliser un embed + + + + + + + Titre de l'embed + + + + Couleur + + + + + Description de l'embed + + + + + + + 📅 Type de planification + + Jours et heures spécifiques + Intervalle régulier + + + + + + + 📆 Jours de la semaine + + Lun + Mar + Mer + Jeu + Ven + Sam + Dim + + + + + 🕐 Heures d'envoi + + + HH + + : + + MM + + + Ajouter + + + + + + + + + + Toutes les + + + + Unité + + Minutes + Heures + + + + + + + + ⚙️ Options d'envoi + + + + Envoyer même si le salon est inactif + + + + Supprimer le message précédent + + + + Activer ce message programmé + + + + + + + 💾 Enregistrer + + + ❌ Annuler + + + + + + + 📋 Messages programmés + + Aucun message programmé. + + + + + + + @@ -793,5 +949,6 @@ +
Programmez l'envoi automatique de messages dans vos salons
Aucun message programmé.