mirror of
https://github.com/ZeppelinBot/Zeppelin.git
synced 2025-05-11 04:45:02 +00:00
Automod actions + ModActions public interface
This commit is contained in:
parent
0f0728bc1c
commit
86023877a2
22 changed files with 508 additions and 16 deletions
35
backend/src/plugins/Automod/actions/addRoles.ts
Normal file
35
backend/src/plugins/Automod/actions/addRoles.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import * as t from "io-ts";
|
||||
import { automodAction } from "../helpers";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import { asyncMap, resolveMember, tNullable } from "../../../utils";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
|
||||
export const AddRolesAction = automodAction({
|
||||
configType: t.array(t.string),
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
const members = contexts.map(c => c.member).filter(Boolean);
|
||||
const uniqueMembers = new Set(members);
|
||||
|
||||
await Promise.all(
|
||||
Array.from(uniqueMembers.values()).map(async member => {
|
||||
const memberRoles = new Set(member.roles);
|
||||
for (const roleId of actionConfig) {
|
||||
memberRoles.add(roleId);
|
||||
}
|
||||
|
||||
if (memberRoles.size === member.roles.length) {
|
||||
// No role changes
|
||||
return;
|
||||
}
|
||||
|
||||
const rolesArr = Array.from(memberRoles.values());
|
||||
await member.edit({
|
||||
roles: rolesArr,
|
||||
});
|
||||
member.roles = rolesArr; // Make sure we know of the new roles internally as well
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
48
backend/src/plugins/Automod/actions/alert.ts
Normal file
48
backend/src/plugins/Automod/actions/alert.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
import * as t from "io-ts";
|
||||
import { automodAction } from "../helpers";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import { asyncMap, messageLink, resolveMember, stripObjectToScalars, tNullable } from "../../../utils";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
import { TextChannel } from "eris";
|
||||
import { renderTemplate } from "../../../templateFormatter";
|
||||
|
||||
export const AlertAction = automodAction({
|
||||
configType: t.type({
|
||||
channel: t.string,
|
||||
text: t.string,
|
||||
}),
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig, ruleName, matchResult }) {
|
||||
const channel = pluginData.guild.channels.get(actionConfig.channel);
|
||||
|
||||
if (channel && channel instanceof TextChannel) {
|
||||
const text = actionConfig.text;
|
||||
const theMessageLink =
|
||||
contexts[0].message && messageLink(pluginData.guild.id, contexts[0].message.channel_id, contexts[0].message.id);
|
||||
|
||||
const safeUsers = contexts.map(c => c.user && stripObjectToScalars(c.user)).filter(Boolean);
|
||||
const safeUser = safeUsers[0];
|
||||
|
||||
const takenActions = Object.keys(pluginData.config.get().rules[ruleName].actions);
|
||||
// TODO: Generate logMessage
|
||||
const logMessage = "";
|
||||
|
||||
const rendered = await renderTemplate(actionConfig.text, {
|
||||
rule: ruleName,
|
||||
user: safeUser,
|
||||
users: safeUsers,
|
||||
text,
|
||||
matchSummary: matchResult.summary,
|
||||
messageLink: theMessageLink,
|
||||
logMessage,
|
||||
});
|
||||
channel.createMessage(rendered);
|
||||
} else {
|
||||
// TODO: Post BOT_ALERT log
|
||||
/*this.getLogs().log(LogType.BOT_ALERT, {
|
||||
body: `Invalid channel id \`${actionConfig.channel}\` for alert action in automod rule **${rule.name}**`,
|
||||
});*/
|
||||
}
|
||||
},
|
||||
});
|
35
backend/src/plugins/Automod/actions/ban.ts
Normal file
35
backend/src/plugins/Automod/actions/ban.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import * as t from "io-ts";
|
||||
import { automodAction } from "../helpers";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import { asyncMap, resolveMember, tNullable } from "../../../utils";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
|
||||
export const BanAction = automodAction({
|
||||
configType: t.type({
|
||||
reason: tNullable(t.string),
|
||||
notify: tNullable(t.string),
|
||||
notifyChannel: tNullable(t.string),
|
||||
deleteMessageDays: tNullable(t.number),
|
||||
}),
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
const reason = actionConfig.reason || "Kicked automatically";
|
||||
const contactMethods = resolveActionContactMethods(pluginData, actionConfig);
|
||||
const deleteMessageDays = actionConfig.deleteMessageDays;
|
||||
|
||||
const caseArgs = {
|
||||
modId: pluginData.client.user.id,
|
||||
extraNotes: [
|
||||
/* TODO */
|
||||
],
|
||||
};
|
||||
|
||||
const userIdsToBan = contexts.map(c => c.user?.id).filter(Boolean);
|
||||
|
||||
const modActions = pluginData.getPlugin(ModActionsPlugin);
|
||||
for (const userId of userIdsToBan) {
|
||||
await modActions.banUserId(userId, reason, { contactMethods, caseArgs, deleteMessageDays });
|
||||
}
|
||||
},
|
||||
});
|
27
backend/src/plugins/Automod/actions/changeNickname.ts
Normal file
27
backend/src/plugins/Automod/actions/changeNickname.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import * as t from "io-ts";
|
||||
import { automodAction } from "../helpers";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import { asyncMap, resolveMember, tNullable } from "../../../utils";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
|
||||
export const ChangeNicknameAction = automodAction({
|
||||
configType: t.type({
|
||||
name: t.string,
|
||||
}),
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
const members = contexts.map(c => c.member).filter(Boolean);
|
||||
const uniqueMembers = new Set(members);
|
||||
|
||||
for (const member of uniqueMembers) {
|
||||
if (pluginData.state.recentNicknameChanges.has(member.id)) continue;
|
||||
|
||||
member.edit({ nick: actionConfig.name }).catch(err => {
|
||||
/* TODO: Log this error */
|
||||
});
|
||||
|
||||
pluginData.state.recentNicknameChanges.set(member.id, { timestamp: Date.now() });
|
||||
}
|
||||
},
|
||||
});
|
12
backend/src/plugins/Automod/actions/exampleAction.ts
Normal file
12
backend/src/plugins/Automod/actions/exampleAction.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import * as t from "io-ts";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const ExampleAction = automodAction({
|
||||
configType: t.type({
|
||||
someValue: t.string,
|
||||
}),
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
// TODO: Everything
|
||||
},
|
||||
});
|
34
backend/src/plugins/Automod/actions/kick.ts
Normal file
34
backend/src/plugins/Automod/actions/kick.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import * as t from "io-ts";
|
||||
import { automodAction } from "../helpers";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import { asyncMap, resolveMember, tNullable } from "../../../utils";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
|
||||
export const KickAction = automodAction({
|
||||
configType: t.type({
|
||||
reason: tNullable(t.string),
|
||||
notify: tNullable(t.string),
|
||||
notifyChannel: tNullable(t.string),
|
||||
}),
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
const reason = actionConfig.reason || "Kicked automatically";
|
||||
const contactMethods = resolveActionContactMethods(pluginData, actionConfig);
|
||||
|
||||
const caseArgs = {
|
||||
modId: pluginData.client.user.id,
|
||||
extraNotes: [
|
||||
/* TODO */
|
||||
],
|
||||
};
|
||||
|
||||
const userIdsToKick = contexts.map(c => c.user?.id).filter(Boolean);
|
||||
const membersToKick = await asyncMap(userIdsToKick, id => resolveMember(pluginData.client, pluginData.guild, id));
|
||||
|
||||
const modActions = pluginData.getPlugin(ModActionsPlugin);
|
||||
for (const member of membersToKick) {
|
||||
await modActions.kickMember(member, reason, { contactMethods, caseArgs });
|
||||
}
|
||||
},
|
||||
});
|
10
backend/src/plugins/Automod/actions/log.ts
Normal file
10
backend/src/plugins/Automod/actions/log.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import * as t from "io-ts";
|
||||
import { automodAction } from "../helpers";
|
||||
|
||||
export const LogAction = automodAction({
|
||||
configType: t.boolean,
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
// TODO: Everything
|
||||
},
|
||||
});
|
36
backend/src/plugins/Automod/actions/mute.ts
Normal file
36
backend/src/plugins/Automod/actions/mute.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import * as t from "io-ts";
|
||||
import { automodAction } from "../helpers";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import { asyncMap, convertDelayStringToMS, resolveMember, tDelayString, tNullable } from "../../../utils";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
import { MutesPlugin } from "../../Mutes/MutesPlugin";
|
||||
|
||||
export const MuteAction = automodAction({
|
||||
configType: t.type({
|
||||
reason: tNullable(t.string),
|
||||
duration: tNullable(tDelayString),
|
||||
notify: tNullable(t.string),
|
||||
notifyChannel: tNullable(t.string),
|
||||
}),
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
const duration = actionConfig.duration ? convertDelayStringToMS(actionConfig.duration) : null;
|
||||
const reason = actionConfig.reason || "Muted automatically";
|
||||
const contactMethods = resolveActionContactMethods(pluginData, actionConfig);
|
||||
|
||||
const caseArgs = {
|
||||
modId: pluginData.client.user.id,
|
||||
extraNotes: [
|
||||
/* TODO */
|
||||
],
|
||||
};
|
||||
|
||||
const userIdsToMute = contexts.map(c => c.user?.id).filter(Boolean);
|
||||
|
||||
const mutes = pluginData.getPlugin(MutesPlugin);
|
||||
for (const userId of userIdsToMute) {
|
||||
await mutes.muteUser(userId, duration, reason, { contactMethods, caseArgs });
|
||||
}
|
||||
},
|
||||
});
|
35
backend/src/plugins/Automod/actions/removeRoles.ts
Normal file
35
backend/src/plugins/Automod/actions/removeRoles.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import * as t from "io-ts";
|
||||
import { automodAction } from "../helpers";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import { asyncMap, resolveMember, tNullable } from "../../../utils";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
|
||||
export const RemoveRolesAction = automodAction({
|
||||
configType: t.array(t.string),
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
const members = contexts.map(c => c.member).filter(Boolean);
|
||||
const uniqueMembers = new Set(members);
|
||||
|
||||
await Promise.all(
|
||||
Array.from(uniqueMembers.values()).map(async member => {
|
||||
const memberRoles = new Set(member.roles);
|
||||
for (const roleId of actionConfig) {
|
||||
memberRoles.delete(roleId);
|
||||
}
|
||||
|
||||
if (memberRoles.size === member.roles.length) {
|
||||
// No role changes
|
||||
return;
|
||||
}
|
||||
|
||||
const rolesArr = Array.from(memberRoles.values());
|
||||
await member.edit({
|
||||
roles: rolesArr,
|
||||
});
|
||||
member.roles = rolesArr; // Make sure we know of the new roles internally as well
|
||||
}),
|
||||
);
|
||||
},
|
||||
});
|
63
backend/src/plugins/Automod/actions/reply.ts
Normal file
63
backend/src/plugins/Automod/actions/reply.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import * as t from "io-ts";
|
||||
import { automodAction } from "../helpers";
|
||||
import {
|
||||
convertDelayStringToMS,
|
||||
noop,
|
||||
renderRecursively,
|
||||
stripObjectToScalars,
|
||||
tDelayString,
|
||||
tMessageContent,
|
||||
tNullable,
|
||||
} from "../../../utils";
|
||||
import { TextChannel } from "eris";
|
||||
import { AutomodContext } from "../types";
|
||||
import { renderTemplate } from "../../../templateFormatter";
|
||||
|
||||
export const ReplyAction = automodAction({
|
||||
configType: t.union([
|
||||
t.string,
|
||||
t.type({
|
||||
text: tMessageContent,
|
||||
auto_delete: tNullable(t.union([tDelayString, t.number])),
|
||||
}),
|
||||
]),
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
const contextsWithTextChannels = contexts
|
||||
.filter(c => c.message?.channel_id)
|
||||
.filter(c => pluginData.guild.channels.get(c.message.channel_id) instanceof TextChannel);
|
||||
|
||||
const contextsByChannelId = contextsWithTextChannels.reduce((map: Map<string, AutomodContext[]>, context) => {
|
||||
if (!map.has(context.message.channel_id)) {
|
||||
map.set(context.message.channel_id, []);
|
||||
}
|
||||
|
||||
map.get(context.message.channel_id).push(context);
|
||||
return map;
|
||||
}, new Map());
|
||||
|
||||
for (const [channelId, _contexts] of contextsByChannelId.entries()) {
|
||||
const users = Array.from(new Set(_contexts.map(c => c.user).filter(Boolean)));
|
||||
const user = users[0];
|
||||
|
||||
const renderReplyText = async str =>
|
||||
renderTemplate(str, {
|
||||
user: stripObjectToScalars(user),
|
||||
});
|
||||
const formatted =
|
||||
typeof actionConfig === "string"
|
||||
? await renderReplyText(actionConfig)
|
||||
: await renderRecursively(actionConfig.text, renderReplyText);
|
||||
|
||||
if (formatted) {
|
||||
const channel = pluginData.guild.channels.get(channelId) as TextChannel;
|
||||
const replyMsg = await channel.createMessage(formatted);
|
||||
|
||||
if (typeof actionConfig === "object" && actionConfig.auto_delete) {
|
||||
const delay = convertDelayStringToMS(String(actionConfig.auto_delete));
|
||||
setTimeout(() => replyMsg.delete().catch(noop), delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
11
backend/src/plugins/Automod/actions/setAntiraidLevel.ts
Normal file
11
backend/src/plugins/Automod/actions/setAntiraidLevel.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import * as t from "io-ts";
|
||||
import { automodAction } from "../helpers";
|
||||
import { setAntiraidLevel } from "../functions/setAntiraidLevel";
|
||||
|
||||
export const SetAntiraidLevelAction = automodAction({
|
||||
configType: t.string,
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
setAntiraidLevel(pluginData, actionConfig);
|
||||
},
|
||||
});
|
34
backend/src/plugins/Automod/actions/warn.ts
Normal file
34
backend/src/plugins/Automod/actions/warn.ts
Normal file
|
@ -0,0 +1,34 @@
|
|||
import * as t from "io-ts";
|
||||
import { automodAction } from "../helpers";
|
||||
import { LogType } from "../../../data/LogType";
|
||||
import { asyncMap, resolveMember, tNullable } from "../../../utils";
|
||||
import { resolveActionContactMethods } from "../functions/resolveActionContactMethods";
|
||||
import { ModActionsPlugin } from "../../ModActions/ModActionsPlugin";
|
||||
|
||||
export const WarnAction = automodAction({
|
||||
configType: t.type({
|
||||
reason: tNullable(t.string),
|
||||
notify: tNullable(t.string),
|
||||
notifyChannel: tNullable(t.string),
|
||||
}),
|
||||
|
||||
async apply({ pluginData, contexts, actionConfig }) {
|
||||
const reason = actionConfig.reason || "Warned automatically";
|
||||
const contactMethods = resolveActionContactMethods(pluginData, actionConfig);
|
||||
|
||||
const caseArgs = {
|
||||
modId: pluginData.client.user.id,
|
||||
extraNotes: [
|
||||
/* TODO */
|
||||
],
|
||||
};
|
||||
|
||||
const userIdsToWarn = contexts.map(c => c.user?.id).filter(Boolean);
|
||||
const membersToWarn = await asyncMap(userIdsToWarn, id => resolveMember(pluginData.client, pluginData.guild, id));
|
||||
|
||||
const modActions = pluginData.getPlugin(ModActionsPlugin);
|
||||
for (const member of membersToWarn) {
|
||||
await modActions.warnMember(member, reason, { contactMethods, caseArgs });
|
||||
}
|
||||
},
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue