diff --git a/backend/src/plugins/Automod/actions/addRoles.ts b/backend/src/plugins/Automod/actions/addRoles.ts index c39cc5e7..c2571620 100644 --- a/backend/src/plugins/Automod/actions/addRoles.ts +++ b/backend/src/plugins/Automod/actions/addRoles.ts @@ -1,6 +1,5 @@ import { Permissions, Snowflake } from "discord.js"; import * as t from "io-ts"; -import { LogType } from "../../../data/LogType"; import { nonNullish, unique } from "../../../utils"; import { canAssignRole } from "../../../utils/canAssignRole"; import { getMissingPermissions } from "../../../utils/getMissingPermissions"; @@ -49,6 +48,7 @@ export const AddRolesAction = automodAction({ "**, **", )}**`, }); + return; } await Promise.all( diff --git a/backend/src/plugins/Automod/actions/availableActions.ts b/backend/src/plugins/Automod/actions/availableActions.ts index 76b2a60e..834b091e 100644 --- a/backend/src/plugins/Automod/actions/availableActions.ts +++ b/backend/src/plugins/Automod/actions/availableActions.ts @@ -6,6 +6,7 @@ import { AlertAction } from "./alert"; import { ArchiveThreadAction } from "./archiveThread"; import { BanAction } from "./ban"; import { ChangeNicknameAction } from "./changeNickname"; +import { ChangeRolesAction } from "./changeRoles"; import { CleanAction } from "./clean"; import { KickAction } from "./kick"; import { LogAction } from "./log"; @@ -34,6 +35,7 @@ export const availableActions: Record> = { set_counter: SetCounterAction, set_slowmode: SetSlowmodeAction, archive_thread: ArchiveThreadAction, + change_roles: ChangeRolesAction, }; export const AvailableActions = t.type({ @@ -53,4 +55,5 @@ export const AvailableActions = t.type({ set_counter: SetCounterAction.configType, set_slowmode: SetSlowmodeAction.configType, archive_thread: ArchiveThreadAction.configType, + change_roles: ChangeRolesAction.configType, }); diff --git a/backend/src/plugins/Automod/actions/changeRoles.ts b/backend/src/plugins/Automod/actions/changeRoles.ts new file mode 100644 index 00000000..3f966585 --- /dev/null +++ b/backend/src/plugins/Automod/actions/changeRoles.ts @@ -0,0 +1,110 @@ +import { Permissions, Snowflake } from "discord.js"; +import * as t from "io-ts"; +import isEqual from "lodash.isequal"; +import { nonNullish, unique } from "../../../utils"; +import { canAssignRole } from "../../../utils/canAssignRole"; +import { getMissingPermissions } from "../../../utils/getMissingPermissions"; +import { memberRolesLock } from "../../../utils/lockNameHelpers"; +import { missingPermissionError } from "../../../utils/missingPermissionError"; +import { LogsPlugin } from "../../Logs/LogsPlugin"; +import { ignoreRoleChange } from "../functions/ignoredRoleChanges"; +import { automodAction } from "../helpers"; + +const p = Permissions.FLAGS; + +export const ChangeRolesAction = automodAction({ + configType: t.type({ + add: t.array(t.string), + remove: t.array(t.string), + }), + defaultConfig: { + add: [], + remove: [], + }, + + async apply({ pluginData, contexts, actionConfig, ruleName }) { + const members = unique(contexts.map(c => c.member).filter(nonNullish)); + const me = pluginData.guild.members.cache.get(pluginData.client.user!.id)!; + + const missingPermissions = getMissingPermissions(me.permissions, p.MANAGE_ROLES); + if (missingPermissions) { + const logs = pluginData.getPlugin(LogsPlugin); + logs.logBotAlert({ + body: `Cannot edit roles in Automod rule **${ruleName}**. ${missingPermissionError(missingPermissions)}`, + }); + return; + } + + const rolesToAssign: string[] = []; + const rolesWeCannotAssign: string[] = []; + const rolesToRemove: string[] = []; + const rolesWeCannotRemove: string[] = []; + for (const roleId of actionConfig.add) { + if (canAssignRole(pluginData.guild, me, roleId)) { + rolesToAssign.push(roleId); + } else { + rolesWeCannotAssign.push(roleId); + } + } + for (const roleId of actionConfig.remove) { + if (canAssignRole(pluginData.guild, me, roleId)) { + rolesToRemove.push(roleId); + } else { + rolesWeCannotRemove.push(roleId); + } + } + + if (rolesWeCannotAssign.length) { + const roleNamesWeCannotAssign = rolesWeCannotAssign.map( + roleId => pluginData.guild.roles.cache.get(roleId as Snowflake)?.name || roleId, + ); + const logs = pluginData.getPlugin(LogsPlugin); + logs.logBotAlert({ + body: `Unable to assign the following roles in Automod rule **${ruleName}**: **${roleNamesWeCannotAssign.join( + "**, **", + )}**`, + }); + } + + if (rolesWeCannotRemove.length) { + const roleNamesWeCannotRemove = rolesWeCannotRemove.map( + roleId => pluginData.guild.roles.cache.get(roleId as Snowflake)?.name || roleId, + ); + const logs = pluginData.getPlugin(LogsPlugin); + logs.logBotAlert({ + body: `Unable to remove the following roles in Automod rule **${ruleName}**: **${roleNamesWeCannotRemove.join( + "**, **", + )}**`, + }); + return; + } + + await Promise.all( + members.map(async member => { + const memberRoles = new Set(member.roles.cache.keys()); + for (const roleId of rolesToAssign) { + memberRoles.add(roleId as Snowflake); + ignoreRoleChange(pluginData, member.id, roleId); + } + for (const roleId of rolesToRemove) { + memberRoles.delete(roleId as Snowflake); + ignoreRoleChange(pluginData, member.id, roleId); + } + + if (isEqual(memberRoles, member.roles.cache.keys())) { + // No role changes + return; + } + + const memberRoleLock = await pluginData.locks.acquire(memberRolesLock(member)); + + const rolesArr = Array.from(memberRoles.values()); + await member.edit({ + roles: rolesArr, + }); + + memberRoleLock.unlock(); + }), + ); + }, +}); diff --git a/backend/src/plugins/Automod/actions/removeRoles.ts b/backend/src/plugins/Automod/actions/removeRoles.ts index 690af257..561756d0 100644 --- a/backend/src/plugins/Automod/actions/removeRoles.ts +++ b/backend/src/plugins/Automod/actions/removeRoles.ts @@ -1,6 +1,5 @@ import { Permissions, Snowflake } from "discord.js"; import * as t from "io-ts"; -import { LogType } from "../../../data/LogType"; import { nonNullish, unique } from "../../../utils"; import { canAssignRole } from "../../../utils/canAssignRole"; import { getMissingPermissions } from "../../../utils/getMissingPermissions"; @@ -25,7 +24,7 @@ export const RemoveRolesAction = automodAction({ if (missingPermissions) { const logs = pluginData.getPlugin(LogsPlugin); logs.logBotAlert({ - body: `Cannot add roles in Automod rule **${ruleName}**. ${missingPermissionError(missingPermissions)}`, + body: `Cannot remove roles in Automod rule **${ruleName}**. ${missingPermissionError(missingPermissions)}`, }); return; } @@ -50,6 +49,7 @@ export const RemoveRolesAction = automodAction({ "**, **", )}**`, }); + return; } await Promise.all(