This commit is contained in:
DjDeveloperr 2021-05-06 09:21:41 +05:30
parent 7570dfffbe
commit 90e4bd6ccf
7 changed files with 173 additions and 76 deletions

View file

@ -1,4 +1,4 @@
import { Interaction } from './src/structures/interactions.ts '
import { Interaction } from './src/structures/interactions.ts'
import {
SlashCommandsManager,
SlashClient,

View file

@ -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
View 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)
}
}

View file

@ -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)
}
}

View file

@ -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

View file

@ -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
View 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!'))