From 9cf7d2ce9a2ef6a84ef25a9e11d6091eb8e25411 Mon Sep 17 00:00:00 2001 From: Tarrgon <61888458+Tarrgon@users.noreply.github.com> Date: Wed, 3 Jun 2026 14:27:29 -0400 Subject: [PATCH 1/9] Add clynamic registry to .npmrc --- .npmrc | 1 + package-lock.json | 13 +++++-------- package.json | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..7eec312 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@clynamic:registry=https://npm.clynamic.net diff --git a/package-lock.json b/package-lock.json index c9c76ac..8276e80 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "packages": { "": { "dependencies": { - "@clynamic/dmark": "0.0.7", + "@clynamic/dmark": "0.0.8", "@redis/client": "5.1.0", "body-parser": "2.2.2", "discord.js": "14.26.4", @@ -30,13 +30,10 @@ } }, "node_modules/@clynamic/dmark": { - "version": "0.0.7", - "resolved": "https://npm.clynamic.net/%40clynamic%2Fdmark/-/0.0.7/dmark-0.0.7.tgz", - "integrity": "sha512-sz/rf0kSUkprTVFO+3dQjHg+jxbtfcqmWIVcSvsYU7Uz6ywAAd5RI4igZ7bsBGrOfXcgyEKRE6/XjygYHKPjQA==", - "license": "UNLICENSED", - "dependencies": { - "markdown-it": "^14.1.1" - } + "version": "0.0.8", + "resolved": "https://npm.clynamic.net/%40clynamic%2Fdmark/-/0.0.8/dmark-0.0.8.tgz", + "integrity": "sha512-KzZqTeWldrB2ij74s5hVUVdHaBur4QqYExv6V9mSiTovdcspbRS0pA5v6OwONdxvcBou7b4p4cIalNj4eARgxg==", + "license": "UNLICENSED" }, "node_modules/@discordjs/builders": { "version": "1.14.1", diff --git a/package.json b/package.json index d831b33..dfd7210 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "typescript-eslint": "8.32.1" }, "dependencies": { - "@clynamic/dmark": "0.0.7", + "@clynamic/dmark": "0.0.8", "@redis/client": "5.1.0", "body-parser": "2.2.2", "discord.js": "14.26.4", 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 2/9] 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 { From 2c7689a189b91b9aca5250717ce699b138bfe842 Mon Sep 17 00:00:00 2001 From: Tarrgon <61888458+Tarrgon@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:04:31 -0400 Subject: [PATCH 3/9] Just use the open post button --- src/utils/appeal-events.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/utils/appeal-events.ts b/src/utils/appeal-events.ts index b15c718..9b5098f 100644 --- a/src/utils/appeal-events.ts +++ b/src/utils/appeal-events.ts @@ -111,17 +111,6 @@ async function createEmbedFromAppeal(appeal: Appeal, flag: PostFlag, post: E621P async function getButtons(appeal: Appeal, flag: PostFlag, post: E621Post): Promise> { const row = new ActionRowBuilder(); - const primaryButton = new ButtonBuilder() - .setStyle(ButtonStyle.Link); - - if (appeal.category == 'flag') { - primaryButton - .setLabel('Open Flag') - .setURL(`${config.E621_BASE_URL}/post_flags/${appeal.target_id}`); - - row.addComponents(primaryButton); - } - const button = new ButtonBuilder() .setLabel('Open Post') .setStyle(ButtonStyle.Link) From 52a227334f201b35dc659f1ea646e389f8928bfa Mon Sep 17 00:00:00 2001 From: Tarrgon <61888458+Tarrgon@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:36:30 -0400 Subject: [PATCH 4/9] Add base URL for relative paths I forgot about this... --- src/utils/event-utils.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/event-utils.ts b/src/utils/event-utils.ts index 67cd0e1..b8726c2 100644 --- a/src/utils/event-utils.ts +++ b/src/utils/event-utils.ts @@ -135,7 +135,9 @@ export async function getLinks(input: string, limit: number = Number.MAX_SAFE_IN } export async function getDescription(data: { reason: string }): Promise { - return parseDTextToMarkdown(data.reason.length <= MAX_DESCRIPTION_LENGTH ? await getLinks(data.reason) : await getLinks(data.reason, MAX_DESCRIPTION_LENGTH)).output; + return parseDTextToMarkdown(data.reason.length <= MAX_DESCRIPTION_LENGTH ? await getLinks(data.reason) : await getLinks(data.reason, MAX_DESCRIPTION_LENGTH), { + baseUrl: 'https://e621.net' + }).output; } export function getAuthor(data: { user_id: number, user: string }): EmbedAuthorOptions { From fd3f626db48255793de9283d6b7024979203ea8b Mon Sep 17 00:00:00 2001 From: Tarrgon <61888458+Tarrgon@users.noreply.github.com> Date: Wed, 3 Jun 2026 19:48:13 -0400 Subject: [PATCH 5/9] Add tsc-alias --- package-lock.json | 261 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- tsconfig.json | 7 +- 3 files changed, 268 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8276e80..d08316a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "@types/express-session": "1.18.1", "eslint": "9.27.0", "rimraf": "6.0.1", + "tsc-alias": "1.8.17", "tsx": "4.19.4", "typescript": "5.8.3", "typescript-eslint": "8.32.1" @@ -1345,11 +1346,48 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1375,6 +1413,19 @@ } ] }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -1540,6 +1591,44 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", @@ -1575,6 +1664,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1691,6 +1790,19 @@ "node": ">=8" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/discord-api-types": { "version": "0.38.42", "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.42.tgz", @@ -2426,6 +2538,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2572,6 +2705,19 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2937,6 +3083,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/mylas": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/mylas/-/mylas-2.1.14.tgz", + "integrity": "sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/raouldeheer" + } + }, "node_modules/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", @@ -3045,6 +3205,16 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -3185,6 +3355,16 @@ "url": "https://opencollective.com/express" } }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", @@ -3198,6 +3378,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/plimit-lit": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/plimit-lit/-/plimit-lit-1.6.1.tgz", + "integrity": "sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "queue-lit": "^1.5.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -3302,6 +3495,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-lit": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/queue-lit/-/queue-lit-1.5.2.tgz", + "integrity": "sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -3404,6 +3607,32 @@ "node": ">= 6" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -3816,6 +4045,16 @@ "simple-concat": "^1.0.0" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/sqlite": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/sqlite/-/sqlite-5.1.1.tgz", @@ -4087,6 +4326,28 @@ "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", "license": "MIT" }, + "node_modules/tsc-alias": { + "version": "1.8.17", + "resolved": "https://registry.npmjs.org/tsc-alias/-/tsc-alias-1.8.17.tgz", + "integrity": "sha512-EIduCZHqbNwPm8BZYfq1aD7BQ697A4h6uSGMOFQfYGoQwfrYFTKwYfy9Bv42YxHkduVBcn9Zx0DkX111DKskyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.3", + "commander": "^9.0.0", + "get-tsconfig": "^4.10.0", + "globby": "^11.0.4", + "mylas": "^2.1.9", + "normalize-path": "^3.0.0", + "plimit-lit": "^1.2.6" + }, + "bin": { + "tsc-alias": "dist/bin/index.js" + }, + "engines": { + "node": ">=16.20.2" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", diff --git a/package.json b/package.json index dfd7210..da44e35 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "@types/express-session": "1.18.1", "eslint": "9.27.0", "rimraf": "6.0.1", + "tsc-alias": "1.8.17", "tsx": "4.19.4", "typescript": "5.8.3", "typescript-eslint": "8.32.1" @@ -27,7 +28,7 @@ "scripts": { "dev": "tsx watch --enable-source-maps src/index.ts", "start": "node dist/index.js", - "build": "npm run clean && npx tsc && npm run copyfiles", + "build": "npm run clean && npx tsc && npx tsc-alias && npm run copyfiles", "clean": "npx rimraf dist", "copyfiles": "npx copyfiles -u 1 \"./src/**/*.html\" ./dist" } diff --git a/tsconfig.json b/tsconfig.json index 26cc7ee..3f8a151 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,10 @@ "ES2021.String" ] }, - "include": ["src"], - "exclude": ["node_modules"] + "exclude": ["node_modules"], + "tsc-alias": { + "resolveFullPaths": true, + "verbose": false + } } \ No newline at end of file From 343cb3123c59bc06ee7a03fa98783849145d3f16 Mon Sep 17 00:00:00 2001 From: Tarrgon <61888458+Tarrgon@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:17:19 -0400 Subject: [PATCH 6/9] Register handlers after db opened --- src/events/handle-message.ts | 1 + src/index.ts | 21 +++++++++++---------- src/shared/Database.ts | 2 ++ 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/events/handle-message.ts b/src/events/handle-message.ts index def9990..d5a633f 100644 --- a/src/events/handle-message.ts +++ b/src/events/handle-message.ts @@ -50,6 +50,7 @@ const regexTesters = [ const uniqueRegexMatches = (g, i, a) => a.findIndex(v => v[1] == g[1]) == i; export async function handleMessageCreate(message: Message) { + console.log('message created'); if (message.author.bot) return; if (message.inGuild()) await Database.putMessage(message); diff --git a/src/index.ts b/src/index.ts index b1c7ca6..23209ee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -140,20 +140,21 @@ client.on('clientReady', async () => { ScheduledTasks.forEach(task => scheduler.add(task)); + client.on('guildAuditLogEntryCreate', handleAuditLogCreate); + client.on('guildBanRemove', handleBanRemove); + client.on('guildCreate', handleGuildCreate); + client.on('guildMemberAdd', handleMemberJoin); + client.on('messageCreate', handleMessageCreate); + client.on('messageDelete', handleMessageDelete); + client.on('messageDeleteBulk', handleBulkMessageDelete); + client.on('messageUpdate', handleMessageUpdate); + client.on('threadCreate', handleThreadCreate); + client.on('voiceStateUpdate', handleVoiceStateUpdate); + ready = true; console.log('Ready'); }); -client.on('guildAuditLogEntryCreate', handleAuditLogCreate); -client.on('guildBanRemove', handleBanRemove); -client.on('guildCreate', handleGuildCreate); -client.on('guildMemberAdd', handleMemberJoin); -client.on('messageCreate', handleMessageCreate); -client.on('messageDelete', handleMessageDelete); -client.on('messageDeleteBulk', handleBulkMessageDelete); -client.on('messageUpdate', handleMessageUpdate); -client.on('threadCreate', handleThreadCreate); -client.on('voiceStateUpdate', handleVoiceStateUpdate); client.on('error', console.error); process.on('uncaughtException', console.error); diff --git a/src/shared/Database.ts b/src/shared/Database.ts index d0ebd3e..9144eb8 100644 --- a/src/shared/Database.ts +++ b/src/shared/Database.ts @@ -122,6 +122,8 @@ export class Database { static async open(file: string): Promise { if (Database.db) return; + console.log('Opening SQLite database') + Database.db = await open({ filename: file, driver: sqlite3.Database From adf00ab26709dfd0e4e7362f8a0a477a468226b2 Mon Sep 17 00:00:00 2001 From: Tarrgon <61888458+Tarrgon@users.noreply.github.com> Date: Wed, 3 Jun 2026 20:17:25 -0400 Subject: [PATCH 7/9] Fix eslint --- eslint.config.js => eslint.config.mjs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) rename eslint.config.js => eslint.config.mjs (87%) diff --git a/eslint.config.js b/eslint.config.mjs similarity index 87% rename from eslint.config.js rename to eslint.config.mjs index c11223c..ac7dd3e 100644 --- a/eslint.config.js +++ b/eslint.config.mjs @@ -1,11 +1,11 @@ -const eslint = require('@eslint/js'); -const globals = require('globals'); -const tseslint = require('typescript-eslint'); -const stylistic = require('@stylistic/eslint-plugin'); +import eslint from '@eslint/js'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; +import stylistic from '@stylistic/eslint-plugin'; -const ignores = ['dist/**/*', 'node_modules/**/*', 'eslint.config.js']; +const ignores = ['dist/**/*', 'node_modules/**/*']; -module.exports = tseslint.config( +const config = tseslint.config( { ignores, extends: [ @@ -65,4 +65,7 @@ module.exports = tseslint.config( '@typescript-eslint/no-namespace': 'off', }, } -); \ No newline at end of file +); + + +export default config; \ No newline at end of file From f75fb5cff96fd81fe827d1c5cb773590c710dbde Mon Sep 17 00:00:00 2001 From: Tarrgon <61888458+Tarrgon@users.noreply.github.com> Date: Thu, 4 Jun 2026 08:04:06 -0400 Subject: [PATCH 8/9] Update Database.ts --- src/shared/Database.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/Database.ts b/src/shared/Database.ts index 9144eb8..719c07f 100644 --- a/src/shared/Database.ts +++ b/src/shared/Database.ts @@ -122,7 +122,7 @@ export class Database { static async open(file: string): Promise { if (Database.db) return; - console.log('Opening SQLite database') + console.log('Opening SQLite database'); Database.db = await open({ filename: file, From 39c7f35cf9b7ad76e00a66a67cc9b7e212992ac5 Mon Sep 17 00:00:00 2001 From: Tarrgon <61888458+Tarrgon@users.noreply.github.com> Date: Thu, 4 Jun 2026 08:09:54 -0400 Subject: [PATCH 9/9] Parse deletion reason to markdown --- src/utils/appeal-events.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/utils/appeal-events.ts b/src/utils/appeal-events.ts index 9b5098f..7292787 100644 --- a/src/utils/appeal-events.ts +++ b/src/utils/appeal-events.ts @@ -2,9 +2,10 @@ import { ActionRowBuilder, APIEmbedField, ButtonBuilder, ButtonStyle, Client, Di import { config } from '../config'; import { Database } from '../shared/Database'; import { Appeal, AppealUpdate, E621Post, PostFlag } from '../types'; -import { getAuthor, getColor, getDescription, getFields } from './event-utils'; +import { getAuthor, getColor, getDescription, getFields, getLinks } from './event-utils'; import { humanizeCapitalization } from './string-utils'; import { getE621Post, getE621PostFlag } from './e621-utils'; +import { parseDTextToMarkdown } from '@clynamic/dmark'; export async function appealUpdateHandler(client: Client, update: string) { const data: AppealUpdate = JSON.parse(update); @@ -87,11 +88,11 @@ function getURL(appeal: Appeal): string { return `${config.E621_BASE_URL}/appeals/${appeal.id}`; } -function getCustomFields(appeal: Appeal, flag: PostFlag, post: E621Post): APIEmbedField[] { +async function getCustomFields(appeal: Appeal, flag: PostFlag, post: E621Post): Promise { return [ { name: 'Deletion Reason', - value: flag.reason, + value: parseDTextToMarkdown(await getLinks(flag.reason)).output, inline: true } ]; @@ -104,7 +105,7 @@ async function createEmbedFromAppeal(appeal: Appeal, flag: PostFlag, post: E621P .setDescription(await getDescription(appeal)) .setAuthor(getAuthor(appeal)) .setColor(getColor(appeal)) - .setFields(...getFields(appeal), ...getCustomFields(appeal, flag, post)) + .setFields(...getFields(appeal), ...(await getCustomFields(appeal, flag, post))) .setFooter({ text: `Appeal #${appeal.id}` }); }