added more owner-only commands

This commit is contained in:
janderedev 2022-01-13 22:53:34 +01:00
parent c2f5d889f5
commit c1b837b38c
Signed by: Lea
GPG key ID: 5D5E18ACB990F57A
16 changed files with 174 additions and 19 deletions

View file

@ -21,7 +21,7 @@ export default {
removeEmptyArgs: true, removeEmptyArgs: true,
category: 'moderation', category: 'moderation',
run: async (message: MessageCommandContext, args: string[]) => { run: async (message: MessageCommandContext, args: string[]) => {
if (!await isModerator(message.member!, message.serverContext)) if (!await isModerator(message))
return message.reply(NO_MANAGER_MSG); return message.reply(NO_MANAGER_MSG);
if (args.length == 0) if (args.length == 0)

136
src/bot/commands/botadm.ts Normal file
View file

@ -0,0 +1,136 @@
import Command from "../../struct/Command";
import MessageCommandContext from "../../struct/MessageCommandContext";
import { client } 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 "revolt.js/dist/maps/Users";
import { adminBotLog } from "../logging";
// id: expireDate
const sudoOverrides: { [key: string]: number|null } = {}
const isSudo = (user: User): boolean => {
console.log(sudoOverrides[user._id])
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',
];
export default {
name: 'botadm',
aliases: [ 'botadmin' ],
description: 'Bot administration',
removeEmptyArgs: true,
restrict: 'BOTOWNER',
category: 'moderation',
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['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;
}
default:
message.reply('Unknown subcommand. Available subcommands: ' + SUBCOMMANDS.join(', '));
}
} catch(e) { console.error(e) }
}
} as Command;
export { isSudo, updateSudoTimeout }

View file

