remove user struct, gateway rate limiting, and more

This commit is contained in:
Skillz
2020-05-23 15:29:52 -04:00
parent 8b4178ba7f
commit 1e78181051
11 changed files with 367 additions and 296 deletions
+4 -25
View File
@@ -62,18 +62,18 @@ This section will list out all the available methods and functionality in the li
## Events
```ts
.botUpdate(user, cachedUser)
.botUpdate(userData)
.channelCreate(channel)
.channelUpdate(channel, cachedChannel)
.channelDelete(channel)
.guildBanAdd(guild, user)
.guildBanRemove(guild, user)
.guildBanAdd(guild, memberOrUserData)
.guildBanRemove(guild, memberOrUserData)
.guildCreate(guild)
.guildUpdate(guild, cachedGuild)
.guildDelete(guild)
.guildEmojisUpdate(guild, emojis, cachedEmojis)
.guildMemberAdd(guild, member)
.guildMemberRemove(guild, member)
.guildMemberRemove(guild, memberOrUserData)
.guildMemberUpdate(guild, member, cachedMember)
.heartbeat()
.messageCreate(message)
@@ -286,27 +286,6 @@ This section will list out all the available methods and functionality in the li
- mentionable
- mention
## User
- id
- username
- discriminator
- avatar
- bot
- system
- mfaEnabled
- locale
- verified
- email
- flags
- premiumType
- mention
- tag
```ts
- .avatarURL(size, format)
- .sendMessage(content)
```
## Utils
```ts
+66 -2
View File
@@ -9,7 +9,7 @@ import {
DiscordHeartbeatPayload,
ReadyPayload,
} from "../types/discord.ts";
import { logRed } from "../utils/logger.ts";
import { logRed, logBlue } from "../utils/logger.ts";
import { FetchMembersOptions } from "../types/guild.ts";
import { delay } from "https://deno.land/std@0.50.0/async/delay.ts";
@@ -21,6 +21,45 @@ let sessionID = "";
// Discord requests null if no number has yet been sent by discord
let previousSequenceNumber: number | null = null;
let needToResume = false;
let shardID = 0;
const RequestMembersQueue: RequestMemberQueuedRequest[] = [];
let processQueue = false;
interface RequestMemberQueuedRequest {
guildID: string;
nonce: string;
options?: FetchMembersOptions;
}
async function processRequestMembersQueue() {
if (!RequestMembersQueue.length) {
processQueue = false;
return;
}
// 2 events per second is the rate limit.
const request = RequestMembersQueue.shift();
if (request) {
requestGuildMembers(request.guildID, request.nonce, request.options, true);
const secondRequest = RequestMembersQueue.shift();
if (secondRequest) {
requestGuildMembers(
secondRequest.guildID,
secondRequest.nonce,
secondRequest.options,
true,
);
}
}
await delay(1500);
logBlue(
`There are still ${RequestMembersQueue.length} requests in queue on shard ${shardID}`,
);
processRequestMembersQueue();
}
// 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 sendConstantHeartbeats(
@@ -107,14 +146,16 @@ const createShard = async (
type: "HANDLE_DISCORD_PAYLOAD",
payload: message,
resumeInterval,
shardID,
},
);
break;
}
} else if (isWebSocketCloseEvent(message)) {
logRed(`Close :( ${JSON.stringify(message)}`);
logRed(`Closeing: ${JSON.stringify(message)}`);
// These error codes should just crash the projects
if ([4004, 4005, 4012, 4013, 4014].includes(message.code)) {
logRed(`Close :( ${JSON.stringify(message)}`);
throw new Error(
"Shard.ts: Error occurred that is not resumeable or able to be reconnected.",
);
@@ -134,7 +175,29 @@ function requestGuildMembers(
guildID: string,
nonce: string,
options?: FetchMembersOptions,
queuedRequest = false,
) {
// This request was not from this queue so we add it to queue first
if (!queuedRequest) {
RequestMembersQueue.push({
guildID,
nonce,
options,
});
if (!processQueue) {
processQueue = true;
processRequestMembersQueue();
}
return;
}
// If its closed add back to queue to redo on resume
if (shardSocket.isClosed) {
requestGuildMembers(guildID, nonce, options);
return;
}
shardSocket.send(JSON.stringify({
op: GatewayOpcode.RequestGuildMembers,
d: {
@@ -158,6 +221,7 @@ onmessage = (message: MessageEvent) => {
message.data.botGatewayData,
message.data.identifyPayload,
);
shardID = message.data.shardID;
}
if (message.data.type === "FETCH_MEMBERS") {
+76 -63
View File
@@ -20,7 +20,7 @@ import {
handleInternalChannelDelete,
} from "../events/channels.ts";
import { ChannelCreatePayload } from "../types/channel.ts";
import { createGuild } from "../structures/guild.ts";
import { createGuild, Guild } from "../structures/guild.ts";
import {
CreateGuildPayload,
GuildDeletePayload,
@@ -40,20 +40,17 @@ import {
handleInternalGuildDelete,
} from "../events/guilds.ts";
import { cache } from "../utils/cache.ts";
import { createUser } from "../structures/user.ts";
import { createMember } from "../structures/member.ts";
import { createRole } from "../structures/role.ts";
import {
MessageCreateOptions,
MessageDeletePayload,
MessageDeleteBulkPayload,
MessageUpdatePayload,
MessageReactionPayload,
BaseMessageReactionPayload,
MessageReactionRemoveEmojiPayload,
} from "../types/message.ts";
import { createMessage } from "../structures/message.ts";
import { logGreen, logBlue } from "../utils/logger.ts";
import { GuildUpdateChange } from "../types/options.ts";
let shardCounter = 0;
@@ -66,7 +63,7 @@ export interface FetchAllMembersRequest {
const fetchAllMembersProcessingRequests = new Map<
string,
FetchAllMembersRequest
Function
>();
const shards: Worker[] = [];
let createNextShard = true;
@@ -77,7 +74,7 @@ export function createShardWorker(shardID?: number) {
shard.onmessage = (message) => {
if (message.data.type === "REQUEST_CLIENT_OPTIONS") {
identifyPayload.shard = [
shardID || shardCounter++,
shardID || shardCounter,
botGatewayData.shards,
];
@@ -86,10 +83,16 @@ export function createShardWorker(shardID?: number) {
type: "CREATE_SHARD",
botGatewayData,
identifyPayload,
shardID: shardCounter,
},
);
// Update the shard counter
shardCounter++;
} else if (message.data.type === "HANDLE_DISCORD_PAYLOAD") {
handleDiscordPayload(JSON.parse(message.data.payload));
handleDiscordPayload(
JSON.parse(message.data.payload),
message.data.shardID,
);
}
};
shards.push(shard);
@@ -112,7 +115,7 @@ export const spawnShards = async (
}
};
async function handleDiscordPayload(data: DiscordPayload) {
async function handleDiscordPayload(data: DiscordPayload, shardID: number) {
eventHandlers.raw?.(data);
switch (data.op) {
@@ -140,11 +143,18 @@ async function handleDiscordPayload(data: DiscordPayload) {
}
if (data.t === "GUILD_CREATE") {
const guild = createGuild(data.d as CreateGuildPayload);
handleInternalGuildCreate(guild);
if (cache.unavailableGuilds.get(guild.id)) {
return cache.unavailableGuilds.delete(guild.id);
const options = data.d as CreateGuildPayload;
// When shards resume they emit GUILD_CREATE again.
if (cache.guilds.has(options.id)) {
return;
}
const guild = createGuild(data.d as CreateGuildPayload, shardID);
handleInternalGuildCreate(guild);
if (cache.unavailableGuilds.get(options.id)) {
cache.unavailableGuilds.delete(options.id);
}
return eventHandlers.guildCreate?.(guild);
}
@@ -207,10 +217,11 @@ async function handleDiscordPayload(data: DiscordPayload) {
const guild = cache.guilds.get(options.guild_id);
if (!guild) return;
const user = createUser(options.user);
const member = guild.members.get(options.user.id);
return data.t === "GUILD_BAN_ADD"
? eventHandlers.guildBanAdd?.(guild, user)
: eventHandlers.guildBanRemove?.(guild, user);
? eventHandlers.guildBanAdd?.(guild, member || options.user)
: eventHandlers.guildBanRemove?.(guild, member || options.user);
}
if (data.t === "GUILD_EMOJIS_UPDATE") {
@@ -240,7 +251,6 @@ async function handleDiscordPayload(data: DiscordPayload) {
guild,
);
guild.members.set(options.user.id, member);
cache.users.set(options.user.id, createUser(member.user));
return eventHandlers.guildMemberAdd?.(guild, member);
}
@@ -256,7 +266,7 @@ async function handleDiscordPayload(data: DiscordPayload) {
const member = guild.members.get(options.user.id);
return eventHandlers.guildMemberRemove?.(
guild,
member || createUser(options.user),
member || options.user,
);
}
@@ -280,7 +290,6 @@ async function handleDiscordPayload(data: DiscordPayload) {
guild,
);
guild.members.set(options.user.id, member);
cache.users.set(options.user.id, createUser(member.user));
if (cachedMember?.nick !== options.nick) {
eventHandlers.nicknameUpdate?.(
@@ -320,19 +329,18 @@ async function handleDiscordPayload(data: DiscordPayload) {
guild,
),
);
cache.users.set(member.user.id, createUser(member.user));
});
// Check if its necessary to resolve the fetchmembers promise for this chunk or if more chunks will be coming
if (
options.nonce
) {
const request = fetchAllMembersProcessingRequests.get(options.nonce);
if (!request) return;
const resolve = fetchAllMembersProcessingRequests.get(options.nonce);
if (!resolve) return;
if (options.chunk_index + 1 === options.chunk_count) {
fetchAllMembersProcessingRequests.delete(options.nonce);
request.resolve();
resolve();
}
}
}
@@ -377,9 +385,6 @@ async function handleDiscordPayload(data: DiscordPayload) {
const channel = cache.channels.get(options.channel_id);
if (channel) channel.last_message_id = options.id;
// Cache the message author themself
cache.users.set(options.author.id, createUser(options.author));
const message = createMessage(options);
// Cache the message
cache.messages.set(options.id, message);
@@ -399,8 +404,6 @@ async function handleDiscordPayload(data: DiscordPayload) {
}
options.mentions.forEach((mention) => {
// For each mention cache the user
cache.users.set(mention.id, createUser(mention));
// Cache the member if its a valid member
if (mention.member) {
guild?.members.set(
@@ -433,12 +436,31 @@ async function handleDiscordPayload(data: DiscordPayload) {
}
if (data.t === "MESSAGE_UPDATE") {
const options = data.d as MessageUpdatePayload;
const options = data.d as MessageCreateOptions;
const channel = cache.channels.get(options.channel_id);
if (!channel) return;
// const cachedMessage = channel.messages().get(options.id)
// return eventHandlers.message_update?.(message, cachedMessage)
const cachedMessage = cache.messages.get(options.id);
if (!cachedMessage) return;
const oldMessage = {
attachments: cachedMessage.attachments,
content: cachedMessage.content,
embeds: cachedMessage.embeds,
editedTimestamp: cachedMessage.editedTimestamp,
tts: cachedMessage.tts,
pinned: cachedMessage.pinned,
};
// Messages with embeds can trigger update but they wont have edited_timestamp
if (
!options.edited_timestamp ||
(cachedMessage.content !== options.content)
) {
return;
}
return eventHandlers.messageUpdate?.(cachedMessage, oldMessage);
}
if (
@@ -476,17 +498,13 @@ async function handleDiscordPayload(data: DiscordPayload) {
if (options.member && options.guild_id) {
const guild = cache.guilds.get(options.guild_id);
if (guild) {
const member = createMember(
guild?.members.set(
options.member.user.id,
createMember(
options.member,
guild,
);
guild.members.set(
options.member.user.id,
member,
);
cache.users.set(options.member.user.id, createUser(member.user));
}
),
);
}
return isAdd
@@ -524,10 +542,19 @@ async function handleDiscordPayload(data: DiscordPayload) {
if (data.t === "USER_UPDATE") {
const userData = data.d as UserPayload;
const cachedUser = cache.users.get(botID);
const user = createUser(userData);
cache.users.set(userData.id, user);
return eventHandlers.botUpdate?.(user, cachedUser);
cache.guilds.forEach((guild) => {
const member = guild.members.get(userData.id);
if (!member) return;
// member.author = userData;
Object.entries(userData).forEach(([key, value]) => {
// @ts-ignore
if (member[key] === value) return;
// @ts-ignore
member[key] = value;
});
});
return eventHandlers.botUpdate?.(userData);
}
if (data.t === "VOICE_STATE_UPDATE") {
@@ -537,7 +564,8 @@ async function handleDiscordPayload(data: DiscordPayload) {
const guild = cache.guilds.get(payload.guild_id);
if (!guild) return;
const member = guild.members.get(payload.user_id);
const member = guild.members.get(payload.user_id) ||
(payload.member ? createMember(payload.member, guild) : undefined);
if (!member) return;
// No cached state before so lets make one for em
@@ -592,31 +620,16 @@ async function handleDiscordPayload(data: DiscordPayload) {
}
export async function requestAllMembers(
guildID: string,
guild: Guild,
resolve: Function,
memberCount: number,
options?: FetchMembersOptions,
) {
if (fetchAllMembersProcessingRequests.size >= 5) {
await delay(1000);
requestAllMembers(guildID, resolve, memberCount, options);
return;
}
const payload = {
resolve,
requestedMax: options?.query
? 100
: options?.userIDs?.length || options?.limit || memberCount,
receivedAmount: 0,
};
const nonce = Math.random().toString();
fetchAllMembersProcessingRequests.set(nonce, payload);
fetchAllMembersProcessingRequests.set(nonce, resolve);
return shards[0].postMessage({
shards[guild.shardID].postMessage({
type: "FETCH_MEMBERS",
guildID,
guildID: guild.id,
nonce,
options,
});
+4 -2
View File
@@ -31,9 +31,11 @@ import { RoleData } from "../types/role.ts";
import { Intents } from "../types/options.ts";
import { requestAllMembers } from "../module/shardingManager.ts";
export const createGuild = (data: CreateGuildPayload) => {
export const createGuild = (data: CreateGuildPayload, shardID: number) => {
const guild = {
...data,
/** The shard id that this guild is on */
shardID,
/** The owner id of the guild. */
ownerID: data.owner_id,
/** The afk channel id for this guild. */
@@ -293,7 +295,7 @@ export const createGuild = (data: CreateGuildPayload) => {
}
return new Promise((resolve) => {
requestAllMembers(data.id, resolve, guild.memberCount, options);
requestAllMembers(guild, resolve, options);
});
},
/** Returns the audit logs for the guild. Requires VIEW AUDIT LOGS permission */
+149 -131
View File
@@ -14,145 +14,163 @@ import { RequestManager } from "../module/requestManager.ts";
import { botID } from "../module/client.ts";
import { Guild } from "./guild.ts";
import { cache } from "../utils/cache.ts";
import { createUser } from "./user.ts";
import { MessageContent, DMChannelCreatePayload } from "../types/channel.ts";
import { createChannel } from "./channel.ts";
export const createMember = (data: MemberCreatePayload, guild: Guild) => {
// Add the user to cache as well
cache.users.set(data.user.id, createUser(data.user));
export const createMember = (data: MemberCreatePayload, guild: Guild) => ({
...data,
/** When the user joined the guild */
joinedAt: Date.parse(data.joined_at),
/** When the user used their nitro boost on the server. */
premiumSince: data.premium_since ? Date.parse(data.premium_since) : undefined,
/** The full username#discriminator */
tag: `${data.user.username}#${data.user.discriminator}`,
/** The user mention with nickname if possible */
mention: `<@!${data.user.id}>`,
/** The guild id where this member exists */
guildID: guild.id,
/** Whether or not this user has 2FA enabled. */
mfaEnabled: data.user.mfa_enabled,
/** The premium type for this user */
premiumType: data.user.premium_type,
return {
...data,
/** When the user joined the guild */
joinedAt: Date.parse(data.joined_at),
/** When the user used their nitro boost on the server. */
premiumSince: data.premium_since
? Date.parse(data.premium_since)
: undefined,
/** The full username#discriminator */
tag: `${data.user.username}#${data.user.discriminator}`,
/** The user mention with nickname if possible */
mention: `<@!${data.user.id}>`,
/** The guild id where this member exists */
guildID: guild.id,
/** Gets the guild object from cache for this member. This is a method instead of a prop to preserve memory. */
guild: () => cache.guilds.get(guild.id)!,
/** Send a message to a users DM. Note: this takes 2 API calls. 1 is to fetch the users dm channel. 2 is to send a message to that channel. */
sendMessage: async function (content: string | MessageContent) {
let dmChannel = cache.channels.get(data.user.id);
if (!dmChannel) {
// If not available in cache create a new one.
const dmChannelData = await RequestManager.post(
endpoints.USER_CREATE_DM,
{ recipient_id: data.user.id },
) as DMChannelCreatePayload;
// Channel create event will have added this channel to the cache
cache.channels.delete(dmChannelData.id);
const channel = createChannel(dmChannelData);
// Recreate the channel and add it undert he users id
cache.channels.set(data.user.id, channel);
dmChannel = channel;
}
/** Gets the guild object from cache for this member. This is a method instead of a prop to preserve memory. */
guild: () => cache.guilds.get(guild.id)!,
/** The users custom avatar or the default avatar */
avatarURL: (size: ImageSize = 128, format?: ImageFormats) =>
data.user.avatar
? formatImageURL(
endpoints.USER_AVATAR(data.user.id, data.user.avatar),
size,
format,
)
: endpoints.USER_DEFAULT_AVATAR(Number(data.user.discriminator) % 5),
/** Add a role to the member */
addRole: (roleID: string, reason?: string) => {
const botsHighestRole = highestRole(guild.id, botID);
// If it does exist try sending a message to this user
return dmChannel?.sendMessage(content);
},
/** The users custom avatar or the default avatar */
avatarURL: (size: ImageSize = 128, format?: ImageFormats) =>
data.user.avatar
? formatImageURL(
endpoints.USER_AVATAR(data.user.id, data.user.avatar),
size,
format,
)
: endpoints.USER_DEFAULT_AVATAR(Number(data.user.discriminator) % 5),
/** Add a role to the member */
addRole: (roleID: string, reason?: string) => {
const botsHighestRole = highestRole(guild.id, botID);
if (
botsHighestRole &&
!higherRolePosition(guild.id, botsHighestRole.id, roleID)
) {
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
}
if (!botHasPermission(guild.id, [Permissions.MANAGE_ROLES])) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.put(
endpoints.GUILD_MEMBER_ROLE(guild.id, data.user.id, roleID),
{ reason },
);
},
/** Remove a role from the member */
removeRole: (roleID: string, reason?: string) => {
const botsHighestRole = highestRole(guild.id, botID);
if (
botsHighestRole &&
!higherRolePosition(guild.id, botsHighestRole.id, roleID)
) {
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
}
if (!botHasPermission(guild.id, [Permissions.MANAGE_ROLES])) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.delete(
endpoints.GUILD_MEMBER_ROLE(guild.id, data.user.id, roleID),
{ reason },
);
},
/** Kick a member from the server */
kick: (reason?: string) => {
const botsHighestRole = highestRole(guild.id, botID);
const membersHighestRole = highestRole(guild.id, data.user.id);
if (
botsHighestRole && membersHighestRole &&
botsHighestRole.position <= membersHighestRole.position
) {
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
}
if (!botHasPermission(guild.id, [Permissions.KICK_MEMBERS])) {
throw new Error(Errors.MISSING_KICK_MEMBERS);
}
return RequestManager.delete(
endpoints.GUILD_MEMBER(guild.id, data.user.id),
{ reason },
);
},
/** Edit the member */
edit: (options: EditMemberOptions) => {
if (options.nick) {
if (options.nick.length > 32) {
throw new Error(Errors.NICKNAMES_MAX_LENGTH);
}
if (!botHasPermission(guild.id, [Permissions.MANAGE_NICKNAMES])) {
throw new Error(Errors.MISSING_MANAGE_NICKNAMES);
}
}
if (
options.roles &&
!botHasPermission(guild.id, [Permissions.MANAGE_ROLES])
) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
if (options.mute) {
// TODO: This should check if the member is in a voice channel
if (
botsHighestRole &&
!higherRolePosition(guild.id, botsHighestRole.id, roleID)
!botHasPermission(guild.id, [Permissions.MUTE_MEMBERS])
) {
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
throw new Error(Errors.MISSING_MUTE_MEMBERS);
}
}
if (!botHasPermission(guild.id, [Permissions.MANAGE_ROLES])) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
if (
options.deaf &&
!botHasPermission(guild.id, [Permissions.DEAFEN_MEMBERS])
) {
throw new Error(Errors.MISSING_DEAFEN_MEMBERS);
}
return RequestManager.put(
endpoints.GUILD_MEMBER_ROLE(guild.id, data.user.id, roleID),
{ reason },
);
},
/** Remove a role from the member */
removeRole: (roleID: string, reason?: string) => {
const botsHighestRole = highestRole(guild.id, botID);
if (
botsHighestRole &&
!higherRolePosition(guild.id, botsHighestRole.id, roleID)
) {
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
}
// TODO: if channel id is provided check if the bot has CONNECT and MOVE in channel and current channel
if (!botHasPermission(guild.id, [Permissions.MANAGE_ROLES])) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
return RequestManager.delete(
endpoints.GUILD_MEMBER_ROLE(guild.id, data.user.id, roleID),
{ reason },
);
},
/** Kick a member from the server */
kick: (reason?: string) => {
const botsHighestRole = highestRole(guild.id, botID);
const membersHighestRole = highestRole(guild.id, data.user.id);
if (
botsHighestRole && membersHighestRole &&
botsHighestRole.position <= membersHighestRole.position
) {
throw new Error(Errors.BOTS_HIGHEST_ROLE_TOO_LOW);
}
if (!botHasPermission(guild.id, [Permissions.KICK_MEMBERS])) {
throw new Error(Errors.MISSING_KICK_MEMBERS);
}
return RequestManager.delete(
endpoints.GUILD_MEMBER(guild.id, data.user.id),
{ reason },
);
},
/** Edit the member */
edit: (options: EditMemberOptions) => {
if (options.nick) {
if (options.nick.length > 32) {
throw new Error(Errors.NICKNAMES_MAX_LENGTH);
}
if (!botHasPermission(guild.id, [Permissions.MANAGE_NICKNAMES])) {
throw new Error(Errors.MISSING_MANAGE_NICKNAMES);
}
}
if (
options.roles &&
!botHasPermission(guild.id, [Permissions.MANAGE_ROLES])
) {
throw new Error(Errors.MISSING_MANAGE_ROLES);
}
if (options.mute) {
// TODO: This should check if the member is in a voice channel
if (
!botHasPermission(guild.id, [Permissions.MUTE_MEMBERS])
) {
throw new Error(Errors.MISSING_MUTE_MEMBERS);
}
}
if (
options.deaf &&
!botHasPermission(guild.id, [Permissions.DEAFEN_MEMBERS])
) {
throw new Error(Errors.MISSING_DEAFEN_MEMBERS);
}
// TODO: if channel id is provided check if the bot has CONNECT and MOVE in channel and current channel
return RequestManager.patch(
endpoints.GUILD_MEMBER(guild.id, data.user.id),
options,
);
},
/** Checks if the member has this permission. If the member is an owner or has admin perms it will always be true. */
hasPermissions: (permissions: Permission[]) => {
return memberHasPermission(
data.user.id,
guild,
data.roles,
permissions,
);
},
};
};
return RequestManager.patch(
endpoints.GUILD_MEMBER(guild.id, data.user.id),
options,
);
},
/** Checks if the member has this permission. If the member is an owner or has admin perms it will always be true. */
hasPermissions: (permissions: Permission[]) => {
return memberHasPermission(
data.user.id,
guild,
data.roles,
permissions,
);
},
});
export interface Member extends ReturnType<typeof createMember> {}
+8 -7
View File
@@ -1,7 +1,6 @@
import { MessageCreateOptions } from "../types/message.ts";
import { endpoints } from "../constants/discord.ts";
import { MessageContent } from "../types/channel.ts";
import { createUser } from "./user.ts";
import { UserPayload } from "../types/guild.ts";
import { botHasPermission } from "../utils/permissions.ts";
import { Errors } from "../types/errors.ts";
@@ -18,18 +17,16 @@ export function createMessage(data: MessageCreateOptions) {
mentionsEveryone: data.mentions_everyone,
mentionRoles: data.mention_roles,
mentionChannels: data.mention_channels,
mentions: data.mentions.map((user) => cache.users.get(user.id)!),
webhookID: data.webhook_id,
messageReference: data.message_reference,
author: cache.users.get(data.author.id)!,
timestamp: Date.parse(data.timestamp),
editedTimestamp: data.edited_timestamp
? Date.parse(data.edited_timestamp)
: undefined,
? Date.parse(data.edited_timestamp)
: undefined,
channel: cache.channels.get(data.channel_id)!,
guild: () => data.guild_id ? cache.guilds.get(data.guild_id) : undefined,
member: () => message.guild()?.members.get(data.author.id)!,
mentions: () => data.mentions.map((mention) => message.guild()?.members.get(mention.id) || mention),
/** Delete a message */
delete: (reason?: string) => {
@@ -119,7 +116,11 @@ export function createMessage(data: MessageCreateOptions) {
const result = (await RequestManager.get(
endpoints.CHANNEL_MESSAGE_REACTION(data.channel_id, data.id, reaction),
)) as UserPayload[];
return result.map((res) => createUser(res));
const guild = message.guild();
return result.map((res) => {
return guild?.members.get(res.id) || res;
});
},
/** Edit the message. */
edit: async (content: string | MessageContent) => {
+52 -53
View File
@@ -1,57 +1,56 @@
import { formatImageURL } from "../utils/cdn.ts";
import { endpoints } from "../constants/discord.ts";
import { ImageSize, ImageFormats } from "../types/cdn.ts";
import { UserPayload } from "../types/guild.ts";
import { RequestManager } from "../module/requestManager.ts";
import { MessageContent, DMChannelCreatePayload } from "../types/channel.ts";
import { cache } from "../utils/cache.ts";
import { logRed, logYellow } from "../utils/logger.ts";
import { createChannel } from "./channel.ts";
// import { formatImageURL } from "../utils/cdn.ts";
// import { endpoints } from "../constants/discord.ts";
// import { ImageSize, ImageFormats } from "../types/cdn.ts";
// import { UserPayload } from "../types/guild.ts";
// import { RequestManager } from "../module/requestManager.ts";
// import { MessageContent, DMChannelCreatePayload } from "../types/channel.ts";
// import { cache } from "../utils/cache.ts";
// import { createChannel } from "./channel.ts";
export const enum PremiumType {
NitroClassic = 1,
Nitro,
}
// export const enum PremiumType {
// NitroClassic = 1,
// Nitro,
// }
export const createUser = (data: UserPayload) => ({
...data,
/** Whether or not this user has 2FA enabled. */
mfaEnabled: data.mfa_enabled,
/** The premium type for this user */
premiumType: data.premium_type,
/** This will return the mention for the user. */
mention: `<@!${data.id}>`,
/** The tag for the user */
tag: `${data.username}#${data.discriminator}`,
/** The full URL of the users avatar from Discords CDN. */
avatarURL: (size: ImageSize = 128, format?: ImageFormats) =>
data.avatar
? formatImageURL(
endpoints.USER_AVATAR(data.id, data.avatar),
size,
format,
)
: endpoints.USER_DEFAULT_AVATAR(Number(data.discriminator) % 5),
/** Send a message to a users DM. Note: this takes 2 API calls. 1 is to fetch the users dm channel. 2 is to send a message to that channel. */
sendMessage: async function (content: string | MessageContent) {
let dmChannel = cache.channels.get(data.id);
if (!dmChannel) {
// If not available in cache create a new one.
const dmChannelData = await RequestManager.post(
endpoints.USER_CREATE_DM,
{ recipient_id: data.id },
) as DMChannelCreatePayload;
// Channel create event will have added this channel to the cache
cache.channels.delete(dmChannelData.id);
const channel = createChannel(dmChannelData);
// Recreate the channel and add it undert he users id
cache.channels.set(data.id, channel);
dmChannel = channel;
}
// export const createUser = (data: UserPayload) => ({
// ...data,
// /** Whether or not this user has 2FA enabled. */
// mfaEnabled: data.mfa_enabled,
// /** The premium type for this user */
// premiumType: data.premium_type,
// /** This will return the mention for the user. */
// mention: `<@!${data.id}>`,
// /** The tag for the user */
// tag: `${data.username}#${data.discriminator}`,
// /** The full URL of the users avatar from Discords CDN. */
// avatarURL: (size: ImageSize = 128, format?: ImageFormats) =>
// data.avatar
// ? formatImageURL(
// endpoints.USER_AVATAR(data.id, data.avatar),
// size,
// format,
// )
// : endpoints.USER_DEFAULT_AVATAR(Number(data.discriminator) % 5),
// /** Send a message to a users DM. Note: this takes 2 API calls. 1 is to fetch the users dm channel. 2 is to send a message to that channel. */
// sendMessage: async function (content: string | MessageContent) {
// let dmChannel = cache.channels.get(data.id);
// if (!dmChannel) {
// // If not available in cache create a new one.
// const dmChannelData = await RequestManager.post(
// endpoints.USER_CREATE_DM,
// { recipient_id: data.id },
// ) as DMChannelCreatePayload;
// // Channel create event will have added this channel to the cache
// cache.channels.delete(dmChannelData.id);
// const channel = createChannel(dmChannelData);
// // Recreate the channel and add it undert he users id
// cache.channels.set(data.id, channel);
// dmChannel = channel;
// }
// If it does exist try sending a message to this user
return dmChannel?.sendMessage(content);
},
});
// // If it does exist try sending a message to this user
// return dmChannel?.sendMessage(content);
// },
// });
export interface User extends ReturnType<typeof createUser> {}
// export interface User extends ReturnType<typeof createUser> {}
+1 -2
View File
@@ -1,5 +1,4 @@
import { Emoji, StatusType } from "./discord.ts";
import { User } from "../structures/user.ts";
import { Permission } from "./permission.ts";
import { RoleData } from "./role.ts";
import { MemberCreatePayload } from "./member.ts";
@@ -204,7 +203,7 @@ export interface BannedUser {
/** The reason for the ban */
reason?: string;
/** The banned user object */
user: User;
user: UserPayload;
}
export interface PositionSwap {
+2 -3
View File
@@ -1,10 +1,9 @@
import { ChannelType, UserPayload } from "./guild.ts";
import { User } from "../structures/user.ts";
import { MemberCreatePayload } from "./member.ts";
import { Channel } from "../structures/channel.ts";
import { Member } from "../structures/member.ts";
export interface MentionedUser extends User {
export interface MentionedUser extends UserPayload {
member: Member;
}
@@ -209,7 +208,7 @@ export interface EmojiReaction {
/** The roles this emoji is whitelisted to */
roles?: string[];
/** The user that created this emoji */
user?: User;
user?: UserPayload;
/** Whether this emoji must be wrapped in colons */
require_colons?: boolean;
/** Whether this emoji is managed */
+5 -5
View File
@@ -6,7 +6,6 @@ import {
TypingStartPayload,
VoiceStateUpdatePayload,
} from "./discord.ts";
import { User } from "../structures/user.ts";
import { Role } from "../structures/role.ts";
import { Message } from "../structures/message.ts";
import {
@@ -21,6 +20,7 @@ import {
import { Channel } from "../structures/channel.ts";
import { Guild } from "../structures/guild.ts";
import { Member } from "../structures/member.ts";
import { UserPayload } from "./guild.ts";
export interface Fulfilled_Client_Options {
token: string;
@@ -54,12 +54,12 @@ export interface OldMessage {
}
export interface EventHandlers {
botUpdate?: (user: User, cachedUser?: User) => unknown;
botUpdate?: (user: UserPayload) => unknown;
channelCreate?: (channel: Channel) => unknown;
channelUpdate?: (channel: Channel, cachedChannel: Channel) => unknown;
channelDelete?: (channel: Channel) => unknown;
guildBanAdd?: (guild: Guild, user: User) => unknown;
guildBanRemove?: (guild: Guild, user: User) => unknown;
guildBanAdd?: (guild: Guild, user: Member | UserPayload) => unknown;
guildBanRemove?: (guild: Guild, user: Member | UserPayload) => unknown;
guildCreate?: (guild: Guild) => unknown;
guildUpdate?: (guild: Guild, changes: GuildUpdateChange[]) => unknown;
guildDelete?: (guild: Guild) => unknown;
@@ -69,7 +69,7 @@ export interface EventHandlers {
cachedEmojis: Emoji[],
) => unknown;
guildMemberAdd?: (guild: Guild, member: Member) => unknown;
guildMemberRemove?: (guild: Guild, member: Member | User) => unknown;
guildMemberRemove?: (guild: Guild, member: Member | UserPayload) => unknown;
guildMemberUpdate?: (
guild: Guild,
member: Member,
-3
View File
@@ -1,11 +1,9 @@
import { User } from "../structures/user.ts";
import { Message } from "../structures/message.ts";
import { Guild } from "../structures/guild.ts";
import { Channel } from "../structures/channel.ts";
export interface CacheData {
guilds: Map<string, Guild>;
users: Map<string, User>;
channels: Map<string, Channel>;
messages: Map<string, Message>;
unavailableGuilds: Map<string, number>;
@@ -13,7 +11,6 @@ export interface CacheData {
export const cache: CacheData = {
guilds: new Map(),
users: new Map(),
channels: new Map(),
messages: new Map(),
unavailableGuilds: new Map(),