diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts index 11982379..120de69e 100644 --- a/src/api/routes/channels/#channel_id/messages/index.ts +++ b/src/api/routes/channels/#channel_id/messages/index.ts @@ -413,43 +413,29 @@ router.post( } // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore + // @ts-ignore message.member.roles = message.member.roles.filter((x) => x.id != x.guild_id).map((x) => x.id); - try { - if (message.content) - for (const rule of await AutomodRule.find({ where: { guild_id: message.guild_id, enabled: true, event_type: AutomodRuleEventType.MESSAGE_SEND } })) { + if (message.content) + try { + const matchingRules = await AutomodRule.find({ + where: { guild_id: message.guild_id, enabled: true, event_type: AutomodRuleEventType.MESSAGE_SEND }, + order: { position: "ASC" }, + }); + for (const rule of matchingRules) { if (rule.exempt_channels.includes(channel_id)) continue; if (message.member.roles.some((x) => rule.exempt_roles.includes(x.id))) continue; + if (rule.trigger_type == AutomodTriggerTypes.CUSTOM_WORDS) { const triggerMeta = rule.trigger_metadata as AutomodCustomWordsRule; - const regexes = triggerMeta.regex_patterns - .map((x) => new RegExp(x, "i")) - .concat( - triggerMeta.keyword_filter - .map((k) => - k - // Convert simple wildcard patterns to regex - .replace(".", "\\.") - .replace("?", ".") - .replace("*", ".*"), - ) - .map((k) => new RegExp(k, "i")), - ); - const allowedRegexes = triggerMeta.allow_list - .map((k) => - k - // Convert simple wildcard patterns to regex - .replace(".", "\\.") - .replace("?", ".") - .replace("*", ".*"), - ) - .map((k) => new RegExp(k, "i")); + const regexes = triggerMeta.regex_patterns.map((x) => new RegExp(x, "i")).concat(triggerMeta.keyword_filter.map((k) => k.globToRegexp("i"))); + const allowedRegexes = triggerMeta.allow_list.map((k) => k.globToRegexp("i")); const matches = regexes .map((r) => message.content!.match(r)) .filter((x) => x !== null && x.length > 0) .filter((x) => !allowedRegexes.some((ar) => ar.test(x![0]))); + if (matches.length > 0) { console.log("Automod triggered by message:", message.id, "matches:", matches); if (rule.actions.some((x) => x.type == AutomodRuleActionType.SEND_ALERT_MESSAGE && x.metadata.channel_id)) { @@ -458,32 +444,32 @@ router.post( const alertChannel = await Channel.findOne({ where: { id: action.metadata.channel_id } }); if (!alertChannel) continue; const msg = await Message.createWithDefaults({ - channel_id: alertChannel.id, content: `Automod Alert: Message ${message.id} by <@${message.author_id}> in <#${channel.id}> triggered automod rule "${rule.name}".\nMatched terms: ${matches .map((x) => `\`${x![0]}\``) .join(", ")}`, author: message.author, + channel_id: alertChannel.id, guild_id: message.guild_id, member_id: message.member_id, - author_id: message.author_id + author_id: message.author_id, }); - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_CREATE", - channel_id: msg.channel_id, - data: msg.toJSON(), - } as MessageCreateEvent), - ]); + await message.save(); + // await Promise.all([ + await emitEvent({ + event: "MESSAGE_CREATE", + channel_id: msg.channel_id, + data: msg.toJSON(), + } as MessageCreateEvent); + // ]); } } } } } - } catch (e) { - console.log("[Automod] failed to process message:", e); - } + } catch (e) { + console.log("[Automod] failed to process message:", e); + } } let read_state = await ReadState.findOne({ diff --git a/src/util/util/extensions/String.test.ts b/src/util/util/extensions/String.test.ts new file mode 100644 index 00000000..d5cc9292 --- /dev/null +++ b/src/util/util/extensions/String.test.ts @@ -0,0 +1,15 @@ +import moduleAlias from "module-alias"; +moduleAlias(); +import './String'; +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +describe("String extensions", () => { + + it("globToRegexp", () => { + const pattern = "file-*.txt"; + const regex = pattern.globToRegexp(); + assert.ok(regex.test("file-123.txt")); + }); + +}); \ No newline at end of file diff --git a/src/util/util/extensions/String.ts b/src/util/util/extensions/String.ts new file mode 100644 index 00000000..ce9b1a51 --- /dev/null +++ b/src/util/util/extensions/String.ts @@ -0,0 +1,37 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2025 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +declare global { + interface String { + globToRegexp(flags?: string): RegExp; + } +} + +export function stringGlobToRegexp(str: string, flags?: string): RegExp { + // Convert simple wildcard patterns to regex + const escaped = str.replace(".", "\\.") + .replace("?", ".") + .replace("*", ".*") + return new RegExp(escaped, flags); +} + +// Register extensions +if (!String.prototype.globToRegexp) + String.prototype.globToRegexp = function (str: string, flags?: string) { + return stringGlobToRegexp.call(null, str, flags); + }; \ No newline at end of file diff --git a/src/util/util/extensions/index.ts b/src/util/util/extensions/index.ts index 3bde4f7f..e006591f 100644 --- a/src/util/util/extensions/index.ts +++ b/src/util/util/extensions/index.ts @@ -1,4 +1,5 @@ export * from "./Array"; export * from "./Math"; export * from "./Url"; -export * from "./Object"; \ No newline at end of file +export * from "./Object"; +export * from "./String"; \ No newline at end of file