mirror of
https://github.com/janderedev/automod.git
synced 2024-12-22 02:35:27 +00:00
Confirmation prompt when kicking/banning via reply
This commit is contained in:
parent
2f9792c616
commit
5c3479268d
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": false,
|
||||||
"editor.formatOnSaveMode": "modifications",
|
"editor.formatOnSaveMode": "modifications",
|
||||||
"prettier.tabWidth": 4
|
"prettier.tabWidth": 4
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,22 @@ import InfractionType from "automod/dist/types/antispam/InfractionType";
|
||||||
import SimpleCommand from "../../../struct/commands/SimpleCommand";
|
import SimpleCommand from "../../../struct/commands/SimpleCommand";
|
||||||
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, generateInfractionDMEmbed, getDmChannel, isModerator, NO_MANAGER_MSG, parseUserOrId, sanitizeMessageContent, storeInfraction } from "../../util";
|
import {
|
||||||
import Day from 'dayjs';
|
dedupeArray,
|
||||||
import RelativeTime from 'dayjs/plugin/relativeTime';
|
embed,
|
||||||
|
EmbedColor,
|
||||||
|
generateInfractionDMEmbed,
|
||||||
|
getDmChannel,
|
||||||
|
getMembers,
|
||||||
|
isModerator,
|
||||||
|
NO_MANAGER_MSG,
|
||||||
|
parseUserOrId,
|
||||||
|
sanitizeMessageContent,
|
||||||
|
storeInfraction,
|
||||||
|
yesNoMessage,
|
||||||
|
} from "../../util";
|
||||||
|
import Day from "dayjs";
|
||||||
|
import RelativeTime from "dayjs/plugin/relativeTime";
|
||||||
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";
|
||||||
|
@ -16,30 +29,40 @@ import logger from "../../logger";
|
||||||
Day.extend(RelativeTime);
|
Day.extend(RelativeTime);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'ban',
|
name: "ban",
|
||||||
aliases: [ 'eject' ],
|
aliases: ["eject"],
|
||||||
description: 'Ban a member from the server',
|
description: "Ban a member from the server",
|
||||||
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, args, serverConfig) => {
|
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')) {
|
return await message.reply({
|
||||||
return await message.reply({ embeds: [
|
embeds: [
|
||||||
embed(`Sorry, I do not have \`BanMembers\` permission.`, '', EmbedColor.SoftError)
|
embed(
|
||||||
] });
|
`Sorry, I do not have \`BanMembers\` permission.`,
|
||||||
|
"",
|
||||||
|
EmbedColor.SoftError
|
||||||
|
),
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const userInput = !message.reply_ids?.length ? args.shift() || '' : undefined;
|
const userInput = !message.reply_ids?.length
|
||||||
if (!userInput && !message.reply_ids?.length) return message.reply({ embeds: [
|
? args.shift() || ""
|
||||||
embed(
|
: undefined;
|
||||||
`Please specify one or more users by replying to their message while running this command or ` +
|
if (!userInput && !message.reply_ids?.length)
|
||||||
`by specifying a comma-separated list of usernames.`,
|
return message.reply({
|
||||||
'No target user specified',
|
embeds: [
|
||||||
EmbedColor.SoftError,
|
embed(
|
||||||
),
|
`Please specify one or more users by replying to their message while running this command or ` +
|
||||||
] });
|
`by specifying a comma-separated list of usernames.`,
|
||||||
|
"No target user specified",
|
||||||
|
EmbedColor.SoftError
|
||||||
|
),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
let banDuration = 0;
|
let banDuration = 0;
|
||||||
let durationStr = args.shift();
|
let durationStr = args.shift();
|
||||||
|
@ -49,48 +72,84 @@ export default {
|
||||||
// Being able to specify the same letter multiple times
|
// Being able to specify the same letter multiple times
|
||||||
// (e.g. 1s1s) and having their values stack is a feature
|
// (e.g. 1s1s) and having their values stack is a feature
|
||||||
for (const piece of pieces) {
|
for (const piece of pieces) {
|
||||||
let [ num, letter ] = [ Number(piece.slice(0, piece.length - 1)), piece.slice(piece.length - 1) ];
|
let [num, letter] = [
|
||||||
|
Number(piece.slice(0, piece.length - 1)),
|
||||||
|
piece.slice(piece.length - 1),
|
||||||
|
];
|
||||||
let multiplier = 0;
|
let multiplier = 0;
|
||||||
|
|
||||||
switch(letter) {
|
switch (letter) {
|
||||||
case 's': multiplier = 1000; break;
|
case "s":
|
||||||
case 'm': multiplier = 1000 * 60; break;
|
multiplier = 1000;
|
||||||
case 'h': multiplier = 1000 * 60 * 60; break;
|
break;
|
||||||
case 'd': multiplier = 1000 * 60 * 60 * 24; break;
|
case "m":
|
||||||
case 'w': multiplier = 1000 * 60 * 60 * 24 * 7; break;
|
multiplier = 1000 * 60;
|
||||||
case 'y': multiplier = 1000 * 60 * 60 * 24 * 365; break;
|
break;
|
||||||
|
case "h":
|
||||||
|
multiplier = 1000 * 60 * 60;
|
||||||
|
break;
|
||||||
|
case "d":
|
||||||
|
multiplier = 1000 * 60 * 60 * 24;
|
||||||
|
break;
|
||||||
|
case "w":
|
||||||
|
multiplier = 1000 * 60 * 60 * 24 * 7;
|
||||||
|
break;
|
||||||
|
case "y":
|
||||||
|
multiplier = 1000 * 60 * 60 * 24 * 365;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
banDuration += num * multiplier;
|
banDuration += num * multiplier;
|
||||||
}
|
}
|
||||||
} else if (durationStr) args.unshift(durationStr);
|
} else if (durationStr) args.unshift(durationStr);
|
||||||
|
|
||||||
let reason = args.join(' ')
|
let reason = args
|
||||||
?.replace(new RegExp('`', 'g'), '\'')
|
.join(" ")
|
||||||
?.replace(new RegExp('\n', 'g'), ' ');
|
?.replace(new RegExp("`", "g"), "'")
|
||||||
|
?.replace(new RegExp("\n", "g"), " ");
|
||||||
|
|
||||||
if (reason.length > 500) return message.reply({
|
if (reason.length > 500)
|
||||||
embeds: [ embed('Ban reason may not be longer than 500 characters.', null, EmbedColor.SoftError) ]
|
return message.reply({
|
||||||
});
|
embeds: [
|
||||||
|
embed(
|
||||||
|
"Ban reason may not be longer than 500 characters.",
|
||||||
|
null,
|
||||||
|
EmbedColor.SoftError
|
||||||
|
),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const embeds: SendableEmbed[] = [];
|
const embeds: SendableEmbed[] = [];
|
||||||
const handledUsers: string[] = [];
|
const handledUsers: string[] = [];
|
||||||
const targetUsers: User|{ _id: string }[] = [];
|
const targetUsers: User | { _id: string }[] = [];
|
||||||
|
|
||||||
const targetInput = dedupeArray(
|
const targetInput = dedupeArray(
|
||||||
message.reply_ids?.length
|
message.reply_ids?.length
|
||||||
? (await Promise.allSettled(
|
? (
|
||||||
message.reply_ids.map(msg => message.channel?.fetchMessage(msg))
|
await Promise.allSettled(
|
||||||
))
|
message.reply_ids.map((msg) =>
|
||||||
.filter(m => m.status == 'fulfilled').map(m => (m as any).value.author_id)
|
message.channel?.fetchMessage(msg)
|
||||||
: userInput!.split(','),
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.filter((m) => m.status == "fulfilled")
|
||||||
|
.map((m) => (m as any).value.author_id)
|
||||||
|
: userInput!.split(",")
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const userStr of targetInput) {
|
for (const userStr of targetInput) {
|
||||||
try {
|
try {
|
||||||
let user = await parseUserOrId(userStr);
|
let user = await parseUserOrId(userStr);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
embeds.push(embed(`I can't resolve \`${sanitizeMessageContent(userStr).trim()}\` to a user.`, null, EmbedColor.SoftError));
|
embeds.push(
|
||||||
|
embed(
|
||||||
|
`I can't resolve \`${sanitizeMessageContent(
|
||||||
|
userStr
|
||||||
|
).trim()}\` to a user.`,
|
||||||
|
null,
|
||||||
|
EmbedColor.SoftError
|
||||||
|
)
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,27 +158,57 @@ export default {
|
||||||
handledUsers.push(user._id);
|
handledUsers.push(user._id);
|
||||||
|
|
||||||
if (user._id == message.author_id) {
|
if (user._id == message.author_id) {
|
||||||
embeds.push(embed('I recommend against banning yourself :yeahokayyy:', null, EmbedColor.Warning));
|
embeds.push(
|
||||||
|
embed(
|
||||||
|
"I recommend against banning yourself :yeahokayyy:",
|
||||||
|
null,
|
||||||
|
EmbedColor.Warning
|
||||||
|
)
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user._id == client.user!._id) {
|
if (user._id == client.user!._id) {
|
||||||
embeds.push(embed('I\'m not going to ban myself :flushee:', null, EmbedColor.Warning));
|
embeds.push(
|
||||||
|
embed(
|
||||||
|
"I'm not going to ban myself :flushee:",
|
||||||
|
null,
|
||||||
|
EmbedColor.Warning
|
||||||
|
)
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
targetUsers.push(user);
|
targetUsers.push(user);
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
embeds.push(embed(
|
embeds.push(
|
||||||
`Failed to ban target \`${sanitizeMessageContent(userStr).trim()}\`: ${e}`,
|
embed(
|
||||||
`Failed to ban: An error has occurred`,
|
`Failed to ban target \`${sanitizeMessageContent(
|
||||||
EmbedColor.Error,
|
userStr
|
||||||
));
|
).trim()}\`: ${e}`,
|
||||||
|
`Failed to ban: An error has occurred`,
|
||||||
|
EmbedColor.Error
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const members = await message.serverContext.fetchMembers();
|
if (message.reply_ids?.length && targetUsers.length) {
|
||||||
|
let res = await yesNoMessage(
|
||||||
|
message.channel!,
|
||||||
|
message.author_id,
|
||||||
|
`This will ban the author${targetUsers.length > 1 ? 's' : ''} `
|
||||||
|
+ `of the message${message.reply_ids.length > 1 ? 's' : ''} you replied to.\n`
|
||||||
|
+ `The following user${targetUsers.length > 1 ? 's' : ''} will be affected: `
|
||||||
|
+ `${targetUsers.map(u => `<@${u._id}>`).join(', ')}.\n`
|
||||||
|
+ `Are you sure?`,
|
||||||
|
'Confirm action'
|
||||||
|
);
|
||||||
|
if (!res) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const members = getMembers(message.serverContext._id);
|
||||||
|
|
||||||
for (const user of targetUsers) {
|
for (const user of targetUsers) {
|
||||||
try {
|
try {
|
||||||
|
@ -129,64 +218,106 @@ export default {
|
||||||
_id: infId,
|
_id: infId,
|
||||||
createdBy: message.author_id,
|
createdBy: message.author_id,
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
reason: reason || 'No reason provided',
|
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: 'ban',
|
actionType: "ban",
|
||||||
expires: Infinity,
|
expires: Infinity,
|
||||||
}
|
};
|
||||||
const { userWarnCount } = await storeInfraction(infraction);
|
const { userWarnCount } = await storeInfraction(infraction);
|
||||||
|
|
||||||
const member = members.members.find(m => m._id.user == user._id);
|
const member = members.find((m) => m._id.user == user._id);
|
||||||
|
|
||||||
if (member && message.member && !member.inferiorTo(message.member)) {
|
if (
|
||||||
embeds.push(embed(
|
member &&
|
||||||
`\`${member.user?.username}\` has an equally or higher ranked role than you; refusing to ban.`,
|
message.member &&
|
||||||
'Missing permission',
|
!member.inferiorTo(message.member)
|
||||||
EmbedColor.SoftError
|
) {
|
||||||
));
|
embeds.push(
|
||||||
|
embed(
|
||||||
|
`\`${member.user?.username}\` has an equally or higher ranked role than you; refusing to ban.`,
|
||||||
|
"Missing permission",
|
||||||
|
EmbedColor.SoftError
|
||||||
|
)
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (member && !member.bannable) {
|
if (member && !member.bannable) {
|
||||||
embeds.push(embed(
|
embeds.push(
|
||||||
`I don't have permission to ban \`${member?.user?.username || user._id}\`.`,
|
embed(
|
||||||
null,
|
`I don't have permission to ban \`${
|
||||||
EmbedColor.SoftError
|
member?.user?.username || user._id
|
||||||
));
|
}\`.`,
|
||||||
|
null,
|
||||||
|
EmbedColor.SoftError
|
||||||
|
)
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (serverConfig?.dmOnKick) {
|
if (serverConfig?.dmOnKick) {
|
||||||
try {
|
try {
|
||||||
const embed = generateInfractionDMEmbed(message.serverContext, serverConfig, infraction, message);
|
const embed = generateInfractionDMEmbed(
|
||||||
|
message.serverContext,
|
||||||
|
serverConfig,
|
||||||
|
infraction,
|
||||||
|
message
|
||||||
|
);
|
||||||
const dmChannel = await getDmChannel(user);
|
const dmChannel = await getDmChannel(user);
|
||||||
|
|
||||||
if (dmChannel.havePermission('SendMessage') || dmChannel.havePermission('SendEmbeds')) {
|
if (
|
||||||
await dmChannel.sendMessage({ embeds: [ embed ] });
|
dmChannel.havePermission("SendMessage") ||
|
||||||
}
|
dmChannel.havePermission("SendEmbeds")
|
||||||
else logger.warn('Missing permission to DM user.');
|
) {
|
||||||
} catch(e) {
|
await dmChannel.sendMessage({
|
||||||
|
embeds: [embed],
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
logger.warn("Missing permission to DM user.");
|
||||||
|
} catch (e) {
|
||||||
console.error(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
|
||||||
|
})`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await logModAction('ban', message.serverContext, message.member!, user._id, reason, infraction._id, `Ban duration: **Permanent**`);
|
await logModAction(
|
||||||
|
"ban",
|
||||||
|
message.serverContext,
|
||||||
|
message.member!,
|
||||||
|
user._id,
|
||||||
|
reason,
|
||||||
|
infraction._id,
|
||||||
|
`Ban duration: **Permanent**`
|
||||||
|
);
|
||||||
|
|
||||||
embeds.push({
|
embeds.push({
|
||||||
title: `User ${Math.random() > 0.8 ? 'ejected' : 'banned'}`,
|
title: `User ${
|
||||||
icon_url: user instanceof User ? user.generateAvatarURL() : undefined,
|
Math.random() > 0.8 ? "ejected" : "banned"
|
||||||
|
}`,
|
||||||
|
icon_url:
|
||||||
|
user instanceof User
|
||||||
|
? user.generateAvatarURL()
|
||||||
|
: undefined,
|
||||||
colour: EmbedColor.Success,
|
colour: EmbedColor.Success,
|
||||||
description: `This is ${userWarnCount == 1 ? '**the first infraction**' : `infraction number **${userWarnCount}**`}` +
|
description:
|
||||||
|
`This is ${
|
||||||
|
userWarnCount == 1
|
||||||
|
? "**the first infraction**"
|
||||||
|
: `infraction number **${userWarnCount}**`
|
||||||
|
}` +
|
||||||
` for ${await fetchUsername(user._id)}.\n` +
|
` for ${await fetchUsername(user._id)}.\n` +
|
||||||
`**User ID:** \`${user._id}\`\n` +
|
`**User ID:** \`${user._id}\`\n` +
|
||||||
`**Infraction ID:** \`${infraction._id}\`\n` +
|
`**Infraction ID:** \`${infraction._id}\`\n` +
|
||||||
`**Reason:** \`${infraction.reason}\``
|
`**Reason:** \`${infraction.reason}\``,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const banUntil = Date.now() + banDuration;
|
const banUntil = Date.now() + banDuration;
|
||||||
|
@ -196,31 +327,47 @@ export default {
|
||||||
_id: infId,
|
_id: infId,
|
||||||
createdBy: message.author_id,
|
createdBy: message.author_id,
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
reason: (reason || 'No reason provided') + ` (${durationStr})`,
|
reason:
|
||||||
|
(reason || "No reason provided") +
|
||||||
|
` (${durationStr})`,
|
||||||
server: message.serverContext._id,
|
server: message.serverContext._id,
|
||||||
type: InfractionType.Manual,
|
type: InfractionType.Manual,
|
||||||
user: user._id,
|
user: user._id,
|
||||||
actionType: 'ban',
|
actionType: "ban",
|
||||||
expires: banUntil,
|
expires: banUntil,
|
||||||
}
|
};
|
||||||
const { userWarnCount } = await storeInfraction(infraction);
|
const { userWarnCount } = await storeInfraction(infraction);
|
||||||
|
|
||||||
if (serverConfig?.dmOnKick) {
|
if (serverConfig?.dmOnKick) {
|
||||||
try {
|
try {
|
||||||
const embed = generateInfractionDMEmbed(message.serverContext, serverConfig, infraction, message);
|
const embed = generateInfractionDMEmbed(
|
||||||
|
message.serverContext,
|
||||||
|
serverConfig,
|
||||||
|
infraction,
|
||||||
|
message
|
||||||
|
);
|
||||||
const dmChannel = await getDmChannel(user);
|
const dmChannel = await getDmChannel(user);
|
||||||
|
|
||||||
if (dmChannel.havePermission('SendMessage') || dmChannel.havePermission('SendEmbeds')) {
|
if (
|
||||||
await dmChannel.sendMessage({ embeds: [ embed ] });
|
dmChannel.havePermission("SendMessage") ||
|
||||||
}
|
dmChannel.havePermission("SendEmbeds")
|
||||||
else logger.warn('Missing permission to DM user.');
|
) {
|
||||||
} catch(e) {
|
await dmChannel.sendMessage({
|
||||||
|
embeds: [embed],
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
logger.warn("Missing permission to DM user.");
|
||||||
|
} catch (e) {
|
||||||
console.error(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})`,
|
||||||
});
|
});
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
@ -231,7 +378,7 @@ export default {
|
||||||
until: banUntil,
|
until: banUntil,
|
||||||
}),
|
}),
|
||||||
logModAction(
|
logModAction(
|
||||||
'ban',
|
"ban",
|
||||||
message.serverContext,
|
message.serverContext,
|
||||||
message.member!,
|
message.member!,
|
||||||
user._id,
|
user._id,
|
||||||
|
@ -243,23 +390,36 @@ export default {
|
||||||
|
|
||||||
embeds.push({
|
embeds.push({
|
||||||
title: `User temporarily banned`,
|
title: `User temporarily banned`,
|
||||||
icon_url: user instanceof User ? user.generateAvatarURL() : undefined,
|
icon_url:
|
||||||
|
user instanceof User
|
||||||
|
? user.generateAvatarURL()
|
||||||
|
: undefined,
|
||||||
colour: EmbedColor.Success,
|
colour: EmbedColor.Success,
|
||||||
description: `This is ${userWarnCount == 1 ? '**the first infraction**' : `infraction number **${userWarnCount}**`}` +
|
description:
|
||||||
|
`This is ${
|
||||||
|
userWarnCount == 1
|
||||||
|
? "**the first infraction**"
|
||||||
|
: `infraction number **${userWarnCount}**`
|
||||||
|
}` +
|
||||||
` for ${await fetchUsername(user._id)}.\n` +
|
` for ${await fetchUsername(user._id)}.\n` +
|
||||||
`**Ban duration:** ${banDurationFancy}\n` +
|
`**Ban duration:** ${banDurationFancy}\n` +
|
||||||
`**User ID:** \`${user._id}\`\n` +
|
`**User ID:** \`${user._id}\`\n` +
|
||||||
`**Infraction ID:** \`${infraction._id}\`\n` +
|
`**Infraction ID:** \`${infraction._id}\`\n` +
|
||||||
`**Reason:** \`${infraction.reason}\``
|
`**Reason:** \`${infraction.reason}\``,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
embeds.push(embed(
|
embeds.push(
|
||||||
`Failed to ban target \`${await fetchUsername(user._id, user._id)}\`: ${e}`,
|
embed(
|
||||||
'Failed to ban: An error has occurred',
|
`Failed to ban target \`${await fetchUsername(
|
||||||
EmbedColor.Error,
|
user._id,
|
||||||
));
|
user._id
|
||||||
|
)}\`: ${e}`,
|
||||||
|
"Failed to ban: An error has occurred",
|
||||||
|
EmbedColor.Error
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,11 +428,14 @@ export default {
|
||||||
const targetEmbeds = embeds.splice(0, 10);
|
const targetEmbeds = embeds.splice(0, 10);
|
||||||
|
|
||||||
if (firstMsg) {
|
if (firstMsg) {
|
||||||
await message.reply({ embeds: targetEmbeds, content: 'Operation completed.' }, false);
|
await message.reply(
|
||||||
|
{ embeds: targetEmbeds, content: "Operation completed." },
|
||||||
|
false
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await message.channel?.sendMessage({ embeds: targetEmbeds });
|
await message.channel?.sendMessage({ embeds: targetEmbeds });
|
||||||
}
|
}
|
||||||
firstMsg = false;
|
firstMsg = false;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
} as SimpleCommand;
|
} as SimpleCommand;
|
||||||
|
|
|
@ -8,58 +8,99 @@ import CommandCategory from "../../../struct/commands/CommandCategory";
|
||||||
import SimpleCommand from "../../../struct/commands/SimpleCommand";
|
import SimpleCommand from "../../../struct/commands/SimpleCommand";
|
||||||
import logger from "../../logger";
|
import logger from "../../logger";
|
||||||
import { fetchUsername, logModAction } from "../../modules/mod_logs";
|
import { fetchUsername, logModAction } from "../../modules/mod_logs";
|
||||||
import { dedupeArray, embed, EmbedColor, generateInfractionDMEmbed, getDmChannel, isModerator, NO_MANAGER_MSG, parseUser, parseUserOrId, sanitizeMessageContent, storeInfraction } from "../../util";
|
import {
|
||||||
|
dedupeArray,
|
||||||
|
embed,
|
||||||
|
EmbedColor,
|
||||||
|
generateInfractionDMEmbed,
|
||||||
|
getDmChannel,
|
||||||
|
getMembers,
|
||||||
|
isModerator,
|
||||||
|
NO_MANAGER_MSG,
|
||||||
|
parseUser,
|
||||||
|
parseUserOrId,
|
||||||
|
sanitizeMessageContent,
|
||||||
|
storeInfraction,
|
||||||
|
yesNoMessage,
|
||||||
|
} from "../../util";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'kick',
|
name: "kick",
|
||||||
aliases: [ 'yeet', 'vent' ],
|
aliases: ["yeet", "vent"],
|
||||||
description: 'Kick a member from the server',
|
description: "Kick a member from the server",
|
||||||
syntax: '/kick @username [reason?]',
|
syntax: "/kick @username [reason?]",
|
||||||
removeEmptyArgs: true,
|
removeEmptyArgs: true,
|
||||||
category: CommandCategory.Moderation,
|
category: CommandCategory.Moderation,
|
||||||
run: async (message, args, serverConfig) => {
|
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')) {
|
return await message.reply(
|
||||||
return await message.reply(`Sorry, I do not have \`KickMembers\` permission.`);
|
`Sorry, I do not have \`KickMembers\` permission.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const userInput = !message.reply_ids?.length ? args.shift() || '' : undefined;
|
const userInput = !message.reply_ids?.length
|
||||||
if (!userInput && !message.reply_ids?.length) return message.reply({ embeds: [
|
? args.shift() || ""
|
||||||
embed(
|
: undefined;
|
||||||
`Please specify one or more users by replying to their message while running this command or ` +
|
if (!userInput && !message.reply_ids?.length)
|
||||||
`by specifying a comma-separated list of usernames.`,
|
return message.reply({
|
||||||
'No target user specified',
|
embeds: [
|
||||||
EmbedColor.SoftError,
|
embed(
|
||||||
),
|
`Please specify one or more users by replying to their message while running this command or ` +
|
||||||
] });
|
`by specifying a comma-separated list of usernames.`,
|
||||||
|
"No target user specified",
|
||||||
let reason = args.join(' ')
|
EmbedColor.SoftError
|
||||||
?.replace(new RegExp('`', 'g'), '\'')
|
),
|
||||||
?.replace(new RegExp('\n', 'g'), ' ');
|
],
|
||||||
|
});
|
||||||
|
|
||||||
if (reason.length > 500) return message.reply({
|
let reason = args
|
||||||
embeds: [ embed('Kick reason may not be longer than 500 characters.', null, EmbedColor.SoftError) ]
|
.join(" ")
|
||||||
});
|
?.replace(new RegExp("`", "g"), "'")
|
||||||
|
?.replace(new RegExp("\n", "g"), " ");
|
||||||
|
|
||||||
|
if (reason.length > 500)
|
||||||
|
return message.reply({
|
||||||
|
embeds: [
|
||||||
|
embed(
|
||||||
|
"Kick reason may not be longer than 500 characters.",
|
||||||
|
null,
|
||||||
|
EmbedColor.SoftError
|
||||||
|
),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
const embeds: SendableEmbed[] = [];
|
const embeds: SendableEmbed[] = [];
|
||||||
const handledUsers: string[] = [];
|
const handledUsers: string[] = [];
|
||||||
const targetUsers: User|{ _id: string }[] = [];
|
const targetUsers: User | { _id: string }[] = [];
|
||||||
|
|
||||||
const targetInput = dedupeArray(
|
const targetInput = dedupeArray(
|
||||||
message.reply_ids?.length
|
message.reply_ids?.length
|
||||||
? (await Promise.allSettled(
|
? (
|
||||||
message.reply_ids.map(msg => message.channel?.fetchMessage(msg))
|
await Promise.allSettled(
|
||||||
))
|
message.reply_ids.map((msg) =>
|
||||||
.filter(m => m.status == 'fulfilled').map(m => (m as any).value.author_id)
|
message.channel?.fetchMessage(msg)
|
||||||
: userInput!.split(','),
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.filter((m) => m.status == "fulfilled")
|
||||||
|
.map((m) => (m as any).value.author_id)
|
||||||
|
: userInput!.split(",")
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const userStr of targetInput) {
|
for (const userStr of targetInput) {
|
||||||
try {
|
try {
|
||||||
let user = await parseUserOrId(userStr);
|
let user = await parseUserOrId(userStr);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
embeds.push(embed(`I can't resolve \`${sanitizeMessageContent(userStr).trim()}\` to a user.`, null, EmbedColor.SoftError));
|
embeds.push(
|
||||||
|
embed(
|
||||||
|
`I can't resolve \`${sanitizeMessageContent(
|
||||||
|
userStr
|
||||||
|
).trim()}\` to a user.`,
|
||||||
|
null,
|
||||||
|
EmbedColor.SoftError
|
||||||
|
)
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,33 +109,69 @@ export default {
|
||||||
handledUsers.push(user._id);
|
handledUsers.push(user._id);
|
||||||
|
|
||||||
if (user._id == message.author_id) {
|
if (user._id == message.author_id) {
|
||||||
embeds.push(embed('You might want to avoid kicking yourself...', null, EmbedColor.Warning));
|
embeds.push(
|
||||||
|
embed(
|
||||||
|
"You might want to avoid kicking yourself...",
|
||||||
|
null,
|
||||||
|
EmbedColor.Warning
|
||||||
|
)
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user._id == client.user!._id) {
|
if (user._id == client.user!._id) {
|
||||||
embeds.push(embed('I won\'t allow you to get rid of me this easily :trol:', null, EmbedColor.Warning));
|
embeds.push(
|
||||||
|
embed(
|
||||||
|
"I won't allow you to get rid of me this easily :trol:",
|
||||||
|
null,
|
||||||
|
EmbedColor.Warning
|
||||||
|
)
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
targetUsers.push(user);
|
targetUsers.push(user);
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
embeds.push(embed(
|
embeds.push(
|
||||||
`Failed to kick target \`${sanitizeMessageContent(userStr).trim()}\`: ${e}`,
|
embed(
|
||||||
`Failed to kick: An error has occurred`,
|
`Failed to kick target \`${sanitizeMessageContent(
|
||||||
EmbedColor.Error,
|
userStr
|
||||||
));
|
).trim()}\`: ${e}`,
|
||||||
|
`Failed to kick: An error has occurred`,
|
||||||
|
EmbedColor.Error
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const members = await message.serverContext.fetchMembers();
|
if (message.reply_ids?.length && targetUsers.length) {
|
||||||
|
let res = await yesNoMessage(
|
||||||
|
message.channel!,
|
||||||
|
message.author_id,
|
||||||
|
`This will kick the author${targetUsers.length > 1 ? 's' : ''} `
|
||||||
|
+ `of the message${message.reply_ids.length > 1 ? 's' : ''} you replied to.\n`
|
||||||
|
+ `The following user${targetUsers.length > 1 ? 's' : ''} will be affected: `
|
||||||
|
+ `${targetUsers.map(u => `<@${u._id}>`).join(', ')}.\n`
|
||||||
|
+ `Are you sure?`,
|
||||||
|
'Confirm action'
|
||||||
|
);
|
||||||
|
if (!res) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const members = getMembers(message.serverContext._id);
|
||||||
|
|
||||||
for (const user of targetUsers) {
|
for (const user of targetUsers) {
|
||||||
try {
|
try {
|
||||||
const member = members.members.find(m => m._id.user == user._id);
|
const member = members.find((m) => m._id.user == user._id);
|
||||||
if (!member) {
|
if (!member) {
|
||||||
embeds.push(embed(''));
|
embeds.push(
|
||||||
|
embed(
|
||||||
|
`\`${await fetchUsername(
|
||||||
|
user._id
|
||||||
|
)}\` is not a member of this server.`
|
||||||
|
)
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,45 +180,75 @@ export default {
|
||||||
_id: infId,
|
_id: infId,
|
||||||
createdBy: message.author_id,
|
createdBy: message.author_id,
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
reason: reason || 'No reason provided',
|
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) {
|
if (serverConfig?.dmOnKick) {
|
||||||
try {
|
try {
|
||||||
const embed = generateInfractionDMEmbed(message.serverContext, serverConfig, infraction, message);
|
const embed = generateInfractionDMEmbed(
|
||||||
|
message.serverContext,
|
||||||
|
serverConfig,
|
||||||
|
infraction,
|
||||||
|
message
|
||||||
|
);
|
||||||
const dmChannel = await getDmChannel(user);
|
const dmChannel = await getDmChannel(user);
|
||||||
|
|
||||||
if (dmChannel.havePermission('SendMessage') || dmChannel.havePermission('SendEmbeds')) {
|
if (
|
||||||
await dmChannel.sendMessage({ embeds: [ embed ] });
|
dmChannel.havePermission("SendMessage") ||
|
||||||
}
|
dmChannel.havePermission("SendEmbeds")
|
||||||
else logger.warn('Missing permission to DM user.');
|
) {
|
||||||
} catch(e) {
|
await dmChannel.sendMessage({ embeds: [embed] });
|
||||||
|
} else logger.warn("Missing permission to DM user.");
|
||||||
|
} catch (e) {
|
||||||
console.error(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
|
||||||
|
),
|
||||||
member.kick(),
|
member.kick(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
embeds.push({
|
embeds.push({
|
||||||
title: `User kicked`,
|
title: `User kicked`,
|
||||||
icon_url: user instanceof User ? user.generateAvatarURL() : undefined,
|
icon_url:
|
||||||
|
user instanceof User
|
||||||
|
? user.generateAvatarURL()
|
||||||
|
: undefined,
|
||||||
colour: EmbedColor.Success,
|
colour: EmbedColor.Success,
|
||||||
description: `This is ${userWarnCount == 1 ? '**the first infraction**' : `infraction number **${userWarnCount}**`}` +
|
description:
|
||||||
|
`This is ${
|
||||||
|
userWarnCount == 1
|
||||||
|
? "**the first infraction**"
|
||||||
|
: `infraction number **${userWarnCount}**`
|
||||||
|
}` +
|
||||||
` for ${await fetchUsername(user._id)}.\n` +
|
` for ${await fetchUsername(user._id)}.\n` +
|
||||||
`**User ID:** \`${user._id}\`\n` +
|
`**User ID:** \`${user._id}\`\n` +
|
||||||
`**Infraction ID:** \`${infraction._id}\`\n` +
|
`**Infraction ID:** \`${infraction._id}\`\n` +
|
||||||
`**Reason:** \`${infraction.reason}\``
|
`**Reason:** \`${infraction.reason}\``,
|
||||||
});
|
});
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
embeds.push(embed(`Failed to kick user ${await fetchUsername(user._id)}: ${e}`, 'Failed to kick user', EmbedColor.Error));
|
embeds.push(
|
||||||
|
embed(
|
||||||
|
`Failed to kick user ${await fetchUsername(
|
||||||
|
user._id
|
||||||
|
)}: ${e}`,
|
||||||
|
"Failed to kick user",
|
||||||
|
EmbedColor.Error
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,11 +257,14 @@ export default {
|
||||||
const targetEmbeds = embeds.splice(0, 10);
|
const targetEmbeds = embeds.splice(0, 10);
|
||||||
|
|
||||||
if (firstMsg) {
|
if (firstMsg) {
|
||||||
await message.reply({ embeds: targetEmbeds, content: 'Operation completed.' }, false);
|
await message.reply(
|
||||||
|
{ embeds: targetEmbeds, content: "Operation completed." },
|
||||||
|
false
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await message.channel?.sendMessage({ embeds: targetEmbeds });
|
await message.channel?.sendMessage({ embeds: targetEmbeds });
|
||||||
}
|
}
|
||||||
firstMsg = false;
|
firstMsg = false;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
} as SimpleCommand;
|
} as SimpleCommand;
|
||||||
|
|
|
@ -17,21 +17,25 @@ import { isSudo } from "./commands/admin/botadm";
|
||||||
import { SendableEmbed } from "revolt-api";
|
import { SendableEmbed } from "revolt-api";
|
||||||
import MessageCommandContext from "../struct/MessageCommandContext";
|
import MessageCommandContext from "../struct/MessageCommandContext";
|
||||||
import ServerConfig from "automod/dist/types/ServerConfig";
|
import ServerConfig from "automod/dist/types/ServerConfig";
|
||||||
|
import { ClientboundNotification } from "@janderedev/revolt.js";
|
||||||
|
|
||||||
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_HTTP_URI = /^http(s?):\/\//g;
|
||||||
const RE_MAILTO_URI = /^mailto:/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;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getAutumnURL() {
|
async function getAutumnURL() {
|
||||||
return autumn_url || ((await axios.get(client.apiURL)).data as any).features.autumn.url;
|
return (
|
||||||
|
autumn_url ||
|
||||||
|
((await axios.get(client.apiURL)).data as any).features.autumn.url
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,20 +44,20 @@ async function getAutumnURL() {
|
||||||
* @param text
|
* @param text
|
||||||
* @returns null if not found, otherwise user object
|
* @returns null if not found, otherwise user object
|
||||||
*/
|
*/
|
||||||
async function parseUser(text: string): Promise<User|null> {
|
async function parseUser(text: string): Promise<User | null> {
|
||||||
if (!text) return null;
|
if (!text) return null;
|
||||||
|
|
||||||
let uid: string|null = null;
|
let uid: string | null = null;
|
||||||
if (USER_MENTION_REGEX.test(text)) {
|
if (USER_MENTION_REGEX.test(text)) {
|
||||||
uid = text.replace(/<@|>/g, '').toUpperCase();
|
uid = text.replace(/<@|>/g, "").toUpperCase();
|
||||||
} else if (/^[0-9A-HJ-KM-NP-TV-Z]{26}$/gi.test(text)) {
|
} else if (/^[0-9A-HJ-KM-NP-TV-Z]{26}$/gi.test(text)) {
|
||||||
uid = text.toUpperCase();
|
uid = text.toUpperCase();
|
||||||
} else {
|
} else {
|
||||||
if (text.startsWith('@')) text = text.substr(1);
|
if (text.startsWith("@")) text = text.substr(1);
|
||||||
|
|
||||||
// Why is there no .find() or .filter()
|
// Why is there no .find() or .filter()
|
||||||
let user: User|null = null;
|
let user: User | null = null;
|
||||||
client.users.forEach(u => {
|
client.users.forEach((u) => {
|
||||||
if (u.username?.toLowerCase() == text.toLowerCase()) {
|
if (u.username?.toLowerCase() == text.toLowerCase()) {
|
||||||
user = u;
|
user = u;
|
||||||
}
|
}
|
||||||
|
@ -63,16 +67,20 @@ async function parseUser(text: string): Promise<User|null> {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (uid) return await client.users.fetch(uid) || null;
|
if (uid) return (await client.users.fetch(uid)) || null;
|
||||||
else return null;
|
else return null;
|
||||||
} catch(e) { return null; }
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Does the exact same as `parseUser`, but returns only `_id` instead
|
* Does the exact same as `parseUser`, but returns only `_id` instead
|
||||||
* of null if the user was not found and the input is also an ID
|
* of null if the user was not found and the input is also an ID
|
||||||
*/
|
*/
|
||||||
async function parseUserOrId(text: string): Promise<User|{_id: string}|null> {
|
async function parseUserOrId(
|
||||||
|
text: string
|
||||||
|
): Promise<User | { _id: string } | null> {
|
||||||
let parsed = await parseUser(text);
|
let parsed = await parseUser(text);
|
||||||
if (parsed) return parsed;
|
if (parsed) return parsed;
|
||||||
if (ULID_REGEX.test(text)) return { _id: text.toUpperCase() };
|
if (ULID_REGEX.test(text)) return { _id: text.toUpperCase() };
|
||||||
|
@ -80,63 +88,87 @@ async function parseUserOrId(text: string): Promise<User|{_id: string}|null> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function isModerator(message: Message, announceSudo?: boolean) {
|
async function isModerator(message: Message, announceSudo?: boolean) {
|
||||||
let member = message.member!, server = message.channel!.server!;
|
let member = message.member!,
|
||||||
|
server = message.channel!.server!;
|
||||||
|
|
||||||
if (hasPerm(member, 'KickMembers')) return true;
|
if (hasPerm(member, "KickMembers")) return true;
|
||||||
|
|
||||||
const [ isManager, mods, isSudo ] = await Promise.all([
|
const [isManager, mods, isSudo] = await Promise.all([
|
||||||
isBotManager(message),
|
isBotManager(message),
|
||||||
dbs.SERVERS.findOne({ id: server._id }),
|
dbs.SERVERS.findOne({ id: server._id }),
|
||||||
checkSudoPermission(message, announceSudo),
|
checkSudoPermission(message, announceSudo),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return isManager
|
return (
|
||||||
|| (mods?.moderators?.indexOf(member.user?._id!) ?? -1) > -1
|
isManager ||
|
||||||
|| isSudo;
|
(mods?.moderators?.indexOf(member.user?._id!) ?? -1) > -1 ||
|
||||||
|
isSudo
|
||||||
|
);
|
||||||
}
|
}
|
||||||
async function isBotManager(message: Message, announceSudo?: boolean) {
|
async function isBotManager(message: Message, announceSudo?: boolean) {
|
||||||
let member = message.member!, server = message.channel!.server!;
|
let member = message.member!,
|
||||||
|
server = message.channel!.server!;
|
||||||
|
|
||||||
if (hasPerm(member, 'ManageServer')) return true;
|
if (hasPerm(member, "ManageServer")) return true;
|
||||||
|
|
||||||
const [ managers, isSudo ] = await Promise.all([
|
const [managers, isSudo] = await Promise.all([
|
||||||
dbs.SERVERS.findOne({ id: server._id }),
|
dbs.SERVERS.findOne({ id: server._id }),
|
||||||
checkSudoPermission(message, announceSudo),
|
checkSudoPermission(message, announceSudo),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (managers?.botManagers?.indexOf(member.user?._id!) ?? -1) > -1
|
return (
|
||||||
|| isSudo;
|
(managers?.botManagers?.indexOf(member.user?._id!) ?? -1) > -1 || isSudo
|
||||||
|
);
|
||||||
}
|
}
|
||||||
async function checkSudoPermission(message: Message, announce?: boolean): Promise<boolean> {
|
async function checkSudoPermission(
|
||||||
|
message: Message,
|
||||||
|
announce?: boolean
|
||||||
|
): Promise<boolean> {
|
||||||
const hasPerm = isSudo(message.author!);
|
const hasPerm = isSudo(message.author!);
|
||||||
if (!hasPerm) return false;
|
if (!hasPerm) return false;
|
||||||
else {
|
else {
|
||||||
if (announce !== false) {
|
if (announce !== false) {
|
||||||
await message.reply(`# :unlock: Bypassed permission check\n`
|
await message.reply(
|
||||||
+ `Sudo mode is enabled for @${message.author!.username}.\n`);
|
`# :unlock: Bypassed permission check\n` +
|
||||||
|
`Sudo mode is enabled for @${message.author!.username}.\n`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async function getPermissionLevel(user: User|Member, server: Server): Promise<0|1|2|3> {
|
async function getPermissionLevel(
|
||||||
if (isSudo(user instanceof User ? user : (user.user || await client.users.fetch(user._id.user)))) return 3;
|
user: User | Member,
|
||||||
|
server: Server
|
||||||
|
): Promise<0 | 1 | 2 | 3> {
|
||||||
|
if (
|
||||||
|
isSudo(
|
||||||
|
user instanceof User
|
||||||
|
? user
|
||||||
|
: user.user || (await client.users.fetch(user._id.user))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return 3;
|
||||||
|
|
||||||
const member = user instanceof User ? await server.fetchMember(user) : user;
|
const member = user instanceof User ? await server.fetchMember(user) : user;
|
||||||
if (user instanceof Member) user = user.user!;
|
if (user instanceof Member) user = user.user!;
|
||||||
|
|
||||||
if (hasPerm(member, 'ManageServer')) return 3;
|
if (hasPerm(member, "ManageServer")) return 3;
|
||||||
|
|
||||||
const config = await dbs.SERVERS.findOne({ id: server._id });
|
const config = await dbs.SERVERS.findOne({ id: server._id });
|
||||||
|
|
||||||
if (config?.botManagers?.includes(user._id)) return 2;
|
if (config?.botManagers?.includes(user._id)) return 2;
|
||||||
if (config?.moderators?.includes(user._id) || hasPerm(member, 'KickMembers')) return 1;
|
if (
|
||||||
|
config?.moderators?.includes(user._id) ||
|
||||||
|
hasPerm(member, "KickMembers")
|
||||||
|
)
|
||||||
|
return 1;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPermissionBasedOnRole(member: Member): 0|1|2|3 {
|
function getPermissionBasedOnRole(member: Member): 0 | 1 | 2 | 3 {
|
||||||
if (hasPerm(member, 'ManageServer')) return 3;
|
if (hasPerm(member, "ManageServer")) return 3;
|
||||||
if (hasPerm(member, 'KickMembers')) return 1;
|
if (hasPerm(member, "KickMembers")) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,85 +185,117 @@ function hasPerm(member: Member, perm: keyof typeof Permission): boolean {
|
||||||
/**
|
/**
|
||||||
* @deprecated Unnecessary
|
* @deprecated Unnecessary
|
||||||
*/
|
*/
|
||||||
function hasPermForChannel(member: Member, channel: Channel, perm: keyof typeof Permission): boolean {
|
function hasPermForChannel(
|
||||||
if (!member.server) throw 'hasPermForChannel(): Server is undefined';
|
member: Member,
|
||||||
|
channel: Channel,
|
||||||
|
perm: keyof typeof Permission
|
||||||
|
): boolean {
|
||||||
|
if (!member.server) throw "hasPermForChannel(): Server is undefined";
|
||||||
return member.hasPermission(channel, perm);
|
return member.hasPermission(channel, perm);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getOwnMemberInServer(server: Server): Promise<Member> {
|
async function getOwnMemberInServer(server: Server): Promise<Member> {
|
||||||
return client.members.getKey({ server: server._id, user: client.user!._id })
|
return (
|
||||||
|| await server.fetchMember(client.user!._id);
|
client.members.getKey({ server: server._id, user: client.user!._id }) ||
|
||||||
|
(await server.fetchMember(client.user!._id))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function storeInfraction(infraction: Infraction): Promise<{ userWarnCount: number }> {
|
async function storeInfraction(
|
||||||
|
infraction: Infraction
|
||||||
|
): Promise<{ userWarnCount: number }> {
|
||||||
let r = await Promise.all([
|
let r = await Promise.all([
|
||||||
dbs.INFRACTIONS.insert(infraction, { castIds: false }),
|
dbs.INFRACTIONS.insert(infraction, { castIds: false }),
|
||||||
dbs.INFRACTIONS.find({
|
dbs.INFRACTIONS.find({
|
||||||
server: infraction.server,
|
server: infraction.server,
|
||||||
user: infraction.user,
|
user: infraction.user,
|
||||||
_id: { $not: { $eq: infraction._id } } },
|
_id: { $not: { $eq: infraction._id } },
|
||||||
),
|
}),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return { userWarnCount: (r[1].length ?? 0) + 1 }
|
return { userWarnCount: (r[1].length ?? 0) + 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadFile(file: any, filename: string): Promise<string> {
|
async function uploadFile(file: any, filename: string): Promise<string> {
|
||||||
let data = new FormData();
|
let data = new FormData();
|
||||||
data.append("file", file, { filename: filename });
|
data.append("file", file, { filename: filename });
|
||||||
|
|
||||||
let req = await axios.post(await getAutumnURL() + '/attachments', data, { headers: data.getHeaders() });
|
let req = await axios.post((await getAutumnURL()) + "/attachments", data, {
|
||||||
return (req.data as any)['id'] as string;
|
headers: data.getHeaders(),
|
||||||
|
});
|
||||||
|
return (req.data as any)["id"] as string;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendLogMessage(config: LogConfig, content: LogMessage) {
|
async function sendLogMessage(config: LogConfig, content: LogMessage) {
|
||||||
if (config.discord?.webhookUrl) {
|
if (config.discord?.webhookUrl) {
|
||||||
let c = { ...content, ...content.overrides?.discord }
|
let c = { ...content, ...content.overrides?.discord };
|
||||||
|
|
||||||
const embed = new MessageEmbed();
|
const embed = new MessageEmbed();
|
||||||
if (c.title) embed.setTitle(content.title);
|
if (c.title) embed.setTitle(content.title);
|
||||||
if (c.description) embed.setDescription(c.description);
|
if (c.description) embed.setDescription(c.description);
|
||||||
if (c.color?.match(/^#[0-9a-fA-F]+$/)) embed.setColor(c.color as ColorResolvable);
|
if (c.color?.match(/^#[0-9a-fA-F]+$/))
|
||||||
|
embed.setColor(c.color as ColorResolvable);
|
||||||
if (c.fields?.length) {
|
if (c.fields?.length) {
|
||||||
for (const field of c.fields) {
|
for (const field of c.fields) {
|
||||||
embed.addField(field.title, field.content.trim() || "\u200b", field.inline);
|
embed.addField(
|
||||||
|
field.title,
|
||||||
|
field.content.trim() || "\u200b",
|
||||||
|
field.inline
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (content.image) {
|
if (content.image) {
|
||||||
if (content.image.type == 'THUMBNAIL') embed.setThumbnail(content.image.url);
|
if (content.image.type == "THUMBNAIL")
|
||||||
else if (content.image.type == 'BIG') embed.setImage(content.image.url);
|
embed.setThumbnail(content.image.url);
|
||||||
|
else if (content.image.type == "BIG")
|
||||||
|
embed.setImage(content.image.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.attachments?.length) {
|
if (content.attachments?.length) {
|
||||||
embed.setFooter(`Attachments: ${content.attachments.map(a => a.name).join(', ')}`);
|
embed.setFooter(
|
||||||
|
`Attachments: ${content.attachments
|
||||||
|
.map((a) => a.name)
|
||||||
|
.join(", ")}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = new FormData();
|
let data = new FormData();
|
||||||
content.attachments?.forEach(a => {
|
content.attachments?.forEach((a) => {
|
||||||
data.append(`files[${ulid()}]`, a.content, { filename: a.name });
|
data.append(`files[${ulid()}]`, a.content, { filename: a.name });
|
||||||
});
|
});
|
||||||
|
|
||||||
data.append("payload_json", JSON.stringify({ embeds: [ embed.toJSON() ] }), { contentType: 'application/json' });
|
data.append(
|
||||||
|
"payload_json",
|
||||||
|
JSON.stringify({ embeds: [embed.toJSON()] }),
|
||||||
|
{ contentType: "application/json" }
|
||||||
|
);
|
||||||
|
|
||||||
axios.post(config.discord.webhookUrl, data, {headers: data.getHeaders() })
|
axios
|
||||||
.catch(e => logger.error(`Failed to send log message (discord): ${e}`));
|
.post(config.discord.webhookUrl, data, {
|
||||||
|
headers: data.getHeaders(),
|
||||||
|
})
|
||||||
|
.catch((e) =>
|
||||||
|
logger.error(`Failed to send log message (discord): ${e}`)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.revolt?.channel) {
|
if (config.revolt?.channel) {
|
||||||
let c = { ...content, ...content.overrides?.revolt };
|
let c = { ...content, ...content.overrides?.revolt };
|
||||||
try {
|
try {
|
||||||
const channel = client.channels.get(config.revolt.channel) || await client.channels.fetch(config.revolt.channel);
|
const channel =
|
||||||
|
client.channels.get(config.revolt.channel) ||
|
||||||
|
(await client.channels.fetch(config.revolt.channel));
|
||||||
|
|
||||||
let message = '';
|
let message = "";
|
||||||
let embed: SendableEmbed|undefined = undefined;
|
let embed: SendableEmbed | undefined = undefined;
|
||||||
switch(config.revolt.type) {
|
switch (config.revolt.type) {
|
||||||
case 'EMBED':
|
case "EMBED":
|
||||||
c = { ...c, ...content.overrides?.revoltEmbed };
|
c = { ...c, ...content.overrides?.revoltEmbed };
|
||||||
embed = {
|
embed = {
|
||||||
title: c.title,
|
title: c.title,
|
||||||
description: c.description,
|
description: c.description,
|
||||||
colour: c.color,
|
colour: c.color,
|
||||||
}
|
};
|
||||||
|
|
||||||
if (c.fields?.length) {
|
if (c.fields?.length) {
|
||||||
for (const field of c.fields) {
|
for (const field of c.fields) {
|
||||||
|
@ -241,35 +305,55 @@ async function sendLogMessage(config: LogConfig, content: LogMessage) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default: // QUOTEBLOCK, PLAIN or unspecified
|
default: // QUOTEBLOCK, PLAIN or unspecified
|
||||||
|
// Wrap entire message in quotes
|
||||||
// please disregard this mess
|
// please disregard this mess
|
||||||
|
|
||||||
c = { ...c, ...content.overrides?.revoltQuoteblock };
|
c = { ...c, ...content.overrides?.revoltQuoteblock };
|
||||||
const quote = config.revolt.type == 'PLAIN' ? '' : '>';
|
const quote = config.revolt.type == "PLAIN" ? "" : ">";
|
||||||
|
|
||||||
if (c.title) message += `## ${c.title}\n`;
|
if (c.title) message += `## ${c.title}\n`;
|
||||||
if (c.description) message += `${c.description}\n`;
|
if (c.description) message += `${c.description}\n`;
|
||||||
if (c.fields?.length) {
|
if (c.fields?.length) {
|
||||||
for (const field of c.fields) {
|
for (const field of c.fields) {
|
||||||
message += `${quote ? '\u200b\n' : ''}${quote}### ${field.title}\n` +
|
message +=
|
||||||
`${quote}${field.content.trim().split('\n').join('\n' + quote)}\n${quote ? '\n' : ''}`;
|
`${quote ? "\u200b\n" : ""}${quote}### ${
|
||||||
|
field.title
|
||||||
|
}\n` +
|
||||||
|
`${quote}${field.content
|
||||||
|
.trim()
|
||||||
|
.split("\n")
|
||||||
|
.join("\n" + quote)}\n${quote ? "\n" : ""}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
message = message.trim().split('\n').join('\n' + quote); // Wrap entire message in quotes
|
message = message
|
||||||
if (c.image?.url) message += `\n[Attachment](${c.image.url})`;
|
.trim()
|
||||||
|
.split("\n")
|
||||||
|
.join("\n" + quote);
|
||||||
|
if (c.image?.url)
|
||||||
|
message += `\n[Attachment](${c.image.url})`;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
channel.sendMessage({
|
channel
|
||||||
content: message,
|
.sendMessage({
|
||||||
embeds: embed ? [ embed ] : undefined,
|
content: message,
|
||||||
attachments: content.attachments ?
|
embeds: embed ? [embed] : undefined,
|
||||||
await Promise.all(content.attachments?.map(a => uploadFile(a.content, a.name))) :
|
attachments: content.attachments
|
||||||
undefined
|
? await Promise.all(
|
||||||
}).catch(e => logger.error(`Failed to send log message (revolt): ${e}`));
|
content.attachments?.map((a) =>
|
||||||
} catch(e) {
|
uploadFile(a.content, a.name)
|
||||||
logger.error(`Failed to send log message in ${config.revolt.channel}: ${e}`);
|
)
|
||||||
|
)
|
||||||
|
: undefined,
|
||||||
|
})
|
||||||
|
.catch((e) =>
|
||||||
|
logger.error(`Failed to send log message (revolt): ${e}`)
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(
|
||||||
|
`Failed to send log message in ${config.revolt.channel}: ${e}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,17 +362,17 @@ async function sendLogMessage(config: LogConfig, content: LogMessage) {
|
||||||
* Attempts to escape a message's markdown content (qoutes, headers, **bold** / *italic*, etc)
|
* Attempts to escape a message's markdown content (qoutes, headers, **bold** / *italic*, etc)
|
||||||
*/
|
*/
|
||||||
function sanitizeMessageContent(msg: string): string {
|
function sanitizeMessageContent(msg: string): string {
|
||||||
let str = '';
|
let str = "";
|
||||||
for (let line of msg.split('\n')) {
|
for (let line of msg.split("\n")) {
|
||||||
|
|
||||||
line = line.trim();
|
line = line.trim();
|
||||||
|
|
||||||
if (line.startsWith('#') || // headers
|
if (
|
||||||
line.startsWith('>') || // quotes
|
line.startsWith("#") || // headers
|
||||||
line.startsWith('|') || // tables
|
line.startsWith(">") || // quotes
|
||||||
line.startsWith('*') || // unordered lists
|
line.startsWith("|") || // tables
|
||||||
line.startsWith('-') || // ^
|
line.startsWith("*") || // unordered lists
|
||||||
line.startsWith('+') // ^
|
line.startsWith("-") || // ^
|
||||||
|
line.startsWith("+") // ^
|
||||||
) {
|
) {
|
||||||
line = `\\${line}`;
|
line = `\\${line}`;
|
||||||
}
|
}
|
||||||
|
@ -299,14 +383,17 @@ function sanitizeMessageContent(msg: string): string {
|
||||||
line = `\u200b${line}`;
|
line = `\u200b${line}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const char of ['_', '!!', '~', '`', '*', '^', '$']) {
|
for (const char of ["_", "!!", "~", "`", "*", "^", "$"]) {
|
||||||
line = line.replace(new RegExp(`(?<!\\\\)\\${char}`, 'g'), `\\${char}`);
|
line = line.replace(
|
||||||
|
new RegExp(`(?<!\\\\)\\${char}`, "g"),
|
||||||
|
`\\${char}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mentions
|
// Mentions
|
||||||
line = line.replace(/<@/g, `<\\@`);
|
line = line.replace(/<@/g, `<\\@`);
|
||||||
|
|
||||||
str += line + '\n';
|
str += line + "\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
|
@ -319,12 +406,16 @@ enum EmbedColor {
|
||||||
Success = "var(--success)",
|
Success = "var(--success)",
|
||||||
}
|
}
|
||||||
|
|
||||||
function embed(content: string, title?: string|null, color?: string|EmbedColor): SendableEmbed {
|
function embed(
|
||||||
|
content: string,
|
||||||
|
title?: string | null,
|
||||||
|
color?: string | EmbedColor
|
||||||
|
): SendableEmbed {
|
||||||
return {
|
return {
|
||||||
description: content,
|
description: content,
|
||||||
title: title,
|
title: title,
|
||||||
colour: color,
|
colour: color,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function dedupeArray<T>(...arrays: T[][]): T[] {
|
function dedupeArray<T>(...arrays: T[][]): T[] {
|
||||||
|
@ -342,65 +433,191 @@ function dedupeArray<T>(...arrays: T[][]): T[] {
|
||||||
function getMutualServers(user: User) {
|
function getMutualServers(user: User) {
|
||||||
const servers: Server[] = [];
|
const servers: Server[] = [];
|
||||||
for (const member of client.members) {
|
for (const member of client.members) {
|
||||||
if (member[1]._id.user == user._id && member[1].server) servers.push(member[1].server);
|
if (member[1]._id.user == user._id && member[1].server)
|
||||||
|
servers.push(member[1].server);
|
||||||
}
|
}
|
||||||
return servers;
|
return servers;
|
||||||
}
|
}
|
||||||
|
|
||||||
const awaitClient = () => new Promise<void>(async resolve => {
|
const awaitClient = () =>
|
||||||
if (!client.user) client.once('ready', () => resolve());
|
new Promise<void>(async (resolve) => {
|
||||||
else resolve();
|
if (!client.user) client.once("ready", () => resolve());
|
||||||
});
|
else resolve();
|
||||||
|
});
|
||||||
|
|
||||||
const getDmChannel = async (user: string|{_id: string}|User) => {
|
const getDmChannel = async (user: string | { _id: string } | User) => {
|
||||||
if (typeof user == 'string') user = client.users.get(user) || await client.users.fetch(user);
|
if (typeof user == "string")
|
||||||
if (!(user instanceof User)) user = client.users.get(user._id) || await client.users.fetch(user._id);
|
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(
|
return (
|
||||||
c => c[1].channel_type == 'DirectMessage' && c[1].recipient?._id == (user as User)._id
|
Array.from(client.channels).find(
|
||||||
)?.[1] || await (user as User).openDM();
|
(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 generateInfractionDMEmbed = (
|
||||||
|
server: Server,
|
||||||
|
serverConfig: ServerConfig,
|
||||||
|
infraction: Infraction,
|
||||||
|
message: MessageCommandContext
|
||||||
|
) => {
|
||||||
const embed: SendableEmbed = {
|
const embed: SendableEmbed = {
|
||||||
title: message.serverContext.name,
|
title: message.serverContext.name,
|
||||||
icon_url: message.serverContext.generateIconURL({ max_side: 128 }),
|
icon_url: message.serverContext.generateIconURL({ max_side: 128 }),
|
||||||
colour: '#ff9e2f',
|
colour: "#ff9e2f",
|
||||||
url: message.url,
|
url: message.url,
|
||||||
description: 'You have been ' +
|
description:
|
||||||
|
"You have been " +
|
||||||
(infraction.actionType
|
(infraction.actionType
|
||||||
? `**${infraction.actionType == 'ban' ? 'banned' : 'kicked'}** from `
|
? `**${
|
||||||
|
infraction.actionType == "ban" ? "banned" : "kicked"
|
||||||
|
}** from `
|
||||||
: `**warned** in `) +
|
: `**warned** in `) +
|
||||||
`'${sanitizeMessageContent(message.serverContext.name).trim()}' <t:${Math.round(infraction.date / 1000)}:R>.\n` +
|
`'${sanitizeMessageContent(
|
||||||
|
message.serverContext.name
|
||||||
|
).trim()}' <t:${Math.round(infraction.date / 1000)}:R>.\n` +
|
||||||
`**Reason:** ${infraction.reason}\n` +
|
`**Reason:** ${infraction.reason}\n` +
|
||||||
`**Moderator:** [@${sanitizeMessageContent(message.author?.username || 'Unknown')}](/@${message.author_id})\n` +
|
`**Moderator:** [@${sanitizeMessageContent(
|
||||||
|
message.author?.username || "Unknown"
|
||||||
|
)}](/@${message.author_id})\n` +
|
||||||
`**Infraction ID:** \`${infraction._id}\`` +
|
`**Infraction ID:** \`${infraction._id}\`` +
|
||||||
(infraction.actionType == 'ban' && infraction.expires
|
(infraction.actionType == "ban" && infraction.expires
|
||||||
? (infraction.expires == Infinity
|
? infraction.expires == Infinity
|
||||||
? '\n**Ban duration:** Permanent'
|
? "\n**Ban duration:** Permanent"
|
||||||
: `\n**Ban expires** <t:${Math.round(infraction.expires / 1000)}:R>`)
|
: `\n**Ban expires** <t:${Math.round(
|
||||||
: '') +
|
infraction.expires / 1000
|
||||||
(infraction.actionType == 'ban'
|
)}:R>`
|
||||||
? '\n\n**Reminder:** Circumventing this ban by using another account is a violation of the Revolt [Terms of Service](<https://revolt.chat/terms>) ' +
|
: "") +
|
||||||
'and may result in your accounts getting suspended from the platform.'
|
(infraction.actionType == "ban"
|
||||||
: '')
|
? "\n\n**Reminder:** Circumventing this ban by using another account is a violation of the Revolt [Terms of Service](<https://revolt.chat/terms>) " +
|
||||||
}
|
"and may result in your accounts getting suspended from the platform."
|
||||||
|
: ""),
|
||||||
|
};
|
||||||
|
|
||||||
if (serverConfig.contact) {
|
if (serverConfig.contact) {
|
||||||
if (RE_MAILTO_URI.test(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 ` +
|
embed.description +=
|
||||||
`[${serverConfig.contact.replace(RE_MAILTO_URI, '')}](${serverConfig.contact}).`
|
`\n\nIf you wish to appeal this decision, you may contact the server's moderation team at ` +
|
||||||
}
|
`[${serverConfig.contact.replace(RE_MAILTO_URI, "")}](${
|
||||||
else if (RE_HTTP_URI.test(serverConfig.contact)) {
|
serverConfig.contact
|
||||||
embed.description += `\n\nIf you wish to appeal this decision, you may do so [here](${serverConfig.contact}).`
|
}).`;
|
||||||
}
|
} else if (RE_HTTP_URI.test(serverConfig.contact)) {
|
||||||
else {
|
embed.description += `\n\nIf you wish to appeal this decision, you may do so [here](${serverConfig.contact}).`;
|
||||||
|
} else {
|
||||||
embed.description += `\n\n${serverConfig.contact}`;
|
embed.description += `\n\n${serverConfig.contact}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return embed;
|
return embed;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
// Copied from https://github.com/janderedev/feeds-bot/blob/master/src/util.ts
|
||||||
|
const yesNoMessage = (
|
||||||
|
channel: Channel,
|
||||||
|
allowedUser: string,
|
||||||
|
message: string,
|
||||||
|
title?: string,
|
||||||
|
messageYes?: string,
|
||||||
|
messageNo?: string
|
||||||
|
): Promise<boolean> =>
|
||||||
|
new Promise(async (resolve, reject) => {
|
||||||
|
const EMOJI_YES = "✅",
|
||||||
|
EMOJI_NO = "❌";
|
||||||
|
try {
|
||||||
|
const msg = await channel.sendMessage({
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
colour: "var(--status-streaming)",
|
||||||
|
title: title,
|
||||||
|
description: message,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
interactions: {
|
||||||
|
reactions: [EMOJI_YES, EMOJI_NO],
|
||||||
|
restrict_reactions: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
let destroyed = false;
|
||||||
|
const cb = async (packet: ClientboundNotification) => {
|
||||||
|
if (packet.type != "MessageReact") return;
|
||||||
|
if (packet.id != msg._id) return;
|
||||||
|
if (packet.user_id != allowedUser) return;
|
||||||
|
|
||||||
|
switch (packet.emoji_id) {
|
||||||
|
case EMOJI_YES:
|
||||||
|
channel.client.removeListener("packet", cb);
|
||||||
|
destroyed = true;
|
||||||
|
resolve(true);
|
||||||
|
msg.edit({
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
colour: "var(--success)",
|
||||||
|
title: title,
|
||||||
|
description: `${EMOJI_YES} ${
|
||||||
|
messageYes ?? "Confirmed!"
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).catch((e) => console.error(e));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case EMOJI_NO:
|
||||||
|
channel.client.removeListener("packet", cb);
|
||||||
|
destroyed = true;
|
||||||
|
resolve(false);
|
||||||
|
msg.edit({
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
colour: "var(--error)",
|
||||||
|
title: title,
|
||||||
|
description: `${EMOJI_NO} ${
|
||||||
|
messageNo ?? "Cancelled."
|
||||||
|
}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).catch((e) => console.error(e));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
logger.warn(
|
||||||
|
"Received unexpected reaction: " + packet.emoji_id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
channel.client.on("packet", cb);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!destroyed) {
|
||||||
|
resolve(false);
|
||||||
|
channel.client.removeListener("packet", cb);
|
||||||
|
msg.edit({
|
||||||
|
embeds: [
|
||||||
|
{
|
||||||
|
colour: "var(--error)",
|
||||||
|
title: title,
|
||||||
|
description: `${EMOJI_NO} Timed out`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).catch((e) => console.error(e));
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get all cached members of a server. Whoever put STRINGIFIED JSON as map keys is now on my hit list.
|
||||||
|
const getMembers = (id: string) =>
|
||||||
|
Array.from(client.members.entries())
|
||||||
|
.filter((item) => item[0].includes(`"${id}"`))
|
||||||
|
.map((entry) => entry[1]);
|
||||||
|
|
||||||
export {
|
export {
|
||||||
getAutumnURL,
|
getAutumnURL,
|
||||||
|
@ -423,9 +640,11 @@ export {
|
||||||
getMutualServers,
|
getMutualServers,
|
||||||
getDmChannel,
|
getDmChannel,
|
||||||
generateInfractionDMEmbed,
|
generateInfractionDMEmbed,
|
||||||
|
yesNoMessage,
|
||||||
|
getMembers,
|
||||||
EmbedColor,
|
EmbedColor,
|
||||||
NO_MANAGER_MSG,
|
NO_MANAGER_MSG,
|
||||||
ULID_REGEX,
|
ULID_REGEX,
|
||||||
USER_MENTION_REGEX,
|
USER_MENTION_REGEX,
|
||||||
CHANNEL_MENTION_REGEX,
|
CHANNEL_MENTION_REGEX,
|
||||||
}
|
};
|
||||||
|
|
Loading…
Reference in a new issue