From c2196869c3f58c3b8a2a5ffaddb189018fed2f54 Mon Sep 17 00:00:00 2001 From: CarelessInternet Date: Sun, 11 Jan 2026 23:44:28 +0000 Subject: [PATCH 1/3] feat(bot): action on ticket author leaving --- .../configuration-ticket-threads/component.ts | 160 ++++- .../configuration-ticket-threads/helpers.ts | 5 + apps/bot/src/events/ThreadDelete.ts | 4 +- apps/bot/src/events/ThreadMembersUpdate.ts | 84 +++ apps/bot/src/events/ThreadUpdate.ts | 2 +- apps/bot/src/i18n/en-GB/index.ts | 7 + apps/bot/src/i18n/i18n-types.ts | 27 + apps/bot/src/i18n/sv-SE/index.ts | 7 + package.json | 2 +- .../database/migrations/0008_lame_nemesis.sql | 1 + .../migrations/meta/0008_snapshot.json | 591 ++++++++++++++++++ .../database/migrations/meta/_journal.json | 7 + packages/database/src/schema.ts | 1 + .../ThreadTicketActionsPermissionBitField.ts | 6 +- pnpm-lock.yaml | 240 +++---- 15 files changed, 983 insertions(+), 161 deletions(-) create mode 100644 apps/bot/src/events/ThreadMembersUpdate.ts create mode 100644 packages/database/migrations/0008_lame_nemesis.sql create mode 100644 packages/database/migrations/meta/0008_snapshot.json diff --git a/apps/bot/src/commands/staff/configuration-ticket-threads/component.ts b/apps/bot/src/commands/staff/configuration-ticket-threads/component.ts index cd935bf9..a459461b 100644 --- a/apps/bot/src/commands/staff/configuration-ticket-threads/component.ts +++ b/apps/bot/src/commands/staff/configuration-ticket-threads/component.ts @@ -81,6 +81,9 @@ export default class extends Component.Interaction { case 'allowed_author_actions': { return this.authorActions({ interaction }); } + case 'author_leave_action': { + return this.authorLeaveAction({ interaction }); + } case 'private': case 'notification': { return this.privateAndNotification({ interaction }); @@ -238,7 +241,7 @@ export default class extends Component.Interaction { await interaction.deferUpdate(); const [row] = await database - .select({ allowedAuthorActions: ticketThreadsCategories.allowedAuthorActions }) + .select() .from(ticketThreadsCategories) .where(and(eq(ticketThreadsCategories.id, categoryId), eq(ticketThreadsCategories.guildId, interaction.guildId))); @@ -256,10 +259,10 @@ export default class extends Component.Interaction { const authorPermissions = new ThreadTicketActionsPermissionBitField(row.allowedAuthorActions); const selectMenu = new StringSelectMenuBuilder() - .setCustomId(customId('ticket_threads_category_configuration_allowed_author_actions', categoryId)) - .setMinValues(1) + .setCustomId(customId('ticket_threads_category_configuration_allowed_author_actions', row.id)) + .setMinValues(0) .setMaxValues(Object.keys(ThreadTicketActionsPermissionBitField.Flags).length) - .setPlaceholder('Edit one of the following ticket author actions:') + .setPlaceholder('Edit the following ticket author actions:') .setOptions( new StringSelectMenuOptionBuilder() .setEmoji('📝') @@ -298,6 +301,79 @@ export default class extends Component.Interaction { return interaction.editReply({ components: [rowBuilder] }); } + private async authorLeaveAction({ interaction }: Component.Context<'string'>) { + const { dynamicValue } = extractCustomId(interaction.customId); + const { + data: categoryId, + error, + success, + } = ticketThreadsCategoriesSelectSchema.shape.id.safeParse(Number(dynamicValue)); + + if (!success) { + return interaction.reply({ + embeds: [ + userEmbedError({ client: interaction.client, description: prettifyError(error), member: interaction.member }), + ], + flags: [MessageFlags.Ephemeral], + }); + } + + await interaction.deferUpdate(); + const [row] = await database + .select() + .from(ticketThreadsCategories) + .where(and(eq(ticketThreadsCategories.id, categoryId), eq(ticketThreadsCategories.guildId, interaction.guildId))); + + if (!row) { + return interaction.editReply({ + embeds: [ + userEmbedError({ + client: interaction.client, + description: 'No category with the given ID could be found.', + member: interaction.member, + }), + ], + }); + } + + const actions = new ThreadTicketActionsPermissionBitField(row.authorLeaveAction, false); + const selectMenu = new StringSelectMenuBuilder() + .setCustomId(customId('ticket_threads_category_configuration_author_leave_action', row.id)) + .setMinValues(0) + .setMaxValues(1) + .setPlaceholder('Edit one of the following ticket author leave actions:') + .setOptions( + new StringSelectMenuOptionBuilder() + .setEmoji('🔒') + .setLabel('Lock') + .setDescription('Toggle whether the ticket will be locked.') + .setValue('lock') + .setDefault(actions.has(ThreadTicketActionsPermissionBitField.Flags.Lock)), + new StringSelectMenuOptionBuilder() + .setEmoji('🗃') + .setLabel('Close') + .setDescription('Toggle whether the ticket well be closed.') + .setValue('close') + .setDefault(actions.has(ThreadTicketActionsPermissionBitField.Flags.Close)), + new StringSelectMenuOptionBuilder() + .setEmoji('🔐') + .setLabel('Lock & Close') + .setDescription('Toggle whether the ticket will be locked and closed.') + .setValue('lock_and_close') + .setDefault(actions.has(ThreadTicketActionsPermissionBitField.Flags.LockAndClose)), + new StringSelectMenuOptionBuilder() + .setEmoji('🗑') + .setLabel('Delete') + .setDescription('Toggle whether the ticket will be deleted.') + .setValue('delete') + .setDefault(actions.has(ThreadTicketActionsPermissionBitField.Flags.Delete)), + ); + + const rowBuilder = new ActionRowBuilder().setComponents(selectMenu); + + return interaction.editReply({ components: [rowBuilder] }); + } + private async privateAndNotification({ interaction }: Component.Context<'string'>) { const { dynamicValue } = extractCustomId(interaction.customId, true); const type = interaction.values.at(0)?.includes('private') ? 'private threads' : 'thread notification'; @@ -704,7 +780,72 @@ export class AuthorActions extends Component.Interaction { interaction.values.includes(name) ? authorPermissions.set(flag) : authorPermissions.clear(flag); } - await authorPermissions.updateAuthorPermissions(row.id, row.guildId); + await authorPermissions.updateAllowedAuthorActions(row.id, row.guildId); + + interaction.editReply({ + components: [], + embeds: [ + userEmbed(interaction) + .setTitle('Updated the Thread Ticket Category') + .setDescription( + `${interaction.member} has edited the allowed author actions to: ${ + interaction.values.length > 0 + ? ThreadTicketing.actionsBitfieldToNames(authorPermissions.permissions) + .map((name) => inlineCode(name)) + .join(', ') + : 'None' + }.`, + ), + ], + }); + return interaction.followUp({ components: configurationMenu(categoryId), embeds: interaction.message.embeds }); + } +} + +export class AuthorLeaveAction extends Component.Interaction { + public readonly customIds = [dynamicCustomId('ticket_threads_category_configuration_author_leave_action')]; + + @DeferUpdate + @HasGlobalConfiguration + public async execute({ interaction }: Component.Context<'string'>) { + const { dynamicValue } = extractCustomId(interaction.customId, true); + const { + data: categoryId, + error, + success, + } = ticketThreadsCategoriesSelectSchema.shape.id.safeParse(Number(dynamicValue)); + + if (!success) { + return interaction.editReply({ + components: [], + embeds: [ + userEmbedError({ client: interaction.client, description: prettifyError(error), member: interaction.member }), + ], + }); + } + + const [row] = await database + .select() + .from(ticketThreadsCategories) + .where(and(eq(ticketThreadsCategories.id, categoryId), eq(ticketThreadsCategories.guildId, interaction.guildId))); + + if (!row) { + return interaction.editReply({ + embeds: [ + userEmbedError({ + client: interaction.client, + description: 'No category with the given ID could be found.', + member: interaction.member, + }), + ], + }); + } + + const bit = ThreadTicketing.actionsAsKeyAndFlagsMap.get(interaction.values.at(0) as ThreadTicketing.KeyOfActions); + await database + .update(ticketThreadsCategories) + .set({ authorLeaveAction: bit }) + .where(and(eq(ticketThreadsCategories.id, categoryId), eq(ticketThreadsCategories.guildId, interaction.guildId))); interaction.editReply({ components: [], @@ -712,11 +853,10 @@ export class AuthorActions extends Component.Interaction { userEmbed(interaction) .setTitle('Updated the Thread Ticket Category') .setDescription( - `${interaction.member} has edited the allowed author actions to: ${ThreadTicketing.actionsBitfieldToNames( - authorPermissions.permissions, - ) - .map((name) => inlineCode(name)) - .join(', ')}.`, + `${interaction.member} has edited the author leave action to: ${ + // biome-ignore lint/style/noNonNullAssertion: It should exist. + bit ? inlineCode(ThreadTicketing.actionsBitfieldToNames(bit).at(0)!) : 'None' + }.`, ), ], }); diff --git a/apps/bot/src/commands/staff/configuration-ticket-threads/helpers.ts b/apps/bot/src/commands/staff/configuration-ticket-threads/helpers.ts index c55dbccb..ce95276f 100644 --- a/apps/bot/src/commands/staff/configuration-ticket-threads/helpers.ts +++ b/apps/bot/src/commands/staff/configuration-ticket-threads/helpers.ts @@ -250,6 +250,11 @@ export function configurationMenu(categoryId: number) { .setLabel('Allowed Author Actions') .setDescription('Change what actions the ticket author can use.') .setValue('allowed_author_actions'), + new StringSelectMenuOptionBuilder() + .setEmoji('👋') + .setLabel('Author Leave Action') + .setDescription('Change the action to perform when the ticket author leaves the thread.') + .setValue('author_leave_action'), new StringSelectMenuOptionBuilder() .setEmoji('🛃') .setLabel('Private Thread') diff --git a/apps/bot/src/events/ThreadDelete.ts b/apps/bot/src/events/ThreadDelete.ts index 472d1daa..cd0e7d7e 100644 --- a/apps/bot/src/events/ThreadDelete.ts +++ b/apps/bot/src/events/ThreadDelete.ts @@ -7,9 +7,7 @@ export default class extends Event.Handler { @LogExceptions public execute([thread]: Event.ArgumentsOf) { - const threadIsByBot = thread.ownerId === thread.client.user.id; - - if (threadIsByBot) { + if (thread.client.user.id) { void database .delete(ticketsThreads) .where(eq(ticketsThreads.threadId, thread.id)) diff --git a/apps/bot/src/events/ThreadMembersUpdate.ts b/apps/bot/src/events/ThreadMembersUpdate.ts new file mode 100644 index 00000000..54a298b7 --- /dev/null +++ b/apps/bot/src/events/ThreadMembersUpdate.ts @@ -0,0 +1,84 @@ +import { + database, + eq, + ThreadTicketActionsPermissionBitField, + ticketsThreads, + ticketThreadsCategories, +} from '@ticketer/database'; +import { Event, embed, userEmbed } from '@ticketer/djs-framework'; +import { ChannelType, Colors, inlineCode, PermissionFlagsBits, userMention } from 'discord.js'; +import { translate } from '@/i18n'; +import { fetchChannel, LogExceptions } from '@/utils'; + +export default class extends Event.Handler { + public readonly name = Event.Name.ThreadMembersUpdate; + + @LogExceptions + public async execute([_, removedMembers, thread]: Event.ArgumentsOf) { + if (thread.type !== ChannelType.PublicThread && thread.type !== ChannelType.PrivateThread) return; + + const author = removedMembers.at(0); + if (!author) return; + + const [row] = await database + .select({ + authorId: ticketsThreads.authorId, + state: ticketsThreads.state, + authorLeaveAction: ticketThreadsCategories.authorLeaveAction, + logsChannelId: ticketThreadsCategories.logsChannelId, + }) + .from(ticketsThreads) + .where(eq(ticketsThreads.threadId, thread.id)) + .innerJoin(ticketThreadsCategories, eq(ticketsThreads.categoryId, ticketThreadsCategories.id)); + + if (row?.authorId !== author.id || row.state !== 'active') return; + + const logEmbed = author.guildMember + ? userEmbed({ client: thread.client, member: author.guildMember }) + : embed({ client: thread.client }); + let state: 'lock' | 'close' | 'lockAndClose' | 'delete' = 'lock'; + + if (row.authorLeaveAction === ThreadTicketActionsPermissionBitField.Flags.Lock) { + if (!thread.manageable) return; + await thread.setLocked(true); + logEmbed.setColor(Colors.DarkVividPink); + } else if (row.authorLeaveAction === ThreadTicketActionsPermissionBitField.Flags.Close) { + if (!thread.editable) return; + await thread.setArchived(true); + logEmbed.setColor(Colors.Yellow); + state = 'close'; + } else if (row.authorLeaveAction === ThreadTicketActionsPermissionBitField.Flags.LockAndClose) { + if (!thread.manageable || !thread.editable) return; + await thread.edit({ archived: true, locked: true }); + logEmbed.setColor(Colors.DarkVividPink); + state = 'lockAndClose'; + } else if (row.authorLeaveAction === ThreadTicketActionsPermissionBitField.Flags.Delete) { + if (!thread.manageable) return; + await thread.delete(); + logEmbed.setColor(Colors.Red); + state = 'delete'; + } + + if (row.logsChannelId) { + const me = await thread.guild.members.fetchMe(); + const logsChannel = await fetchChannel(thread.guild, row.logsChannelId); + + if (!logsChannel?.isTextBased()) return; + if (!logsChannel.permissionsFor(me).has([PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages])) + return; + + const translations = translate(thread.guild.preferredLocale).events.threadMembersUpdate.logs; + void logsChannel.send({ + embeds: [ + logEmbed.setTitle(translations.title()).setDescription( + translations.description({ + member: userMention(author.id), + state, + thread: state === 'delete' ? `${inlineCode(thread.name)} (${thread.id})` : thread.toString(), + }), + ), + ], + }); + } + } +} diff --git a/apps/bot/src/events/ThreadUpdate.ts b/apps/bot/src/events/ThreadUpdate.ts index bd5f9fbe..403b3f37 100644 --- a/apps/bot/src/events/ThreadUpdate.ts +++ b/apps/bot/src/events/ThreadUpdate.ts @@ -21,7 +21,7 @@ export default class extends Event.Handler { exists(database.select().from(ticketsThreads).where(eq(ticketsThreads.threadId, newThread.id)).limit(1)), ); - // Two events are sent when the thread is archived and the locked button is pressed. We pray for no race conditions 🙏. + // Two events are sent when the thread is archived but the locked button is pressed. We pray for no race conditions 🙏. if (lockedAndArchived) { return database.update(ticketsThreads).set({ state: 'lockedAndArchived' }).where(whereCondition); } else if (unarchived || unlocked) { diff --git a/apps/bot/src/i18n/en-GB/index.ts b/apps/bot/src/i18n/en-GB/index.ts index 0e80e34e..92a42490 100644 --- a/apps/bot/src/i18n/en-GB/index.ts +++ b/apps/bot/src/i18n/en-GB/index.ts @@ -554,6 +554,13 @@ const en_GB = { message: '{member:string} has left the server.', }, }, + threadMembersUpdate: { + logs: { + title: 'Ticket Author Left', + description: + '{member:string} left the thread at {thread:string} and therefore the ticket has been {state|{lock: locked, close: closed, lockAndClose: locked and closed, delete: deleted}}.', + }, + }, }, miscellaneous: { paginationButtons: { diff --git a/apps/bot/src/i18n/i18n-types.ts b/apps/bot/src/i18n/i18n-types.ts index 78798d28..dbbd852e 100644 --- a/apps/bot/src/i18n/i18n-types.ts +++ b/apps/bot/src/i18n/i18n-types.ts @@ -1163,6 +1163,21 @@ type RootTranslation = { message: RequiredParams<'member'> } } + threadMembersUpdate: { + logs: { + /** + * T​i​c​k​e​t​ ​A​u​t​h​o​r​ ​L​e​f​t + */ + title: string + /** + * {​m​e​m​b​e​r​}​ ​l​e​f​t​ ​t​h​e​ ​t​h​r​e​a​d​ ​a​t​ ​{​t​h​r​e​a​d​}​ ​a​n​d​ ​t​h​e​r​e​f​o​r​e​ ​t​h​e​ ​t​i​c​k​e​t​ ​h​a​s​ ​b​e​e​n​ ​{​s​t​a​t​e​|​{​l​o​c​k​:​ ​l​o​c​k​e​d​,​ ​c​l​o​s​e​:​ ​c​l​o​s​e​d​,​ ​l​o​c​k​A​n​d​C​l​o​s​e​:​ ​l​o​c​k​e​d​ ​a​n​d​ ​c​l​o​s​e​d​,​ ​d​e​l​e​t​e​:​ ​d​e​l​e​t​e​d​}​}​. + * @param {string} member + * @param {'lock' | 'close' | 'lockAndClose' | 'delete'} state + * @param {string} thread + */ + description: RequiredParams<'member' | `state|{lock:${string}, close:${string}, lockAndClose:${string}, delete:${string}}` | 'thread'> + } + } } miscellaneous: { paginationButtons: { @@ -3246,6 +3261,18 @@ export type TranslationFunctions = { message: (arg: { member: string }) => LocalizedString } } + threadMembersUpdate: { + logs: { + /** + * Ticket Author Left + */ + title: () => LocalizedString + /** + * {member} left the thread at {thread} and therefore the ticket has been {state|{lock: locked, close: closed, lockAndClose: locked and closed, delete: deleted}}. + */ + description: (arg: { member: string, state: 'lock' | 'close' | 'lockAndClose' | 'delete', thread: string }) => LocalizedString + } + } } miscellaneous: { paginationButtons: { diff --git a/apps/bot/src/i18n/sv-SE/index.ts b/apps/bot/src/i18n/sv-SE/index.ts index 24a98712..695fb38c 100644 --- a/apps/bot/src/i18n/sv-SE/index.ts +++ b/apps/bot/src/i18n/sv-SE/index.ts @@ -554,6 +554,13 @@ const sv_SE = { message: '{member} har lämnat servern.', }, }, + threadMembersUpdate: { + logs: { + title: 'Stödbiljettsägare lämnade', + description: + '{member} lämnade tråden vid {thread} och därför har stödbiljetten {state|{lock: låsts, close: stängts, lockAndClose: låsts och stängts, delete: raderats}}.', + }, + }, }, miscellaneous: { paginationButtons: { diff --git a/package.json b/package.json index ae498297..80891078 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@biomejs/biome": "^2.3.11", - "turbo": "^2.7.3" + "turbo": "^2.7.4" }, "packageManager": "pnpm@10.28.0+sha512.05df71d1421f21399e053fde567cea34d446fa02c76571441bfc1c7956e98e363088982d940465fd34480d4d90a0668bc12362f8aa88000a64e83d0b0e47be48" } diff --git a/packages/database/migrations/0008_lame_nemesis.sql b/packages/database/migrations/0008_lame_nemesis.sql new file mode 100644 index 00000000..4d1fbad3 --- /dev/null +++ b/packages/database/migrations/0008_lame_nemesis.sql @@ -0,0 +1 @@ +ALTER TABLE `ticketThreadsCategories` ADD `authorLeaveAction` int unsigned; \ No newline at end of file diff --git a/packages/database/migrations/meta/0008_snapshot.json b/packages/database/migrations/meta/0008_snapshot.json new file mode 100644 index 00000000..19780d39 --- /dev/null +++ b/packages/database/migrations/meta/0008_snapshot.json @@ -0,0 +1,591 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "b7efcf48-6e4c-41c5-8c49-0081dac6e7b2", + "prevId": "0791c300-37d8-427b-abe9-ebb46cf85598", + "tables": { + "automaticThreadsConfigurations": { + "name": "automaticThreadsConfigurations", + "columns": { + "channelId": { + "name": "channelId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guildId": { + "name": "guildId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "managers": { + "name": "managers", + "type": "json", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('[]')" + }, + "openingMessageTitle": { + "name": "openingMessageTitle", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "openingMessageDescription": { + "name": "openingMessageDescription", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "guildId_index": { + "name": "guildId_index", + "columns": [ + "guildId" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "automaticThreadsConfigurations_channelId": { + "name": "automaticThreadsConfigurations_channelId", + "columns": [ + "channelId" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "guildBlacklists": { + "name": "guildBlacklists", + "columns": { + "guildId": { + "name": "guildId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reason": { + "name": "reason", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "datetime", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": { + "timestamp_index": { + "name": "timestamp_index", + "columns": [ + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "guildBlacklists_guildId": { + "name": "guildBlacklists_guildId", + "columns": [ + "guildId" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ticketThreadsCategories": { + "name": "ticketThreadsCategories", + "columns": { + "id": { + "name": "id", + "type": "int unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "guildId": { + "name": "guildId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "allowedAuthorActions": { + "name": "allowedAuthorActions", + "type": "int unsigned", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "authorLeaveAction": { + "name": "authorLeaveAction", + "type": "int unsigned", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "categoryEmoji": { + "name": "categoryEmoji", + "type": "varchar(21)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "categoryTitle": { + "name": "categoryTitle", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "categoryDescription": { + "name": "categoryDescription", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channelId": { + "name": "channelId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "logsChannelId": { + "name": "logsChannelId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "managers": { + "name": "managers", + "type": "json", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('[]')" + }, + "openingMessageTitle": { + "name": "openingMessageTitle", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "openingMessageDescription": { + "name": "openingMessageDescription", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "privateThreads": { + "name": "privateThreads", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "silentPings": { + "name": "silentPings", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "skipModal": { + "name": "skipModal", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "threadNotifications": { + "name": "threadNotifications", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "threadTitle": { + "name": "threadTitle", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "titleAndDescriptionRequired": { + "name": "titleAndDescriptionRequired", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": { + "guildId_index": { + "name": "guildId_index", + "columns": [ + "guildId" + ], + "isUnique": false + } + }, + "foreignKeys": { + "ticket_threads_categories_fk": { + "name": "ticket_threads_categories_fk", + "tableFrom": "ticketThreadsCategories", + "tableTo": "ticketThreadsConfigurations", + "columnsFrom": [ + "guildId" + ], + "columnsTo": [ + "guildId" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ticketThreadsCategories_id": { + "name": "ticketThreadsCategories_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ticketThreadsConfigurations": { + "name": "ticketThreadsConfigurations", + "columns": { + "guildId": { + "name": "guildId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "activeTickets": { + "name": "activeTickets", + "type": "tinyint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "ticketThreadsConfigurations_guildId": { + "name": "ticketThreadsConfigurations_guildId", + "columns": [ + "guildId" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "ticketsThreads": { + "name": "ticketsThreads", + "columns": { + "threadId": { + "name": "threadId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "authorId": { + "name": "authorId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "categoryId": { + "name": "categoryId", + "type": "int unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guildId": { + "name": "guildId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "state": { + "name": "state", + "type": "enum('active','archived','locked','lockedAndArchived')", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'active'" + } + }, + "indexes": { + "authorId_index": { + "name": "authorId_index", + "columns": [ + "authorId" + ], + "isUnique": false + }, + "categoryId_index": { + "name": "categoryId_index", + "columns": [ + "categoryId" + ], + "isUnique": false + }, + "guildId_index": { + "name": "guildId_index", + "columns": [ + "guildId" + ], + "isUnique": false + }, + "state_index": { + "name": "state_index", + "columns": [ + "state" + ], + "isUnique": false + } + }, + "foreignKeys": { + "tickets_threads_fk": { + "name": "tickets_threads_fk", + "tableFrom": "ticketsThreads", + "tableTo": "ticketThreadsCategories", + "columnsFrom": [ + "guildId", + "categoryId" + ], + "columnsTo": [ + "guildId", + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "ticketsThreads_threadId": { + "name": "ticketsThreads_threadId", + "columns": [ + "threadId" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "userForumsConfigurations": { + "name": "userForumsConfigurations", + "columns": { + "channelId": { + "name": "channelId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "guildId": { + "name": "guildId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "managers": { + "name": "managers", + "type": "json", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('[]')" + }, + "openingMessageTitle": { + "name": "openingMessageTitle", + "type": "varchar(100)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "openingMessageDescription": { + "name": "openingMessageDescription", + "type": "varchar(500)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "guildId_index": { + "name": "guildId_index", + "columns": [ + "guildId" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "userForumsConfigurations_channelId": { + "name": "userForumsConfigurations_channelId", + "columns": [ + "channelId" + ] + } + }, + "uniqueConstraints": {}, + "checkConstraint": {} + }, + "welcomeAndFarewell": { + "name": "welcomeAndFarewell", + "columns": { + "guildId": { + "name": "guildId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "welcomeChannelId": { + "name": "welcomeChannelId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "welcomeMessageTitle": { + "name": "welcomeMessageTitle", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "welcomeMessageDescription": { + "name": "welcomeMessageDescription", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "welcomeNewMemberRoles": { + "name": "welcomeNewMemberRoles", + "type": "json", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "('[]')" + }, + "welcomeEnabled": { + "name": "welcomeEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "farewellChannelId": { + "name": "farewellChannelId", + "type": "bigint unsigned", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "farewellMessageTitle": { + "name": "farewellMessageTitle", + "type": "varchar(100)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "farewellMessageDescription": { + "name": "farewellMessageDescription", + "type": "varchar(500)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "farewellEnabled": { + "name": "farewellEnabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "welcomeAndFarewell_guildId": { + "name": "welcomeAndFarewell_guildId", + "columns": [ + "guildId" + ] + } + }, + "uniqueConstraints": { + "welcomeAndFarewell_welcomeChannelId_unique": { + "name": "welcomeAndFarewell_welcomeChannelId_unique", + "columns": [ + "welcomeChannelId" + ] + }, + "welcomeAndFarewell_farewellChannelId_unique": { + "name": "welcomeAndFarewell_farewellChannelId_unique", + "columns": [ + "farewellChannelId" + ] + } + }, + "checkConstraint": {} + } + }, + "views": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "tables": {}, + "indexes": {} + } +} \ No newline at end of file diff --git a/packages/database/migrations/meta/_journal.json b/packages/database/migrations/meta/_journal.json index 781b7156..e0ec359e 100644 --- a/packages/database/migrations/meta/_journal.json +++ b/packages/database/migrations/meta/_journal.json @@ -57,6 +57,13 @@ "when": 1767380338577, "tag": "0007_nappy_green_goblin", "breakpoints": true + }, + { + "idx": 8, + "version": "5", + "when": 1768163263334, + "tag": "0008_lame_nemesis", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/database/src/schema.ts b/packages/database/src/schema.ts index 0bb2960e..ae663fa4 100644 --- a/packages/database/src/schema.ts +++ b/packages/database/src/schema.ts @@ -64,6 +64,7 @@ export const ticketThreadsCategories = mysqlTable( id: int('id', { unsigned: true }).autoincrement().primaryKey(), guildId: snowflake('guildId').notNull(), allowedAuthorActions: int('allowedAuthorActions', { unsigned: true }), + authorLeaveAction: int('authorLeaveAction', { unsigned: true }), // This is either a Unicode emoji or a Discord emoji (snowflake). categoryEmoji: varchar('categoryEmoji', { length: 21 }), categoryTitle: varchar('categoryTitle', { length: 100 }).notNull(), diff --git a/packages/database/src/utility/ThreadTicketActionsPermissionBitField.ts b/packages/database/src/utility/ThreadTicketActionsPermissionBitField.ts index 942071bc..ef2b7eca 100644 --- a/packages/database/src/utility/ThreadTicketActionsPermissionBitField.ts +++ b/packages/database/src/utility/ThreadTicketActionsPermissionBitField.ts @@ -19,8 +19,8 @@ export class ThreadTicketActionsPermissionBitField { private bitfield: number; - public constructor(bitfield?: typeof ThreadTicketActionsPermissionBitField.Default | null) { - this.bitfield = bitfield ?? ThreadTicketActionsPermissionBitField.Default; + public constructor(bitfield?: typeof ThreadTicketActionsPermissionBitField.Default | null, defaultToAll = true) { + this.bitfield = bitfield ?? (defaultToAll ? ThreadTicketActionsPermissionBitField.Default : 0); } public get permissions() { @@ -46,7 +46,7 @@ export class ThreadTicketActionsPermissionBitField { return this.has(bit); } - public updateAuthorPermissions( + public updateAllowedAuthorActions( categoryId: typeof ticketThreadsCategories.$inferSelect.id, guildId: DiscordSnowflake, ) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5b54141..f8a70afb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^2.3.11 version: 2.3.11 turbo: - specifier: ^2.7.3 - version: 2.7.3 + specifier: ^2.7.4 + version: 2.7.4 apps/bot: dependencies: @@ -999,86 +999,86 @@ packages: cpu: [x64] os: [win32] - '@parcel/watcher-android-arm64@2.5.1': - resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + '@parcel/watcher-android-arm64@2.5.4': + resolution: {integrity: sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [android] - '@parcel/watcher-darwin-arm64@2.5.1': - resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + '@parcel/watcher-darwin-arm64@2.5.4': + resolution: {integrity: sha512-kphKy377pZiWpAOyTgQYPE5/XEKVMaj6VUjKT5VkNyUJlr2qZAn8gIc7CPzx+kbhvqHDT9d7EqdOqRXT6vk0zw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [darwin] - '@parcel/watcher-darwin-x64@2.5.1': - resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + '@parcel/watcher-darwin-x64@2.5.4': + resolution: {integrity: sha512-UKaQFhCtNJW1A9YyVz3Ju7ydf6QgrpNQfRZ35wNKUhTQ3dxJ/3MULXN5JN/0Z80V/KUBDGa3RZaKq1EQT2a2gg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [darwin] - '@parcel/watcher-freebsd-x64@2.5.1': - resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + '@parcel/watcher-freebsd-x64@2.5.4': + resolution: {integrity: sha512-Dib0Wv3Ow/m2/ttvLdeI2DBXloO7t3Z0oCp4bAb2aqyqOjKPPGrg10pMJJAQ7tt8P4V2rwYwywkDhUia/FgS+Q==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [freebsd] - '@parcel/watcher-linux-arm-glibc@2.5.1': - resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + '@parcel/watcher-linux-arm-glibc@2.5.4': + resolution: {integrity: sha512-I5Vb769pdf7Q7Sf4KNy8Pogl/URRCKu9ImMmnVKYayhynuyGYMzuI4UOWnegQNa2sGpsPSbzDsqbHNMyeyPCgw==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - '@parcel/watcher-linux-arm-musl@2.5.1': - resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + '@parcel/watcher-linux-arm-musl@2.5.4': + resolution: {integrity: sha512-kGO8RPvVrcAotV4QcWh8kZuHr9bXi9a3bSZw7kFarYR0+fGliU7hd/zevhjw8fnvIKG3J9EO5G6sXNGCSNMYPQ==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] - '@parcel/watcher-linux-arm64-glibc@2.5.1': - resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + '@parcel/watcher-linux-arm64-glibc@2.5.4': + resolution: {integrity: sha512-KU75aooXhqGFY2W5/p8DYYHt4hrjHZod8AhcGAmhzPn/etTa+lYCDB2b1sJy3sWJ8ahFVTdy+EbqSBvMx3iFlw==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - '@parcel/watcher-linux-arm64-musl@2.5.1': - resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + '@parcel/watcher-linux-arm64-musl@2.5.4': + resolution: {integrity: sha512-Qx8uNiIekVutnzbVdrgSanM+cbpDD3boB1f8vMtnuG5Zau4/bdDbXyKwIn0ToqFhIuob73bcxV9NwRm04/hzHQ==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] - '@parcel/watcher-linux-x64-glibc@2.5.1': - resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + '@parcel/watcher-linux-x64-glibc@2.5.4': + resolution: {integrity: sha512-UYBQvhYmgAv61LNUn24qGQdjtycFBKSK3EXr72DbJqX9aaLbtCOO8+1SkKhD/GNiJ97ExgcHBrukcYhVjrnogA==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - '@parcel/watcher-linux-x64-musl@2.5.1': - resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + '@parcel/watcher-linux-x64-musl@2.5.4': + resolution: {integrity: sha512-YoRWCVgxv8akZrMhdyVi6/TyoeeMkQ0PGGOf2E4omODrvd1wxniXP+DBynKoHryStks7l+fDAMUBRzqNHrVOpg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] - '@parcel/watcher-win32-arm64@2.5.1': - resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + '@parcel/watcher-win32-arm64@2.5.4': + resolution: {integrity: sha512-iby+D/YNXWkiQNYcIhg8P5hSjzXEHaQrk2SLrWOUD7VeC4Ohu0WQvmV+HDJokZVJ2UjJ4AGXW3bx7Lls9Ln4TQ==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [win32] - '@parcel/watcher-win32-ia32@2.5.1': - resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + '@parcel/watcher-win32-ia32@2.5.4': + resolution: {integrity: sha512-vQN+KIReG0a2ZDpVv8cgddlf67J8hk1WfZMMP7sMeZmJRSmEax5xNDNWKdgqSe2brOKTQQAs3aCCUal2qBHAyg==} engines: {node: '>= 10.0.0'} cpu: [ia32] os: [win32] - '@parcel/watcher-win32-x64@2.5.1': - resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + '@parcel/watcher-win32-x64@2.5.4': + resolution: {integrity: sha512-3A6efb6BOKwyw7yk9ro2vus2YTt2nvcd56AuzxdMiVOxL9umDyN5PKkKfZ/gZ9row41SjVmTVQNWQhaRRGpOKw==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [win32] - '@parcel/watcher@2.5.1': - resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + '@parcel/watcher@2.5.4': + resolution: {integrity: sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ==} engines: {node: '>= 10.0.0'} '@radix-ui/primitive@1.1.3': @@ -1714,10 +1714,6 @@ packages: resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==} hasBin: true - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - bright@1.0.0: resolution: {integrity: sha512-AX93OXFCdQQrnCmyZ85gazHhKBfKcTLnj7Hx3DM3vJwuU7VONDlB58vh3hJZ9rKWLTULbScAmuapgNgzK4u4fQ==} peerDependencies: @@ -1731,8 +1727,8 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - caniuse-lite@1.0.30001763: - resolution: {integrity: sha512-mh/dGtq56uN98LlNX9qdbKnzINhX0QzhiWBFEkFfsFO4QyCvL8YegrJAazCwXIeqkIob8BlZPGM3xdnY+sgmvQ==} + caniuse-lite@1.0.30001764: + resolution: {integrity: sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==} chalk@5.6.2: resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} @@ -1774,11 +1770,6 @@ packages: resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==} engines: {node: '>=0.10'} - detect-libc@1.0.3: - resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} - engines: {node: '>=0.10'} - hasBin: true - detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -1929,10 +1920,6 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} @@ -1969,10 +1956,6 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - is-property@1.0.2: resolution: {integrity: sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==} @@ -2074,10 +2057,6 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2147,9 +2126,9 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} po-parser@2.1.1: resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==} @@ -2275,10 +2254,6 @@ packages: tm-grammars@1.27.1: resolution: {integrity: sha512-6FeoP1GdQvvy0BASbwcDGdJiHFzxMAxSeNXZOV4NKbOUai0YUh/nluDh4rIH+LVGwEw6JFXNYXBmgPn2CVU03g==} - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - ts-mixer@6.0.4: resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==} @@ -2290,38 +2265,38 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - turbo-darwin-64@2.7.3: - resolution: {integrity: sha512-aZHhvRiRHXbJw1EcEAq4aws1hsVVUZ9DPuSFaq9VVFAKCup7niIEwc22glxb7240yYEr1vLafdQ2U294Vcwz+w==} + turbo-darwin-64@2.7.4: + resolution: {integrity: sha512-xDR30ltfkSsRfGzABBckvl1nz1cZ3ssTujvdj+TPwOweeDRvZ0e06t5DS0rmRBvyKpgGs42K/EK6Mn2qLlFY9A==} cpu: [x64] os: [darwin] - turbo-darwin-arm64@2.7.3: - resolution: {integrity: sha512-CkVrHSq+Bnhl9sX2LQgqQYVfLTWC2gvI74C4758OmU0djfrssDKU9d4YQF0AYXXhIIRZipSXfxClQziIMD+EAg==} + turbo-darwin-arm64@2.7.4: + resolution: {integrity: sha512-P7sjqXtOL/+nYWPvcDGWhi8wf8M8mZHHB8XEzw2VX7VJrS8IGHyJHGD1AYfDvhAEcr7pnk3gGifz3/xyhI655w==} cpu: [arm64] os: [darwin] - turbo-linux-64@2.7.3: - resolution: {integrity: sha512-GqDsCNnzzr89kMaLGpRALyigUklzgxIrSy2pHZVXyifgczvYPnLglex78Aj3T2gu+T3trPPH2iJ+pWucVOCC2Q==} + turbo-linux-64@2.7.4: + resolution: {integrity: sha512-GofFOxRO/IhG8BcPyMSSB3Y2+oKQotsaYbHxL9yD6JPb20/o35eo+zUSyazOtilAwDHnak5dorAJFoFU8MIg2A==} cpu: [x64] os: [linux] - turbo-linux-arm64@2.7.3: - resolution: {integrity: sha512-NdCDTfIcIo3dWjsiaAHlxu5gW61Ed/8maah1IAF/9E3EtX0aAHNiBMbuYLZaR4vRJ7BeVkYB6xKWRtdFLZ0y3g==} + turbo-linux-arm64@2.7.4: + resolution: {integrity: sha512-+RQKgNjksVPxYAyAgmDV7w/1qj++qca+nSNTAOKGOfJiDtSvRKoci89oftJ6anGs00uamLKVEQ712TI/tfNAIw==} cpu: [arm64] os: [linux] - turbo-windows-64@2.7.3: - resolution: {integrity: sha512-7bVvO987daXGSJVYBoG8R4Q+csT1pKIgLJYZevXRQ0Hqw0Vv4mKme/TOjYXs9Qb1xMKh51Tb3bXKDbd8/4G08g==} + turbo-windows-64@2.7.4: + resolution: {integrity: sha512-rfak1+g+ON3czs1mDYsCS4X74ZmK6gOgRQTXjDICtzvR4o61paqtgAYtNPofcVsMWeF4wvCajSeoAkkeAnQ1kg==} cpu: [x64] os: [win32] - turbo-windows-arm64@2.7.3: - resolution: {integrity: sha512-nTodweTbPmkvwMu/a55XvjMsPtuyUSC+sV7f/SR57K36rB2I0YG21qNETN+00LOTUW9B3omd8XkiXJkt4kx/cw==} + turbo-windows-arm64@2.7.4: + resolution: {integrity: sha512-1ZgBNjNRbDu/fPeqXuX9i26x3CJ/Y1gcwUpQ+Vp7kN9Un6RZ9kzs164f/knrjcu5E+szCRexVjRSJay1k5jApA==} cpu: [arm64] os: [win32] - turbo@2.7.3: - resolution: {integrity: sha512-+HjKlP4OfYk+qzvWNETA3cUO5UuK6b5MSc2UJOKyvBceKucQoQGb2g7HlC2H1GHdkfKrk4YF1VPvROkhVZDDLQ==} + turbo@2.7.4: + resolution: {integrity: sha512-bkO4AddmDishzJB2ze7aYYPaejMoJVfS0XnaR6RCdXFOY8JGJfQE+l9fKiV7uDPa5Ut44gmOWJL3894CIMeH9g==} hasBin: true tw-animate-css@1.4.0: @@ -2925,65 +2900,65 @@ snapshots: '@next/swc-win32-x64-msvc@16.1.1': optional: true - '@parcel/watcher-android-arm64@2.5.1': + '@parcel/watcher-android-arm64@2.5.4': optional: true - '@parcel/watcher-darwin-arm64@2.5.1': + '@parcel/watcher-darwin-arm64@2.5.4': optional: true - '@parcel/watcher-darwin-x64@2.5.1': + '@parcel/watcher-darwin-x64@2.5.4': optional: true - '@parcel/watcher-freebsd-x64@2.5.1': + '@parcel/watcher-freebsd-x64@2.5.4': optional: true - '@parcel/watcher-linux-arm-glibc@2.5.1': + '@parcel/watcher-linux-arm-glibc@2.5.4': optional: true - '@parcel/watcher-linux-arm-musl@2.5.1': + '@parcel/watcher-linux-arm-musl@2.5.4': optional: true - '@parcel/watcher-linux-arm64-glibc@2.5.1': + '@parcel/watcher-linux-arm64-glibc@2.5.4': optional: true - '@parcel/watcher-linux-arm64-musl@2.5.1': + '@parcel/watcher-linux-arm64-musl@2.5.4': optional: true - '@parcel/watcher-linux-x64-glibc@2.5.1': + '@parcel/watcher-linux-x64-glibc@2.5.4': optional: true - '@parcel/watcher-linux-x64-musl@2.5.1': + '@parcel/watcher-linux-x64-musl@2.5.4': optional: true - '@parcel/watcher-win32-arm64@2.5.1': + '@parcel/watcher-win32-arm64@2.5.4': optional: true - '@parcel/watcher-win32-ia32@2.5.1': + '@parcel/watcher-win32-ia32@2.5.4': optional: true - '@parcel/watcher-win32-x64@2.5.1': + '@parcel/watcher-win32-x64@2.5.4': optional: true - '@parcel/watcher@2.5.1': + '@parcel/watcher@2.5.4': dependencies: - detect-libc: 1.0.3 + detect-libc: 2.1.2 is-glob: 4.0.3 - micromatch: 4.0.8 node-addon-api: 7.1.1 + picomatch: 4.0.3 optionalDependencies: - '@parcel/watcher-android-arm64': 2.5.1 - '@parcel/watcher-darwin-arm64': 2.5.1 - '@parcel/watcher-darwin-x64': 2.5.1 - '@parcel/watcher-freebsd-x64': 2.5.1 - '@parcel/watcher-linux-arm-glibc': 2.5.1 - '@parcel/watcher-linux-arm-musl': 2.5.1 - '@parcel/watcher-linux-arm64-glibc': 2.5.1 - '@parcel/watcher-linux-arm64-musl': 2.5.1 - '@parcel/watcher-linux-x64-glibc': 2.5.1 - '@parcel/watcher-linux-x64-musl': 2.5.1 - '@parcel/watcher-win32-arm64': 2.5.1 - '@parcel/watcher-win32-ia32': 2.5.1 - '@parcel/watcher-win32-x64': 2.5.1 + '@parcel/watcher-android-arm64': 2.5.4 + '@parcel/watcher-darwin-arm64': 2.5.4 + '@parcel/watcher-darwin-x64': 2.5.4 + '@parcel/watcher-freebsd-x64': 2.5.4 + '@parcel/watcher-linux-arm-glibc': 2.5.4 + '@parcel/watcher-linux-arm-musl': 2.5.4 + '@parcel/watcher-linux-arm64-glibc': 2.5.4 + '@parcel/watcher-linux-arm64-musl': 2.5.4 + '@parcel/watcher-linux-x64-glibc': 2.5.4 + '@parcel/watcher-linux-x64-musl': 2.5.4 + '@parcel/watcher-win32-arm64': 2.5.4 + '@parcel/watcher-win32-ia32': 2.5.4 + '@parcel/watcher-win32-x64': 2.5.4 '@radix-ui/primitive@1.1.3': {} @@ -3539,7 +3514,7 @@ snapshots: autoprefixer@10.4.23(postcss@8.5.6): dependencies: browserslist: 4.28.1 - caniuse-lite: 1.0.30001763 + caniuse-lite: 1.0.30001764 fraction.js: 5.3.4 picocolors: 1.1.1 postcss: 8.5.6 @@ -3553,10 +3528,6 @@ snapshots: baseline-browser-mapping@2.9.14: {} - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - bright@1.0.0(react@19.2.3): dependencies: '@code-hike/lighter': 1.0.3 @@ -3566,14 +3537,14 @@ snapshots: browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.9.14 - caniuse-lite: 1.0.30001763 + caniuse-lite: 1.0.30001764 electron-to-chromium: 1.5.267 node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) buffer-from@1.1.2: {} - caniuse-lite@1.0.30001763: {} + caniuse-lite@1.0.30001764: {} chalk@5.6.2: {} @@ -3599,8 +3570,6 @@ snapshots: denque@2.1.0: {} - detect-libc@1.0.3: {} - detect-libc@2.1.2: {} detect-node-es@1.1.0: {} @@ -3745,10 +3714,6 @@ snapshots: fast-deep-equal@3.1.3: {} - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - fraction.js@5.3.4: {} fsevents@2.3.3: @@ -3783,8 +3748,6 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-number@7.0.0: {} - is-property@1.0.2: {} jiti@2.6.1: {} @@ -3856,11 +3819,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - ms@2.1.3: {} mysql2@3.16.0: @@ -3888,7 +3846,7 @@ snapshots: next-intl@4.7.0(next@16.1.1(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)(typescript@5.9.3): dependencies: '@formatjs/intl-localematcher': 0.5.10 - '@parcel/watcher': 2.5.1 + '@parcel/watcher': 2.5.4 '@swc/core': 1.15.8 negotiator: 1.0.0 next: 16.1.1(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -3911,7 +3869,7 @@ snapshots: '@next/env': 16.1.1 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.9.14 - caniuse-lite: 1.0.30001763 + caniuse-lite: 1.0.30001764 postcss: 8.4.31 react: 19.2.3 react-dom: 19.2.3(react@19.2.3) @@ -3937,7 +3895,7 @@ snapshots: picocolors@1.1.1: {} - picomatch@2.3.1: {} + picomatch@4.0.3: {} po-parser@2.1.1: {} @@ -4063,10 +4021,6 @@ snapshots: tm-grammars@1.27.1: {} - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - ts-mixer@6.0.4: {} tslib@2.8.1: {} @@ -4078,32 +4032,32 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - turbo-darwin-64@2.7.3: + turbo-darwin-64@2.7.4: optional: true - turbo-darwin-arm64@2.7.3: + turbo-darwin-arm64@2.7.4: optional: true - turbo-linux-64@2.7.3: + turbo-linux-64@2.7.4: optional: true - turbo-linux-arm64@2.7.3: + turbo-linux-arm64@2.7.4: optional: true - turbo-windows-64@2.7.3: + turbo-windows-64@2.7.4: optional: true - turbo-windows-arm64@2.7.3: + turbo-windows-arm64@2.7.4: optional: true - turbo@2.7.3: + turbo@2.7.4: optionalDependencies: - turbo-darwin-64: 2.7.3 - turbo-darwin-arm64: 2.7.3 - turbo-linux-64: 2.7.3 - turbo-linux-arm64: 2.7.3 - turbo-windows-64: 2.7.3 - turbo-windows-arm64: 2.7.3 + turbo-darwin-64: 2.7.4 + turbo-darwin-arm64: 2.7.4 + turbo-linux-64: 2.7.4 + turbo-linux-arm64: 2.7.4 + turbo-windows-64: 2.7.4 + turbo-windows-arm64: 2.7.4 tw-animate-css@1.4.0: {} From 75d03d80253e41b9d3667c997c24a938c9543d7d Mon Sep 17 00:00:00 2001 From: CarelessInternet Date: Sun, 11 Jan 2026 23:49:23 +0000 Subject: [PATCH 2/3] fix: allow no action --- .../commands/staff/configuration-ticket-threads/component.ts | 2 +- apps/bot/src/events/ThreadMembersUpdate.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/bot/src/commands/staff/configuration-ticket-threads/component.ts b/apps/bot/src/commands/staff/configuration-ticket-threads/component.ts index a459461b..2b63acf6 100644 --- a/apps/bot/src/commands/staff/configuration-ticket-threads/component.ts +++ b/apps/bot/src/commands/staff/configuration-ticket-threads/component.ts @@ -844,7 +844,7 @@ export class AuthorLeaveAction extends Component.Interaction { const bit = ThreadTicketing.actionsAsKeyAndFlagsMap.get(interaction.values.at(0) as ThreadTicketing.KeyOfActions); await database .update(ticketThreadsCategories) - .set({ authorLeaveAction: bit }) + .set({ authorLeaveAction: bit ?? null }) .where(and(eq(ticketThreadsCategories.id, categoryId), eq(ticketThreadsCategories.guildId, interaction.guildId))); interaction.editReply({ diff --git a/apps/bot/src/events/ThreadMembersUpdate.ts b/apps/bot/src/events/ThreadMembersUpdate.ts index 54a298b7..5ac2f8ef 100644 --- a/apps/bot/src/events/ThreadMembersUpdate.ts +++ b/apps/bot/src/events/ThreadMembersUpdate.ts @@ -59,7 +59,7 @@ export default class extends Event.Handler { state = 'delete'; } - if (row.logsChannelId) { + if (row.logsChannelId && row.authorLeaveAction) { const me = await thread.guild.members.fetchMe(); const logsChannel = await fetchChannel(thread.guild, row.logsChannelId); From 2f818e12603ca5ecccc4e4be182da102ecf74b91 Mon Sep 17 00:00:00 2001 From: CarelessInternet Date: Sun, 11 Jan 2026 23:51:03 +0000 Subject: [PATCH 3/3] fix: oopsies --- apps/bot/src/events/ThreadDelete.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/bot/src/events/ThreadDelete.ts b/apps/bot/src/events/ThreadDelete.ts index cd0e7d7e..bea31361 100644 --- a/apps/bot/src/events/ThreadDelete.ts +++ b/apps/bot/src/events/ThreadDelete.ts @@ -7,7 +7,7 @@ export default class extends Event.Handler { @LogExceptions public execute([thread]: Event.ArgumentsOf) { - if (thread.client.user.id) { + if (thread.ownerId === thread.client.user.id) { void database .delete(ticketsThreads) .where(eq(ticketsThreads.threadId, thread.id))