mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-14 18:43:31 +01:00
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:
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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.',
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
109
src/structures/BaseGuildVoiceChannel.js
Normal file
109
src/structures/BaseGuildVoiceChannel.js
Normal 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;
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
34
src/structures/StageChannel.js
Normal file
34
src/structures/StageChannel.js
Normal 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;
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
66
typings/index.d.ts
vendored
@@ -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>> {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user