From 52300b12275a62935f53136132d8d1549f352130 Mon Sep 17 00:00:00 2001 From: Tarrgon <61888458+Tarrgon@users.noreply.github.com> Date: Wed, 17 Jun 2026 09:12:01 -0400 Subject: [PATCH] Automatically delete pools that embed bad content --- src/events/handle-message.ts | 74 ++++++++++++++++++++++++++++++++++-- src/types/e621-types.d.ts | 14 +++++++ src/utils/e621-utils.ts | 10 ++++- 3 files changed, 94 insertions(+), 4 deletions(-) diff --git a/src/events/handle-message.ts b/src/events/handle-message.ts index def9990..d0b9b89 100644 --- a/src/events/handle-message.ts +++ b/src/events/handle-message.ts @@ -1,8 +1,8 @@ import { Message as DiscordMessage, GuildBasedChannel, GuildTextBasedChannel, OmitPartialGroupDMChannel, PartialMessage, ReadonlyCollection, spoiler } from 'discord.js'; import { config } from '../config'; import { Database } from '../shared/Database'; -import { E621Post } from '../types'; -import { ALLOWED_MIMETYPES, appealIDRegex, artistIDRegex, blipIDRegex, calculateMD5FromURL, channelIgnoresLinks, channelIsInStaffCategory, channelIsSafe, commentIDRegex, flagIDRegex, forumTopicIDRegex, getE621Post, getE621PostByMd5, getPostUrl, isEdited, isInSpoilerTags, issueRegex, logDeletion, logEdit, poolIDRegex, PostAction, postIDRegex, prRegex, recordIDRegex, searchLinkRegex, setIDRegex, spoilerOrBlacklist, takedownIDRegex, ticketIDRegex, userIDRegex, wikiLinkRegex } from '../utils'; +import { E621Pool, E621Post } from '../types'; +import { ALLOWED_MIMETYPES, appealIDRegex, artistIDRegex, blipIDRegex, calculateMD5FromURL, channelIgnoresLinks, channelIsInStaffCategory, channelIsSafe, commentIDRegex, flagIDRegex, forumTopicIDRegex, getE621Pool, getE621Post, getE621PostByMd5, getManyE621Posts, getPoolUrl, getPostUrl, isEdited, isInSpoilerTags, issueRegex, logDeletion, logEdit, poolIDRegex, PostAction, postIDRegex, prRegex, recordIDRegex, searchLinkRegex, setIDRegex, spoilerOrBlacklist, takedownIDRegex, ticketIDRegex, userIDRegex, wikiLinkRegex } from '../utils'; export type Message = OmitPartialGroupDMChannel>; export type Partial = OmitPartialGroupDMChannel; @@ -11,9 +11,11 @@ export type Partial = OmitPartialGroupDMChannel; const postRegex = new RegExp('!?https?://(?:.*@)?(?:e621|e926)\\.net/+posts/+([0-9]+)', 'gi'); const postShareRegex = new RegExp('!?https?://(?:.*@)?(?:e621|e926)\\.net/+p/+([a-z0-9]+)', 'gi'); const imageRegex = new RegExp('!?https?://(?:.*@)?static[0-9]*\\.(?:e621|e926)\\.net/+data/+(?:sample/+|preview/+|)[\\da-f]{2}/+[\\da-f]{2}/+([\\da-f]{32})\\.[\\da-z]+', 'gi'); +const poolRegex = new RegExp('!?https?://(?:.*@)?(?:e621|e926)\\.net/+pools/+([0-9]+)', 'gi'); const postRegex_DEV = new RegExp('!?https?://(?:.*@)?localhost:3000/+posts/+([0-9]+)', 'gi'); const imageRegex_DEV = new RegExp('!?https?://(?:.*@)?localhost:3000/+data/+(?:sample/+|preview/+|)[\\da-f]{2}/+[\\da-f]{2}/+([\\da-f]{32})\\.[\\da-z]+', 'gi'); +const poolRegex_DEV = new RegExp('!?https?://(?:.*@)?localhost:3000/+pools/+([0-9]+)', 'gi'); const md5Regex = new RegExp('^([a-f0-9]{32}).(?:png|apng|jpg|jpeg|gif|webm|mp4)$', 'gi'); @@ -25,14 +27,16 @@ const regexTesters = [ }) }, { runInDev: false, regex: imageRegex, handler: imageHandler }, + { runInDev: false, regex: poolRegex, handler: poolHandler }, { runInDev: true, regex: postRegex_DEV, handler: postHandler.bind(null, null) }, { runInDev: true, regex: imageRegex_DEV, handler: imageHandler }, + { runInDev: true, regex: poolRegex_DEV, handler: poolHandler }, { runInDev: true, regex: postIDRegex, handler: postIdHandler }, + { runInDev: true, regex: poolIDRegex, handler: poolIdHandler }, { runInDev: true, regex: userIDRegex, handler: idHandler.bind(null, 'users') }, { runInDev: true, regex: forumTopicIDRegex, handler: idHandler.bind(null, 'forum_topics') }, { runInDev: true, regex: commentIDRegex, handler: idHandler.bind(null, 'comments') }, { runInDev: true, regex: blipIDRegex, handler: idHandler.bind(null, 'blips') }, - { runInDev: true, regex: poolIDRegex, handler: idHandler.bind(null, 'pools') }, { runInDev: true, regex: setIDRegex, handler: idHandler.bind(null, 'post_sets') }, { runInDev: true, regex: takedownIDRegex, handler: idHandler.bind(null, 'takedowns') }, { runInDev: true, regex: recordIDRegex, handler: idHandler.bind(null, 'user_feedbacks') }, @@ -294,6 +298,42 @@ async function postIdHandler(message: Message, matchedGroups: RegExpExecArray[]) return true; } +async function poolIdHandler(message: Message, matchedGroups: RegExpExecArray[]): Promise { + if (!message.guildId) return true; + + const pools: E621Pool[] = []; + + for (const match of matchedGroups) { + try { + const pool = await getE621Pool(match[1]); + if (pool) pools.push(pool); + } catch (e) { + console.error(e); + } + } + + const posts: E621Post[] = []; + + try { + const res = await getManyE621Posts(pools.map(p => p.post_ids[0])); + if (res) posts.push(...res); + } catch (e) { + console.error(e); + } + + if (await blacklistIfNecessary(message, posts)) return false; + + const skip = await channelIgnoresLinks(message.channel as GuildBasedChannel); + + if (skip) return true; + + const content = pools.map(getPoolUrl).join('\n'); + + if (content.trim().length > 0) return content.trim(); + + return true; +} + async function idHandler(path: string, message: Message, matchedGroups: RegExpExecArray[]): Promise { if (!message.guildId) return true; @@ -354,6 +394,34 @@ async function imageHandler(message: Message, matchedGroups: RegExpExecArray[]): return true; } +async function poolHandler(message: Message, matchedGroups: RegExpExecArray[]) { + if (!message.guildId) return true; + + const pools: E621Pool[] = []; + + for (const match of matchedGroups) { + try { + const pool = await getE621Pool(match[1]); + if (pool) pools.push(pool); + } catch (e) { + console.error(e); + } + } + + const posts: E621Post[] = []; + + try { + const res = await getManyE621Posts(pools.map(p => p.post_ids[0])); + if (res) posts.push(...res); + } catch (e) { + console.error(e); + } + + if (await blacklistIfNecessary(message, posts)) return false; + + return true; +} + async function githubPullRequestHandler(message: Message, matchedGroups: RegExpExecArray[]): Promise { const skip = await channelIgnoresLinks(message.channel as GuildBasedChannel); diff --git a/src/types/e621-types.d.ts b/src/types/e621-types.d.ts index 0f466bb..c29d92e 100644 --- a/src/types/e621-types.d.ts +++ b/src/types/e621-types.d.ts @@ -188,4 +188,18 @@ export type PostFlag = { needs_parent_id: boolean needs_staff_reason: boolean type: string +} + +export type E621Pool = { + id: number + name: string + created_at: string + updated_at: string + creator_id: number + creator_name: string + description: string + is_active: boolean + category: 'collection' | 'series' + post_ids: number[] + post_count: number } \ No newline at end of file diff --git a/src/utils/e621-utils.ts b/src/utils/e621-utils.ts index 474502b..3a07229 100644 --- a/src/utils/e621-utils.ts +++ b/src/utils/e621-utils.ts @@ -1,5 +1,5 @@ import { config } from '../config'; -import { E621Post, E621User, PostFlag, Record } from '../types'; +import { E621Pool, E621Post, E621User, PostFlag, Record } from '../types'; const BLACKLISTED_TAGS: string[] = []; const BLACKLISTED_NONSAFE_TAGS: string[] = ['young']; @@ -52,6 +52,10 @@ export async function getE621PostFlag(id: number): Promise { return await request(`/post_flags/${id}`) as PostFlag ?? null; } +export async function getE621Pool(id: string | number): Promise { + return (await request(`/pools/${id}`)) as E621Pool ?? null; +} + export const enum PostAction { NoAction = 0, Spoiler = 1, @@ -79,6 +83,10 @@ export function getPostUrl(post: E621Post): string { return `${config.E621_BASE_URL}/posts/${post.id}`; } +export function getPoolUrl(pool: E621Pool): string { + return `${config.E621_BASE_URL}/pools/${pool.id}`; +} + export async function userIsBanned(idOrName: string | number): Promise { const user = await getE621User(idOrName); return user?.is_banned ?? false;