diff --git a/src/bot/commands/bot_managers.ts b/src/bot/commands/bot_managers.ts index 72623ea..8b9f391 100644 --- a/src/bot/commands/bot_managers.ts +++ b/src/bot/commands/bot_managers.ts @@ -12,7 +12,6 @@ export default { aliases: [ 'admins', 'manager', 'managers' ], description: 'Allow users to control the bot\'s configuration', syntax: SYNTAX, - serverOnly: true, run: async (message: Message, args: string[]) => { if (!hasPerm(message.member!, 'ManageServer')) return message.reply('You need **ManageServer** permission to use this command.'); diff --git a/src/bot/commands/debug.ts b/src/bot/commands/debug.ts index 15813e2..3c76218 100644 --- a/src/bot/commands/debug.ts +++ b/src/bot/commands/debug.ts @@ -6,7 +6,6 @@ export default { name: 'debug', aliases: null, description: 'give info helpful for development and debugging', - serverOnly: false, run: (message: Message, args: string[]) => { message.reply(`Server ID: ${message.channel?.server_id || 'None'}\n` + `Channel ID: ${message.channel_id}\n` diff --git a/src/bot/commands/eval.ts b/src/bot/commands/eval.ts index 86e52fd..14bd012 100644 --- a/src/bot/commands/eval.ts +++ b/src/bot/commands/eval.ts @@ -8,7 +8,6 @@ export default { description: 'Evaluate JS code', restrict: 'BOTOWNER', removeEmptyArgs: false, - serverOnly: false, run: async (message: Message, args: string[]) => { let cmd = `let { client } = require("../..");` + `let axios = require("axios").default;` diff --git a/src/bot/commands/ping.ts b/src/bot/commands/ping.ts index ef3556b..ab97b33 100644 --- a/src/bot/commands/ping.ts +++ b/src/bot/commands/ping.ts @@ -6,7 +6,6 @@ export default { name: 'ping', aliases: null, description: 'ping pong', - serverOnly: false, run: async (message: Message, args: string[]) => { let now = Date.now(); message.reply(`Measuring...`) diff --git a/src/bot/commands/prefix.ts b/src/bot/commands/prefix.ts index a3a461a..c0e4c42 100644 --- a/src/bot/commands/prefix.ts +++ b/src/bot/commands/prefix.ts @@ -13,7 +13,6 @@ export default { aliases: null, description: 'modify prefix', syntax: SYNTAX, - serverOnly: true, run: async (message: Message, args: string[]) => { let config: ServerConfig = (await client.db.get('servers').findOne({ id: message.channel?.server_id })) ?? {}; diff --git a/src/bot/commands/settings.ts b/src/bot/commands/settings.ts index a3a0107..2dfbf27 100644 --- a/src/bot/commands/settings.ts +++ b/src/bot/commands/settings.ts @@ -11,7 +11,6 @@ export default { name: 'settings', aliases: [ 'setting' ], description: 'change antispam settings', - serverOnly: false, run: async (message: Message, args: string[]) => { if (!isBotManager(message.member!)) return message.reply(NO_MANAGER_MSG); @@ -22,12 +21,21 @@ export default { max_msg: 5, timeframe: 3, action: ModerationAction.Delete, - channels: [ '01FHJD5D2PBRTEVPNFM1FRY85J' ], + channels: null, + } as AntispamRule, + { + id: ulid(), + max_msg: 4, + timeframe: 3, + action: ModerationAction.Warn, + channels: null, } as AntispamRule ] } as AutomodSettings; client.db.get('servers') .update({ id: message.channel?.server_id }, { $set: { automodSettings: settings } }); + + message.reply('Default config restored'); } } as Command; diff --git a/src/bot/commands/shell_eval.ts b/src/bot/commands/shell_eval.ts index 8cc010f..9a1f722 100644 --- a/src/bot/commands/shell_eval.ts +++ b/src/bot/commands/shell_eval.ts @@ -8,7 +8,6 @@ export default { description: 'Run code in a shell', restrict: 'BOTOWNER', removeEmptyArgs: false, - serverOnly: false, run: async (message: Message, args: string[]) => { let cmd = args.join(' '); diff --git a/src/bot/commands/test.ts b/src/bot/commands/test.ts index 27e465c..9a3ed92 100644 --- a/src/bot/commands/test.ts +++ b/src/bot/commands/test.ts @@ -5,7 +5,6 @@ export default { name: 'test', aliases: [ 'testalias' ], description: 'epic test command', - serverOnly: false, run: (message: Message, args: string[]) => { message.reply('I am here'); } diff --git a/src/bot/commands/whitelist.ts b/src/bot/commands/whitelist.ts new file mode 100644 index 0000000..f5a330f --- /dev/null +++ b/src/bot/commands/whitelist.ts @@ -0,0 +1,114 @@ +import { Message } from "revolt.js/dist/maps/Messages"; +import { User } from "revolt.js/dist/maps/Users"; +import { client } from "../.."; +import Command from "../../struct/Command"; +import ServerConfig from "../../struct/ServerConfig"; +import { isBotManager, NO_MANAGER_MSG, parseUser } from "../util"; + +const SYNTAX = ''; + +export default { + name: 'whitelist', + aliases: [], + description: 'Allow users or roles to bypass moderation rules', + syntax: SYNTAX, + run: async (message: Message, args: string[]) => { + let config: ServerConfig = await client.db.get('servers').findOne({ id: message.channel?.server_id }) || {} + if (!config.whitelist) config.whitelist = { users: [], roles: [], managers: true } + + if (!isBotManager(message.member!)) return message.reply(NO_MANAGER_MSG); + + let user: User|null, role: string|undefined; + switch(args[0]?.toLowerCase()) { + case 'add': + case 'set': + if (!args[1]) return message.reply('You need to spefify a user or role name.'); + + role = Object.entries(message.channel?.server?.roles ?? {}) + .find((r) => r[1].name?.toLowerCase() == args[1].toLowerCase() + || r[0] == args[1].toUpperCase()) + ?.[0]; + + if (role) { + if (config.whitelist!.roles?.includes(role)) + return message.reply('That role is already whitelisted.'); + + config.whitelist!.roles = [role, ...(config.whitelist!.roles ?? [])]; + await client.db.get('servers').update({ id: message.channel?.server_id }, { $set: { whitelist: config.whitelist } }); + return message.reply(`Added role to whitelist!`); + } + + user = await parseUser(args[1]) + if (user == null) return message.reply('I can\'t find that user or role.'); + if (user.bot != null) return message.reply('Bots cannot be whitelisted.'); + if (config.whitelist!.users?.includes(user._id)) + return message.reply('That user is already whitelisted.'); + + config.whitelist!.users = [user._id, ...(config.whitelist!.users ?? [])]; + await client.db.get('servers').update({ id: message.channel?.server_id }, { $set: { whitelist: config.whitelist } }); + return message.reply('Added user to whitelist!'); + break; + case 'rm': + case 'del': + case 'remove': + case 'delete': + if (!args[1]) return message.reply('You need to spefify a user or role name.'); + + role = Object.entries(message.channel?.server?.roles ?? {}) + .find((r) => r[1].name?.toLowerCase() == args[1].toLowerCase() + || r[0] == args[1].toUpperCase()) + ?.[0]; + + if (role) { + if (!config.whitelist!.roles?.includes(role)) + return message.reply('That role is not whitelisted.'); + + config.whitelist!.roles = config.whitelist!.roles.filter(r => r != role); + await client.db.get('servers').update({ id: message.channel?.server_id }, { $set: { whitelist: config.whitelist } }); + return message.reply(`Removed role from whitelist!`); + } + + user = await parseUser(args[1]) + if (user == null) return message.reply('I can\'t find that user or role.'); + if (!config.whitelist!.users?.includes(user._id)) + return message.reply('That user is not whitelisted.'); + + config.whitelist!.users = config.whitelist!.users.filter(u => u != user?._id); + await client.db.get('servers').update({ id: message.channel?.server_id }, { $set: { whitelist: config.whitelist } }); + return message.reply('Removed user from whitelist!'); + break; + case 'l': + case 'ls': + case 'list': + case 'show': + let str = `## Whitelisted users\n` + + `### Users\n`; + + if (config.whitelist.users?.length) { + config.whitelist.users?.forEach((u, index) => { + if (index < 15) str += `* <@${u}>\n`; + if (index == 15) str += `**${index - 15} more user${config.whitelist?.users?.length == 16 ? '' : 's'}**\n`; + }); + } else str += `**No whitelisted users**\n`; + + str += `\u200b\n### Roles\n`; + + if (config.whitelist.roles?.length) { + config.whitelist.roles + ?.map(r => message.channel?.server?.roles?.[r]?.name || `Unknown role (${r})`) + .forEach((r, index) => { + if (index < 15) str += `* ${r}\n`; + if (index == 15) str += `**${config.whitelist!.roles!.length - 15} more role${config.whitelist?.roles?.length == 16 ? '' : 's'}**\n`; + }); + } else str += `**No whitelisted roles**\n`; + + str += `\u200b\nAdmins and bot managers: **${config.whitelist.managers === false ? 'No' : 'Yes'}**`; + + message.reply(str) + ?.catch(e => message.reply(String(e))); + break; + default: + message.reply(`Command syntax: ${SYNTAX}`); + } + } +} as Command; diff --git a/src/bot/modules/antispam.ts b/src/bot/modules/antispam.ts index 980f042..af232f8 100644 --- a/src/bot/modules/antispam.ts +++ b/src/bot/modules/antispam.ts @@ -3,6 +3,7 @@ import { client } from "../.."; import ModerationAction from "../../struct/antispam/ModerationAction"; import ServerConfig from "../../struct/ServerConfig"; import logger from "../logger"; +import { isBotManager } from "../util"; let msgCountStore: Map = new Map(); @@ -22,6 +23,10 @@ async function antispam(message: Message): Promise { msgCountStore.set(rule.id, { users: {} }); } + if (message.author?.bot != null) break; + if (serverRules.whitelist?.users?.includes(message.author_id)) break; + if (message.member?.roles?.filter(r => serverRules.whitelist?.roles?.includes(r)).length) break; + if (serverRules.whitelist?.managers !== false && isBotManager(message.member!)) break; if (rule.channels?.indexOf(message.channel_id) == -1) break; let store = msgCountStore.get(rule.id)!; diff --git a/src/bot/modules/command_handler.ts b/src/bot/modules/command_handler.ts index 1dd6c96..0ae1f86 100644 --- a/src/bot/modules/command_handler.ts +++ b/src/bot/modules/command_handler.ts @@ -6,7 +6,10 @@ import path from 'path'; import ServerConfig from "../../struct/ServerConfig"; import { antispam } from "./antispam"; -const DEFAULT_PREFIX = process.env['PREFIX'] ?? '/'; +const DEFAULT_PREFIX = process.env['PREFIX'] + ?? process.env['BOT_PREFIX'] + ?? process.env['COMMAND_PREFIX'] + ?? '/'; let commands: Command[] = fs.readdirSync(path.join(__dirname, '..', 'commands')) .filter(file => file.endsWith('.js')) @@ -14,7 +17,10 @@ let commands: Command[] = fs.readdirSync(path.join(__dirname, '..', 'commands')) client.on('message', async message => { logger.debug(`Message -> ${message.content}`); - if (typeof message.content != 'string' || message.author_id == client.user?._id || !message.channel) return; + + if (typeof message.content != 'string' || + message.author_id == client.user?._id || + !message.channel?.server) return; // Send message through anti spam check if (!antispam(message)) return; @@ -50,10 +56,6 @@ client.on('message', async message => { // Create document for server in DB, if not already present if (JSON.stringify(config) == '{}') await client.db.get('servers').insert({ id: message.channel?.server_id }); - if (cmd.serverOnly && !message.channel?.server) { - return message.reply('This command is not available in direct messages.'); - } - if (cmd.removeEmptyArgs !== false) { args = args.filter(a => a.length > 0); } diff --git a/src/bot/util.ts b/src/bot/util.ts index 72fd09d..09e5574 100644 --- a/src/bot/util.ts +++ b/src/bot/util.ts @@ -48,8 +48,10 @@ async function parseUser(text: string): Promise { if (user) return user; } - if (uid) return await client.users.fetch(uid) || null; - else return null; + try { + if (uid) return await client.users.fetch(uid) || null; + else return null; + } catch(e) { return null; } } async function isBotManager(member: Member) { diff --git a/src/struct/Command.ts b/src/struct/Command.ts index 84aa34b..a33b855 100644 --- a/src/struct/Command.ts +++ b/src/struct/Command.ts @@ -6,7 +6,6 @@ class Command { restrict?: 'BOTOWNER' | null; removeEmptyArgs?: boolean | null; run: Function; - serverOnly: boolean; } export default Command; diff --git a/src/struct/ServerConfig.ts b/src/struct/ServerConfig.ts index b02447c..1399132 100644 --- a/src/struct/ServerConfig.ts +++ b/src/struct/ServerConfig.ts @@ -6,6 +6,11 @@ class ServerConfig { spaceAfterPrefix: boolean | undefined; automodSettings: AutomodSettings | undefined; botManagers: string[] | undefined; + whitelist: { + users: string[] | undefined, + roles: string[] | undefined, + managers: boolean | undefined, + } | undefined; } export default ServerConfig;