fix
This commit is contained in:
parent
7570dfffbe
commit
90e4bd6ccf
7 changed files with 173 additions and 76 deletions
|
@ -1,4 +1,4 @@
|
|||
import { Interaction } from './src/structures/interactions.ts '
|
||||
import { Interaction } from './src/structures/interactions.ts'
|
||||
import {
|
||||
SlashCommandsManager,
|
||||
SlashClient,
|
||||
|
|
|
@ -23,6 +23,7 @@ import type { VoiceRegion } from '../types/voice.ts'
|
|||
import { fetchAuto } from '../../deps.ts'
|
||||
import type { DMChannel } from '../structures/dmChannel.ts'
|
||||
import { Template } from '../structures/template.ts'
|
||||
import { VoiceManager } from './voice.ts'
|
||||
|
||||
/** OS related properties sent with Gateway Identify */
|
||||
export interface ClientProperties {
|
||||
|
@ -119,6 +120,9 @@ export class Client extends HarmonyEventEmitter<ClientEvents> {
|
|||
/** Whether to fetch Gateway info or not */
|
||||
fetchGatewayInfo: boolean = true
|
||||
|
||||
/** Voice Connections Manager */
|
||||
readonly voice = new VoiceManager(this)
|
||||
|
||||
/** Users Manager, containing all Users cached */
|
||||
readonly users: UsersManager = new UsersManager(this)
|
||||
/** Guilds Manager, providing cache & API interface to Guilds */
|
||||
|
|
116
src/client/voice.ts
Normal file
116
src/client/voice.ts
Normal file
|
@ -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)
|
||||
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) {
|
||||
gateway.client.emit(
|
||||
'voiceStateAdd',
|
||||
(newVoiceState as unknown) as VoiceState
|
||||
)
|
||||
gateway.client.emit('voiceStateAdd', newVoiceState)
|
||||
} else {
|
||||
gateway.client.emit(
|
||||
'voiceStateUpdate',
|
||||
voiceState,
|
||||
(newVoiceState as unknown) as VoiceState
|
||||
)
|
||||
gateway.client.emit('voiceStateUpdate', voiceState, newVoiceState)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -371,13 +371,13 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
|||
: channel?.id,
|
||||
self_mute:
|
||||
channel === undefined
|
||||
? undefined
|
||||
? false
|
||||
: voiceOptions.mute === undefined
|
||||
? false
|
||||
: voiceOptions.mute,
|
||||
self_deaf:
|
||||
channel === undefined
|
||||
? undefined
|
||||
? false
|
||||
: voiceOptions.deaf === undefined
|
||||
? false
|
||||
: 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 {
|
||||
GuildVoiceChannelPayload,
|
||||
|
@ -9,14 +7,13 @@ import type {
|
|||
import { CHANNEL } from '../types/endpoint.ts'
|
||||
import { GuildChannel } from './channel.ts'
|
||||
import type { Guild } from './guild.ts'
|
||||
import type { VoiceState } from './voiceState.ts'
|
||||
import { GuildChannelVoiceStatesManager } from '../managers/guildChannelVoiceStates.ts'
|
||||
import type { User } from './user.ts'
|
||||
import type { Member } from './member.ts'
|
||||
|
||||
export interface VoiceServerData extends VoiceServerUpdateData {
|
||||
sessionID: string
|
||||
}
|
||||
import type {
|
||||
VoiceChannelJoinOptions,
|
||||
VoiceServerData
|
||||
} from '../client/voice.ts'
|
||||
|
||||
export class VoiceChannel extends GuildChannel {
|
||||
bitrate: string
|
||||
|
@ -34,65 +31,13 @@ export class VoiceChannel extends GuildChannel {
|
|||
}
|
||||
|
||||
/** Join the Voice Channel */
|
||||
async join(
|
||||
options?: VoiceStateOptions & { onlyJoin?: boolean }
|
||||
): 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)
|
||||
})
|
||||
async join(options?: VoiceChannelJoinOptions): Promise<VoiceServerData> {
|
||||
return this.client.voice.join(this.id, options)
|
||||
}
|
||||
|
||||
/** Leave the Voice Channel */
|
||||
leave(): void {
|
||||
this.client.shards
|
||||
.get(this.guild.shardID)
|
||||
?.updateVoiceState(this.guild.id, undefined)
|
||||
async leave(): Promise<void> {
|
||||
return this.client.voice.leave(this.guild)
|
||||
}
|
||||
|
||||
readFromData(data: GuildVoiceChannelPayload): void {
|
||||
|
@ -116,6 +61,14 @@ export class VoiceChannel extends GuildChannel {
|
|||
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(
|
||||
member: User | Member | string
|
||||
): Promise<Member | undefined> {
|
||||
|
|
26
test/vc.ts
Normal file
26
test/vc.ts
Normal file
|
@ -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 a new issue