Update ticket and appeal handlers

This commit is contained in:
Tarrgon
2026-06-03 15:22:25 -04:00
parent 9cf7d2ce9a
commit c95a81e784
8 changed files with 110 additions and 37 deletions
+24
View File
@@ -282,6 +282,18 @@ export class Database {
await Database.db.run('INSERT INTO tickets(id, message_id) VALUES (?, ?)', ticketId, messageId); 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) { static async removeTicket(ticketId: number) {
await Database.db.run('DELETE from tickets WHERE id = ?', ticketId); 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); 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) { static async removeAppeal(appealId: number) {
await Database.db.run('DELETE from appeals WHERE id = ?', appealId); await Database.db.run('DELETE from appeals WHERE id = ?', appealId);
} }
+16
View File
@@ -173,3 +173,19 @@ export type Record = {
updater_id: number updater_id: number
is_deleted: boolean 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
}
+45 -16
View File
@@ -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 { config } from '../config';
import { Database } from '../shared/Database'; 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 { getAuthor, getColor, getDescription, getFields } from './event-utils';
import { humanizeCapitalization } from './string-utils'; import { humanizeCapitalization } from './string-utils';
import { getE621Post, getE621PostFlag } from './e621-utils';
export async function appealUpdateHandler(client: Client, update: string) { export async function appealUpdateHandler(client: Client, update: string) {
const data: AppealUpdate = JSON.parse(update); const data: AppealUpdate = JSON.parse(update);
@@ -26,13 +27,16 @@ async function postAppeal(client: Client, data: AppealUpdate) {
const appeal = data.appeal; 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] : [] }); 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) { async function updateAppeal(client: Client, data: AppealUpdate) {
@@ -48,18 +52,26 @@ async function updateAppeal(client: Client, data: AppealUpdate) {
if (!messageId) return postAppeal(client, data); if (!messageId) return postAppeal(client, data);
const message = await channel.messages.fetch(messageId); let message: Message | undefined;
const embed = await createEmbedFromAppeal(data.appeal);
if (!message || message.author.id != config.DISCORD_CLIENT_ID) { try {
const newMessage = await channel.send({ embeds: [embed] }); message = await channel.messages.fetch(messageId);
await Database.removeAppeal(data.appeal.id); } catch (e) {
await Database.putAppeal(data.appeal.id, newMessage.id); // Ignore unknown message errors
} else { if (!(e instanceof DiscordAPIError && e.code == 10008)) {
await message.edit({ embeds: [embed] }); 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 { function getTitle(appeal: Appeal): string {
if (!appeal.target) return `${humanizeCapitalization(appeal.category)} appeal from ${appeal.user}`; if (!appeal.target) return `${humanizeCapitalization(appeal.category)} appeal from ${appeal.user}`;
@@ -75,18 +87,28 @@ function getURL(appeal: Appeal): string {
return `${config.E621_BASE_URL}/appeals/${appeal.id}`; return `${config.E621_BASE_URL}/appeals/${appeal.id}`;
} }
async function createEmbedFromAppeal(appeal: Appeal): Promise<EmbedBuilder> { 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<EmbedBuilder> {
return new EmbedBuilder() return new EmbedBuilder()
.setTitle(getTitle(appeal)) .setTitle(getTitle(appeal))
.setURL(await getURL(appeal)) .setURL(await getURL(appeal))
.setDescription(await getDescription(appeal)) .setDescription(await getDescription(appeal))
.setAuthor(getAuthor(appeal)) .setAuthor(getAuthor(appeal))
.setColor(getColor(appeal)) .setColor(getColor(appeal))
.setFields(...getFields(appeal)) .setFields(...getFields(appeal), ...getCustomFields(appeal, flag, post))
.setFooter({ text: `Appeal #${appeal.id}` }); .setFooter({ text: `Appeal #${appeal.id}` });
} }
async function getButtons(appeal: Appeal): Promise<ActionRowBuilder<ButtonBuilder>> { async function getButtons(appeal: Appeal, flag: PostFlag, post: E621Post): Promise<ActionRowBuilder<ButtonBuilder>> {
const row = new ActionRowBuilder<ButtonBuilder>(); const row = new ActionRowBuilder<ButtonBuilder>();
const primaryButton = new ButtonBuilder() const primaryButton = new ButtonBuilder()
@@ -100,5 +122,12 @@ async function getButtons(appeal: Appeal): Promise<ActionRowBuilder<ButtonBuilde
row.addComponents(primaryButton); row.addComponents(primaryButton);
} }
const button = new ButtonBuilder()
.setLabel('Open Post')
.setStyle(ButtonStyle.Link)
.setURL(`${config.E621_BASE_URL}/posts/${post.id}`);
row.addComponents(button);
return row; return row;
} }
-5
View File
@@ -1,5 +0,0 @@
import { formatMarkdown, parseDTextToAST } from '@clynamic/dmark';
export function dtextToMarkdown(dtext: string): string {
return formatMarkdown(parseDTextToAST(dtext)).output;
}
+7 -3
View File
@@ -1,5 +1,5 @@
import { config } from '../config'; import { config } from '../config';
import { E621Post, E621User, Record } from '../types'; import { E621Post, E621User, PostFlag, Record } from '../types';
const BLACKLISTED_TAGS: string[] = []; const BLACKLISTED_TAGS: string[] = [];
const BLACKLISTED_NONSAFE_TAGS: string[] = ['young']; const BLACKLISTED_NONSAFE_TAGS: string[] = ['young'];
@@ -35,11 +35,15 @@ export async function getE621User(idOrName: string | number): Promise<E621User |
} }
export async function getE621Post(id: string | number): Promise<E621Post | null> { export async function getE621Post(id: string | number): Promise<E621Post | null> {
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<E621Post | null> { export async function getE621PostByMd5(md5: string): Promise<E621Post | null> {
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<PostFlag | null> {
return await request(`/post_flags/${id}`) as PostFlag ?? null;
} }
export const enum PostAction { export const enum PostAction {
+2 -2
View File
@@ -2,7 +2,7 @@ import { APIEmbedField, EmbedAuthorOptions } from 'discord.js';
import { config } from '../config'; import { config } from '../config';
import { getE621Post, PostAction, spoilerOrBlacklist } from './e621-utils'; 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 { 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. // TODO: Condense this and the message event handler regex array.
const linkReplacers = [ 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<string> { export async function getDescription(data: { reason: string }): Promise<string> {
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 { export function getAuthor(data: { user_id: number, user: string }): EmbedAuthorOptions {
-1
View File
@@ -8,7 +8,6 @@ export * from './channel-utils';
export * from './commands'; export * from './commands';
export * from './debug-utils'; export * from './debug-utils';
export * from './discord-user-utils'; export * from './discord-user-utils';
export * from './dtext-utils';
export * from './e621-utils'; export * from './e621-utils';
export * from './embed-utils'; export * from './embed-utils';
export * from './event-log-utils'; export * from './event-log-utils';
+15 -9
View File
@@ -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 { config } from '../config';
import { Database } from '../shared/Database'; import { Database } from '../shared/Database';
import { Ticket, TicketPhrase, TicketUpdate } from '../types'; 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] }); 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); sendTicketAlerts(ticket, channel);
} }
@@ -52,17 +52,23 @@ async function updateTicket(client: Client, data: TicketUpdate) {
if (!messageId) return postTicket(client, data); 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); 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 { function getTitle(ticket: Ticket): string {
if (!ticket.target) return `${humanizeCapitalization(ticket.category)} report by ${ticket.user}`; if (!ticket.target) return `${humanizeCapitalization(ticket.category)} report by ${ticket.user}`;