From ca6cd2fbbf70ddeb5d6344b19a306c21d5159dc7 Mon Sep 17 00:00:00 2001 From: doodles172 Date: Sat, 8 Aug 2026 21:38:43 +0200 Subject: [PATCH 01/13] feat(logging): log setting updates QoL and has been on the todo for a while. --- apps/bot/src/commands/utility/settings.ts | 58 +++++++++++++---------- apps/bot/src/commands/utility/setup.ts | 20 +++++--- apps/bot/src/lib/logging.ts | 40 +++++++++++++++- apps/bot/src/lib/settings.ts | 32 +++++++++++++ 4 files changed, 117 insertions(+), 33 deletions(-) diff --git a/apps/bot/src/commands/utility/settings.ts b/apps/bot/src/commands/utility/settings.ts index cd582f9..d61fa82 100644 --- a/apps/bot/src/commands/utility/settings.ts +++ b/apps/bot/src/commands/utility/settings.ts @@ -24,6 +24,7 @@ import { TextInputStyle, } from 'discord.js'; import { createHoneypot, deleteHoneypot } from '#lib/honeypot.js'; +import { logSettingsChange } from '#lib/logging.js'; import { SCAM_ACTIONS, type ScamAction } from '#lib/scamProtection.js'; import { getSettings, type ServerSettings, updateSettings } from '#lib/settings.js'; import { errorEmbed, infoEmbed } from '#utils/embeds.js'; @@ -546,6 +547,15 @@ export class SettingsCommand extends Command { const settings = await normalizeTicketSettings(guildId, guild, await getSettings(guildId)); + const applySettings = async (i: MessageComponentInteraction, patch: Partial) => { // migration from "updateSettings" as we now also log changes + const before = await getSettings(guildId); + const next = await updateSettings(guildId, guild.name, patch); + + await logSettingsChange(guild, i.user, before, next); + + return next; + }; + if (settingChoice.values[0] === 'welcome') { await settingChoice.update(buildWelcomePanel(settings, guild)); } else if (settingChoice.values[0] === 'tickets') { @@ -576,79 +586,79 @@ export class SettingsCommand extends Command { collector.on('collect', async (i) => { if (i.customId === 'welcomeToggle' && i.isStringSelectMenu()) { const enable = i.values[0] === 'enable'; - const next = await updateSettings(guildId, guild.name, { welcomePeople: enable }); + const next = await applySettings(i, { welcomePeople: enable }); await i.update(buildWelcomePanel(next, guild, `Welcome module **${enable ? 'enabled' : 'disabled'}**.`)); } else if (i.customId === 'welcomeChannel' && i.isChannelSelectMenu()) { const channelId = i.values[0]; - const next = await updateSettings(guildId, guild.name, { welcomeChannelId: channelId }); + const next = await applySettings(i, { welcomeChannelId: channelId }); await i.update(buildWelcomePanel(next, guild, `Welcome channel set to <#${channelId}>.`)); } else if (i.customId === 'ticketCategory' && i.isChannelSelectMenu()) { const categoryId = i.values[0]; - const next = await updateSettings(guildId, guild.name, { ticketCategoryId: categoryId }); + const next = await applySettings(i, { ticketCategoryId: categoryId }); await i.update(buildTicketPanel(next, guild, `Ticket category set to <#${categoryId}>.`)); } else if (i.customId === 'ticketCategoryRemove' && i.isButton()) { - const next = await updateSettings(guildId, guild.name, { ticketCategoryId: null }); + const next = await applySettings(i, { ticketCategoryId: null }); await i.update(buildTicketPanel(next, guild, 'Ticket category removed.')); } else if (i.customId === 'staffRole' && i.isRoleSelectMenu()) { const roleId = i.values[0]; - const next = await updateSettings(guildId, guild.name, { staffRole: roleId }); + const next = await applySettings(i, { staffRole: roleId }); await i.update(buildTicketPanel(next, guild, `Ticket staff role set to <@&${roleId}>.`)); } else if (i.customId === 'removeStaffRole' && i.isButton()) { - const next = await updateSettings(guildId, guild.name, { staffRole: null }); + const next = await applySettings(i, { staffRole: null }); await i.update(buildTicketPanel(next, guild, 'Ticket staff role removed.')); } else if (i.customId === 'ticketTranscriptChannel' && i.isChannelSelectMenu()) { const channelId = i.values[0]; - const next = await updateSettings(guildId, guild.name, { ticketTranscriptChannelId: channelId }); + const next = await applySettings(i, { ticketTranscriptChannelId: channelId }); await i.update(buildTicketPanel(next, guild, `Ticket transcript channel set to <#${channelId}>.`)); } else if (i.customId === 'removeTranscriptChannel' && i.isButton()) { - const next = await updateSettings(guildId, guild.name, { ticketTranscriptChannelId: null }); + const next = await applySettings(i, { ticketTranscriptChannelId: null }); await i.update(buildTicketPanel(next, guild, 'Ticket transcript channel removed.')); } else if (i.customId === 'loggingToggle' && i.isStringSelectMenu()) { const enable = i.values[0] === 'enable'; - const next = await updateSettings(guildId, guild.name, { loggingEnabled: enable }); + const next = await applySettings(i, { loggingEnabled: enable }); await i.update(buildLoggingPanel(next, guild, `Logging module **${enable ? 'enabled' : 'disabled'}**.`)); } else if (i.customId === 'loggingChannel' && i.isChannelSelectMenu()) { const channelId = i.values[0]; - const next = await updateSettings(guildId, guild.name, { loggingChannelId: channelId }); + const next = await applySettings(i, { loggingChannelId: channelId }); await i.update(buildLoggingPanel(next, guild, `Logging channel set to <#${channelId}>.`)); } else if (i.customId === 'confessionToggle' && i.isStringSelectMenu()) { const enable = i.values[0] === 'enable'; - const next = await updateSettings(guildId, guild.name, { confessionEnabled: enable }); + const next = await applySettings(i, { confessionEnabled: enable }); await i.update(buildConfessionPanel(next, guild, `Confessions **${enable ? 'enabled' : 'disabled'}**.`)); } else if (i.customId === 'confessionChannel' && i.isChannelSelectMenu()) { const channelId = i.values[0]; - const next = await updateSettings(guildId, guild.name, { confessionChannelId: channelId }); + const next = await applySettings(i, { confessionChannelId: channelId }); await i.update(buildConfessionPanel(next, guild, `Confession channel set to <#${channelId}>.`)); } else if (i.customId === 'haikuToggle' && i.isStringSelectMenu()) { const enable = i.values[0] === 'enable'; - const next = await updateSettings(guildId, guild.name, { haikuEnabled: enable }); + const next = await applySettings(i, { haikuEnabled: enable }); await i.update(buildHaikuPanel(next, `Haiku **${enable ? 'enabled' : 'disabled'}**.`)); } else if (i.customId === 'autoPublisherToggle' && i.isStringSelectMenu()) { const enable = i.values[0] === 'enable'; - const next = await updateSettings(guildId, guild.name, { autoPublisher: enable }); + const next = await applySettings(i, { autoPublisher: enable }); await i.update(buildAutoPublisherPanel(next, `Auto Publisher **${enable ? 'enabled' : 'disabled'}**.`)); } else if (i.customId === 'starboardToggle' && i.isStringSelectMenu()) { const enable = i.values[0] === 'enable'; - const next = await updateSettings(guildId, guild.name, { starboardEnable: enable }); + const next = await applySettings(i, { starboardEnable: enable }); await i.update(buildStarboardPanel(next, guild, `Starboard **${enable ? 'enabled' : 'disabled'}**.`)); } else if (i.customId === 'starboardChannel' && i.isChannelSelectMenu()) { const channelId = i.values[0]; - const next = await updateSettings(guildId, guild.name, { starboardChannelId: channelId }); + const next = await applySettings(i, { starboardChannelId: channelId }); await i.update(buildStarboardPanel(next, guild, `Starboard channel set to <#${channelId}>.`)); } else if (i.customId === 'starboardCount' && i.isButton()) { @@ -679,7 +689,7 @@ export class SettingsCommand extends Command { return; } - const next = await updateSettings(guildId, guild.name, { starboardRequirement: requirement }); + const next = await applySettings(i, { starboardRequirement: requirement }); await submitted.editReply( buildStarboardPanel( @@ -723,12 +733,12 @@ export class SettingsCommand extends Command { return; } - const next = await updateSettings(guildId, guild.name, { starboardEmoji: emoji }); + const next = await applySettings(i, { starboardEmoji: emoji }); await submitted.editReply(buildStarboardPanel(next, guild, `Starboard emoji set to ${emoji}.`)); } else if (i.customId === 'scamProtectionToggle' && i.isStringSelectMenu()) { const enable = i.values[0] === 'enable'; - const next = await updateSettings(guildId, guild.name, { scamProtectionEnabled: enable }); + const next = await applySettings(i, { scamProtectionEnabled: enable }); await i.update( buildScamProtectionPanel(next, guild, `Scam Protection **${enable ? 'enabled' : 'disabled'}**.`), @@ -738,16 +748,16 @@ export class SettingsCommand extends Command { if (!value || !(value in SCAM_ACTIONS)) return; // exported from lib/scamProtection.ts const action = value as ScamAction; - const next = await updateSettings(guildId, guild.name, { scamProtectionAction: action }); + const next = await applySettings(i, { scamProtectionAction: action }); await i.update(buildScamProtectionPanel(next, guild, `Scam Protection set to **${SCAM_ACTIONS[action]}**.`)); } else if (i.customId === 'exemptionRole' && i.isRoleSelectMenu()) { const roleId = i.values[0]; - const next = await updateSettings(guildId, guild.name, { scamProtectionExemptionRole: roleId }); + const next = await applySettings(i, { scamProtectionExemptionRole: roleId }); await i.update(buildScamProtectionPanel(next, guild, `Exemption role set to <@&${roleId}>.`)); } else if (i.customId === 'removeExemptionRole' && i.isButton()) { - const next = await updateSettings(guildId, guild.name, { scamProtectionExemptionRole: null }); + const next = await applySettings(i, { scamProtectionExemptionRole: null }); await i.update(buildScamProtectionPanel(next, guild, 'Exemption role removed.')); } else if (i.customId === 'honeypotCreate' && i.isButton()) { @@ -775,7 +785,7 @@ export class SettingsCommand extends Command { return; } - const next = await updateSettings(guildId, guild.name, { honeypotChannelId: channel.id }); + const next = await applySettings(i, { honeypotChannelId: channel.id }); await i.editReply(buildHoneypotPanel(next, `Honey Pot channel created at <#${channel.id}>.`)); } else if (i.customId === 'honeypotDelete' && i.isButton()) { @@ -784,7 +794,7 @@ export class SettingsCommand extends Command { const current = await getSettings(guildId); if (current.honeypotChannelId) await deleteHoneypot(guild, current.honeypotChannelId); - const next = await updateSettings(guildId, guild.name, { honeypotChannelId: null }); + const next = await applySettings(i, { honeypotChannelId: null }); await i.editReply(buildHoneypotPanel(next, 'Honey Pot channel deleted.')); } diff --git a/apps/bot/src/commands/utility/setup.ts b/apps/bot/src/commands/utility/setup.ts index af4a7d1..ea4ea5d 100644 --- a/apps/bot/src/commands/utility/setup.ts +++ b/apps/bot/src/commands/utility/setup.ts @@ -16,7 +16,8 @@ import { PermissionFlagsBits, RoleSelectMenuBuilder, } from 'discord.js'; -import { getSettings, updateSettings } from '#lib/settings.js'; +import { logSettingsChange } from '#lib/logging.js'; +import { getSettings, type ServerSettings, updateSettings } from '#lib/settings.js'; import { awaitMessageComponentSafe } from '#utils/collectors.js'; import { errorEmbed, infoEmbed, successEmbed } from '#utils/embeds.js'; import { emojis } from '#utils/emoji.js'; @@ -148,6 +149,13 @@ export class SetupCommand extends Command { let settings = await getSettings(guildId); const summary: string[] = []; + const applySettings = async (patch: Partial) => { // migration from just "settings = " as we now also log changes + const before = settings; + settings = await updateSettings(guildId, guild.name, patch); + + await logSettingsChange(guild, interaction.user, before, settings); + }; + // 1. welcome messages await startChoice.update({ embeds: [ @@ -223,7 +231,7 @@ export class SetupCommand extends Command { } if (welcomeChannelId) { - settings = await updateSettings(guildId, guild.name, { + await applySettings({ welcomePeople: true, welcomeChannelId, }); @@ -333,7 +341,7 @@ export class SetupCommand extends Command { } if (ticketCategoryId) { - settings = await updateSettings(guildId, guild.name, { + await applySettings({ ticketCategoryId, staffRole: staffRoleId, ticketTranscriptChannelId: transcriptChannelId, @@ -518,7 +526,7 @@ export class SetupCommand extends Command { } if (loggingChannelId) { - settings = await updateSettings(guildId, guild.name, { + await applySettings({ loggingEnabled: true, loggingChannelId, }); @@ -605,7 +613,7 @@ export class SetupCommand extends Command { } if (confessionChannelId) { - settings = await updateSettings(guildId, guild.name, { + await applySettings({ confessionEnabled: true, confessionChannelId, }); @@ -634,7 +642,5 @@ export class SetupCommand extends Command { ], components: [], }); - - void settings; } } diff --git a/apps/bot/src/lib/logging.ts b/apps/bot/src/lib/logging.ts index cf7defb..c872357 100644 --- a/apps/bot/src/lib/logging.ts +++ b/apps/bot/src/lib/logging.ts @@ -2,8 +2,18 @@ // Copyright(C) 2026 Vantern // SPDX-License-Identifier: AGPL-3.0-or-later -import type { AuditLogEvent, EmbedBuilder, Guild } from 'discord.js'; -import { getSettings } from '#lib/settings.js'; +import { type AuditLogEvent, EmbedBuilder, type Guild, type User } from 'discord.js'; +import { getSettings, SETTING_LABELS, type ServerSettings } from '#lib/settings.js'; + +// so we don't return the raw value (looks ugly) +function formatSetting(key: keyof ServerSettings, value: ServerSettings[keyof ServerSettings]): string { + if (typeof value === 'boolean') return value ? 'Enabled' : 'Disabled'; + if (value === null || value === undefined) return 'None'; + if (key.endsWith('ChannelId') || key.endsWith('CategoryId')) return `<#${value}>`; + if (key.endsWith('Role')) return `<@&${value}>`; + + return String(value); +} export async function logEmbed(guild: Guild, embed: EmbedBuilder) { const settings = await getSettings(guild.id).catch((err) => { @@ -19,6 +29,32 @@ export async function logEmbed(guild: Guild, embed: EmbedBuilder) { await channel.send({ embeds: [embed] }).catch((err) => console.error(err)); } +// used in settings.ts for all changes +export async function logSettingsChange(guild: Guild, user: User, before: ServerSettings, after: ServerSettings) { + for (const key of Object.keys(SETTING_LABELS) as (keyof ServerSettings)[]) { + if (before[key] === after[key]) continue; + + const { category, name } = SETTING_LABELS[key]; + + const embed = new EmbedBuilder() + .setTitle('Setting Updated') + .setColor(0xfac898) + .addFields( + // \u200b is a messy hack allowing for 2x2 format, discord is annoying. + { name: 'Category', value: category, inline: true }, + { name: 'Setting', value: name, inline: true }, + { name: '\u200b', value: '\u200b', inline: true }, + { name: 'Before', value: formatSetting(key, before[key]), inline: true }, + { name: 'After', value: formatSetting(key, after[key]), inline: true }, + { name: '\u200b', value: '\u200b', inline: true }, + { name: 'Moderator', value: `<@${user.id}>`, inline: false }, + ) + .setTimestamp(); + + await logEmbed(guild, embed); + } +} + export async function isLoggingChannel(guild: Guild, channelId: string | null | undefined) { if (!channelId) return false; diff --git a/apps/bot/src/lib/settings.ts b/apps/bot/src/lib/settings.ts index 9b7223e..92cc8df 100644 --- a/apps/bot/src/lib/settings.ts +++ b/apps/bot/src/lib/settings.ts @@ -49,6 +49,38 @@ export const DefaultSettings: ServerSettings = { honeypotChannelId: null, }; +// used for logging setting changes +// update: originally was done as only the setting name, now also contains category +export const SETTING_LABELS: Record = { + welcomePeople: { category: 'Welcome', name: 'Status' }, + welcomeChannelId: { category: 'Welcome', name: 'Channel' }, + + ticketCategoryId: { category: 'Tickets', name: 'Category' }, + ticketTranscriptChannelId: { category: 'Tickets', name: 'Transcript Channel' }, + staffRole: { category: 'Tickets', name: 'Staff Role' }, + + confessionChannelId: { category: 'Confessions', name: 'Channel' }, + confessionEnabled: { category: 'Confessions', name: 'Status' }, + + loggingEnabled: { category: 'Logging', name: 'Status' }, + loggingChannelId: { category: 'Logging', name: 'Channel' }, + + haikuEnabled: { category: 'Haiku', name: 'Status' }, + + autoPublisher: { category: 'Auto Publisher', name: 'Status' }, + + starboardEnable: { category: 'Starboard', name: 'Status' }, + starboardChannelId: { category: 'Starboard', name: 'Channel' }, + starboardRequirement: { category: 'Starboard', name: 'Reactions Required' }, + starboardEmoji: { category: 'Starboard', name: 'Emoji' }, + + scamProtectionEnabled: { category: 'Scam Protection', name: 'Status' }, + scamProtectionAction: { category: 'Scam Protection', name: 'Action' }, + scamProtectionExemptionRole: { category: 'Scam Protection', name: 'Exemption Role' }, + + honeypotChannelId: { category: 'Honey Pot', name: 'Channel' }, +}; + // caching rather than ending up fetching the settings basically each message const settingsCache = new Map(); From 75c79db6244d2817aaf48f8d266f371c05c76f76 Mon Sep 17 00:00:00 2001 From: doodles172 Date: Sat, 8 Aug 2026 23:51:56 +0200 Subject: [PATCH 02/13] fix(logging): ignore own edits --- apps/bot/src/listeners/messageUpdate.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/bot/src/listeners/messageUpdate.ts b/apps/bot/src/listeners/messageUpdate.ts index f7d5614..b615bed 100644 --- a/apps/bot/src/listeners/messageUpdate.ts +++ b/apps/bot/src/listeners/messageUpdate.ts @@ -14,6 +14,10 @@ export class MessageUpdateListener extends Listener { public async run(oldMsg: Message | PartialMessage, newMsg: Message) { const guild = newMsg.guild ?? oldMsg?.guild; if (!guild) return; + + //* this one line is important, it ignores our own messages as we consider them noise + if (newMsg.author?.id === newMsg.client.user.id) return; + if (await isLoggingChannel(guild, newMsg.channel?.id ?? oldMsg?.channel?.id)) return; const oldContent = oldMsg?.content ?? ''; From afcde6547c61638c55312d85bbcaa406b0e9a936 Mon Sep 17 00:00:00 2001 From: doodles172 Date: Sun, 9 Aug 2026 17:54:08 +0200 Subject: [PATCH 03/13] fix(sticky): log /sticky --- apps/bot/src/commands/moderation/sticky.ts | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/apps/bot/src/commands/moderation/sticky.ts b/apps/bot/src/commands/moderation/sticky.ts index 5e0d88a..84a9e60 100644 --- a/apps/bot/src/commands/moderation/sticky.ts +++ b/apps/bot/src/commands/moderation/sticky.ts @@ -4,6 +4,7 @@ import { Command } from '@sapphire/framework'; import { + EmbedBuilder, type GuildTextBasedChannel, MessageFlags, PermissionFlagsBits, @@ -12,6 +13,7 @@ import { type SlashCommandSubcommandBuilder, } from 'discord.js'; import { containsBlockedWord } from '#lib/automod.js'; +import { logEmbed, truncate } from '#lib/logging.js'; import { getSticky, removeSticky, repostSticky, setSticky } from '#lib/sticky.js'; import { errorEmbed, infoEmbed, successEmbed } from '#utils/embeds.js'; import { emojis } from '#utils/emoji.js'; @@ -113,6 +115,18 @@ export class StickyCommand extends Command { const sticky = await setSticky(interaction.guildId, interaction.guild.name, channel.id, content); await repostSticky(channel, sticky, true); + const logEntry = new EmbedBuilder() + .setTitle('Sticky Message Set') + .setColor(0x77dd76) + .addFields( + { name: 'Moderator', value: `<@${interaction.user.id}>`, inline: true }, + { name: 'Channel', value: channel.toString(), inline: true }, + { name: 'Message', value: truncate(content) || '-', inline: false }, // if this actually shows up as '-' then my security researcher would probably kill me and send me questbot's gh advisories + ) + .setTimestamp(); + + await logEmbed(interaction.guild, logEntry); + await interaction.reply({ embeds: [successEmbed(`${emojis.rightArrow1} Sticky message set for this channel.`)], flags: MessageFlags.Ephemeral, @@ -143,6 +157,18 @@ export class StickyCommand extends Command { await channel.messages.delete(sticky.stickyMessageId).catch(() => {}); } + const logEntry = new EmbedBuilder() + .setTitle('Sticky Message Removed') + .setColor(0xff6962) + .addFields( + { name: 'Moderator', value: `<@${interaction.user.id}>`, inline: true }, + { name: 'Channel', value: channel.toString(), inline: true }, + { name: 'Message', value: truncate(sticky?.stickyContent) || '-', inline: false }, + ) + .setTimestamp(); + + await logEmbed(interaction.guild, logEntry); + await interaction.reply({ embeds: [successEmbed(`${emojis.rightArrow1} Sticky message removed from this channel.`)], flags: MessageFlags.Ephemeral, From 08e885140d8bfb8ef5b739c4fe4bf0d4d493c33c Mon Sep 17 00:00:00 2001 From: doodles172 Date: Sun, 9 Aug 2026 18:08:26 +0200 Subject: [PATCH 04/13] fix(logging): log on disabling Moved logging to its own function (sendLog) as we now also log when it gets disabled. --- apps/bot/src/lib/logging.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/apps/bot/src/lib/logging.ts b/apps/bot/src/lib/logging.ts index c872357..a237dac 100644 --- a/apps/bot/src/lib/logging.ts +++ b/apps/bot/src/lib/logging.ts @@ -15,6 +15,13 @@ function formatSetting(key: keyof ServerSettings, value: ServerSettings[keyof Se return String(value); } +async function sendLog(guild: Guild, channelId: string, embed: EmbedBuilder) { + const channel = await guild.channels.fetch(channelId).catch(() => null); + if (!channel?.isTextBased() || !channel.isSendable()) return; + + await channel.send({ embeds: [embed] }).catch((err) => console.error(err)); +} + export async function logEmbed(guild: Guild, embed: EmbedBuilder) { const settings = await getSettings(guild.id).catch((err) => { console.error(err); @@ -23,14 +30,13 @@ export async function logEmbed(guild: Guild, embed: EmbedBuilder) { if (!settings?.loggingEnabled || !settings.loggingChannelId) return; - const channel = await guild.channels.fetch(settings.loggingChannelId).catch(() => null); - if (!channel?.isTextBased() || !channel.isSendable()) return; - - await channel.send({ embeds: [embed] }).catch((err) => console.error(err)); + await sendLog(guild, settings.loggingChannelId, embed); } // used in settings.ts for all changes export async function logSettingsChange(guild: Guild, user: User, before: ServerSettings, after: ServerSettings) { + const finalChannelId = before.loggingEnabled && !after.loggingEnabled ? before.loggingChannelId : null; // aka it was just disabled + for (const key of Object.keys(SETTING_LABELS) as (keyof ServerSettings)[]) { if (before[key] === after[key]) continue; @@ -51,7 +57,8 @@ export async function logSettingsChange(guild: Guild, user: User, before: Server ) .setTimestamp(); - await logEmbed(guild, embed); + if (finalChannelId) await sendLog(guild, finalChannelId, embed); + else await logEmbed(guild, embed); } } From ffaa821a5b7b52a5eb7db29eb847bdada19773b4 Mon Sep 17 00:00:00 2001 From: doodles172 Date: Sun, 9 Aug 2026 18:21:44 +0200 Subject: [PATCH 05/13] fix(automod): exempt people that shouldn't be moderator Including, administrator & manage guild permission. --- apps/bot/src/listeners/messageCreate.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/bot/src/listeners/messageCreate.ts b/apps/bot/src/listeners/messageCreate.ts index 2cd4f16..8d02419 100644 --- a/apps/bot/src/listeners/messageCreate.ts +++ b/apps/bot/src/listeners/messageCreate.ts @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import { Listener } from '@sapphire/framework'; -import { Events, type Message } from 'discord.js'; +import { Events, type Message, PermissionFlagsBits } from 'discord.js'; import { containsBlockedWord } from '#lib/automod.js'; import { autoPublish } from '#lib/autoPublisher.js'; import { isHaiku } from '#lib/haiku.js'; @@ -34,7 +34,11 @@ export class MessageCreateListener extends Listener await message.reply("That's a haiku!").catch((err) => console.error(err)); } - const isBlocked = await containsBlockedWord(message.guild.id, message.content); + // administrator perm and people that can modify automod are exempt from automod + const permissions = message.member?.permissions; + const isExempt = + permissions?.has(PermissionFlagsBits.Administrator) || permissions?.has(PermissionFlagsBits.ManageGuild) || false; + const isBlocked = !isExempt && (await containsBlockedWord(message.guild.id, message.content)); if (isBlocked && !message.author.bot) { await message.delete().catch((err) => console.error(err)); From 39b1dcf2356ba21191ef382c178f022e4470b8e3 Mon Sep 17 00:00:00 2001 From: doodles172 Date: Sun, 9 Aug 2026 19:20:52 +0200 Subject: [PATCH 06/13] fix(logging): filter out embed and pin edits --- apps/bot/src/listeners/messageUpdate.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/bot/src/listeners/messageUpdate.ts b/apps/bot/src/listeners/messageUpdate.ts index b615bed..6297fee 100644 --- a/apps/bot/src/listeners/messageUpdate.ts +++ b/apps/bot/src/listeners/messageUpdate.ts @@ -18,6 +18,9 @@ export class MessageUpdateListener extends Listener { //* this one line is important, it ignores our own messages as we consider them noise if (newMsg.author?.id === newMsg.client.user.id) return; + // discord also sends out updates for embeds (and pins) so this will filter out those + if (!newMsg.editedTimestamp) return; + if (await isLoggingChannel(guild, newMsg.channel?.id ?? oldMsg?.channel?.id)) return; const oldContent = oldMsg?.content ?? ''; From c54c239e141560cb3c9694a9bf1214ab9d197247 Mon Sep 17 00:00:00 2001 From: doodles172 Date: Sun, 9 Aug 2026 19:29:16 +0200 Subject: [PATCH 07/13] style(logging): switch from username to <@id> Excluding: - Leave - Ban (still changed moderator) - Unban (still changed moderator) - Kick (still changed moderator) --- apps/bot/src/listeners/channelCreate.ts | 3 +-- apps/bot/src/listeners/channelDelete.ts | 3 +-- apps/bot/src/listeners/emojiCreate.ts | 6 +----- apps/bot/src/listeners/emojiDelete.ts | 6 +----- apps/bot/src/listeners/emojiUpdate.ts | 6 +----- apps/bot/src/listeners/guildBanAdd.ts | 2 +- apps/bot/src/listeners/guildMemberRemove.ts | 2 +- apps/bot/src/listeners/guildMemberUpdate.ts | 7 ++----- apps/bot/src/listeners/messageDelete.ts | 2 +- apps/bot/src/listeners/messageUpdate.ts | 3 ++- 10 files changed, 12 insertions(+), 28 deletions(-) diff --git a/apps/bot/src/listeners/channelCreate.ts b/apps/bot/src/listeners/channelCreate.ts index 18012fe..440225a 100644 --- a/apps/bot/src/listeners/channelCreate.ts +++ b/apps/bot/src/listeners/channelCreate.ts @@ -34,8 +34,7 @@ export class ChannelCreateListener extends Listener const auditEntry = await getRecentAuditLogEntry(channel.guild, AuditLogEvent.ChannelCreate, channel.id); if (auditEntry?.executor) { - const moderatorLabel = auditEntry.executor.tag ?? auditEntry.executor.username ?? auditEntry.executor.id; - embed.addFields({ name: 'Moderator', value: moderatorLabel, inline: true }); + embed.addFields({ name: 'Moderator', value: `<@${auditEntry.executor.id}>`, inline: true }); } await logEmbed(channel.guild, embed); diff --git a/apps/bot/src/listeners/channelDelete.ts b/apps/bot/src/listeners/channelDelete.ts index 44eb563..0fe43ff 100644 --- a/apps/bot/src/listeners/channelDelete.ts +++ b/apps/bot/src/listeners/channelDelete.ts @@ -46,8 +46,7 @@ export class ChannelDeleteListener extends Listener const auditEntry = await getRecentAuditLogEntry(channel.guild, AuditLogEvent.ChannelDelete, channel.id); if (auditEntry?.executor) { - const moderatorLabel = String(auditEntry.executor.tag ?? auditEntry.executor.username ?? auditEntry.executor.id); - embed.addFields({ name: 'Moderator', value: moderatorLabel, inline: true }); + embed.addFields({ name: 'Moderator', value: `<@${auditEntry.executor.id}>`, inline: true }); } await logEmbed(channel.guild, embed); diff --git a/apps/bot/src/listeners/emojiCreate.ts b/apps/bot/src/listeners/emojiCreate.ts index 65c5a30..95093e8 100644 --- a/apps/bot/src/listeners/emojiCreate.ts +++ b/apps/bot/src/listeners/emojiCreate.ts @@ -25,11 +25,7 @@ export class EmojiCreateListener extends Listener`, inline: true }); } await logEmbed(emoji.guild, embed); diff --git a/apps/bot/src/listeners/emojiDelete.ts b/apps/bot/src/listeners/emojiDelete.ts index 0c991ab..e1c8bc7 100644 --- a/apps/bot/src/listeners/emojiDelete.ts +++ b/apps/bot/src/listeners/emojiDelete.ts @@ -25,11 +25,7 @@ export class EmojiDeleteListener extends Listener`, inline: true }); } await logEmbed(emoji.guild, embed); diff --git a/apps/bot/src/listeners/emojiUpdate.ts b/apps/bot/src/listeners/emojiUpdate.ts index f4ac208..9477979 100644 --- a/apps/bot/src/listeners/emojiUpdate.ts +++ b/apps/bot/src/listeners/emojiUpdate.ts @@ -32,11 +32,7 @@ export class EmojiUpdateListener extends Listener`, inline: true }); } await logEmbed(newEmoji.guild, embed); diff --git a/apps/bot/src/listeners/guildBanAdd.ts b/apps/bot/src/listeners/guildBanAdd.ts index cd045fa..379073e 100644 --- a/apps/bot/src/listeners/guildBanAdd.ts +++ b/apps/bot/src/listeners/guildBanAdd.ts @@ -27,7 +27,7 @@ export class GuildBanAddListener extends Listener { .setTimestamp(); if (auditEntry?.executor) { - embed.addFields({ name: 'Moderator', value: `${auditEntry.executor.tag}`, inline: true }); + embed.addFields({ name: 'Moderator', value: `<@${auditEntry.executor.id}>`, inline: true }); } if (auditEntry?.reason) { diff --git a/apps/bot/src/listeners/guildMemberRemove.ts b/apps/bot/src/listeners/guildMemberRemove.ts index 7476d78..e48f6ce 100644 --- a/apps/bot/src/listeners/guildMemberRemove.ts +++ b/apps/bot/src/listeners/guildMemberRemove.ts @@ -38,7 +38,7 @@ export class GuildMemberRemoveListener extends Listener`, inline: true }); } if (kickEntry?.reason) { diff --git a/apps/bot/src/listeners/guildMemberUpdate.ts b/apps/bot/src/listeners/guildMemberUpdate.ts index dc7b95d..f015dd7 100644 --- a/apps/bot/src/listeners/guildMemberUpdate.ts +++ b/apps/bot/src/listeners/guildMemberUpdate.ts @@ -26,14 +26,11 @@ export class GuildMemberUpdateListener extends Listener`, inline: true }) .setTimestamp(); if (auditEntry?.executor) { - embed.addFields({ name: 'Moderator', value: `${auditEntry.executor.tag}`, inline: true }); + embed.addFields({ name: 'Moderator', value: `<@${auditEntry.executor.id}>`, inline: true }); } if (auditEntry?.reason) { diff --git a/apps/bot/src/listeners/messageDelete.ts b/apps/bot/src/listeners/messageDelete.ts index b865c3f..c6c8faa 100644 --- a/apps/bot/src/listeners/messageDelete.ts +++ b/apps/bot/src/listeners/messageDelete.ts @@ -30,7 +30,7 @@ export class MessageDeleteListener extends Listener .setColor(0xff6962) .addFields( { name: 'Channel', value: message.channel?.toString() ?? 'Unknown', inline: true }, - { name: 'Author', value: message.author?.tag ?? 'Unknown', inline: true }, + { name: 'Author', value: message.author ? `<@${message.author.id}>` : 'Unknown', inline: true }, { name: 'Content', value: contentValue }, ) .setFooter({ text: `ID: ${message.id}` }) diff --git a/apps/bot/src/listeners/messageUpdate.ts b/apps/bot/src/listeners/messageUpdate.ts index 6297fee..086bde8 100644 --- a/apps/bot/src/listeners/messageUpdate.ts +++ b/apps/bot/src/listeners/messageUpdate.ts @@ -40,13 +40,14 @@ export class MessageUpdateListener extends Listener { const beforeValue = truncate(beforeParts.join('\n'), 1024) || '-'; const afterValue = truncate(afterParts.join('\n'), 1024) || '-'; + const authorId = newMsg.author?.id ?? oldMsg?.author?.id; const embed = new EmbedBuilder() .setTitle('Message Edited') .setColor(0xfac898) .addFields( { name: 'Message', value: newMsg.url ?? 'Unknown', inline: true }, - { name: 'Author', value: newMsg.author?.tag ?? oldMsg?.author?.tag ?? 'Unknown', inline: true }, + { name: 'Author', value: authorId ? `<@${authorId}>` : 'Unknown', inline: true }, { name: 'Before', value: beforeValue }, { name: 'After', value: afterValue }, ) From 0d19f3522a72b4b111c503769eb5eab4ab024e45 Mon Sep 17 00:00:00 2001 From: doodles172 Date: Sun, 9 Aug 2026 20:05:20 +0200 Subject: [PATCH 08/13] fix(sticky): push sticky message down when a command is ran Excluding ephemeral commands obviously. --- apps/bot/src/listeners/stickyCommand.ts | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 apps/bot/src/listeners/stickyCommand.ts diff --git a/apps/bot/src/listeners/stickyCommand.ts b/apps/bot/src/listeners/stickyCommand.ts new file mode 100644 index 0000000..1c16805 --- /dev/null +++ b/apps/bot/src/listeners/stickyCommand.ts @@ -0,0 +1,25 @@ +// QuestBot: A free and open-source Discord Bot. +// Copyright(C) 2026 Vantern +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { type ChatInputCommandSuccessPayload, Listener } from '@sapphire/framework'; +import { getSticky, repostSticky } from '#lib/sticky.js'; + +// this file exists because discord doesn't send messageCreate for commands +export class StickyCommandListener extends Listener { + public constructor(context: Listener.LoaderContext, options: Listener.Options) { + super(context, { ...options, event: 'chatInputCommandSuccess' }); + } + + public override async run({ interaction }: ChatInputCommandSuccessPayload) { + if (!interaction.inCachedGuild() || !interaction.channel) return; + + // discord fires this event for ephemeral commands, don't repost if so + if (interaction.ephemeral !== false) return; + + const sticky = await getSticky(interaction.guildId, interaction.channel.id); + if (!sticky) return; + + await repostSticky(interaction.channel, sticky); + } +} From 619c4d970c95e058ab9baba375cf4da4b516c38c Mon Sep 17 00:00:00 2001 From: doodles172 Date: Tue, 11 Aug 2026 04:25:55 +0200 Subject: [PATCH 09/13] feat: lockdown + format Formatted some files that I forgot to format last time. --- .../commands/moderation/lockdown/lockdown.ts | 89 +++++++++++++ .../moderation/lockdown/unlockdown.ts | 88 ++++++++++++ apps/bot/src/commands/utility/settings.ts | 3 +- apps/bot/src/commands/utility/setup.ts | 3 +- apps/bot/src/commands/utility/suggest.ts | 1 + apps/bot/src/lib/honeypot.ts | 5 +- apps/bot/src/lib/lockdown.ts | 125 ++++++++++++++++++ apps/bot/src/lib/settings.ts | 2 +- biome.json | 2 +- packages/database/prisma/schema.prisma | 6 +- 10 files changed, 317 insertions(+), 7 deletions(-) create mode 100644 apps/bot/src/commands/moderation/lockdown/lockdown.ts create mode 100644 apps/bot/src/commands/moderation/lockdown/unlockdown.ts create mode 100644 apps/bot/src/lib/lockdown.ts diff --git a/apps/bot/src/commands/moderation/lockdown/lockdown.ts b/apps/bot/src/commands/moderation/lockdown/lockdown.ts new file mode 100644 index 0000000..3f234eb --- /dev/null +++ b/apps/bot/src/commands/moderation/lockdown/lockdown.ts @@ -0,0 +1,89 @@ +// QuestBot: A free and open-source Discord Bot. +// Copyright(C) 2026 Vantern +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { BucketScope, Command } from '@sapphire/framework'; +import { EmbedBuilder, MessageFlags, PermissionFlagsBits } from 'discord.js'; +import { isServerLocked, lockdownServer } from '#lib/lockdown.js'; +import { logEmbed } from '#lib/logging.js'; +import { errorEmbed, infoEmbed, successEmbed } from '#utils/embeds.js'; +import { emojis } from '#utils/emoji.js'; + +export class LockdownCommand extends Command { + public constructor(context: Command.LoaderContext, options: Command.Options) { + super(context, { + ...options, + preconditions: ['devMode'], + cooldownDelay: 30_000, + cooldownLimit: 1, + cooldownScope: BucketScope.Guild, + }); + } + + public override registerApplicationCommands(registry: Command.Registry) { + registry.registerChatInputCommand((builder) => + builder + .setName('lockdown') + .setDescription('Lock down all channels in the server, preventing everyone from speaking.') + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), + ); + } + + public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { + if (!interaction.inCachedGuild()) { + await interaction.reply({ + embeds: [errorEmbed(`${emojis.rightArrow2} This command can only be used in a server.`)], + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (!interaction.member.permissions.has(PermissionFlagsBits.Administrator)) { + await interaction.reply({ + embeds: [errorEmbed(`${emojis.rightArrow2} You do not have permission to lock down the server.`)], + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (await isServerLocked(interaction.guildId)) { + await interaction.reply({ + embeds: [infoEmbed(`${emojis.rightArrow2} The server is already locked down.`)], + flags: MessageFlags.Ephemeral, + }); + return; + } + + // defer as we are about to change all channels perms + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); + + try { + const { affected, skipped } = await lockdownServer(interaction.guild, `Lockdown by ${interaction.user.tag}`); + + const logEntry = new EmbedBuilder() + .setTitle('Server Locked') + .setColor(0x000000) + .addFields( + { name: 'Moderator', value: `<@${interaction.user.id}>`, inline: true }, + { name: 'Locked', value: String(affected), inline: true }, + { name: 'Skipped', value: String(skipped), inline: true }, + ) + .setTimestamp(); + + await logEmbed(interaction.guild, logEntry); + + await interaction.editReply({ + embeds: [ + successEmbed( + `${emojis.rightArrow1} Locked ${affected} channel${affected === 1 ? '' : 's'}.${skipped ? `\nSkipped ${skipped} I could not lock.` : ''}`, + ), + ], + }); + } catch (error) { + console.error(error); + await interaction.editReply({ + embeds: [errorEmbed(`${emojis.rightArrow2} Failed to lock down this server.`)], + }); + } + } +} diff --git a/apps/bot/src/commands/moderation/lockdown/unlockdown.ts b/apps/bot/src/commands/moderation/lockdown/unlockdown.ts new file mode 100644 index 0000000..5f2cc99 --- /dev/null +++ b/apps/bot/src/commands/moderation/lockdown/unlockdown.ts @@ -0,0 +1,88 @@ +// QuestBot: A free and open-source Discord Bot. +// Copyright(C) 2026 Vantern +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { BucketScope, Command } from '@sapphire/framework'; +import { EmbedBuilder, MessageFlags, PermissionFlagsBits } from 'discord.js'; +import { isServerLocked, unLockdown } from '#lib/lockdown.js'; +import { logEmbed } from '#lib/logging.js'; +import { errorEmbed, infoEmbed, successEmbed } from '#utils/embeds.js'; +import { emojis } from '#utils/emoji.js'; + +export class UnlockdownCommand extends Command { + public constructor(context: Command.LoaderContext, options: Command.Options) { + super(context, { + ...options, + preconditions: ['devMode'], + cooldownDelay: 30_000, + cooldownLimit: 1, + cooldownScope: BucketScope.Guild, + }); + } + + public override registerApplicationCommands(registry: Command.Registry) { + registry.registerChatInputCommand((builder) => + builder + .setName('unlockdown') + .setDescription('Unlock the whole server.') + .setDefaultMemberPermissions(PermissionFlagsBits.Administrator), + ); + } + + public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { + if (!interaction.inCachedGuild()) { + await interaction.reply({ + embeds: [errorEmbed(`${emojis.rightArrow2} This command can only be used in a server.`)], + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (!interaction.member.permissions.has(PermissionFlagsBits.Administrator)) { + await interaction.reply({ + embeds: [errorEmbed(`${emojis.rightArrow2} You do not have permission to unlock the server.`)], + flags: MessageFlags.Ephemeral, + }); + return; + } + + if (!(await isServerLocked(interaction.guildId))) { + await interaction.reply({ + embeds: [infoEmbed(`${emojis.rightArrow2} The server is not locked down.`)], + flags: MessageFlags.Ephemeral, + }); + return; + } + + await interaction.deferReply({ flags: MessageFlags.Ephemeral }); + + try { + const { affected, skipped } = await unLockdown(interaction.guild, `Lockdown removed by ${interaction.user.tag}`); + + const logEntry = new EmbedBuilder() + .setTitle('Server Unlocked') + .setColor(0x77dd76) + .addFields( + { name: 'Moderator', value: `<@${interaction.user.id}>`, inline: true }, + { name: 'Unlocked', value: String(affected), inline: true }, + { name: 'Skipped', value: String(skipped), inline: true }, + ) + .setTimestamp(); + + await logEmbed(interaction.guild, logEntry); + + await interaction.editReply({ + embeds: [ + successEmbed( + `${emojis.rightArrow1} Unlocked ${affected} channel${affected === 1 ? '' : 's'}.${skipped ? `\nSkipped ${skipped} I could not unlock.` : ''}`, + ), + ], + }); + } catch (error) { + console.error(error); + await interaction.editReply({ + embeds: [errorEmbed(`${emojis.rightArrow2} Failed to unlock the server.`)], + }); + } + } +} diff --git a/apps/bot/src/commands/utility/settings.ts b/apps/bot/src/commands/utility/settings.ts index d61fa82..7b2227c 100644 --- a/apps/bot/src/commands/utility/settings.ts +++ b/apps/bot/src/commands/utility/settings.ts @@ -547,7 +547,8 @@ export class SettingsCommand extends Command { const settings = await normalizeTicketSettings(guildId, guild, await getSettings(guildId)); - const applySettings = async (i: MessageComponentInteraction, patch: Partial) => { // migration from "updateSettings" as we now also log changes + const applySettings = async (i: MessageComponentInteraction, patch: Partial) => { + // migration from "updateSettings" as we now also log changes const before = await getSettings(guildId); const next = await updateSettings(guildId, guild.name, patch); diff --git a/apps/bot/src/commands/utility/setup.ts b/apps/bot/src/commands/utility/setup.ts index ea4ea5d..09c117d 100644 --- a/apps/bot/src/commands/utility/setup.ts +++ b/apps/bot/src/commands/utility/setup.ts @@ -149,7 +149,8 @@ export class SetupCommand extends Command { let settings = await getSettings(guildId); const summary: string[] = []; - const applySettings = async (patch: Partial) => { // migration from just "settings = " as we now also log changes + const applySettings = async (patch: Partial) => { + // migration from just "settings = " as we now also log changes const before = settings; settings = await updateSettings(guildId, guild.name, patch); diff --git a/apps/bot/src/commands/utility/suggest.ts b/apps/bot/src/commands/utility/suggest.ts index a0d149e..3502539 100644 --- a/apps/bot/src/commands/utility/suggest.ts +++ b/apps/bot/src/commands/utility/suggest.ts @@ -12,6 +12,7 @@ export class SuggestCommand extends Command { super(context, { ...options, preconditions: ['devMode'], + // i forgot sapphire had a built-in cooldown! :D cooldownDelay: 60_000, cooldownLimit: 1, cooldownScope: BucketScope.User, diff --git a/apps/bot/src/lib/honeypot.ts b/apps/bot/src/lib/honeypot.ts index 8faac41..e59c091 100644 --- a/apps/bot/src/lib/honeypot.ts +++ b/apps/bot/src/lib/honeypot.ts @@ -28,12 +28,13 @@ export async function createHoneypot(guild: Guild) { const embed = new EmbedBuilder() .setTitle('🍯 Honey Pot') .setColor(0xffdc67) - .setDescription('**DO NOT SEND MESSAGES HERE**\n\nYou will be kicked if you send a message here.'); + .setDescription('**DO NOT SEND MESSAGES HERE**') + .setFooter({ text: 'You will be kicked if you send a message here.' }); const learnMore = new ButtonBuilder() .setCustomId(HONEYPOT_LEARN_ID) .setLabel('Learn More') - .setStyle(ButtonStyle.Primary); + .setStyle(ButtonStyle.Secondary); await channel.send({ embeds: [embed], diff --git a/apps/bot/src/lib/lockdown.ts b/apps/bot/src/lib/lockdown.ts new file mode 100644 index 0000000..7e9f348 --- /dev/null +++ b/apps/bot/src/lib/lockdown.ts @@ -0,0 +1,125 @@ +// QuestBot: A free and open-source Discord Bot. +// Copyright(C) 2026 Vantern +// SPDX-License-Identifier: AGPL-3.0-or-later + +import { prisma } from '@questbot/database'; +import { EmbedBuilder, type Guild, PermissionFlagsBits } from 'discord.js'; +import { HONEYPOT_CHANNEL_NAME } from '#lib/honeypot.js'; + +export type LockdownResult = { affected: number; skipped: number }; + +export async function isServerLocked(guildId: string): Promise { + const server = await prisma.server.findUnique({ where: { id: guildId }, select: { locked: true } }); + + return server?.locked ?? false; +} + +export async function lockdownServer(guild: Guild, reason: string): Promise { + const me = guild.members.me; + if (!me) return { affected: 0, skipped: 0 }; + + const everyone = guild.roles.everyone; + + await prisma.server.upsert({ + where: { id: guild.id }, + create: { id: guild.id, name: guild.name, locked: true }, + update: { name: guild.name, locked: true }, + }); + + const notice = new EmbedBuilder() + .setTitle('Lockdown') + .setColor(0xff6962) + .setDescription('The server has been locked by server administrators.') + .setFooter({ text: 'This affects all channels everyone can speak in.' }); + + const channels = await guild.channels.fetch(); + + let affected = 0; + let skipped = 0; + + for (const channel of channels.values()) { + if (!channel?.isTextBased() || channel.isThread()) continue; + if (channel.name === HONEYPOT_CHANNEL_NAME) continue; // the trap only works while it stays open + + const everyonePerms = channel.permissionsFor(everyone); + if (!everyonePerms.has(PermissionFlagsBits.ViewChannel)) continue; + if (!everyonePerms.has(PermissionFlagsBits.SendMessages)) continue; + if (!everyonePerms.has(PermissionFlagsBits.SendMessagesInThreads)) continue; + + if (!channel.isSendable() || !channel.permissionsFor(me).has(PermissionFlagsBits.ManageRoles)) { + skipped++; + continue; + } + + try { + await channel.permissionOverwrites.edit( + everyone, + { SendMessages: false, SendMessagesInThreads: false }, + { reason }, + ); + } catch (error) { + console.error(error); + skipped++; + continue; + } + + const message = await channel.send({ embeds: [notice] }).catch((err) => { + console.error(err); + return null; + }); + + await prisma.channel.upsert({ + where: { id: channel.id }, + create: { + id: channel.id, + guildId: guild.id, + lockedDown: true, + lockdownMessageId: message?.id ?? null, + }, + update: { lockedDown: true, lockdownMessageId: message?.id ?? null }, + }); + + affected++; + } + + return { affected, skipped }; +} + +export async function unLockdown(guild: Guild, reason: string): Promise { + const everyone = guild.roles.everyone; + const locked = await prisma.channel.findMany({ where: { guildId: guild.id, lockedDown: true } }); + + let affected = 0; + let skipped = 0; + + for (const row of locked) { + const channel = guild.channels.cache.get(row.id) ?? (await guild.channels.fetch(row.id).catch(() => null)); + + if (channel?.isTextBased() && !channel.isThread()) { + try { + // null clears the overwrite + await channel.permissionOverwrites.edit( + everyone, + { SendMessages: null, SendMessagesInThreads: null }, + { reason }, + ); + + if (row.lockdownMessageId) await channel.messages.delete(row.lockdownMessageId).catch(() => {}); + + affected++; + } catch (error) { + console.error(error); + skipped++; + } + } + + await prisma.channel.update({ + where: { id: row.id }, + data: { lockedDown: false, lockdownMessageId: null }, + }); + } + + await prisma.server.updateMany({ where: { id: guild.id }, data: { locked: false } }); + + return { affected, skipped }; +} diff --git a/apps/bot/src/lib/settings.ts b/apps/bot/src/lib/settings.ts index 92cc8df..c196a2d 100644 --- a/apps/bot/src/lib/settings.ts +++ b/apps/bot/src/lib/settings.ts @@ -77,7 +77,7 @@ export const SETTING_LABELS: Record Date: Tue, 11 Aug 2026 04:28:34 +0200 Subject: [PATCH 10/13] fix(honeypot): migrate from name to id --- apps/bot/src/lib/lockdown.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/bot/src/lib/lockdown.ts b/apps/bot/src/lib/lockdown.ts index 7e9f348..5198811 100644 --- a/apps/bot/src/lib/lockdown.ts +++ b/apps/bot/src/lib/lockdown.ts @@ -4,7 +4,7 @@ import { prisma } from '@questbot/database'; import { EmbedBuilder, type Guild, PermissionFlagsBits } from 'discord.js'; -import { HONEYPOT_CHANNEL_NAME } from '#lib/honeypot.js'; +import { getSettings } from '#lib/settings.js'; export type LockdownResult = { affected: number; skipped: number }; @@ -19,6 +19,7 @@ export async function lockdownServer(guild: Guild, reason: string): Promise Date: Tue, 11 Aug 2026 04:46:09 +0200 Subject: [PATCH 11/13] fix(packages): cleanup + bump undici + min release age --- apps/bot/package.json | 2 +- pnpm-lock.yaml | 625 ++++++++++++++++++++---------------------- pnpm-workspace.yaml | 3 + 3 files changed, 304 insertions(+), 326 deletions(-) diff --git a/apps/bot/package.json b/apps/bot/package.json index 668cfc6..6813f7a 100644 --- a/apps/bot/package.json +++ b/apps/bot/package.json @@ -33,7 +33,7 @@ "ms": "^2.1.3", "sharp": "^0.35.3", "syllable-count-english": "^1.0.6", - "undici": "^8.7.0", + "undici": "^8.10.0", "zod": "^4.4.3" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f61ec9..d9faca6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + fast-uri: ^3.1.5 + importers: .: @@ -55,8 +58,8 @@ importers: specifier: ^1.0.6 version: 1.0.6 undici: - specifier: ^8.7.0 - version: 8.9.0 + specifier: ^8.10.0 + version: 8.10.0 zod: specifier: ^4.4.3 version: 4.4.3 @@ -75,7 +78,7 @@ importers: version: 6.1.3 tsx: specifier: ^4.23.4 - version: 4.23.4 + version: 4.23.5 typescript: specifier: ~7.0.2 version: 7.0.2 @@ -84,7 +87,7 @@ importers: dependencies: '@mdx-js/rollup': specifier: ^3.1.1 - version: 3.1.1(rollup@4.62.2) + version: 3.1.1(rollup@4.62.4) '@questbot/database': specifier: workspace:* version: link:../../packages/database @@ -121,10 +124,10 @@ importers: devDependencies: '@react-router/dev': specifier: 8.3.0 - version: 8.3.0(@react-router/serve@8.3.0(react-router@8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@7.0.2))(react-router@8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@7.0.2)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4)) + version: 8.3.0(@react-router/serve@8.3.0(react-router@8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@7.0.2))(react-router@8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@7.0.2)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.5)) '@tailwindcss/vite': specifier: ^4.3.3 - version: 4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4)) + version: 4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.5)) '@types/node': specifier: ^26 version: 26.1.2 @@ -145,7 +148,7 @@ importers: version: 7.0.2 vite: specifier: ^8.2.0 - version: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4) + version: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.5) packages/database: dependencies: @@ -180,8 +183,8 @@ packages: resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.29.7': - resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} engines: {node: '>=6.9.0'} '@babel/helper-annotate-as-pure@7.29.7': @@ -250,8 +253,8 @@ packages: resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.7': - resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} engines: {node: '>=6.0.0'} hasBin: true @@ -289,12 +292,12 @@ packages: resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.29.7': - resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} engines: {node: '>=6.9.0'} - '@babel/types@7.29.7': - resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} engines: {node: '>=6.9.0'} '@better-auth/core@1.6.25': @@ -373,9 +376,6 @@ packages: '@better-auth/utils@0.4.2': resolution: {integrity: sha512-AUxrvu+HaaODsUyzDxFgwd/8RZ1yZaYo42LXKSrU2oGgR38pS1ij8nqQKNgtTWoYGpNevNXtCfgTy6loHveW9A==} - '@better-auth/utils@0.4.3': - resolution: {integrity: sha512-/X4gRYJBwRuLFjjANVpTDJJkAhLD6Sf8SIBypQ+Q2kDW1MnCsyxUujJzdKYsp59i4c41+AaMtpY/WQAQumqtOA==} - '@better-fetch/fetch@1.3.1': resolution: {integrity: sha512-ABkD1WhyfPZprKRQI3bhATjeiFuNWC9PXhfGWqL+sg/gKrM977oFrYkdb4msM3hgUGonr7KlOsOFT5TU2rht9g==} @@ -456,10 +456,6 @@ packages: resolution: {integrity: sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==} engines: {node: '>=16.11.0'} - '@discordjs/rest@2.6.2': - resolution: {integrity: sha512-c5HI3hJuRWWrnpyCZAUYfIUTAEv9weX4tE2UfDqtM3ztizdKE9RFEf3G5IWhPyO+kPldUDpGv02XJAQzdUTC9Q==} - engines: {node: '>=18'} - '@discordjs/rest@2.6.3': resolution: {integrity: sha512-wvOylxNYJkwKjctS/Mn5GP1w9r3/rzyH+ThD1JlAca6zEdlHs8QWBBUQJpU5Q+W6DoIj/Ljh1IPlZs7hTU+UAg==} engines: {node: '>=18'} @@ -490,8 +486,8 @@ packages: '@electric-sql/pglite@0.4.3': resolution: {integrity: sha512-ichuWTgtd4mOM1G4SpyGJa5trT03lWbMypDV0fUXUCXg5hiHqVAz/bZyV68NqmkLB7WcYmj1RMJVSp8HV/v/ZQ==} - '@emnapi/runtime@1.11.2': - resolution: {integrity: sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==} + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} '@esbuild/aix-ppc64@0.28.1': resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} @@ -835,6 +831,13 @@ packages: peerDependencies: rollup: '>=2' + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + resolution: {integrity: sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==} + engines: {node: ^22.20 || ^24.12 || >=25} + cpu: [x64] + os: [linux] + libc: [glibc] + '@noble/ciphers@2.2.0': resolution: {integrity: sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA==} engines: {node: '>= 20.19.0'} @@ -1144,141 +1147,141 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.62.2': - resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + '@rollup/rollup-android-arm-eabi@4.62.4': + resolution: {integrity: sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.62.2': - resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + '@rollup/rollup-android-arm64@4.62.4': + resolution: {integrity: sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.62.2': - resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + '@rollup/rollup-darwin-arm64@4.62.4': + resolution: {integrity: sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.62.2': - resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + '@rollup/rollup-darwin-x64@4.62.4': + resolution: {integrity: sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.62.2': - resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + '@rollup/rollup-freebsd-arm64@4.62.4': + resolution: {integrity: sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.62.2': - resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + '@rollup/rollup-freebsd-x64@4.62.4': + resolution: {integrity: sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.62.2': - resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + '@rollup/rollup-linux-arm-gnueabihf@4.62.4': + resolution: {integrity: sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.62.2': - resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + '@rollup/rollup-linux-arm-musleabihf@4.62.4': + resolution: {integrity: sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.62.2': - resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + '@rollup/rollup-linux-arm64-gnu@4.62.4': + resolution: {integrity: sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.62.2': - resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + '@rollup/rollup-linux-arm64-musl@4.62.4': + resolution: {integrity: sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.62.2': - resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + '@rollup/rollup-linux-loong64-gnu@4.62.4': + resolution: {integrity: sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.62.2': - resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + '@rollup/rollup-linux-loong64-musl@4.62.4': + resolution: {integrity: sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.62.2': - resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + '@rollup/rollup-linux-ppc64-gnu@4.62.4': + resolution: {integrity: sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.62.2': - resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + '@rollup/rollup-linux-ppc64-musl@4.62.4': + resolution: {integrity: sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.62.2': - resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + '@rollup/rollup-linux-riscv64-gnu@4.62.4': + resolution: {integrity: sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.62.2': - resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + '@rollup/rollup-linux-riscv64-musl@4.62.4': + resolution: {integrity: sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.62.2': - resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + '@rollup/rollup-linux-s390x-gnu@4.62.4': + resolution: {integrity: sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.62.2': - resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + '@rollup/rollup-linux-x64-gnu@4.62.4': + resolution: {integrity: sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.62.2': - resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + '@rollup/rollup-linux-x64-musl@4.62.4': + resolution: {integrity: sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.62.2': - resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + '@rollup/rollup-openbsd-x64@4.62.4': + resolution: {integrity: sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.62.2': - resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + '@rollup/rollup-openharmony-arm64@4.62.4': + resolution: {integrity: sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.62.2': - resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + '@rollup/rollup-win32-arm64-msvc@4.62.4': + resolution: {integrity: sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.62.2': - resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + '@rollup/rollup-win32-ia32-msvc@4.62.4': + resolution: {integrity: sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.62.2': - resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + '@rollup/rollup-win32-x64-gnu@4.62.4': + resolution: {integrity: sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.62.2': - resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + '@rollup/rollup-win32-x64-msvc@4.62.4': + resolution: {integrity: sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==} cpu: [x64] os: [win32] @@ -1517,8 +1520,8 @@ packages: '@types/hast@3.0.5': resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} - '@types/lodash@4.17.24': - resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + '@types/lodash@4.17.25': + resolution: {integrity: sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==} '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} @@ -1532,8 +1535,8 @@ packages: '@types/node@26.1.2': resolution: {integrity: sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==} - '@types/pg@8.20.4': - resolution: {integrity: sha512-Jz7UDOlIiFJuacC0TlBoLyNtmwlA/wpIyPDd3tvUqlRM+HzkWy2xUgpFpaXtbfTAFF6sIGq5lsCDBdJnhky1Xg==} + '@types/pg@8.20.3': + resolution: {integrity: sha512-4Tvg+HO6+oQaAkpT8GTYoSExzpGGZz532GXgbbCElWJQeQdMozBWxEKNBhJJpHFjWXsMxqPbyypvj/89FWNoSQ==} '@types/pluralize@0.0.29': resolution: {integrity: sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA==} @@ -1726,8 +1729,8 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.17.0: - resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + acorn@8.18.0: + resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==} engines: {node: '>=0.4.0'} hasBin: true @@ -1760,8 +1763,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.11.3: - resolution: {integrity: sha512-sbT0Ui/CZwyAyy7icT1Gw5P1LKRlFaHwaF6tDCW5YHq2X5SeeZFphBuIagopSfwSSZq3sQcbmEL072yphxm7ew==} + baseline-browser-mapping@2.11.12: + resolution: {integrity: sha512-r7WnVImvVCeFpf2DOXfy41aPWzeNg3H/A2X4dKmy1QL0MSyyk/e7z8ihJ3N6Nn2PsdhkVlqnEfnUE4a05P2aTA==} engines: {node: '>=6.0.0'} hasBin: true @@ -1839,16 +1842,16 @@ packages: zod: optional: true - better-result@2.9.2: - resolution: {integrity: sha512-WIFoBPCdnTOdk9inkE1ZRvCZ4P0CpSkAiLlchC65N7n9DcjZ3NhqkBOlafzpOVnO8ixyi37kicmSJ3ENhPZl7Q==} + better-result@2.10.0: + resolution: {integrity: sha512-oQhh0y1qo2/ZKdAAEvHZAqKKiHOFU5k/bW96fE2ScgQOVkJRiHwB+nOS1SgFsYqRlxMDWvefXi9Q3px7QvgNDw==} body-parser@2.3.0: resolution: {integrity: sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==} engines: {node: '>=18'} - brace-expansion@5.0.7: - resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} - engines: {node: 18 || 20 || >=22} + brace-expansion@5.0.9: + resolution: {integrity: sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==} + engines: {node: 20 || >=22} browserslist@4.28.7: resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} @@ -2078,11 +2081,8 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - discord-api-types@0.38.50: - resolution: {integrity: sha512-J2n/bpIETX3DQ6AJ7/0xbsTLmYiJQtO/LKcXKC1YDbB56OUwtDbdXOFE8Q4g8jVGHBR2VAy1+D4ngaIgkMNV9w==} - - discord-api-types@0.38.51: - resolution: {integrity: sha512-SnaFHd+b8Z3HgjEIoSA1wkstXCt6J0Zrxvny0BJZZz7AhmFSetRY3DMrIdO3LFDMeeTxF7WKornnAGxaws5Adw==} + discord-api-types@0.38.52: + resolution: {integrity: sha512-uwe9EKfbjsmgWc2fdFjvDbj+dQqx3lp7wqDCmIha0jInuU+xeQjkCK9tMMn+p7RXfdVQORCInq4cD3U2ymDmyg==} discord.js@14.27.0: resolution: {integrity: sha512-qHbFlFG2N7y3LjPySYsL6A1+BnX6bkTVgo842EX0CqVPk/KTMwZkojPHEXKsQUpWZNyz5BISNHK1cPpQw0+m4A==} @@ -2114,8 +2114,8 @@ packages: effect@3.20.0: resolution: {integrity: sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==} - electron-to-chromium@1.5.396: - resolution: {integrity: sha512-yHiw2Y3C3H9U6TMbOfoWK/BPreiOPXRfTWPBwQBoZG6/8TB6eOPnsy5oaRYuatR7Fw2SJ4kKforgufeo7fq0EQ==} + electron-to-chromium@1.5.400: + resolution: {integrity: sha512-96EWDNjM59SYflgeV5Ylsf4EMiq1a25YjCnJH7cxn/AF2H3pILRweaUnoLax0yKHWdpOzY6JKEu45e8irqZIHA==} elkjs@0.11.1: resolution: {integrity: sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg==} @@ -2131,8 +2131,8 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - enhanced-resolve@5.24.3: - resolution: {integrity: sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ==} + enhanced-resolve@5.24.5: + resolution: {integrity: sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==} engines: {node: '>=10.13.0'} env-paths@3.0.0: @@ -2212,8 +2212,8 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} - exsolve@1.1.0: - resolution: {integrity: sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==} + exsolve@1.1.1: + resolution: {integrity: sha512-9U/jZUgjnSGyntRr6y5Muu1MJcwFl6kPu7k8qLF0IMNfLqvw0NZ4nnVDq0RVoZ0RvCyumib4Ez3KYrVfilrw+g==} extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -2231,8 +2231,8 @@ packages: fast-querystring@1.1.2: resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==} - fast-uri@3.1.3: - resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==} + fast-uri@3.1.5: + resolution: {integrity: sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==} fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} @@ -2297,8 +2297,8 @@ packages: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - giget@3.3.0: - resolution: {integrity: sha512-gzi2D96p+AMfDcmJHGDj3KJ9NRiwvlFAU5yfa3ROwWZmFUjX4P43x3BcyRaOMMLto1vUo7C+86+MFhYTl6Ryiw==} + giget@3.3.1: + resolution: {integrity: sha512-r+mvuDjrjMpsdw46Kmeydb8bdHm7wOKw8wNBtTndkjbPjgAp5oUJUxRE76wZFknxIPokfWvep2qSXK37aXE6zg==} hasBin: true github-slugger@2.0.0: @@ -2406,8 +2406,8 @@ packages: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true - jose@6.2.4: - resolution: {integrity: sha512-N8acGzVsQy6M/fjFcxtysNc4Q379TcM5dM/qKkNtsHFji88yANnXTr7BLeP75iPnFwBfQzM/jg2BZ9+HZrHCZA==} + jose@6.2.8: + resolution: {integrity: sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2600,9 +2600,6 @@ packages: resolution: {integrity: sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==} engines: {bun: '>=1.0.0', deno: '>=1.30.0', node: '>=8.0.0'} - magic-bytes.js@1.13.0: - resolution: {integrity: sha512-afO2mnxW7GDTXMm5/AoN1WuOcdoKhtgXjIvHmobqTD1grNplhGdv3PFOyjCVmrnOZBIT/gD/koDKpYG+0mvHcg==} - magic-bytes.js@1.13.1: resolution: {integrity: sha512-x5sn4UX2k5gCWlcfmoFwG4TPie8+dctESyqOBdhB5p6MsgWXdBKGmt9nXPObj/JI50TTL928lc5Yt1WntMn1bw==} @@ -2789,8 +2786,8 @@ packages: resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} engines: {node: '>=18'} - minimatch@10.2.5: - resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} engines: {node: 18 || 20 || >=22} minimist@1.2.8: @@ -2823,8 +2820,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanostores@1.4.1: - resolution: {integrity: sha512-PGd3uPojJB9Z07d5NX3Db/SOSBbyy3wLMUGq0GpnEEJfVzY9mq7daPMAZ3jObV5D3Jn+YKND636eI5ULg7F80Q==} + nanostores@1.4.2: + resolution: {integrity: sha512-Wxv8Roefr2nqtiRG0bnaFlpYqpIVtOEeJZHaH+4nGgOK1/7n6OHOuHCb/bhqrNQgZM8fyd0s1PqhdrJc9Ib44g==} engines: {node: ^20.0.0 || >=22.0.0} negotiator@0.6.4: @@ -3106,8 +3103,8 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup@4.62.2: - resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + rollup@4.62.4: + resolution: {integrity: sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -3299,8 +3296,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.23.4: - resolution: {integrity: sha512-ZiUQ8oT/KzN51mJUWPqARYqwFLFJZtGZipRkw1ynHMr9vy3eU77m5yfF3Gzm6meEg/beW+lUu3fHYgskTN2oVQ==} + tsx@4.23.5: + resolution: {integrity: sha512-rw55FUaqOoI7RvlQwLbhO4nSDApnQ4/CykPuiQ/EPvtrX3WA9Ig55jIt9VvbBJbzJuj12ueRu4PMZ2SxPVbihg==} engines: {node: '>=18.0.0'} hasBin: true @@ -3325,16 +3322,12 @@ packages: undici-types@8.3.0: resolution: {integrity: sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==} - undici@6.27.0: - resolution: {integrity: sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==} - engines: {node: '>=18.17'} - undici@6.28.0: resolution: {integrity: sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==} engines: {node: '>=18.17'} - undici@8.9.0: - resolution: {integrity: sha512-aWZpUj7XoGonMClx4gdDRfgBjqeA+F473aDmROQQbM9n6PRfK/u1q/a0X4wMTgcHfT8H6fpbt98PFuDUwFg2YA==} + undici@8.10.0: + resolution: {integrity: sha512-HvltHd7avK13QIw/oLe4qoOLyoVSoafqJ2jYOrtMRBkbYT31eiBQ8O0ehRKZiEZCMEyLFQNIADpgCWC5fALvYQ==} engines: {node: '>=22.19.0'} unified@11.0.5: @@ -3441,8 +3434,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.21.1: - resolution: {integrity: sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==} + ws@8.21.2: + resolution: {integrity: sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -3494,14 +3487,14 @@ snapshots: '@babel/core@7.29.7': dependencies: '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 + '@babel/generator': 7.29.8 '@babel/helper-compilation-targets': 7.29.7 '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) '@babel/helpers': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@babel/template': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -3511,17 +3504,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.29.7': + '@babel/generator@7.29.8': dependencies: - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-annotate-as-pure@7.29.7': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/helper-compilation-targets@7.29.7': dependencies: @@ -3539,7 +3532,7 @@ snapshots: '@babel/helper-optimise-call-expression': 7.29.7 '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -3548,15 +3541,15 @@ snapshots: '@babel/helper-member-expression-to-functions@7.29.7': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color '@babel/helper-module-imports@7.29.7': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -3565,13 +3558,13 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-module-imports': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8 transitivePeerDependencies: - supports-color '@babel/helper-optimise-call-expression@7.29.7': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/helper-plugin-utils@7.29.7': {} @@ -3580,14 +3573,14 @@ snapshots: '@babel/core': 7.29.7 '@babel/helper-member-expression-to-functions': 7.29.7 '@babel/helper-optimise-call-expression': 7.29.7 - '@babel/traverse': 7.29.7 + '@babel/traverse': 7.29.8 transitivePeerDependencies: - supports-color '@babel/helper-skip-transparent-expression-wrappers@7.29.7': dependencies: - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -3600,11 +3593,11 @@ snapshots: '@babel/helpers@7.29.7': dependencies: '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 - '@babel/parser@7.29.7': + '@babel/parser@7.29.8': dependencies: - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': dependencies: @@ -3649,71 +3642,71 @@ snapshots: '@babel/template@7.29.7': dependencies: '@babel/code-frame': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 - '@babel/traverse@7.29.7': + '@babel/traverse@7.29.8': dependencies: '@babel/code-frame': 7.29.7 - '@babel/generator': 7.29.7 + '@babel/generator': 7.29.8 '@babel/helper-globals': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/parser': 7.29.8 '@babel/template': 7.29.7 - '@babel/types': 7.29.7 + '@babel/types': 7.29.8 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.29.7': + '@babel/types@7.29.8': dependencies: '@babel/helper-string-parser': 7.29.7 '@babel/helper-validator-identifier': 7.29.7 - '@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1)': + '@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2)': dependencies: '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 '@opentelemetry/semantic-conventions': 1.43.0 '@standard-schema/spec': 1.1.0 better-call: 1.3.7(zod@4.4.3) - jose: 6.2.4 + jose: 6.2.8 kysely: 0.29.4 - nanostores: 1.4.1 + nanostores: 1.4.2 zod: 4.4.3 - '@better-auth/drizzle-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)': + '@better-auth/drizzle-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) + '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 - '@better-auth/kysely-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(kysely@0.29.4)': + '@better-auth/kysely-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.29.4)': dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) + '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 optionalDependencies: kysely: 0.29.4 - '@better-auth/memory-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)': + '@better-auth/memory-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) + '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 - '@better-auth/mongo-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)': + '@better-auth/mongo-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)': dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) + '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 - '@better-auth/prisma-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(@prisma/client@7.9.1(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))(typescript@7.0.2))(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))': + '@better-auth/prisma-adapter@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@prisma/client@7.9.1(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))(typescript@7.0.2))(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))': dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) + '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 optionalDependencies: '@prisma/client': 7.9.1(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))(typescript@7.0.2) prisma: 7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2) - '@better-auth/telemetry@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': + '@better-auth/telemetry@1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)': dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) + '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 @@ -3721,10 +3714,6 @@ snapshots: dependencies: '@noble/hashes': 2.2.0 - '@better-auth/utils@0.4.3': - dependencies: - '@noble/hashes': 2.2.0 - '@better-fetch/fetch@1.3.1': {} '@biomejs/biome@2.5.6': @@ -3767,7 +3756,7 @@ snapshots: '@discordjs/formatters': 0.6.2 '@discordjs/util': 1.2.0 '@sapphire/shapeshift': 4.0.0 - discord-api-types: 0.38.51 + discord-api-types: 0.38.52 fast-deep-equal: 3.1.3 ts-mixer: 6.0.4 tslib: 2.8.1 @@ -3783,26 +3772,14 @@ snapshots: '@discordjs/ws': 2.0.4 '@sapphire/snowflake': 3.5.5 '@vladfrangu/async_event_emitter': 2.4.7 - discord-api-types: 0.38.51 + discord-api-types: 0.38.52 transitivePeerDependencies: - bufferutil - utf-8-validate '@discordjs/formatters@0.6.2': dependencies: - discord-api-types: 0.38.51 - - '@discordjs/rest@2.6.2': - dependencies: - '@discordjs/collection': 2.1.1 - '@discordjs/util': 1.2.0 - '@sapphire/async-queue': 1.5.5 - '@sapphire/snowflake': 3.5.5 - '@vladfrangu/async_event_emitter': 2.4.7 - discord-api-types: 0.38.51 - magic-bytes.js: 1.13.0 - tslib: 2.8.1 - undici: 6.27.0 + discord-api-types: 0.38.52 '@discordjs/rest@2.6.3': dependencies: @@ -3811,14 +3788,14 @@ snapshots: '@sapphire/async-queue': 1.5.5 '@sapphire/snowflake': 3.5.5 '@vladfrangu/async_event_emitter': 2.4.7 - discord-api-types: 0.38.51 + discord-api-types: 0.38.52 magic-bytes.js: 1.13.1 tslib: 2.8.1 undici: 6.28.0 '@discordjs/util@1.2.0': dependencies: - discord-api-types: 0.38.51 + discord-api-types: 0.38.52 '@discordjs/ws@1.2.3': dependencies: @@ -3828,9 +3805,9 @@ snapshots: '@sapphire/async-queue': 1.5.5 '@types/ws': 8.18.1 '@vladfrangu/async_event_emitter': 2.4.7 - discord-api-types: 0.38.51 + discord-api-types: 0.38.52 tslib: 2.8.1 - ws: 8.21.1 + ws: 8.21.2 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -3843,9 +3820,9 @@ snapshots: '@sapphire/async-queue': 1.5.5 '@types/ws': 8.18.1 '@vladfrangu/async_event_emitter': 2.4.7 - discord-api-types: 0.38.51 + discord-api-types: 0.38.52 tslib: 2.8.1 - ws: 8.21.1 + ws: 8.21.2 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -3860,7 +3837,7 @@ snapshots: '@electric-sql/pglite@0.4.3': {} - '@emnapi/runtime@1.11.2': + '@emnapi/runtime@1.11.3': dependencies: tslib: 2.8.1 optional: true @@ -4032,7 +4009,7 @@ snapshots: '@img/sharp-wasm32@0.35.3': dependencies: - '@emnapi/runtime': 1.11.2 + '@emnapi/runtime': 1.11.3 optional: true '@img/sharp-webcontainers-wasm32@0.35.3': @@ -4074,7 +4051,7 @@ snapshots: '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.5 '@types/mdx': 2.0.14 - acorn: 8.17.0 + acorn: 8.18.0 collapse-white-space: 2.1.0 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 @@ -4083,7 +4060,7 @@ snapshots: hast-util-to-jsx-runtime: 2.3.6 markdown-extensions: 2.0.0 recma-build-jsx: 1.0.0 - recma-jsx: 1.0.1(acorn@8.17.0) + recma-jsx: 1.0.1(acorn@8.18.0) recma-stringify: 1.0.0 rehype-recma: 1.0.0 remark-mdx: 3.1.1 @@ -4098,16 +4075,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@mdx-js/rollup@3.1.1(rollup@4.62.2)': + '@mdx-js/rollup@3.1.1(rollup@4.62.4)': dependencies: '@mdx-js/mdx': 3.1.1 - '@rollup/pluginutils': 5.4.0(rollup@4.62.2) - rollup: 4.62.2 + '@rollup/pluginutils': 5.4.0(rollup@4.62.4) + rollup: 4.62.4 source-map: 0.7.6 vfile: 6.0.3 transitivePeerDependencies: - supports-color + '@napi-rs/lzma-linux-x64-gnu@1.5.1': + optional: true + '@noble/ciphers@2.2.0': {} '@noble/hashes@2.2.0': {} @@ -4119,7 +4099,7 @@ snapshots: '@prisma/adapter-pg@7.9.1': dependencies: '@prisma/driver-adapter-utils': 7.9.1 - '@types/pg': 8.20.4 + '@types/pg': 8.20.3 pg: 8.22.0 postgres-array: 3.0.4 transitivePeerDependencies: @@ -4199,7 +4179,7 @@ snapshots: '@prisma/streams-local@0.1.11': dependencies: ajv: 8.20.0 - better-result: 2.9.2 + better-result: 2.10.0 env-paths: 3.0.0 proper-lockfile: 4.1.2 @@ -4278,15 +4258,15 @@ snapshots: optionalDependencies: '@types/react': 19.2.18 - '@react-router/dev@8.3.0(@react-router/serve@8.3.0(react-router@8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@7.0.2))(react-router@8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@7.0.2)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4))': + '@react-router/dev@8.3.0(@react-router/serve@8.3.0(react-router@8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@7.0.2))(react-router@8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@7.0.2)(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.5))': dependencies: '@babel/core': 7.29.7 - '@babel/generator': 7.29.7 - '@babel/parser': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/parser': 7.29.8 '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 '@react-router/node': 8.3.0(react-router@8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@7.0.2) '@remix-run/node-fetch-server': 0.13.3 babel-dead-code-elimination: 1.0.12 @@ -4307,7 +4287,7 @@ snapshots: semver: 7.8.5 tinyglobby: 0.2.17 valibot: 1.4.2(typescript@7.0.2) - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4) + vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.5) optionalDependencies: '@react-router/serve': 8.3.0(react-router@8.3.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(typescript@7.0.2) typescript: 7.0.2 @@ -4390,98 +4370,98 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} - '@rollup/pluginutils@5.4.0(rollup@4.62.2)': + '@rollup/pluginutils@5.4.0(rollup@4.62.4)': dependencies: '@types/estree': 1.0.9 estree-walker: 2.0.2 picomatch: 4.0.5 optionalDependencies: - rollup: 4.62.2 + rollup: 4.62.4 - '@rollup/rollup-android-arm-eabi@4.62.2': + '@rollup/rollup-android-arm-eabi@4.62.4': optional: true - '@rollup/rollup-android-arm64@4.62.2': + '@rollup/rollup-android-arm64@4.62.4': optional: true - '@rollup/rollup-darwin-arm64@4.62.2': + '@rollup/rollup-darwin-arm64@4.62.4': optional: true - '@rollup/rollup-darwin-x64@4.62.2': + '@rollup/rollup-darwin-x64@4.62.4': optional: true - '@rollup/rollup-freebsd-arm64@4.62.2': + '@rollup/rollup-freebsd-arm64@4.62.4': optional: true - '@rollup/rollup-freebsd-x64@4.62.2': + '@rollup/rollup-freebsd-x64@4.62.4': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + '@rollup/rollup-linux-arm-gnueabihf@4.62.4': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.62.2': + '@rollup/rollup-linux-arm-musleabihf@4.62.4': optional: true - '@rollup/rollup-linux-arm64-gnu@4.62.2': + '@rollup/rollup-linux-arm64-gnu@4.62.4': optional: true - '@rollup/rollup-linux-arm64-musl@4.62.2': + '@rollup/rollup-linux-arm64-musl@4.62.4': optional: true - '@rollup/rollup-linux-loong64-gnu@4.62.2': + '@rollup/rollup-linux-loong64-gnu@4.62.4': optional: true - '@rollup/rollup-linux-loong64-musl@4.62.2': + '@rollup/rollup-linux-loong64-musl@4.62.4': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.62.2': + '@rollup/rollup-linux-ppc64-gnu@4.62.4': optional: true - '@rollup/rollup-linux-ppc64-musl@4.62.2': + '@rollup/rollup-linux-ppc64-musl@4.62.4': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.62.2': + '@rollup/rollup-linux-riscv64-gnu@4.62.4': optional: true - '@rollup/rollup-linux-riscv64-musl@4.62.2': + '@rollup/rollup-linux-riscv64-musl@4.62.4': optional: true - '@rollup/rollup-linux-s390x-gnu@4.62.2': + '@rollup/rollup-linux-s390x-gnu@4.62.4': optional: true - '@rollup/rollup-linux-x64-gnu@4.62.2': + '@rollup/rollup-linux-x64-gnu@4.62.4': optional: true - '@rollup/rollup-linux-x64-musl@4.62.2': + '@rollup/rollup-linux-x64-musl@4.62.4': optional: true - '@rollup/rollup-openbsd-x64@4.62.2': + '@rollup/rollup-openbsd-x64@4.62.4': optional: true - '@rollup/rollup-openharmony-arm64@4.62.2': + '@rollup/rollup-openharmony-arm64@4.62.4': optional: true - '@rollup/rollup-win32-arm64-msvc@4.62.2': + '@rollup/rollup-win32-arm64-msvc@4.62.4': optional: true - '@rollup/rollup-win32-ia32-msvc@4.62.2': + '@rollup/rollup-win32-ia32-msvc@4.62.4': optional: true - '@rollup/rollup-win32-x64-gnu@4.62.2': + '@rollup/rollup-win32-x64-gnu@4.62.4': optional: true - '@rollup/rollup-win32-x64-msvc@4.62.2': + '@rollup/rollup-win32-x64-msvc@4.62.4': optional: true '@sapphire/async-queue@1.5.5': {} '@sapphire/discord-utilities@3.5.0': dependencies: - discord-api-types: 0.38.51 + discord-api-types: 0.38.52 '@sapphire/discord-utilities@4.0.0': dependencies: - discord-api-types: 0.38.51 + discord-api-types: 0.38.52 '@sapphire/discord.js-utilities@7.3.3': dependencies: @@ -4541,7 +4521,7 @@ snapshots: '@tailwindcss/node@4.3.3': dependencies: '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.24.3 + enhanced-resolve: 5.24.5 jiti: 2.7.0 lightningcss: 1.32.0 magic-string: 0.30.21 @@ -4599,12 +4579,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.3 '@tailwindcss/oxide-win32-x64-msvc': 4.3.3 - '@tailwindcss/vite@4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4))': + '@tailwindcss/vite@4.3.3(vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.5))': dependencies: '@tailwindcss/node': 4.3.3 '@tailwindcss/oxide': 4.3.3 tailwindcss: 4.3.3 - vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4) + vite: 8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.5) '@turbo/darwin-64@2.10.8': optional: true @@ -4670,7 +4650,7 @@ snapshots: dependencies: '@types/unist': 3.0.3 - '@types/lodash@4.17.24': {} + '@types/lodash@4.17.25': {} '@types/mdast@4.0.4': dependencies: @@ -4684,7 +4664,7 @@ snapshots: dependencies: undici-types: 8.3.0 - '@types/pg@8.20.4': + '@types/pg@8.20.3': dependencies: '@types/node': 26.1.2 pg-protocol: 1.15.0 @@ -4800,7 +4780,7 @@ snapshots: '@visx/responsive@4.0.1-alpha.0(react@19.2.8)': dependencies: - '@types/lodash': 4.17.24 + '@types/lodash': 4.17.25 '@types/react': 19.2.18 lodash: 4.18.1 react: 19.2.8 @@ -4811,7 +4791,7 @@ snapshots: '@visx/shape@4.0.1-alpha.0(react@19.2.8)': dependencies: - '@types/lodash': 4.17.24 + '@types/lodash': 4.17.25 '@types/react': 19.2.18 '@visx/curve': 4.0.1-alpha.0 '@visx/group': 4.0.1-alpha.0(react@19.2.8) @@ -4854,16 +4834,16 @@ snapshots: mime-types: 3.0.2 negotiator: 1.0.0 - acorn-jsx@5.3.2(acorn@8.17.0): + acorn-jsx@5.3.2(acorn@8.18.0): dependencies: - acorn: 8.17.0 + acorn: 8.18.0 - acorn@8.17.0: {} + acorn@8.18.0: {} ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 - fast-uri: 3.1.3 + fast-uri: 3.1.5 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 @@ -4878,9 +4858,9 @@ snapshots: babel-dead-code-elimination@1.0.12: dependencies: '@babel/core': 7.29.7 - '@babel/parser': 7.29.7 - '@babel/traverse': 7.29.7 - '@babel/types': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 transitivePeerDependencies: - supports-color @@ -4888,7 +4868,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.11.3: {} + baseline-browser-mapping@2.11.12: {} basic-auth@2.0.1: dependencies: @@ -4896,22 +4876,22 @@ snapshots: better-auth@1.6.25(@prisma/client@7.9.1(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))(typescript@7.0.2))(mysql2@3.15.3)(pg@8.22.0)(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))(react-dom@19.2.8(react@19.2.8))(react@19.2.8): dependencies: - '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1) - '@better-auth/drizzle-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2) - '@better-auth/kysely-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(kysely@0.29.4) - '@better-auth/memory-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2) - '@better-auth/mongo-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2) - '@better-auth/prisma-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(@prisma/client@7.9.1(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))(typescript@7.0.2))(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2)) - '@better-auth/telemetry': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.4)(kysely@0.29.4)(nanostores@1.4.1))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) + '@better-auth/core': 1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2) + '@better-auth/drizzle-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/kysely-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.29.4) + '@better-auth/memory-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/mongo-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2) + '@better-auth/prisma-adapter': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@prisma/client@7.9.1(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))(typescript@7.0.2))(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2)) + '@better-auth/telemetry': 1.6.25(@better-auth/core@1.6.25(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.8)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1) '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 '@noble/ciphers': 2.2.0 '@noble/hashes': 2.2.0 better-call: 1.3.7(zod@4.4.3) defu: 6.1.7 - jose: 6.2.4 + jose: 6.2.8 kysely: 0.29.4 - nanostores: 1.4.1 + nanostores: 1.4.2 zod: 4.4.3 optionalDependencies: '@prisma/client': 7.9.1(prisma@7.9.1(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)(typescript@7.0.2))(typescript@7.0.2) @@ -4926,14 +4906,14 @@ snapshots: better-call@1.3.7(zod@4.4.3): dependencies: - '@better-auth/utils': 0.4.3 + '@better-auth/utils': 0.4.2 '@better-fetch/fetch': 1.3.1 rou3: 0.7.12 set-cookie-parser: 3.1.2 optionalDependencies: zod: 4.4.3 - better-result@2.9.2: {} + better-result@2.10.0: {} body-parser@2.3.0: dependencies: @@ -4949,15 +4929,15 @@ snapshots: transitivePeerDependencies: - supports-color - brace-expansion@5.0.7: + brace-expansion@5.0.9: dependencies: balanced-match: 4.0.4 browserslist@4.28.7: dependencies: - baseline-browser-mapping: 2.11.3 + baseline-browser-mapping: 2.11.12 caniuse-lite: 1.0.30001806 - electron-to-chromium: 1.5.396 + electron-to-chromium: 1.5.400 node-releases: 2.0.51 update-browserslist-db: 1.2.3(browserslist@4.28.7) @@ -4971,8 +4951,8 @@ snapshots: confbox: 0.2.4 defu: 6.1.7 dotenv: 17.4.2 - exsolve: 1.1.0 - giget: 3.3.0 + exsolve: 1.1.1 + giget: 3.3.1 jiti: 2.7.0 ohash: 2.0.11 pathe: 2.0.3 @@ -5153,25 +5133,23 @@ snapshots: dependencies: dequal: 2.0.3 - discord-api-types@0.38.50: {} - - discord-api-types@0.38.51: {} + discord-api-types@0.38.52: {} discord.js@14.27.0: dependencies: '@discordjs/builders': 1.14.1 '@discordjs/collection': 1.5.3 '@discordjs/formatters': 0.6.2 - '@discordjs/rest': 2.6.2 + '@discordjs/rest': 2.6.3 '@discordjs/util': 1.2.0 '@discordjs/ws': 1.2.3 '@sapphire/snowflake': 3.5.5 - discord-api-types: 0.38.50 + discord-api-types: 0.38.52 fast-deep-equal: 3.1.3 lodash.snakecase: 4.1.1 - magic-bytes.js: 1.13.0 + magic-bytes.js: 1.13.1 tslib: 2.8.1 - undici: 6.27.0 + undici: 6.28.0 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -5204,7 +5182,7 @@ snapshots: '@standard-schema/spec': 1.1.0 fast-check: 3.23.2 - electron-to-chromium@1.5.396: {} + electron-to-chromium@1.5.400: {} elkjs@0.11.1: {} @@ -5214,7 +5192,7 @@ snapshots: encodeurl@2.0.0: {} - enhanced-resolve@5.24.3: + enhanced-resolve@5.24.5: dependencies: graceful-fs: 4.2.11 tapable: 2.3.3 @@ -5241,7 +5219,7 @@ snapshots: esast-util-from-js@2.0.1: dependencies: '@types/estree-jsx': 1.0.5 - acorn: 8.17.0 + acorn: 8.18.0 esast-util-from-estree: 2.0.0 vfile-message: 4.0.3 @@ -5352,7 +5330,7 @@ snapshots: transitivePeerDependencies: - supports-color - exsolve@1.1.0: {} + exsolve@1.1.1: {} extend@3.0.2: {} @@ -5368,7 +5346,7 @@ snapshots: dependencies: fast-decode-uri-component: 1.0.1 - fast-uri@3.1.3: {} + fast-uri@3.1.5: {} fdir@6.5.0(picomatch@4.0.5): optionalDependencies: @@ -5435,13 +5413,13 @@ snapshots: dunder-proto: 1.0.1 es-object-atoms: 1.1.2 - giget@3.3.0: {} + giget@3.3.1: {} github-slugger@2.0.0: {} glob@13.0.6: dependencies: - minimatch: 10.2.5 + minimatch: 10.2.6 minipass: 7.1.3 path-scurry: 2.0.2 @@ -5561,7 +5539,7 @@ snapshots: jiti@2.7.0: {} - jose@6.2.4: {} + jose@6.2.8: {} js-tokens@4.0.0: {} @@ -5687,8 +5665,6 @@ snapshots: lru.min@1.1.4: {} - magic-bytes.js@1.13.0: {} - magic-bytes.js@1.13.1: {} magic-string@0.30.21: @@ -5987,8 +5963,8 @@ snapshots: micromark-extension-mdxjs@3.0.0: dependencies: - acorn: 8.17.0 - acorn-jsx: 5.3.2(acorn@8.17.0) + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) micromark-extension-mdx-expression: 3.0.1 micromark-extension-mdx-jsx: 3.0.2 micromark-extension-mdx-md: 2.0.0 @@ -6138,9 +6114,9 @@ snapshots: dependencies: mime-db: 1.54.0 - minimatch@10.2.5: + minimatch@10.2.6: dependencies: - brace-expansion: 5.0.7 + brace-expansion: 5.0.9 minimist@1.2.8: {} @@ -6178,7 +6154,7 @@ snapshots: nanoid@3.3.17: {} - nanostores@1.4.1: {} + nanostores@1.4.2: {} negotiator@0.6.4: {} @@ -6273,7 +6249,7 @@ snapshots: pkg-types@2.3.1: dependencies: confbox: 0.2.4 - exsolve: 1.1.0 + exsolve: 1.1.1 pathe: 2.0.3 pluralize@8.0.0: {} @@ -6375,10 +6351,10 @@ snapshots: estree-util-build-jsx: 3.0.1 vfile: 6.0.3 - recma-jsx@1.0.1(acorn@8.17.0): + recma-jsx@1.0.1(acorn@8.18.0): dependencies: - acorn: 8.17.0 - acorn-jsx: 5.3.2(acorn@8.17.0) + acorn: 8.18.0 + acorn-jsx: 5.3.2(acorn@8.18.0) estree-util-to-js: 2.0.0 recma-parse: 1.0.0 recma-stringify: 1.0.0 @@ -6499,35 +6475,36 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.2.2 '@rolldown/binding-win32-x64-msvc': 1.2.2 - rollup@4.62.2: + rollup@4.62.4: dependencies: '@types/estree': 1.0.9 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.62.2 - '@rollup/rollup-android-arm64': 4.62.2 - '@rollup/rollup-darwin-arm64': 4.62.2 - '@rollup/rollup-darwin-x64': 4.62.2 - '@rollup/rollup-freebsd-arm64': 4.62.2 - '@rollup/rollup-freebsd-x64': 4.62.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 - '@rollup/rollup-linux-arm-musleabihf': 4.62.2 - '@rollup/rollup-linux-arm64-gnu': 4.62.2 - '@rollup/rollup-linux-arm64-musl': 4.62.2 - '@rollup/rollup-linux-loong64-gnu': 4.62.2 - '@rollup/rollup-linux-loong64-musl': 4.62.2 - '@rollup/rollup-linux-ppc64-gnu': 4.62.2 - '@rollup/rollup-linux-ppc64-musl': 4.62.2 - '@rollup/rollup-linux-riscv64-gnu': 4.62.2 - '@rollup/rollup-linux-riscv64-musl': 4.62.2 - '@rollup/rollup-linux-s390x-gnu': 4.62.2 - '@rollup/rollup-linux-x64-gnu': 4.62.2 - '@rollup/rollup-linux-x64-musl': 4.62.2 - '@rollup/rollup-openbsd-x64': 4.62.2 - '@rollup/rollup-openharmony-arm64': 4.62.2 - '@rollup/rollup-win32-arm64-msvc': 4.62.2 - '@rollup/rollup-win32-ia32-msvc': 4.62.2 - '@rollup/rollup-win32-x64-gnu': 4.62.2 - '@rollup/rollup-win32-x64-msvc': 4.62.2 + '@napi-rs/lzma-linux-x64-gnu': 1.5.1 + '@rollup/rollup-android-arm-eabi': 4.62.4 + '@rollup/rollup-android-arm64': 4.62.4 + '@rollup/rollup-darwin-arm64': 4.62.4 + '@rollup/rollup-darwin-x64': 4.62.4 + '@rollup/rollup-freebsd-arm64': 4.62.4 + '@rollup/rollup-freebsd-x64': 4.62.4 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.4 + '@rollup/rollup-linux-arm-musleabihf': 4.62.4 + '@rollup/rollup-linux-arm64-gnu': 4.62.4 + '@rollup/rollup-linux-arm64-musl': 4.62.4 + '@rollup/rollup-linux-loong64-gnu': 4.62.4 + '@rollup/rollup-linux-loong64-musl': 4.62.4 + '@rollup/rollup-linux-ppc64-gnu': 4.62.4 + '@rollup/rollup-linux-ppc64-musl': 4.62.4 + '@rollup/rollup-linux-riscv64-gnu': 4.62.4 + '@rollup/rollup-linux-riscv64-musl': 4.62.4 + '@rollup/rollup-linux-s390x-gnu': 4.62.4 + '@rollup/rollup-linux-x64-gnu': 4.62.4 + '@rollup/rollup-linux-x64-musl': 4.62.4 + '@rollup/rollup-openbsd-x64': 4.62.4 + '@rollup/rollup-openharmony-arm64': 4.62.4 + '@rollup/rollup-win32-arm64-msvc': 4.62.4 + '@rollup/rollup-win32-ia32-msvc': 4.62.4 + '@rollup/rollup-win32-x64-gnu': 4.62.4 + '@rollup/rollup-win32-x64-msvc': 4.62.4 fsevents: 2.3.3 rou3@0.7.12: {} @@ -6743,7 +6720,7 @@ snapshots: tslib@2.8.1: {} - tsx@4.23.4: + tsx@4.23.5: dependencies: esbuild: 0.28.1 optionalDependencies: @@ -6791,11 +6768,9 @@ snapshots: undici-types@8.3.0: {} - undici@6.27.0: {} - undici@6.28.0: {} - undici@8.9.0: {} + undici@8.10.0: {} unified@11.0.5: dependencies: @@ -6858,7 +6833,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.4): + vite@8.2.0(@types/node@26.1.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.5): dependencies: lightningcss: 1.33.0 picomatch: 4.0.5 @@ -6870,7 +6845,7 @@ snapshots: esbuild: 0.28.1 fsevents: 2.3.3 jiti: 2.7.0 - tsx: 4.23.4 + tsx: 4.23.5 which@2.0.2: dependencies: @@ -6884,7 +6859,7 @@ snapshots: wrappy@1.0.2: {} - ws@8.21.1: {} + ws@8.21.2: {} xtend@4.0.2: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 089b2db..613996a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,9 @@ packages: - 'apps/*' - 'packages/*' +overrides: + fast-uri: ^3.1.5 +minimumReleaseAge: 10080 # 7d allowBuilds: '@prisma/engines': true esbuild: true From 365514fa4e69e97062f217b2686b500563ac6956 Mon Sep 17 00:00:00 2001 From: doodles172 Date: Tue, 11 Aug 2026 13:51:40 +0200 Subject: [PATCH 12/13] fix(vite.conf): replace __dirname with import.meta.dirname --- apps/dash/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dash/vite.config.ts b/apps/dash/vite.config.ts index a1d7390..78f58e8 100644 --- a/apps/dash/vite.config.ts +++ b/apps/dash/vite.config.ts @@ -14,7 +14,7 @@ import { defineConfig } from 'vite'; export default defineConfig({ resolve: { alias: { - '~': path.resolve(__dirname, './app'), + '~': path.resolve(import.meta.dirname, './app'), }, }, plugins: [ From c68343f87ff886396f2a60fb73f05935de5e5cb2 Mon Sep 17 00:00:00 2001 From: doodles172 Date: Tue, 11 Aug 2026 15:43:39 +0200 Subject: [PATCH 13/13] fix(starboard): only post if everyone can view the channel + if post is deleted don't repost --- apps/bot/src/lib/starboard.ts | 79 +++++++++++++++++++-- apps/bot/src/listeners/channelDelete.ts | 13 +++- apps/bot/src/listeners/messageDelete.ts | 2 + apps/bot/src/listeners/messageDeleteBulk.ts | 2 + 4 files changed, 86 insertions(+), 10 deletions(-) diff --git a/apps/bot/src/lib/starboard.ts b/apps/bot/src/lib/starboard.ts index 08acdf2..d07f5df 100644 --- a/apps/bot/src/lib/starboard.ts +++ b/apps/bot/src/lib/starboard.ts @@ -2,13 +2,17 @@ // Copyright(C) 2026 Vantern // SPDX-License-Identifier: AGPL-3.0-or-later -import { prisma } from '@questbot/database'; +import { type Prisma, prisma } from '@questbot/database'; import { DiscordAPIError, EmbedBuilder, + type Guild, + type GuildTextBasedChannel, type Message, type MessageReaction, + OverwriteType, type PartialUser, + PermissionFlagsBits, RESTJSONErrorCodes, type User, } from 'discord.js'; @@ -62,6 +66,27 @@ async function countStars(emoji: string, message: Message, posted: Message | nul return stars + mirrored - both; } +function appearsAcrossChannels(source: GuildTextBasedChannel, starboard: GuildTextBasedChannel): boolean { + if (source.permissionsFor(source.guild.roles.everyone)?.has(PermissionFlagsBits.ViewChannel)) return false; + + for (const role of source.guild.roles.cache.values()) { + if (!starboard.permissionsFor(role)?.has(PermissionFlagsBits.ViewChannel)) continue; + if (!source.permissionsFor(role)?.has(PermissionFlagsBits.ViewChannel)) return true; + } + + const overwrites = starboard.isThread() ? starboard.parent?.permissionOverwrites : starboard.permissionOverwrites; + + for (const overwrite of overwrites?.cache.values() ?? []) { + if (overwrite.type !== OverwriteType.Member) continue; + if (!overwrite.allow.has(PermissionFlagsBits.ViewChannel)) continue; + + const member = source.guild.members.cache.get(overwrite.id); + if (member && !source.permissionsFor(member)?.has(PermissionFlagsBits.ViewChannel)) return true; + } + + return false; +} + function buildStarboardMessage(message: Message, emoji: string, count: number) { const image = [...message.attachments.values()].find((attachment) => attachment.contentType?.startsWith('image/')); @@ -90,8 +115,34 @@ export async function removeStarboardEntry(messageId: string) { await prisma.starboard.deleteMany({ where: { messageId } }); } -export async function removeStarboardEntriesByChannel(channelId: string) { - await prisma.starboard.deleteMany({ where: { channelId } }); +async function purgeStarboardEntries(guild: Guild, where: Prisma.StarboardWhereInput): Promise { + const entries = await prisma.starboard.findMany({ where: { ...where, guildId: guild.id } }); + if (!entries.length) return; + + const settings = await getSettings(guild.id); + const starboard = settings.starboardChannelId + ? await getChannel(guild.client.channels, settings.starboardChannelId) + : null; + + if (starboard?.isTextBased()) { + await Promise.allSettled(entries.map((entry) => starboard.messages.delete(entry.starboardMessageId))); + } + + await prisma.starboard.deleteMany({ where: { messageId: { in: entries.map((entry) => entry.messageId) } } }); +} + +export async function clearStarboardEntries(guildId: string): Promise { + await prisma.starboard.deleteMany({ where: { guildId } }); +} + +export async function removeStarboardPostsByMessages(guild: Guild, messageIds: string[]): Promise { + if (!messageIds.length) return; + + await purgeStarboardEntries(guild, { messageId: { in: messageIds } }); +} + +export async function removeStarboardPostsByChannel(guild: Guild, channelId: string): Promise { + await purgeStarboardEntries(guild, { channelId }); } export async function syncStarboard(reaction: MessageReaction, user: User | PartialUser): Promise { @@ -108,7 +159,7 @@ export async function syncStarboard(reaction: MessageReaction, user: User | Part if (normalize(reactionEmoji(full)) !== normalize(settings.starboardEmoji)) return; const channel = await getChannel(reacted.client.channels, settings.starboardChannelId); - if (!channel?.isTextBased() || !channel.isSendable()) return; + if (!channel?.isTextBased() || channel.isDMBased() || !channel.isSendable()) return; const isPost = reacted.author.id === reacted.client.user.id && reacted.channelId === settings.starboardChannelId; const entry = isPost @@ -140,7 +191,23 @@ export async function syncStarboard(reaction: MessageReaction, user: User | Part return; } } else if (entry) { - posted = await channel.messages.fetch(entry.starboardMessageId).catch(() => null); + try { + posted = await channel.messages.fetch(entry.starboardMessageId); + } catch (err: unknown) { + if (!(err instanceof DiscordAPIError) || err.code !== RESTJSONErrorCodes.UnknownMessage) return; + + // post removed = forever removed (previously would come back) + await removeStarboardEntry(entry.messageId); + + return; + } + } + + if (appearsAcrossChannels(message.channel, channel)) { + await posted?.delete().catch(() => {}); + if (entry) await removeStarboardEntry(entry.messageId); + + return; } const emoji = settings.starboardEmoji; @@ -162,8 +229,6 @@ export async function syncStarboard(reaction: MessageReaction, user: User | Part return; } - if (entry) await removeStarboardEntry(message.id); - const sent = await channel.send(payload).catch(() => null); if (!sent) return; diff --git a/apps/bot/src/listeners/channelDelete.ts b/apps/bot/src/listeners/channelDelete.ts index 0fe43ff..74393c9 100644 --- a/apps/bot/src/listeners/channelDelete.ts +++ b/apps/bot/src/listeners/channelDelete.ts @@ -7,7 +7,7 @@ import { type APIEmbedField, AuditLogEvent, type Channel, EmbedBuilder, Events } import { removeConfessionContextsByChannel } from '#lib/confessions.js'; import { getRecentAuditLogEntry, isLoggingChannel, logEmbed } from '#lib/logging.js'; import { getSettings, updateSettings } from '#lib/settings.js'; -import { removeStarboardEntriesByChannel } from '#lib/starboard.js'; +import { clearStarboardEntries, removeStarboardPostsByChannel } from '#lib/starboard.js'; export class ChannelDeleteListener extends Listener { public constructor(context: Listener.LoaderContext, options: Listener.Options) { @@ -21,11 +21,18 @@ export class ChannelDeleteListener extends Listener if (!('guild' in channel) || !channel.guild) return; await removeConfessionContextsByChannel(channel.id).catch(() => null); - await removeStarboardEntriesByChannel(channel.id).catch(() => null); - // if the honey pot channel gets deleted, remove it from the settings const settings = await getSettings(channel.guild.id).catch(() => null); + if (settings?.starboardChannelId === channel.id) { + await clearStarboardEntries(channel.guild.id).catch(() => null); + await updateSettings(channel.guild.id, channel.guild.name, { starboardChannelId: null }).catch((err) => + console.error(err), + ); + } else { + await removeStarboardPostsByChannel(channel.guild, channel.id).catch(() => null); + } + if (settings?.honeypotChannelId === channel.id) { await updateSettings(channel.guild.id, channel.guild.name, { honeypotChannelId: null }).catch((err) => console.error(err), diff --git a/apps/bot/src/listeners/messageDelete.ts b/apps/bot/src/listeners/messageDelete.ts index c6c8faa..df6d961 100644 --- a/apps/bot/src/listeners/messageDelete.ts +++ b/apps/bot/src/listeners/messageDelete.ts @@ -7,6 +7,7 @@ import { EmbedBuilder, Events, type Message, type PartialMessage } from 'discord import { removeConfessionContext } from '#lib/confessions.js'; import { deleteGiveawayByMessageId } from '#lib/giveaways.js'; import { isLoggingChannel, logEmbed, truncate } from '#lib/logging.js'; +import { removeStarboardPostsByMessages } from '#lib/starboard.js'; export class MessageDeleteListener extends Listener { public constructor(context: Listener.LoaderContext, options: Listener.Options) { @@ -38,6 +39,7 @@ export class MessageDeleteListener extends Listener await removeConfessionContext(message.id).catch(() => null); await deleteGiveawayByMessageId(message.id).catch(() => null); + await removeStarboardPostsByMessages(guild, [message.id]).catch(() => null); await logEmbed(guild, embed); } } diff --git a/apps/bot/src/listeners/messageDeleteBulk.ts b/apps/bot/src/listeners/messageDeleteBulk.ts index ecdd0c8..c39bfb5 100644 --- a/apps/bot/src/listeners/messageDeleteBulk.ts +++ b/apps/bot/src/listeners/messageDeleteBulk.ts @@ -7,6 +7,7 @@ import { type Collection, EmbedBuilder, Events, type Message, type PartialMessag import { removeConfessionContexts } from '#lib/confessions.js'; import { deleteGiveawaysByMessageIds } from '#lib/giveaways.js'; import { isLoggingChannel, logEmbed } from '#lib/logging.js'; +import { removeStarboardPostsByMessages } from '#lib/starboard.js'; export class MessageDeleteBulkListener extends Listener { public constructor(context: Listener.LoaderContext, options: Listener.Options) { @@ -32,6 +33,7 @@ export class MessageDeleteBulkListener extends Listener { await removeConfessionContexts([...messages.keys()]).catch(() => null); await deleteGiveawaysByMessageIds([...messages.keys()]).catch(() => null); + await removeStarboardPostsByMessages(guild, [...messages.keys()]).catch(() => null); await logEmbed(guild, embed); } }