286 lines
13 KiB
TypeScript
286 lines
13 KiB
TypeScript
import SimpleCommand from "../../struct/commands/SimpleCommand";
|
|
import MessageCommandContext from "../../struct/MessageCommandContext";
|
|
import { client, dbs } from "../..";
|
|
import { commands, DEFAULT_PREFIX, ownerIDs } from "../modules/command_handler";
|
|
import child_process from 'child_process';
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { wordlist } from "../modules/user_scan";
|
|
import { User } from "@janderedev/revolt.js/dist/maps/Users";
|
|
import { adminBotLog } from "../logging";
|
|
import CommandCategory from "../../struct/commands/CommandCategory";
|
|
import { parseUserOrId } from "../util";
|
|
import { Permission } from "@janderedev/revolt.js/dist/permissions/definitions";
|
|
|
|
const BLACKLIST_BAN_REASON = `This user is globally blacklisted and has been banned automatically. If you wish to opt out of the global blacklist, run '/botctl ignore_blacklist yes'.`;
|
|
const BLACKLIST_MESSAGE = (username: string) => `\`@${username}\` has been banned automatically. Check the ban reason for more info.`;
|
|
|
|
// id: expireDate
|
|
const sudoOverrides: { [key: string]: number|null } = {}
|
|
|
|
const isSudo = (user: User): boolean => {
|
|
return !!(sudoOverrides[user._id] && sudoOverrides[user._id]! > Date.now());
|
|
}
|
|
|
|
const updateSudoTimeout = (user: User) => {
|
|
sudoOverrides[user._id] = Date.now() + (1000 * 60 * 5);
|
|
}
|
|
|
|
const getCommitHash = (): Promise<string|null> => new Promise((resolve) => {
|
|
child_process.exec('git rev-parse HEAD', (err, stdout) => {
|
|
if (err?.code) resolve(null); else resolve(stdout);
|
|
});
|
|
});
|
|
|
|
const SUBCOMMANDS: string[] = [
|
|
'stats',
|
|
'sudo',
|
|
'userinfo',
|
|
'blacklist',
|
|
'unblacklist',
|
|
'blacklistreason',
|
|
'ignore',
|
|
'unignore',
|
|
];
|
|
|
|
export default {
|
|
name: 'botadm',
|
|
aliases: [ 'botadmin' ],
|
|
description: 'Bot administration',
|
|
removeEmptyArgs: true,
|
|
restrict: 'BOTOWNER',
|
|
category: CommandCategory.Owner,
|
|
run: async (message: MessageCommandContext, args: string[]) => {
|
|
if (!args.length) return message.reply('No subcommand specified. Available subcommands: ' + SUBCOMMANDS.join(', '));
|
|
|
|
try {
|
|
switch(args.shift()?.toLowerCase()) {
|
|
case 'stats': {
|
|
const pjson = JSON.parse((await fs.promises.readFile(path.join(process.cwd(), 'package.json'))).toString());
|
|
let msg = `# AutoMod stats\n`
|
|
+ `### Cache\n`
|
|
+ `Servers: \`${client.servers.size}\`\n`
|
|
+ `Channels: \`${client.channels.size}\`\n`
|
|
+ `Users: \`${client.users.size}\`\n`
|
|
+ `### Misc\n`
|
|
+ `Command count: \`${commands.length}\`\n`
|
|
+ `Environment: \`${process.env.NODE_ENV || 'testing'}\`\n`
|
|
+ `Commit hash: \`${await getCommitHash() || 'Unknown'}\`\n`
|
|
+ `### Packages\n`
|
|
+ `revolt.js: \`${pjson.dependencies['@janderedev/revolt.js']}\`\n`
|
|
+ `discord.js: \`${pjson.dependencies['discord.js']}\`\n`
|
|
+ `axios: \`${pjson.dependencies['axios']}\`\n`
|
|
+ `log75: \`${pjson.dependencies['log75']}\`\n`
|
|
+ `typescript: \`${pjson.devDependencies['typescript']}\`\n`
|
|
+ `### Connection\n`
|
|
+ `API Endpoint: \`${client.apiURL}\`\n`
|
|
+ `Heartbeat: \`${client.heartbeat}\`\n`
|
|
+ `Ping: \`${client.websocket.ping ?? 'Unknown'}\`\n`
|
|
+ `### Bot configuration\n`
|
|
+ `Owners: \`${ownerIDs.length}\` (${ownerIDs.join(', ')})\n`
|
|
+ `Wordlist loaded: \`${wordlist ? `Yes (${wordlist.length} line${wordlist.length == 1 ? '' : 's'})` : 'No'}\`\n`;
|
|
|
|
await message.reply(msg, false);
|
|
break;
|
|
}
|
|
|
|
case 'sudo': {
|
|
switch(args[0]?.toLowerCase()) {
|
|
case 'enable':
|
|
case 'on': {
|
|
if (isSudo(message.author!)) return message.reply('You are already in sudo mode!');
|
|
|
|
sudoOverrides[message.author_id] = Date.now() + (1000 * 60 * 5);
|
|
|
|
let msg = `# %emoji% Sudo mode enabled\n`
|
|
+ `In sudo mode, you will be able to run any command regardless of your server permissions.\n`
|
|
+ `Sudo mode will automatically expire **5 minutes** after your last bot interaction. `
|
|
+ `To disable now, run \`${DEFAULT_PREFIX}botadm sudo disable\`.`;
|
|
|
|
const sentMsg = await message.reply(msg.replace('%emoji%', ':lock:'), false);
|
|
setTimeout(() => sentMsg?.edit({ content: msg.replace('%emoji%', ':unlock:') }).catch(()=>{}), 200);
|
|
|
|
await adminBotLog({ type: 'WARN', message: `@${message.author!.username} has enabled sudo mode.` });
|
|
|
|
break;
|
|
}
|
|
|
|
case 'disable':
|
|
case 'off': {
|
|
if (!isSudo(message.author!)) return message.reply('You currently not in sudo mode.');
|
|
|
|
sudoOverrides[message.author_id] = null;
|
|
|
|
let msg = `# %emoji% Sudo mode disabled.`;
|
|
const sentMsg = await message.reply(msg.replace('%emoji%', ':unlock:'), false);
|
|
setTimeout(() => sentMsg?.edit({ content: msg.replace('%emoji%', ':lock:') }).catch(()=>{}), 200);
|
|
break;
|
|
}
|
|
|
|
case null:
|
|
case undefined:
|
|
case '': {
|
|
let msg = `# :unlock: Sudo mode\n`
|
|
+ `Sudo mode allows bot owners to bypass all permission checks for a limited time. `
|
|
+ `After activating, you will be able to run any command regardless of your server permissions.\n\n`
|
|
+ `To enable, run \`${DEFAULT_PREFIX}botadm sudo enable\`.\n`
|
|
+ `It will automatically be deactivated **5 minutes** after your last bot interaction.`;
|
|
|
|
await message.reply(msg, false);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
await message.reply('sudo: Unknown subcommand');
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 'userinfo': {
|
|
const target = await parseUserOrId(args.shift() || '');
|
|
if (!target) return message.reply('Specified user could not be found.');
|
|
|
|
const res = await dbs.USERS.findOne({ id: target._id });
|
|
|
|
if (!res) await message.reply(`Nothing stored about this user.`);
|
|
else await message.reply(`\`\`\`json\n${JSON.stringify(res, null, 4)}\n\`\`\``);
|
|
|
|
break;
|
|
}
|
|
|
|
case 'blacklist': {
|
|
const target = await parseUserOrId(args.shift() || '');
|
|
if (!target) return message.reply('Specified user could not be found.');
|
|
if (target._id == message.author_id) return message.reply(`no`);
|
|
|
|
await dbs.USERS.update({
|
|
id: target._id,
|
|
}, {
|
|
$setOnInsert: { id: target._id },
|
|
$set: { globalBlacklist: true }
|
|
}, { upsert: true });
|
|
|
|
try {
|
|
// Ban the user from all shared servers (unless those who opted out)
|
|
if (target instanceof User) {
|
|
const msg = await message.reply(`User update stored.`);
|
|
let bannedServers = 0;
|
|
|
|
const mutuals = await target.fetchMutual();
|
|
for (const serverid of mutuals.servers) {
|
|
const server = client.servers.get(serverid);
|
|
if (!server) continue;
|
|
|
|
if (server.havePermission('BanMembers')) {
|
|
const config = await dbs.SERVERS.findOne({ id: server._id });
|
|
if (config?.allowBlacklistedUsers) continue;
|
|
|
|
try {
|
|
await server.banUser(target._id, {
|
|
reason: BLACKLIST_BAN_REASON,
|
|
});
|
|
bannedServers++;
|
|
|
|
if (server.system_messages?.user_banned) {
|
|
const channel = server.channels.find(c => c!._id == server.system_messages!.user_banned);
|
|
if (channel && channel.havePermission('SendMessage')) {
|
|
await channel.sendMessage(BLACKLIST_MESSAGE(target.username));
|
|
}
|
|
}
|
|
} catch(e) {
|
|
console.error(`Failed to ban in ${serverid}: ${e}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bannedServers) {
|
|
msg?.edit({ content: `User update stored. User has been banned from ${bannedServers} servers.` });
|
|
}
|
|
} else await message.reply(`User update stored. No servers are currently shared with this user.`);
|
|
} catch(e) {
|
|
console.error(''+e);
|
|
await message.reply(`Failed to ban target from mutual servers: ${e}\n`);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case 'unblacklist': {
|
|
const target = await parseUserOrId(args.shift() || '');
|
|
if (!target) return message.reply('Specified user could not be found.');
|
|
|
|
await dbs.USERS.update({
|
|
id: target._id,
|
|
}, {
|
|
$setOnInsert: { id: target._id },
|
|
$set: { globalBlacklist: false }
|
|
}, { upsert: true });
|
|
|
|
await message.reply(`User update stored. Existing bans will not be lifted automatically.`);
|
|
|
|
break;
|
|
}
|
|
|
|
case 'blacklistreason': {
|
|
const target = await parseUserOrId(args.shift() || '');
|
|
if (!target) return message.reply('Specified user could not be found.');
|
|
|
|
await dbs.USERS.update({
|
|
id: target._id,
|
|
}, {
|
|
$setOnInsert: { id: target._id },
|
|
$set: { blacklistReason: args.join(' ') || undefined }
|
|
}, { upsert: true });
|
|
|
|
await message.reply(`User update stored.`);
|
|
|
|
break;
|
|
}
|
|
|
|
case 'ignore': {
|
|
const target = await parseUserOrId(args.shift() || '');
|
|
if (!target) return message.reply('Specified user could not be found.');
|
|
if (target._id == message.author_id) return message.reply(`no`);
|
|
|
|
await dbs.USERS.update(
|
|
{ id: target._id },
|
|
{
|
|
$setOnInsert: { id: target._id },
|
|
$set: { ignore: true },
|
|
},
|
|
{ upsert: true }
|
|
);
|
|
|
|
await message.reply(`User update stored.`);
|
|
|
|
break;
|
|
}
|
|
|
|
case 'unignore': {
|
|
const target = await parseUserOrId(args.shift() || '');
|
|
if (!target) return message.reply('Specified user could not be found.');
|
|
if (target._id == message.author_id) return message.reply(`no`);
|
|
|
|
await dbs.USERS.update(
|
|
{ id: target._id },
|
|
{
|
|
$setOnInsert: { id: target._id },
|
|
$set: { ignore: false },
|
|
},
|
|
{ upsert: true }
|
|
);
|
|
|
|
await message.reply(`User update stored.`);
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
message.reply('Unknown subcommand. Available subcommands: ' + SUBCOMMANDS.join(', '));
|
|
}
|
|
} catch(e) { console.error(e) }
|
|
}
|
|
} as SimpleCommand;
|
|
|
|
export { isSudo, updateSudoTimeout, BLACKLIST_BAN_REASON, BLACKLIST_MESSAGE }
|