From bf3d219716b46daf51dc4907270b8bbbb7d1ef62 Mon Sep 17 00:00:00 2001 From: Cadence Ember Date: Wed, 6 Mar 2024 17:37:16 +1300 Subject: [PATCH] Add helper for permission calculations --- d2m/converters/remove-reaction.js | 2 +- d2m/event-dispatcher.js | 6 ++-- discord/discord-command-handler.js | 2 +- discord/utils.js | 46 ++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/d2m/converters/remove-reaction.js b/d2m/converters/remove-reaction.js index a6c8ace3..caa96d16 100644 --- a/d2m/converters/remove-reaction.js +++ b/d2m/converters/remove-reaction.js @@ -12,7 +12,7 @@ const utils = sync.require("../../m2d/converters/utils") * @typedef ReactionRemoveRequest * @prop {string} eventID * @prop {string | null} mxid - * @prop {BigInt} [hash] + * @prop {bigint} [hash] */ /** diff --git a/d2m/event-dispatcher.js b/d2m/event-dispatcher.js index e7690537..3db1d459 100644 --- a/d2m/event-dispatcher.js +++ b/d2m/event-dispatcher.js @@ -115,8 +115,7 @@ module.exports = { if (!member) return if (!("permission_overwrites" in channel)) continue const permissions = dUtils.getPermissions(member.roles, guild.roles, client.user.id, channel.permission_overwrites) - const wants = BigInt(1 << 10) | BigInt(1 << 16) // VIEW_CHANNEL + READ_MESSAGE_HISTORY - if ((permissions & wants) !== wants) continue // We don't have permission to look back in this channel + if (!dUtils.hasAllPermissions(permissions, ["ViewChannel", "ReadMessageHistory"])) continue // We don't have permission to look back in this channel /** More recent messages come first. */ // console.log(`[check missed messages] in ${channel.id} (${guild.name} / ${channel.name}) because its last message ${channel.last_message_id} is not in the database`) @@ -164,8 +163,7 @@ module.exports = { // Permissions check const permissions = dUtils.getPermissions(member.roles, guild.roles, client.user.id, channel.permission_overwrites) - const wants = BigInt(1 << 10) | BigInt(1 << 16) // VIEW_CHANNEL + READ_MESSAGE_HISTORY - if ((permissions & wants) !== wants) continue // We don't have permission to look up the pins in this channel + if (!dUtils.hasAllPermissions(permissions, ["ViewChannel", "ReadMessageHistory"])) continue // We don't have permission to look up the pins in this channel const row = select("channel_room", ["room_id", "last_bridged_pin_timestamp"], {channel_id: channel.id}).get() if (!row) continue // Only care about already bridged channels diff --git a/discord/discord-command-handler.js b/discord/discord-command-handler.js index aad06e1e..f69346f9 100644 --- a/discord/discord-command-handler.js +++ b/discord/discord-command-handler.js @@ -137,7 +137,7 @@ const commands = [{ // Check CREATE_INSTANT_INVITE permission assert(message.member) const guildPermissions = utils.getPermissions(message.member.roles, guild.roles) - if (!(guildPermissions & BigInt(1))) { + if (!(guildPermissions & DiscordTypes.PermissionFlagsBits.CreateInstantInvite)) { return discord.snow.channel.createMessage(channel.id, { ...ctx, content: "You don't have permission to invite people to this Discord server." diff --git a/discord/utils.js b/discord/utils.js index 6788bcf9..ff1f2152 100644 --- a/discord/utils.js +++ b/discord/utils.js @@ -1,6 +1,7 @@ // @ts-check const DiscordTypes = require("discord-api-types/v10") +const assert = require("assert").strict const EPOCH = 1420070400000 @@ -49,6 +50,48 @@ function getPermissions(userRoles, guildRoles, userID, channelOverwrites) { return allowed } +/** + * Note: You can only provide one permission bit to permissionToCheckFor. To check multiple permissions, call `hasAllPermissions` or `hasSomePermissions`. + * It is designed like this to avoid developer error with bit manipulations. + * + * @param {bigint} resolvedPermissions + * @param {bigint} permissionToCheckFor + * @returns {boolean} whether the user has the requested permission + * @example + * const permissions = getPermissions(userRoles, guildRoles, userID, channelOverwrites) + * hasPermission(permissions, DiscordTypes.PermissionFlagsBits.ViewChannel) + */ +function hasPermission(resolvedPermissions, permissionToCheckFor) { + // Make sure permissionToCheckFor has exactly one permission in it + assert.equal(permissionToCheckFor.toString(2).match(/1/g), 1) + // Do the actual calculation + return (resolvedPermissions & permissionToCheckFor) === permissionToCheckFor +} + +/** + * @param {bigint} resolvedPermissions + * @param {(keyof DiscordTypes.PermissionFlagsBits)[]} permissionsToCheckFor + * @returns {boolean} whether the user has any of the requested permissions + * @example + * const permissions = getPermissions(userRoles, guildRoles, userID, channelOverwrites) + * hasSomePermissions(permissions, ["ViewChannel", "ReadMessageHistory"]) + */ +function hasSomePermissions(resolvedPermissions, permissionsToCheckFor) { + return permissionsToCheckFor.some(x => hasPermission(resolvedPermissions, DiscordTypes.PermissionFlagsBits[x])) +} + +/** + * @param {bigint} resolvedPermissions + * @param {(keyof DiscordTypes.PermissionFlagsBits)[]} permissionsToCheckFor + * @returns {boolean} whether the user has all of the requested permissions + * @example + * const permissions = getPermissions(userRoles, guildRoles, userID, channelOverwrites) + * hasAllPermissions(permissions, ["ViewChannel", "ReadMessageHistory"]) + */ +function hasAllPermissions(resolvedPermissions, permissionsToCheckFor) { + return permissionsToCheckFor.every(x => hasPermission(resolvedPermissions, DiscordTypes.PermissionFlagsBits[x])) +} + /** * Command interaction responses have a webhook_id for some reason, but still have real author info of a real bot user in the server. * @param {DiscordTypes.APIMessage} message @@ -69,6 +112,9 @@ function timestampToSnowflakeInexact(timestamp) { } module.exports.getPermissions = getPermissions +module.exports.hasPermission = hasPermission +module.exports.hasSomePermissions = hasSomePermissions +module.exports.hasAllPermissions = hasAllPermissions module.exports.isWebhookMessage = isWebhookMessage module.exports.snowflakeToTimestampExact = snowflakeToTimestampExact module.exports.timestampToSnowflakeInexact = timestampToSnowflakeInexact