From a6697a75a7102ee46932ec7e7bea46d3edd6f902 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 1 Dec 2020 13:54:49 +0530 Subject: [PATCH] feat: void -- start work --- src/consts/urlsAndVersions.ts | 16 +- src/gateway/handlers/index.ts | 16 +- src/gateway/handlers/voiceServerUpdate.ts | 14 + src/gateway/handlers/voiceStateUpdate.ts | 30 ++ src/managers/guildVoiceStates.ts | 30 ++ src/models/command.ts | 380 +++++++++++----------- src/models/voice.ts | 18 +- src/structures/guild.ts | 5 +- src/structures/voiceState.ts | 102 +++--- src/test/cmd.ts | 8 + src/types/gateway.ts | 1 + src/types/voice.ts | 90 ++--- tsconfig.json | 140 ++++---- 13 files changed, 485 insertions(+), 365 deletions(-) create mode 100644 src/gateway/handlers/voiceServerUpdate.ts create mode 100644 src/gateway/handlers/voiceStateUpdate.ts create mode 100644 src/managers/guildVoiceStates.ts diff --git a/src/consts/urlsAndVersions.ts b/src/consts/urlsAndVersions.ts index 4c3c239..257c937 100644 --- a/src/consts/urlsAndVersions.ts +++ b/src/consts/urlsAndVersions.ts @@ -1,7 +1,9 @@ -export const DISCORD_API_URL: string = 'https://discord.com/api' - -export const DISCORD_GATEWAY_URL: string = 'wss://gateway.discord.gg' - -export const DISCORD_CDN_URL: string = 'https://cdn.discordapp.com' - -export const DISCORD_API_VERSION: number = 8 +export const DISCORD_API_URL: string = 'https://discord.com/api' + +export const DISCORD_GATEWAY_URL: string = 'wss://gateway.discord.gg' + +export const DISCORD_CDN_URL: string = 'https://cdn.discordapp.com' + +export const DISCORD_API_VERSION: number = 8 + +export const DISCORD_VOICE_VERSION: number = 4 \ No newline at end of file diff --git a/src/gateway/handlers/index.ts b/src/gateway/handlers/index.ts index 319c3f9..9974fff 100644 --- a/src/gateway/handlers/index.ts +++ b/src/gateway/handlers/index.ts @@ -36,6 +36,9 @@ import { Member } from "../../structures/member.ts" import { Role } from "../../structures/role.ts" import { Message } from "../../structures/message.ts" import { Collection } from "../../utils/collection.ts" +import { voiceServerUpdate } from "./voiceServerUpdate.ts" +import { voiceStateUpdate } from "./voiceStateUpdate.ts" +import { VoiceState } from "../../structures/voiceState.ts" export const gatewayHandlers: { [eventCode in GatewayEvents]: GatewayEventHandler | undefined @@ -74,7 +77,8 @@ export const gatewayHandlers: { PRESENCE_UPDATE: undefined, TYPING_START: typingStart, USER_UPDATE: userUpdate, - VOICE_SERVER_UPDATE: undefined, + VOICE_STATE_UPDATE: voiceStateUpdate, + VOICE_SERVER_UPDATE: voiceServerUpdate, WEBHOOKS_UPDATE: webhooksUpdate } @@ -82,6 +86,12 @@ export interface EventTypes { [name: string]: (...args: any[]) => void } +export interface VoiceServerUpdateData { + token: string + endpoint: string + guild: Guild +} + export interface ClientEvents extends EventTypes { 'ready': () => void 'reconnect': () => void @@ -111,5 +121,9 @@ export interface ClientEvents extends EventTypes { 'messageUpdate': (before: Message, after: Message) => void 'typingStart': (user: User, channel: TextChannel, at: Date, guildData?: TypingStartGuildData) => void 'userUpdate': (before: User, after: User) => void + 'voiceServerUpdate': (data: VoiceServerUpdateData) => void + 'voiceStateAdd': (state: VoiceState) => void + 'voiceStateRemove': (state: VoiceState) => void + 'voiceStateUpdate': (state: VoiceState, after: VoiceState) => void 'webhooksUpdate': (guild: Guild, channel: GuildTextChannel) => void } \ No newline at end of file diff --git a/src/gateway/handlers/voiceServerUpdate.ts b/src/gateway/handlers/voiceServerUpdate.ts new file mode 100644 index 0000000..6a17138 --- /dev/null +++ b/src/gateway/handlers/voiceServerUpdate.ts @@ -0,0 +1,14 @@ +import { Guild } from "../../structures/guild.ts" +import { VoiceServerUpdatePayload } from "../../types/gateway.ts" +import { Gateway, GatewayEventHandler } from '../index.ts' + +export const voiceServerUpdate: GatewayEventHandler = async ( + gateway: Gateway, + d: VoiceServerUpdatePayload +) => { + gateway.client.emit('voiceServerUpdate', { + token: d.token, + endpoint: d.endpoint, + guild: (await gateway.client.guilds.get(d.guild_id) as unknown) as Guild + }) +} diff --git a/src/gateway/handlers/voiceStateUpdate.ts b/src/gateway/handlers/voiceStateUpdate.ts new file mode 100644 index 0000000..fb8b95a --- /dev/null +++ b/src/gateway/handlers/voiceStateUpdate.ts @@ -0,0 +1,30 @@ +import { Guild } from "../../structures/guild.ts" +import { VoiceState } from "../../structures/voiceState.ts" +import { VoiceStatePayload } from "../../types/voice.ts" +import { Gateway, GatewayEventHandler } from '../index.ts' + +export const voiceStateUpdate: GatewayEventHandler = async ( + gateway: Gateway, + d: VoiceStatePayload +) => { + // TODO(DjDeveloperr): Support self-bot here; they can be in DMs (Call) + if (d.guild_id === undefined) return + const guild = (await gateway.client.guilds.get(d.guild_id) as unknown) as Guild + + const voiceState = await guild.voiceStates.get(d.user_id) + + if (d.channel_id === null) { + // No longer in the channel, so delete + await guild.voiceStates.delete(d.user_id) + gateway.client.emit('voiceStateRemove', (voiceState as unknown) as VoiceState) + return + } + + await guild.voiceStates.set(d.user_id, d) + const newVoiceState = await guild.voiceStates.get(d.user_id) + if (voiceState === undefined) { + gateway.client.emit('voiceStateAdd', (newVoiceState as unknown) as VoiceState) + } else { + gateway.client.emit('voiceStateUpdate', voiceState, (newVoiceState as unknown) as VoiceState) + } +} diff --git a/src/managers/guildVoiceStates.ts b/src/managers/guildVoiceStates.ts new file mode 100644 index 0000000..70d0a07 --- /dev/null +++ b/src/managers/guildVoiceStates.ts @@ -0,0 +1,30 @@ +import { Client } from '../models/client.ts' +import { Guild } from "../structures/guild.ts" +import { VoiceChannel } from "../structures/guildVoiceChannel.ts" +import { User } from "../structures/user.ts" +import { VoiceState } from "../structures/voiceState.ts" +import { VoiceStatePayload } from "../types/voice.ts" +import { BaseManager } from './base.ts' + +export class GuildVoiceStatesManager extends BaseManager { + guild: Guild + + async get (key: string): Promise { + const raw = await this._get(key) + if (raw === undefined) return + + const guild = raw.guild_id === undefined ? undefined : await this.client.guilds.get(raw.guild_id) + + return new VoiceState(this.client, raw, { + user: (await this.client.users.get(raw.user_id) as unknown) as User, + channel: raw.channel_id == null ? null : (await this.client.channels.get(raw.channel_id) as unknown) as VoiceChannel, + guild, + member: guild === undefined ? undefined : await guild.members.get(raw.user_id) + }) + } + + constructor (client: Client, guild: Guild) { + super(client, `vs:${guild.id}`, VoiceState) + this.guild = guild + } +} diff --git a/src/models/command.ts b/src/models/command.ts index 3d6d207..1c99110 100644 --- a/src/models/command.ts +++ b/src/models/command.ts @@ -1,190 +1,190 @@ -import { Guild } from '../structures/guild.ts' -import { Message } from '../structures/message.ts' -import { TextChannel } from '../structures/textChannel.ts' -import { User } from '../structures/user.ts' -import { Collection } from '../utils/collection.ts' -import { CommandClient } from './commandClient.ts' -import { Extension } from "./extensions.ts" - -export interface CommandContext { - /** The Client object */ - client: CommandClient - /** Message which was parsed for Command */ - message: Message - /** The Author of the Message */ - author: User - /** The Channel in which Command was used */ - channel: TextChannel - /** Prefix which was used */ - prefix: string - /** Oject of Command which was used */ - command: Command - /** Name of Command which was used */ - name: string - /** Array of Arguments used with Command */ - args: string[] - /** Complete Raw String of Arguments */ - argString: string - /** Guild which the command has called */ - guild?: Guild -} - -export class Command { - /** Name of the Command */ - name: string = '' - /** Description of the Command */ - description?: string - /** Array of Aliases of Command, or only string */ - aliases?: string | string[] - /** Extension (Parent) of the Command */ - extension?: Extension - /** Usage of Command, only Argument Names */ - usage?: string | string[] - /** Usage Example of Command, only Arguments (without Prefix and Name) */ - examples?: string | string[] - /** Does the Command take Arguments? Maybe number of required arguments? */ - args?: number | boolean - /** Permission(s) required for using Command */ - permissions?: string | string[] - /** Permission(s) bot will need in order to execute Command */ - botPermissions?: string | string[] - /** Role(s) user will require in order to use Command. List or one of ID or name */ - roles?: string | string[] - /** Whitelisted Guilds. Only these Guild(s) can execute Command. (List or one of IDs) */ - whitelistedGuilds?: string | string[] - /** Whitelisted Channels. Command can be executed only in these channels. (List or one of IDs) */ - whitelistedChannels?: string | string[] - /** Whitelisted Users. Command can be executed only by these Users (List or one of IDs) */ - whitelistedUsers?: string | string[] - /** Whether the Command can only be used in Guild (if allowed in DMs) */ - guildOnly?: boolean - /** Whether the Command can only be used in Bot's DMs (if allowed) */ - dmOnly?: boolean - /** Whether the Command can only be used by Bot Owners */ - ownerOnly?: boolean - - /** Method executed before executing actual command. Returns bool value - whether to continue or not (optional) */ - beforeExecute(ctx: CommandContext): boolean | Promise { return true } - /** Actual command code, which is executed when all checks have passed. */ - execute(ctx: CommandContext): any { } - /** Method executed after executing command, passes on CommandContext and the value returned by execute too. (optional) */ - afterExecute(ctx: CommandContext, executeResult: any): any { } - - toString(): string { - return `Command: ${this.name}${this.extension !== undefined ? ` [${this.extension.name}]` : ''}` - } -} - -export class CommandsManager { - client: CommandClient - list: Collection = new Collection() - disabled: Set = new Set() - - constructor(client: CommandClient) { - this.client = client - } - - /** Number of loaded Commands */ - get count(): number { return this.list.size } - - /** Find a Command by name/alias */ - find(search: string): Command | undefined { - if (this.client.caseSensitive === false) search = search.toLowerCase() - return this.list.find((cmd: Command): boolean => { - const name = - this.client.caseSensitive === true ? cmd.name : cmd.name.toLowerCase() - if (name === search) return true - else if (cmd.aliases !== undefined) { - let aliases: string[] - if (typeof cmd.aliases === 'string') aliases = [cmd.aliases] - else aliases = cmd.aliases - if (this.client.caseSensitive === false) - aliases = aliases.map(e => e.toLowerCase()) - return aliases.includes(search) - } else return false - }) - } - - /** Fetch a Command including disable checks */ - fetch(name: string, bypassDisable?: boolean): Command | undefined { - const cmd = this.find(name) - if (cmd === undefined) return - if (this.isDisabled(cmd) && bypassDisable !== true) return - return cmd - } - - /** Check whether a Command exists or not */ - exists(search: Command | string): boolean { - let exists = false - if (typeof search === 'string') return this.find(search) !== undefined - else { - exists = this.find(search.name) !== undefined - if (search.aliases !== undefined) { - const aliases: string[] = - typeof search.aliases === 'string' ? [search.aliases] : search.aliases - exists = - aliases.map(alias => this.find(alias) !== undefined).find(e => e) ?? - false - } - return exists - } - } - - /** Add a Command */ - add(cmd: Command | typeof Command): boolean { - // eslint-disable-next-line new-cap - if (!(cmd instanceof Command)) cmd = new cmd() - if (this.exists(cmd)) throw new Error(`Failed to add Command '${cmd.toString()}' with name/alias already exists.`) - this.list.set(cmd.name, cmd) - return true - } - - /** Delete a Command */ - delete(cmd: string | Command): boolean { - const find = typeof cmd === 'string' ? this.find(cmd) : cmd - if (find === undefined) return false - else return this.list.delete(find.name) - } - - /** Check whether a Command is disabled or not */ - isDisabled(name: string | Command): boolean { - const cmd = typeof name === "string" ? this.find(name) : name - if (cmd === undefined) return false - const exists = this.exists(name) - if (!exists) return false - return this.disabled.has(cmd.name) - } - - /** Disable a Command */ - disable(name: string | Command): boolean { - const cmd = typeof name === "string" ? this.find(name) : name - if (cmd === undefined) return false - if (this.isDisabled(cmd)) return false - this.disabled.add(cmd.name) - return true - } -} - -export interface ParsedCommand { - name: string - args: string[] - argString: string -} - -export const parseCommand = ( - client: CommandClient, - msg: Message, - prefix: string -): ParsedCommand => { - let content = msg.content.slice(prefix.length) - if (client.spacesAfterPrefix === true) content = content.trim() - const args = content.split(client.betterArgs === true ? /[\S\s]*/ : / +/) - const name = args.shift() as string - const argString = content.slice(name.length).trim() - - return { - name, - args, - argString - } -} +import { Guild } from '../structures/guild.ts' +import { Message } from '../structures/message.ts' +import { TextChannel } from '../structures/textChannel.ts' +import { User } from '../structures/user.ts' +import { Collection } from '../utils/collection.ts' +import { CommandClient } from './commandClient.ts' +import { Extension } from "./extensions.ts" + +export interface CommandContext { + /** The Client object */ + client: CommandClient + /** Message which was parsed for Command */ + message: Message + /** The Author of the Message */ + author: User + /** The Channel in which Command was used */ + channel: TextChannel + /** Prefix which was used */ + prefix: string + /** Oject of Command which was used */ + command: Command + /** Name of Command which was used */ + name: string + /** Array of Arguments used with Command */ + args: string[] + /** Complete Raw String of Arguments */ + argString: string + /** Guild which the command has called */ + guild?: Guild +} + +export class Command { + /** Name of the Command */ + name: string = '' + /** Description of the Command */ + description?: string + /** Array of Aliases of Command, or only string */ + aliases?: string | string[] + /** Extension (Parent) of the Command */ + extension?: Extension + /** Usage of Command, only Argument Names */ + usage?: string | string[] + /** Usage Example of Command, only Arguments (without Prefix and Name) */ + examples?: string | string[] + /** Does the Command take Arguments? Maybe number of required arguments? */ + args?: number | boolean + /** Permission(s) required for using Command */ + permissions?: string | string[] + /** Permission(s) bot will need in order to execute Command */ + botPermissions?: string | string[] + /** Role(s) user will require in order to use Command. List or one of ID or name */ + roles?: string | string[] + /** Whitelisted Guilds. Only these Guild(s) can execute Command. (List or one of IDs) */ + whitelistedGuilds?: string | string[] + /** Whitelisted Channels. Command can be executed only in these channels. (List or one of IDs) */ + whitelistedChannels?: string | string[] + /** Whitelisted Users. Command can be executed only by these Users (List or one of IDs) */ + whitelistedUsers?: string | string[] + /** Whether the Command can only be used in Guild (if allowed in DMs) */ + guildOnly?: boolean + /** Whether the Command can only be used in Bot's DMs (if allowed) */ + dmOnly?: boolean + /** Whether the Command can only be used by Bot Owners */ + ownerOnly?: boolean + + /** Method executed before executing actual command. Returns bool value - whether to continue or not (optional) */ + beforeExecute(ctx: CommandContext): boolean | Promise { return true } + /** Actual command code, which is executed when all checks have passed. */ + execute(ctx: CommandContext): any { } + /** Method executed after executing command, passes on CommandContext and the value returned by execute too. (optional) */ + afterExecute(ctx: CommandContext, executeResult: any): any { } + + toString(): string { + return `Command: ${this.name}${this.extension !== undefined ? ` [${this.extension.name}]` : ''}` + } +} + +export class CommandsManager { + client: CommandClient + list: Collection = new Collection() + disabled: Set = new Set() + + constructor(client: CommandClient) { + this.client = client + } + + /** Number of loaded Commands */ + get count(): number { return this.list.size } + + /** Find a Command by name/alias */ + find(search: string): Command | undefined { + if (this.client.caseSensitive === false) search = search.toLowerCase() + return this.list.find((cmd: Command): boolean => { + const name = + this.client.caseSensitive === true ? cmd.name : cmd.name.toLowerCase() + if (name === search) return true + else if (cmd.aliases !== undefined) { + let aliases: string[] + if (typeof cmd.aliases === 'string') aliases = [cmd.aliases] + else aliases = cmd.aliases + if (this.client.caseSensitive === false) + aliases = aliases.map(e => e.toLowerCase()) + return aliases.includes(search) + } else return false + }) + } + + /** Fetch a Command including disable checks */ + fetch(name: string, bypassDisable?: boolean): Command | undefined { + const cmd = this.find(name) + if (cmd === undefined) return + if (this.isDisabled(cmd) && bypassDisable !== true) return + return cmd + } + + /** Check whether a Command exists or not */ + exists(search: Command | string): boolean { + let exists = false + if (typeof search === 'string') return this.find(search) !== undefined + else { + exists = this.find(search.name) !== undefined + if (search.aliases !== undefined) { + const aliases: string[] = + typeof search.aliases === 'string' ? [search.aliases] : search.aliases + exists = + aliases.map(alias => this.find(alias) !== undefined).find(e => e) ?? + false + } + return exists + } + } + + /** Add a Command */ + add(cmd: Command | typeof Command): boolean { + // eslint-disable-next-line new-cap + if (!(cmd instanceof Command)) cmd = new cmd() + if (this.exists(cmd)) throw new Error(`Failed to add Command '${cmd.toString()}' with name/alias already exists.`) + this.list.set(cmd.name, cmd) + return true + } + + /** Delete a Command */ + delete(cmd: string | Command): boolean { + const find = typeof cmd === 'string' ? this.find(cmd) : cmd + if (find === undefined) return false + else return this.list.delete(find.name) + } + + /** Check whether a Command is disabled or not */ + isDisabled(name: string | Command): boolean { + const cmd = typeof name === "string" ? this.find(name) : name + if (cmd === undefined) return false + const exists = this.exists(name) + if (!exists) return false + return this.disabled.has(cmd.name) + } + + /** Disable a Command */ + disable(name: string | Command): boolean { + const cmd = typeof name === "string" ? this.find(name) : name + if (cmd === undefined) return false + if (this.isDisabled(cmd)) return false + this.disabled.add(cmd.name) + return true + } +} + +export interface ParsedCommand { + name: string + args: string[] + argString: string +} + +export const parseCommand = ( + client: CommandClient, + msg: Message, + prefix: string +): ParsedCommand => { + let content = msg.content.slice(prefix.length) + if (client.spacesAfterPrefix === true) content = content.trim() + const args = content.split(client.betterArgs === true ? /[\S\s]*/ : / +/) + const name = args.shift() as string + const argString = content.slice(name.length).trim() + + return { + name, + args, + argString + } +} diff --git a/src/models/voice.ts b/src/models/voice.ts index b4c02be..6dc866d 100644 --- a/src/models/voice.ts +++ b/src/models/voice.ts @@ -1,10 +1,26 @@ +import { Guild } from "../structures/guild.ts" +import { VoiceChannel } from "../structures/guildVoiceChannel.ts" import { Client } from './client.ts' +export interface VoiceOptions { + guild: Guild, + channel: VoiceChannel +} + export class VoiceClient { client: Client + ws?: WebSocket + guild: Guild + channel: VoiceChannel - constructor(client: Client) { + constructor(client: Client, options: VoiceOptions) { this.client = client + this.guild = options.guild + this.channel = options.channel + } + + async connect(): Promise { + return this } } \ No newline at end of file diff --git a/src/structures/guild.ts b/src/structures/guild.ts index 395d3cb..49873ce 100644 --- a/src/structures/guild.ts +++ b/src/structures/guild.ts @@ -2,7 +2,6 @@ import { Client } from '../models/client.ts' import { GuildFeatures, GuildIntegrationPayload, GuildPayload, IntegrationAccountPayload, IntegrationExpireBehavior } from '../types/guild.ts' import { PresenceUpdatePayload } from '../types/gateway.ts' import { Base } from './base.ts' -import { VoiceState } from './voiceState.ts' import { RolesManager } from '../managers/roles.ts' import { GuildChannelsManager } from '../managers/guildChannels.ts' import { MembersManager } from '../managers/members.ts' @@ -12,6 +11,7 @@ import { Member } from "./member.ts" import { User } from "./user.ts" import { Application } from "./application.ts" import { GUILD_INTEGRATIONS } from "../types/endpoint.ts" +import { GuildVoiceStatesManager } from "../managers/guildVoiceStates.ts" export class Guild extends Base { id: string @@ -43,7 +43,7 @@ export class Guild extends Base { large?: boolean unavailable: boolean memberCount?: number - voiceStates?: VoiceState[] + voiceStates: GuildVoiceStatesManager members: MembersManager channels: GuildChannelsManager presences?: PresenceUpdatePayload[] @@ -65,6 +65,7 @@ export class Guild extends Base { this.id = data.id this.unavailable = data.unavailable this.members = new MembersManager(this.client, this) + this.voiceStates = new GuildVoiceStatesManager(client, this) this.channels = new GuildChannelsManager( this.client, this.client.channels, diff --git a/src/structures/voiceState.ts b/src/structures/voiceState.ts index b1672f6..026d5dd 100644 --- a/src/structures/voiceState.ts +++ b/src/structures/voiceState.ts @@ -1,49 +1,53 @@ -import { Client } from '../models/client.ts' -import { MemberPayload } from '../types/guild.ts' -import { VoiceStatePayload } from '../types/voice.ts' -import { Base } from './base.ts' - -export class VoiceState extends Base { - guildID?: string - channelID?: string - userID: string - member?: MemberPayload - sessionID: string - deaf: boolean - mute: boolean - selfDeaf: boolean - selfMute: boolean - selfStream?: boolean - selfVideo: boolean - suppress: boolean - - constructor (client: Client, data: VoiceStatePayload) { - super(client, data) - this.channelID = data.channel_id - this.sessionID = data.session_id - this.userID = data.user_id - this.deaf = data.deaf - this.mute = data.mute - this.selfDeaf = data.self_deaf - this.selfMute = data.self_mute - this.selfStream = data.self_stream - this.selfVideo = data.self_video - this.suppress = data.suppress - // TODO: Cache in Gateway Event Code - // cache.set('voiceState', `${this.guildID}:${this.userID}`, this) - } - - protected readFromData (data: VoiceStatePayload): void { - super.readFromData(data) - this.channelID = data.channel_id ?? this.channelID - this.sessionID = data.session_id ?? this.sessionID - this.userID = data.user_id ?? this.userID - this.deaf = data.deaf ?? this.deaf - this.mute = data.mute ?? this.mute - this.selfDeaf = data.self_deaf ?? this.selfDeaf - this.selfMute = data.self_mute ?? this.selfMute - this.selfStream = data.self_stream ?? this.selfStream - this.selfVideo = data.self_video ?? this.selfVideo - this.suppress = data.suppress ?? this.suppress - } -} +import { Client } from '../models/client.ts' +import { VoiceStatePayload } from '../types/voice.ts' +import { Base } from './base.ts' +import { Guild } from "./guild.ts" +import { VoiceChannel } from "./guildVoiceChannel.ts" +import { Member } from "./member.ts" +import { User } from "./user.ts" + +export class VoiceState extends Base { + guild?: Guild + channel: VoiceChannel | null + user: User + member?: Member + sessionID: string + deaf: boolean + mute: boolean + stream?: boolean + video: boolean + suppress: boolean + + constructor (client: Client, data: VoiceStatePayload, _data: { + user: User, + channel: VoiceChannel | null, + member?: Member, + guild?: Guild + }) { + super(client, data) + this.channel = _data.channel + this.sessionID = data.session_id + this.user = _data.user + this.member = _data.member + this.guild = _data.guild + this.deaf = data.deaf + this.mute = data.mute + this.deaf = data.self_deaf + this.mute = data.self_mute + this.stream = data.self_stream + this.video = data.self_video + this.suppress = data.suppress + } + + protected readFromData (data: VoiceStatePayload): void { + super.readFromData(data) + this.sessionID = data.session_id ?? this.sessionID + this.deaf = data.deaf ?? this.deaf + this.mute = data.mute ?? this.mute + this.deaf = data.self_deaf ?? this.deaf + this.mute = data.self_mute ?? this.mute + this.stream = data.self_stream ?? this.stream + this.video = data.self_video ?? this.video + this.suppress = data.suppress ?? this.suppress + } +} diff --git a/src/test/cmd.ts b/src/test/cmd.ts index 85efd56..68f0052 100644 --- a/src/test/cmd.ts +++ b/src/test/cmd.ts @@ -92,6 +92,14 @@ client.on('typingStart', (user, channel, at, guildData) => { console.log(`${user.tag} started typing in ${channel.id} at ${at}${guildData !== undefined ? `\nGuild: ${guildData.guild.name}` : ''}`) }) +client.on('voiceStateAdd', (state) => { + console.log('VC Join', state) +}) + +client.on('voiceStateRemove', (state) => { + console.log('VC Leave', state) +}) + // client.on('raw', (evt: string) => console.log(`EVENT: ${evt}`)) const files = Deno.readDirSync('./src/test/cmds') diff --git a/src/types/gateway.ts b/src/types/gateway.ts index aebf1e9..188103c 100644 --- a/src/types/gateway.ts +++ b/src/types/gateway.ts @@ -104,6 +104,7 @@ export enum GatewayEvents { Typing_Start = 'TYPING_START', User_Update = 'USER_UPDATE', Voice_Server_Update = 'VOICE_SERVER_UPDATE', + Voice_State_Update = 'VOICE_STATE_UPDATE', Webhooks_Update = 'WEBHOOKS_UPDATE' } diff --git a/src/types/voice.ts b/src/types/voice.ts index c6b3143..c796d1c 100644 --- a/src/types/voice.ts +++ b/src/types/voice.ts @@ -1,45 +1,45 @@ -// https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice -import { MemberPayload } from './guild.ts' - -export enum VoiceOpcodes { // VoiceOpcodes 추가 - UnderC - - IDENTIFY = 0, - SELECT_PROTOCOL = 1, - READY = 2, - HEARTBEAT = 3, - SESSION_DESCRIPTION = 4, - SPEAKING = 6, - HEARTBEAT_ACK = 6, - RESUME = 7, - HELLO = 8, - RESUMED = 9, - CLIENT_DISCONNECT = 13 -} - -export enum VoiceCloseCodes { - UNKNOWN_OPCODE = 4001, - NOT_AUTHENTICATED = 4003, - AUTHENTICATION_FAILED = 4004, - ALREADY_AUTHENTICATED = 4005, - SESSION_NO_LONGER_VALID = 4006, - SESSION_TIMEOUT = 4009, - SERVER_NOT_FOUNT = 4011, - UNKNOWN_PROTOCOL = 4012, - DISCONNECTED = 4014, - VOICE_SERVER_CRASHED = 4015, - UNKNOWN_ENCRYPTION_MODE = 4016 -} - -export interface VoiceStatePayload { - guild_id?: string - channel_id?: string - user_id: string - member?: MemberPayload - session_id: string - deaf: boolean - mute: boolean - self_deaf: boolean - self_mute: boolean - self_stream?: boolean - self_video: boolean - suppress: boolean -} +// https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice +import { MemberPayload } from './guild.ts' + +export enum VoiceOpcodes { // VoiceOpcodes 추가 - UnderC - + IDENTIFY = 0, + SELECT_PROTOCOL = 1, + READY = 2, + HEARTBEAT = 3, + SESSION_DESCRIPTION = 4, + SPEAKING = 6, + HEARTBEAT_ACK = 6, + RESUME = 7, + HELLO = 8, + RESUMED = 9, + CLIENT_DISCONNECT = 13 +} + +export enum VoiceCloseCodes { + UNKNOWN_OPCODE = 4001, + NOT_AUTHENTICATED = 4003, + AUTHENTICATION_FAILED = 4004, + ALREADY_AUTHENTICATED = 4005, + SESSION_NO_LONGER_VALID = 4006, + SESSION_TIMEOUT = 4009, + SERVER_NOT_FOUNT = 4011, + UNKNOWN_PROTOCOL = 4012, + DISCONNECTED = 4014, + VOICE_SERVER_CRASHED = 4015, + UNKNOWN_ENCRYPTION_MODE = 4016 +} + +export interface VoiceStatePayload { + guild_id?: string + channel_id: string | null + user_id: string + member?: MemberPayload + session_id: string + deaf: boolean + mute: boolean + self_deaf: boolean + self_mute: boolean + self_stream?: boolean + self_video: boolean + suppress: boolean +} diff --git a/tsconfig.json b/tsconfig.json index 5f09fcc..a4477bb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,70 +1,70 @@ -{ - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ - /* Basic Options */ - // "incremental": true, /* Enable incremental compilation */ - "target": "ESNext", - /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ - "module": "ESNext", - /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ - "lib": [ - "esnext" - ] /* Specify library files to be included in the compilation. */, - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - // "declaration": true, /* Generates corresponding '.d.ts' file. */ - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - // "outDir": "./", /* Redirect output structure to the directory. */ - // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true, /* Import emit helpers from 'tslib'. */ - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - "isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */, - /* Strict Type-Checking Options */ - "strict": true, - /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* Enable strict null checks. */ - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - /* Module Resolution Options */ - "moduleResolution": "node", - /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true, - /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - "emitDecoratorMetadata": false /* Enables experimental support for emitting type metadata for decorators. */, - /* Advanced Options */ - "skipLibCheck": true, - /* Skip type checking of declaration files. */ - "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ - } -} +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ESNext", + /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "ESNext", + /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + "lib": [ + "esnext" + ] /* Specify library files to be included in the compilation. */, + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + // "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + // "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + // "outDir": "./", /* Redirect output structure to the directory. */ + // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + "isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */, + /* Strict Type-Checking Options */ + "strict": true, + /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + /* Module Resolution Options */ + "moduleResolution": "node", + /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, + /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + /* Experimental Options */ + "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + "emitDecoratorMetadata": false /* Enables experimental support for emitting type metadata for decorators. */, + /* Advanced Options */ + "skipLibCheck": true, + /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +}