diff --git a/index.d.ts b/index.d.ts index f5612eb7f..3ea29fbcd 100644 --- a/index.d.ts +++ b/index.d.ts @@ -978,6 +978,7 @@ declare namespace Eris { userUpdate: [user: User, oldUser: PartialUser | null]; voiceChannelJoin: [member: Member, channel: AnyVoiceChannel]; voiceChannelLeave: [member: Member, channel: AnyVoiceChannel]; + voiceChannelStatusUpdate: [channel: AnyVoiceChannel, oldChannel: VoiceStatus]; voiceChannelSwitch: [member: Member, newChannel: AnyVoiceChannel, oldChannel: AnyVoiceChannel]; voiceStateUpdate: [member: Member, oldState: OldVoiceState]; warn: [message: string, id?: number]; @@ -1840,6 +1841,9 @@ declare namespace Eris { requestToSpeakTimestamp?: Date | null; suppress?: boolean; } + interface VoiceStatus { + status: string; + } interface VoiceStreamCurrent { buffer: Buffer | null; bufferingTicks: number; @@ -2010,6 +2014,9 @@ declare namespace Eris { AUTO_MODERATION_RULE_UPDATE: 141; AUTO_MODERATION_RULE_DELETE: 142; AUTO_MODERATION_BLOCK_MESSAGE: 143; + + VOICE_CHANNEL_STATUS_UPDATE: 192; + VOICE_CHANNEL_STATUS_DELETE: 193; }; AutoModerationActionTypes: { BLOCK_MESSAGE: 1; @@ -2441,10 +2448,11 @@ declare namespace Eris { createEvents: 17592186044416n; useExternalSounds: 35184372088832n; sendVoiceMessages: 70368744177664n; - allGuild: 29697484783806n; + setVoiceChannelStatus: 281474976710656n; + allGuild: 311172461494462n; allText: 70904273435729n; - allVoice: 110505548056337n; - all: 140737488355327n; + allVoice: 391980524766993n; + all: 422212465065983n; }; PremiumTiers: { NONE: 0; @@ -3119,6 +3127,7 @@ declare namespace Eris { searchGuildMembers(guildID: string, query: string, limit?: number): Promise; searchGuildMessages(guildID: string, query: SearchOptions): Promise; sendChannelTyping(channelID: string): Promise; + setVoiceChannelStatus(channelID: string, status: string, reason?: string): Promise; syncGuildIntegration(guildID: string, integrationID: string): Promise; syncGuildTemplate(guildID: string, code: string): Promise; unbanGuildMember(guildID: string, userID: string, reason?: string): Promise; @@ -3471,6 +3480,7 @@ declare namespace Eris { message?: Message | Uncached; reason: string | null; role?: Role | { id: string; name: string }; + status?: string; target?: Guild | AnyGuildChannel | Member | Role | Invite | Emoji | Sticker | Message | null; targetID: string; user: User | Uncached; @@ -4254,6 +4264,7 @@ declare namespace Eris { permissionOverwrites: Collection; position: number; rtcRegion: string | null; + status?: string; type: GuildVoiceChannelTypes; userLimit: number; videoQualityMode: VideoQualityMode; @@ -4265,6 +4276,7 @@ declare namespace Eris { getInvites(): Promise[]>; join(options?: JoinVoiceChannelOptions): Promise; leave(): void; + setStatus(status: string, reason?: string): Promise; } export class VoiceConnection extends EventEmitter implements SimpleJSON { diff --git a/lib/Client.js b/lib/Client.js index f0f8d6d66..25365c1d0 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -3996,6 +3996,20 @@ class Client extends EventEmitter { return this.requestHandler.request("POST", Endpoints.CHANNEL_TYPING(channelID), true); } + /** + * Set the status of a voice channel. Note: This will not work in stage channels + * @arg {String} channelID The ID of the channel + * @arg {String} status The new voice channel status + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + setVoiceChannelStatus(channelID, status, reason) { + return this.requestHandler.request("PUT", Endpoints.CHANNEL_VOICE_STATUS(channelID), true, { + status, + reason + }); + } + /** * Force a guild integration to sync * @arg {String} guildID The ID of the guild diff --git a/lib/Constants.js b/lib/Constants.js index 12a733144..5ea68625d 100644 --- a/lib/Constants.js +++ b/lib/Constants.js @@ -130,7 +130,10 @@ module.exports.AuditLogActions = { ROLE_PROMPT_DELETE: 162, GUILD_HOME_FEATURE_ITEM: 171, - GUILD_HOME_REMOVE_ITEM: 172 + GUILD_HOME_REMOVE_ITEM: 172, + + VOICE_CHANNEL_STATUS_UPDATE: 192, + VOICE_CHANNEL_STATUS_DELETE: 193 }; module.exports.AutoModerationActionTypes = { @@ -576,7 +579,9 @@ const Permissions = { createGuildExpressions: 1n << 43n, createEvents: 1n << 44n, useExternalSounds: 1n << 45n, - sendVoiceMessages: 1n << 46n + sendVoiceMessages: 1n << 46n, + + setVoiceChannelStatus: 1n << 48n }; Permissions.allGuild = Permissions.kickMembers | Permissions.banMembers @@ -632,7 +637,8 @@ Permissions.allVoice = Permissions.createInstantInvite | Permissions.useEmbeddedActivities | Permissions.useSoundboard | Permissions.useExternalSounds - | Permissions.sendVoiceMessages; + | Permissions.sendVoiceMessages + | Permissions.setVoiceChannelStatus; Permissions.all = Permissions.allGuild | Permissions.allText | Permissions.allVoice; module.exports.Permissions = Permissions; diff --git a/lib/gateway/Shard.js b/lib/gateway/Shard.js index 6a9458ae5..108e75d85 100644 --- a/lib/gateway/Shard.js +++ b/lib/gateway/Shard.js @@ -972,6 +972,27 @@ class Shard extends EventEmitter { } break; } + case "VOICE_CHANNEL_STATUS_UPDATE": { + const channel = this.client.getChannel(packet.d.id); + if(!channel) { + break; + } + const oldChannel = { + status: channel.status + }; + channel.update({ + status: packet.d.status + }); + /** + * Fired when a voice channel status is updated + * @event Client#voiceChannelStatusUpdate + * @prop {VoiceChannel} channel The updated voice channel + * @prop {Object} oldChannel The old channel data + * @prop {String?} oldChannel.status The old voice channel status + */ + this.emit("voiceChannelStatusUpdate", channel, oldChannel); + break; + } case "TYPING_START": { let member = null; const guild = this.client.guilds.get(packet.d.guild_id); diff --git a/lib/rest/Endpoints.js b/lib/rest/Endpoints.js index 0aaa0b343..536d3542f 100644 --- a/lib/rest/Endpoints.js +++ b/lib/rest/Endpoints.js @@ -29,6 +29,7 @@ module.exports.CHANNEL_PIN = (chanID, msgID) module.exports.CHANNEL_PINS = (chanID) => `/channels/${chanID}/pins`; module.exports.CHANNEL_RECIPIENT = (groupID, userID) => `/channels/${groupID}/recipients/${userID}`; module.exports.CHANNEL_TYPING = (chanID) => `/channels/${chanID}/typing`; +module.exports.CHANNEL_VOICE_STATUS = (chanID) => `/channels/${chanID}/voice-status`; module.exports.CHANNEL_WEBHOOKS = (chanID) => `/channels/${chanID}/webhooks`; module.exports.CHANNELS = "/channels"; module.exports.CUSTOM_EMOJI_GUILD = (emojiID) => `/emojis/${emojiID}/guild`; diff --git a/lib/structures/GuildAuditLogEntry.js b/lib/structures/GuildAuditLogEntry.js index 36d633fb7..65d4cca9d 100644 --- a/lib/structures/GuildAuditLogEntry.js +++ b/lib/structures/GuildAuditLogEntry.js @@ -24,6 +24,7 @@ const {AuditLogActions} = require("../Constants"); * @prop {(Message | Object)?} message The message that was (un)pinned, action types 74/75 (MESSAGE_PIN/UNPIN) only. If the channel or message is not cached, this will be an object with an `id` key. No other property is guaranteed. * @prop {String?} reason The reason for the action * @prop {(Role | Object)?} role The role described by the permission overwrite, action types 13-15 (CHANNEL\_OVERWRITE\_CREATE/UPDATE/DELETE) only. If the guild or role is not cached, this could be {id: String, name: String} + * @prop {String?} status The new voice channel status, action type 192 (VOICE\_CHANNEL\_STATUS\_UPDATE) only * @prop {(CategoryChannel | Guild | Member | Invite | Role | Object | TextChannel | VoiceChannel | NewsChannel)?} target The object of the action target * If the item or the item's guild (where relevant, not including invites) is not cached, this property will be null * If the action targets a guild, this could be a Guild object @@ -104,6 +105,9 @@ class GuildAuditLogEntry extends Base { } } } + if(data.options.status) { + this.status = data.options.status; + } } } @@ -111,7 +115,7 @@ class GuildAuditLogEntry extends Base { const guildAvailable = this.guild instanceof Guild; if(this.actionType < 10) { // Guild return this.guild; - } else if(this.actionType < 20) { // Channel + } else if(this.actionType < 20 || this.actionType === 192 || this.actionType === 193) { // Channel return this._client.getChannel(this.targetID) || null; } else if(this.actionType < 30) { // Member if(this.actionType === AuditLogActions.MEMBER_MOVE || this.actionType === AuditLogActions.MEMBER_DISCONNECT) { // MEMBER_MOVE / MEMBER_DISCONNECT diff --git a/lib/structures/VoiceChannel.js b/lib/structures/VoiceChannel.js index f0e55750c..220efc77c 100644 --- a/lib/structures/VoiceChannel.js +++ b/lib/structures/VoiceChannel.js @@ -13,6 +13,7 @@ const PermissionOverwrite = require("./PermissionOverwrite"); * @prop {Collection} permissionOverwrites Collection of PermissionOverwrites in the channel * @prop {Number} position The position of the channel * @prop {String?} rtcRegion The RTC region ID of the channel (automatic when `null`) + * @prop {String?} status The voice channel status * @prop {Number?} userLimit The max number of users that can join the channel * @prop {Number?} videoQualityMode The camera video quality mode of the voice channel. `1` is auto, `2` is 720p * @prop {Collection} voiceMembers Collection of Members in this channel @@ -50,6 +51,9 @@ class VoiceChannel extends GuildTextableChannel { if(data.video_quality_mode !== undefined) { this.videoQualityMode = data.video_quality_mode; } + if(data.status !== undefined) { + this.status = data.status; + } } /** @@ -117,6 +121,16 @@ class VoiceChannel extends GuildTextableChannel { return this._client.leaveVoiceChannel.call(this._client, this.id); } + /** + * Set the status of the voice channel. Note: This will not work in stage channels + * @arg {String} status The new voice channel status + * @arg {String} [reason] The reason to be displayed in audit logs + * @returns {Promise} + */ + setStatus(status, reason) { + return this._client.setVoiceChannelStatus.call(this._client, this.id, status, reason); + } + toJSON(props = []) { return super.toJSON([ "bitrate", @@ -124,6 +138,7 @@ class VoiceChannel extends GuildTextableChannel { "permissionOverwrites", "position", "rtcRegion", + "status", "userLimit", "videoQualityMode", "voiceMembers",