mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-03-16 22:21:51 +00:00
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>;
|
type TMessageSpamTriggerConfig = t.TypeOf<typeof MessageSpamTriggerConfig>;
|
||||||
|
|
||||||
const MessageSpamMatchResultType = t.type({
|
interface TMessageSpamMatchResultType {
|
||||||
archiveId: t.string,
|
archiveId: string;
|
||||||
});
|
}
|
||||||
type TMessageSpamMatchResultType = t.TypeOf<typeof MessageSpamMatchResultType>;
|
|
||||||
|
|
||||||
export function createMessageSpamTrigger(spamType: RecentActionType, prettyName: string) {
|
export function createMessageSpamTrigger(spamType: RecentActionType, prettyName: string) {
|
||||||
return automodTrigger({
|
return automodTrigger<TMessageSpamMatchResultType>()({
|
||||||
configType: MessageSpamTriggerConfig,
|
configType: MessageSpamTriggerConfig,
|
||||||
defaultConfig: {},
|
defaultConfig: {},
|
||||||
|
|
||||||
matchResultType: MessageSpamMatchResultType,
|
|
||||||
|
|
||||||
async match({ pluginData, context, triggerConfig }) {
|
async match({ pluginData, context, triggerConfig }) {
|
||||||
if (!context.message) {
|
if (!context.message) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -13,18 +13,9 @@ type TextTriggerWithMultipleMatchTypes = {
|
||||||
match_custom_status: boolean;
|
match_custom_status: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MatchableTextType = t.union([
|
export type MatchableTextType = "message" | "embed" | "visiblename" | "username" | "nickname" | "customstatus";
|
||||||
t.literal("message"),
|
|
||||||
t.literal("embed"),
|
|
||||||
t.literal("visiblename"),
|
|
||||||
t.literal("username"),
|
|
||||||
t.literal("nickname"),
|
|
||||||
t.literal("customstatus"),
|
|
||||||
]);
|
|
||||||
|
|
||||||
export type TMatchableTextType = t.TypeOf<typeof MatchableTextType>;
|
type YieldedContent = [MatchableTextType, string];
|
||||||
|
|
||||||
type YieldedContent = [TMatchableTextType, string];
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generator function that allows iterating through matchable pieces of text of a SavedMessage
|
* 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) {
|
export async function runAutomod(pluginData: PluginData<AutomodPluginType>, context: AutomodContext) {
|
||||||
const userId = context.user?.id || context.message?.user_id;
|
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 member = userId && pluginData.guild.members.get(userId);
|
||||||
const channelId = context.message?.channel_id;
|
const channelId = context.message?.channel_id;
|
||||||
const channel = channelId && pluginData.guild.channels.get(channelId);
|
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)) {
|
for (const [ruleName, rule] of Object.entries(config.rules)) {
|
||||||
if (rule.enabled === false) continue;
|
if (rule.enabled === false) continue;
|
||||||
|
if (!rule.affects_bots && user.bot) continue;
|
||||||
|
|
||||||
let matchResult: AutomodTriggerMatchResult<any>;
|
let matchResult: AutomodTriggerMatchResult<any>;
|
||||||
let matchSummary: string;
|
let matchSummary: string;
|
||||||
|
|
|
@ -25,20 +25,28 @@ type AutomodTriggerRenderMatchInformationFn<TConfigType, TMatchResultExtra> = (m
|
||||||
matchResult: AutomodTriggerMatchResult<TMatchResultExtra>;
|
matchResult: AutomodTriggerMatchResult<TMatchResultExtra>;
|
||||||
}) => Awaitable<string>;
|
}) => Awaitable<string>;
|
||||||
|
|
||||||
export interface AutomodTriggerBlueprint<TConfigType extends t.Any, TMatchResultExtra extends t.Any> {
|
export interface AutomodTriggerBlueprint<TConfigType extends t.Any, TMatchResultExtra> {
|
||||||
configType: TConfigType;
|
configType: TConfigType;
|
||||||
defaultConfig: Partial<t.TypeOf<TConfigType>>;
|
defaultConfig: Partial<t.TypeOf<TConfigType>>;
|
||||||
|
|
||||||
matchResultType: TMatchResultExtra;
|
match: AutomodTriggerMatchFn<t.TypeOf<TConfigType>, TMatchResultExtra>;
|
||||||
|
renderMatchInformation: AutomodTriggerRenderMatchInformationFn<t.TypeOf<TConfigType>, TMatchResultExtra>;
|
||||||
match: AutomodTriggerMatchFn<t.TypeOf<TConfigType>, t.TypeOf<TMatchResultExtra>>;
|
|
||||||
renderMatchInformation: AutomodTriggerRenderMatchInformationFn<t.TypeOf<TConfigType>, t.TypeOf<TMatchResultExtra>>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function automodTrigger<TConfigType extends t.Any, TMatchResultExtra extends t.Any>(
|
export function automodTrigger<TMatchResultExtra>(): <TConfigType extends t.Any>(
|
||||||
blueprint: AutomodTriggerBlueprint<TConfigType, TMatchResultExtra>,
|
blueprint: AutomodTriggerBlueprint<TConfigType, TMatchResultExtra>,
|
||||||
): AutomodTriggerBlueprint<TConfigType, TMatchResultExtra> {
|
) => AutomodTriggerBlueprint<TConfigType, TMatchResultExtra>;
|
||||||
return blueprint;
|
|
||||||
|
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: {
|
type AutomodActionApplyFn<TConfigType> = (meta: {
|
||||||
|
|
|
@ -9,10 +9,16 @@ import { EmojiSpamTrigger } from "./emojiSpam";
|
||||||
import { LineSpamTrigger } from "./lineSpam";
|
import { LineSpamTrigger } from "./lineSpam";
|
||||||
import { CharacterSpamTrigger } from "./characterSpam";
|
import { CharacterSpamTrigger } from "./characterSpam";
|
||||||
import { MatchRegexTrigger } from "./matchRegex";
|
import { MatchRegexTrigger } from "./matchRegex";
|
||||||
|
import { MatchInvitesTrigger } from "./matchInvites";
|
||||||
|
import { MatchLinksTrigger } from "./matchLinks";
|
||||||
|
import { MatchAttachmentTypeTrigger } from "./matchAttachmentType";
|
||||||
|
|
||||||
export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>> = {
|
export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>> = {
|
||||||
match_words: MatchWordsTrigger,
|
match_words: MatchWordsTrigger,
|
||||||
match_regex: MatchRegexTrigger,
|
match_regex: MatchRegexTrigger,
|
||||||
|
match_invites: MatchInvitesTrigger,
|
||||||
|
match_links: MatchLinksTrigger,
|
||||||
|
match_attachment_type: MatchAttachmentTypeTrigger,
|
||||||
|
|
||||||
message_spam: MessageSpamTrigger,
|
message_spam: MessageSpamTrigger,
|
||||||
mention_spam: MentionSpamTrigger,
|
mention_spam: MentionSpamTrigger,
|
||||||
|
@ -26,6 +32,9 @@ export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>
|
||||||
export const AvailableTriggers = t.type({
|
export const AvailableTriggers = t.type({
|
||||||
match_words: MatchWordsTrigger.configType,
|
match_words: MatchWordsTrigger.configType,
|
||||||
match_regex: MatchRegexTrigger.configType,
|
match_regex: MatchRegexTrigger.configType,
|
||||||
|
match_invites: MatchInvitesTrigger.configType,
|
||||||
|
match_links: MatchLinksTrigger.configType,
|
||||||
|
match_attachment_type: MatchAttachmentTypeTrigger.configType,
|
||||||
|
|
||||||
message_spam: MessageSpamTrigger.configType,
|
message_spam: MessageSpamTrigger.configType,
|
||||||
mention_spam: MentionSpamTrigger.configType,
|
mention_spam: MentionSpamTrigger.configType,
|
||||||
|
|
|
@ -1,27 +1,31 @@
|
||||||
import * as t from "io-ts";
|
import * as t from "io-ts";
|
||||||
import { automodTrigger } from "../helpers";
|
import { automodTrigger } from "../helpers";
|
||||||
|
|
||||||
export const ExampleTrigger = automodTrigger({
|
interface ExampleMatchResultType {
|
||||||
|
isBanana: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExampleTrigger = automodTrigger<ExampleMatchResultType>()({
|
||||||
configType: t.type({
|
configType: t.type({
|
||||||
some: t.number,
|
allowedFruits: t.array(t.string),
|
||||||
value: t.string,
|
|
||||||
}),
|
}),
|
||||||
|
|
||||||
defaultConfig: {},
|
defaultConfig: {
|
||||||
|
allowedFruits: ["peach", "banana"],
|
||||||
matchResultType: t.type({
|
|
||||||
thing: t.string,
|
|
||||||
}),
|
|
||||||
|
|
||||||
async match() {
|
|
||||||
return {
|
|
||||||
extra: {
|
|
||||||
thing: "hi",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
},
|
||||||
|
|
||||||
renderMatchInformation() {
|
async match({ triggerConfig, context }) {
|
||||||
return "";
|
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 { automodTrigger } from "../helpers";
|
||||||
import { disableInlineCode, verboseChannelMention } from "../../../utils";
|
import { disableInlineCode, verboseChannelMention } from "../../../utils";
|
||||||
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
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({
|
configType: t.type({
|
||||||
patterns: t.array(t.string),
|
patterns: t.array(TSafeRegex),
|
||||||
case_sensitive: t.boolean,
|
case_sensitive: t.boolean,
|
||||||
normalize: t.boolean,
|
normalize: t.boolean,
|
||||||
match_messages: t.boolean,
|
match_messages: t.boolean,
|
||||||
|
@ -29,11 +35,6 @@ export const MatchRegexTrigger = automodTrigger({
|
||||||
match_custom_status: false,
|
match_custom_status: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
matchResultType: t.type({
|
|
||||||
pattern: t.string,
|
|
||||||
type: MatchableTextType,
|
|
||||||
}),
|
|
||||||
|
|
||||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||||
if (!context.message) {
|
if (!context.message) {
|
||||||
return;
|
return;
|
||||||
|
@ -44,13 +45,13 @@ export const MatchRegexTrigger = automodTrigger({
|
||||||
str = transliterate(str);
|
str = transliterate(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const pattern of trigger.patterns) {
|
for (const sourceRegex of trigger.patterns) {
|
||||||
const regex = new RegExp(pattern, trigger.case_sensitive ? "" : "i");
|
const regex = new RegExp(sourceRegex.source, trigger.case_sensitive ? "" : "i");
|
||||||
const test = regex.test(str);
|
const test = regex.test(str);
|
||||||
if (test) {
|
if (test) {
|
||||||
return {
|
return {
|
||||||
extra: {
|
extra: {
|
||||||
pattern,
|
pattern: sourceRegex.source,
|
||||||
type,
|
type,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,7 +5,12 @@ import { automodTrigger } from "../helpers";
|
||||||
import { disableInlineCode, verboseChannelMention } from "../../../utils";
|
import { disableInlineCode, verboseChannelMention } from "../../../utils";
|
||||||
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
import { MatchableTextType, matchMultipleTextTypesOnMessage } from "../functions/matchMultipleTextTypesOnMessage";
|
||||||
|
|
||||||
export const MatchWordsTrigger = automodTrigger({
|
interface MatchResultType {
|
||||||
|
word: string;
|
||||||
|
type: MatchableTextType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const MatchWordsTrigger = automodTrigger<MatchResultType>()({
|
||||||
configType: t.type({
|
configType: t.type({
|
||||||
words: t.array(t.string),
|
words: t.array(t.string),
|
||||||
case_sensitive: t.boolean,
|
case_sensitive: t.boolean,
|
||||||
|
@ -35,11 +40,6 @@ export const MatchWordsTrigger = automodTrigger({
|
||||||
match_custom_status: false,
|
match_custom_status: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
matchResultType: t.type({
|
|
||||||
word: t.string,
|
|
||||||
type: MatchableTextType,
|
|
||||||
}),
|
|
||||||
|
|
||||||
async match({ pluginData, context, triggerConfig: trigger }) {
|
async match({ pluginData, context, triggerConfig: trigger }) {
|
||||||
if (!context.message) {
|
if (!context.message) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {
|
import {
|
||||||
|
AnyInvite,
|
||||||
Attachment,
|
Attachment,
|
||||||
ChannelInvite,
|
ChannelInvite,
|
||||||
Client,
|
Client,
|
||||||
|
@ -9,6 +10,7 @@ import {
|
||||||
GuildAuditLog,
|
GuildAuditLog,
|
||||||
GuildAuditLogEntry,
|
GuildAuditLogEntry,
|
||||||
GuildChannel,
|
GuildChannel,
|
||||||
|
GuildInvite,
|
||||||
Member,
|
Member,
|
||||||
Message,
|
Message,
|
||||||
MessageContent,
|
MessageContent,
|
||||||
|
@ -1216,3 +1218,7 @@ export function trimPluginDescription(str) {
|
||||||
export function isFullMessage(msg: PossiblyUncachedMessage): msg is Message {
|
export function isFullMessage(msg: PossiblyUncachedMessage): msg is Message {
|
||||||
return (msg as Message).createdAt != null;
|
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