automod: add role_added and role_removed triggers
This commit is contained in:
parent
bfa9cf55a7
commit
4c7a51f586
9 changed files with 187 additions and 4 deletions
|
@ -27,6 +27,7 @@ import { RegExpRunner } from "../../RegExpRunner";
|
||||||
import { LogType } from "../../data/LogType";
|
import { LogType } from "../../data/LogType";
|
||||||
import { logger } from "../../logger";
|
import { logger } from "../../logger";
|
||||||
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
|
import { discardRegExpRunner, getRegExpRunner } from "../../regExpRunners";
|
||||||
|
import { RunAutomodOnMemberUpdate } from "./events/RunAutomodOnMemberUpdate";
|
||||||
|
|
||||||
const defaultOptions = {
|
const defaultOptions = {
|
||||||
config: {
|
config: {
|
||||||
|
@ -83,10 +84,15 @@ const configPreprocessor: ConfigPreprocessorFn<AutomodPluginType> = options => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const triggerBlueprint = availableTriggers[triggerName];
|
const triggerBlueprint = availableTriggers[triggerName];
|
||||||
|
|
||||||
|
if (typeof triggerBlueprint.defaultConfig === "object" && triggerBlueprint.defaultConfig != null) {
|
||||||
triggerObj[triggerName] = configUtils.mergeConfig(
|
triggerObj[triggerName] = configUtils.mergeConfig(
|
||||||
triggerBlueprint.defaultConfig,
|
triggerBlueprint.defaultConfig,
|
||||||
triggerObj[triggerName] || {},
|
triggerObj[triggerName] || {},
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
triggerObj[triggerName] = triggerObj[triggerName] || triggerBlueprint.defaultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
if (triggerObj[triggerName].match_attachment_type) {
|
if (triggerObj[triggerName].match_attachment_type) {
|
||||||
const white = triggerObj[triggerName].match_attachment_type.whitelist_enabled;
|
const white = triggerObj[triggerName].match_attachment_type.whitelist_enabled;
|
||||||
|
@ -157,6 +163,7 @@ export const AutomodPlugin = zeppelinPlugin<AutomodPluginType>()("automod", {
|
||||||
|
|
||||||
events: [
|
events: [
|
||||||
RunAutomodOnJoinEvt,
|
RunAutomodOnJoinEvt,
|
||||||
|
RunAutomodOnMemberUpdate,
|
||||||
// Messages use message events from SavedMessages, see onLoad below
|
// Messages use message events from SavedMessages, see onLoad below
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -179,6 +186,8 @@ export const AutomodPlugin = zeppelinPlugin<AutomodPluginType>()("automod", {
|
||||||
30 * SECONDS,
|
30 * SECONDS,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
pluginData.state.ignoredRoleChanges = new Set();
|
||||||
|
|
||||||
pluginData.state.cooldownManager = new CooldownManager();
|
pluginData.state.cooldownManager = new CooldownManager();
|
||||||
|
|
||||||
pluginData.state.logs = new GuildLogs(pluginData.guild.id);
|
pluginData.state.logs = new GuildLogs(pluginData.guild.id);
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||||
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
|
import { getMissingPermissions } from "../../../utils/getMissingPermissions";
|
||||||
import { canAssignRole } from "../../../utils/canAssignRole";
|
import { canAssignRole } from "../../../utils/canAssignRole";
|
||||||
import { missingPermissionError } from "../../../utils/missingPermissionError";
|
import { missingPermissionError } from "../../../utils/missingPermissionError";
|
||||||
|
import { ignoreRoleChange } from "../functions/ignoredRoleChanges";
|
||||||
|
|
||||||
const p = Constants.Permissions;
|
const p = Constants.Permissions;
|
||||||
|
|
||||||
|
@ -55,6 +56,7 @@ export const AddRolesAction = automodAction({
|
||||||
const memberRoles = new Set(member.roles);
|
const memberRoles = new Set(member.roles);
|
||||||
for (const roleId of rolesToAssign) {
|
for (const roleId of rolesToAssign) {
|
||||||
memberRoles.add(roleId);
|
memberRoles.add(roleId);
|
||||||
|
ignoreRoleChange(pluginData, member.id, roleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memberRoles.size === member.roles.length) {
|
if (memberRoles.size === member.roles.length) {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { LogsPlugin } from "../../Logs/LogsPlugin";
|
||||||
import { missingPermissionError } from "../../../utils/missingPermissionError";
|
import { missingPermissionError } from "../../../utils/missingPermissionError";
|
||||||
import { canAssignRole } from "../../../utils/canAssignRole";
|
import { canAssignRole } from "../../../utils/canAssignRole";
|
||||||
import { Constants } from "eris";
|
import { Constants } from "eris";
|
||||||
|
import { ignoreRoleChange } from "../functions/ignoredRoleChanges";
|
||||||
|
|
||||||
const p = Constants.Permissions;
|
const p = Constants.Permissions;
|
||||||
|
|
||||||
|
@ -57,6 +58,7 @@ export const RemoveRolesAction = automodAction({
|
||||||
const memberRoles = new Set(member.roles);
|
const memberRoles = new Set(member.roles);
|
||||||
for (const roleId of rolesToRemove) {
|
for (const roleId of rolesToRemove) {
|
||||||
memberRoles.delete(roleId);
|
memberRoles.delete(roleId);
|
||||||
|
ignoreRoleChange(pluginData, member.id, roleId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (memberRoles.size === member.roles.length) {
|
if (memberRoles.size === member.roles.length) {
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
import { eventListener } from "knub";
|
||||||
|
import { AutomodContext, AutomodPluginType } from "../types";
|
||||||
|
import { RecentActionType } from "../constants";
|
||||||
|
import { runAutomod } from "../functions/runAutomod";
|
||||||
|
import isEqual from "lodash.isequal";
|
||||||
|
import diff from "lodash.difference";
|
||||||
|
|
||||||
|
export const RunAutomodOnMemberUpdate = eventListener<AutomodPluginType>()(
|
||||||
|
"guildMemberUpdate",
|
||||||
|
({ pluginData, args: { member, oldMember } }) => {
|
||||||
|
if (!oldMember) return;
|
||||||
|
|
||||||
|
if (isEqual(oldMember.roles, member.roles)) return;
|
||||||
|
|
||||||
|
const addedRoles = diff(member.roles, oldMember.roles);
|
||||||
|
const removedRoles = diff(oldMember.roles, member.roles);
|
||||||
|
|
||||||
|
if (addedRoles.length || removedRoles.length) {
|
||||||
|
const context: AutomodContext = {
|
||||||
|
timestamp: Date.now(),
|
||||||
|
user: member.user,
|
||||||
|
member,
|
||||||
|
rolesChanged: {
|
||||||
|
added: addedRoles,
|
||||||
|
removed: removedRoles,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pluginData.state.queue.add(() => {
|
||||||
|
runAutomod(pluginData, context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
38
backend/src/plugins/Automod/functions/ignoredRoleChanges.ts
Normal file
38
backend/src/plugins/Automod/functions/ignoredRoleChanges.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import { PluginData } from "knub";
|
||||||
|
import { AutomodPluginType } from "../types";
|
||||||
|
import { MINUTES } from "../../../utils";
|
||||||
|
|
||||||
|
const IGNORED_ROLE_CHANGE_LIFETIME = 5 * MINUTES;
|
||||||
|
|
||||||
|
function cleanupIgnoredRoleChanges(pluginData: PluginData<AutomodPluginType>) {
|
||||||
|
const cutoff = Date.now() - IGNORED_ROLE_CHANGE_LIFETIME;
|
||||||
|
for (const ignoredChange of pluginData.state.ignoredRoleChanges.values()) {
|
||||||
|
if (ignoredChange.timestamp < cutoff) {
|
||||||
|
pluginData.state.ignoredRoleChanges.delete(ignoredChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ignoreRoleChange(pluginData: PluginData<AutomodPluginType>, memberId: string, roleId: string) {
|
||||||
|
pluginData.state.ignoredRoleChanges.add({
|
||||||
|
memberId,
|
||||||
|
roleId,
|
||||||
|
timestamp: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
cleanupIgnoredRoleChanges(pluginData);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Whether the role change should be ignored
|
||||||
|
*/
|
||||||
|
export function consumeIgnoredRoleChange(pluginData: PluginData<AutomodPluginType>, memberId: string, roleId: string) {
|
||||||
|
for (const ignoredChange of pluginData.state.ignoredRoleChanges.values()) {
|
||||||
|
if (ignoredChange.memberId === memberId && ignoredChange.roleId === roleId) {
|
||||||
|
pluginData.state.ignoredRoleChanges.delete(ignoredChange);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
|
@ -14,6 +14,8 @@ import { MatchLinksTrigger } from "./matchLinks";
|
||||||
import { MatchAttachmentTypeTrigger } from "./matchAttachmentType";
|
import { MatchAttachmentTypeTrigger } from "./matchAttachmentType";
|
||||||
import { MemberJoinSpamTrigger } from "./memberJoinSpam";
|
import { MemberJoinSpamTrigger } from "./memberJoinSpam";
|
||||||
import { MemberJoinTrigger } from "./memberJoin";
|
import { MemberJoinTrigger } from "./memberJoin";
|
||||||
|
import { RoleAddedTrigger } from "./roleAdded";
|
||||||
|
import { RoleRemovedTrigger } from "./roleRemoved";
|
||||||
|
|
||||||
export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>> = {
|
export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>> = {
|
||||||
match_words: MatchWordsTrigger,
|
match_words: MatchWordsTrigger,
|
||||||
|
@ -22,6 +24,8 @@ export const availableTriggers: Record<string, AutomodTriggerBlueprint<any, any>
|
||||||
match_links: MatchLinksTrigger,
|
match_links: MatchLinksTrigger,
|
||||||
match_attachment_type: MatchAttachmentTypeTrigger,
|
match_attachment_type: MatchAttachmentTypeTrigger,
|
||||||
member_join: MemberJoinTrigger,
|
member_join: MemberJoinTrigger,
|
||||||
|
role_added: RoleAddedTrigger,
|
||||||
|
role_removed: RoleRemovedTrigger,
|
||||||
|
|
||||||
message_spam: MessageSpamTrigger,
|
message_spam: MessageSpamTrigger,
|
||||||
mention_spam: MentionSpamTrigger,
|
mention_spam: MentionSpamTrigger,
|
||||||
|
|
42
backend/src/plugins/Automod/triggers/roleAdded.ts
Normal file
42
backend/src/plugins/Automod/triggers/roleAdded.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import * as t from "io-ts";
|
||||||
|
import { automodTrigger } from "../helpers";
|
||||||
|
import { consumeIgnoredRoleChange } from "../functions/ignoredRoleChanges";
|
||||||
|
|
||||||
|
interface RoleAddedMatchResult {
|
||||||
|
matchedRoleId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RoleAddedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
||||||
|
configType: t.union([t.string, t.array(t.string)]),
|
||||||
|
|
||||||
|
defaultConfig: null,
|
||||||
|
|
||||||
|
async match({ triggerConfig, context, pluginData }) {
|
||||||
|
if (!context.member || !context.rolesChanged || context.rolesChanged.added.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggerRoles = Array.isArray(triggerConfig) ? triggerConfig : [triggerConfig];
|
||||||
|
for (const roleId of triggerRoles) {
|
||||||
|
if (context.rolesChanged.added.includes(roleId)) {
|
||||||
|
if (consumeIgnoredRoleChange(pluginData, context.member.id, roleId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
extra: {
|
||||||
|
matchedRoleId: roleId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMatchInformation({ matchResult, pluginData, contexts }) {
|
||||||
|
const role = pluginData.guild.roles.get(matchResult.extra.matchedRoleId);
|
||||||
|
const roleName = role?.name || "Unknown";
|
||||||
|
const member = contexts[0].member!;
|
||||||
|
const memberName = `**${member.user.username}#${member.user.discriminator}** (\`${member.id}\`)`;
|
||||||
|
return `Role ${roleName} (\`${matchResult.extra.matchedRoleId}\`) was added to ${memberName}`;
|
||||||
|
},
|
||||||
|
});
|
42
backend/src/plugins/Automod/triggers/roleRemoved.ts
Normal file
42
backend/src/plugins/Automod/triggers/roleRemoved.ts
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import * as t from "io-ts";
|
||||||
|
import { automodTrigger } from "../helpers";
|
||||||
|
import { consumeIgnoredRoleChange } from "../functions/ignoredRoleChanges";
|
||||||
|
|
||||||
|
interface RoleAddedMatchResult {
|
||||||
|
matchedRoleId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RoleRemovedTrigger = automodTrigger<RoleAddedMatchResult>()({
|
||||||
|
configType: t.union([t.string, t.array(t.string)]),
|
||||||
|
|
||||||
|
defaultConfig: null,
|
||||||
|
|
||||||
|
async match({ triggerConfig, context, pluginData }) {
|
||||||
|
if (!context.member || !context.rolesChanged || context.rolesChanged.removed.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const triggerRoles = Array.isArray(triggerConfig) ? triggerConfig : [triggerConfig];
|
||||||
|
for (const roleId of triggerRoles) {
|
||||||
|
if (consumeIgnoredRoleChange(pluginData, context.member.id, roleId)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.rolesChanged.removed.includes(roleId)) {
|
||||||
|
return {
|
||||||
|
extra: {
|
||||||
|
matchedRoleId: roleId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMatchInformation({ matchResult, pluginData, contexts }) {
|
||||||
|
const role = pluginData.guild.roles.get(matchResult.extra.matchedRoleId);
|
||||||
|
const roleName = role?.name || "Unknown";
|
||||||
|
const member = contexts[0].member!;
|
||||||
|
const memberName = `**${member.user.username}#${member.user.discriminator}** (\`${member.id}\`)`;
|
||||||
|
return `Role ${roleName} (\`${matchResult.extra.matchedRoleId}\`) was removed from ${memberName}`;
|
||||||
|
},
|
||||||
|
});
|
|
@ -69,6 +69,12 @@ export interface AutomodPluginType extends BasePluginType {
|
||||||
recentNicknameChanges: Map<string, { timestamp: number }>;
|
recentNicknameChanges: Map<string, { timestamp: number }>;
|
||||||
clearRecentNicknameChangesInterval: Timeout;
|
clearRecentNicknameChangesInterval: Timeout;
|
||||||
|
|
||||||
|
ignoredRoleChanges: Set<{
|
||||||
|
memberId: string;
|
||||||
|
roleId: string;
|
||||||
|
timestamp: number;
|
||||||
|
}>;
|
||||||
|
|
||||||
cachedAntiraidLevel: string | null;
|
cachedAntiraidLevel: string | null;
|
||||||
|
|
||||||
cooldownManager: CooldownManager;
|
cooldownManager: CooldownManager;
|
||||||
|
@ -91,6 +97,10 @@ export interface AutomodContext {
|
||||||
message?: SavedMessage;
|
message?: SavedMessage;
|
||||||
member?: Member;
|
member?: Member;
|
||||||
joined?: boolean;
|
joined?: boolean;
|
||||||
|
rolesChanged?: {
|
||||||
|
added?: string[];
|
||||||
|
removed?: string[];
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RecentAction {
|
export interface RecentAction {
|
||||||
|
|
Loading…
Add table
Reference in a new issue