Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: stage channels #5456

Merged
merged 70 commits into from
Apr 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
a8a45cb
feat: add stage channel type
amishshah Mar 31, 2021
cbd5103
feat: initialise stage channel structure
amishshah Mar 31, 2021
d9c460e
feat: add STAGE_MODERATOR permissions bitfield
amishshah Mar 31, 2021
fe4a554
fix: typo in permissions
amishshah Mar 31, 2021
542e05e
fix(Channel): type selection logic
amishshah Mar 31, 2021
3afd591
feat: add rtcRegion to StageChannel and VoiceChannel
amishshah Mar 31, 2021
5d590de
feat: rtc region editing for stage and voice channels
amishshah Mar 31, 2021
e3773f2
feat: stage channel userLimit
amishshah Mar 31, 2021
1d7b3cc
feat: add stage channels to exports
amishshah Mar 31, 2021
b5d567a
feat: add computed properties to stage channel
amishshah Mar 31, 2021
d071aca
feat(VoiceState): include stage channel in docs
amishshah Mar 31, 2021
eab45f5
feat: allow ability to join stage channels
amishshah Mar 31, 2021
226c5f1
feat(StageChannel): join and leave methods
amishshah Mar 31, 2021
f8a8c61
docs: add StageChannel link in GuildChannel docs
amishshah Mar 31, 2021
cc74d90
feat(VoiceState): suppress and requestToSpeakTimestamp
amishshah Mar 31, 2021
c620e04
feat(StageChannel): setRequestToSpeak
amishshah Mar 31, 2021
88543f1
refactor(StageChannel): update setRequestToSpeak
amishshah Mar 31, 2021
eb1a15d
feat(VoiceState): add moveToSpeakers and moveToAudience
amishshah Mar 31, 2021
ba919bc
feat(VoiceState): add methods to move in/out of speakers
amishshah Mar 31, 2021
cbf31b2
feat(VoiceState): add stage channel sanity checks
amishshah Mar 31, 2021
a7a66c0
feat(Permissions): add REQUEST_TO_SPEAK
amishshah Mar 31, 2021
43870b9
feat(VoiceState): simpler methods
amishshah Mar 31, 2021
15cc468
docs(VoiceState): add documentation for new methods
amishshah Mar 31, 2021
e9bfff5
refactor: remove unused error message
amishshah Mar 31, 2021
4c135a2
chore: remove debug statements
amishshah Mar 31, 2021
9333b5a
Merge branch 'master' into feat/stage-channels
amishshah Mar 31, 2021
4bdcbb3
chore: revert changes to package-lock.json
amishshah Mar 31, 2021
e102b5b
docs(VoiceState): clarify suppress
amishshah Mar 31, 2021
43fb41b
docs(VoiceState): add missing @type param
amishshah Mar 31, 2021
b3146bf
feat(StageChannel): remove nsfw property
amishshah Mar 31, 2021
d8fa3af
fix(VoiceState): check permissions in channel
amishshah Apr 1, 2021
28b0800
fix(VoiceState): instantiate error with new
amishshah Apr 1, 2021
33430d6
refactor(VoiceState): more readable API route builder
amishshah Apr 1, 2021
dc12a56
style(VoiceState): fix lint errors
amishshah Apr 1, 2021
10db472
docs(VoiceState): add example usage for new methods
amishshah Apr 1, 2021
b1a4b92
docs: setRTCRegion examples
amishshah Apr 1, 2021
b64e375
chore: update typings
amishshah Apr 1, 2021
11b3062
fix(VoiceState): calculate permissions for self
amishshah Apr 1, 2021
0f189d1
refactor(VoiceState): tidy up implementation
amishshah Apr 1, 2021
fe68446
Update src/structures/VoiceState.js
amishshah Apr 1, 2021
cf961d0
refactor: vaporox's suggestions
amishshah Apr 1, 2021
8377d06
Merge branch 'feat/stage-channels' of github.com:amishshah/discord.js…
amishshah Apr 1, 2021
e3f6318
style(VoiceState): fix linter errors
amishshah Apr 1, 2021
1ba508d
chore: update typings
amishshah Apr 1, 2021
7f1bad5
chore: remove unused error message
amishshah Apr 1, 2021
2585d75
refactor(VoiceState): use optional chaining
amishshah Apr 2, 2021
2ec94d4
chore: move getters below constructor in typings
amishshah Apr 2, 2021
c6ffeb9
refactor(StageChannel): optional chaining
amishshah Apr 2, 2021
b020dd9
style(VoiceState): fix lint errors
amishshah Apr 2, 2021
220b48a
docs: fix incorrect types
amishshah Apr 2, 2021
211496a
Update src/structures/VoiceChannel.js
amishshah Apr 2, 2021
e6d3c00
Update src/structures/VoiceChannel.js
amishshah Apr 2, 2021
29e5e5c
refactor(VoiceState): use optional chaining
amishshah Apr 2, 2021
738082b
refactor(StageChannel): remove permission override check in joinable
amishshah Apr 2, 2021
86c5330
refactor: make ChannelTypes a proper enum
amishshah Apr 2, 2021
7f4f08a
Use createEnum
amishshah Apr 4, 2021
08d34ee
chore: remove unused code from Constants
amishshah Apr 4, 2021
9f92542
refactor(StageChannel): remove unnecessary getters
amishshah Apr 4, 2021
2bee55c
chore: update typings
amishshah Apr 7, 2021
4b575c2
refactor: introduce BaseGuildVoiceChannel class
amishshah Apr 7, 2021
c0514cc
refactor(VoiceChannel): reduce code duplication
amishshah Apr 7, 2021
dcbac25
feat: export BaseGuildVoiceChannel
amishshah Apr 7, 2021
be11202
chore: update typings
amishshah Apr 7, 2021
9e45914
docs: fix typos
amishshah Apr 7, 2021
0158d87
refactor: move setRTCRegion to BaseGuildVoiceChannel
amishshah Apr 7, 2021
3cab4de
feat(VoiceState): remove permission checks
amishshah Apr 7, 2021
bd72ffc
chore: update typings
amishshah Apr 7, 2021
76ef577
Apply suggestions from code review
amishshah Apr 7, 2021
57790e3
chore: update esm exports and typings
amishshah Apr 11, 2021
fe8ad16
Update src/structures/VoiceState.js
amishshah Apr 14, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions esm/discord.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export const {
Activity,
APIMessage,
BaseGuildEmoji,
BaseGuildVoiceChannel,
CategoryChannel,
Channel,
ClientApplication,
Expand Down Expand Up @@ -84,6 +85,7 @@ export const {
RichPresenceAssets,
Role,
StoreChannel,
StageChannel,
Team,
TeamMember,
TextChannel,
Expand Down
4 changes: 2 additions & 2 deletions src/client/voice/ClientVoiceManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ class ClientVoiceManager {
}

/**
* Sets up a request to join a voice channel.
* @param {VoiceChannel} channel The voice channel to join
* Sets up a request to join a voice or stage channel.
* @param {VoiceChannel|StageChannel} channel The channel to join
* @returns {Promise<VoiceConnection>}
* @private
*/
Expand Down
8 changes: 4 additions & 4 deletions src/client/voice/VoiceConnection.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ class VoiceConnection extends EventEmitter {
this.voiceManager = voiceManager;

/**
* The voice channel this connection is currently serving
* @type {VoiceChannel}
* The voice channel or stage channel this connection is currently serving
* @type {VoiceChannel|StageChannel}
*/
this.channel = channel;

Expand Down Expand Up @@ -306,8 +306,8 @@ class VoiceConnection extends EventEmitter {
}

/**
* Move to a different voice channel in the same guild.
* @param {VoiceChannel} channel The channel to move to
* Move to a different voice channel or stage channel in the same guild.
* @param {VoiceChannel|StageChannel} channel The channel to move to
* @private
*/
updateChannel(channel) {
Expand Down
4 changes: 3 additions & 1 deletion src/errors/Messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ const Messages = {
VOICE_PLAY_INTERFACE_NO_BROADCAST: 'A broadcast cannot be played in this context.',
VOICE_PLAY_INTERFACE_BAD_TYPE: 'Unknown stream type',
VOICE_PRISM_DEMUXERS_NEED_STREAM: 'To play a webm/ogg stream, you need to pass a ReadableStream.',
VOICE_NOT_STAGE_CHANNEL: 'You are only allowed to do this in stage channels.',

VOICE_STATE_UNCACHED_MEMBER: 'The member of this voice state is uncached.',
VOICE_STATE_NOT_OWN: 'You cannot self-deafen/mute on VoiceStates that do not belong to the ClientUser.',
VOICE_STATE_NOT_OWN:
'You cannot self-deafen/mute/request to speak on VoiceStates that do not belong to the ClientUser.',
amishshah marked this conversation as resolved.
Show resolved Hide resolved
VOICE_STATE_INVALID_TYPE: name => `${name} must be a boolean.`,

UDP_SEND_FAIL: 'Tried to send a UDP packet, but there is no socket available.',
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ module.exports = {
Activity: require('./structures/Presence').Activity,
APIMessage: require('./structures/APIMessage'),
BaseGuildEmoji: require('./structures/BaseGuildEmoji'),
BaseGuildVoiceChannel: require('./structures/BaseGuildVoiceChannel'),
amishshah marked this conversation as resolved.
Show resolved Hide resolved
CategoryChannel: require('./structures/CategoryChannel'),
Channel: require('./structures/Channel'),
ClientApplication: require('./structures/ClientApplication'),
Expand Down Expand Up @@ -96,6 +97,7 @@ module.exports = {
RichPresenceAssets: require('./structures/Presence').RichPresenceAssets,
Role: require('./structures/Role'),
StoreChannel: require('./structures/StoreChannel'),
StageChannel: require('./structures/StageChannel'),
Team: require('./structures/Team'),
TeamMember: require('./structures/TeamMember'),
TextChannel: require('./structures/TextChannel'),
Expand Down
109 changes: 109 additions & 0 deletions src/structures/BaseGuildVoiceChannel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
'use strict';

const GuildChannel = require('./GuildChannel');
const Collection = require('../util/Collection');
const Permissions = require('../util/Permissions');

/**
* Represents a voice-based guild channel on Discord.
* @extends {GuildChannel}
*/
class BaseGuildVoiceChannel extends GuildChannel {
_patch(data) {
super._patch(data);

/**
* The RTC region for this voice-based channel. This region is automatically selected if `null`.
* @type {?string}
*/
this.rtcRegion = data.rtc_region;

/**
* The bitrate of this voice-based channel
* @type {number}
*/
this.bitrate = data.bitrate;

/**
* The maximum amount of users allowed in this channel.
* @type {number}
*/
this.userLimit = data.user_limit;
}

/**
* The members in this voice-based channel
* @type {Collection<Snowflake, GuildMember>}
* @readonly
*/
get members() {
const coll = new Collection();
for (const state of this.guild.voiceStates.cache.values()) {
if (state.channelID === this.id && state.member) {
coll.set(state.id, state.member);
}
}
return coll;
}

/**
* Checks if the voice-based channel is full
* @type {boolean}
* @readonly
*/
get full() {
return this.userLimit > 0 && this.members.size >= this.userLimit;
}

/**
* Whether the channel is joinable by the client user
* @type {boolean}
* @readonly
*/
get joinable() {
if (!this.viewable) return false;
if (!this.permissionsFor(this.client.user).has(Permissions.FLAGS.CONNECT, false)) return false;
return true;
}

/**
* Attempts to join this voice-based channel.
* @returns {Promise<VoiceConnection>}
* @example
* // Join a voice-based channel
* channel.join()
* .then(connection => console.log('Connected!'))
* .catch(console.error);
*/
join() {
return this.client.voice.joinChannel(this);
}

/**
* Leaves this voice-based channel.
* @example
* // Leave a voice-based channel
* channel.leave();
*/
leave() {
const connection = this.client.voice.connections.get(this.guild.id);
if (connection?.channel.id === this.id) connection.disconnect();
}

/**
* Sets the RTC region of the channel.
* @param {?string} region The new region of the channel. Set to `null` to remove a specific region for the channel
* @returns {Promise<BaseGuildVoiceChannel>}
* @example
* // Set the RTC region to europe
* channel.setRTCRegion('europe');
* @example
* // Remove a fixed region for this channel - let Discord decide automatically
* channel.setRTCRegion(null);
*/
setRTCRegion(region) {
return this.edit({ rtcRegion: region });
}
}

module.exports = BaseGuildVoiceChannel;
8 changes: 7 additions & 1 deletion src/structures/Channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Channel extends Base {
constructor(client, data) {
super(client);

const type = Object.keys(ChannelTypes)[data.type];
const type = ChannelTypes[data.type];
/**
* The type of the channel, either:
* * `dm` - a DM channel
Expand All @@ -22,6 +22,7 @@ class Channel extends Base {
* * `category` - a guild category channel
* * `news` - a guild news channel
* * `store` - a guild store channel
* * `stage` - a guild stage channel
* * `unknown` - a generic channel of unknown type, could be Channel or GuildChannel
* @type {string}
*/
Expand Down Expand Up @@ -146,6 +147,11 @@ class Channel extends Base {
channel = new StoreChannel(guild, data);
break;
}
case ChannelTypes.STAGE: {
const StageChannel = Structures.get('StageChannel');
channel = new StageChannel(guild, data);
break;
}
}
if (channel) guild.channels.cache.set(channel.id, channel);
}
Expand Down
5 changes: 4 additions & 1 deletion src/structures/GuildChannel.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const Util = require('../util/Util');
* - {@link CategoryChannel}
* - {@link NewsChannel}
* - {@link StoreChannel}
* - {@link StageChannel}
* @extends {Channel}
* @abstract
*/
Expand Down Expand Up @@ -317,6 +318,7 @@ class GuildChannel extends Channel {
* @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites]
* Permission overwrites for the channel
* @property {number} [rateLimitPerUser] The ratelimit per user for the channel in seconds
* @property {?string} [rtcRegion] The RTC region of the channel
*/

/**
Expand Down Expand Up @@ -372,6 +374,7 @@ class GuildChannel extends Channel {
nsfw: data.nsfw,
bitrate: data.bitrate || this.bitrate,
user_limit: typeof data.userLimit !== 'undefined' ? data.userLimit : this.userLimit,
rtc_region: typeof data.rtcRegion !== 'undefined' ? data.rtcRegion : this.rtcRegion,
amishshah marked this conversation as resolved.
Show resolved Hide resolved
parent_id: data.parentID,
lock_permissions: data.lockPermissions,
rate_limit_per_user: data.rateLimitPerUser,
Expand Down Expand Up @@ -592,7 +595,7 @@ class GuildChannel extends Channel {
*/
get manageable() {
if (this.client.user.id === this.guild.ownerID) return true;
if (this.type === 'voice') {
if (this.type === 'voice' || this.type === 'stage') {
if (!this.permissionsFor(this.client.user).has(Permissions.FLAGS.CONNECT, false)) {
return false;
}
Expand Down
34 changes: 34 additions & 0 deletions src/structures/StageChannel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use strict';

const BaseGuildVoiceChannel = require('./BaseGuildVoiceChannel');

/**
* Represents a guild stage channel on Discord.
* @extends {BaseGuildVoiceChannel}
*/
class StageChannel extends BaseGuildVoiceChannel {
_patch(data) {
super._patch(data);

/**
* The topic of the stage channel
* @type {?string}
*/
this.topic = data.topic;
}

/**
* Sets the RTC region of the channel.
amishshah marked this conversation as resolved.
Show resolved Hide resolved
* @name StageChannel#setRTCRegion
* @param {?string} region The new region of the channel. Set to `null` to remove a specific region for the channel
* @returns {Promise<StageChannel>}
* @example
* // Set the RTC region to europe
* stageChannel.setRTCRegion('europe');
* @example
* // Remove a fixed region for this channel - let Discord decide automatically
* stageChannel.setRTCRegion(null);
*/
}

module.exports = StageChannel;
76 changes: 12 additions & 64 deletions src/structures/VoiceChannel.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,13 @@
'use strict';

const GuildChannel = require('./GuildChannel');
const Collection = require('../util/Collection');
const BaseGuildVoiceChannel = require('./BaseGuildVoiceChannel');
const Permissions = require('../util/Permissions');

/**
* Represents a guild voice channel on Discord.
* @extends {GuildChannel}
* @extends {BaseGuildVoiceChannel}
*/
class VoiceChannel extends GuildChannel {
_patch(data) {
super._patch(data);
/**
* The bitrate of this voice channel
* @type {number}
*/
this.bitrate = data.bitrate;

/**
* The maximum amount of users allowed in this channel - 0 means unlimited.
* @type {number}
*/
this.userLimit = data.user_limit;
}

/**
* The members in this voice channel
* @type {Collection<Snowflake, GuildMember>}
* @readonly
*/
get members() {
const coll = new Collection();
for (const state of this.guild.voiceStates.cache.values()) {
if (state.channelID === this.id && state.member) {
coll.set(state.id, state.member);
}
}
return coll;
}

/**
* Checks if the voice channel is full
* @type {boolean}
* @readonly
*/
get full() {
return this.userLimit > 0 && this.members.size >= this.userLimit;
}

class VoiceChannel extends BaseGuildVoiceChannel {
/**
* Whether the channel is deletable by the client user
* @type {boolean}
Expand All @@ -72,8 +32,7 @@ class VoiceChannel extends GuildChannel {
* @readonly
*/
get joinable() {
if (!this.viewable) return false;
if (!this.permissionsFor(this.client.user).has(Permissions.FLAGS.CONNECT, false)) return false;
if (!super.joinable) return false;
if (this.full && !this.permissionsFor(this.client.user).has(Permissions.FLAGS.MOVE_MEMBERS, false)) return false;
return true;
}
amishshah marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -118,28 +77,17 @@ class VoiceChannel extends GuildChannel {
}

/**
amishshah marked this conversation as resolved.
Show resolved Hide resolved
* Attempts to join this voice channel.
* @returns {Promise<VoiceConnection>}
* Sets the RTC region of the channel.
* @name VoiceChannel#setRTCRegion
* @param {?string} region The new region of the channel. Set to `null` to remove a specific region for the channel
* @returns {Promise<VoiceChannel>}
* @example
* // Join a voice channel
* voiceChannel.join()
* .then(connection => console.log('Connected!'))
* .catch(console.error);
*/
join() {
return this.client.voice.joinChannel(this);
}

/**
* Leaves this voice channel.
* // Set the RTC region to europe
* voiceChannel.setRTCRegion('europe');
* @example
* // Leave a voice channel
* voiceChannel.leave();
* // Remove a fixed region for this channel - let Discord decide automatically
* voiceChannel.setRTCRegion(null);
*/
leave() {
const connection = this.client.voice.connections.get(this.guild.id);
if (connection && connection.channel.id === this.id) connection.disconnect();
}
}

module.exports = VoiceChannel;
Loading