diff --git a/src/bot/commands/bot_managers.ts b/src/bot/commands/bot_managers.ts index 9e445a2..bc529fa 100644 --- a/src/bot/commands/bot_managers.ts +++ b/src/bot/commands/bot_managers.ts @@ -4,6 +4,7 @@ import { hasPerm, parseUser } from "../util"; import ServerConfig from "../../struct/ServerConfig"; import { client } from "../.."; import { User } from "@janderedev/revolt.js/dist/maps/Users"; +import MessageCommandContext from "../../struct/MessageCommandContext"; const SYNTAX = '/admin add @user; /admin remove @user; /admin list'; @@ -12,11 +13,11 @@ export default { aliases: [ 'admins', 'manager', 'managers' ], description: 'Allow users to control the bot\'s configuration', syntax: SYNTAX, - run: async (message: Message, args: string[]) => { + run: async (message: MessageCommandContext, args: string[]) => { if (!hasPerm(message.member!, 'ManageServer')) return message.reply('You need **ManageServer** permission to use this command.'); - let config: ServerConfig = (await client.db.get('servers').findOne({ id: message.channel?.server_id })) ?? {}; + let config: ServerConfig = (await client.db.get('servers').findOne({ id: message.serverContext._id })) ?? {}; let admins = config.botManagers ?? []; let user: User|null; @@ -30,7 +31,7 @@ export default { if (admins.indexOf(user._id) > -1) return message.reply('This user is already added as bot admin.'); admins.push(user._id); - await client.db.get('servers').update({ id: message.channel?.server_id }, { $set: { botManagers: admins } }); + await client.db.get('servers').update({ id: message.serverContext._id }, { $set: { botManagers: admins } }); message.reply(`✅ Added \`@${user.username}\` to bot admins.`); break; @@ -45,7 +46,7 @@ export default { if (admins.indexOf(user._id) == -1) return message.reply('This user is not added as bot admin.'); admins = admins.filter(a => a != user?._id); - await client.db.get('servers').update({ id: message.channel?.server_id }, { $set: { botManagers: admins } }); + await client.db.get('servers').update({ id: message.serverContext._id }, { $set: { botManagers: admins } }); message.reply(`✅ Removed \`@${user.username}\` from bot admins.`); break; diff --git a/src/bot/commands/debug.ts b/src/bot/commands/debug.ts index 506a1ca..0f413e7 100644 --- a/src/bot/commands/debug.ts +++ b/src/bot/commands/debug.ts @@ -1,12 +1,14 @@ import Command from "../../struct/Command"; -import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; +import MessageCommandContext from "../../struct/MessageCommandContext"; export default { name: 'debug', aliases: null, description: 'give info helpful for development and debugging', - run: (message: Message, args: string[]) => { + run: (message: MessageCommandContext, args: string[]) => { message.reply(`Server ID: ${message.channel?.server_id || 'None'}\n` + + `Server context: ${message.serverContext._id} ` + + `(${message.serverContext._id == message.channel?.server_id ? ' This server' : message.serverContext.name})\n` + `Channel ID: ${message.channel_id}\n` + `User ID: ${message.author_id}`); } diff --git a/src/bot/commands/moderator.ts b/src/bot/commands/moderator.ts index d0dc9a9..0a71927 100644 --- a/src/bot/commands/moderator.ts +++ b/src/bot/commands/moderator.ts @@ -4,6 +4,7 @@ import { isBotManager, NO_MANAGER_MSG, parseUser } from "../util"; import ServerConfig from "../../struct/ServerConfig"; import { client } from "../.."; import { User } from "@janderedev/revolt.js/dist/maps/Users"; +import MessageCommandContext from "../../struct/MessageCommandContext"; const SYNTAX = '/mod add @user; /mod remove @user; /mod list'; @@ -14,10 +15,10 @@ export default { aliases: [ 'moderators', 'mod', 'mods' ], description: 'Allow users to moderate other users', syntax: SYNTAX, - run: async (message: Message, args: string[]) => { - if (!await isBotManager(message.member!)) return message.reply(NO_MANAGER_MSG); + run: async (message: MessageCommandContext, args: string[]) => { + if (!await isBotManager(message.member!, message.channel?.server!)) return message.reply(NO_MANAGER_MSG); - let config: ServerConfig = (await client.db.get('servers').findOne({ id: message.channel?.server_id })) ?? {}; + let config: ServerConfig = (await client.db.get('servers').findOne({ id: message.serverContext._id })) ?? {}; let mods = config.moderators ?? []; let user: User|null; @@ -31,7 +32,7 @@ export default { if (mods.indexOf(user._id) > -1) return message.reply('This user is already added as moderator.'); mods.push(user._id); - await client.db.get('servers').update({ id: message.channel?.server_id }, { $set: { moderators: mods } }); + await client.db.get('servers').update({ id: message.serverContext._id }, { $set: { moderators: mods } }); message.reply(`✅ Added \`@${user.username}\` to moderators.`); break; @@ -46,7 +47,7 @@ export default { if (mods.indexOf(user._id) == -1) return message.reply('This user is not added as moderator.'); mods = mods.filter(a => a != user?._id); - await client.db.get('servers').update({ id: message.channel?.server_id }, { $set: { moderators: mods } }); + await client.db.get('servers').update({ id: message.serverContext._id }, { $set: { moderators: mods } }); message.reply(`✅ Removed \`@${user.username}\` from moderators.`); break; diff --git a/src/bot/commands/prefix.ts b/src/bot/commands/prefix.ts index c37564d..d0d55c0 100644 --- a/src/bot/commands/prefix.ts +++ b/src/bot/commands/prefix.ts @@ -18,7 +18,7 @@ export default { switch(args[0]?.toLowerCase()) { case 'set': - if (!await isBotManager(message.member!)) return message.reply(NO_MANAGER_MSG); + if (!await isBotManager(message.member!, message.channel?.server!)) return message.reply(NO_MANAGER_MSG); args.shift(); if (args.length == 0) return message.reply('You need to specify a prefix.'); @@ -41,7 +41,7 @@ export default { break; case 'clear': case 'reset': - if (!await isBotManager(message.member!)) return message.reply(NO_MANAGER_MSG); + if (!await isBotManager(message.member!, message.channel?.server!)) return message.reply(NO_MANAGER_MSG); if (config.prefix != null) { await client.db.get('servers').update({ 'id': message.channel?.server_id }, { $set: { 'prefix': null } }); diff --git a/src/bot/commands/purge.ts b/src/bot/commands/purge.ts index 3ff5736..6bd95cf 100644 --- a/src/bot/commands/purge.ts +++ b/src/bot/commands/purge.ts @@ -13,7 +13,7 @@ export default { syntax: SYNTAX, run: async (message: Message, args: string[]) => { try { - if (!message.member || !await isModerator(message.member!)) return message.reply('🔒 Access denied'); + if (!message.member || !await isModerator(message.member!, message.channel?.server!)) return message.reply('🔒 Access denied'); let messages: Array = []; // X amount of messages from bottom diff --git a/src/bot/commands/settings.ts b/src/bot/commands/settings.ts index 7570ddf..4f3c4ec 100644 --- a/src/bot/commands/settings.ts +++ b/src/bot/commands/settings.ts @@ -6,13 +6,16 @@ import AntispamRule from "../../struct/antispam/AntispamRule"; import ModerationAction from "../../struct/antispam/ModerationAction"; import { isBotManager, NO_MANAGER_MSG } from "../util"; import { ulid } from 'ulid'; +import MessageCommandContext from "../../struct/MessageCommandContext"; export default { name: 'settings', aliases: [ 'setting' ], description: 'change antispam settings', - run: async (message: Message, args: string[]) => { - if (!isBotManager(message.member!)) return message.reply(NO_MANAGER_MSG); + run: async (message: MessageCommandContext, args: string[]) => { + if (!isBotManager(message.member!, message.serverContext)) return message.reply(NO_MANAGER_MSG); + + return 'command is disabled for now'; let settings = { spam: [ diff --git a/src/bot/commands/warn.ts b/src/bot/commands/warn.ts index 4bfef63..0cb0c10 100644 --- a/src/bot/commands/warn.ts +++ b/src/bot/commands/warn.ts @@ -5,14 +5,15 @@ import Infraction from "../../struct/antispam/Infraction"; import { ulid } from "ulid"; import InfractionType from "../../struct/antispam/InfractionType"; import { logModAction } from "../modules/mod_logs"; +import MessageCommandContext from "../../struct/MessageCommandContext"; export default { name: 'warn', aliases: null, removeEmptyArgs: false, description: 'add an infraction to an user\'s record', - run: async (message: Message, args: string[]) => { - if (!await isModerator(message.member!)) return message.reply(NO_MANAGER_MSG); + run: async (message: MessageCommandContext, args: string[]) => { + if (!await isModerator(message.member!, message.serverContext)) return message.reply(NO_MANAGER_MSG); let user = await parseUser(args.shift() ?? ''); if (!user) return message.reply('I can\'t find that user.'); if (user.bot != null) return message.reply('You cannot warn bots.'); @@ -28,7 +29,7 @@ export default { createdBy: message.author_id, user: user._id, reason: reason, - server: message.channel?.server_id!, + server: message.serverContext._id, type: InfractionType.Manual, date: Date.now(), } as Infraction; @@ -36,7 +37,8 @@ export default { let { userWarnCount } = await storeInfraction(infraction); await Promise.all([ - message.reply(`### User warned.\n` + message.reply(`### User warned` + + `${message.serverContext._id != message.channel?.server_id ? ` in **${message.serverContext.name}**` : ''}.\n` + `This is ${userWarnCount == 1 ? '**the first warn**' : `warn number **${userWarnCount}**`}` + ` for ${user.username ?? 'this user'}.\n` + `**Infraction ID:** \`${infraction._id}\`\n` diff --git a/src/bot/commands/warns.ts b/src/bot/commands/warns.ts index 8821067..49ad767 100644 --- a/src/bot/commands/warns.ts +++ b/src/bot/commands/warns.ts @@ -10,6 +10,7 @@ import Xlsx from 'xlsx'; import FormData from 'form-data'; import axios from "axios"; import { fetchUsername } from "../modules/mod_logs"; +import MessageCommandContext from "../../struct/MessageCommandContext"; Day.extend(RelativeTime); @@ -18,12 +19,12 @@ export default { aliases: [ 'warnings', 'infractions', 'infraction' ], description: 'Show all user infractions', syntax: '/warns; /warns @username ["export-csv"]; /warns rm [ID]', - run: async (message: Message, args: string[]) => { - if (!await isModerator(message.member!)) return message.reply(NO_MANAGER_MSG); + run: async (message: MessageCommandContext, args: string[]) => { + if (!await isModerator(message.member!, message.serverContext)) return message.reply(NO_MANAGER_MSG); let collection = client.db.get('infractions'); let infractions: Array = await collection.find({ - server: message.channel?.server_id, + server: message.serverContext._id, }); let userInfractions: Map = new Map(); infractions.forEach(i => { @@ -33,7 +34,7 @@ export default { if (!args[0]) { // Show top most warned users - let msg = `## Most warned users in ${message.channel?.server?.name ?? 'this server'}\n\u200b\n`; + let msg = `## Most warned users in ${message.serverContext.name}\n\u200b\n`; for (let inf of Array.from(userInfractions.values()).sort((a, b) => b.length - a.length).slice(0, 9)) { inf = inf.sort((a, b) => b.date - a.date); msg += `**${await fetchUsername(inf[0].user)}** (${inf[0].user}): **${inf.length}** infractions\n`; @@ -52,7 +53,7 @@ export default { if (!id) return message.reply('No infraction ID provided.'); let inf: Infraction|null = await client.db.get('infractions').findOneAndDelete({ _id: { $eq: id.toUpperCase() }, - server: message.channel?.server_id + server: message.serverContext._id }); if (!inf) return message.reply('I can\'t find that ID.'); diff --git a/src/bot/commands/whitelist.ts b/src/bot/commands/whitelist.ts index 6f404a5..3c7a7d6 100644 --- a/src/bot/commands/whitelist.ts +++ b/src/bot/commands/whitelist.ts @@ -2,6 +2,7 @@ import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; import { User } from "@janderedev/revolt.js/dist/maps/Users"; import { client } from "../.."; import Command from "../../struct/Command"; +import MessageCommandContext from "../../struct/MessageCommandContext"; import ServerConfig from "../../struct/ServerConfig"; import { isBotManager, NO_MANAGER_MSG, parseUser } from "../util"; @@ -12,11 +13,11 @@ export default { 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 }) || {} + run: async (message: MessageCommandContext, args: string[]) => { + let config: ServerConfig = await client.db.get('servers').findOne({ id: message.serverContext._id }) || {} if (!config.whitelist) config.whitelist = { users: [], roles: [], managers: true } - if (!isBotManager(message.member!)) return message.reply(NO_MANAGER_MSG); + if (!isBotManager(message.member!, message.serverContext)) return message.reply(NO_MANAGER_MSG); let user: User|null, role: string|undefined; switch(args[0]?.toLowerCase()) { @@ -24,7 +25,7 @@ export default { case 'set': if (!args[1]) return message.reply('You need to spefify a user or role name.'); - role = Object.entries(message.channel?.server?.roles ?? {}) + role = Object.entries(message.serverContext.roles ?? {}) .find((r) => r[1].name?.toLowerCase() == args[1].toLowerCase() || r[0] == args[1].toUpperCase()) ?.[0]; @@ -34,7 +35,7 @@ export default { 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 } }); + await client.db.get('servers').update({ id: message.serverContext._id }, { $set: { whitelist: config.whitelist } }); return message.reply(`Added role to whitelist!`); } @@ -45,7 +46,7 @@ export default { 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 } }); + await client.db.get('servers').update({ id: message.serverContext._id }, { $set: { whitelist: config.whitelist } }); return message.reply('Added user to whitelist!'); break; case 'rm': @@ -54,7 +55,7 @@ export default { case 'delete': if (!args[1]) return message.reply('You need to spefify a user or role name.'); - role = Object.entries(message.channel?.server?.roles ?? {}) + role = Object.entries(message.serverContext.roles ?? {}) .find((r) => r[1].name?.toLowerCase() == args[1].toLowerCase() || r[0] == args[1].toUpperCase()) ?.[0]; @@ -64,7 +65,7 @@ export default { 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 } }); + await client.db.get('servers').update({ id: message.serverContext._id }, { $set: { whitelist: config.whitelist } }); return message.reply(`Removed role from whitelist!`); } @@ -74,7 +75,7 @@ export default { 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 } }); + await client.db.get('servers').update({ id: message.serverContext._id }, { $set: { whitelist: config.whitelist } }); return message.reply('Removed user from whitelist!'); break; case 'l': @@ -95,7 +96,7 @@ export default { if (config.whitelist.roles?.length) { config.whitelist.roles - ?.map(r => message.channel?.server?.roles?.[r]?.name || `Unknown role (${r})`) + ?.map(r => message.serverContext.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`; diff --git a/src/bot/modules/antispam.ts b/src/bot/modules/antispam.ts index 3a6bdbc..be54007 100644 --- a/src/bot/modules/antispam.ts +++ b/src/bot/modules/antispam.ts @@ -7,7 +7,7 @@ import InfractionType from "../../struct/antispam/InfractionType"; import ModerationAction from "../../struct/antispam/ModerationAction"; import ServerConfig from "../../struct/ServerConfig"; import logger from "../logger"; -import { isBotManager, isModerator, storeInfraction } from "../util"; +import { isModerator, storeInfraction } from "../util"; let msgCountStore: Map = new Map(); @@ -30,7 +30,7 @@ async function antispam(message: Message): Promise { 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 && await isModerator(message.member!)) break; + if (serverRules.whitelist?.managers !== false && await isModerator(message.member!, message.channel?.server!)) 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 13ccb91..107ac54 100644 --- a/src/bot/modules/command_handler.ts +++ b/src/bot/modules/command_handler.ts @@ -6,6 +6,7 @@ import path from 'path'; import ServerConfig from "../../struct/ServerConfig"; import { antispam } from "./antispam"; import checkCustomRules from "./custom_rules/custom_rules"; +import MessageCommandContext from "../../struct/MessageCommandContext"; const DEFAULT_PREFIX = process.env['PREFIX'] ?? process.env['BOT_PREFIX'] @@ -16,22 +17,22 @@ let commands: Command[] = fs.readdirSync(path.join(__dirname, '..', 'commands')) .filter(file => file.endsWith('.js')) .map(file => require(path.join(__dirname, '..', 'commands', file)).default as Command); -client.on('message', async message => { - logger.debug(`Message -> ${message.content}`); +client.on('message', async msg => { + logger.debug(`Message -> ${msg.content}`); - if (typeof message.content != 'string' || - message.author_id == client.user?._id || - !message.channel?.server) return; + if (typeof msg.content != 'string' || + msg.author_id == client.user?._id || + !msg.channel?.server) return; // Send message through anti spam check and custom rules - if (!await antispam(message)) return; - checkCustomRules(message); + if (!await antispam(msg)) return; + checkCustomRules(msg); - let config: ServerConfig = (await client.db.get('servers').findOne({ 'id': message.channel?.server_id })) ?? {}; - let guildPrefix = config.prefix ?? DEFAULT_PREFIX; - - let args = message.content.split(' '); + let args = msg.content.split(' '); let cmdName = args.shift() ?? ''; + + let config: ServerConfig = (await client.db.get('servers').findOne({ 'id': msg.channel?.server_id })) ?? {}; + let guildPrefix = config.prefix ?? DEFAULT_PREFIX; if (cmdName.startsWith(`<@${client.user?._id}>`)) { cmdName = cmdName.substr(`<@${client.user?._id}>`.length); @@ -47,12 +48,28 @@ client.on('message', async message => { if (!cmd) return; let ownerIDs = process.env['BOT_OWNERS'] ? process.env['BOT_OWNERS'].split(',') : []; - if (cmd.restrict == 'BOTOWNER' && ownerIDs.indexOf(message.author_id) == -1) { - logger.warn(`User ${message.author?.username} tried to run owner-only command: ${cmdName}`); - message.reply('🔒 Access denied'); + if (cmd.restrict == 'BOTOWNER' && ownerIDs.indexOf(msg.author_id) == -1) { + logger.warn(`User ${msg.author?.username} tried to run owner-only command: ${cmdName}`); + msg.reply('🔒 Access denied'); return; } + let serverCtx = msg.channel?.server; + + if (config.linkedServer) { + try { + serverCtx = client.servers.get(config.linkedServer) + || await client.servers.fetch(config.linkedServer); + } catch(e) { + msg.reply(`# Error\n` + + `Failed to fetch linked server. This command will be executed in the context of this server.\n\n` + + `Error: \`\`\`js\n${e}\n\`\`\``); + } + } + + let message: MessageCommandContext = msg as MessageCommandContext; + message.serverContext = serverCtx; + logger.info(`Command: ${message.author?.username} in ${message.channel?.server?.name}: ${message.content}`); // Create document for server in DB, if not already present diff --git a/src/bot/util.ts b/src/bot/util.ts index 3f43120..657f8e8 100644 --- a/src/bot/util.ts +++ b/src/bot/util.ts @@ -5,6 +5,7 @@ import Infraction from "../struct/antispam/Infraction"; import ServerConfig from "../struct/ServerConfig"; import FormData from 'form-data'; import axios from 'axios'; +import { Server } from "@janderedev/revolt.js/dist/maps/Servers"; let ServerPermissions = { ['View' as string]: 1 << 0, @@ -65,15 +66,15 @@ async function parseUser(text: string): Promise { } catch(e) { return null; } } -async function isModerator(member: Member) { +async function isModerator(member: Member, server: Server) { return hasPerm(member, 'KickMembers') - || await isBotManager(member) - || (((await client.db.get('servers').findOne({ id: member.server?._id }) || {}) as ServerConfig) + || await isBotManager(member, server) + || (((await client.db.get('servers').findOne({ id: server._id }) || {}) as ServerConfig) .moderators?.indexOf(member.user?._id!) ?? -1) > -1; } -async function isBotManager(member: Member) { +async function isBotManager(member: Member, server: Server) { return hasPerm(member, 'ManageServer') - || (((await client.db.get('servers').findOne({ id: member.server?._id }) || {}) as ServerConfig) + || (((await client.db.get('servers').findOne({ id: server._id }) || {}) as ServerConfig) .botManagers?.indexOf(member.user?._id!) ?? -1) > -1; } diff --git a/src/struct/AutomodClient.ts b/src/struct/AutomodClient.ts index 9deea8e..77992a7 100644 --- a/src/struct/AutomodClient.ts +++ b/src/struct/AutomodClient.ts @@ -13,7 +13,7 @@ class AutomodClient extends Revolt.Client { } let login = (client: Revolt.Client): Promise => new Promise((resolve, reject) => { - logger.info('Logging in...'); + logger.info('Bot logging in...'); let env = process.env; if (!env['BOT_TOKEN']) { @@ -24,7 +24,7 @@ let login = (client: Revolt.Client): Promise => new Promise((resolve, reje client.loginBot(env['BOT_TOKEN']); client.once('ready', () => { - logger.done('Logged in!'); + logger.done('Bot logged in!'); resolve(); }); }); diff --git a/src/struct/MessageCommandContext.ts b/src/struct/MessageCommandContext.ts new file mode 100644 index 0000000..a98eb11 --- /dev/null +++ b/src/struct/MessageCommandContext.ts @@ -0,0 +1,13 @@ +import { Message } from "@janderedev/revolt.js/dist/maps/Messages"; +import { Server } from "@janderedev/revolt.js/dist/maps/Servers"; + +class MessageCommandContext extends Message { + // The server to which the command should be applied. + serverContext: Server; + + /* Override types */ + + content: string; +} + +export default MessageCommandContext; diff --git a/src/struct/ServerConfig.ts b/src/struct/ServerConfig.ts index 568cdd5..2fea148 100644 --- a/src/struct/ServerConfig.ts +++ b/src/struct/ServerConfig.ts @@ -7,6 +7,7 @@ class ServerConfig { automodSettings: AutomodSettings | undefined; botManagers: string[] | undefined; moderators: string[] | undefined; + linkedServer: string | undefined; whitelist: { users: string[] | undefined, roles: string[] | undefined,