Skip to content
5 changes: 4 additions & 1 deletion index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -528,6 +528,7 @@ declare namespace Dysnomia {
// Client
interface ClientOptions {
allowedMentions?: AllowedMentions;
caching?: CachingOptions;
defaultImageFormat?: string;
defaultImageSize?: number;
gateway?: GatewayOptions;
Expand Down Expand Up @@ -559,7 +560,9 @@ declare namespace Dysnomia {
decryptionFailureTolerance?: number;
ws?: unknown;
}

interface CachingOptions {
disableMaps?: boolean;
}
interface EditSelfOptions {
avatar?: string | null;
banner?: string | null;
Expand Down
56 changes: 44 additions & 12 deletions lib/Client.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ class Client extends EventEmitter {
users: true,
roles: true
},
caching: {},
defaultImageFormat: "jpg",
defaultImageSize: 128,
messageLimit: 100,
Expand Down Expand Up @@ -399,7 +400,7 @@ class Client extends EventEmitter {
* @param {String} guildID The ID of the guild
*/
closeVoiceConnection(guildID) {
this.shards.get(this.guildShardMap[guildID] || 0).sendWS(Constants.GatewayOPCodes.VOICE_STATE_UPDATE, {
this.shards.get(this.guildShardMap[guildID] || this.shards.resolveFromGuild(guildID)).sendWS(Constants.GatewayOPCodes.VOICE_STATE_UPDATE, {
guild_id: guildID || null,
channel_id: null,
self_mute: false,
Expand Down Expand Up @@ -1515,10 +1516,15 @@ class Client extends EventEmitter {
* @param {Object} [options] Additional options when editing position
* @param {Boolean} [options.lockPermissions] Whether to sync the channel's permissions with the new parent, if changing parents
* @param {String} [options.parentID] The new parent ID (category channel) for the channel that is moved
* @param {String} [options.guildIDHint] The ID of the guild that is hinted to have the channel, if channel maps are disabled, this is used to avoid searching for the channel
* @returns {Promise}
*/
editChannelPosition(channelID, position, options = {}) {
let channels = this.guilds.get(this.channelGuildMap[channelID]).channels;
let channels = this.options.caching.disableMaps ? this.getChannel(channelID, options?.guildIDHint)?.guild?.channels : this.guilds.get(this.channelGuildMap[channelID])?.channels;
if(!channels) {
return Promise.reject(new Error(`Channel ${channelID} not found`));
}

const channel = channels.get(channelID);
if(!channel) {
return Promise.reject(new Error(`Channel ${channelID} not found`));
Expand Down Expand Up @@ -2379,17 +2385,30 @@ class Client extends EventEmitter {
/**
* Get a Channel object from a channel ID
* @param {String} channelID The ID of the channel
* @param {String} [guildIDHint] The ID of the guild that is hinted to have the channel, if channel maps are disabled, this is used to avoid searching for the channel. Will fall back to linear search if not found in the provided guild.
* @returns {CategoryChannel | PrivateChannel | TextChannel | TextVoiceChannel | NewsChannel | NewsThreadChannel | PrivateThreadChannel | PublicThreadChannel}
*/
getChannel(channelID) {
getChannel(channelID, guildIDHint) {
if(!channelID) {
throw new Error(`Invalid channel ID: ${channelID}`);
}

if(this.channelGuildMap[channelID] && this.guilds.get(this.channelGuildMap[channelID])) {
if(this.options.caching.disableMaps) {
let channel;
if(guildIDHint) {
const guild = this.guilds.get(guildIDHint);
if(guild && (channel = guild.channels.get(channelID) ?? guild.threads.get(channelID))) {
return channel;
}
}
for(const guild of this.guilds.values()) {
if((channel = guild.channels.get(channelID) ?? guild.threads.get(channelID))) {
return channel;
}
}
} else if(this.channelGuildMap[channelID] && this.guilds.get(this.channelGuildMap[channelID])) {
return this.guilds.get(this.channelGuildMap[channelID]).channels.get(channelID);
}
if(this.threadGuildMap[channelID] && this.guilds.get(this.threadGuildMap[channelID])) {
} else if(this.threadGuildMap[channelID] && this.guilds.get(this.threadGuildMap[channelID])) {
return this.guilds.get(this.threadGuildMap[channelID]).threads.get(channelID);
}
return this.privateChannels.get(channelID);
Expand Down Expand Up @@ -3351,23 +3370,24 @@ class Client extends EventEmitter {
* @param {Boolean} [options.selfMute] Whether the bot joins the channel muted or not
* @param {Boolean} [options.selfDeaf] Whether the bot joins the channel deafened or not
* @param {Object} [options.ws] Additional options for the WebSocket connection, defaults to the options set in the client
* @param {String} [options.guildIDHint] The ID of the guild hinted to have this channel
* @returns {Promise<VoiceConnection>} Resolves with a VoiceConnection
*/
joinVoiceChannel(channelID, options = {}) {
const channel = this.getChannel(channelID);
const channel = this.getChannel(channelID, options?.guildIDHint);
if(!channel) {
return Promise.reject(new Error("Channel not found"));
}
if(channel.guild?.members.has(this.user.id) && !(channel.permissionsOf(this.user.id).allow & Constants.Permissions.voiceConnect)) {
return Promise.reject(new Error("Insufficient permission to connect to voice channel"));
}
this.shards.get(this.guildShardMap[this.channelGuildMap[channelID]] || 0).sendWS(Constants.GatewayOPCodes.VOICE_STATE_UPDATE, {
guild_id: this.channelGuildMap[channelID] || null,
this.shards.get(this.guildShardMap[channel.guild.id] || this.shards.resolveFromGuild(channel.guild.id)).sendWS(Constants.GatewayOPCodes.VOICE_STATE_UPDATE, {
guild_id: channel.guild.id || null,
channel_id: channelID || null,
self_mute: options.selfMute || false,
self_deaf: options.selfDeaf || false
});
return this.voiceConnections.join(this.channelGuildMap[channelID], channelID, options);
return this.voiceConnections.join(channel.guild.id, channelID, options);
}

/**
Expand Down Expand Up @@ -3405,9 +3425,21 @@ class Client extends EventEmitter {
/**
* Leaves a voice channel
* @param {String} channelID The ID of the voice channel
* @param {String} [guildIDHint] The ID of the guild that is hinted to have the channel
*/
leaveVoiceChannel(channelID) {
if(!channelID || !this.channelGuildMap[channelID]) {
leaveVoiceChannel(channelID, guildIDHint) {
if(!channelID) {
return;
}

if(this.options.caching.disableMaps) {
const guildID = this.getChannel(channelID, guildIDHint)?.guild?.id;
if(guildID) {
return this.closeVoiceConnection(guildID);
}
}

if(!this.channelGuildMap[channelID]) {
return;
}
this.closeVoiceConnection(this.channelGuildMap[channelID]);
Expand Down
49 changes: 27 additions & 22 deletions lib/gateway/Shard.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ class Shard extends EventEmitter {
}

createGuild(_guild) {
this.client.guildShardMap[_guild.id] = this.id;
if(!this.client.options.caching.disableMaps) {
this.client.guildShardMap[_guild.id] = this.id;
}
const guild = this.client.guilds.add(_guild, this.client, true);
if(this.client.shards.options.getAllUsers && guild.members.size < guild.memberCount) {
this.getAllUsersCount[guild.id] = true;
Expand Down Expand Up @@ -875,7 +877,7 @@ class Shard extends EventEmitter {
break;
}
case "VOICE_CHANNEL_EFFECT_SEND": {
const channel = this.client.getChannel(packet.d.channel_id) ?? {id: packet.d.channel_id};
const channel = this.client.getChannel(packet.d.channel_id, packet.d.guild_id) ?? {id: packet.d.channel_id};
const guild = this.client.guilds.get(packet.d.guild_id) ?? {id: packet.d.guild_id};
const user = this.client.users.get(packet.d.user_id) ?? {id: packet.d.user_id};
/**
Expand Down Expand Up @@ -918,12 +920,12 @@ class Shard extends EventEmitter {
* @prop {User | Object} user The user. If the user is not cached, this will be an object with an `id` key. No other property is guaranteed
* @prop {Member?} member The guild member, if typing in a guild channel, or `null`, if typing in a PrivateChannel
*/
this.emit("typingStart", this.client.getChannel(packet.d.channel_id) || {id: packet.d.channel_id}, this.client.users.get(packet.d.user_id) || {id: packet.d.user_id}, member);
this.emit("typingStart", this.client.getChannel(packet.d.channel_id, packet.d.guild_id) || {id: packet.d.channel_id}, this.client.users.get(packet.d.user_id) || {id: packet.d.user_id}, member);
}
break;
}
case "MESSAGE_CREATE": {
const channel = this.client.getChannel(packet.d.channel_id);
const channel = this.client.getChannel(packet.d.channel_id, packet.d.guild_id);
if(channel) { // MESSAGE_CREATE just when deleting o.o
channel.lastMessageID = packet.d.id;
if(channel instanceof ThreadChannel) {
Expand All @@ -942,7 +944,7 @@ class Shard extends EventEmitter {
break;
}
case "MESSAGE_UPDATE": {
const channel = this.client.getChannel(packet.d.channel_id);
const channel = this.client.getChannel(packet.d.channel_id, packet.d.guild_id);
if(!channel) {
this.emit("messageUpdate", new Message(packet.d, this.client), null);
break;
Expand Down Expand Up @@ -985,7 +987,7 @@ class Shard extends EventEmitter {
break;
}
case "MESSAGE_DELETE": {
const channel = this.client.getChannel(packet.d.channel_id);
const channel = this.client.getChannel(packet.d.channel_id, packet.d.guild_id);
if(channel instanceof ThreadChannel) {
channel.messageCount--;
}
Expand All @@ -1006,7 +1008,7 @@ class Shard extends EventEmitter {
break;
}
case "MESSAGE_DELETE_BULK": {
const channel = this.client.getChannel(packet.d.channel_id);
const channel = this.client.getChannel(packet.d.channel_id, packet.d.guild_id);
if(channel instanceof ThreadChannel) {
channel.messageCount -= packet.d.ids.length;
}
Expand All @@ -1026,7 +1028,7 @@ class Shard extends EventEmitter {
break;
}
case "MESSAGE_REACTION_ADD": {
const channel = this.client.getChannel(packet.d.channel_id);
const channel = this.client.getChannel(packet.d.channel_id, packet.d.guild_id);
let message = channel?.messages.get(packet.d.message_id);
let member;
if(channel?.guild) {
Expand Down Expand Up @@ -1092,7 +1094,7 @@ class Shard extends EventEmitter {
break;
}
case "MESSAGE_REACTION_REMOVE": {
const channel = this.client.getChannel(packet.d.channel_id);
const channel = this.client.getChannel(packet.d.channel_id, packet.d.guild_id);
let message = channel?.messages.get(packet.d.message_id);
if(message) {
const reaction = packet.d.emoji.id ? `${packet.d.emoji.name}:${packet.d.emoji.id}` : packet.d.emoji.name;
Expand Down Expand Up @@ -1136,7 +1138,7 @@ class Shard extends EventEmitter {
break;
}
case "MESSAGE_REACTION_REMOVE_ALL": {
const channel = this.client.getChannel(packet.d.channel_id);
const channel = this.client.getChannel(packet.d.channel_id, packet.d.guild_id);
let message = channel?.messages.get(packet.d.message_id);
if(message) {
message.reactions = {};
Expand All @@ -1160,7 +1162,7 @@ class Shard extends EventEmitter {
break;
}
case "MESSAGE_REACTION_REMOVE_EMOJI": {
const channel = this.client.getChannel(packet.d.channel_id);
const channel = this.client.getChannel(packet.d.channel_id, packet.d.guild_id);
let message = channel?.messages.get(packet.d.message_id);
if(message) {
const reaction = packet.d.emoji.id ? `${packet.d.emoji.name}:${packet.d.emoji.id}` : packet.d.emoji.name;
Expand Down Expand Up @@ -1560,7 +1562,7 @@ class Shard extends EventEmitter {
this.emit("debug", `Missing guild ${packet.d.guild_id} in INVITE_CREATE`);
break;
}
const channel = this.client.getChannel(packet.d.channel_id);
const channel = guild.channels.get(packet.d.channel_id);
if(!channel) {
this.emit("debug", `Missing channel ${packet.d.channel_id} in INVITE_CREATE`);
break;
Expand All @@ -1584,7 +1586,7 @@ class Shard extends EventEmitter {
this.emit("debug", `Missing guild ${packet.d.guild_id} in INVITE_DELETE`);
break;
}
const channel = this.client.getChannel(packet.d.channel_id);
const channel = guild.channels.get(packet.d.channel_id);
if(!channel) {
this.emit("debug", `Missing channel ${packet.d.channel_id} in INVITE_DELETE`);
break;
Expand All @@ -1611,7 +1613,10 @@ class Shard extends EventEmitter {
break;
}
channel.guild.channels.add(channel, this.client);
this.client.channelGuildMap[packet.d.id] = packet.d.guild_id;
if(!this.client.options.caching.disableMaps) {
this.client.channelGuildMap[packet.d.id] = packet.d.guild_id;
}

/**
* Fired when a channel is created
* @event Client#channelCreate
Expand All @@ -1625,7 +1630,7 @@ class Shard extends EventEmitter {
break;
}
case "CHANNEL_UPDATE": {
let channel = this.client.getChannel(packet.d.id);
let channel = this.client.getChannel(packet.d.id, packet.d.guild_id);
if(!channel) {
break;
}
Expand Down Expand Up @@ -1925,7 +1930,7 @@ class Shard extends EventEmitter {
break;
}
case "CHANNEL_PINS_UPDATE": {
const channel = this.client.getChannel(packet.d.channel_id);
const channel = this.client.getChannel(packet.d.channel_id, packet.d.guild_id);
if(!channel) {
this.emit("debug", `CHANNEL_PINS_UPDATE target channel ${packet.d.channel_id} not found`);
break;
Expand Down Expand Up @@ -1963,7 +1968,7 @@ class Shard extends EventEmitter {
this.emit("debug", `Received THREAD_CREATE for channel in missing guild ${packet.d.guild_id}`);
break;
}
const parent = this.client.getChannel(packet.d.parent_id);
const parent = channel.guild.channels.get(packet.d.parent_id);
if(parent instanceof ForumChannel) {
parent.lastThreadID = packet.d.id;
}
Expand All @@ -1979,7 +1984,7 @@ class Shard extends EventEmitter {
break;
}
case "THREAD_UPDATE": {
const channel = this.client.getChannel(packet.d.id);
const channel = this.client.guilds.get(packet.d.guild_id)?.threads.get(packet.d.id);
if(!channel) {
const thread = Channel.from(packet.d, this.client);
this.emit("threadUpdate", this.client.guilds.get(packet.d.guild_id).threads.add(thread, this.client), null);
Expand Down Expand Up @@ -2058,7 +2063,7 @@ class Shard extends EventEmitter {
break;
}
case "THREAD_MEMBER_UPDATE": {
const channel = this.client.getChannel(packet.d.id);
const channel = this.client.guilds.get(packet.d.guild_id)?.threads.get(packet.d.id);
if(!channel) {
this.emit("debug", `Missing channel ${packet.d.id} in THREAD_MEMBER_UPDATE`);
break;
Expand All @@ -2085,7 +2090,7 @@ class Shard extends EventEmitter {
break;
}
case "THREAD_MEMBERS_UPDATE": {
const channel = this.client.getChannel(packet.d.id);
const channel = this.client.guilds.get(packet.d.guild_id)?.threads.get(packet.d.id);
if(!channel) {
this.emit("debug", `Missing channel ${packet.d.id} in THREAD_MEMBERS_UPDATE`);
break;
Expand Down Expand Up @@ -2393,7 +2398,7 @@ class Shard extends EventEmitter {
break;
}
case "MESSAGE_POLL_VOTE_ADD": {
const channel = this.client.getChannel(packet.d.channel_id);
const channel = this.client.getChannel(packet.d.channel_id, packet.d.guild_id);
let message = channel?.messages.get(packet.d.message_id);
const user = this.client.users.get(packet.d.user_id);
if(!message) {
Expand All @@ -2417,7 +2422,7 @@ class Shard extends EventEmitter {
break;
}
case "MESSAGE_POLL_VOTE_REMOVE": {
const channel = this.client.getChannel(packet.d.channel_id);
const channel = this.client.getChannel(packet.d.channel_id, packet.d.guild_id);
let message = channel?.messages.get(packet.d.message_id);
const user = this.client.users.get(packet.d.user_id);
if(!message) {
Expand Down
4 changes: 4 additions & 0 deletions lib/gateway/ShardManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ class ShardManager extends Collection {
this.tryConnect();
}

resolveFromGuild(guildID) {
return Number((BigInt(guildID) >> 22n) % BigInt(this.options.maxShards));
}

spawn(id) {
let shard = this.get(id);
if(!shard) {
Expand Down
Loading