From 8619746de0ca16247791c23f639ca02c55509a31 Mon Sep 17 00:00:00 2001 From: Brian Le Date: Fri, 26 Jul 2024 11:38:03 -0700 Subject: [PATCH] feat(VoiceChannel): Support voice channel statuses (#1496) --- index.d.ts | 21 +++++++++++++++++---- lib/Client.js | 14 ++++++++++++++ lib/Constants.js | 14 +++++++++++--- lib/gateway/Shard.js | 21 +++++++++++++++++++++ lib/rest/Endpoints.js | 1 + lib/structures/GuildAuditLogEntry.js | 7 ++++++- lib/structures/VoiceChannel.js | 14 ++++++++++++++ 7 files changed, 84 insertions(+), 8 deletions(-) diff --git a/index.d.ts b/index.d.ts index ddfc4ec49..3759c3205 100644 --- a/index.d.ts +++ b/index.d.ts @@ -866,6 +866,7 @@ declare namespace Dysnomia { 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]; @@ -1717,6 +1718,9 @@ declare namespace Dysnomia { requestToSpeakTimestamp?: Date | null; suppress?: boolean; } + interface VoiceStatus { + status: string; + } interface VoiceStreamCurrent { buffer: Buffer | null; bufferingTicks: number; @@ -1944,6 +1948,7 @@ declare namespace Dysnomia { AUTO_MODERATION_RULE_UPDATE: 141; AUTO_MODERATION_RULE_DELETE: 142; AUTO_MODERATION_BLOCK_MESSAGE: 143; + AUTO_MODERATION_FLAG_TO_CHANNEL: 144; AUTO_MODERATION_USER_COMMUNICATION_DISABLED: 145; @@ -1956,6 +1961,9 @@ declare namespace Dysnomia { GUILD_HOME_FEATURE_ITEM: 171; GUILD_HOME_REMOVE_ITEM: 172; + + VOICE_CHANNEL_STATUS_UPDATE: 192; + VOICE_CHANNEL_STATUS_DELETE: 193; }; AutoModerationActionTypes: { BLOCK_MESSAGE: 1; @@ -2284,10 +2292,11 @@ declare namespace Dysnomia { sendMessagesInThreads: 274877906944n; startEmbeddedActivities: 549755813888n; moderateMembers: 1099511627776n; - allGuild: 1110182461630n; - allText: 535529258065n; - allVoice: 554385278737n; - all: 2199023255551n; + setVoiceChannelStatus: 281474976710656n; + allGuild: 311172461494462n; + allText: 70904273435729n; + allVoice: 391980524766993n; + all: 422212465065983n; }; PremiumTiers: { NONE: 0; @@ -2836,6 +2845,7 @@ declare namespace Dysnomia { removeMessageReactions(channelID: string, messageID: string): Promise; searchGuildMembers(guildID: string, query: string, limit?: number): 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; @@ -3206,6 +3216,7 @@ declare namespace Dysnomia { message?: Message; reason: string | null; role?: Role | { id: string; name: string }; + status?: string; target?: Guild | AnyGuildChannel | AnyThreadChannel | Member | Role | Invite | Emoji | Sticker | StageInstance | User | GuildScheduledEvent | null; targetID: string; user: User | Uncached; @@ -3927,6 +3938,7 @@ declare namespace Dysnomia { permissionOverwrites: Collection; position: number; rtcRegion: string | null; + status?: string; type: GuildVoiceChannelTypes; userLimit: number; videoQualityMode: VideoQualityMode; @@ -3935,6 +3947,7 @@ declare namespace Dysnomia { getInvites(): Promise<(Invite<"withMetadata", VoiceChannel>)[]>; 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 29b4265a1..bb8820c76 100644 --- a/lib/Client.js +++ b/lib/Client.js @@ -3209,6 +3209,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 template to sync * @arg {String} guildID The ID of the guild diff --git a/lib/Constants.js b/lib/Constants.js index 7d5350ec2..4328e2fe3 100644 --- a/lib/Constants.js +++ b/lib/Constants.js @@ -144,7 +144,11 @@ module.exports.AuditLogActions = { ROLE_PROMPT_DELETE: 162, GUILD_HOME_FEATURE_ITEM: 171, - GUILD_HOME_REMOVE_ITEM: 172 + GUILD_HOME_REMOVE_ITEM: 172, + GUILD_HOME_REMOVE_ITEM: 172, + + VOICE_CHANNEL_STATUS_UPDATE: 192, + VOICE_CHANNEL_STATUS_DELETE: 193 }; module.exports.AutoModerationActionTypes = { @@ -500,7 +504,9 @@ const Permissions = { useExternalStickers: 1n << 37n, sendMessagesInThreads: 1n << 38n, startEmbeddedActivities: 1n << 39n, - moderateMembers: 1n << 40n + moderateMembers: 1n << 40n, + sendVoiceMessages: 1n << 46n, + setVoiceChannelStatus: 1n << 48n }; Permissions.allGuild = Permissions.kickMembers | Permissions.banMembers @@ -549,7 +555,9 @@ Permissions.allVoice = Permissions.createInstantInvite | Permissions.voiceUseVAD | Permissions.manageRoles | Permissions.voiceRequestToSpeak - | Permissions.startEmbeddedActivities; + | Permissions.startEmbeddedActivities + | 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 966d2e69a..05951ed92 100644 --- a/lib/gateway/Shard.js +++ b/lib/gateway/Shard.js @@ -801,6 +801,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 8e30481a9..cd06235b5 100644 --- a/lib/rest/Endpoints.js +++ b/lib/rest/Endpoints.js @@ -26,6 +26,7 @@ module.exports.CHANNEL_PERMISSION = (chanID, overID) module.exports.CHANNEL_PIN = (chanID, msgID) => `/channels/${chanID}/pins/${msgID}`; module.exports.CHANNEL_PINS = (chanID) => `/channels/${chanID}/pins`; 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.DISCOVERY_CATEGORIES = "/discovery/categories"; module.exports.DISCOVERY_VALIDATION = "/discovery/valid-term"; diff --git a/lib/structures/GuildAuditLogEntry.js b/lib/structures/GuildAuditLogEntry.js index 495d28fe4..0672b536d 100644 --- a/lib/structures/GuildAuditLogEntry.js +++ b/lib/structures/GuildAuditLogEntry.js @@ -25,6 +25,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 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 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 | TextVoiceChannel | NewsChannel | StageInstance | ThreadChannel)?} target The object of the action target * If the item is not cached, this property will be null * If the action targets a guild, this could be a Guild object @@ -108,13 +109,17 @@ class GuildAuditLogEntry extends Base { }; } } + + if(data.options.status) { + this.status = data.options.status; + } } } get target() { // pay more, get less 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.guild && this.guild.channels.get(this.targetID); } 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 e50cdf9ce..e2f928acc 100644 --- a/lib/structures/VoiceChannel.js +++ b/lib/structures/VoiceChannel.js @@ -12,6 +12,7 @@ const PermissionOverwrite = require("./PermissionOverwrite"); * @prop {Collection} permissionOverwrites Collection of PermissionOverwrites in this 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 {Collection} voiceMembers Collection of Members in this channel */ class VoiceChannel extends GuildChannel { @@ -39,6 +40,9 @@ class VoiceChannel extends GuildChannel { if(data.position !== undefined) { this.position = data.position; } + if(data.status !== undefined) { + this.status = data.status; + } } /** @@ -83,6 +87,16 @@ class VoiceChannel extends GuildChannel { 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",