This commit is contained in:
DjDeveloperr 2021-04-04 10:13:26 +05:30
commit f8d7cbccc7
16 changed files with 267 additions and 45 deletions

View File

@ -17,6 +17,7 @@ module.exports = {
}, },
plugins: ['@typescript-eslint'], plugins: ['@typescript-eslint'],
rules: { rules: {
'@typescript-eslint/restrict-template-expressions': 'off' '@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/no-non-null-assertion': 'off'
} }
} }

View File

@ -1,7 +1,6 @@
export { EventEmitter } from 'https://deno.land/x/event@0.2.1/mod.ts' export { EventEmitter } from 'https://deno.land/x/event@0.2.1/mod.ts'
export { unzlib } from 'https://denopkg.com/DjDeveloperr/denoflate@1.2/mod.ts' export { unzlib } from 'https://denopkg.com/DjDeveloperr/denoflate@1.2/mod.ts'
export { fetchAuto } from 'https://deno.land/x/fetchbase64@1.0.0/mod.ts' export { fetchAuto } from 'https://deno.land/x/fetchbase64@1.0.0/mod.ts'
export { parse } from 'https://deno.land/x/mutil@0.1.2/mod.ts'
export { walk } from 'https://deno.land/std@0.86.0/fs/walk.ts' export { walk } from 'https://deno.land/std@0.86.0/fs/walk.ts'
export { join } from 'https://deno.land/std@0.86.0/path/mod.ts' export { join } from 'https://deno.land/std@0.86.0/path/mod.ts'
export { Mixin } from 'https://esm.sh/ts-mixer@5.4.0' export { Mixin } from 'https://esm.sh/ts-mixer@5.4.0'

5
mod.ts
View File

@ -52,7 +52,8 @@ export { UsersManager } from './src/managers/users.ts'
export { InviteManager } from './src/managers/invites.ts' export { InviteManager } from './src/managers/invites.ts'
export { Application } from './src/structures/application.ts' export { Application } from './src/structures/application.ts'
// export { ImageURL } from './src/structures/cdn.ts' // export { ImageURL } from './src/structures/cdn.ts'
export { Channel } from './src/structures/channel.ts' export { Channel, GuildChannel } from './src/structures/channel.ts'
export type { EditOverwriteOptions } from './src/structures/channel.ts'
export { DMChannel } from './src/structures/dmChannel.ts' export { DMChannel } from './src/structures/dmChannel.ts'
export { Embed } from './src/structures/embed.ts' export { Embed } from './src/structures/embed.ts'
export { Emoji } from './src/structures/emoji.ts' export { Emoji } from './src/structures/emoji.ts'
@ -167,3 +168,5 @@ export type { WebhookPayload } from './src/types/webhook.ts'
export * from './src/models/collectors.ts' export * from './src/models/collectors.ts'
export type { Dict } from './src/utils/dict.ts' export type { Dict } from './src/utils/dict.ts'
export * from './src/models/redisCache.ts' export * from './src/models/redisCache.ts'
export { ColorUtil } from './src/utils/colorutil.ts'
export type { Colors } from './src/utils/colorutil.ts'

View File

@ -0,0 +1,39 @@
import { Client } from '../models/client.ts'
import { BaseChildManager } from './baseChild.ts'
import { VoiceStatePayload } from '../types/voice.ts'
import { VoiceState } from '../structures/voiceState.ts'
import { GuildVoiceStatesManager } from './guildVoiceStates.ts'
import { VoiceChannel } from '../structures/guildVoiceChannel.ts'
export class GuildChannelVoiceStatesManager extends BaseChildManager<
VoiceStatePayload,
VoiceState
> {
channel: VoiceChannel
constructor(
client: Client,
parent: GuildVoiceStatesManager,
channel: VoiceChannel
) {
super(client, parent as any)
this.channel = channel
}
async get(id: string): Promise<VoiceState | undefined> {
const res = await this.parent.get(id)
if (res !== undefined && res.channel?.id === this.channel.id) return res
else return undefined
}
async array(): Promise<VoiceState[]> {
const arr = (await this.parent.array()) as VoiceState[]
return arr.filter((c: any) => c.channel?.id === this.channel.id) as any
}
async fromPayload(d: VoiceStatePayload[]): Promise<void> {
for (const data of d) {
await this.set(data.user_id, data)
}
}
}

