2023-09-17 08:15:29 +00:00
|
|
|
// @ts-check
|
|
|
|
|
|
|
|
const DiscordTypes = require("discord-api-types/v10")
|
2024-03-06 04:37:16 +00:00
|
|
|
const assert = require("assert").strict
|
2023-09-17 08:15:29 +00:00
|
|
|
|
2023-11-23 02:51:25 +00:00
|
|
|
const EPOCH = 1420070400000
|
|
|
|
|
2023-09-17 08:15:29 +00:00
|
|
|
/**
|
|
|
|
* @param {string[]} userRoles
|
|
|
|
* @param {DiscordTypes.APIGuild["roles"]} guildRoles
|
|
|
|
* @param {string} [userID]
|
|
|
|
* @param {DiscordTypes.APIGuildChannel["permission_overwrites"]} [channelOverwrites]
|
|
|
|
*/
|
|
|
|
function getPermissions(userRoles, guildRoles, userID, channelOverwrites) {
|
|
|
|
let allowed = BigInt(0)
|
|
|
|
let everyoneID
|
|
|
|
// Guild allows
|
|
|
|
for (const role of guildRoles) {
|
|
|
|
if (role.name === "@everyone") {
|
|
|
|
allowed |= BigInt(role.permissions)
|
|
|
|
everyoneID = role.id
|
|
|
|
}
|
|
|
|
if (userRoles.includes(role.id)) {
|
|
|
|
allowed |= BigInt(role.permissions)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (channelOverwrites) {
|
|
|
|
/** @type {((overwrite: Required<DiscordTypes.APIGuildChannel>["permission_overwrites"][0]) => any)[]} */
|
|
|
|
const actions = [
|
|
|
|
// Channel @everyone deny
|
|
|
|
overwrite => overwrite.id === everyoneID && (allowed &= ~BigInt(overwrite.deny)),
|
|
|
|
// Channel @everyone allow
|
|
|
|
overwrite => overwrite.id === everyoneID && (allowed |= BigInt(overwrite.allow)),
|
|
|
|
// Role deny
|
|
|
|
overwrite => userRoles.includes(overwrite.id) && (allowed &= ~BigInt(overwrite.deny)),
|
|
|
|
// Role allow
|
2024-01-17 11:30:55 +00:00
|
|
|
overwrite => userRoles.includes(overwrite.id) && (allowed |= BigInt(overwrite.allow)),
|
2023-09-17 08:15:29 +00:00
|
|
|
// User deny
|
|
|
|
overwrite => overwrite.id === userID && (allowed &= ~BigInt(overwrite.deny)),
|
|
|
|
// User allow
|
|
|
|
overwrite => overwrite.id === userID && (allowed |= BigInt(overwrite.allow))
|
|
|
|
]
|
|
|
|
for (let i = 0; i < actions.length; i++) {
|
|
|
|
for (const overwrite of channelOverwrites) {
|
|
|
|
actions[i](overwrite)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return allowed
|
|
|
|
}
|
|
|
|
|
2024-03-06 04:37:16 +00:00
|
|
|
/**
|
|
|
|
* 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
|
2024-03-06 20:13:25 +00:00
|
|
|
assert.equal(permissionToCheckFor.toString(2).match(/1/g)?.length, 1)
|
2024-03-06 04:37:16 +00:00
|
|
|
// 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]))
|
|
|
|
}
|
|
|
|
|
2023-10-09 22:23:51 +00:00
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
function isWebhookMessage(message) {
|
|
|
|
const isInteractionResponse = message.type === 20
|
|
|
|
return message.webhook_id && !isInteractionResponse
|
|
|
|
}
|
|
|
|
|
2023-11-23 02:51:25 +00:00
|
|
|
/** @param {string} snowflake */
|
|
|
|
function snowflakeToTimestampExact(snowflake) {
|
|
|
|
return Number(BigInt(snowflake) >> 22n) + EPOCH
|
|
|
|
}
|
|
|
|
|
|
|
|
/** @param {number} timestamp */
|
|
|
|
function timestampToSnowflakeInexact(timestamp) {
|
|
|
|
return String((timestamp - EPOCH) * 2**22)
|
|
|
|
}
|
|
|
|
|
2023-09-17 08:15:29 +00:00
|
|
|
module.exports.getPermissions = getPermissions
|
2024-03-06 04:37:16 +00:00
|
|
|
module.exports.hasPermission = hasPermission
|
|
|
|
module.exports.hasSomePermissions = hasSomePermissions
|
|
|
|
module.exports.hasAllPermissions = hasAllPermissions
|
2023-10-09 22:23:51 +00:00
|
|
|
module.exports.isWebhookMessage = isWebhookMessage
|
2023-11-23 02:51:25 +00:00
|
|
|
module.exports.snowflakeToTimestampExact = snowflakeToTimestampExact
|
|
|
|
module.exports.timestampToSnowflakeInexact = timestampToSnowflakeInexact
|