Automod work vol 2
This commit is contained in:
parent
f657b169df
commit
0e9f65e0d5
12 changed files with 420 additions and 59 deletions
|
@ -14,18 +14,15 @@ const MessageSpamTriggerConfig = t.type({
|
|||
});
|
||||
type TMessageSpamTriggerConfig = t.TypeOf<typeof MessageSpamTriggerConfig>;
|
||||
|
||||
const MessageSpamMatchResultType = t.type({
|
||||
archiveId: t.string,
|
||||
});
|
||||
type TMessageSpamMatchResultType = t.TypeOf<typeof MessageSpamMatchResultType>;
|
||||
interface TMessageSpamMatchResultType {
|
||||
archiveId: string;
|
||||
}
|
||||
|
||||
export function createMessageSpamTrigger(spamType: RecentActionType, prettyName: string) {
|
||||
return automodTrigger({
|
||||
return automodTrigger<TMessageSpamMatchResultType>()({
|
||||
configType: MessageSpamTriggerConfig,
|
||||
defaultConfig: {},
|
||||
|
||||
matchResultType: MessageSpamMatchResultType,
|
||||
|
||||
async match({ pluginData, context, triggerConfig }) {
|
||||
if (!context.message) {
|
||||
return;
|
||||
|
|
|
@ -13,18 +13,9 @@ type TextTriggerWithMultipleMatchTypes = {
|
|||
match_custom_status: boolean;
|
||||
};
|
||||
|
||||
export const MatchableTextType = t.union([
|
||||
t.literal("message"),
|
||||
t.literal("embed"),
|
||||
t.literal("visiblename"),
|
||||
t.literal("username"),
|
||||
t.literal("nickname"),
|
||||
t.literal("customstatus"),
|
||||
]);
|
||||
export type MatchableTextType = "message" | "embed" | "visiblename" | "username" | "nickname" | "customstatus";
|
||||
|
||||
export type TMatchableTextType = t.TypeOf<typeof MatchableTextType>;
|
||||
|
||||
type YieldedContent = [TMatchableTextType, string];
|
||||
type YieldedContent = [MatchableTextType, string];
|
||||
|
||||
/**
|
||||
* Generator function that allows iterating through matchable pieces of text of a SavedMessage
|
||||
|
|
|
@ -7,6 +7,7 @@ import { CleanAction } from "../actions/clean";
|
|||
|
||||
export async function runAutomod(pluginData: PluginData<AutomodPluginType>, context: AutomodContext) {
|
||||
const userId = context.user?.id || context.message?.user_id;
|
||||
const user = userId && pluginData.client.users.get(userId);
|
||||
const member = userId && pluginData.guild.members.get(userId);
|
||||
const channelId = context.message?.channel_id;
|
||||
const channel = channelId && pluginData.guild.channels.get(channelId);
|
||||
|
@ -21,6 +22,7 @@ export async function runAutomod(pluginData: PluginData<AutomodPluginType>, cont
|
|||
|
||||
for (const [ruleName, rule] of Object.entries(config.rules)) {
|
||||
if (rule.enabled === false) continue;
|
||||
if (!rule.affects_bots && user.bot) continue;
|
||||
|
||||
let matchResult: AutomodTriggerMatchResult<any>;
|
||||
let matchSummary: string;
|
||||
|
|
|
@ -25,20 +25,28 @@ type AutomodTriggerRenderMatchInformationFn<TConfigType, TMatchResultExtra> = (m
|
|||
matchResult: AutomodTriggerMatchResult<TMatchResultExtra>;
|
||||
}) => Awaitable<string>;
|
||||
|
||||
export interface AutomodTriggerBlueprint<TConfigType extends t.Any, TMatchResultExtra extends t.Any> {
|
||||
export interface AutomodTriggerBlueprint<TConfigType extends t.Any, TMatchResultExtra> {
|
||||
configType: TConfigType;
|
||||
defaultConfig: Partial<t.TypeOf<TConfigType>>;
|
||||
|
||||
matchResultType: TMatchResultExtra;
|
||||
|
||||
match: AutomodTriggerMatchFn<t.TypeOf<TConfigType>, t.TypeOf<TMatchResultExtra>>;
|
||||
renderMatchInformation: AutomodTriggerRenderMatchInformationFn<t.TypeOf<TConfigType>, t.TypeOf<TMatchResultExtra>>;
|
||||
match: AutomodTriggerMatchFn<t.TypeOf<TConfigType>, TMatchResultExtra>;
|
||||
renderMatchInformation: AutomodTriggerRenderMatchInformationFn<t.TypeOf<TConfigType>, TMatchResultExtra>;
|
||||
}
|
||||
|
||||
export function automodTrigger<TConfigType extends t.Any, TMatchResultExtra extends t.Any>(
|
||||
export function automodTrigger<TMatchResultExtra>(): <TConfigType extends t.Any>(
|
||||
blueprint: AutomodTriggerBlueprint<TConfigType, TMatchResultExtra>,
|
||||
): AutomodTriggerBlueprint<TConfigType, TMatchResultExtra> {
|
||||
return blueprint;
|
||||
) => AutomodTriggerBlueprint<TConfigType, TMatchResultExtra>;
|
||||
|
||||
export function automodTrigger<TConfigType extends t.Any>(
|
||||
blueprint: AutomodTriggerBlueprint<TConfigType, unknown>,
|
||||
): AutomodTriggerBlueprint<TConfigType, unknown>;
|
||||
|
||||
export function automodTrigger(...args) {
|
||||
if (args.length) {
|
||||
return args[0];
|
||||
} else {
|
||||
return automodTrigger;
|
||||
}
|
||||
}
|
||||
|
||||
type AutomodActionApplyFn<TConfigType> = (meta: {
|
||||
|
|
|
@ -9,10 +9,16 @@ import { EmojiSpamTrigger } from "./emojiSpam";
|
|||
import { LineSpamTrigger } from "./lineSpam";
|
||||
import { CharacterSpamTrigger } from "./characterSpam";
|
||||
import { MatchRegexTrigger } from "./matchRegex";
|
||||
import { MatchInvitesTrigger } from "./matchInvites";
|
||||
import { MatchLinksTrigger } from "./matchLinks";
|
||||
import { MatchAttachmentTypeTrigger } from "./matchAttachmentType";
|
||||
|
||||
export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>> = {
|
||||
match_words: MatchWordsTrigger,
|
||||
match_regex: MatchRegexTrigger,
|
||||
match_invites: MatchInvitesTrigger,
|
||||
match_links: MatchLinksTrigger,
|
||||
match_attachment_type: MatchAttachmentTypeTrigger,
|
||||
|
||||
message_spam: MessageSpamTrigger,
|
||||
mention_spam: MentionSpamTrigger,
|
||||
|
@ -26,6 +32,9 @@ export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>
|
|||
export const AvailableTriggers = t.type({
|
||||
match_words: MatchWordsTrigger.configType,
|
||||
match_regex: MatchRegexTrigger.configType,
|
||||
match_invites: MatchInvitesTrigger.configType,
|
||||
match_links: MatchLinksTrigger.configType,
|
||||
match_attachment_type: MatchAttachmentTypeTrigger.configType,
|
||||
|
||||
message_spam: MessageSpamTrigger.configType,
|
||||
mention_spam: MentionSpamTrigger.configType,
|
||||
|
|
|
@ -1,27 +1,31 @@
|
|||
import * as t from "io-ts";
|
||||
import { automodTrigger } from "../helpers";
|
||||
|
||||
export const ExampleTrigger = automodTrigger({
|
||||
interface ExampleMatchResultType {
|
||||
isBanana: boolean;
|
||||
}
|
||||
|
||||
export const ExampleTrigger = automodTrigger<ExampleMatchResultType>()({
|
||||
configType: t.type({
|
||||
some: t.number,
|
||||
value: t.string,
|
||||
allowedFruits: t.array(t.string),
|
||||
}),
|
||||
|
||||
defaultConfig: {},
|
||||
|
||||
matchResultType: t.type({
|
||||
thing: t.string,
|
||||
}),
|
||||
|
||||
async match() {
|
||||
return {
|
||||
extra: {
|
||||
thing: "hi",
|
||||
},
|
||||
};
|
||||
defaultConfig: {
|
||||
allowedFruits: ["peach", "banana"],
|
||||
},
|
||||
|
||||
renderMatchInformation() {
|
||||
return "";
|
||||
async match({ triggerConfig, context }) {
|
||||
const foundFruit = triggerConfig.allowedFruits.find(fruit => context.message?.data.content === fruit);
|
||||
if (foundFruit) {
|
||||
return {
|
||||
extra: {
|
||||
isBanana: foundFruit === "banana",
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
renderMatchInformation({ matchResult }) {
|
||||
return `Matched fruit, isBanana: ${matchResult.extra.isBanana ? "yes" : "no"}`;
|
||||
},
|
||||
});
|
||||
|
|
86
backend/src/plugins/Automod/triggers/matchAttachmentType.ts
Normal file
86
backend/src/plugins/Automod/triggers/matchAttachmentType.ts
Normal file
|
@ -0,0 +1,86 @@
|
|||
import * as t from "io-ts";
|
||||
import { transliterate } from "transliteration";
|
||||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import { AnyInvite, Attachment, GuildInvite } from "eris";
|
||||
import { automodTrigger } from "../helpers";
|
||||
import {
|
||||
asSingleLine,
|
||||
disableCodeBlocks,
|
||||
disableInlineCode,
|
||||
getInviteCodesInString,
|
||||
isGuildInvite,
|
||||
resolveInvite,
|
||||
tNullable,
|
||||
verboseChannelMention,
|
||||
} from "../../../utils";
|
||||
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
||||
|
||||
interface MatchResultType {
|
||||
matchedType: string;
|
||||
mode: "blacklist" | "whitelist";
|
||||
}
|
||||
|
||||
export const MatchAttachmentTypeTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
filetype_blacklist: t.array(t.string),
|
||||
blacklist_enabled: t.boolean,
|
||||
filetype_whitelist: t.array(t.string),
|
||||
whitelist_enabled: t.boolean,
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
filetype_blacklist: [],
|
||||
blacklist_enabled: false,
|
||||
filetype_whitelist: [],
|
||||
whitelist_enabled: false,
|
||||
},
|
||||
|
||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!context.message.data.attachments) return null;
|
||||
const attachments: any[] = context.message.data.attachments;
|
||||
|
||||
for (const attachment of attachments) {
|
||||
const attachmentType = attachment.filename.split(`.`).pop();
|
||||
|
||||
if (trigger.blacklist_enabled && trigger.filetype_blacklist.includes(attachmentType)) {
|
||||
return {
|
||||
extra: {
|
||||
matchedType: attachmentType,
|
||||
mode: "blacklist",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (trigger.whitelist_enabled && !trigger.filetype_whitelist.includes(attachmentType)) {
|
||||
return {
|
||||
extra: {
|
||||
matchedType: attachmentType,
|
||||
mode: "whitelist",
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
renderMatchInformation({ pluginData, contexts, matchResult }) {
|
||||
const channel = pluginData.guild.channels.get(contexts[0].message.channel_id);
|
||||
const prettyChannel = verboseChannelMention(channel);
|
||||
|
||||
return (
|
||||
asSingleLine(`
|
||||
Matched attachment type \`${disableInlineCode(matchResult.extra.matchedType)}\`
|
||||
(${matchResult.extra.mode === "blacklist" ? "(blacklisted)" : "(not in whitelist)"})
|
||||
in message (\`${contexts[0].message.id}\`) in ${prettyChannel}:
|
||||
`) +
|
||||
"\n```" +
|
||||
disableCodeBlocks(contexts[0].message.data.content) +
|
||||
"```"
|
||||
);
|
||||
},
|
||||
});
|
105
backend/src/plugins/Automod/triggers/matchInvites.ts
Normal file
105
backend/src/plugins/Automod/triggers/matchInvites.ts
Normal file
|
@ -0,0 +1,105 @@
|
|||
import * as t from "io-ts";
|
||||
import { transliterate } from "transliteration";
|
||||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import { AnyInvite, GuildInvite } from "eris";
|
||||
import { automodTrigger } from "../helpers";
|
||||
import {
|
||||
asSingleLine,
|
||||
disableCodeBlocks,
|
||||
disableInlineCode,
|
||||
getInviteCodesInString,
|
||||
isGuildInvite,
|
||||
resolveInvite,
|
||||
tNullable,
|
||||
verboseChannelMention,
|
||||
} from "../../../utils";
|
||||
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
||||
|
||||
interface MatchResultType {
|
||||
type: MatchableTextType;
|
||||
code: string;
|
||||
invite?: GuildInvite;
|
||||
}
|
||||
|
||||
export const MatchInvitesTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
include_guilds: tNullable(t.array(t.string)),
|
||||
exclude_guilds: tNullable(t.array(t.string)),
|
||||
include_invite_codes: tNullable(t.array(t.string)),
|
||||
exclude_invite_codes: tNullable(t.array(t.string)),
|
||||
allow_group_dm_invites: t.boolean,
|
||||
match_messages: t.boolean,
|
||||
match_embeds: t.boolean,
|
||||
match_visible_names: t.boolean,
|
||||
match_usernames: t.boolean,
|
||||
match_nicknames: t.boolean,
|
||||
match_custom_status: t.boolean,
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
allow_group_dm_invites: false,
|
||||
match_messages: true,
|
||||
match_embeds: true,
|
||||
match_visible_names: false,
|
||||
match_usernames: false,
|
||||
match_nicknames: false,
|
||||
match_custom_status: false,
|
||||
},
|
||||
|
||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
for await (const [type, str] of matchMultipleTextTypesOnMessage(pluginData, trigger, context.message)) {
|
||||
const inviteCodes = getInviteCodesInString(str);
|
||||
if (inviteCodes.length === 0) return null;
|
||||
|
||||
const uniqueInviteCodes = Array.from(new Set(inviteCodes));
|
||||
|
||||
for (const code of uniqueInviteCodes) {
|
||||
if (trigger.include_invite_codes && trigger.include_invite_codes.includes(code)) {
|
||||
return { extra: { type, code } };
|
||||
}
|
||||
if (trigger.exclude_invite_codes && !trigger.exclude_invite_codes.includes(code)) {
|
||||
return { extra: { type, code } };
|
||||
}
|
||||
}
|
||||
|
||||
for (const code of uniqueInviteCodes) {
|
||||
const invite = await resolveInvite(pluginData.client, code);
|
||||
if (!invite || !isGuildInvite(invite)) return { code };
|
||||
|
||||
if (trigger.include_guilds && trigger.include_guilds.includes(invite.guild.id)) {
|
||||
return { extra: { type, code, invite } };
|
||||
}
|
||||
if (trigger.exclude_guilds && !trigger.exclude_guilds.includes(invite.guild.id)) {
|
||||
return { extra: { type, code, invite } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
renderMatchInformation({ pluginData, contexts, matchResult }) {
|
||||
const channel = pluginData.guild.channels.get(contexts[0].message.channel_id);
|
||||
const prettyChannel = verboseChannelMention(channel);
|
||||
|
||||
let matchedText;
|
||||
|
||||
if (matchResult.extra.invite) {
|
||||
const invite = matchResult.extra.invite as GuildInvite;
|
||||
matchedText = `invite code \`${matchResult.extra.code}\` (**${invite.guild.name}**, \`${invite.guild.id}\`)`;
|
||||
} else {
|
||||
matchedText = `invite code \`${matchResult.extra.code}\``;
|
||||
}
|
||||
|
||||
return (
|
||||
`${matchedText} in message (\`${contexts[0].message.id}\`) in ${prettyChannel}:\n` +
|
||||
"```" +
|
||||
disableCodeBlocks(contexts[0].message.data.content) +
|
||||
"```"
|
||||
);
|
||||
},
|
||||
});
|
152
backend/src/plugins/Automod/triggers/matchLinks.ts
Normal file
152
backend/src/plugins/Automod/triggers/matchLinks.ts
Normal file
|
@ -0,0 +1,152 @@
|
|||
import * as t from "io-ts";
|
||||
import { transliterate } from "transliteration";
|
||||
import escapeStringRegexp from "escape-string-regexp";
|
||||
import { AnyInvite, GuildInvite } from "eris";
|
||||
import { automodTrigger } from "../helpers";
|
||||
import {
|
||||
asSingleLine,
|
||||
disableCodeBlocks,
|
||||
disableInlineCode,
|
||||
getInviteCodesInString,
|
||||
getUrlsInString,
|
||||
isGuildInvite,
|
||||
resolveInvite,
|
||||
tNullable,
|
||||
verboseChannelMention,
|
||||
} from "../../../utils";
|
||||
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
||||
import { TSafeRegex } from "../../../validatorUtils";
|
||||
|
||||
interface MatchResultType {
|
||||
type: MatchableTextType;
|
||||
link: string;
|
||||
}
|
||||
|
||||
export const MatchLinksTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
include_domains: tNullable(t.array(t.string)),
|
||||
exclude_domains: tNullable(t.array(t.string)),
|
||||
include_subdomains: t.boolean,
|
||||
include_words: tNullable(t.array(t.string)),
|
||||
exclude_words: tNullable(t.array(t.string)),
|
||||
include_regex: tNullable(t.array(TSafeRegex)),
|
||||
exclude_regex: tNullable(t.array(TSafeRegex)),
|
||||
only_real_links: t.boolean,
|
||||
match_messages: t.boolean,
|
||||
match_embeds: t.boolean,
|
||||
match_visible_names: t.boolean,
|
||||
match_usernames: t.boolean,
|
||||
match_nicknames: t.boolean,
|
||||
match_custom_status: t.boolean,
|
||||
}),
|
||||
|
||||
defaultConfig: {
|
||||
include_subdomains: true,
|
||||
match_messages: true,
|
||||
match_embeds: true,
|
||||
match_visible_names: false,
|
||||
match_usernames: false,
|
||||
match_nicknames: false,
|
||||
match_custom_status: false,
|
||||
only_real_links: true,
|
||||
},
|
||||
|
||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
return;
|
||||
}
|
||||
|
||||
typeLoop: for await (const [type, str] of matchMultipleTextTypesOnMessage(pluginData, trigger, context.message)) {
|
||||
const links = getUrlsInString(str, true);
|
||||
|
||||
for (const link of links) {
|
||||
// "real link" = a link that Discord highlights
|
||||
if (trigger.only_real_links && !link.input.match(/^https?:\/\//i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const normalizedHostname = link.hostname.toLowerCase();
|
||||
|
||||
// Exclude > Include
|
||||
// In order of specificity, regex > word > domain
|
||||
|
||||
if (trigger.exclude_regex) {
|
||||
for (const pattern of trigger.exclude_regex) {
|
||||
if (pattern.test(link.input)) {
|
||||
continue typeLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trigger.include_regex) {
|
||||
for (const pattern of trigger.include_regex) {
|
||||
if (pattern.test(link.input)) {
|
||||
return { extra: { type, link: link.input } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trigger.exclude_words) {
|
||||
for (const word of trigger.exclude_words) {
|
||||
const regex = new RegExp(escapeStringRegexp(word), "i");
|
||||
if (regex.test(link.input)) {
|
||||
continue typeLoop;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trigger.include_words) {
|
||||
for (const word of trigger.include_words) {
|
||||
const regex = new RegExp(escapeStringRegexp(word), "i");
|
||||
if (regex.test(link.input)) {
|
||||
return { extra: { type, link: link.input } };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trigger.exclude_domains) {
|
||||
for (const domain of trigger.exclude_domains) {
|
||||
const normalizedDomain = domain.toLowerCase();
|
||||
if (normalizedDomain === normalizedHostname) {
|
||||
continue typeLoop;
|
||||
}
|
||||
if (trigger.include_subdomains && normalizedHostname.endsWith(`.${domain}`)) {
|
||||
continue typeLoop;
|
||||
}
|
||||
}
|
||||
|
||||
return { extra: { type, link: link.toString() } };
|
||||
}
|
||||
|
||||
if (trigger.include_domains) {
|
||||
for (const domain of trigger.include_domains) {
|
||||
const normalizedDomain = domain.toLowerCase();
|
||||
if (normalizedDomain === normalizedHostname) {
|
||||
return { extra: { type, link: domain } };
|
||||
}
|
||||
if (trigger.include_subdomains && normalizedHostname.endsWith(`.${domain}`)) {
|
||||
return { extra: { type, link: domain } };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
renderMatchInformation({ pluginData, contexts, matchResult }) {
|
||||
const channel = pluginData.guild.channels.get(contexts[0].message.channel_id);
|
||||
const prettyChannel = verboseChannelMention(channel);
|
||||
|
||||
return (
|
||||
asSingleLine(`
|
||||
Matched link \`${disableInlineCode(matchResult.extra.link)}\`
|
||||
in message (\`${contexts[0].message.id}\`) in ${prettyChannel}:
|
||||
`) +
|
||||
"\n```" +
|
||||
disableCodeBlocks(contexts[0].message.data.content) +
|
||||
"```"
|
||||
);
|
||||
},
|
||||
});
|
|
@ -4,10 +4,16 @@ import escapeStringRegexp from "escape-string-regexp";
|
|||
import { automodTrigger } from "../helpers";
|
||||
import { disableInlineCode, verboseChannelMention } from "../../../utils";
|
||||
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
||||
import { TSafeRegex } from "../../../validatorUtils";
|
||||
|
||||
export const MatchRegexTrigger = automodTrigger({
|
||||
interface MatchResultType {
|
||||
pattern: string;
|
||||
type: MatchableTextType;
|
||||
}
|
||||
|
||||
export const MatchRegexTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
patterns: t.array(t.string),
|
||||
patterns: t.array(TSafeRegex),
|
||||
case_sensitive: t.boolean,
|
||||
normalize: t.boolean,
|
||||
match_messages: t.boolean,
|
||||
|
@ -29,11 +35,6 @@ export const MatchRegexTrigger = automodTrigger({
|
|||
match_custom_status: false,
|
||||
},
|
||||
|
||||
matchResultType: t.type({
|
||||
pattern: t.string,
|
||||
type: MatchableTextType,
|
||||
}),
|
||||
|
||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
return;
|
||||
|
@ -44,13 +45,13 @@ export const MatchRegexTrigger = automodTrigger({
|
|||
str = transliterate(str);
|
||||
}
|
||||
|
||||
for (const pattern of trigger.patterns) {
|
||||
const regex = new RegExp(pattern, trigger.case_sensitive ? "" : "i");
|
||||
for (const sourceRegex of trigger.patterns) {
|
||||
const regex = new RegExp(sourceRegex.source, trigger.case_sensitive ? "" : "i");
|
||||
const test = regex.test(str);
|
||||
if (test) {
|
||||
return {
|
||||
extra: {
|
||||
pattern,
|
||||
pattern: sourceRegex.source,
|
||||
type,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -5,7 +5,12 @@ import { automodTrigger } from "../helpers";
|
|||
import { disableInlineCode, verboseChannelMention } from "../../../utils";
|
||||
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
||||
|
||||
export const MatchWordsTrigger = automodTrigger({
|
||||
interface MatchResultType {
|
||||
word: string;
|
||||
type: MatchableTextType;
|
||||
}
|
||||
|
||||
export const MatchWordsTrigger = automodTrigger<MatchResultType>()({
|
||||
configType: t.type({
|
||||
words: t.array(t.string),
|
||||
case_sensitive: t.boolean,
|
||||
|
@ -35,11 +40,6 @@ export const MatchWordsTrigger = automodTrigger({
|
|||
match_custom_status: false,
|
||||
},
|
||||
|
||||
matchResultType: t.type({
|
||||
word: t.string,
|
||||
type: MatchableTextType,
|
||||
}),
|
||||
|
||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||
if (!context.message) {
|
||||
return;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {
|
||||
AnyInvite,
|
||||
Attachment,
|
||||
ChannelInvite,
|
||||
Client,
|
||||
|
@ -9,6 +10,7 @@ import {
|
|||
GuildAuditLog,
|
||||
GuildAuditLogEntry,
|
||||
GuildChannel,
|
||||
GuildInvite,
|
||||
Member,
|
||||
Message,
|
||||
MessageContent,
|
||||
|
@ -1216,3 +1218,7 @@ export function trimPluginDescription(str) {
|
|||
export function isFullMessage(msg: PossiblyUncachedMessage): msg is Message {
|
||||
return (msg as Message).createdAt != null;
|
||||
}
|
||||
|
||||
export function isGuildInvite(invite: AnyInvite): invite is GuildInvite {
|
||||
return (invite as GuildInvite).guild != null;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue