mirror of
https://github.com/janderedev/automod.git
synced 2024-12-22 18:55:28 +00:00
option to DM users on kick/ban/warn
This commit is contained in:
parent
7cee4638a9
commit
5546ec3c59
|
@ -3,15 +3,15 @@ import { client } from "../../../index";
|
||||||
import Infraction from "../../../struct/antispam/Infraction";
|
import Infraction from "../../../struct/antispam/Infraction";
|
||||||
import InfractionType from "../../../struct/antispam/InfractionType";
|
import InfractionType from "../../../struct/antispam/InfractionType";
|
||||||
import SimpleCommand from "../../../struct/commands/SimpleCommand";
|
import SimpleCommand from "../../../struct/commands/SimpleCommand";
|
||||||
import MessageCommandContext from "../../../struct/MessageCommandContext";
|
|
||||||
import { fetchUsername, logModAction } from "../../modules/mod_logs";
|
import { fetchUsername, logModAction } from "../../modules/mod_logs";
|
||||||
import { storeTempBan } from "../../modules/tempbans";
|
import { storeTempBan } from "../../modules/tempbans";
|
||||||
import { dedupeArray, embed, EmbedColor, isModerator, NO_MANAGER_MSG, parseUserOrId, sanitizeMessageContent, storeInfraction } from "../../util";
|
import { dedupeArray, embed, EmbedColor, generateInfractionDMEmbed, getDmChannel, isModerator, NO_MANAGER_MSG, parseUserOrId, sanitizeMessageContent, storeInfraction } from "../../util";
|
||||||
import Day from 'dayjs';
|
import Day from 'dayjs';
|
||||||
import RelativeTime from 'dayjs/plugin/relativeTime';
|
import RelativeTime from 'dayjs/plugin/relativeTime';
|
||||||
import CommandCategory from "../../../struct/commands/CommandCategory";
|
import CommandCategory from "../../../struct/commands/CommandCategory";
|
||||||
import { SendableEmbed } from "@janderedev/revolt.js/node_modules/revolt-api";
|
import { SendableEmbed } from "@janderedev/revolt.js/node_modules/revolt-api";
|
||||||
import { User } from "@janderedev/revolt.js";
|
import { User } from "@janderedev/revolt.js";
|
||||||
|
import logger from "../../logger";
|
||||||
|
|
||||||
Day.extend(RelativeTime);
|
Day.extend(RelativeTime);
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ export default {
|
||||||
syntax: '/ban @username [10m|1h|...?] [reason?]',
|
syntax: '/ban @username [10m|1h|...?] [reason?]',
|
||||||
removeEmptyArgs: true,
|
removeEmptyArgs: true,
|
||||||
category: CommandCategory.Moderation,
|
category: CommandCategory.Moderation,
|
||||||
run: async (message: MessageCommandContext, args: string[]) => {
|
run: async (message, args, serverConfig) => {
|
||||||
if (!await isModerator(message))
|
if (!await isModerator(message))
|
||||||
return message.reply(NO_MANAGER_MSG);
|
return message.reply(NO_MANAGER_MSG);
|
||||||
if (!message.serverContext.havePermission('BanMembers')) {
|
if (!message.serverContext.havePermission('BanMembers')) {
|
||||||
|
@ -134,6 +134,7 @@ export default {
|
||||||
type: InfractionType.Manual,
|
type: InfractionType.Manual,
|
||||||
user: user._id,
|
user: user._id,
|
||||||
actionType: 'ban',
|
actionType: 'ban',
|
||||||
|
expires: Infinity,
|
||||||
}
|
}
|
||||||
const { userWarnCount } = await storeInfraction(infraction);
|
const { userWarnCount } = await storeInfraction(infraction);
|
||||||
|
|
||||||
|
@ -157,6 +158,20 @@ export default {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (serverConfig?.dmOnKick) {
|
||||||
|
try {
|
||||||
|
const embed = generateInfractionDMEmbed(message.serverContext, serverConfig, infraction, message);
|
||||||
|
const dmChannel = await getDmChannel(user);
|
||||||
|
|
||||||
|
if (dmChannel.havePermission('SendMessage') || dmChannel.havePermission('SendEmbeds')) {
|
||||||
|
await dmChannel.sendMessage({ embeds: [ embed ] });
|
||||||
|
}
|
||||||
|
else logger.warn('Missing permission to DM user.');
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await message.serverContext.banUser(user._id, {
|
await message.serverContext.banUser(user._id, {
|
||||||
reason: reason + ` (by ${await fetchUsername(message.author_id)} ${message.author_id})`
|
reason: reason + ` (by ${await fetchUsername(message.author_id)} ${message.author_id})`
|
||||||
});
|
});
|
||||||
|
@ -186,9 +201,24 @@ export default {
|
||||||
type: InfractionType.Manual,
|
type: InfractionType.Manual,
|
||||||
user: user._id,
|
user: user._id,
|
||||||
actionType: 'ban',
|
actionType: 'ban',
|
||||||
|
expires: banUntil,
|
||||||
}
|
}
|
||||||
const { userWarnCount } = await storeInfraction(infraction);
|
const { userWarnCount } = await storeInfraction(infraction);
|
||||||
|
|
||||||
|
if (serverConfig?.dmOnKick) {
|
||||||
|
try {
|
||||||
|
const embed = generateInfractionDMEmbed(message.serverContext, serverConfig, infraction, message);
|
||||||
|
const dmChannel = await getDmChannel(user);
|
||||||
|
|
||||||
|
if (dmChannel.havePermission('SendMessage') || dmChannel.havePermission('SendEmbeds')) {
|
||||||
|
await dmChannel.sendMessage({ embeds: [ embed ] });
|
||||||
|
}
|
||||||
|
else logger.warn('Missing permission to DM user.');
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await message.serverContext.banUser(user._id, {
|
await message.serverContext.banUser(user._id, {
|
||||||
reason: reason + ` (by ${await fetchUsername(message.author_id)} ${message.author_id}) (${durationStr})`
|
reason: reason + ` (by ${await fetchUsername(message.author_id)} ${message.author_id}) (${durationStr})`
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { User } from "@janderedev/revolt.js";
|
import { User } from "@janderedev/revolt.js";
|
||||||
import { Member } from "@janderedev/revolt.js/dist/maps/Members";
|
|
||||||
import { SendableEmbed } from "revolt-api";
|
import { SendableEmbed } from "revolt-api";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
import { client } from "../../../";
|
import { client } from "../../../";
|
||||||
|
@ -7,9 +6,9 @@ import Infraction from "../../../struct/antispam/Infraction";
|
||||||
import InfractionType from "../../../struct/antispam/InfractionType";
|
import InfractionType from "../../../struct/antispam/InfractionType";
|
||||||
import CommandCategory from "../../../struct/commands/CommandCategory";
|
import CommandCategory from "../../../struct/commands/CommandCategory";
|
||||||
import SimpleCommand from "../../../struct/commands/SimpleCommand";
|
import SimpleCommand from "../../../struct/commands/SimpleCommand";
|
||||||
import MessageCommandContext from "../../../struct/MessageCommandContext";
|
import logger from "../../logger";
|
||||||
import { fetchUsername, logModAction } from "../../modules/mod_logs";
|
import { fetchUsername, logModAction } from "../../modules/mod_logs";
|
||||||
import { dedupeArray, embed, EmbedColor, isModerator, NO_MANAGER_MSG, parseUser, parseUserOrId, sanitizeMessageContent, storeInfraction } from "../../util";
|
import { dedupeArray, embed, EmbedColor, generateInfractionDMEmbed, getDmChannel, isModerator, NO_MANAGER_MSG, parseUser, parseUserOrId, sanitizeMessageContent, storeInfraction } from "../../util";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'kick',
|
name: 'kick',
|
||||||
|
@ -18,7 +17,7 @@ export default {
|
||||||
syntax: '/kick @username [reason?]',
|
syntax: '/kick @username [reason?]',
|
||||||
removeEmptyArgs: true,
|
removeEmptyArgs: true,
|
||||||
category: CommandCategory.Moderation,
|
category: CommandCategory.Moderation,
|
||||||
run: async (message: MessageCommandContext, args: string[]) => {
|
run: async (message, args, serverConfig) => {
|
||||||
if (!await isModerator(message))
|
if (!await isModerator(message))
|
||||||
return message.reply(NO_MANAGER_MSG);
|
return message.reply(NO_MANAGER_MSG);
|
||||||
if (!message.serverContext.havePermission('KickMembers')) {
|
if (!message.serverContext.havePermission('KickMembers')) {
|
||||||
|
@ -104,13 +103,27 @@ export default {
|
||||||
_id: infId,
|
_id: infId,
|
||||||
createdBy: message.author_id,
|
createdBy: message.author_id,
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
reason: reason,
|
reason: reason || 'No reason provided',
|
||||||
server: message.serverContext._id,
|
server: message.serverContext._id,
|
||||||
type: InfractionType.Manual,
|
type: InfractionType.Manual,
|
||||||
user: user._id,
|
user: user._id,
|
||||||
actionType: 'kick',
|
actionType: 'kick',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (serverConfig?.dmOnKick) {
|
||||||
|
try {
|
||||||
|
const embed = generateInfractionDMEmbed(message.serverContext, serverConfig, infraction, message);
|
||||||
|
const dmChannel = await getDmChannel(user);
|
||||||
|
|
||||||
|
if (dmChannel.havePermission('SendMessage') || dmChannel.havePermission('SendEmbeds')) {
|
||||||
|
await dmChannel.sendMessage({ embeds: [ embed ] });
|
||||||
|
}
|
||||||
|
else logger.warn('Missing permission to DM user.');
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let [ { userWarnCount } ] = await Promise.all([
|
let [ { userWarnCount } ] = await Promise.all([
|
||||||
storeInfraction(infraction),
|
storeInfraction(infraction),
|
||||||
logModAction('kick', message.serverContext, message.member!, user._id, reason, infraction._id),
|
logModAction('kick', message.serverContext, message.member!, user._id, reason, infraction._id),
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import SimpleCommand from "../../../struct/commands/SimpleCommand";
|
import SimpleCommand from "../../../struct/commands/SimpleCommand";
|
||||||
import { dedupeArray, embed, EmbedColor, isModerator, NO_MANAGER_MSG, parseUserOrId, sanitizeMessageContent, storeInfraction } from "../../util";
|
import { dedupeArray, embed, EmbedColor, generateInfractionDMEmbed, getDmChannel, isModerator, NO_MANAGER_MSG, parseUserOrId, sanitizeMessageContent, storeInfraction } from "../../util";
|
||||||
import Infraction from "../../../struct/antispam/Infraction";
|
import Infraction from "../../../struct/antispam/Infraction";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
import InfractionType from "../../../struct/antispam/InfractionType";
|
import InfractionType from "../../../struct/antispam/InfractionType";
|
||||||
import { fetchUsername, logModAction } from "../../modules/mod_logs";
|
import { fetchUsername, logModAction } from "../../modules/mod_logs";
|
||||||
import MessageCommandContext from "../../../struct/MessageCommandContext";
|
|
||||||
import CommandCategory from "../../../struct/commands/CommandCategory";
|
import CommandCategory from "../../../struct/commands/CommandCategory";
|
||||||
import { SendableEmbed } from "revolt-api";
|
import { SendableEmbed } from "revolt-api";
|
||||||
import { User } from "@janderedev/revolt.js";
|
import { User } from "@janderedev/revolt.js";
|
||||||
|
import logger from "../../logger";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'warn',
|
name: 'warn',
|
||||||
|
@ -15,7 +15,7 @@ export default {
|
||||||
removeEmptyArgs: false,
|
removeEmptyArgs: false,
|
||||||
description: 'add an infraction to an user\'s record',
|
description: 'add an infraction to an user\'s record',
|
||||||
category: CommandCategory.Moderation,
|
category: CommandCategory.Moderation,
|
||||||
run: async (message: MessageCommandContext, args: string[]) => {
|
run: async (message, args, serverConfig) => {
|
||||||
if (!await isModerator(message)) return message.reply(NO_MANAGER_MSG);
|
if (!await isModerator(message)) return message.reply(NO_MANAGER_MSG);
|
||||||
|
|
||||||
const userInput = args.shift() || '';
|
const userInput = args.shift() || '';
|
||||||
|
@ -94,15 +94,32 @@ export default {
|
||||||
} as Infraction;
|
} as Infraction;
|
||||||
|
|
||||||
let { userWarnCount } = await storeInfraction(infraction);
|
let { userWarnCount } = await storeInfraction(infraction);
|
||||||
await logModAction(
|
await Promise.all([
|
||||||
'warn',
|
logModAction(
|
||||||
message.serverContext,
|
'warn',
|
||||||
message.member!,
|
message.serverContext,
|
||||||
user._id,
|
message.member!,
|
||||||
reason || 'No reason provided',
|
user._id,
|
||||||
infraction._id,
|
reason || 'No reason provided',
|
||||||
`This is warn number ${userWarnCount} for this user.`
|
infraction._id,
|
||||||
);
|
`This is warn number ${userWarnCount} for this user.`
|
||||||
|
),
|
||||||
|
(async () => {
|
||||||
|
if (serverConfig?.dmOnWarn) {
|
||||||
|
try {
|
||||||
|
const embed = generateInfractionDMEmbed(message.serverContext, serverConfig, infraction, message);
|
||||||
|
const dmChannel = await getDmChannel(user);
|
||||||
|
|
||||||
|
if (dmChannel.havePermission('SendMessage') || dmChannel.havePermission('SendEmbeds')) {
|
||||||
|
await dmChannel.sendMessage({ embeds: [ embed ] });
|
||||||
|
}
|
||||||
|
else logger.warn('Missing permission to DM user.');
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
]);
|
||||||
|
|
||||||
embeds.push({
|
embeds.push({
|
||||||
title: `User warned`,
|
title: `User warned`,
|
||||||
|
|
|
@ -136,7 +136,7 @@ let commands: SimpleCommand[];
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await cmd.run(message, args);
|
await cmd.run(message, args, config);
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
message.reply(`### An error has occurred:\n\`\`\`js\n${e}\n\`\`\``);
|
message.reply(`### An error has occurred:\n\`\`\`js\n${e}\n\`\`\``);
|
||||||
|
|
|
@ -15,11 +15,16 @@ import { Permission } from "@janderedev/revolt.js/dist/permissions/definitions";
|
||||||
import { Message } from "@janderedev/revolt.js/dist/maps/Messages";
|
import { Message } from "@janderedev/revolt.js/dist/maps/Messages";
|
||||||
import { isSudo } from "./commands/admin/botadm";
|
import { isSudo } from "./commands/admin/botadm";
|
||||||
import { SendableEmbed } from "revolt-api";
|
import { SendableEmbed } from "revolt-api";
|
||||||
|
import MessageCommandContext from "../struct/MessageCommandContext";
|
||||||
|
import ServerConfig from "../struct/ServerConfig";
|
||||||
|
|
||||||
const NO_MANAGER_MSG = '🔒 Missing permission';
|
const NO_MANAGER_MSG = '🔒 Missing permission';
|
||||||
const ULID_REGEX = /^[0-9A-HJ-KM-NP-TV-Z]{26}$/i;
|
const ULID_REGEX = /^[0-9A-HJ-KM-NP-TV-Z]{26}$/i;
|
||||||
const USER_MENTION_REGEX = /^<@[0-9A-HJ-KM-NP-TV-Z]{26}>$/i;
|
const USER_MENTION_REGEX = /^<@[0-9A-HJ-KM-NP-TV-Z]{26}>$/i;
|
||||||
const CHANNEL_MENTION_REGEX = /^<#[0-9A-HJ-KM-NP-TV-Z]{26}>$/i;
|
const CHANNEL_MENTION_REGEX = /^<#[0-9A-HJ-KM-NP-TV-Z]{26}>$/i;
|
||||||
|
const RE_HTTP_URI = /^http(s?):\/\//g;
|
||||||
|
const RE_MAILTO_URI = /^mailto:/g;
|
||||||
|
|
||||||
let autumn_url: string|null = null;
|
let autumn_url: string|null = null;
|
||||||
let apiConfig: any = axios.get(client.apiURL).then(res => {
|
let apiConfig: any = axios.get(client.apiURL).then(res => {
|
||||||
autumn_url = (res.data as any).features.autumn.url;
|
autumn_url = (res.data as any).features.autumn.url;
|
||||||
|
@ -347,6 +352,52 @@ const awaitClient = () => new Promise<void>(async resolve => {
|
||||||
else resolve();
|
else resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const getDmChannel = async (user: string|{_id: string}|User) => {
|
||||||
|
if (typeof user == 'string') user = client.users.get(user) || await client.users.fetch(user);
|
||||||
|
if (!(user instanceof User)) user = client.users.get(user._id) || await client.users.fetch(user._id);
|
||||||
|
|
||||||
|
return Array.from(client.channels).find(
|
||||||
|
c => c[1].channel_type == 'DirectMessage' && c[1].recipient?._id == (user as User)._id
|
||||||
|
)?.[1] || await (user as User).openDM();
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateInfractionDMEmbed = (server: Server, serverConfig: ServerConfig, infraction: Infraction, message: MessageCommandContext) => {
|
||||||
|
const embed: SendableEmbed = {
|
||||||
|
title: message.serverContext.name,
|
||||||
|
icon_url: message.serverContext.generateIconURL({ max_side: 128 }),
|
||||||
|
colour: '#ff9e2f',
|
||||||
|
url: message.url,
|
||||||
|
description: 'You have been ' +
|
||||||
|
(infraction.actionType
|
||||||
|
? `**${infraction.actionType == 'ban' ? 'banned' : 'kicked'}** from `
|
||||||
|
: `**warned** in `) +
|
||||||
|
`'${sanitizeMessageContent(message.serverContext.name).trim()}' <t:${Math.round(infraction.date / 1000)}:R>.\n` +
|
||||||
|
`**Reason:** ${infraction.reason}\n` +
|
||||||
|
`**Moderator:** [@${sanitizeMessageContent(message.author?.username || 'Unknown')}](/@${message.author_id})\n` +
|
||||||
|
`**Infraction ID:** \`${infraction._id}\`` +
|
||||||
|
(infraction.actionType == 'ban' && infraction.expires
|
||||||
|
? (infraction.expires == Infinity
|
||||||
|
? '\n**Ban duration:** Permanent'
|
||||||
|
: `\n**Ban expires** <t:${Math.round(infraction.expires / 1000)}:R>`)
|
||||||
|
: '')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverConfig.contact) {
|
||||||
|
if (RE_MAILTO_URI.test(serverConfig.contact)) {
|
||||||
|
embed.description += `\n\nIf you wish to appeal this decision, you may contact the server's moderation team at ` +
|
||||||
|
`[${serverConfig.contact.replace(RE_MAILTO_URI, '')}](${serverConfig.contact}).`
|
||||||
|
}
|
||||||
|
else if (RE_HTTP_URI.test(serverConfig.contact)) {
|
||||||
|
embed.description += `\n\nIf you wish to appeal this decision, you may do so [here](${serverConfig.contact}).`
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
embed.description += `\n\n${serverConfig.contact}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return embed;
|
||||||
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getAutumnURL,
|
getAutumnURL,
|
||||||
hasPerm,
|
hasPerm,
|
||||||
|
@ -366,6 +417,8 @@ export {
|
||||||
dedupeArray,
|
dedupeArray,
|
||||||
awaitClient,
|
awaitClient,
|
||||||
getMutualServers,
|
getMutualServers,
|
||||||
|
getDmChannel,
|
||||||
|
generateInfractionDMEmbed,
|
||||||
EmbedColor,
|
EmbedColor,
|
||||||
NO_MANAGER_MSG,
|
NO_MANAGER_MSG,
|
||||||
ULID_REGEX,
|
ULID_REGEX,
|
||||||
|
|
|
@ -25,6 +25,9 @@ class ServerConfig {
|
||||||
modAction?: LogConfig, // User warned, kicked or banned
|
modAction?: LogConfig, // User warned, kicked or banned
|
||||||
};
|
};
|
||||||
allowBlacklistedUsers?: boolean; // Whether the server explicitly allows users that are globally blacklisted
|
allowBlacklistedUsers?: boolean; // Whether the server explicitly allows users that are globally blacklisted
|
||||||
|
dmOnKick?: boolean; // Whether users should receive a DM when kicked/banned. Default false
|
||||||
|
dmOnWarn?: boolean; // Whether users should receive a DM when warned. Default false
|
||||||
|
contact?: string; // How to contact the server staff. Sent on kick/ban/warn DMs. http(s)/mailto link or normal text.
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ServerConfig;
|
export default ServerConfig;
|
||||||
|
|
|
@ -12,6 +12,7 @@ class Infraction {
|
||||||
targetMessages?: string[];
|
targetMessages?: string[];
|
||||||
reason: string;
|
reason: string;
|
||||||
date: number;
|
date: number;
|
||||||
|
expires?: number; // Only applies to bans
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Infraction;
|
export default Infraction;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import CommandCategory from "./CommandCategory";
|
import CommandCategory from "./CommandCategory";
|
||||||
import MessageCommandContext from "../MessageCommandContext";
|
import MessageCommandContext from "../MessageCommandContext";
|
||||||
|
import ServerConfig from "../ServerConfig";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A basic command, consisting of basic attributes
|
* A basic command, consisting of basic attributes
|
||||||
|
@ -26,7 +27,7 @@ class SimpleCommand {
|
||||||
removeEmptyArgs?: boolean | null;
|
removeEmptyArgs?: boolean | null;
|
||||||
|
|
||||||
// This is executed whenever the command is ran.
|
// This is executed whenever the command is ran.
|
||||||
run: (message: MessageCommandContext, args: string[]) => Promise<any>;
|
run: (message: MessageCommandContext, args: string[], serverConfig?: ServerConfig|null) => Promise<any>;
|
||||||
|
|
||||||
// The category the command belongs to, used for /help.
|
// The category the command belongs to, used for /help.
|
||||||
category: CommandCategory;
|
category: CommandCategory;
|
||||||
|
|
Loading…
Reference in a new issue