@ -14,7 +14,7 @@ export default {
description: 'Perform administrative actions', description: 'Perform administrative actions',
category: 'configuration', category: 'configuration',
run: async (message: MessageCommandContext, args: string[]) => { run: async (message: MessageCommandContext, args: string[]) => {
if (!isBotManager(message.member!, message.serverContext)) return message.reply(NO_MANAGER_MSG); if (!isBotManager(message)) return message.reply(NO_MANAGER_MSG);
let action = args.shift(); let action = args.shift();
switch(action) { switch(action) {

View file

@ -16,7 +16,7 @@ export default {
removeEmptyArgs: true, removeEmptyArgs: true,
category: 'moderation', category: 'moderation',
run: async (message: MessageCommandContext, args: string[]) => { run: async (message: MessageCommandContext, args: string[]) => {
if (!await isModerator(message.member!, message.serverContext)) if (!await isModerator(message))
return message.reply(NO_MANAGER_MSG); return message.reply(NO_MANAGER_MSG);
if (args.length == 0) if (args.length == 0)

View file

@ -17,7 +17,7 @@ export default {
syntax: SYNTAX, syntax: SYNTAX,
category: 'configuration', category: 'configuration',
run: async (message: MessageCommandContext, args: string[]) => { run: async (message: MessageCommandContext, args: string[]) => {
if (!await isBotManager(message.member!, message.channel?.server!)) return message.reply(NO_MANAGER_MSG); if (!await isBotManager(message)) return message.reply(NO_MANAGER_MSG);
let config: ServerConfig = (await client.db.get('servers').findOne({ id: message.serverContext._id })) ?? {}; let config: ServerConfig = (await client.db.get('servers').findOne({ id: message.serverContext._id })) ?? {};
let mods = config.moderators ?? []; let mods = config.moderators ?? [];

View file

@ -20,7 +20,7 @@ export default {
switch(args[0]?.toLowerCase()) { switch(args[0]?.toLowerCase()) {
case 'set': case 'set':
if (!await isBotManager(message.member!, message.channel?.server!)) return message.reply(NO_MANAGER_MSG); if (!await isBotManager(message)) return message.reply(NO_MANAGER_MSG);
args.shift(); args.shift();
if (args.length == 0) return message.reply('You need to specify a prefix.'); if (args.length == 0) return message.reply('You need to specify a prefix.');
@ -43,7 +43,7 @@ export default {
break; break;
case 'clear': case 'clear':
case 'reset': case 'reset':
if (!await isBotManager(message.member!, message.channel?.server!)) return message.reply(NO_MANAGER_MSG); if (!await isBotManager(message)) return message.reply(NO_MANAGER_MSG);
if (config.prefix != null) { if (config.prefix != null) {
await client.db.get('servers').update({ 'id': message.channel?.server_id }, { $set: { 'prefix': null } }); await client.db.get('servers').update({ 'id': message.channel?.server_id }, { $set: { 'prefix': null } });

View file

@ -15,7 +15,7 @@ export default {
category: 'moderation', category: 'moderation',
run: async (message: MessageCommandContext, args: string[]) => { run: async (message: MessageCommandContext, args: string[]) => {
try { try {
if (!message.member || !await isModerator(message.member!, message.channel?.server!)) return message.reply('🔒 Access denied'); if (!message.member || !await isModerator(message)) return message.reply('🔒 Access denied');
let messages: Array<Message> = []; let messages: Array<Message> = [];
// X amount of messages from bottom // X amount of messages from bottom

View file

@ -14,7 +14,7 @@ export default {
description: 'Manage AutoMod\'s configuration', description: 'Manage AutoMod\'s configuration',
category: 'configuration', category: 'configuration',
run: async (message: MessageCommandContext, args: string[]) => { run: async (message: MessageCommandContext, args: string[]) => {
if (!isBotManager(message.member!, message.serverContext)) return message.reply(NO_MANAGER_MSG); if (!isBotManager(message)) return message.reply(NO_MANAGER_MSG);
return 'This feature is currently disabled'; return 'This feature is currently disabled';

View file

@ -13,7 +13,7 @@ export default {
syntax: '/unban [@user or ID]', syntax: '/unban [@user or ID]',
category: 'moderation', category: 'moderation',
run: async (message: MessageCommandContext, args: string[]) => { run: async (message: MessageCommandContext, args: string[]) => {
if (!isModerator(message.member!, message.serverContext)) return message.reply(NO_MANAGER_MSG); if (!isModerator(message)) return message.reply(NO_MANAGER_MSG);
let checkTempBans = async (id: string): Promise<number> => { let checkTempBans = async (id: string): Promise<number> => {
let tempbans: FindResult<TempBan> = await client.db.get('tempbans').find({ bannedUser: id, server: message.serverContext._id }); let tempbans: FindResult<TempBan> = await client.db.get('tempbans').find({ bannedUser: id, server: message.serverContext._id });

View file

@ -13,7 +13,7 @@ export default {
description: 'add an infraction to an user\'s record', description: 'add an infraction to an user\'s record',
category: 'moderation', category: 'moderation',
run: async (message: MessageCommandContext, args: string[]) => { run: async (message: MessageCommandContext, args: string[]) => {
if (!await isModerator(message.member!, message.serverContext)) return message.reply(NO_MANAGER_MSG); if (!await isModerator(message)) return message.reply(NO_MANAGER_MSG);
let user = await parseUserOrId(args.shift() ?? ''); let user = await parseUserOrId(args.shift() ?? '');
if (!user) return message.reply('I can\'t find that user.'); if (!user) return message.reply('I can\'t find that user.');
if ((user as any)?.bot != null) return message.reply('You cannot warn bots.'); if ((user as any)?.bot != null) return message.reply('You cannot warn bots.');

View file

@ -18,7 +18,7 @@ export default {
syntax: '/warns; /warns @username ["export-csv"]; /warns rm [ID]', syntax: '/warns; /warns @username ["export-csv"]; /warns rm [ID]',
category: 'moderation', category: 'moderation',
run: async (message: MessageCommandContext, args: string[]) => { run: async (message: MessageCommandContext, args: string[]) => {
if (!await isModerator(message.member!, message.serverContext)) return message.reply(NO_MANAGER_MSG); if (!await isModerator(message)) return message.reply(NO_MANAGER_MSG);
let collection = client.db.get('infractions'); let collection = client.db.get('infractions');
let infractions: Array<Infraction> = await collection.find({ let infractions: Array<Infraction> = await collection.find({

View file

@ -18,7 +18,7 @@ export default {
let config: ServerConfig = await client.db.get('servers').findOne({ id: message.serverContext._id }) || {} let config: ServerConfig = await client.db.get('servers').findOne({ id: message.serverContext._id }) || {}
if (!config.whitelist) config.whitelist = { users: [], roles: [], managers: true } if (!config.whitelist) config.whitelist = { users: [], roles: [], managers: true }
if (!isBotManager(message.member!, message.serverContext)) return message.reply(NO_MANAGER_MSG); if (!isBotManager(message)) return message.reply(NO_MANAGER_MSG);
let user: User|null, role: string|undefined; let user: User|null, role: string|undefined;
switch(args[0]?.toLowerCase()) { switch(args[0]?.toLowerCase()) {

View file

@ -30,7 +30,7 @@ async function antispam(message: Message): Promise<boolean> {
if (message.author?.bot != null) break; if (message.author?.bot != null) break;
if (serverRules.whitelist?.users?.includes(message.author_id)) break; if (serverRules.whitelist?.users?.includes(message.author_id)) break;
if (message.member?.roles?.filter(r => serverRules.whitelist?.roles?.includes(r)).length) break; if (message.member?.roles?.filter(r => serverRules.whitelist?.roles?.includes(r)).length) break;
if (serverRules.whitelist?.managers !== false && await isModerator(message.member!, message.channel?.server!)) break; if (serverRules.whitelist?.managers !== false && await isModerator(message)) break;
if (rule.channels?.indexOf(message.channel_id) == -1) break; if (rule.channels?.indexOf(message.channel_id) == -1) break;
let store = msgCountStore.get(rule.id)!; let store = msgCountStore.get(rule.id)!;

View file

@ -10,6 +10,7 @@ import MessageCommandContext from "../../struct/MessageCommandContext";
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { getOwnMemberInServer, hasPermForChannel } from "../util"; import { getOwnMemberInServer, hasPermForChannel } from "../util";
import { prepareMessage } from "./prepare_message"; import { prepareMessage } from "./prepare_message";
import { isSudo, updateSudoTimeout } from "../commands/botadm";
// thanks a lot esm // thanks a lot esm
const filename = fileURLToPath(import.meta.url); const filename = fileURLToPath(import.meta.url);
@ -68,6 +69,8 @@ let commands: Command[];
let cmd = commands.find(c => c.name == cmdName || (c.aliases?.indexOf(cmdName!) ?? -1) > -1); let cmd = commands.find(c => c.name == cmdName || (c.aliases?.indexOf(cmdName!) ?? -1) > -1);
if (!cmd) return; if (!cmd) return;
if (isSudo(msg.author!)) updateSudoTimeout(msg.author!);
if (cmd.restrict == 'BOTOWNER' && ownerIDs.indexOf(msg.author_id) == -1) { if (cmd.restrict == 'BOTOWNER' && ownerIDs.indexOf(msg.author_id) == -1) {
logger.warn(`User ${msg.author?.username} tried to run owner-only command: ${cmdName}`); logger.warn(`User ${msg.author?.username} tried to run owner-only command: ${cmdName}`);
msg.reply('🔒 Access denied'); msg.reply('🔒 Access denied');

View file

@ -191,4 +191,4 @@ new Promise((res: (value: void) => void) => client.user ? res() : client.once('r
}); });
}); });
export { scanServer }; export { scanServer, USERSCAN_WORDLIST_PATH, wordlist };

View file

@ -13,6 +13,8 @@ import logger from "./logger";
import { ulid } from "ulid"; import { ulid } from "ulid";
import { Channel } from "revolt.js/dist/maps/Channels"; import { Channel } from "revolt.js/dist/maps/Channels";
import { ChannelPermission, ServerPermission } from "revolt.js"; import { ChannelPermission, ServerPermission } from "revolt.js";
import { Message } from "revolt.js/dist/maps/Messages";
import { isSudo } from "./commands/botadm";
const NO_MANAGER_MSG = '🔒 Missing permission'; const NO_MANAGER_MSG = '🔒 Missing permission';
@ -73,16 +75,30 @@ async function parseUserOrId(text: string): Promise<User|{_id: string}|null> {
return null; return null;
} }
async function isModerator(member: Member, server: Server) { async function isModerator(message: Message) {
let member = message.member!, server = message.channel!.server!;
return hasPerm(member, 'KickMembers') return hasPerm(member, 'KickMembers')
|| await isBotManager(member, server) || await isBotManager(message)
|| (((await client.db.get('servers').findOne({ id: server._id }) || {}) as ServerConfig) || (((await client.db.get('servers').findOne({ id: server._id }) || {}) as ServerConfig)
.moderators?.indexOf(member.user?._id!) ?? -1) > -1; .moderators?.indexOf(member.user?._id!) ?? -1) > -1
|| await checkSudoPermission(message);
} }
async function isBotManager(member: Member, server: Server) { async function isBotManager(message: Message) {
let member = message.member!, server = message.channel!.server!;
return hasPerm(member, 'ManageServer') return hasPerm(member, 'ManageServer')
|| (((await client.db.get('servers').findOne({ id: server._id }) || {}) as ServerConfig) || (((await client.db.get('servers').findOne({ id: server._id }) || {}) as ServerConfig)
.botManagers?.indexOf(member.user?._id!) ?? -1) > -1; .botManagers?.indexOf(member.user?._id!) ?? -1) > -1
|| await checkSudoPermission(message);
}
async function checkSudoPermission(message: Message): Promise<boolean> {
const hasPerm = isSudo(message.author!);
console.log(hasPerm)
if (!hasPerm) return false;
else {
await message.reply(`# :unlock: Bypassed permission check\n`
+ `Sudo mode is enabled for @${message.author!.username}.\n`);
return true;
}
} }
function hasPerm(member: Member, perm: keyof typeof ServerPermission): boolean { function hasPerm(member: Member, perm: keyof typeof ServerPermission): boolean {