Skip to content

Commit

Permalink
feat: stage instances (#5749)
Browse files Browse the repository at this point in the history
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
Co-authored-by: SpaceEEC <spaceeec@yahoo.com>
Co-authored-by: Antonio Román <kyradiscord@gmail.com>
  • Loading branch information
4 people authored Jun 14, 2021
1 parent a1f94f6 commit 918921e
Show file tree
Hide file tree
Showing 16 changed files with 556 additions and 8 deletions.
3 changes: 3 additions & 0 deletions src/client/actions/ActionsManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ class ActionsManager {
this.register(require('./GuildIntegrationsUpdate'));
this.register(require('./WebhooksUpdate'));
this.register(require('./TypingStart'));
this.register(require('./StageInstanceCreate'));
this.register(require('./StageInstanceUpdate'));
this.register(require('./StageInstanceDelete'));
}

register(Action) {
Expand Down
28 changes: 28 additions & 0 deletions src/client/actions/StageInstanceCreate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
'use strict';

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

class StageInstanceCreateAction extends Action {
handle(data) {
const client = this.client;
const channel = this.getChannel(data);

if (channel) {
const stageInstance = channel.guild.stageInstances.add(data);

/**
* Emitted whenever a stage instance is created.
* @event Client#stageInstanceCreate
* @param {StageInstance} stageInstance The created stage instance
*/
client.emit(Events.STAGE_INSTANCE_CREATE, stageInstance);

return { stageInstance };
}

return {};
}
}

module.exports = StageInstanceCreateAction;
32 changes: 32 additions & 0 deletions src/client/actions/StageInstanceDelete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict';

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

class StageInstanceDeleteAction extends Action {
handle(data) {
const client = this.client;
const channel = this.getChannel(data);

if (channel) {
const stageInstance = channel.guild.stageInstances.add(data);
if (stageInstance) {
channel.guild.stageInstances.cache.delete(stageInstance.id);
stageInstance.deleted = true;

/**
* Emitted whenever a stage instance is deleted.
* @event Client#stageInstanceDelete
* @param {StageInstance} stageInstance The deleted stage instance
*/
client.emit(Events.STAGE_INSTANCE_DELETE, stageInstance);

return { stageInstance };
}
}

return {};
}
}

module.exports = StageInstanceDeleteAction;
30 changes: 30 additions & 0 deletions src/client/actions/StageInstanceUpdate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
'use strict';

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

class StageInstanceUpdateAction extends Action {
handle(data) {
const client = this.client;
const channel = this.getChannel(data);

if (channel) {
const oldStageInstance = channel.guild.stageInstances.cache.get(data.id)?._clone() ?? null;
const newStageInstance = channel.guild.stageInstances.add(data);

/**
* Emitted whenever a stage instance gets updated - e.g. change in topic or privacy level
* @event Client#stageInstanceUpdate
* @param {?StageInstance} oldStageInstance The stage instance before the update
* @param {StageInstance} newStageInstance The stage instance after the update
*/
client.emit(Events.STAGE_INSTANCE_UPDATE, oldStageInstance, newStageInstance);

return { oldStageInstance, newStageInstance };
}

return {};
}
}

module.exports = StageInstanceUpdateAction;
5 changes: 5 additions & 0 deletions src/client/websocket/handlers/STAGE_INSTANCE_CREATE.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

module.exports = (client, packet) => {
client.actions.StageInstanceCreate.handle(packet.d);
};
5 changes: 5 additions & 0 deletions src/client/websocket/handlers/STAGE_INSTANCE_DELETE.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

module.exports = (client, packet) => {
client.actions.StageInstanceDelete.handle(packet.d);
};
5 changes: 5 additions & 0 deletions src/client/websocket/handlers/STAGE_INSTANCE_UPDATE.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict';

module.exports = (client, packet) => {
client.actions.StageInstanceUpdate.handle(packet.d);
};
1 change: 1 addition & 0 deletions src/errors/Messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const Messages = {
GUILD_OWNED: 'Guild is owned by the client.',
GUILD_MEMBERS_TIMEOUT: "Members didn't arrive in time.",
GUILD_UNCACHED_ME: 'The client user as a member of this guild is uncached.',
STAGE_CHANNEL_RESOLVE: 'Could not resolve channel to a stage channel.',

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}`,
Expand Down
150 changes: 150 additions & 0 deletions src/managers/StageInstanceManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
'use strict';

const BaseManager = require('./BaseManager');
const { TypeError, Error } = require('../errors');
const StageInstance = require('../structures/StageInstance');
const { PrivacyLevels } = require('../util/Constants');

/**
* Manages API methods for {@link StageInstance} objects and holds their cache.
* @extends {BaseManager}
*/
class StageInstanceManager extends BaseManager {
constructor(guild, iterable) {
super(guild.client, iterable, StageInstance);

/**
* The guild this manager belongs to
* @type {Guild}
*/
this.guild = guild;
}

/**
* The cache of this Manager
* @type {Collection<Snowflake, StageInstance>}
* @name StageInstanceManager#cache
*/

/**
* Options used to create a stage instance.
* @typedef {Object} CreateStageInstanceOptions
* @property {StageChannel|Snowflake} channel The stage channel whose instance is to be created
* @property {string} topic The topic of the stage instance
* @property {PrivacyLevel|number} [privacyLevel] The privacy level of the stage instance
*/

/**
* Creates a new stage instance.
* @param {CreateStageInstanceOptions} options The options to create the stage instance
* @returns {Promise<StageInstance>}
* @example
* // Create a stage instance
* guild.stageInstances.create({
* channel: '1234567890123456789',
* topic: 'A very creative topic',
* privacyLevel: 'GUILD_ONLY'
* })
* .then(stageInstance => console.log(stageInstance))
* .catch(console.error);
*/
async create(options) {
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true);
let { channel, topic, privacyLevel } = options;
const channelID = this.guild.channels.resolveID(channel);
if (!channelID) throw new Error('STAGE_CHANNEL_RESOLVE');

if (privacyLevel) privacyLevel = typeof privacyLevel === 'number' ? privacyLevel : PrivacyLevels[privacyLevel];

const data = await this.client.api['stage-instances'].post({
data: {
channel_id: channelID,
topic,
privacy_level: privacyLevel,
},
});

return this.add(data);
}

/**
* Fetches the stage instance associated with a stage channel, if it exists.
* @param {StageChannel|Snowflake} channel The stage channel whose instance is to be fetched
* @param {BaseFetchOptions} [options] Additional options for this fetch
* @returns {Promise<StageInstance>}
* @example
* // Fetch a stage instance
* guild.stageInstances.fetch('1234567890123456789')
* .then(stageInstance => console.log(stageInstance))
* .catch(console.error);
*/
async fetch(channel, { cache = true, force = false } = {}) {
const channelID = this.guild.channels.resolveID(channel);
if (!channelID) throw new Error('STAGE_CHANNEL_RESOLVE');

if (!force) {
const existing = this.cache.find(stageInstance => stageInstance.channelID === channelID);
if (existing) return existing;
}

const data = await this.client.api('stage-instances', channelID).get();
return this.add(data, cache);
}

/**
* Options used to edit an existing stage instance.
* @typedef {Object} StageInstanceEditOptions
* @property {string} [topic] The new topic of the stage instance
* @property {PrivacyLevel|number} [privacyLevel] The new privacy level of the stage instance
*/

/**
* Edits an existing stage instance.
* @param {StageChannel|Snowflake} channel The stage channel whose instance is to be edited
* @param {StageInstanceEditOptions} options The options to edit the stage instance
* @returns {Promise<StageInstance>}
* @example
* // Edit a stage instance
* guild.stageInstances.edit('1234567890123456789', { topic: 'new topic' })
* .then(stageInstance => console.log(stageInstance))
* .catch(console.error);
*/
async edit(channel, options) {
if (typeof options !== 'object') throw new TypeError('INVALID_TYPE', 'options', 'object', true);
const channelID = this.guild.channels.resolveID(channel);
if (!channelID) throw new Error('STAGE_CHANNEL_RESOLVE');

let { topic, privacyLevel } = options;

if (privacyLevel) privacyLevel = typeof privacyLevel === 'number' ? privacyLevel : PrivacyLevels[privacyLevel];

const data = await this.client.api('stage-instances', channelID).patch({
data: {
topic,
privacy_level: privacyLevel,
},
});

if (this.cache.has(data.id)) {
const clone = this.cache.get(data.id)._clone();
clone._patch(data);
return clone;
}

return this.add(data);
}

/**
* Deletes an existing stage instance.
* @param {StageChannel|Snowflake} channel The stage channel whose instance is to be deleted
* @returns {Promise<void>}
*/
async delete(channel) {
const channelID = this.guild.channels.resolveID(channel);
if (!channelID) throw new Error('STAGE_CHANNEL_RESOLVE');

await this.client.api('stage-instances', channelID).delete();
}
}

module.exports = StageInstanceManager;
14 changes: 14 additions & 0 deletions src/structures/Guild.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const GuildEmojiManager = require('../managers/GuildEmojiManager');
const GuildMemberManager = require('../managers/GuildMemberManager');
const PresenceManager = require('../managers/PresenceManager');
const RoleManager = require('../managers/RoleManager');
const StageInstanceManager = require('../managers/StageInstanceManager');
const VoiceStateManager = require('../managers/VoiceStateManager');
const Collection = require('../util/Collection');
const {
Expand Down Expand Up @@ -83,6 +84,12 @@ class Guild extends BaseGuild {
*/
this.voiceStates = new VoiceStateManager(this);

/**
* A manager of the stage instances of this guild
* @type {StageInstanceManager}
*/
this.stageInstances = new StageInstanceManager(this);

/**
* Whether the bot has been removed from the guild
* @type {boolean}
Expand Down Expand Up @@ -402,6 +409,13 @@ class Guild extends BaseGuild {
}
}

if (data.stage_instances) {
this.stageInstances.cache.clear();
for (const stageInstance of data.stage_instances) {
this.stageInstances.add(stageInstance);
}
}

if (data.voice_states) {
this.voiceStates.cache.clear();
for (const voiceState of data.voice_states) {
Expand Down
Loading

0 comments on commit 918921e

Please sign in to comment.