Skip to content

Commit

Permalink
feat: api v9 and threads (#5570)
Browse files Browse the repository at this point in the history
Co-authored-by: Noel <icrawltogo@gmail.com>
Co-authored-by: Amish Shah <dev@shah.gg>
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
Co-authored-by: SynthGhost <60333233+synthghost@users.noreply.github.com>
Co-authored-by: SpaceEEC <24881032+SpaceEEC@users.noreply.github.com>
Co-authored-by: Elliot <elliot@maisl.fr>
Co-authored-by: Antonio Román <kyradiscord@gmail.com>
Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com>
  • Loading branch information
9 people committed Jun 24, 2021
1 parent ea49f7c commit 7346621
Show file tree
Hide file tree
Showing 34 changed files with 1,461 additions and 24 deletions.
5 changes: 5 additions & 0 deletions src/client/actions/ActionsManager.js
Expand Up @@ -34,6 +34,11 @@ class ActionsManager {
this.register(require('./GuildEmojiDelete'));
this.register(require('./GuildEmojiUpdate'));
this.register(require('./GuildEmojisUpdate'));
this.register(require('./ThreadCreate'));
this.register(require('./ThreadDelete'));
this.register(require('./ThreadListSync'));
this.register(require('./ThreadMemberUpdate'));
this.register(require('./ThreadMembersUpdate'));
this.register(require('./GuildRolesPositionUpdate'));
this.register(require('./GuildChannelsPositionUpdate'));
this.register(require('./GuildIntegrationsUpdate'));
Expand Down
23 changes: 23 additions & 0 deletions src/client/actions/ThreadCreate.js
@@ -0,0 +1,23 @@
'use strict';

const Action = require('./Action');
const { Events } = require('../../util/Constants');

class ThreadCreateAction extends Action {
handle(data) {
const client = this.client;
const existing = client.channels.cache.has(data.id);
const thread = client.channels.add(data);
if (!existing && thread) {
/**
* Emitted whenever a thread is created or when the client user is added to a thread.
* @event Client#threadCreate
* @param {ThreadChannel} thread The thread that was created
*/
client.emit(Events.THREAD_CREATE, thread);
}
return { thread };
}
}

module.exports = ThreadCreateAction;
30 changes: 30 additions & 0 deletions src/client/actions/ThreadDelete.js
@@ -0,0 +1,30 @@
'use strict';

const Action = require('./Action');
const { Events } = require('../../util/Constants');

class ThreadDeleteAction extends Action {
handle(data) {
const client = this.client;
const thread = client.channels.cache.get(data.id);

if (thread) {
client.channels.remove(thread.id);
thread.deleted = true;
for (const message of thread.messages.cache.values()) {
message.deleted = true;
}

/**
* Emitted whenever a thread is deleted.
* @event Client#threadDelete
* @param {ThreadChannel} thread The thread that was deleted
*/
client.emit(Events.THREAD_DELETE, thread);
}

return { thread };
}
}

module.exports = ThreadDeleteAction;
59 changes: 59 additions & 0 deletions src/client/actions/ThreadListSync.js
@@ -0,0 +1,59 @@
'use strict';

const Action = require('./Action');
const Collection = require('../../util/Collection');
const { Events } = require('../../util/Constants');

class ThreadListSyncAction extends Action {
handle(data) {
const client = this.client;

const guild = client.guilds.cache.get(data.guild_id);
if (!guild) return {};

if (data.channels_ids) {
for (const id of data.channel_ids) {
const channel = client.channels.resolve(id);
if (channel) this.removeStale(channel);
}
} else {
for (const channel of guild.channels.cache.values()) {
this.removeStale(channel);
}
}

const syncedThreads = data.threads.reduce((coll, rawThread) => {
const thread = client.channels.add(rawThread);
return coll.set(thread.id, thread);
}, new Collection());

for (const rawMember of Object.values(data.members)) {
// Discord sends the thread id as id in this object
const thread = client.channels.cache.get(rawMember.id);
if (thread) {
thread.members._add(rawMember);
}
}

/**
* Emitted whenever the client user gains access to a text or news channel that contains threads
* @event Client#threadListSync
* @param {Collection<Snowflake, ThreadChannel>} threads The threads that were synced
*/
client.emit(Events.THREAD_LIST_SYNC, syncedThreads);

return {
syncedThreads,
};
}

removeStale(channel) {
channel.threads?.cache.forEach(thread => {
if (!thread.archived) {
this.client.channels.remove(thread.id);
}
});
}
}

module.exports = ThreadListSyncAction;
30 changes: 30 additions & 0 deletions src/client/actions/ThreadMemberUpdate.js
@@ -0,0 +1,30 @@
'use strict';

const Action = require('./Action');
const { Events } = require('../../util/Constants');

class ThreadMemberUpdateAction extends Action {
handle(data) {
const client = this.client;
// Discord sends the thread id as id in this object
const thread = client.channels.cache.get(data.id);
if (thread) {
const member = thread.members.cache.get(data.user_id);
if (!member) {
const newMember = thread.members._add(data);
return { newMember };
}
const old = member._update(data);
/**
* Emitted whenever the client user's thread member is updated.
* @event Client#threadMemberUpdate
* @param {ThreadMember} oldMember The member before the update
* @param {ThreadMember} newMember The member after the update
*/
client.emit(Events.THREAD_MEMBER_UPDATE, old, member);
}
return {};
}
}

module.exports = ThreadMemberUpdateAction;
34 changes: 34 additions & 0 deletions src/client/actions/ThreadMembersUpdate.js
@@ -0,0 +1,34 @@
'use strict';

const Action = require('./Action');
const { Events } = require('../../util/Constants');

class ThreadMembersUpdateAction extends Action {
handle(data) {
const client = this.client;
const thread = client.channels.cache.get(data.id);
if (thread) {
const old = thread.members.cache.clone();
thread.memberCount = data.member_count;

data.added_members?.forEach(rawMember => {
thread.members._add(rawMember);
});

data.removed_member_ids?.forEach(memberId => {
thread.members.cache.delete(memberId);
});

/**
* Emitted whenever members are added or removed from a thread. Requires `GUILD_MEMBERS` privileged intent
* @event Client#threadMembersUpdate
* @param {Collection<Snowflake, ThreadMember>} oldMembers The members before the update
* @param {Collection<Snowflake, ThreadMember>} newMembers The members after the update
*/
client.emit(Events.THREAD_MEMBERS_UPDATE, old, thread.members.cache);
}
return {};
}
}

module.exports = ThreadMembersUpdateAction;
2 changes: 1 addition & 1 deletion src/client/actions/TypingStart.js
Expand Up @@ -2,7 +2,7 @@

const Action = require('./Action');
const { Events } = require('../../util/Constants');
const textBasedChannelTypes = ['dm', 'text', 'news'];
const textBasedChannelTypes = ['dm', 'text', 'news', 'news_thread', 'public_thread', 'private_thread'];

class TypingStart extends Action {
handle(data) {
Expand Down
5 changes: 5 additions & 0 deletions src/client/websocket/handlers/THREAD_CREATE.js
@@ -0,0 +1,5 @@
'use strict';

module.exports = (client, packet) => {
client.actions.ThreadCreate.handle(packet.d);
};
5 changes: 5 additions & 0 deletions src/client/websocket/handlers/THREAD_DELETE.js
@@ -0,0 +1,5 @@
'use strict';

module.exports = (client, packet) => {
client.actions.ThreadDelete.handle(packet.d);
};
5 changes: 5 additions & 0 deletions src/client/websocket/handlers/THREAD_LIST_SYNC.js
@@ -0,0 +1,5 @@
'use strict';

module.exports = (client, packet) => {
client.actions.ThreadListSync.handle(packet.d);
};
5 changes: 5 additions & 0 deletions src/client/websocket/handlers/THREAD_MEMBERS_UPDATE.js
@@ -0,0 +1,5 @@
'use strict';

module.exports = (client, packet) => {
client.actions.ThreadMembersUpdate.handle(packet.d);
};
5 changes: 5 additions & 0 deletions src/client/websocket/handlers/THREAD_MEMBER_UPDATE.js
@@ -0,0 +1,5 @@
'use strict';

module.exports = (client, packet) => {
client.actions.ThreadMemberUpdate.handle(packet.d);
};
16 changes: 16 additions & 0 deletions src/client/websocket/handlers/THREAD_UPDATE.js
@@ -0,0 +1,16 @@
'use strict';

const { Events } = require('../../../util/Constants');

module.exports = (client, packet) => {
const { old, updated } = client.actions.ChannelUpdate.handle(packet.d);
if (old && updated) {
/**
* Emitted whenever a thread is updated - e.g. name change, archive state change, locked state change.
* @event Client#threadUpdate
* @param {ThreadChannel} oldThread The thread before the update
* @param {ThreadChannel} newThread The thread after the update
*/
client.emit(Events.THREAD_UPDATE, old, updated);
}
};
2 changes: 2 additions & 0 deletions src/errors/Messages.js
Expand Up @@ -92,6 +92,8 @@ const Messages = {
INVALID_TYPE: (name, expected, an = false) => `Supplied ${name} is not a${an ? 'n' : ''} ${expected}.`,
INVALID_ELEMENT: (type, name, elem) => `Supplied ${type} ${name} includes an invalid element: ${elem}`,

MESSAGE_THREAD_PARENT: 'The message was not sent in a guild text or news channel',

WEBHOOK_MESSAGE: 'The message was not sent by a webhook.',
WEBHOOK_TOKEN_UNAVAILABLE: 'This action requires a webhook token, but none is available.',
MESSAGE_REFERENCE_MISSING: 'The message does not reference another message',
Expand Down
5 changes: 5 additions & 0 deletions src/index.js
Expand Up @@ -26,6 +26,7 @@ module.exports = {
SnowflakeUtil: require('./util/SnowflakeUtil'),
Structures: require('./util/Structures'),
SystemChannelFlags: require('./util/SystemChannelFlags'),
ThreadMemberFlags: require('./util/ThreadMemberFlags'),
UserFlags: require('./util/UserFlags'),
Util: require('./util/Util'),
version: require('../package.json').version,
Expand All @@ -47,6 +48,8 @@ module.exports = {
MessageManager: require('./managers/MessageManager'),
PresenceManager: require('./managers/PresenceManager'),
RoleManager: require('./managers/RoleManager'),
ThreadManager: require('./managers/ThreadManager'),
ThreadMemberManager: require('./managers/ThreadMemberManager'),
UserManager: require('./managers/UserManager'),

// Structures
Expand Down Expand Up @@ -109,6 +112,8 @@ module.exports = {
Team: require('./structures/Team'),
TeamMember: require('./structures/TeamMember'),
TextChannel: require('./structures/TextChannel'),
ThreadChannel: require('./structures/ThreadChannel'),
ThreadMember: require('./structures/ThreadMember'),
User: require('./structures/User'),
VoiceChannel: require('./structures/VoiceChannel'),
VoiceRegion: require('./structures/VoiceRegion'),
Expand Down
6 changes: 5 additions & 1 deletion src/managers/ChannelManager.js
Expand Up @@ -2,7 +2,7 @@

const BaseManager = require('./BaseManager');
const Channel = require('../structures/Channel');
const { Events } = require('../util/Constants');
const { Events, ThreadChannelTypes } = require('../util/Constants');

/**
* A manager of channels belonging to a client
Expand All @@ -24,6 +24,9 @@ class ChannelManager extends BaseManager {
if (existing) {
if (existing._patch && cache) existing._patch(data);
if (guild) guild.channels?.add(existing);
if (ThreadChannelTypes.includes(existing.type) && typeof existing.parent?.threads !== 'undefined') {
existing.parent.threads.add(existing);
}
return existing;
}

Expand All @@ -42,6 +45,7 @@ class ChannelManager extends BaseManager {
remove(id) {
const channel = this.cache.get(id);
channel?.guild?.channels.cache.delete(id);
channel?.parent?.threads?.cache.delete(id);
this.cache.delete(id);
}

Expand Down
15 changes: 14 additions & 1 deletion src/managers/GuildChannelManager.js
Expand Up @@ -4,7 +4,7 @@ const BaseManager = require('./BaseManager');
const GuildChannel = require('../structures/GuildChannel');
const PermissionOverwrites = require('../structures/PermissionOverwrites');
const Collection = require('../util/Collection');
const { ChannelTypes } = require('../util/Constants');
const { ChannelTypes, ThreadChannelTypes } = require('../util/Constants');

/**
* Manages API methods for GuildChannels and stores their cache.
Expand All @@ -21,6 +21,19 @@ class GuildChannelManager extends BaseManager {
this.guild = guild;
}

/**
* The number of channels in this managers cache excluding thread channels
* that do not count towards a guild's maximum channels restriction.
* @type {number}
* @readonly
*/
get channelCountWithoutThreads() {
return this.cache.reduce((acc, channel) => {
if (ThreadChannelTypes.includes(channel.type)) return acc;
return ++acc;
}, 0);
}

/**
* The cache of this Manager
* @type {Collection<Snowflake, GuildChannel>}
Expand Down

0 comments on commit 7346621

Please sign in to comment.