mirror of
https://github.com/discordeno/discordeno.git
synced 2026-09-17 16:57:22 +00:00
remove user struct, gateway rate limiting, and more
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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> {}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user