diff --git a/src/bot/commands/eval.ts b/src/bot/commands/eval.ts index 0d975d1..e294285 100644 --- a/src/bot/commands/eval.ts +++ b/src/bot/commands/eval.ts @@ -11,10 +11,7 @@ export default { removeEmptyArgs: false, category: 'owner', run: async (message: Message, args: string[]) => { - let cmd = `let { client } = require("../..");` - + `let axios = require("axios").default;` - + `let crypto = require("crypto");` - + args.join(' '); + let cmd = args.join(' '); let m = await message.channel?.sendMessage(`Executing...`); diff --git a/src/bot/logger.ts b/src/bot/logger.ts index bc13cd4..62384c4 100644 --- a/src/bot/logger.ts +++ b/src/bot/logger.ts @@ -1,6 +1,6 @@ import Log75, { LogLevel } from 'log75'; // Thanks to being forced to switch to ESM this broke somehow? -let logger = new (Log75 as any).default(process.env.NODE_ENV == 'production' ? LogLevel.Standard : LogLevel.Debug); +let logger: Log75 = new (Log75 as any).default(process.env.NODE_ENV == 'production' ? LogLevel.Standard : LogLevel.Debug); export default logger; diff --git a/src/bot/modules/antispam.ts b/src/bot/modules/antispam.ts index 3fe04cf..70cd276 100644 --- a/src/bot/modules/antispam.ts +++ b/src/bot/modules/antispam.ts @@ -55,14 +55,15 @@ async function antispam(message: Message): Promise { if (!userStore.warnTriggered) { userStore.warnTriggered = true; setTimeout(() => userStore.warnTriggered = false, 5000); - message.channel?.sendMessage(getWarnMsg(rule, message)); + message.channel?.sendMessage(getWarnMsg(rule, message)) + .catch(() => logger.warn('Antispam: Failed to send message')); } break; case ModerationAction.Warn: if (!userStore.warnTriggered) { userStore.warnTriggered = true; setTimeout(() => userStore.warnTriggered = false, 5000); - + let inf = { _id: ulid(), createdBy: null, @@ -73,7 +74,8 @@ async function antispam(message: Message): Promise { user: message.author_id, } as Infraction; - let m = message.channel?.sendMessage('## User has been warned.\n\u200b\n' + getWarnMsg(rule, message)); + message.channel?.sendMessage('## User has been warned.\n\u200b\n' + getWarnMsg(rule, message)) + .catch(() => logger.warn('Antispam: Failed to send warn message')); await storeInfraction(inf); } diff --git a/src/bot/modules/command_handler.ts b/src/bot/modules/command_handler.ts index 8cdc573..62c0a60 100644 --- a/src/bot/modules/command_handler.ts +++ b/src/bot/modules/command_handler.ts @@ -8,6 +8,7 @@ import { antispam } from "./antispam"; import checkCustomRules from "./custom_rules/custom_rules"; import MessageCommandContext from "../../struct/MessageCommandContext"; import { fileURLToPath } from 'url'; +import { getOwnMemberInServer, hasPermForChannel } from "../util"; // thanks a lot esm const filename = fileURLToPath(import.meta.url); @@ -35,6 +36,14 @@ let commands: Command[]; msg.author_id == client.user?._id || !msg.channel?.server) return; + if (!msg.member) await msg.channel.server.fetchMember(msg.author_id); + + // If we can't reply to the message, return + if (!hasPermForChannel(await getOwnMemberInServer(msg.channel.server), msg.channel, 'SendMessage')) { + logger.debug('Cannot reply to message; returning'); + return; + } + // Send message through anti spam check and custom rules if (!await antispam(msg)) return; checkCustomRules(msg); diff --git a/src/bot/modules/custom_rules/custom_rules.ts b/src/bot/modules/custom_rules/custom_rules.ts index 7dbf40e..c4e990a 100644 --- a/src/bot/modules/custom_rules/custom_rules.ts +++ b/src/bot/modules/custom_rules/custom_rules.ts @@ -7,6 +7,7 @@ import messageContentTrigger from "./message_content_trigger"; import custom_sendMessage from "./actions/sendMessage"; import custom_delete from "./actions/delete"; import custom_warn from "./actions/warn"; +import { getOwnMemberInServer, hasPerm, hasPermForChannel } from "../../util"; async function checkCustomRules(message: Message, isEdit: boolean = false) { let serverConfig: ServerConfig = await client.db.get('servers').findOne({ id: message.channel?.server_id }) ?? {}; @@ -24,13 +25,22 @@ async function checkCustomRules(message: Message, isEdit: boolean = false) { for (const action of rule.action) { switch(action.action) { case 'sendMessage': - await custom_sendMessage(message, action); + if (hasPermForChannel(await getOwnMemberInServer(message.channel!.server!), message.channel!, 'SendMessage')) + await custom_sendMessage(message, action); + else + logger.warn(`Custom rule ${rule._id}: 'sendMessage' action lacks permission`); break; case 'delete': - await custom_delete(message, action); + if (hasPermForChannel(await getOwnMemberInServer(message.channel!.server!), message.channel!, 'ManageMessages')) + await custom_delete(message, action); + else + logger.warn(`Custom rule ${rule._id}: 'delete' action lacks permission`); break; case 'warn': - await custom_warn(message, action); + if (hasPermForChannel(await getOwnMemberInServer(message.channel!.server!), message.channel!, 'SendMessage')) + await custom_warn(message, action); + else + logger.warn(`Custom rule ${rule._id}: 'warn' action lacks permission`); break; default: logger.warn(`Unknown action ${action.action} in custom rule ${rule._id} in server ${message.channel?.server_id}`); diff --git a/src/bot/util.ts b/src/bot/util.ts index d0a72fe..8917fa0 100644 --- a/src/bot/util.ts +++ b/src/bot/util.ts @@ -8,22 +8,12 @@ import axios from 'axios'; import { Server } from "revolt.js/dist/maps/Servers"; import LogConfig from "../struct/LogConfig"; import LogMessage from "../struct/LogMessage"; -import { ColorResolvable, MessageAttachment, MessageEmbed, WebhookClient } from "discord.js"; +import { ColorResolvable, MessageEmbed } from "discord.js"; import logger from "./logger"; import { ulid } from "ulid"; +import { Channel } from "revolt.js/dist/maps/Channels"; +import { ChannelPermission, ServerPermission } from "revolt.js"; -let ServerPermissions = { - ['View' as string]: 1 << 0, - ['ManageRoles' as string]: 1 << 1, - ['ManageChannels' as string]: 1 << 2, - ['ManageServer' as string]: 1 << 3, - ['KickMembers' as string]: 1 << 4, - ['BanMembers' as string]: 1 << 5, - ['ChangeNickname' as string]: 1 << 12, - ['ManageNicknames' as string]: 1 << 13, - ['ChangeAvatar' as string]: 1 << 14, - ['RemoveAvatars' as string]: 1 << 15, -} const NO_MANAGER_MSG = '🔒 Missing permission'; const ULID_REGEX = /^[0-9A-HJ-KM-NP-TV-Z]{26}$/i; @@ -95,10 +85,8 @@ async function isBotManager(member: Member, server: Server) { .botManagers?.indexOf(member.user?._id!) ?? -1) > -1; } -function hasPerm(member: Member, perm: 'View'|'ManageRoles'|'ManageChannels'|'ManageServer'| // its late and im tired - 'KickMembers'|'BanMembers'|'ChangeNickname'| // dont judge my code - 'ManageNicknames'|'ChangeAvatar'|'RemoveAvatars'): boolean { - let p = ServerPermissions[perm]; +function hasPerm(member: Member, perm: keyof typeof ServerPermission): boolean { + let p = ServerPermission[perm]; if (member.server?.owner == member.user?._id) return true; // this should work but im not 100% certain @@ -108,6 +96,16 @@ function hasPerm(member: Member, perm: 'View'|'ManageRoles'|'ManageChannels'|'M return !!(userPerm & p); } +function hasPermForChannel(member: Member, channel: Channel, perm: keyof typeof ChannelPermission): boolean { + if (!member.server) throw 'hasPermForChannel(): Server is undefined'; + return !!(channel.permission & ChannelPermission[perm]); +} + +async function getOwnMemberInServer(server: Server): Promise { + return client.members.getKey({ server: server._id, user: client.user!._id }) + || await server.fetchMember(client.user!._id); +} + async function storeInfraction(infraction: Infraction): Promise<{ userWarnCount: number }> { let collection = client.db.get('infractions'); let p = [ @@ -162,9 +160,7 @@ async function sendLogMessage(config: LogConfig, content: LogMessage) { data.append("payload_json", JSON.stringify({ embeds: [ embed.toJSON() ] }), { contentType: 'application/json' }); axios.post(config.discord.webhookUrl, data, {headers: data.getHeaders() }) - .catch(e => { - logger.error('Failed to fire Discord webhook: ' + e); - }); + .catch(e => logger.error(`Failed to send log message (discord): ${e}`)); } if (config.revolt?.channel) { @@ -224,12 +220,12 @@ async function sendLogMessage(config: LogConfig, content: LogMessage) { break; } - await channel.sendMessage({ + channel.sendMessage({ content: message, attachments: content.attachments ? await Promise.all(content.attachments?.map(a => uploadFile(a.content, a.name))) : 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}`); } @@ -277,6 +273,8 @@ function sanitizeMessageContent(msg: string): string { export { getAutumnURL, hasPerm, + hasPermForChannel, + getOwnMemberInServer, isModerator, isBotManager, parseUser, diff --git a/src/struct/Command.ts b/src/struct/Command.ts index 80e394c..3a75f0e 100644 --- a/src/struct/Command.ts +++ b/src/struct/Command.ts @@ -1,3 +1,5 @@ +import { ChannelPermission, ServerPermission } from "revolt.js"; + class Command { name: string; aliases: string[] | null; @@ -7,6 +9,10 @@ class Command { removeEmptyArgs?: boolean | null; run: Function; category?: string; + requiredPermissions?: { + server?: keyof typeof ServerPermission, + channel?: keyof typeof ChannelPermission, + } } export default Command;