From c95a81e784757513ee80674d322ec37edeb651ca Mon Sep 17 00:00:00 2001 From: Tarrgon <61888458+Tarrgon@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:22:25 -0400 Subject: [PATCH] Update ticket and appeal handlers --- src/shared/Database.ts | 24 +++++++++++++++ src/types/e621-types.d.ts | 16 ++++++++++ src/utils/appeal-events.ts | 61 ++++++++++++++++++++++++++++---------- src/utils/dtext-utils.ts | 5 ---- src/utils/e621-utils.ts | 10 +++++-- src/utils/event-utils.ts | 4 +-- src/utils/index.ts | 1 - src/utils/ticket-events.ts | 26 +++++++++------- 8 files changed, 110 insertions(+), 37 deletions(-) delete mode 100644 src/utils/dtext-utils.ts diff --git a/src/shared/Database.ts b/src/shared/Database.ts index 8257efb..d0ebd3e 100644 --- a/src/shared/Database.ts +++ b/src/shared/Database.ts @@ -282,6 +282,18 @@ export class Database { await Database.db.run('INSERT INTO tickets(id, message_id) VALUES (?, ?)', ticketId, messageId); } + static async updateTicketMessageId(ticketId: number, messageId: string) { + await Database.db.run('UPDATE tickets SET message_id = ? WHERE id = ?', messageId, ticketId); + } + + static async putTicketOrUpdate(ticketId: number, messageId: string) { + if (await Database.getTicketMessageId(ticketId)) { + await Database.updateTicketMessageId(ticketId, messageId); + } else { + await Database.putTicket(ticketId, messageId); + } + } + static async removeTicket(ticketId: number) { await Database.db.run('DELETE from tickets WHERE id = ?', ticketId); } @@ -327,6 +339,18 @@ export class Database { await Database.db.run('INSERT INTO appeals(id, message_id) VALUES (?, ?)', appealId, messageId); } + static async updateAppealMessageId(appealId: number, messageId: string) { + await Database.db.run('UPDATE appeals SET message_id = ? WHERE id = ?', messageId, appealId); + } + + static async putAppealOrUpdate(ticketId: number, messageId: string) { + if (await Database.getAppealMessageId(ticketId)) { + await Database.updateAppealMessageId(ticketId, messageId); + } else { + await Database.putAppeal(ticketId, messageId); + } + } + static async removeAppeal(appealId: number) { await Database.db.run('DELETE from appeals WHERE id = ?', appealId); } diff --git a/src/types/e621-types.d.ts b/src/types/e621-types.d.ts index e891723..0f466bb 100644 --- a/src/types/e621-types.d.ts +++ b/src/types/e621-types.d.ts @@ -172,4 +172,20 @@ export type Record = { updated_at: string updater_id: number is_deleted: boolean +} + +export type PostFlag = { + id: number + post_id: number + creator_id: number + reason: string + is_resolved: boolean + created_at: string + updated_at: string + is_deletion: boolean + note: string | null + reason_name: string + needs_parent_id: boolean + needs_staff_reason: boolean + type: string } \ No newline at end of file diff --git a/src/utils/appeal-events.ts b/src/utils/appeal-events.ts index 82c23a0..b15c718 100644 --- a/src/utils/appeal-events.ts +++ b/src/utils/appeal-events.ts @@ -1,9 +1,10 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, EmbedBuilder } from 'discord.js'; +import { ActionRowBuilder, APIEmbedField, ButtonBuilder, ButtonStyle, Client, DiscordAPIError, EmbedBuilder, Message } from 'discord.js'; import { config } from '../config'; import { Database } from '../shared/Database'; -import { Appeal, AppealUpdate } from '../types'; +import { Appeal, AppealUpdate, E621Post, PostFlag } from '../types'; import { getAuthor, getColor, getDescription, getFields } from './event-utils'; import { humanizeCapitalization } from './string-utils'; +import { getE621Post, getE621PostFlag } from './e621-utils'; export async function appealUpdateHandler(client: Client, update: string) { const data: AppealUpdate = JSON.parse(update); @@ -26,13 +27,16 @@ async function postAppeal(client: Client, data: AppealUpdate) { const appeal = data.appeal; - const embed = await createEmbedFromAppeal(appeal); + const flag = await getE621PostFlag(appeal.target_id); + const post = await getE621Post(flag!.post_id); - const row = await getButtons(appeal); + const embed = await createEmbedFromAppeal(appeal, flag!, post!); + + const row = await getButtons(appeal, flag!, post!); const message = await channel.send({ embeds: [embed], components: row.components.length > 0 ? [row] : [] }); - await Database.putAppeal(appeal.id, message.id); + await Database.putAppealOrUpdate(appeal.id, message.id); } async function updateAppeal(client: Client, data: AppealUpdate) { @@ -48,16 +52,24 @@ async function updateAppeal(client: Client, data: AppealUpdate) { if (!messageId) return postAppeal(client, data); - const message = await channel.messages.fetch(messageId); - const embed = await createEmbedFromAppeal(data.appeal); + let message: Message | undefined; - if (!message || message.author.id != config.DISCORD_CLIENT_ID) { - const newMessage = await channel.send({ embeds: [embed] }); - await Database.removeAppeal(data.appeal.id); - await Database.putAppeal(data.appeal.id, newMessage.id); - } else { - await message.edit({ embeds: [embed] }); + try { + message = await channel.messages.fetch(messageId); + } catch (e) { + // Ignore unknown message errors + if (!(e instanceof DiscordAPIError && e.code == 10008)) { + throw e; + } } + + if (!message || message.author.id != config.DISCORD_CLIENT_ID) return postAppeal(client, data); + + const flag = await getE621PostFlag(data.appeal.target_id); + const post = await getE621Post(flag!.post_id); + + const embed = await createEmbedFromAppeal(data.appeal, flag!, post!); + await message.edit({ embeds: [embed] }); } function getTitle(appeal: Appeal): string { @@ -75,18 +87,28 @@ function getURL(appeal: Appeal): string { return `${config.E621_BASE_URL}/appeals/${appeal.id}`; } -async function createEmbedFromAppeal(appeal: Appeal): Promise { +function getCustomFields(appeal: Appeal, flag: PostFlag, post: E621Post): APIEmbedField[] { + return [ + { + name: 'Deletion Reason', + value: flag.reason, + inline: true + } + ]; +} + +async function createEmbedFromAppeal(appeal: Appeal, flag: PostFlag, post: E621Post): Promise { return new EmbedBuilder() .setTitle(getTitle(appeal)) .setURL(await getURL(appeal)) .setDescription(await getDescription(appeal)) .setAuthor(getAuthor(appeal)) .setColor(getColor(appeal)) - .setFields(...getFields(appeal)) + .setFields(...getFields(appeal), ...getCustomFields(appeal, flag, post)) .setFooter({ text: `Appeal #${appeal.id}` }); } -async function getButtons(appeal: Appeal): Promise> { +async function getButtons(appeal: Appeal, flag: PostFlag, post: E621Post): Promise> { const row = new ActionRowBuilder(); const primaryButton = new ButtonBuilder() @@ -100,5 +122,12 @@ async function getButtons(appeal: Appeal): Promise { - return (await request(`/posts/${id}`))?.post as E621Post ?? null; + return (await request(`/posts/${id}`, { v2: 'true' })) as E621Post ?? null; } export async function getE621PostByMd5(md5: string): Promise { - return (await request('/posts', { md5 }))?.post as E621Post ?? null; + return (await request('/posts', { md5, v2: 'true' })) as E621Post ?? null; +} + +export async function getE621PostFlag(id: number): Promise { + return await request(`/post_flags/${id}`) as PostFlag ?? null; } export const enum PostAction { diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 79166b2..67cd0e1 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -2,7 +2,7 @@ import { APIEmbedField, EmbedAuthorOptions } from 'discord.js'; import { config } from '../config'; import { getE621Post, PostAction, spoilerOrBlacklist } from './e621-utils'; import { appealIDRegex, blipIDRegex, commentIDRegex, flagIDRegex, forumTopicIDRegex, poolIDRegex, postIDRegex, recordIDRegex, searchLinkRegex, setIDRegex, takedownIDRegex, ticketIDRegex, userIDRegex, wikiLinkRegex } from './message-matcher-regex'; -import { dtextToMarkdown } from './dtext-utils'; +import { parseDTextToMarkdown } from '@clynamic/dmark'; // TODO: Condense this and the message event handler regex array. const linkReplacers = [ @@ -135,7 +135,7 @@ export async function getLinks(input: string, limit: number = Number.MAX_SAFE_IN } export async function getDescription(data: { reason: string }): Promise { - return dtextToMarkdown(data.reason.length <= MAX_DESCRIPTION_LENGTH ? await getLinks(data.reason) : await getLinks(data.reason, MAX_DESCRIPTION_LENGTH)); + return parseDTextToMarkdown(data.reason.length <= MAX_DESCRIPTION_LENGTH ? await getLinks(data.reason) : await getLinks(data.reason, MAX_DESCRIPTION_LENGTH)).output; } export function getAuthor(data: { user_id: number, user: string }): EmbedAuthorOptions { diff --git a/src/utils/index.ts b/src/utils/index.ts index 00332f9..af8b12f 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -8,7 +8,6 @@ export * from './channel-utils'; export * from './commands'; export * from './debug-utils'; export * from './discord-user-utils'; -export * from './dtext-utils'; export * from './e621-utils'; export * from './embed-utils'; export * from './event-log-utils'; diff --git a/src/utils/ticket-events.ts b/src/utils/ticket-events.ts index fd33e75..90af9cb 100644 --- a/src/utils/ticket-events.ts +++ b/src/utils/ticket-events.ts @@ -1,4 +1,4 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, EmbedBuilder, SendableChannels } from 'discord.js'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, DiscordAPIError, EmbedBuilder, Message, SendableChannels } from 'discord.js'; import { config } from '../config'; import { Database } from '../shared/Database'; import { Ticket, TicketPhrase, TicketUpdate } from '../types'; @@ -34,7 +34,7 @@ async function postTicket(client: Client, data: TicketUpdate) { const message = await channel.send({ embeds: [embed], components: [row] }); - await Database.putTicket(ticket.id, message.id); + await Database.putTicketOrUpdate(ticket.id, message.id); sendTicketAlerts(ticket, channel); } @@ -52,16 +52,22 @@ async function updateTicket(client: Client, data: TicketUpdate) { if (!messageId) return postTicket(client, data); - const message = await channel.messages.fetch(messageId); + let message: Message | undefined; + + try { + message = await channel.messages.fetch(messageId); + } catch (e) { + // Ignore unknown message errors + if (!(e instanceof DiscordAPIError && e.code == 10008)) { + throw e; + } + } + + if (!message || message.author.id != config.DISCORD_CLIENT_ID) return postTicket(client, data); + const embed = await createEmbedFromTicket(data.ticket); - if (!message || message.author.id != config.DISCORD_CLIENT_ID) { - const newMessage = await channel.send({ embeds: [embed] }); - await Database.removeTicket(data.ticket.id); - await Database.putTicket(data.ticket.id, newMessage.id); - } else { - await message.edit({ embeds: [embed] }); - } + await message.edit({ embeds: [embed] }); } function getTitle(ticket: Ticket): string {