Merge pull request #148 from DjDeveloperr/vc-fix
refactor: voice join/leave
This commit is contained in:
commit
a25aef6400
|
@ -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 {
|
||||
|
@ -38,7 +39,7 @@ export interface ClientOptions {
|
|||
/** Token of the Bot/User */
|
||||
token?: string
|
||||
/** Gateway Intents */
|
||||
intents?: GatewayIntents[]
|
||||
intents?: Array<GatewayIntents | keyof typeof GatewayIntents>
|
||||
/** Cache Adapter to use, defaults to Collections one */
|
||||
cache?: ICacheAdapter
|
||||
/** Force New Session and don't use cached Session (by persistent caching) */
|
||||
|
@ -77,10 +78,29 @@ export class Client extends HarmonyEventEmitter<ClientEvents> {
|
|||
rest: RESTManager
|
||||
/** User which Client logs in to, undefined until logs in */
|
||||
user?: User
|
||||
|
||||
#token?: string
|
||||
|
||||
/** Token of the Bot/User */
|
||||
token?: string
|
||||
get token(): string | undefined {
|
||||
return this.#token
|
||||
}
|
||||
|
||||
set token(val: string | undefined) {
|
||||
this.#token = val
|
||||
}
|
||||
|
||||
/** Cache Adapter */
|
||||
cache: ICacheAdapter = new DefaultCacheAdapter()
|
||||
get cache(): ICacheAdapter {
|
||||
return this.#cache
|
||||
}
|
||||
|
||||
set cache(val: ICacheAdapter) {
|
||||
this.#cache = val
|
||||
}
|
||||
|
||||
#cache: ICacheAdapter = new DefaultCacheAdapter()
|
||||
|
||||
/** Gateway Intents */
|
||||
intents?: GatewayIntents[]
|
||||
/** Whether to force new session or not */
|
||||
|
@ -91,21 +111,26 @@ export class Client extends HarmonyEventEmitter<ClientEvents> {
|
|||
reactionCacheLifetime: number = 3600000
|
||||
/** Whether to fetch Uncached Message of Reaction or not? */
|
||||
fetchUncachedReactions: boolean = false
|
||||
|
||||
/** Client Properties */
|
||||
clientProperties: ClientProperties
|
||||
readonly clientProperties!: ClientProperties
|
||||
|
||||
/** Slash-Commands Management client */
|
||||
slash: SlashClient
|
||||
/** Whether to fetch Gateway info or not */
|
||||
fetchGatewayInfo: boolean = true
|
||||
|
||||
/** Voice Connections Manager */
|
||||
readonly voice = new VoiceManager(this)
|
||||
|
||||
/** Users Manager, containing all Users cached */
|
||||
users: UsersManager = new UsersManager(this)
|
||||
readonly users: UsersManager = new UsersManager(this)
|
||||
/** Guilds Manager, providing cache & API interface to Guilds */
|
||||
guilds: GuildManager = new GuildManager(this)
|
||||
readonly guilds: GuildManager = new GuildManager(this)
|
||||
/** Channels Manager, providing cache interface to Channels */
|
||||
channels: ChannelsManager = new ChannelsManager(this)
|
||||
readonly channels: ChannelsManager = new ChannelsManager(this)
|
||||
/** Channels Manager, providing cache interface to Channels */
|
||||
emojis: EmojisManager = new EmojisManager(this)
|
||||
readonly emojis: EmojisManager = new EmojisManager(this)
|
||||
|
||||
/** Last READY timestamp */
|
||||
upSince?: Date
|
||||
|
@ -146,7 +171,9 @@ export class Client extends HarmonyEventEmitter<ClientEvents> {
|
|||
super()
|
||||
this._id = options.id
|
||||
this.token = options.token
|
||||
this.intents = options.intents
|
||||
this.intents = options.intents?.map((e) =>
|
||||
typeof e === 'string' ? GatewayIntents[e] : e
|
||||
)
|
||||
this.shards = new ShardManager(this)
|
||||
this.forceNewSession = options.forceNewSession
|
||||
if (options.cache !== undefined) this.cache = options.cache
|
||||
|
@ -172,14 +199,17 @@ export class Client extends HarmonyEventEmitter<ClientEvents> {
|
|||
;(this as any)._decoratedEvents = undefined
|
||||
}
|
||||
|
||||
this.clientProperties =
|
||||
options.clientProperties === undefined
|
||||
? {
|
||||
os: Deno.build.os,
|
||||
browser: 'harmony',
|
||||
device: 'harmony'
|
||||
}
|
||||
: options.clientProperties
|
||||
Object.defineProperty(this, 'clientProperties', {
|
||||
value:
|
||||
options.clientProperties === undefined
|
||||
? {
|
||||
os: Deno.build.os,
|
||||
browser: 'harmony',
|
||||
device: 'harmony'
|
||||
}
|
||||
: options.clientProperties,
|
||||
enumerable: false
|
||||
})
|
||||
|
||||
if (options.shard !== undefined) this.shard = options.shard
|
||||
if (options.shardCount !== undefined) this.shardCount = options.shardCount
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
|||
lastPingTimestamp = 0
|
||||
sessionID?: string
|
||||
private heartbeatServerResponded = false
|
||||
client: Client
|
||||
client!: Client
|
||||
cache: GatewayCache
|
||||
private timedIdentify: number | null = null
|
||||
shards?: number[]
|
||||
|
@ -70,7 +70,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
|||
|
||||
constructor(client: Client, shards?: number[]) {
|
||||
super()
|
||||
this.client = client
|
||||
Object.defineProperty(this, 'client', { value: client, enumerable: false })
|
||||
this.cache = new GatewayCache(client)
|
||||
this.shards = shards
|
||||
}
|
||||
|
@ -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
|
||||
|
|
|
@ -50,11 +50,21 @@ export type SlashClientEvents = {
|
|||
export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
|
||||
id: string | (() => string)
|
||||
client?: Client
|
||||
token?: string
|
||||
|
||||
#token?: string
|
||||
|
||||
get token(): string | undefined {
|
||||
return this.#token
|
||||
}
|
||||
|
||||
set token(val: string | undefined) {
|
||||
this.#token = val
|
||||
}
|
||||
|
||||
enabled: boolean = true
|
||||
commands: SlashCommandsManager
|
||||
handlers: SlashCommandHandler[] = []
|
||||
rest: RESTManager
|
||||
readonly rest!: RESTManager
|
||||
modules: SlashModule[] = []
|
||||
publicKey?: string
|
||||
|
||||
|
@ -65,7 +75,14 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
|
|||
if (id === undefined)
|
||||
throw new Error('ID could not be found. Pass at least client or token')
|
||||
this.id = id
|
||||
this.client = options.client
|
||||
|
||||
if (options.client !== undefined) {
|
||||
Object.defineProperty(this, 'client', {
|
||||
value: options.client,
|
||||
enumerable: false
|
||||
})
|
||||
}
|
||||
|
||||
this.token = options.token
|
||||
this.publicKey = options.publicKey
|
||||
|
||||
|
@ -88,14 +105,17 @@ export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
|
|||
})
|
||||
}
|
||||
|
||||
this.rest =
|
||||
options.client === undefined
|
||||
? options.rest === undefined
|
||||
? new RESTManager({
|
||||
token: this.token
|
||||
})
|
||||
: options.rest
|
||||
: options.client.rest
|
||||
Object.defineProperty(this, 'rest', {
|
||||
value:
|
||||
options.client === undefined
|
||||
? options.rest === undefined
|
||||
? new RESTManager({
|
||||
token: this.token
|
||||
})
|
||||
: options.rest
|
||||
: options.client.rest,
|
||||
enumerable: false
|
||||
})
|
||||
|
||||
this.client?.on(
|
||||
'interactionCreate',
|
||||
|
|
|
@ -198,12 +198,15 @@ export class SlashBuilder {
|
|||
|
||||
/** Manages Slash Commands, allows fetching/modifying/deleting/creating Slash Commands. */
|
||||
export class SlashCommandsManager {
|
||||
slash: SlashClient
|
||||
rest: RESTManager
|
||||
readonly slash!: SlashClient
|
||||
readonly rest!: RESTManager
|
||||
|
||||
constructor(client: SlashClient) {
|
||||
this.slash = client
|
||||
this.rest = client.rest
|
||||
Object.defineProperty(this, 'slash', { value: client, enumerable: false })
|
||||
Object.defineProperty(this, 'rest', {
|
||||
enumerable: false,
|
||||
value: client.rest
|
||||
})
|
||||
}
|
||||
|
||||
/** Get all Global Slash Commands */
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { Client } from '../client/mod.ts'
|
||||
import { Base } from '../structures/base.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
|
||||
/**
|
||||
|
@ -6,15 +7,14 @@ import { Collection } from '../utils/collection.ts'
|
|||
*
|
||||
* You should not be making Managers yourself.
|
||||
*/
|
||||
export class BaseManager<T, T2> {
|
||||
client: Client
|
||||
export class BaseManager<T, T2> extends Base {
|
||||
/** Caches Name or Key used to differentiate caches */
|
||||
cacheName: string
|
||||
/** Which data type does this cache have */
|
||||
DataType: any
|
||||
|
||||
constructor(client: Client, cacheName: string, DataType: any) {
|
||||
this.client = client
|
||||
super(client)
|
||||
this.cacheName = cacheName
|
||||
this.DataType = DataType
|
||||
}
|
||||
|
@ -87,4 +87,8 @@ export class BaseManager<T, T2> {
|
|||
flush(): any {
|
||||
return this.client.cache.deleteCache(this.cacheName)
|
||||
}
|
||||
|
||||
[Deno.customInspect](): string {
|
||||
return `Manager(${this.cacheName})`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import type { Client } from '../client/mod.ts'
|
||||
import { Base } from '../structures/base.ts'
|
||||
import { Collection } from '../utils/collection.ts'
|
||||
import { BaseManager } from './base.ts'
|
||||
|
||||
/** Child Managers validate data from their parents i.e. from Managers */
|
||||
export class BaseChildManager<T, T2> {
|
||||
client: Client
|
||||
export class BaseChildManager<T, T2> extends Base {
|
||||
/** Parent Manager */
|
||||
parent: BaseManager<T, T2>
|
||||
|
||||
constructor(client: Client, parent: BaseManager<T, T2>) {
|
||||
this.client = client
|
||||
super(client)
|
||||
this.parent = parent
|
||||
}
|
||||
|
||||
|
@ -62,4 +62,8 @@ export class BaseChildManager<T, T2> {
|
|||
if (fetchValue !== undefined) return fetchValue
|
||||
}
|
||||
}
|
||||
|
||||
[Deno.customInspect](): string {
|
||||
return `ChildManager(${this.parent.cacheName})`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,8 +103,18 @@ export class RESTManager {
|
|||
* ```
|
||||
*/
|
||||
api: APIMap
|
||||
|
||||
#token?: string | (() => string | undefined)
|
||||
|
||||
/** Token being used for Authorization */
|
||||
token?: string | (() => string | undefined)
|
||||
get token(): string | (() => string | undefined) | undefined {
|
||||
return this.#token
|
||||
}
|
||||
|
||||
set token(val: string | (() => string | undefined) | undefined) {
|
||||
this.#token = val
|
||||
}
|
||||
|
||||
/** Token Type of the Token if any */
|
||||
tokenType: TokenType = TokenType.Bot
|
||||
/** Headers object which patch the current ones */
|
||||
|
@ -114,13 +124,13 @@ export class RESTManager {
|
|||
/** Whether REST Manager is using Canary API */
|
||||
canary?: boolean
|
||||
/** Optional Harmony Client object */
|
||||
client?: Client
|
||||
readonly client?: Client
|
||||
endpoints: RESTEndpoints
|
||||
requestTimeout = 30000
|
||||
timers: Set<number> = new Set()
|
||||
readonly timers!: Set<number>
|
||||
apiURL = Constants.DISCORD_API_URL
|
||||
|
||||
handlers = new Collection<string, BucketHandler>()
|
||||
readonly handlers!: Collection<string, BucketHandler>
|
||||
globalLimit = Infinity
|
||||
globalRemaining = this.globalLimit
|
||||
globalReset: number | null = null
|
||||
|
@ -136,11 +146,28 @@ export class RESTManager {
|
|||
if (options?.tokenType !== undefined) this.tokenType = options.tokenType
|
||||
if (options?.userAgent !== undefined) this.userAgent = options.userAgent
|
||||
if (options?.canary !== undefined) this.canary = options.canary
|
||||
if (options?.client !== undefined) this.client = options.client
|
||||
if (options?.retryLimit !== undefined) this.retryLimit = options.retryLimit
|
||||
if (options?.requestTimeout !== undefined)
|
||||
this.requestTimeout = options.requestTimeout
|
||||
|
||||
if (options?.client !== undefined) {
|
||||
Object.defineProperty(this, 'client', {
|
||||
value: options.client,
|
||||
enumerable: false
|
||||
})
|
||||
}
|
||||
|
||||
this.endpoints = new RESTEndpoints(this)
|
||||
|
||||
Object.defineProperty(this, 'timers', {
|
||||
value: new Set(),
|
||||
enumerable: false
|
||||
})
|
||||
|
||||
Object.defineProperty(this, 'handlers', {
|
||||
value: new Collection<string, BucketHandler>(),
|
||||
enumerable: false
|
||||
})
|
||||
}
|
||||
|
||||
setTimeout(fn: (...args: any[]) => any, ms: number): number {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/* eslint-disable no-eval */
|
||||
import * as discord from '../mod.ts'
|
||||
import { TOKEN } from './config.ts'
|
||||
|
||||
const client = new discord.Client({
|
||||
token: TOKEN,
|
||||
intents: ['GUILD_MESSAGES', 'GUILDS']
|
||||
})
|
||||
|
||||
client.on('messageCreate', async (msg) => {
|
||||
if (msg.author.id !== '422957901716652033') return
|
||||
|
||||
if (msg.content.startsWith('.eval') === true) {
|
||||
let code = msg.content.slice(5).trim()
|
||||
if (code.startsWith('```') === true) code = code.slice(3).trim()
|
||||
if (code.endsWith('```') === true) code = code.substr(0, code.length - 3)
|
||||
try {
|
||||
const result = await eval(code)
|
||||
msg.reply(
|
||||
`\`\`\`js\n${Deno.inspect(result).substr(0, 2000 - 20)}\n\`\`\``
|
||||
)
|
||||
} catch (e) {
|
||||
msg.reply(`\`\`\`js\n${e}\n\`\`\``, {
|
||||
allowedMentions: { replied_user: false }
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
client.connect().then(() => console.log('Ready!'))
|
|
@ -0,0 +1,87 @@
|
|||
/* eslint-disable @typescript-eslint/strict-boolean-expressions */
|
||||
/* eslint-disable no-control-regex */
|
||||
import { Client, Embed } from '../mod.ts'
|
||||
import { TOKEN } from './config.ts'
|
||||
|
||||
const client = new Client({
|
||||
token: TOKEN,
|
||||
intents: ['GUILDS', 'GUILD_MESSAGES']
|
||||
})
|
||||
|
||||
const NAME_MATCH = /[^a-zA-Z0-9_]/
|
||||
const STD_REGEX = /\/?std(@[\x00-\x2e\x30-\xff]+)?\/([a-zA-Z0-9]+)(\/[\S\s]+)?/
|
||||
const X_REGEX = /\/?x\/([a-zA-Z0-9]+)(@[\x00-\x2e\x30-\xff]+)?(\/[\S\s]+)?/
|
||||
|
||||
export async function fetchModule(name: string): Promise<any> {
|
||||
if (name.match(NAME_MATCH) !== null) return null
|
||||
return fetch(`https://api.deno.land/modules/${name}`, {
|
||||
credentials: 'omit',
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:83.0) Gecko/20100101 Firefox/83.0',
|
||||
Accept: 'application/json',
|
||||
'Accept-Language': 'en-US,en;q=0.5',
|
||||
Pragma: 'no-cache',
|
||||
'Cache-Control': 'no-cache'
|
||||
},
|
||||
referrer: 'https://deno.land/x',
|
||||
mode: 'cors'
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.status !== 200) throw new Error('not found')
|
||||
return res
|
||||
})
|
||||
.then((r) => r.json())
|
||||
.then((json) => {
|
||||
if (!json.success) throw new Error('failed')
|
||||
return json
|
||||
})
|
||||
.then((data) => data.data)
|
||||
.catch(() => null)
|
||||
}
|
||||
|
||||
client.on('messageCreate', async (msg) => {
|
||||
if (msg.author.bot === true) return
|
||||
|
||||
let match
|
||||
if (
|
||||
(match = msg.content.match(STD_REGEX)) ||
|
||||
(match = msg.content.match(X_REGEX))
|
||||
) {
|
||||
let x = match[0].trim()
|
||||
|
||||
if (!x.startsWith('/')) x = `/${x}`
|
||||
|
||||
let type
|
||||
if (x.startsWith('/std')) {
|
||||
x = x.slice(4)
|
||||
type = 'std'
|
||||
} else {
|
||||
x = x.slice(3)
|
||||
type = 'x'
|
||||
}
|
||||
|
||||
x = x.trim()
|
||||
const name = x.split('/')[0].split('@')[0]
|
||||
const mod = await fetchModule(type === 'std' ? 'std' : name)
|
||||
if (mod === null) return
|
||||
|
||||
msg.channel.send(
|
||||
new Embed()
|
||||
.setColor('#7289DA')
|
||||
.setURL(
|
||||
`https://deno.land/${type}${
|
||||
x.startsWith('/') || x.startsWith('@') ? '' : '/'
|
||||
}${x}`
|
||||
)
|
||||
.setTitle(
|
||||
`${type}${x.startsWith('/') || x.startsWith('@') ? '' : '/'}${x}`
|
||||
)
|
||||
.setDescription(mod.description ?? 'No description.')
|
||||
.setFooter(`${mod.star_count ?? 0}`, 'https://kokoro.pw/colleague.png')
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Connecting...')
|
||||
client.connect().then(() => console.log('Ready!'))
|
|
@ -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