Merge pull request #159 from Skillz4Killz/rename-permission_overwrites

Rename permission overwrites
This commit is contained in:
Skillz4Killz
2020-10-29 16:27:45 -04:00
committed by GitHub
49 changed files with 713 additions and 311 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
import { eventHandlers } from "../module/client.ts";
import type { DiscordPayload } from "../types/discord.ts";
import type { GuildBanPayload } from "../types/guild.ts";
import { DiscordPayload } from "../types/discord.ts";
import { GuildBanPayload } from "../types/guild.ts";
import { cacheHandlers } from "./cache.ts";
export async function handleInternalGuildBanAdd(data: DiscordPayload) {
+5 -5
View File
@@ -1,9 +1,9 @@
import type { Channel } from "../structures/channel.ts";
import type { Guild } from "../structures/guild.ts";
import type { Message } from "../structures/message.ts";
import type { PresenceUpdatePayload } from "../types/discord.ts";
import { Channel } from "../structures/channel.ts";
import { Guild } from "../structures/guild.ts";
import { Message } from "../structures/message.ts";
import { PresenceUpdatePayload } from "../types/discord.ts";
import { cache } from "../utils/cache.ts";
import type { Collection } from "../utils/collection.ts";
import { Collection } from "../utils/collection.ts";
export type TableName =
| "guilds"
+2 -3
View File
@@ -1,8 +1,7 @@
import { eventHandlers } from "../module/client.ts";
import { structures } from "../structures/mod.ts";
import type { ChannelCreatePayload } from "../types/channel.ts";
import { ChannelTypes } from "../types/channel.ts";
import type { DiscordPayload } from "../types/discord.ts";
import { ChannelCreatePayload, ChannelTypes } from "../types/channel.ts";
import { DiscordPayload } from "../types/discord.ts";
import { cacheHandlers } from "./cache.ts";
export async function handleInternalChannelCreate(data: DiscordPayload) {
+3 -3
View File
@@ -1,13 +1,13 @@
import { eventHandlers } from "../module/client.ts";
import { structures } from "../structures/mod.ts";
import type { DiscordPayload } from "../types/discord.ts";
import type {
import { DiscordPayload } from "../types/discord.ts";
import {
CreateGuildPayload,
GuildDeletePayload,
GuildEmojisUpdatePayload,
UpdateGuildPayload,
} from "../types/guild.ts";
import type { GuildUpdateChange } from "../types/options.ts";
import { GuildUpdateChange } from "../types/options.ts";
import { cache } from "../utils/cache.ts";
import { cacheHandlers } from "./cache.ts";
+2 -7
View File
@@ -1,7 +1,7 @@
import { eventHandlers } from "../module/client.ts";
import { structures } from "../structures/mod.ts";
import type { DiscordPayload } from "../types/discord.ts";
import type {
import { DiscordPayload } from "../types/discord.ts";
import {
GuildBanPayload,
GuildMemberAddPayload,
GuildMemberChunkPayload,
@@ -41,11 +41,6 @@ export async function handleInternalGuildMemberRemove(data: DiscordPayload) {
member || payload.user,
);
eventHandlers.guildMemberRemove?.(
guild,
member || payload.user,
);
guild.members.delete(payload.user.id);
}
+6 -5
View File
@@ -1,7 +1,7 @@
import { eventHandlers } from "../module/client.ts";
import { structures } from "../structures/mod.ts";
import type { DiscordPayload } from "../types/discord.ts";
import type {
import { DiscordPayload } from "../types/discord.ts";
import {
MessageCreateOptions,
MessageDeleteBulkPayload,
MessageDeletePayload,
@@ -15,9 +15,6 @@ export async function handleInternalMessageCreate(data: DiscordPayload) {
const channel = await cacheHandlers.get("channels", payload.channel_id);
if (channel) channel.lastMessageID = payload.id;
const message = await structures.createMessage(payload);
// Cache the message
cacheHandlers.set("messages", payload.id, message);
const guild = payload.guild_id
? await cacheHandlers.get("guilds", payload.guild_id)
: undefined;
@@ -46,6 +43,10 @@ export async function handleInternalMessageCreate(data: DiscordPayload) {
}
});
const message = await structures.createMessage(payload);
// Cache the message
cacheHandlers.set("messages", payload.id, message);
eventHandlers.messageCreate?.(message);
}
+2 -2
View File
@@ -2,7 +2,7 @@ import { delay } from "../../deps.ts";
import { eventHandlers, setBotID } from "../module/client.ts";
import { allowNextShard } from "../module/shardingManager.ts";
import { structures } from "../structures/mod.ts";
import type {
import {
DiscordPayload,
PresenceUpdatePayload,
ReadyPayload,
@@ -10,7 +10,7 @@ import type {
VoiceStateUpdatePayload,
WebhookUpdatePayload,
} from "../types/discord.ts";
import type { UserPayload } from "../types/guild.ts";
import { UserPayload } from "../types/guild.ts";
import { cache } from "../utils/cache.ts";
import { cacheHandlers } from "./cache.ts";
+2 -2
View File
@@ -1,7 +1,7 @@
import { botID, eventHandlers } from "../module/client.ts";
import { structures } from "../structures/mod.ts";
import type { DiscordPayload } from "../types/discord.ts";
import type {
import { DiscordPayload } from "../types/discord.ts";
import {
BaseMessageReactionPayload,
MessageReactionPayload,
MessageReactionRemoveEmojiPayload,
+8 -5
View File
@@ -1,10 +1,7 @@
import { eventHandlers } from "../module/client.ts";
import { structures } from "../structures/mod.ts";
import type { DiscordPayload } from "../types/discord.ts";
import type {
GuildRoleDeletePayload,
GuildRolePayload,
} from "../types/guild.ts";
import { DiscordPayload } from "../types/discord.ts";
import { GuildRoleDeletePayload, GuildRolePayload } from "../types/guild.ts";
import { cacheHandlers } from "./cache.ts";
export async function handleInternalGuildRoleCreate(data: DiscordPayload) {
@@ -30,6 +27,11 @@ export async function handleInternalGuildRoleDelete(data: DiscordPayload) {
const cachedRole = guild.roles.get(payload.role_id)!;
guild.roles.delete(payload.role_id);
eventHandlers.roleDelete?.(guild, cachedRole);
// For bots without GUILD_MEMBERS member.roles is never updated breaking permissions checking.
guild.members.forEach((member) => {
member.roles = member.roles.filter((id) => id !== payload.role_id);
});
}
export async function handleInternalGuildRoleUpdate(data: DiscordPayload) {
@@ -43,5 +45,6 @@ export async function handleInternalGuildRoleUpdate(data: DiscordPayload) {
if (!cachedRole) return;
const role = await structures.createRole(payload.role);
guild.roles.set(payload.role.id, role);
eventHandlers.roleUpdate?.(guild, role, cachedRole);
}
+23 -7
View File
@@ -1,8 +1,10 @@
import { endpoints } from "../constants/discord.ts";
import { cacheHandlers } from "../controllers/cache.ts";
import { RequestManager } from "../module/requestManager.ts";
import { structures } from "../structures/mod.ts";
import type {
import {
ChannelEditOptions,
ChannelTypes,
CreateInviteOptions,
FollowedChannelPayload,
GetMessages,
@@ -12,16 +14,19 @@ import type {
MessageContent,
} from "../types/channel.ts";
import { Errors } from "../types/errors.ts";
import type { RawOverwrite } from "../types/guild.ts";
import type { MessageCreateOptions } from "../types/message.ts";
import { PermissionOverwrite } from "../types/guild.ts";
import { MessageCreateOptions } from "../types/message.ts";
import { Permissions } from "../types/permission.ts";
import { botHasChannelPermissions } from "../utils/permissions.ts";
import {
botHasChannelPermissions,
calculateBits,
} from "../utils/permissions.ts";
/** Checks if a channel overwrite for a user id or a role id has permission in this channel */
export function channelOverwriteHasPermission(
guildID: string,
id: string,
overwrites: RawOverwrite[],
overwrites: PermissionOverwrite[],
permissions: Permissions[],
) {
const overwrite = overwrites.find((perm) => perm.id === id) ||
@@ -29,8 +34,10 @@ export function channelOverwriteHasPermission(
return permissions.every((perm) => {
if (overwrite) {
if (BigInt(overwrite.deny) & BigInt(perm)) return false;
if (BigInt(overwrite.allow) & BigInt(perm)) return true;
const allowBits = calculateBits(overwrite.allow);
const denyBits = calculateBits(overwrite.deny);
if (BigInt(denyBits) & BigInt(perm)) return false;
if (BigInt(allowBits) & BigInt(perm)) return true;
}
return false;
});
@@ -160,6 +167,15 @@ export async function sendMessage(
}
}
const channel = await cacheHandlers.get("channels", channelID);
if (!channel) throw new Error(Errors.CHANNEL_NOT_FOUND);
if (
![ChannelTypes.DM, ChannelTypes.GUILD_NEWS, ChannelTypes.GUILD_TEXT]
.includes(channel.type)
) {
throw new Error(Errors.CHANNEL_NOT_TEXT_BASED);
}
const result = await RequestManager.post(
endpoints.CHANNEL_MESSAGES(channelID),
{
+14 -9
View File
@@ -3,13 +3,13 @@ import { cacheHandlers } from "../controllers/cache.ts";
import { identifyPayload } from "../module/client.ts";
import { RequestManager } from "../module/requestManager.ts";
import { requestAllMembers } from "../module/shardingManager.ts";
import type { Guild } from "../structures/guild.ts";
import type { Member } from "../structures/member.ts";
import { Guild } from "../structures/guild.ts";
import { Member } from "../structures/member.ts";
import { structures } from "../structures/mod.ts";
import type { ImageFormats, ImageSize } from "../types/cdn.ts";
import { ImageFormats, ImageSize } from "../types/cdn.ts";
import { ChannelCreatePayload, ChannelTypes } from "../types/channel.ts";
import { Errors } from "../types/errors.ts";
import type {
import {
BannedUser,
BanOptions,
ChannelCreateOptions,
@@ -26,13 +26,13 @@ import type {
PrunePayload,
UserPayload,
} from "../types/guild.ts";
import type { MemberCreatePayload } from "../types/member.ts";
import { MemberCreatePayload } from "../types/member.ts";
import { Intents } from "../types/options.ts";
import { Permissions } from "../types/permission.ts";
import type { RoleData } from "../types/role.ts";
import { RoleData } from "../types/role.ts";
import { formatImageURL } from "../utils/cdn.ts";
import { Collection } from "../utils/collection.ts";
import { botHasPermission } from "../utils/permissions.ts";
import { botHasPermission, calculateBits } from "../utils/permissions.ts";
import { urlToBase64 } from "../utils/utils.ts";
/** Create a new guild. Returns a guild object on success. Fires a Guild Create Gateway event. This endpoint can be used only by bots in less than 10 guilds. */
@@ -105,7 +105,7 @@ export async function createGuildChannel(
(await RequestManager.post(endpoints.GUILD_CHANNELS(guild.id), {
...options,
name,
permission_overwrites: options?.permission_overwrites?.map((perm) => ({
permission_overwrites: options?.permissionOverwrites?.map((perm) => ({
...perm,
allow: perm.allow.reduce(
@@ -317,7 +317,12 @@ export function editRole(
) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.patch(endpoints.GUILD_ROLE(guildID, id), options);
return RequestManager.patch(endpoints.GUILD_ROLE(guildID, id), {
...options,
permissions: options.permissions
? calculateBits(options.permissions)
: undefined,
});
}
/** Delete a guild role. Requires the MANAGE_ROLES permission. */
+4 -7
View File
@@ -2,15 +2,12 @@ import { endpoints } from "../constants/discord.ts";
import { cacheHandlers } from "../controllers/cache.ts";
import { botID } from "../module/client.ts";
import { RequestManager } from "../module/requestManager.ts";
import type { Member } from "../structures/member.ts";
import { Member } from "../structures/member.ts";
import { structures } from "../structures/mod.ts";
import type { ImageFormats, ImageSize } from "../types/cdn.ts";
import type {
DMChannelCreatePayload,
MessageContent,
} from "../types/channel.ts";
import { ImageFormats, ImageSize } from "../types/cdn.ts";
import { DMChannelCreatePayload, MessageContent } from "../types/channel.ts";
import { Errors } from "../types/errors.ts";
import type { EditMemberOptions } from "../types/member.ts";
import { EditMemberOptions } from "../types/member.ts";
import { Permissions } from "../types/permission.ts";
import { formatImageURL } from "../utils/cdn.ts";
import {
+4 -4
View File
@@ -3,12 +3,12 @@ import { endpoints } from "../constants/discord.ts";
import { cacheHandlers } from "../controllers/cache.ts";
import { botID } from "../module/client.ts";
import { RequestManager } from "../module/requestManager.ts";
import type { Message } from "../structures/message.ts";
import { Message } from "../structures/message.ts";
import { structures } from "../structures/mod.ts";
import type { MessageContent } from "../types/channel.ts";
import { MessageContent } from "../types/channel.ts";
import { Errors } from "../types/errors.ts";
import type { UserPayload } from "../types/guild.ts";
import type { MessageCreateOptions } from "../types/message.ts";
import { UserPayload } from "../types/guild.ts";
import { MessageCreateOptions } from "../types/message.ts";
import { Permissions } from "../types/permission.ts";
import { botHasChannelPermissions } from "../utils/permissions.ts";
+2 -2
View File
@@ -2,9 +2,9 @@ import { endpoints } from "../constants/discord.ts";
import { RequestManager } from "../module/requestManager.ts";
import { structures } from "../structures/mod.ts";
import { Errors } from "../types/errors.ts";
import type { MessageCreateOptions } from "../types/message.ts";
import { MessageCreateOptions } from "../types/message.ts";
import { Permissions } from "../types/permission.ts";
import type {
import {
ExecuteWebhookOptions,
WebhookCreateOptions,
WebhookPayload,
+38 -14
View File
@@ -7,21 +7,22 @@ import {
isWebSocketPongEvent,
WebSocket,
} from "../../deps.ts";
import type {
import {
DiscordBotGatewayData,
DiscordHeartbeatPayload,
GatewayOpcode,
ReadyPayload,
} from "../types/discord.ts";
import { GatewayOpcode } from "../types/discord.ts";
import type { FetchMembersOptions } from "../types/guild.ts";
import type { BotStatusRequest } from "../utils/utils.ts";
import type { IdentifyPayload } from "./client.ts";
import { botGatewayData, eventHandlers } from "./client.ts";
import { FetchMembersOptions } from "../types/guild.ts";
import { BotStatusRequest } from "../utils/utils.ts";
import { botGatewayData, eventHandlers, IdentifyPayload } from "./client.ts";
import { handleDiscordPayload } from "./shardingManager.ts";
const basicShards = new Map<number, BasicShard>();
const heartbeating = new Set<number>();
const heartbeating = new Map<number, boolean>();
const utf8decoder = new TextDecoder();
const RequestMembersQueue: RequestMemberQueuedRequest[] = [];
let processQueue = false;
export interface BasicShard {
id: number;
@@ -32,9 +33,6 @@ export interface BasicShard {
needToResume: boolean;
}
const RequestMembersQueue: RequestMemberQueuedRequest[] = [];
let processQueue = false;
interface RequestMemberQueuedRequest {
guildID: string;
shardID: number;
@@ -52,7 +50,7 @@ export async function createBasicShard(
const basicShard: BasicShard = {
id: shardID,
socket: await connectWebSocket(`${data.url}?v=6&encoding=json`),
socket: await connectWebSocket(`${data.url}?v=8&encoding=json`),
resumeInterval: 0,
sessionID: oldShard?.sessionID || "",
previousSequenceNumber: oldShard?.previousSequenceNumber || 0,
@@ -123,9 +121,13 @@ export async function createBasicShard(
heartbeat(
basicShard,
(data.d as DiscordHeartbeatPayload).heartbeat_interval,
identifyPayload,
);
}
break;
case GatewayOpcode.HeartbeatACK:
heartbeating.set(shardID, true);
break;
case GatewayOpcode.Reconnect:
eventHandlers.debug?.(
{ type: "reconnect", data: { shardID: basicShard.id } },
@@ -200,17 +202,39 @@ function resume(shard: BasicShard, payload: IdentifyPayload) {
}));
}
// TODO: If a client does not receive a heartbeat ack between its attempts at sending heartbeats, it should immediately terminate the connection with a non-1000 close code, reconnect, and attempt to resume.
async function heartbeat(
shard: BasicShard,
interval: number,
payload: IdentifyPayload,
) {
// We lost socket connection between heartbeats, resume connection
if (shard.socket.isClosed) {
shard.needToResume = true;
resumeConnection(botGatewayData, payload, shard.id);
heartbeating.delete(shard.id);
return;
}
if (!heartbeating.has(shard.id)) heartbeating.add(shard.id);
if (heartbeating.has(shard.id)) {
const receivedACK = heartbeating.get(shard.id);
// If a ACK response was not received since last heartbeat, issue invalid session close
if (!receivedACK) {
eventHandlers.debug?.(
{
type: "heartbeatStopped",
data: {
interval,
previousSequenceNumber: shard.previousSequenceNumber,
shardID: shard.id,
},
},
);
return shard.socket.send(JSON.stringify({ op: 4009 }));
}
}
// Set it to false as we are issuing a new heartbeat
heartbeating.set(shard.id, false);
shard.socket.send(
JSON.stringify(
@@ -228,7 +252,7 @@ async function heartbeat(
},
);
await delay(interval);
heartbeat(shard, interval);
heartbeat(shard, interval, payload);
}
async function resumeConnection(
+2 -2
View File
@@ -1,6 +1,6 @@
import { endpoints } from "../constants/discord.ts";
import type { DiscordBotGatewayData } from "../types/discord.ts";
import type { ClientOptions, EventHandlers } from "../types/options.ts";
import { DiscordBotGatewayData } from "../types/discord.ts";
import { ClientOptions, EventHandlers } from "../types/options.ts";
import { RequestManager } from "./requestManager.ts";
import { spawnShards } from "./shardingManager.ts";
+19 -8
View File
@@ -2,7 +2,7 @@ import { delay } from "../../deps.ts";
import { baseEndpoints } from "../constants/discord.ts";
import { HttpResponseCode } from "../types/discord.ts";
import { Errors } from "../types/errors.ts";
import type { RequestMethods } from "../types/fetch.ts";
import { RequestMethods } from "../types/fetch.ts";
import { authorization, eventHandlers } from "./client.ts";
const pathQueues: { [key: string]: QueuedRequest[] } = {};
@@ -286,6 +286,23 @@ async function runMethod(
});
}
async function logErrors(response: Response, errorStack?: unknown) {
try {
const error = await response.json();
console.error(error);
eventHandlers.debug?.({ type: "error", data: { errorStack, error } });
} catch {
eventHandlers.debug?.(
{
type: "error",
data: { errorStack },
},
);
console.error(response);
}
}
function handleStatusCode(response: Response, errorStack?: unknown) {
const status = response.status;
@@ -296,13 +313,7 @@ function handleStatusCode(response: Response, errorStack?: unknown) {
return true;
}
eventHandlers.debug?.(
{
type: "error",
data: { errorStack },
},
);
console.error(response);
logErrors(response, errorStack);
switch (status) {
case HttpResponseCode.BadRequest:
+10 -6
View File
@@ -1,13 +1,17 @@
import type { WebSocket } from "../../deps.ts";
import { connectWebSocket, delay, isWebSocketCloseEvent } from "../../deps.ts";
import type {
import {
connectWebSocket,
delay,
isWebSocketCloseEvent,
WebSocket,
} from "../../deps.ts";
import {
DiscordBotGatewayData,
DiscordHeartbeatPayload,
GatewayOpcode,
ReadyPayload,
} from "../types/discord.ts";
import { GatewayOpcode } from "../types/discord.ts";
import type { FetchMembersOptions } from "../types/guild.ts";
import type { DebugArg } from "../types/options.ts";
import { FetchMembersOptions } from "../types/guild.ts";
import { DebugArg } from "../types/options.ts";
let shardSocket: WebSocket;
+11 -7
View File
@@ -1,21 +1,25 @@
import { delay } from "../../deps.ts";
import { controllers } from "../controllers/mod.ts";
import type { Guild } from "../structures/guild.ts";
import type {
import { Guild } from "../structures/guild.ts";
import {
DiscordBotGatewayData,
DiscordPayload,
GatewayOpcode,
} from "../types/discord.ts";
import { GatewayOpcode } from "../types/discord.ts";
import type { FetchMembersOptions } from "../types/guild.ts";
import { FetchMembersOptions } from "../types/guild.ts";
import { cache } from "../utils/cache.ts";
import type { BotStatusRequest } from "../utils/utils.ts";
import { BotStatusRequest } from "../utils/utils.ts";
import {
botGatewayStatusRequest,
createBasicShard,
requestGuildMembers,
} from "./basicShard.ts";
import type { IdentifyPayload } from "./client.ts";
import { botGatewayData, eventHandlers, identifyPayload } from "./client.ts";
import {
botGatewayData,
eventHandlers,
IdentifyPayload,
identifyPayload,
} from "./client.ts";
let shardCounter = 0;
let basicSharding = false;
+13 -10
View File
@@ -1,6 +1,7 @@
import { cacheHandlers } from "../controllers/cache.ts";
import type { ChannelCreatePayload } from "../types/channel.ts";
import type { Unpromise } from "../types/misc.ts";
import { ChannelCreatePayload } from "../types/channel.ts";
import { PermissionOverwrite } from "../types/guild.ts";
import { Unpromise } from "../types/misc.ts";
import { calculatePermissions } from "../utils/permissions.ts";
export async function createChannel(
@@ -14,13 +15,14 @@ export async function createChannel(
rate_limit_per_user: rateLimitPerUser,
parent_id: parentID,
last_pin_timestamp: lastPinTimestamp,
permission_overwrites,
...rest
} = data;
const channel = {
...rest,
/** The guild id of the channel if it is a guild channel. */
guildID: guildID || rawGuildID,
guildID: guildID || rawGuildID || "",
/** The id of the last message sent in this channel */
lastMessageID,
/** The amount of users allowed in this voice channel. */
@@ -32,13 +34,14 @@ export async function createChannel(
/** The last time when a message was pinned in this channel */
lastPinTimestamp,
/** The permission overwrites for this channel */
permissions: data.permission_overwrites
? data.permission_overwrites.map((perm) => ({
...perm,
allow: calculatePermissions(BigInt(perm.allow)),
deny: calculatePermissions(BigInt(perm.deny)),
}))
: [],
permissionOverwrites:
(data.permission_overwrites
? data.permission_overwrites.map((perm) => ({
...perm,
allow: calculatePermissions(BigInt(perm.allow)),
deny: calculatePermissions(BigInt(perm.deny)),
}))
: []) as PermissionOverwrite[],
/** Whether this channel is nsfw or not */
nsfw: data.nsfw || false,
/** The mention of the channel */
+3 -3
View File
@@ -1,7 +1,7 @@
import type { CreateGuildPayload } from "../types/guild.ts";
import type { Unpromise } from "../types/misc.ts";
import { CreateGuildPayload } from "../types/guild.ts";
import { Unpromise } from "../types/misc.ts";
import { Collection } from "../utils/collection.ts";
import type { Member } from "./member.ts";
import { Member } from "./member.ts";
import { structures } from "./mod.ts";
export async function createGuild(data: CreateGuildPayload, shardID: number) {
+2 -2
View File
@@ -1,5 +1,5 @@
import type { MemberCreatePayload } from "../types/member.ts";
import type { Unpromise } from "../types/misc.ts";
import { MemberCreatePayload } from "../types/member.ts";
import { Unpromise } from "../types/misc.ts";
export async function createMember(data: MemberCreatePayload, guildID: string) {
const {
+2 -2
View File
@@ -1,5 +1,5 @@
import type { MessageCreateOptions } from "../types/message.ts";
import type { Unpromise } from "../types/misc.ts";
import { MessageCreateOptions } from "../types/message.ts";
import { Unpromise } from "../types/misc.ts";
export async function createMessage(data: MessageCreateOptions) {
const {
+2 -2
View File
@@ -1,5 +1,5 @@
import type { Unpromise } from "../types/misc.ts";
import type { RoleData } from "../types/role.ts";
import { Unpromise } from "../types/misc.ts";
import { RoleData } from "../types/role.ts";
export async function createRole(data: RoleData) {
return {
+49 -3
View File
@@ -1,12 +1,18 @@
import type { Timestamps } from "./discord.ts";
export interface ActivityPayload {
name: string;
type: number;
url?: string;
created_at: number;
timestamps: Timestamps;
timestamps?: ActivityTimestamps;
application_id?: string;
details?: string;
state?: string;
emoji?: ActivityEmoji;
party?: ActivityParty;
assets?: ActivityAssets;
secrets?: ActivitySecrets;
instance?: boolean;
flags?: number;
}
export enum ActivityType {
@@ -18,4 +24,44 @@ export enum ActivityType {
Listening,
/** Example: ":smiley: I am cool" */
Custom = 4,
/** Example: "Competing in Arena World Champions" */
Competing,
}
export interface ActivityTimestamps {
start?: number;
end?: number;
}
export interface ActivityEmoji {
name: string;
id?: string;
animated?: boolean;
}
export interface ActivityParty {
id?: string;
size?: [number, number];
}
export interface ActivityAssets {
large_image?: string;
large_text?: string;
small_image?: string;
small_text?: string;
}
export interface ActivitySecrets {
join?: string;
spectate?: string;
match?: string;
}
export enum ActivityFlags {
INSTANCE = 1 << 0,
JOIN = 1 << 1,
SPECTATE = 1 << 2,
JOIN_REQUEST = 1 << 3,
SYNC = 1 << 4,
PLAY = 1 << 5,
}
+2 -2
View File
@@ -1,5 +1,5 @@
import type { Overwrite, RawOverwrite } from "./guild.ts";
import type { Embed } from "./message.ts";
import { Overwrite, RawOverwrite } from "./guild.ts";
import { Embed } from "./message.ts";
export interface ChannelEditOptions {
/** 2-100 character channel name. All */
+4 -9
View File
@@ -1,7 +1,7 @@
import type { PartialUser, UserPayload } from "./guild.ts";
import type { MemberCreatePayload } from "./member.ts";
import type { Activity } from "./message.ts";
import type { ClientStatusPayload } from "./presence.ts";
import { PartialUser, UserPayload } from "./guild.ts";
import { MemberCreatePayload } from "./member.ts";
import { Activity } from "./message.ts";
import { ClientStatusPayload } from "./presence.ts";
export interface DiscordPayload {
/** OP code for the payload */
@@ -196,11 +196,6 @@ export interface Properties {
$device: string;
}
export interface Timestamps {
start?: number;
end?: number;
}
export interface Emoji {
name: string;
id?: string;
+2
View File
@@ -31,4 +31,6 @@ export enum Errors {
CHANNEL_NOT_IN_GUILD = "CHANNEL_NOT_IN_GUILD",
INVALID_WEBHOOK_NAME = "INVALID_WEBHOOK_NAME",
INVALID_WEBHOOK_OPTIONS = "INVALID_WEBHOOK_OPTIONS",
CHANNEL_NOT_FOUND = "CHANNEL_NOT_FOUND",
CHANNEL_NOT_TEXT_BASED = "CHANNEL_NOT_TEXT_BASED",
}
+14 -8
View File
@@ -1,10 +1,10 @@
import type { ChannelCreatePayload, ChannelTypes } from "./channel.ts";
import type { Emoji, StatusType } from "./discord.ts";
import type { MemberCreatePayload } from "./member.ts";
import type { Activity } from "./message.ts";
import type { Permission } from "./permission.ts";
import type { ClientStatusPayload } from "./presence.ts";
import type { RoleData } from "./role.ts";
import { ChannelCreatePayload, ChannelTypes } from "./channel.ts";
import { Emoji, StatusType } from "./discord.ts";
import { MemberCreatePayload } from "./member.ts";
import { Activity } from "./message.ts";
import { Permission } from "./permission.ts";
import { ClientStatusPayload } from "./presence.ts";
import { RoleData } from "./role.ts";
export interface GuildRolePayload {
/** The id of the guild */
@@ -473,6 +473,12 @@ export interface RawOverwrite {
deny: number;
}
export interface PermissionOverwrite
extends Omit<RawOverwrite, "allow" | "deny"> {
allow: Permission[];
deny: Permission[];
}
export interface ChannelCreateOptions {
/** The type of the channel */
type?: ChannelTypes;
@@ -487,7 +493,7 @@ export interface ChannelCreateOptions {
/** The sorting position of the channel */
position?: number;
/** The channel's permission overwrites */
permission_overwrites?: Overwrite[];
permissionOverwrites?: Overwrite[];
/** The id of the parent category for the channel */
parent_id?: string;
/** Whether the channel is nsfw */
+1 -1
View File
@@ -1,4 +1,4 @@
import type { UserPayload } from "./guild.ts";
import { UserPayload } from "./guild.ts";
export interface EditMemberOptions {
/** Value to set users nickname to. Requires MANAGE_NICKNAMES permission. */
+4 -4
View File
@@ -1,7 +1,7 @@
import type { Channel } from "../structures/channel.ts";
import type { ChannelType } from "./channel.ts";
import type { UserPayload } from "./guild.ts";
import type { MemberCreatePayload } from "./member.ts";
import { Channel } from "../structures/channel.ts";
import { ChannelType } from "./channel.ts";
import { UserPayload } from "./guild.ts";
import { MemberCreatePayload } from "./member.ts";
export interface MentionedUser extends UserPayload {
member: MemberCreatePayload;
+9 -8
View File
@@ -1,9 +1,9 @@
import type { Channel } from "../structures/channel.ts";
import type { Guild } from "../structures/guild.ts";
import type { Member } from "../structures/member.ts";
import type { Message } from "../structures/message.ts";
import type { Role } from "../structures/role.ts";
import type {
import { Channel } from "../structures/channel.ts";
import { Guild } from "../structures/guild.ts";
import { Member } from "../structures/member.ts";
import { Message } from "../structures/message.ts";
import { Role } from "../structures/role.ts";
import {
DiscordPayload,
Emoji,
PresenceUpdatePayload,
@@ -11,8 +11,8 @@ import type {
TypingStartPayload,
VoiceStateUpdatePayload,
} from "./discord.ts";
import type { UserPayload } from "./guild.ts";
import type {
import { UserPayload } from "./guild.ts";
import {
Attachment,
BaseMessageReactionPayload,
Embed,
@@ -64,6 +64,7 @@ export interface DebugArg {
| "requestManagerFetched"
| "requestMembersProcessing"
| "heartbeat"
| "heartbeatStopped"
| "createShard"
| "invalidSession"
| "reconnect"
+1 -1
View File
@@ -1,4 +1,4 @@
import type { StatusType } from "./discord.ts";
import { StatusType } from "./discord.ts";
export interface ClientStatusPayload {
/** The user's status set for an active desktop (Windows, Linux, Mac) application session */
+2 -2
View File
@@ -1,5 +1,5 @@
import type { UserPayload } from "./guild.ts";
import type { Embed } from "./message.ts";
import { UserPayload } from "./guild.ts";
import { Embed } from "./message.ts";
export interface WebhookPayload {
/** The id of the webhook */
+5 -5
View File
@@ -1,7 +1,7 @@
import type { Channel } from "../structures/channel.ts";
import type { Guild } from "../structures/guild.ts";
import type { Message } from "../structures/message.ts";
import type { PresenceUpdatePayload } from "../types/discord.ts";
import { Channel } from "../structures/channel.ts";
import { Guild } from "../structures/guild.ts";
import { Message } from "../structures/message.ts";
import { PresenceUpdatePayload } from "../types/discord.ts";
import { Collection } from "./collection.ts";
export interface CacheData {
@@ -21,5 +21,5 @@ export const cache: CacheData = {
messages: new Collection(),
unavailableGuilds: new Collection(),
presences: new Collection(),
fetchAllMembersProcessingRequests: new Collection<string, Function>(),
fetchAllMembersProcessingRequests: new Collection(),
};
+1 -1
View File
@@ -1,4 +1,4 @@
import type { ImageFormats, ImageSize } from "../types/cdn.ts";
import { ImageFormats, ImageSize } from "../types/cdn.ts";
export const formatImageURL = (
url: string,
+85 -70
View File
@@ -1,9 +1,9 @@
import { cacheHandlers } from "../controllers/cache.ts";
import { botID } from "../module/client.ts";
import type { Guild } from "../structures/guild.ts";
import type { Role } from "../structures/role.ts";
import type { Permission } from "../types/permission.ts";
import { Permissions } from "../types/permission.ts";
import { Guild } from "../structures/guild.ts";
import { Role } from "../structures/role.ts";
import { PermissionOverwrite } from "../types/guild.ts";
import { Permission, Permissions } from "../types/permission.ts";
/** Checks if the member has this permission. If the member is an owner or has admin perms it will always be true. */
export async function memberIDHasPermission(
@@ -34,6 +34,8 @@ export function memberHasPermission(
const permissionBits = memberRoleIDs.map((id) =>
guild.roles.get(id)?.permissions
)
// Removes any edge case undefined
.filter((id) => id)
.reduce((bits, permissions) => {
bits |= BigInt(permissions);
return bits;
@@ -58,6 +60,8 @@ export async function botHasPermission(
const permissionBits = member.roles
.map((id) => guild.roles.get(id)!)
// Remove any edge case undefined
.filter((r) => r)
.reduce((bits, data) => {
bits |= BigInt(data.permissions);
@@ -84,101 +88,104 @@ export async function hasChannelPermissions(
permissions: Permissions[],
) {
const channel = await cacheHandlers.get("channels", channelID);
if (!channel?.guildID) return true;
if (!channel) return false;
if (!channel.guildID) return true;
const guild = await cacheHandlers.get("guilds", channel.guildID);
if (!guild) return false;
if (guild.ownerID === memberID) return true;
if (botHasPermission(guild.id, [Permissions.ADMINISTRATOR])) return true;
if (
await memberIDHasPermission(memberID, guild.id, ["ADMINISTRATOR"])
) {
return true;
}
const member = guild.members.get(memberID);
if (!member) return false;
const memberOverwrite = channel.permission_overwrites?.find((o) =>
o.id === memberID
);
let memberOverwrite: PermissionOverwrite | undefined;
let everyoneOverwrite: PermissionOverwrite | undefined;
let rolesOverwrites: PermissionOverwrite[] = [];
const rolesOverwrites = channel.permission_overwrites?.filter((o) =>
member.roles.includes(o.id)
);
const everyoneOverwrite = channel.permission_overwrites?.find((o) =>
o.id === guild.id
);
for (const overwrite of channel.permissionOverwrites || []) {
// If the overwrite on this channel is specific to this member
if (overwrite.id === memberID) memberOverwrite = overwrite;
// If it is the everyone role overwrite
if (overwrite.id === guild.id) everyoneOverwrite = overwrite;
// If it is one of the roles the member has
if (member.roles.includes(overwrite.id)) rolesOverwrites.push(overwrite);
}
const allowedPermissions = new Set<Permissions>();
// Member perms override everything so we must check them first
if (memberOverwrite) {
// One of the necessary permissions is denied
if (
permissions.some((perm) => BigInt(memberOverwrite.deny) & BigInt(perm))
) {
return false;
}
permissions.forEach((perm) => {
const allowBits = calculateBits(memberOverwrite.allow);
const denyBits = calculateBits(memberOverwrite.deny);
for (const perm of permissions) {
// One of the necessary permissions is denied. Since this is main permission we can cancel if its denied.
if (BigInt(denyBits) & BigInt(perm)) return false;
// Already allowed perm
if (allowedPermissions.has(perm)) return;
if (allowedPermissions.has(perm)) continue;
// This perm is allowed so we save it
if (BigInt(memberOverwrite.allow) & BigInt(perm)) {
if (BigInt(allowBits) & BigInt(perm)) {
allowedPermissions.add(perm);
}
});
}
}
// Check the necessary permissions for roles
if (rolesOverwrites?.length) {
if (
rolesOverwrites.some((overwrite) =>
permissions.some((perm) =>
(BigInt(overwrite.deny) & BigInt(perm)) &&
// If another role allows these perms then they are not denied
!rolesOverwrites.some((o) => BigInt(o.allow) & BigInt(perm)) &&
// Make sure the memberOverwrite does not allow this perm
!(memberOverwrite && BigInt(memberOverwrite.allow) & BigInt(perm))
)
)
) {
return false;
}
for (const perm of permissions) {
// If this is already allowed, skip
if (allowedPermissions.has(perm)) continue;
permissions.forEach((perm) => {
for (const overwrite of rolesOverwrites) {
const allowBits = calculateBits(overwrite.allow);
// This perm is allowed so we save it
if (BigInt(allowBits) & BigInt(perm)) {
allowedPermissions.add(perm);
break;
}
const denyBits = calculateBits(overwrite.deny);
// If this role denies it we need to save and check if another role allows it, allows > deny
if (BigInt(denyBits) & BigInt(perm)) {
// This role denies his perm, but before denying we need to check all other roles if any allow as allow > deny
const isAllowed = rolesOverwrites.some((o) =>
BigInt(calculateBits(o.allow)) & BigInt(perm)
);
if (isAllowed) continue;
// This permission is in fact denied. Since Roles overrule everything below here we can cancel ou here
return false;
}
}
}
if (everyoneOverwrite) {
const allowBits = calculateBits(everyoneOverwrite.allow);
const denyBits = calculateBits(everyoneOverwrite.deny);
for (const perm of permissions) {
// Already allowed perm
if (allowedPermissions.has(perm)) return;
rolesOverwrites.forEach((overwrite) => {
// This perm is allowed so we save it
if (BigInt(overwrite.allow) & BigInt(perm)) {
allowedPermissions.add(perm);
}
});
});
}
// Check the necessary permissions for everyone
if (
everyoneOverwrite
) {
if (
permissions.some((perm) =>
BigInt(everyoneOverwrite.deny) & BigInt(perm) &&
!allowedPermissions.has(perm)
)
) {
return false;
}
// If all permissions are granted
if (
permissions.every((perm) =>
BigInt(everyoneOverwrite.allow) & BigInt(perm)
)
) {
return true;
if (allowedPermissions.has(perm)) continue;
// One of the necessary permissions is denied. Since everyone overwrite overrides role perms we can cancel here
if (BigInt(denyBits) & BigInt(perm)) return false;
// This perm is allowed so we save it
if (BigInt(allowBits) & BigInt(perm)) {
allowedPermissions.add(perm);
}
}
}
// Is there any remaining permission to check role perms or can we determine that permissions are allowed
if (permissions.every((perm) => allowedPermissions.has(perm))) return true;
// Some permission was not explicitly allowed so we default to checking role perms directly
return botHasPermission(guild.id, permissions);
}
/** This function converts a bitwise string to permission strings */
export function calculatePermissions(permissionBits: bigint) {
return Object.keys(Permissions).filter((perm) => {
if (typeof perm !== "number") return false;
@@ -186,6 +193,14 @@ export function calculatePermissions(permissionBits: bigint) {
}) as Permission[];
}
/** This function converts an array of permissions into the bitwise string. */
export function calculateBits(permissions: Permission[]) {
return permissions.reduce(
(bits, perm) => bits |= BigInt(Permissions[perm]),
BigInt(0),
).toString();
}
export async function highestRole(guildID: string, memberID: string) {
const guild = await cacheHandlers.get("guilds", guildID);
if (!guild) return;
+1 -1
View File
@@ -1,7 +1,7 @@
import { encode } from "../../deps.ts";
import { sendGatewayCommand } from "../module/shardingManager.ts";
import { ActivityType } from "../types/activity.ts";
import type { StatusType } from "../types/discord.ts";
import { StatusType } from "../types/discord.ts";
export const sleep = (timeout: number) => {
return new Promise((resolve) => setTimeout(resolve, timeout));