diff --git a/rtc/src/util/Permissions.ts b/rtc/src/util/Permissions.ts
new file mode 100644
index 00000000..445e901f
--- /dev/null
+++ b/rtc/src/util/Permissions.ts
@@ -0,0 +1,262 @@
+// https://github.com/discordjs/discord.js/blob/master/src/util/Permissions.js
+// Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah
+import { MemberDocument, MemberModel } from "../models/Member";
+import { ChannelDocument, ChannelModel } from "../models/Channel";
+import { ChannelPermissionOverwrite } from "../models/Channel";
+import { Role, RoleDocument, RoleModel } from "../models/Role";
+import { BitField } from "./BitField";
+import { GuildDocument, GuildModel } from "../models/Guild";
+// TODO: check role hierarchy permission
+
+var HTTPError: any;
+
+try {
+ HTTPError = require("lambert-server").HTTPError;
+} catch (e) {
+ HTTPError = Error;
+}
+
+export type PermissionResolvable = bigint | number | Permissions | PermissionResolvable[] | PermissionString;
+
+type PermissionString =
+ | "CREATE_INSTANT_INVITE"
+ | "KICK_MEMBERS"
+ | "BAN_MEMBERS"
+ | "ADMINISTRATOR"
+ | "MANAGE_CHANNELS"
+ | "MANAGE_GUILD"
+ | "ADD_REACTIONS"
+ | "VIEW_AUDIT_LOG"
+ | "PRIORITY_SPEAKER"
+ | "STREAM"
+ | "VIEW_CHANNEL"
+ | "SEND_MESSAGES"
+ | "SEND_TTS_MESSAGES"
+ | "MANAGE_MESSAGES"
+ | "EMBED_LINKS"
+ | "ATTACH_FILES"
+ | "READ_MESSAGE_HISTORY"
+ | "MENTION_EVERYONE"
+ | "USE_EXTERNAL_EMOJIS"
+ | "VIEW_GUILD_INSIGHTS"
+ | "CONNECT"
+ | "SPEAK"
+ | "MUTE_MEMBERS"
+ | "DEAFEN_MEMBERS"
+ | "MOVE_MEMBERS"
+ | "USE_VAD"
+ | "CHANGE_NICKNAME"
+ | "MANAGE_NICKNAMES"
+ | "MANAGE_ROLES"
+ | "MANAGE_WEBHOOKS"
+ | "MANAGE_EMOJIS";
+
+const CUSTOM_PERMISSION_OFFSET = BigInt(1) << BigInt(48); // 16 free custom permission bits, and 16 for discord to add new ones
+
+export class Permissions extends BitField {
+ cache: PermissionCache = {};
+
+ static FLAGS = {
+ CREATE_INSTANT_INVITE: BigInt(1) << BigInt(0),
+ KICK_MEMBERS: BigInt(1) << BigInt(1),
+ BAN_MEMBERS: BigInt(1) << BigInt(2),
+ ADMINISTRATOR: BigInt(1) << BigInt(3),
+ MANAGE_CHANNELS: BigInt(1) << BigInt(4),
+ MANAGE_GUILD: BigInt(1) << BigInt(5),
+ ADD_REACTIONS: BigInt(1) << BigInt(6),
+ VIEW_AUDIT_LOG: BigInt(1) << BigInt(7),
+ PRIORITY_SPEAKER: BigInt(1) << BigInt(8),
+ STREAM: BigInt(1) << BigInt(9),
+ VIEW_CHANNEL: BigInt(1) << BigInt(10),
+ SEND_MESSAGES: BigInt(1) << BigInt(11),
+ SEND_TTS_MESSAGES: BigInt(1) << BigInt(12),
+ MANAGE_MESSAGES: BigInt(1) << BigInt(13),
+ EMBED_LINKS: BigInt(1) << BigInt(14),
+ ATTACH_FILES: BigInt(1) << BigInt(15),
+ READ_MESSAGE_HISTORY: BigInt(1) << BigInt(16),
+ MENTION_EVERYONE: BigInt(1) << BigInt(17),
+ USE_EXTERNAL_EMOJIS: BigInt(1) << BigInt(18),
+ VIEW_GUILD_INSIGHTS: BigInt(1) << BigInt(19),
+ CONNECT: BigInt(1) << BigInt(20),
+ SPEAK: BigInt(1) << BigInt(21),
+ MUTE_MEMBERS: BigInt(1) << BigInt(22),
+ DEAFEN_MEMBERS: BigInt(1) << BigInt(23),
+ MOVE_MEMBERS: BigInt(1) << BigInt(24),
+ USE_VAD: BigInt(1) << BigInt(25),
+ CHANGE_NICKNAME: BigInt(1) << BigInt(26),
+ MANAGE_NICKNAMES: BigInt(1) << BigInt(27),
+ MANAGE_ROLES: BigInt(1) << BigInt(28),
+ MANAGE_WEBHOOKS: BigInt(1) << BigInt(29),
+ MANAGE_EMOJIS: BigInt(1) << BigInt(30),
+ /**
+ * CUSTOM PERMISSIONS ideas:
+ * - allow user to dm members
+ * - allow user to pin messages (without MANAGE_MESSAGES)
+ * - allow user to publish messages (without MANAGE_MESSAGES)
+ */
+ // CUSTOM_PERMISSION: BigInt(1) << BigInt(0) + CUSTOM_PERMISSION_OFFSET
+ };
+
+ any(permission: PermissionResolvable, checkAdmin = true) {
+ return (checkAdmin && super.any(Permissions.FLAGS.ADMINISTRATOR)) || super.any(permission);
+ }
+
+ /**
+ * Checks whether the bitfield has a permission, or multiple permissions.
+ */
+ has(permission: PermissionResolvable, checkAdmin = true) {
+ return (checkAdmin && super.has(Permissions.FLAGS.ADMINISTRATOR)) || super.has(permission);
+ }
+
+ /**
+ * Checks whether the bitfield has a permission, or multiple permissions, but throws an Error if user fails to match auth criteria.
+ */
+ hasThrow(permission: PermissionResolvable) {
+ if (this.has(permission) && this.has("VIEW_CHANNEL")) return true;
+ // @ts-ignore
+ throw new HTTPError(`You are missing the following permissions ${permission}`, 403);
+ }
+
+ overwriteChannel(overwrites: ChannelPermissionOverwrite[]) {
+ if (!this.cache) throw new Error("permission chache not available");
+ overwrites = overwrites.filter((x) => {
+ if (x.type === 0 && this.cache.roles?.some((r) => r.id === x.id)) return true;
+ if (x.type === 1 && x.id == this.cache.user_id) return true;
+ return false;
+ });
+ return new Permissions(Permissions.channelPermission(overwrites, this.bitfield));
+ }
+
+ static channelPermission(overwrites: ChannelPermissionOverwrite[], init?: bigint) {
+ // TODO: do not deny any permissions if admin
+ return overwrites.reduce((permission, overwrite) => {
+ // apply disallowed permission
+ // * permission: current calculated permission (e.g. 010)
+ // * deny contains all denied permissions (e.g. 011)
+ // * allow contains all explicitly allowed permisions (e.g. 100)
+ return (permission & ~BigInt(overwrite.deny)) | BigInt(overwrite.allow);
+ // ~ operator inverts deny (e.g. 011 -> 100)
+ // & operator only allows 1 for both ~deny and permission (e.g. 010 & 100 -> 000)
+ // | operators adds both together (e.g. 000 + 100 -> 100)
+ }, init || 0n);
+ }
+
+ static rolePermission(roles: Role[]) {
+ // adds all permissions of all roles together (Bit OR)
+ return roles.reduce((permission, role) => permission | BigInt(role.permissions), 0n);
+ }
+
+ static finalPermission({
+ user,
+ guild,
+ channel,
+ }: {
+ user: { id: string; roles: string[] };
+ guild: { roles: Role[] };
+ channel?: {
+ overwrites?: ChannelPermissionOverwrite[];
+ recipient_ids?: string[] | null;
+ owner_id?: string;
+ };
+ }) {
+ if (user.id === "0") return new Permissions("ADMINISTRATOR"); // system user id
+
+ let roles = guild.roles.filter((x) => user.roles.includes(x.id));
+ let permission = Permissions.rolePermission(roles);
+
+ if (channel?.overwrites) {
+ let overwrites = channel.overwrites.filter((x) => {
+ if (x.type === 0 && user.roles.includes(x.id)) return true;
+ if (x.type === 1 && x.id == user.id) return true;
+ return false;
+ });
+ permission = Permissions.channelPermission(overwrites, permission);
+ }
+
+ if (channel?.recipient_ids) {
+ if (channel?.owner_id === user.id) return new Permissions("ADMINISTRATOR");
+ if (channel.recipient_ids.includes(user.id)) {
+ // Default dm permissions
+ return new Permissions([
+ "VIEW_CHANNEL",
+ "SEND_MESSAGES",
+ "STREAM",
+ "ADD_REACTIONS",
+ "EMBED_LINKS",
+ "ATTACH_FILES",
+ "READ_MESSAGE_HISTORY",
+ "MENTION_EVERYONE",
+ "USE_EXTERNAL_EMOJIS",
+ "CONNECT",
+ "SPEAK",
+ "MANAGE_CHANNELS",
+ ]);
+ }
+
+ return new Permissions();
+ }
+
+ return new Permissions(permission);
+ }
+}
+
+export type PermissionCache = {
+ channel?: ChannelDocument | null;
+ member?: MemberDocument | null;
+ guild?: GuildDocument | null;
+ roles?: RoleDocument[] | null;
+ user_id?: string;
+};
+
+export async function getPermission(
+ user_id?: string,
+ guild_id?: string,
+ channel_id?: string,
+ cache: PermissionCache = {}
+) {
+ var { channel, member, guild, roles } = cache;
+
+ if (!user_id) throw new HTTPError("User not found");
+
+ if (channel_id && !channel) {
+ channel = await ChannelModel.findOne(
+ { id: channel_id },
+ { permission_overwrites: true, recipient_ids: true, owner_id: true, guild_id: true }
+ ).exec();
+ if (!channel) throw new HTTPError("Channel not found", 404);
+ if (channel.guild_id) guild_id = channel.guild_id;
+ }
+
+ if (guild_id) {
+ if (!guild) guild = await GuildModel.findOne({ id: guild_id }, { owner_id: true }).exec();
+ if (!guild) throw new HTTPError("Guild not found");
+ if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR);
+
+ if (!member) member = await MemberModel.findOne({ guild_id, id: user_id }, "roles").exec();
+ if (!member) throw new HTTPError("Member not found");
+
+ if (!roles) roles = await RoleModel.find({ guild_id, id: { $in: member.roles } }).exec();
+ }
+
+ var permission = Permissions.finalPermission({
+ user: {
+ id: user_id,
+ roles: member?.roles || [],
+ },
+ guild: {
+ roles: roles || [],
+ },
+ channel: {
+ overwrites: channel?.permission_overwrites,
+ owner_id: channel?.owner_id,
+ recipient_ids: channel?.recipient_ids,
+ },
+ });
+
+ const obj = new Permissions(permission);
+
+ // pass cache to permission for possible future getPermission calls
+ obj.cache = { guild, member, channel, roles, user_id };
+
+ return obj;
+}
|