fix
This commit is contained in:
parent
7570dfffbe
commit
90e4bd6ccf
|
@ -23,6 +23,7 @@ import type { VoiceRegion } from '../types/voice.ts'
|
||||||
import { fetchAuto } from '../../deps.ts'
|
import { fetchAuto } from '../../deps.ts'
|
||||||
import type { DMChannel } from '../structures/dmChannel.ts'
|
import type { DMChannel } from '../structures/dmChannel.ts'
|
||||||
import { Template } from '../structures/template.ts'
|
import { Template } from '../structures/template.ts'
|
||||||
|
import { VoiceManager } from './voice.ts'
|
||||||
|
|
||||||
/** OS related properties sent with Gateway Identify */
|
/** OS related properties sent with Gateway Identify */
|
||||||
export interface ClientProperties {
|
export interface ClientProperties {
|
||||||
|
@ -119,6 +120,9 @@ export class Client extends HarmonyEventEmitter<ClientEvents> {
|
||||||
/** Whether to fetch Gateway info or not */
|
/** Whether to fetch Gateway info or not */
|
||||||
fetchGatewayInfo: boolean = true
|
fetchGatewayInfo: boolean = true
|
||||||
|
|
||||||
|
/** Voice Connections Manager */
|
||||||
|
readonly voice = new VoiceManager(this)
|
||||||
|
|
||||||
/** Users Manager, containing all Users cached */
|
/** Users Manager, containing all Users cached */
|
||||||
readonly users: UsersManager = new UsersManager(this)
|
readonly users: UsersManager = new UsersManager(this)
|
||||||
/** Guilds Manager, providing cache & API interface to Guilds */
|
/** Guilds Manager, providing cache & API interface to Guilds */
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
import type { VoiceServerUpdateData } from '../gateway/handlers/mod.ts'
|
||||||
|
import type { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||||
|
import type { VoiceStateOptions } from '../gateway/mod.ts'
|
||||||
|
import { VoiceState } from '../structures/voiceState.ts'
|
||||||
|
import { ChannelTypes } from '../types/channel.ts'
|
||||||
|
import type { Guild } from '../structures/guild.ts'
|
||||||
|
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||||
|
import type { Client } from './client.ts'
|
||||||
|
|
||||||
|
export interface VoiceServerData extends VoiceServerUpdateData {
|
||||||
|
userID: string
|
||||||
|
sessionID: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VoiceChannelJoinOptions extends VoiceStateOptions {
|
||||||
|
timeout?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export class VoiceManager extends HarmonyEventEmitter<{
|
||||||
|
voiceStateUpdate: [VoiceState]
|
||||||
|
}> {
|
||||||
|
#pending = new Map<string, [number, CallableFunction]>()
|
||||||
|
|
||||||
|
readonly client!: Client
|
||||||
|
|
||||||
|
constructor(client: Client) {
|
||||||
|
super()
|
||||||
|
Object.defineProperty(this, 'client', {
|
||||||
|
value: client,
|
||||||
|
enumerable: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async join(
|
||||||
|
channel: string | VoiceChannel,
|
||||||
|
options?: VoiceChannelJoinOptions
|
||||||
|
): Promise<VoiceServerData> {
|
||||||
|
const id = typeof channel === 'string' ? channel : channel.id
|
||||||
|
const chan = await this.client.channels.get<VoiceChannel>(id)
|
||||||
|
if (chan === undefined) throw new Error('Voice Channel not cached')
|
||||||
|
if (
|
||||||
|
chan.type !== ChannelTypes.GUILD_VOICE &&
|
||||||
|
chan.type !== ChannelTypes.GUILD_STAGE_VOICE
|
||||||
|
)
|
||||||
|
throw new Error('Cannot join non-voice channel')
|
||||||
|
|
||||||
|
const pending = this.#pending.get(chan.guild.id)
|
||||||
|
if (pending !== undefined) {
|
||||||
|
clearTimeout(pending[0])
|
||||||
|
pending[1](new Error('Voice Connection timed out'))
|
||||||
|
this.#pending.delete(chan.guild.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await new Promise((resolve, reject) => {
|
||||||
|
let vcdata: VoiceServerData
|
||||||
|
let done = 0
|
||||||
|
|
||||||
|
const onVoiceStateAdd = (state: VoiceState): void => {
|
||||||
|
if (state.user.id !== this.client.user?.id) return
|
||||||
|
if (state.channel?.id !== id) return
|
||||||
|
this.client.off('voiceStateAdd', onVoiceStateAdd)
|
||||||
|
done++
|
||||||
|
vcdata = vcdata ?? {}
|
||||||
|
vcdata.sessionID = state.sessionID
|
||||||
|
vcdata.userID = state.user.id
|
||||||
|
if (done >= 2) {
|
||||||
|
this.#pending.delete(chan.guild.id)
|
||||||
|
resolve(vcdata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onVoiceServerUpdate = (data: VoiceServerUpdateData): void => {
|
||||||
|
if (data.guild.id !== chan.guild.id) return
|
||||||
|
vcdata = Object.assign(vcdata ?? {}, data)
|
||||||
|
this.client.off('voiceServerUpdate', onVoiceServerUpdate)
|
||||||
|
done++
|
||||||
|
if (done >= 2) {
|
||||||
|
this.#pending.delete(chan.guild.id)
|
||||||
|
resolve(vcdata)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.client.shards
|
||||||
|
.get(chan.guild.shardID)!
|
||||||
|
.updateVoiceState(chan.guild.id, chan.id, options)
|
||||||
|
|
||||||
|
this.on('voiceStateUpdate', onVoiceStateAdd)
|
||||||
|
this.client.on('voiceServerUpdate', onVoiceServerUpdate)
|
||||||
|
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
if (done < 2) {
|
||||||
|
this.client.off('voiceServerUpdate', onVoiceServerUpdate)
|
||||||
|
this.client.off('voiceStateAdd', onVoiceStateAdd)
|
||||||
|
reject(
|
||||||
|
new Error(
|
||||||
|
"Connection timed out - couldn't connect to Voice Channel"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}, options?.timeout ?? 1000 * 30)
|
||||||
|
|
||||||
|
this.#pending.set(chan.guild.id, [timer, reject])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async leave(guildOrID: Guild | string): Promise<void> {
|
||||||
|
const id = typeof guildOrID === 'string' ? guildOrID : guildOrID.id
|
||||||
|
const guild = await this.client.guilds.get(id)
|
||||||
|
if (guild === undefined) throw new Error('Guild not cached')
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
|
||||||
|
const vcs = await guild.voiceStates.get(this.client.user?.id!)
|
||||||
|
if (vcs === undefined) throw new Error('Not in Voice Channel')
|
||||||
|
|
||||||
|
this.client.shards.get(guild.shardID)!.updateVoiceState(guild, undefined)
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,17 +33,15 @@ export const voiceStateUpdate: GatewayEventHandler = async (
|
||||||
}
|
}
|
||||||
|
|
||||||
await guild.voiceStates.set(d.user_id, d)
|
await guild.voiceStates.set(d.user_id, d)
|
||||||
const newVoiceState = await guild.voiceStates.get(d.user_id)
|
const newVoiceState = (await guild.voiceStates.get(d.user_id))!
|
||||||
|
|
||||||
|
if (d.user_id === gateway.client.user!.id) {
|
||||||
|
gateway.client.voice.emit('voiceStateUpdate', newVoiceState)
|
||||||
|
}
|
||||||
|
|
||||||
if (voiceState === undefined) {
|
if (voiceState === undefined) {
|
||||||
gateway.client.emit(
|
gateway.client.emit('voiceStateAdd', newVoiceState)
|
||||||
'voiceStateAdd',
|
|
||||||
(newVoiceState as unknown) as VoiceState
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
gateway.client.emit(
|
gateway.client.emit('voiceStateUpdate', voiceState, newVoiceState)
|
||||||
'voiceStateUpdate',
|
|
||||||
voiceState,
|
|
||||||
(newVoiceState as unknown) as VoiceState
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -371,13 +371,13 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
: channel?.id,
|
: channel?.id,
|
||||||
self_mute:
|
self_mute:
|
||||||
channel === undefined
|
channel === undefined
|
||||||
? undefined
|
? false
|
||||||
: voiceOptions.mute === undefined
|
: voiceOptions.mute === undefined
|
||||||
? false
|
? false
|
||||||
: voiceOptions.mute,
|
: voiceOptions.mute,
|
||||||
self_deaf:
|
self_deaf:
|
||||||
channel === undefined
|
channel === undefined
|
||||||
? undefined
|
? false
|
||||||
: voiceOptions.deaf === undefined
|
: voiceOptions.deaf === undefined
|
||||||
? false
|
? false
|
||||||
: voiceOptions.deaf
|
: voiceOptions.deaf
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import type { VoiceServerUpdateData } from '../gateway/handlers/mod.ts'
|
|
||||||
import type { VoiceStateOptions } from '../gateway/mod.ts'
|
|
||||||
import type { Client } from '../client/mod.ts'
|
import type { Client } from '../client/mod.ts'
|
||||||
import type {
|
import type {
|
||||||
GuildVoiceChannelPayload,
|
GuildVoiceChannelPayload,
|
||||||
|
@ -9,14 +7,13 @@ import type {
|
||||||
import { CHANNEL } from '../types/endpoint.ts'
|
import { CHANNEL } from '../types/endpoint.ts'
|
||||||
import { GuildChannel } from './channel.ts'
|
import { GuildChannel } from './channel.ts'
|
||||||
import type { Guild } from './guild.ts'
|
import type { Guild } from './guild.ts'
|
||||||
import type { VoiceState } from './voiceState.ts'
|
|
||||||
import { GuildChannelVoiceStatesManager } from '../managers/guildChannelVoiceStates.ts'
|
import { GuildChannelVoiceStatesManager } from '../managers/guildChannelVoiceStates.ts'
|
||||||
import type { User } from './user.ts'
|
import type { User } from './user.ts'
|
||||||
import type { Member } from './member.ts'
|
import type { Member } from './member.ts'
|
||||||
|
import type {
|
||||||
export interface VoiceServerData extends VoiceServerUpdateData {
|
VoiceChannelJoinOptions,
|
||||||
sessionID: string
|
VoiceServerData
|
||||||
}
|
} from '../client/voice.ts'
|
||||||
|
|
||||||
export class VoiceChannel extends GuildChannel {
|
export class VoiceChannel extends GuildChannel {
|
||||||
bitrate: string
|
bitrate: string
|
||||||
|
@ -34,65 +31,13 @@ export class VoiceChannel extends GuildChannel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Join the Voice Channel */
|
/** Join the Voice Channel */
|
||||||
async join(
|
async join(options?: VoiceChannelJoinOptions): Promise<VoiceServerData> {
|
||||||
options?: VoiceStateOptions & { onlyJoin?: boolean }
|
return this.client.voice.join(this.id, options)
|
||||||
): Promise<VoiceServerData> {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
let vcdata: VoiceServerData
|
|
||||||
let sessionID: string
|
|
||||||
let done = 0
|
|
||||||
|
|
||||||
const onVoiceStateAdd = (state: VoiceState): void => {
|
|
||||||
if (state.user.id !== this.client.user?.id) return
|
|
||||||
if (state.channel?.id !== this.id) return
|
|
||||||
this.client.off('voiceStateAdd', onVoiceStateAdd)
|
|
||||||
done++
|
|
||||||
sessionID = state.sessionID
|
|
||||||
if (done >= 2) {
|
|
||||||
vcdata.sessionID = sessionID
|
|
||||||
if (options?.onlyJoin !== true) {
|
|
||||||
}
|
|
||||||
resolve(vcdata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onVoiceServerUpdate = (data: VoiceServerUpdateData): void => {
|
|
||||||
if (data.guild.id !== this.guild.id) return
|
|
||||||
vcdata = (data as unknown) as VoiceServerData
|
|
||||||
this.client.off('voiceServerUpdate', onVoiceServerUpdate)
|
|
||||||
done++
|
|
||||||
if (done >= 2) {
|
|
||||||
vcdata.sessionID = sessionID
|
|
||||||
resolve(vcdata)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.client.shards
|
|
||||||
.get(this.guild.shardID)
|
|
||||||
?.updateVoiceState(this.guild.id, this.id, options)
|
|
||||||
|
|
||||||
this.client.on('voiceStateAdd', onVoiceStateAdd)
|
|
||||||
this.client.on('voiceServerUpdate', onVoiceServerUpdate)
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
if (done < 2) {
|
|
||||||
this.client.off('voiceServerUpdate', onVoiceServerUpdate)
|
|
||||||
this.client.off('voiceStateAdd', onVoiceStateAdd)
|
|
||||||
reject(
|
|
||||||
new Error(
|
|
||||||
"Connection timed out - couldn't connect to Voice Channel"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, 1000 * 60)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Leave the Voice Channel */
|
/** Leave the Voice Channel */
|
||||||
leave(): void {
|
async leave(): Promise<void> {
|
||||||
this.client.shards
|
return this.client.voice.leave(this.guild)
|
||||||
.get(this.guild.shardID)
|
|
||||||
?.updateVoiceState(this.guild.id, undefined)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readFromData(data: GuildVoiceChannelPayload): void {
|
readFromData(data: GuildVoiceChannelPayload): void {
|
||||||
|
@ -116,6 +61,14 @@ export class VoiceChannel extends GuildChannel {
|
||||||
return new VoiceChannel(this.client, resp, this.guild)
|
return new VoiceChannel(this.client, resp, this.guild)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setBitrate(rate: number | undefined): Promise<VoiceChannel> {
|
||||||
|
return await this.edit({ bitrate: rate })
|
||||||
|
}
|
||||||
|
|
||||||
|
async setUserLimit(limit: number | undefined): Promise<VoiceChannel> {
|
||||||
|
return await this.edit({ userLimit: limit })
|
||||||
|
}
|
||||||
|
|
||||||
async disconnectMember(
|
async disconnectMember(
|
||||||
member: User | Member | string
|
member: User | Member | string
|
||||||
): Promise<Member | undefined> {
|
): Promise<Member | undefined> {
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import * as discord from '../mod.ts'
|
||||||
|
import { TOKEN } from './config.ts'
|
||||||
|
|
||||||
|
const client = new discord.Client({
|
||||||
|
token: TOKEN,
|
||||||
|
intents: ['GUILDS', 'GUILD_VOICE_STATES', 'GUILD_MESSAGES']
|
||||||
|
})
|
||||||
|
|
||||||
|
client.on('messageCreate', async (msg) => {
|
||||||
|
if (msg.author.bot === true || msg.guild === undefined) return
|
||||||
|
|
||||||
|
if (msg.content === '!join') {
|
||||||
|
const vs = await msg.guild.voiceStates.get(msg.author.id)
|
||||||
|
if (vs === undefined) return msg.reply("You're not in Voice Channel!")
|
||||||
|
const data = await vs.channel!.join()
|
||||||
|
console.log(data)
|
||||||
|
msg.reply('Joined voice channel.')
|
||||||
|
} else if (msg.content === '!leave') {
|
||||||
|
const vs = await msg.guild.voiceStates.get(msg.client.user!.id!)
|
||||||
|
if (vs === undefined) return msg.reply("I'm not in Voice Channel!")
|
||||||
|
await vs.channel!.leave()
|
||||||
|
msg.reply('Left voice channel.')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
client.connect().then(() => console.log('Ready!'))
|
Loading…
Reference in New Issue