feat: stage channels (#5456)

* feat: add stage channel type

* feat: initialise stage channel structure

* feat: add STAGE_MODERATOR permissions bitfield

* fix: typo in permissions

* fix(Channel): type selection logic

* feat: add rtcRegion to StageChannel and VoiceChannel

* feat: rtc region editing for stage and voice channels

* feat: stage channel userLimit

* feat: add stage channels to exports

* feat: add computed properties to stage channel

* feat(VoiceState): include stage channel in docs

* feat: allow ability to join stage channels

* feat(StageChannel): join and leave methods

* docs: add StageChannel link in GuildChannel docs

* feat(VoiceState): suppress and requestToSpeakTimestamp

* feat(StageChannel): setRequestToSpeak

* refactor(StageChannel): update setRequestToSpeak

* feat(VoiceState): add moveToSpeakers and moveToAudience

* feat(VoiceState): add methods to move in/out of speakers

* feat(VoiceState): add stage channel sanity checks

* feat(Permissions): add REQUEST_TO_SPEAK

* feat(VoiceState): simpler methods

* docs(VoiceState): add documentation for new methods

* refactor: remove unused error message

* chore: remove debug statements

* chore: revert changes to package-lock.json

* docs(VoiceState): clarify suppress

* docs(VoiceState): add missing @type param

* feat(StageChannel): remove nsfw property

* fix(VoiceState): check permissions in channel

Co-authored-by: Advaith <advaithj1@gmail.com>

* fix(VoiceState): instantiate error with new

Co-authored-by: BannerBomb <BannerBomb55@gmail.com>

* refactor(VoiceState): more readable API route builder

Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com>

* style(VoiceState): fix lint errors

* docs(VoiceState): add example usage for new methods

* docs: setRTCRegion examples

* chore: update typings

* fix(VoiceState): calculate permissions for self

* refactor(VoiceState): tidy up implementation

* Update src/structures/VoiceState.js

Co-authored-by: Jan <66554238+vaporox@users.noreply.github.com>

* refactor: vaporox's suggestions

* style(VoiceState): fix linter errors

* chore: update typings

* chore: remove unused error message

* refactor(VoiceState): use optional chaining

Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com>

* chore: move getters below constructor in typings

* refactor(StageChannel): optional chaining

Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com>

* style(VoiceState): fix lint errors

* docs: fix incorrect types

Co-authored-by: izexi <43889168+izexi@users.noreply.github.com>

* Update src/structures/VoiceChannel.js

Co-authored-by: izexi <43889168+izexi@users.noreply.github.com>

* Update src/structures/VoiceChannel.js

Co-authored-by: izexi <43889168+izexi@users.noreply.github.com>

* refactor(VoiceState): use optional chaining

Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com>

* refactor(StageChannel): remove permission override check in joinable

* refactor: make ChannelTypes a proper enum

* Use createEnum

Co-authored-by: izexi <43889168+izexi@users.noreply.github.com>

* chore: remove unused code from Constants

* refactor(StageChannel): remove unnecessary getters

* chore: update typings

* refactor: introduce BaseGuildVoiceChannel class

* refactor(VoiceChannel): reduce code duplication

* feat: export BaseGuildVoiceChannel

* chore: update typings

* docs: fix typos

* refactor: move setRTCRegion to BaseGuildVoiceChannel

* feat(VoiceState): remove permission checks

* chore: update typings

* Apply suggestions from code review

Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com>
Co-authored-by: Jan <66554238+vaporox@users.noreply.github.com>

* chore: update esm exports and typings

* Update src/structures/VoiceState.js

Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>

Co-authored-by: Advaith <advaithj1@gmail.com>
Co-authored-by: BannerBomb <BannerBomb55@gmail.com>
Co-authored-by: Sugden <28943913+NotSugden@users.noreply.github.com>
Co-authored-by: Jan <66554238+vaporox@users.noreply.github.com>
Co-authored-by: izexi <43889168+izexi@users.noreply.github.com>
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
This commit is contained in:
Amish Shah
2021-04-14 13:35:55 +01:00
committed by GitHub
parent 63ff6a07eb
commit eec7cf7634
15 changed files with 320 additions and 108 deletions

View File

@@ -54,6 +54,7 @@ export const {
Activity, Activity,
APIMessage, APIMessage,
BaseGuildEmoji, BaseGuildEmoji,
BaseGuildVoiceChannel,
CategoryChannel, CategoryChannel,
Channel, Channel,
ClientApplication, ClientApplication,
@@ -86,6 +87,7 @@ export const {
RichPresenceAssets, RichPresenceAssets,
Role, Role,
StoreChannel, StoreChannel,
StageChannel,
Team, Team,
TeamMember, TeamMember,
TextChannel, TextChannel,

View File

@@ -67,8 +67,8 @@ class ClientVoiceManager {
} }
/** /**
* Sets up a request to join a voice channel. * Sets up a request to join a voice or stage channel.
* @param {VoiceChannel} channel The voice channel to join * @param {VoiceChannel|StageChannel} channel The channel to join
* @returns {Promise<VoiceConnection>} * @returns {Promise<VoiceConnection>}
* @private * @private
*/ */

View File

@@ -45,8 +45,8 @@ class VoiceConnection extends EventEmitter {
this.voiceManager = voiceManager; this.voiceManager = voiceManager;
/** /**
* The voice channel this connection is currently serving * The voice channel or stage channel this connection is currently serving
* @type {VoiceChannel} * @type {VoiceChannel|StageChannel}
*/ */
this.channel = channel; this.channel = channel;
@@ -306,8 +306,8 @@ class VoiceConnection extends EventEmitter {
} }
/** /**
* Move to a different voice channel in the same guild. * Move to a different voice channel or stage channel in the same guild.
* @param {VoiceChannel} channel The channel to move to * @param {VoiceChannel|StageChannel} channel The channel to move to
* @private * @private
*/ */
updateChannel(channel) { updateChannel(channel) {

View File

@@ -57,9 +57,11 @@ const Messages = {
VOICE_PLAY_INTERFACE_NO_BROADCAST: 'A broadcast cannot be played in this context.', VOICE_PLAY_INTERFACE_NO_BROADCAST: 'A broadcast cannot be played in this context.',
VOICE_PLAY_INTERFACE_BAD_TYPE: 'Unknown stream type', 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_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_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.',
VOICE_STATE_INVALID_TYPE: name => `${name} must be a boolean.`, 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.', UDP_SEND_FAIL: 'Tried to send a UDP packet, but there is no socket available.',

View File

@@ -63,6 +63,7 @@ module.exports = {
Activity: require('./structures/Presence').Activity, Activity: require('./structures/Presence').Activity,
APIMessage: require('./structures/APIMessage'), APIMessage: require('./structures/APIMessage'),
BaseGuildEmoji: require('./structures/BaseGuildEmoji'), BaseGuildEmoji: require('./structures/BaseGuildEmoji'),
BaseGuildVoiceChannel: require('./structures/BaseGuildVoiceChannel'),
CategoryChannel: require('./structures/CategoryChannel'), CategoryChannel: require('./structures/CategoryChannel'),
Channel: require('./structures/Channel'), Channel: require('./structures/Channel'),
ClientApplication: require('./structures/ClientApplication'), ClientApplication: require('./structures/ClientApplication'),
@@ -98,6 +99,7 @@ module.exports = {
RichPresenceAssets: require('./structures/Presence').RichPresenceAssets, RichPresenceAssets: require('./structures/Presence').RichPresenceAssets,
Role: require('./structures/Role'), Role: require('./structures/Role'),
StoreChannel: require('./structures/StoreChannel'), StoreChannel: require('./structures/StoreChannel'),
StageChannel: require('./structures/StageChannel'),
Team: require('./structures/Team'), Team: require('./structures/Team'),
TeamMember: require('./structures/TeamMember'), TeamMember: require('./structures/TeamMember'),
TextChannel: require('./structures/TextChannel'), TextChannel: require('./structures/TextChannel'),

View File

@@ -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;

View File

@@ -13,7 +13,7 @@ class Channel extends Base {
constructor(client, data) { constructor(client, data) {
super(client); super(client);
const type = Object.keys(ChannelTypes)[data.type]; const type = ChannelTypes[data.type];
/** /**
* The type of the channel, either: * The type of the channel, either:
* * `dm` - a DM channel * * `dm` - a DM channel
@@ -22,6 +22,7 @@ class Channel extends Base {
* * `category` - a guild category channel * * `category` - a guild category channel
* * `news` - a guild news channel * * `news` - a guild news channel
* * `store` - a guild store channel * * `store` - a guild store channel
* * `stage` - a guild stage channel
* * `unknown` - a generic channel of unknown type, could be Channel or GuildChannel * * `unknown` - a generic channel of unknown type, could be Channel or GuildChannel
* @type {string} * @type {string}
*/ */
@@ -146,6 +147,11 @@ class Channel extends Base {
channel = new StoreChannel(guild, data); channel = new StoreChannel(guild, data);
break; break;
} }
case ChannelTypes.STAGE: {
const StageChannel = Structures.get('StageChannel');
channel = new StageChannel(guild, data);
break;
}
} }
if (channel) guild.channels.cache.set(channel.id, channel); if (channel) guild.channels.cache.set(channel.id, channel);
} }

View File

@@ -18,6 +18,7 @@ const Util = require('../util/Util');
* - {@link CategoryChannel} * - {@link CategoryChannel}
* - {@link NewsChannel} * - {@link NewsChannel}
* - {@link StoreChannel} * - {@link StoreChannel}
* - {@link StageChannel}
* @extends {Channel} * @extends {Channel}
* @abstract * @abstract
*/ */
@@ -319,6 +320,7 @@ class GuildChannel extends Channel {
* @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites] * @property {OverwriteResolvable[]|Collection<Snowflake, OverwriteResolvable>} [permissionOverwrites]
* Permission overwrites for the channel * Permission overwrites for the channel
* @property {number} [rateLimitPerUser] The ratelimit per user for the channel in seconds * @property {number} [rateLimitPerUser] The ratelimit per user for the channel in seconds
* @property {?string} [rtcRegion] The RTC region of the channel
*/ */
/** /**
@@ -374,6 +376,7 @@ class GuildChannel extends Channel {
nsfw: data.nsfw, nsfw: data.nsfw,
bitrate: data.bitrate || this.bitrate, bitrate: data.bitrate || this.bitrate,
user_limit: typeof data.userLimit !== 'undefined' ? data.userLimit : this.userLimit, user_limit: typeof data.userLimit !== 'undefined' ? data.userLimit : this.userLimit,
rtc_region: typeof data.rtcRegion !== 'undefined' ? data.rtcRegion : this.rtcRegion,
parent_id: data.parentID, parent_id: data.parentID,
lock_permissions: data.lockPermissions, lock_permissions: data.lockPermissions,
rate_limit_per_user: data.rateLimitPerUser, rate_limit_per_user: data.rateLimitPerUser,
@@ -594,7 +597,7 @@ class GuildChannel extends Channel {
*/ */
get manageable() { get manageable() {
if (this.client.user.id === this.guild.ownerID) return true; 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)) { if (!this.permissionsFor(this.client.user).has(Permissions.FLAGS.CONNECT, false)) {
return false; return false;
} }

View File

@@ -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.
* @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;

View File

@@ -1,53 +1,13 @@
'use strict'; 'use strict';
const GuildChannel = require('./GuildChannel'); const BaseGuildVoiceChannel = require('./BaseGuildVoiceChannel');
const Collection = require('../util/Collection');
const Permissions = require('../util/Permissions'); const Permissions = require('../util/Permissions');
/** /**
* Represents a guild voice channel on Discord. * Represents a guild voice channel on Discord.
* @extends {GuildChannel} * @extends {BaseGuildVoiceChannel}
*/ */
class VoiceChannel extends GuildChannel { class VoiceChannel extends BaseGuildVoiceChannel {
_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;
}
/** /**
* Whether the channel is deletable by the client user * Whether the channel is deletable by the client user
* @type {boolean} * @type {boolean}
@@ -72,8 +32,7 @@ class VoiceChannel extends GuildChannel {
* @readonly * @readonly
*/ */
get joinable() { get joinable() {
if (!this.viewable) return false; if (!super.joinable) return false;
if (!this.permissionsFor(this.client.user).has(Permissions.FLAGS.CONNECT, false)) return false;
if (this.full && !this.permissionsFor(this.client.user).has(Permissions.FLAGS.MOVE_MEMBERS, false)) return false; if (this.full && !this.permissionsFor(this.client.user).has(Permissions.FLAGS.MOVE_MEMBERS, false)) return false;
return true; return true;
} }
@@ -118,28 +77,17 @@ class VoiceChannel extends GuildChannel {
} }
/** /**
* Attempts to join this voice channel. * Sets the RTC region of the channel.
* @returns {Promise<VoiceConnection>} * @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 * @example
* // Join a voice channel * // Set the RTC region to europe
* voiceChannel.join() * voiceChannel.setRTCRegion('europe');
* .then(connection => console.log('Connected!'))
* .catch(console.error);
*/
join() {
return this.client.voice.joinChannel(this);
}
/**
* Leaves this voice channel.
* @example * @example
* // Leave a voice channel * // Remove a fixed region for this channel - let Discord decide automatically
* voiceChannel.leave(); * 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; module.exports = VoiceChannel;

View File

@@ -63,10 +63,22 @@ class VoiceState extends Base {
*/ */
this.streaming = data.self_stream || false; this.streaming = data.self_stream || false;
/** /**
* The ID of the voice channel that this member is in * The ID of the voice or stage channel that this member is in
* @type {?Snowflake} * @type {?Snowflake}
*/ */
this.channelID = data.channel_id || null; this.channelID = data.channel_id || null;
/**
* Whether this member is suppressed from speaking. This property is specific to stage channels only.
* @type {boolean}
*/
this.suppress = data.suppress;
/**
* The time at which the member requested to speak. This property is specific to stage channels only.
* @type {?number}
*/
this.requestToSpeakTimestamp = data.request_to_speak_timestamp
? new Date(data.request_to_speak_timestamp).getTime()
: null;
return this; return this;
} }
@@ -81,7 +93,7 @@ class VoiceState extends Base {
/** /**
* The channel that the member is connected to * The channel that the member is connected to
* @type {?VoiceChannel} * @type {?VoiceChannel|StageChannel}
* @readonly * @readonly
*/ */
get channel() { get channel() {
@@ -118,7 +130,7 @@ class VoiceState extends Base {
/** /**
* Whether this member is currently speaking. A boolean if the information is available (aka * Whether this member is currently speaking. A boolean if the information is available (aka
* the bot is connected to any voice channel in the guild), otherwise this is null * the bot is connected to any voice channel or stage channel in the guild), otherwise this is `null`
* @type {?boolean} * @type {?boolean}
* @readonly * @readonly
*/ */
@@ -147,7 +159,7 @@ class VoiceState extends Base {
} }
/** /**
* Kicks the member from the voice channel. * Kicks the member from the channel.
* @param {string} [reason] Reason for kicking member from the channel * @param {string} [reason] Reason for kicking member from the channel
* @returns {Promise<GuildMember>} * @returns {Promise<GuildMember>}
*/ */
@@ -196,6 +208,63 @@ class VoiceState extends Base {
return true; return true;
} }
/**
* Toggles the request to speak in the channel.
* Only applicable for stage channels and for the client's own voice state.
* @param {boolean} request Whether or not the client is requesting to become a speaker.
* @example
* // Making the client request to speak in a stage channel (raise its hand)
* guild.me.voice.setRequestToSpeak(true);
* @example
* // Making the client cancel a request to speak
* guild.me.voice.setRequestToSpeak(false);
*/
async setRequestToSpeak(request) {
const channel = this.channel;
if (channel?.type !== 'stage') throw new Error('VOICE_NOT_STAGE_CHANNEL');
if (this.client.user.id !== this.id) throw new Error('VOICE_STATE_NOT_OWN');
await this.client.api.guilds(this.guild.id, 'voice-states', '@me').patch({
data: {
channel_id: this.channelID,
request_to_speak_timestamp: request ? new Date().toISOString() : null,
},
});
}
/**
* Suppress/unsuppress the user. Only applicable for stage channels.
* @param {boolean} suppressed - Whether or not the user should be suppressed.
* @example
* // Making the client a speaker
* guild.me.voice.setSuppressed(false);
* @example
* // Making the client an audience member
* guild.me.voice.setSuppressed(true);
* @example
* // Inviting another user to speak
* voiceState.setSuppressed(false);
* @example
* // Moving another user to the audience, or cancelling their invite to speak
* voiceState.setSuppressed(true);
*/
async setSuppressed(suppressed) {
if (typeof suppressed !== 'boolean') throw new TypeError('VOICE_STATE_INVALID_TYPE', 'suppressed');
const channel = this.channel;
if (channel?.type !== 'stage') throw new Error('VOICE_NOT_STAGE_CHANNEL');
const target = this.client.user.id === this.id ? '@me' : this.id;
await this.client.api.guilds(this.guild.id, 'voice-states', target).patch({
data: {
channel_id: this.channelID,
suppress: suppressed,
},
});
}
toJSON() { toJSON() {
return super.toJSON({ return super.toJSON({
id: true, id: true,

View File

@@ -477,15 +477,19 @@ exports.SystemMessageTypes = exports.MessageTypes.filter(type => type && type !=
*/ */
exports.ActivityTypes = ['PLAYING', 'STREAMING', 'LISTENING', 'WATCHING', 'CUSTOM_STATUS', 'COMPETING']; exports.ActivityTypes = ['PLAYING', 'STREAMING', 'LISTENING', 'WATCHING', 'CUSTOM_STATUS', 'COMPETING'];
exports.ChannelTypes = { exports.ChannelTypes = createEnum([
TEXT: 0, 'TEXT',
DM: 1, 'DM',
VOICE: 2, 'VOICE',
GROUP: 3, 'GROUP',
CATEGORY: 4, 'CATEGORY',
NEWS: 5, 'NEWS',
STORE: 6, // 6
}; 'STORE',
...Array(6).fill(null),
// 13
'STAGE',
]);
exports.ClientApplicationAssetTypes = { exports.ClientApplicationAssetTypes = {
SMALL: 1, SMALL: 1,

View File

@@ -78,6 +78,7 @@ class Permissions extends BitField {
* * `MANAGE_ROLES` * * `MANAGE_ROLES`
* * `MANAGE_WEBHOOKS` * * `MANAGE_WEBHOOKS`
* * `MANAGE_EMOJIS` * * `MANAGE_EMOJIS`
* * `REQUEST_TO_SPEAK`
* @type {Object<string, bigint>} * @type {Object<string, bigint>}
* @see {@link https://discord.com/developers/docs/topics/permissions} * @see {@link https://discord.com/developers/docs/topics/permissions}
*/ */
@@ -113,6 +114,7 @@ Permissions.FLAGS = {
MANAGE_ROLES: 1n << 28n, MANAGE_ROLES: 1n << 28n,
MANAGE_WEBHOOKS: 1n << 29n, MANAGE_WEBHOOKS: 1n << 29n,
MANAGE_EMOJIS: 1n << 30n, MANAGE_EMOJIS: 1n << 30n,
REQUEST_TO_SPEAK: 1n << 32n,
}; };
/** /**
@@ -127,6 +129,13 @@ Permissions.ALL = Object.values(Permissions.FLAGS).reduce((all, p) => all | p, 0
*/ */
Permissions.DEFAULT = BigInt(104324673); Permissions.DEFAULT = BigInt(104324673);
/**
* Bitfield representing the permissions required for moderators of stage channels
* @type {bigint}
*/
Permissions.STAGE_MODERATOR =
Permissions.FLAGS.MANAGE_CHANNELS | Permissions.FLAGS.MUTE_MEMBERS | Permissions.FLAGS.MOVE_MEMBERS;
Permissions.defaultBit = BigInt(0); Permissions.defaultBit = BigInt(0);
module.exports = Permissions; module.exports = Permissions;

View File

@@ -9,6 +9,7 @@
* * **`CategoryChannel`** * * **`CategoryChannel`**
* * **`NewsChannel`** * * **`NewsChannel`**
* * **`StoreChannel`** * * **`StoreChannel`**
* * **`StageChannel`**
* * **`GuildMember`** * * **`GuildMember`**
* * **`Guild`** * * **`Guild`**
* * **`Message`** * * **`Message`**
@@ -98,6 +99,7 @@ const structures = {
CategoryChannel: require('../structures/CategoryChannel'), CategoryChannel: require('../structures/CategoryChannel'),
NewsChannel: require('../structures/NewsChannel'), NewsChannel: require('../structures/NewsChannel'),
StoreChannel: require('../structures/StoreChannel'), StoreChannel: require('../structures/StoreChannel'),
StageChannel: require('../structures/StageChannel'),
GuildMember: require('../structures/GuildMember'), GuildMember: require('../structures/GuildMember'),
Guild: require('../structures/Guild'), Guild: require('../structures/Guild'),
Message: require('../structures/Message'), Message: require('../structures/Message'),

66
typings/index.d.ts vendored
View File

@@ -7,6 +7,18 @@ declare enum ChannelType {
news = 5, news = 5,
store = 6, store = 6,
unknown = 7, unknown = 7,
stage = 13,
}
declare enum ChannelTypes {
TEXT = 0,
DM = 1,
VOICE = 2,
GROUP = 3,
CATEGORY = 4,
NEWS = 5,
STORE = 6,
STAGE = 13,
} }
declare enum OverwriteTypes { declare enum OverwriteTypes {
@@ -166,6 +178,19 @@ declare module 'discord.js' {
public requiresColons: boolean | null; public requiresColons: boolean | null;
} }
export class BaseGuildVoiceChannel extends GuildChannel {
constructor(guild: Guild, data?: object);
public readonly members: Collection<Snowflake, GuildMember>;
public readonly full: boolean;
public readonly joinable: boolean;
public rtcRegion: string | null;
public bitrate: number;
public userLimit: number;
public join(): Promise<VoiceConnection>;
public leave(): void;
public setRTCRegion(region: string | null): Promise<this>;
}
class BroadcastDispatcher extends VolumeMixin(StreamDispatcher) { class BroadcastDispatcher extends VolumeMixin(StreamDispatcher) {
public broadcast: VoiceBroadcast; public broadcast: VoiceBroadcast;
} }
@@ -293,7 +318,7 @@ declare module 'discord.js' {
public connections: Collection<Snowflake, VoiceConnection>; public connections: Collection<Snowflake, VoiceConnection>;
public broadcasts: VoiceBroadcast[]; public broadcasts: VoiceBroadcast[];
private joinChannel(channel: VoiceChannel): Promise<VoiceConnection>; private joinChannel(channel: VoiceChannel | StageChannel): Promise<VoiceConnection>;
public createBroadcast(): VoiceBroadcast; public createBroadcast(): VoiceBroadcast;
} }
@@ -520,15 +545,7 @@ declare module 'discord.js' {
CLIENT_CONNECT: 12; CLIENT_CONNECT: 12;
CLIENT_DISCONNECT: 13; CLIENT_DISCONNECT: 13;
}; };
ChannelTypes: { ChannelTypes: typeof ChannelTypes;
TEXT: 0;
DM: 1;
VOICE: 2;
GROUP: 3;
CATEGORY: 4;
NEWS: 5;
STORE: 6;
};
ClientApplicationAssetTypes: { ClientApplicationAssetTypes: {
SMALL: 1; SMALL: 1;
BIG: 2; BIG: 2;
@@ -1213,6 +1230,7 @@ declare module 'discord.js' {
public static ALL: bigint; public static ALL: bigint;
public static DEFAULT: bigint; public static DEFAULT: bigint;
public static STAGE_MODERATOR: bigint;
public static FLAGS: PermissionFlags; public static FLAGS: PermissionFlags;
public static resolve(permission?: PermissionResolvable): bigint; public static resolve(permission?: PermissionResolvable): bigint;
} }
@@ -1428,6 +1446,11 @@ declare module 'discord.js' {
public static resolve(bit?: BitFieldResolvable<SpeakingString, number>): number; public static resolve(bit?: BitFieldResolvable<SpeakingString, number>): number;
} }
export class StageChannel extends BaseGuildVoiceChannel {
public topic: string | null;
public type: 'stage';
}
export class StoreChannel extends GuildChannel { export class StoreChannel extends GuildChannel {
constructor(guild: Guild, data?: object); constructor(guild: Guild, data?: object);
public nsfw: boolean; public nsfw: boolean;
@@ -1623,17 +1646,10 @@ declare module 'discord.js' {
public once(event: string, listener: (...args: any[]) => void): this; public once(event: string, listener: (...args: any[]) => void): this;
} }
export class VoiceChannel extends GuildChannel { export class VoiceChannel extends BaseGuildVoiceChannel {
constructor(guild: Guild, data?: object);
public bitrate: number;
public readonly editable: boolean; public readonly editable: boolean;
public readonly full: boolean;
public readonly joinable: boolean;
public readonly speakable: boolean; public readonly speakable: boolean;
public type: 'voice'; public type: 'voice';
public userLimit: number;
public join(): Promise<VoiceConnection>;
public leave(): void;
public setBitrate(bitrate: number, reason?: string): Promise<VoiceChannel>; public setBitrate(bitrate: number, reason?: string): Promise<VoiceChannel>;
public setUserLimit(userLimit: number, reason?: string): Promise<VoiceChannel>; public setUserLimit(userLimit: number, reason?: string): Promise<VoiceChannel>;
} }
@@ -1657,9 +1673,9 @@ declare module 'discord.js' {
private sendVoiceStateUpdate(options: object): Promise<Shard>; private sendVoiceStateUpdate(options: object): Promise<Shard>;
private setSessionID(sessionID: string): void; private setSessionID(sessionID: string): void;
private setTokenAndEndpoint(token: string, endpoint: string): void; private setTokenAndEndpoint(token: string, endpoint: string): void;
private updateChannel(channel: VoiceChannel): void; private updateChannel(channel: VoiceChannel | StageChannel): void;
public channel: VoiceChannel; public channel: VoiceChannel | StageChannel;
public readonly client: Client; public readonly client: Client;
public readonly dispatcher: StreamDispatcher | null; public readonly dispatcher: StreamDispatcher | null;
public player: object; public player: object;
@@ -1717,7 +1733,7 @@ declare module 'discord.js' {
export class VoiceState extends Base { export class VoiceState extends Base {
constructor(guild: Guild, data: object); constructor(guild: Guild, data: object);
public readonly channel: VoiceChannel | null; public readonly channel: VoiceChannel | StageChannel | null;
public channelID: Snowflake | null; public channelID: Snowflake | null;
public readonly connection: VoiceConnection | null; public readonly connection: VoiceConnection | null;
public readonly deaf: boolean | null; public readonly deaf: boolean | null;
@@ -1732,6 +1748,8 @@ declare module 'discord.js' {
public sessionID: string | null; public sessionID: string | null;
public streaming: boolean; public streaming: boolean;
public selfVideo: boolean; public selfVideo: boolean;
public suppress: boolean;
public requestToSpeakTimestamp: number | null;
public readonly speaking: boolean | null; public readonly speaking: boolean | null;
public setDeaf(deaf: boolean, reason?: string): Promise<GuildMember>; public setDeaf(deaf: boolean, reason?: string): Promise<GuildMember>;
@@ -1740,6 +1758,8 @@ declare module 'discord.js' {
public setChannel(channel: ChannelResolvable | null, reason?: string): Promise<GuildMember>; public setChannel(channel: ChannelResolvable | null, reason?: string): Promise<GuildMember>;
public setSelfDeaf(deaf: boolean): Promise<boolean>; public setSelfDeaf(deaf: boolean): Promise<boolean>;
public setSelfMute(mute: boolean): Promise<boolean>; public setSelfMute(mute: boolean): Promise<boolean>;
public setRequestToSpeak(request: boolean): Promise<void>;
public setSuppressed(suppressed: boolean): Promise<void>;
} }
class VolumeInterface extends EventEmitter { class VolumeInterface extends EventEmitter {
@@ -2411,6 +2431,7 @@ declare module 'discord.js' {
rateLimitPerUser?: number; rateLimitPerUser?: number;
lockPermissions?: boolean; lockPermissions?: boolean;
permissionOverwrites?: readonly OverwriteResolvable[] | Collection<Snowflake, OverwriteResolvable>; permissionOverwrites?: readonly OverwriteResolvable[] | Collection<Snowflake, OverwriteResolvable>;
rtcRegion?: string | null;
} }
interface ChannelLogsQueryOptions { interface ChannelLogsQueryOptions {
@@ -3104,7 +3125,8 @@ declare module 'discord.js' {
| 'MANAGE_NICKNAMES' | 'MANAGE_NICKNAMES'
| 'MANAGE_ROLES' | 'MANAGE_ROLES'
| 'MANAGE_WEBHOOKS' | 'MANAGE_WEBHOOKS'
| 'MANAGE_EMOJIS'; | 'MANAGE_EMOJIS'
| 'REQUEST_TO_SPEAK';
interface RecursiveArray<T> extends ReadonlyArray<T | RecursiveArray<T>> {} interface RecursiveArray<T> extends ReadonlyArray<T | RecursiveArray<T>> {}