View File

@ -30,7 +30,7 @@ export class GuildVoiceStatesManager extends BaseManager<
const guild = const guild =
raw.guild_id === undefined raw.guild_id === undefined
? undefined ? this.guild
: await this.client.guilds.get(raw.guild_id) : await this.client.guilds.get(raw.guild_id)
return new VoiceState(this.client, raw, { return new VoiceState(this.client, raw, {
@ -57,7 +57,7 @@ export class GuildVoiceStatesManager extends BaseManager<
arr.map(async (raw) => { arr.map(async (raw) => {
const guild = const guild =
raw.guild_id === undefined raw.guild_id === undefined
? undefined ? this.guild
: await this.client.guilds.get(raw.guild_id) : await this.client.guilds.get(raw.guild_id)
return new VoiceState(this.client, raw, { return new VoiceState(this.client, raw, {

View File

@ -5,7 +5,7 @@ import { User } from '../structures/user.ts'
import { Collection } from '../utils/collection.ts' import { Collection } from '../utils/collection.ts'
import { CommandClient } from './commandClient.ts' import { CommandClient } from './commandClient.ts'
import { Extension } from './extensions.ts' import { Extension } from './extensions.ts'
import { join, parse, walk } from '../../deps.ts' import { join, walk } from '../../deps.ts'
export interface CommandContext { export interface CommandContext {
/** The Client object */ /** The Client object */
@ -599,7 +599,7 @@ export const parseCommand = (
): ParsedCommand | undefined => { ): ParsedCommand | undefined => {
let content = msg.content.slice(prefix.length) let content = msg.content.slice(prefix.length)
if (client.spacesAfterPrefix === true) content = content.trim() if (client.spacesAfterPrefix === true) content = content.trim()
const args = parse(content)._.map((e) => e.toString()) const args = content.split(' ')
const name = args.shift() const name = args.shift()
if (name === undefined) return if (name === undefined) return

View File

@ -38,6 +38,13 @@ export class Channel extends SnowflakeBase {
} }
} }
export interface EditOverwriteOptions {
/** Allow Override Type */
allow?: OverrideType
/** Deny Override Type */
deny?: OverrideType
}
export class GuildChannel extends Channel { export class GuildChannel extends Channel {
guildID: string guildID: string
name: string name: string
@ -74,7 +81,7 @@ export class GuildChannel extends Channel {
const stringToObject = const stringToObject =
typeof target === 'string' typeof target === 'string'
? (await this.guild.members.get(target)) ?? ? (await this.guild.members.get(target)) ??
(await this.guild.roles.get(target)) (await this.guild.roles.get(target))
: target : target
if (stringToObject === undefined) { if (stringToObject === undefined) {
@ -121,7 +128,7 @@ export class GuildChannel extends Channel {
const stringToObject = const stringToObject =
typeof target === 'string' typeof target === 'string'
? (await this.guild.members.get(target)) ?? ? (await this.guild.members.get(target)) ??
(await this.guild.roles.get(target)) (await this.guild.roles.get(target))
: target : target
if (stringToObject === undefined) { if (stringToObject === undefined) {
@ -193,8 +200,8 @@ export class GuildChannel extends Channel {
overwrite.id instanceof Role overwrite.id instanceof Role
? 0 ? 0
: overwrite.id instanceof Member : overwrite.id instanceof Member
? 1 ? 1
: overwrite.type : overwrite.type
if (type === undefined) { if (type === undefined) {
throw new Error('Overwrite type is undefined.') throw new Error('Overwrite type is undefined.')
} }
@ -226,8 +233,8 @@ export class GuildChannel extends Channel {
overwrite.id instanceof Role overwrite.id instanceof Role
? 0 ? 0
: overwrite.id instanceof Member : overwrite.id instanceof Member
? 1 ? 1
: overwrite.type : overwrite.type
if (type === undefined) { if (type === undefined) {
throw new Error('Overwrite type is undefined.') throw new Error('Overwrite type is undefined.')
} }
@ -257,12 +264,9 @@ export class GuildChannel extends Channel {
async editOverwrite( async editOverwrite(
overwrite: OverwriteAsArg, overwrite: OverwriteAsArg,
{ {
overriteAllow = OverrideType.ADD, allow: overwriteAllow = OverrideType.ADD,
overriteDeny = OverrideType.ADD deny: overwriteDeny = OverrideType.ADD
}: { }: EditOverwriteOptions
overriteAllow?: OverrideType
overriteDeny?: OverrideType
}
): Promise<GuildChannels> { ): Promise<GuildChannels> {
const id = typeof overwrite.id === 'string' ? overwrite.id : overwrite.id.id const id = typeof overwrite.id === 'string' ? overwrite.id : overwrite.id.id
const index = this.permissionOverwrites.findIndex((e) => e.id === id) const index = this.permissionOverwrites.findIndex((e) => e.id === id)
@ -274,9 +278,9 @@ export class GuildChannel extends Channel {
if ( if (
overwrite.allow !== undefined && overwrite.allow !== undefined &&
overriteAllow !== OverrideType.REPLACE overwriteAllow !== OverrideType.REPLACE
) { ) {
switch (overriteAllow) { switch (overwriteAllow) {
case OverrideType.ADD: { case OverrideType.ADD: {
const originalAllow = new Permissions(overwrites[index].allow) const originalAllow = new Permissions(overwrites[index].allow)
const newAllow = new Permissions(overwrite.allow) const newAllow = new Permissions(overwrite.allow)
@ -299,8 +303,8 @@ export class GuildChannel extends Channel {
: overwrite.allow?.toJSON() ?? overwrites[index].allow : overwrite.allow?.toJSON() ?? overwrites[index].allow
} }
if (overwrite.deny !== undefined && overriteDeny !== OverrideType.REPLACE) { if (overwrite.deny !== undefined && overwriteDeny !== OverrideType.REPLACE) {
switch (overriteDeny) { switch (overwriteDeny) {
case OverrideType.ADD: { case OverrideType.ADD: {
const originalDeny = new Permissions(overwrites[index].deny) const originalDeny = new Permissions(overwrites[index].deny)
const newDeny = new Permissions(overwrite.deny) const newDeny = new Permissions(overwrite.deny)
@ -327,8 +331,8 @@ export class GuildChannel extends Channel {
overwrite.id instanceof Role overwrite.id instanceof Role
? 0 ? 0
: overwrite.id instanceof Member : overwrite.id instanceof Member
? 1 ? 1
: overwrite.type : overwrite.type
if (type === undefined) { if (type === undefined) {
throw new Error('Overwrite type is undefined.') throw new Error('Overwrite type is undefined.')
} }

View File

@ -10,6 +10,9 @@ import { CHANNEL } from '../types/endpoint.ts'
import { GuildChannel } from './channel.ts' import { GuildChannel } from './channel.ts'
import { Guild } from './guild.ts' import { Guild } from './guild.ts'
import { VoiceState } from './voiceState.ts' import { VoiceState } from './voiceState.ts'
import { GuildChannelVoiceStatesManager } from '../managers/guildChannelVoiceStates.ts'
import { User } from './user.ts'
import { Member } from './member.ts'
export interface VoiceServerData extends VoiceServerUpdateData { export interface VoiceServerData extends VoiceServerUpdateData {
sessionID: string sessionID: string
@ -18,6 +21,11 @@ export interface VoiceServerData extends VoiceServerUpdateData {
export class VoiceChannel extends GuildChannel { export class VoiceChannel extends GuildChannel {
bitrate: string bitrate: string
userLimit: number userLimit: number
voiceStates = new GuildChannelVoiceStatesManager(
this.client,
this.guild.voiceStates,
this
)
constructor(client: Client, data: GuildVoiceChannelPayload, guild: Guild) { constructor(client: Client, data: GuildVoiceChannelPayload, guild: Guild) {
super(client, data, guild) super(client, data, guild)
@ -107,4 +115,25 @@ export class VoiceChannel extends GuildChannel {
return new VoiceChannel(this.client, resp, this.guild) return new VoiceChannel(this.client, resp, this.guild)
} }
async disconnectMember(
member: User | Member | string
): Promise<Member | undefined> {
const memberID = typeof member === 'string' ? member : member.id
const memberVoiceState = await this.voiceStates.get(memberID)
return memberVoiceState?.disconnect()
}
async disconnectAll(): Promise<Member[]> {
const members: Member[] = []
for await (const memberVoiceState of this.voiceStates) {
const member = await memberVoiceState.disconnect()
if (member !== undefined) {
members.push(member)
}
}
return members
}
} }

View File

@ -5,6 +5,7 @@ import { MemberPayload } from '../types/guild.ts'
import { Permissions } from '../utils/permissions.ts' import { Permissions } from '../utils/permissions.ts'
import { SnowflakeBase } from './base.ts' import { SnowflakeBase } from './base.ts'
import { Guild } from './guild.ts' import { Guild } from './guild.ts'
import { VoiceChannel } from './guildVoiceChannel.ts'
import { Role } from './role.ts' import { Role } from './role.ts'
import { User } from './user.ts' import { User } from './user.ts'
@ -13,6 +14,7 @@ export interface MemberData {
roles?: Array<Role | string> roles?: Array<Role | string>
deaf?: boolean deaf?: boolean
mute?: boolean mute?: boolean
channel?: string | VoiceChannel | null
} }
export class Member extends SnowflakeBase { export class Member extends SnowflakeBase {
@ -84,8 +86,11 @@ export class Member extends SnowflakeBase {
nick: data.nick, nick: data.nick,
roles: data.roles?.map((e) => (typeof e === 'string' ? e : e.id)), roles: data.roles?.map((e) => (typeof e === 'string' ? e : e.id)),
deaf: data.deaf, deaf: data.deaf,
mute: data.mute mute: data.mute,
channel_id:
data.channel instanceof VoiceChannel ? data.channel.id : data.channel
} }
const res = await this.client.rest.patch( const res = await this.client.rest.patch(
GUILD_MEMBER(this.guild.id, this.id), GUILD_MEMBER(this.guild.id, this.id),
payload, payload,
@ -125,7 +130,7 @@ export class Member extends SnowflakeBase {
*/ */
async setMute(mute?: boolean): Promise<Member> { async setMute(mute?: boolean): Promise<Member> {
return await this.edit({ return await this.edit({
mute: mute === undefined ? false : mute mute: mute ?? false
}) })
} }
@ -135,7 +140,28 @@ export class Member extends SnowflakeBase {
*/ */
async setDeaf(deaf?: boolean): Promise<Member> { async setDeaf(deaf?: boolean): Promise<Member> {
return await this.edit({ return await this.edit({
deaf: deaf === undefined ? false : deaf deaf: deaf ?? false
})
}
/**
* Moves a Member to another VC
* @param channel Channel to move(null or undefined to disconnect)
*/
async moveVoiceChannel(
channel?: string | VoiceChannel | null
): Promise<Member> {
return await this.edit({
channel: channel ?? null
})
}
/**
* Disconnects a Member from connected VC
*/
async disconnectVoice(): Promise<Member> {
return await this.edit({
channel: null
}) })
} }
@ -146,6 +172,13 @@ export class Member extends SnowflakeBase {
return await this.setMute(false) return await this.setMute(false)
} }
/**
* Undeafs the Member from VC.
*/
async undeaf(): Promise<Member> {
return await this.setDeaf(false)
}
/** /**
* Kicks the member. * Kicks the member.
*/ */

View File

@ -1,4 +1,5 @@
import { Client } from '../models/client.ts' import { Client } from '../models/client.ts'
import { ChannelTypes } from '../types/channel.ts'
import { VoiceStatePayload } from '../types/voice.ts' import { VoiceStatePayload } from '../types/voice.ts'
import { Base } from './base.ts' import { Base } from './base.ts'
import { Guild } from './guild.ts' import { Guild } from './guild.ts'
@ -52,12 +53,81 @@ export class VoiceState extends Base {
this.deaf = data.deaf ?? this.deaf this.deaf = data.deaf ?? this.deaf
this.channelID = data.channel_id ?? this.channelID this.channelID = data.channel_id ?? this.channelID
this.mute = data.mute ?? this.mute this.mute = data.mute ?? this.mute
this.deaf = data.self_deaf ?? this.deaf
this.mute = data.self_mute ?? this.mute
this.selfDeaf = data.self_deaf ?? this.selfDeaf this.selfDeaf = data.self_deaf ?? this.selfDeaf
this.selfMute = data.self_mute ?? this.selfMute this.selfMute = data.self_mute ?? this.selfMute
this.stream = data.self_stream ?? this.stream this.stream = data.self_stream ?? this.stream
this.video = data.self_video ?? this.video this.video = data.self_video ?? this.video
this.suppress = data.suppress ?? this.suppress this.suppress = data.suppress ?? this.suppress
} }
/**
* Disconnects a Member from connected VC
*/
async disconnect(): Promise<Member | undefined> {
const result = this.member?.disconnectVoice()
if (result !== undefined) {
this.channelID = null
this.channel = null
}
return result
}
/**
* Moves a Member to another VC
* @param channel Channel to move(null or undefined to disconnect)
*/
async moveChannel(
channel?: string | VoiceChannel | null
): Promise<Member | undefined> {
const result = this.member?.moveVoiceChannel(channel)
if (result !== undefined) {
let channelFetched: VoiceChannel | null
let channelID: string | null
if (typeof channel === 'string') {
channelID = channel
const channelCached = await this.guild?.channels.fetch(channel)
if (channelCached?.type === ChannelTypes.GUILD_VOICE) {
channelFetched = channelCached as VoiceChannel
} else {
throw new Error(`Channel ${channel} is not a VoiceChannel.`)
}
} else {
channelID = channel?.id ?? null
channelFetched = channel ?? null
}
this.channelID = channelID
this.channel = channelFetched
}
return result
}
/**
* Sets a Member mute in VC
* @param mute Value to set
*/
async setMute(mute?: boolean): Promise<Member | undefined> {
return this.member?.setMute(mute)
}
/**
* Sets a Member deaf in VC
* @param deaf Value to set
*/
async setDeaf(deaf?: boolean): Promise<Member | undefined> {
return this.member?.setDeaf(deaf)
}
/**
* Unmutes the Member from VC.
*/
async unmute(): Promise<Member | undefined> {
return await this.setMute(false)
}
/**
* Undeafs the Member from VC.
*/
async undeaf(): Promise<Member | undefined> {
return await this.setDeaf(false)
}
} }

View File

@ -139,7 +139,7 @@ client.on('messageReactionRemoveAll', (message) => {
// client.on('raw', (evt: string) => console.log(`EVENT: ${evt}`)) // client.on('raw', (evt: string) => console.log(`EVENT: ${evt}`))
const files = Deno.readDirSync('./src/test/cmds') const files = Deno.readDirSync('cmds')
for (const file of files) { for (const file of files) {
const module = await import(`./cmds/${file.name}`) const module = await import(`./cmds/${file.name}`)

View File

@ -0,0 +1,23 @@
import { Command, VoiceChannel } from '../../../mod.ts'
import { CommandContext } from '../../models/command.ts'
import { ChannelTypes } from '../../types/channel.ts'
export default class KickFromSpecificVoiceCommand extends Command {
name = 'kickFromSpecificVoice'
async execute(ctx: CommandContext): Promise<void> {
if (ctx.guild !== undefined) {
const channel = await ctx.guild.channels.get('YOUR VOICE CHANNEL ID')
if (channel === undefined || channel.type !== ChannelTypes.GUILD_VOICE) {
ctx.channel.send('The channel is either not a voice or not available.')
return
}
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
const members = await (channel as VoiceChannel).disconnectAll()
members.forEach((member) => {
ctx.channel.send(`Kicked member ${member.id}`)
})
}
}
}

View File

@ -0,0 +1,20 @@
import { Command } from '../../../mod.ts'
import { CommandContext } from '../../models/command.ts'
export default class KickFromVoiceCommand extends Command {
name = 'kickFromVoice'
async execute(ctx: CommandContext): Promise<void> {
if (ctx.guild !== undefined) {
const voiceStates = await ctx.guild.voiceStates.array()
if (voiceStates !== undefined) {
voiceStates.forEach(async (voiceState) => {
const member = await voiceState.disconnect()
if (member !== undefined) {
ctx.channel.send(`Kicked member ${member.id}`)
}
})
}
}
}
}

View File

@ -5,6 +5,7 @@ export default class PingCommand extends Command {
name = 'ping' name = 'ping'
execute(ctx: CommandContext): void { execute(ctx: CommandContext): void {
console.log(ctx.args, ctx.argString)
ctx.message.reply(`Pong! Latency: ${ctx.client.ping}ms`) ctx.message.reply(`Pong! Latency: ${ctx.client.ping}ms`)
} }
} }

View File

@ -74,12 +74,12 @@ client.on('messageCreate', async (msg: Message) => {
const guilds = await msg.client.guilds.collection() const guilds = await msg.client.guilds.collection()
msg.channel.send( msg.channel.send(
'Guild List:\n' + 'Guild List:\n' +
(guilds (guilds
.array() .array()
.map((c: Guild, i: number) => { .map((c: Guild, i: number) => {
return `${i + 1}. ${c.name} - ${c.memberCount} members` return `${i + 1}. ${c.name} - ${c.memberCount} members`
}) })
.join('\n') as string) .join('\n') as string)
) )
} else if (msg.content === '!roles') { } else if (msg.content === '!roles') {
const col = await msg.guild?.roles.collection() const col = await msg.guild?.roles.collection()
@ -236,7 +236,7 @@ client.on('messageCreate', async (msg: Message) => {
allow: Permissions.DEFAULT.toString() allow: Permissions.DEFAULT.toString()
}, },
{ {
overriteAllow: OverrideType.REMOVE allow: OverrideType.REMOVE
} }
) )
msg.channel.send(`Done!`) msg.channel.send(`Done!`)

View File

@ -29,7 +29,7 @@ export interface GuildChannelPayload extends ChannelPayload {
export interface GuildTextBasedChannelPayload export interface GuildTextBasedChannelPayload
extends TextChannelPayload, extends TextChannelPayload,
GuildChannelPayload { GuildChannelPayload {
topic?: string topic?: string
} }
@ -37,7 +37,7 @@ export interface GuildTextChannelPayload extends GuildTextBasedChannelPayload {
rate_limit_per_user: number rate_limit_per_user: number
} }
export interface GuildNewsChannelPayload extends GuildTextBasedChannelPayload {} export interface GuildNewsChannelPayload extends GuildTextBasedChannelPayload { }
export interface GuildVoiceChannelPayload extends GuildChannelPayload { export interface GuildVoiceChannelPayload extends GuildChannelPayload {
bitrate: string bitrate: string
@ -56,7 +56,7 @@ export interface GroupDMChannelPayload extends DMChannelPayload {
export interface GuildCategoryChannelPayload export interface GuildCategoryChannelPayload
extends ChannelPayload, extends ChannelPayload,
GuildChannelPayload {} GuildChannelPayload { }
export interface ModifyChannelPayload { export interface ModifyChannelPayload {
name?: string name?: string
@ -67,7 +67,7 @@ export interface ModifyChannelPayload {
} }
export interface ModifyGuildCategoryChannelPayload export interface ModifyGuildCategoryChannelPayload
extends ModifyChannelPayload {} extends ModifyChannelPayload { }
export interface ModifyGuildTextBasedChannelPayload export interface ModifyGuildTextBasedChannelPayload
extends ModifyChannelPayload { extends ModifyChannelPayload {
@ -81,7 +81,7 @@ export interface ModifyGuildTextChannelPayload
} }
export interface ModifyGuildNewsChannelPayload export interface ModifyGuildNewsChannelPayload
extends ModifyGuildTextBasedChannelPayload {} extends ModifyGuildTextBasedChannelPayload { }
export interface ModifyVoiceChannelPayload extends ModifyChannelPayload { export interface ModifyVoiceChannelPayload extends ModifyChannelPayload {
bitrate?: number | null bitrate?: number | null
@ -96,7 +96,7 @@ export interface ModifyChannelOption {
nsfw?: boolean | null nsfw?: boolean | null
} }
export interface ModifyGuildCategoryChannelOption extends ModifyChannelOption {} export interface ModifyGuildCategoryChannelOption extends ModifyChannelOption { }
export interface ModifyGuildTextBasedChannelOption extends ModifyChannelOption { export interface ModifyGuildTextBasedChannelOption extends ModifyChannelOption {
type?: number type?: number
@ -109,7 +109,7 @@ export interface ModifyGuildTextChannelOption
} }
export interface ModifyGuildNewsChannelOption export interface ModifyGuildNewsChannelOption
extends ModifyGuildTextBasedChannelOption {} extends ModifyGuildTextBasedChannelOption { }
export interface ModifyVoiceChannelOption extends ModifyChannelOption { export interface ModifyVoiceChannelOption extends ModifyChannelOption {
bitrate?: number | null bitrate?: number | null