From 91c07e551e51e4bd391859ab541b18c1fbe68731 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Wed, 9 Dec 2020 10:48:39 +0530 Subject: [PATCH 01/10] feat(gateway): option to customize client props --- mod.ts | 39 ++++++++ src/gateway/handlers/index.ts | 171 +++++++++++++++++++++++++++++++++- src/gateway/index.ts | 6 +- src/models/client.ts | 20 ++++ src/structures/presence.ts | 26 +++++- src/test/index.ts | 8 +- src/types/gateway.ts | 10 +- 7 files changed, 261 insertions(+), 19 deletions(-) diff --git a/mod.ts b/mod.ts index a94898b..9606984 100644 --- a/mod.ts +++ b/mod.ts @@ -2,6 +2,7 @@ export { GatewayIntents } from './src/types/gateway.ts' export { default as EventEmitter } from 'https://deno.land/std@0.74.0/node/events.ts' export { Base } from './src/structures/base.ts' export { Gateway } from './src/gateway/index.ts' +export type { ClientEvents } from './src/gateway/handlers/index.ts' export * from './src/models/client.ts' export { RESTManager } from './src/models/rest.ts' export * from './src/models/cacheAdapter.ts' @@ -80,3 +81,41 @@ export type { StatusType } from './src/types/presence.ts' export { ChannelTypes } from './src/types/channel.ts' +export type { ApplicationPayload } from './src/types/application.ts' +export type { ImageFormats, ImageSize } from './src/types/cdn.ts' +export type { + ChannelMention, + ChannelPayload, + FollowedChannel, + GuildNewsChannelPayload, + GuildChannelCategoryPayload, + GuildChannelPayload, + GuildTextChannelPayload, + GuildVoiceChannelPayload, + GroupDMChannelPayload +} from './src/types/channel.ts' +export type { EmojiPayload } from './src/types/emoji.ts' +export type { + GuildBanPayload, + GuildFeatures, + GuildIntegrationPayload, + GuildPayload +} from './src/types/guild.ts' +export type { InvitePayload, PartialInvitePayload } from './src/types/invite.ts' +export { PermissionFlags } from './src/types/permissionFlags.ts' +export type { + ActivityAssets, + ActivityEmoji, + ActivityFlags, + ActivityParty, + ActivityPayload, + ActivitySecrets, + ActivityTimestamps, + ActivityType +} from './src/types/presence.ts' +export type { RolePayload } from './src/types/role.ts' +export type { TemplatePayload } from './src/types/template.ts' +export type { UserPayload } from './src/types/user.ts' +export { UserFlags } from './src/types/userFlags.ts' +export type { VoiceStatePayload } from './src/types/voice.ts' +export type { WebhookPayload } from './src/types/webhook.ts' diff --git a/src/gateway/handlers/index.ts b/src/gateway/handlers/index.ts index 8934068..85e1d75 100644 --- a/src/gateway/handlers/index.ts +++ b/src/gateway/handlers/index.ts @@ -27,7 +27,7 @@ import { webhooksUpdate } from './webhooksUpdate.ts' import { messageDeleteBulk } from './messageDeleteBulk.ts' import { userUpdate } from './userUpdate.ts' import { typingStart } from './typingStart.ts' -import { GuildTextChannel } from '../../structures/textChannel.ts' +import { GuildTextChannel, TextChannel } from '../../structures/textChannel.ts' import { Guild } from '../../structures/guild.ts' import { User } from '../../structures/user.ts' import { Emoji } from '../../structures/emoji.ts' @@ -107,56 +107,223 @@ export interface VoiceServerUpdateData { } export interface ClientEvents extends EventTypes { + /** When Client has successfully connected to Discord */ ready: () => void + /** When a successful reconnect has been made */ reconnect: () => void + /** When a successful session resume has been done */ resumed: () => void + /** + * When a new Channel is created + * @param channel New Channel object + */ channelCreate: (channel: EveryChannelTypes) => void + /** + * When a Channel was deleted + * @param channel Channel object which was deleted + */ channelDelete: (channel: EveryChannelTypes) => void + /** + * Channel's Pinned Messages were updated + * @param before Channel object before update + * @param after Channel object after update + */ channelPinsUpdate: ( before: EveryTextChannelTypes, after: EveryTextChannelTypes ) => void + /** + * A Channel was updated + * @param before Channel object before update + * @param after Channel object after update + */ channelUpdate: (before: EveryChannelTypes, after: EveryChannelTypes) => void + /** + * A User was banned from a Guild + * @param guild The Guild from which User was banned + * @param user The User who was banned + */ guildBanAdd: (guild: Guild, user: User) => void + /** + * A ban from a User in Guild was elevated + * @param guild Guild from which ban was removed + * @param user User of which ban was elevated + */ guildBanRemove: (guild: Guild, user: User) => void + /** + * Client has joined a new Guild. + * @param guild The new Guild object + */ guildCreate: (guild: Guild) => void + /** + * A Guild in which Client was either deleted, or bot was kicked + * @param guild The Guild object + */ guildDelete: (guild: Guild) => void + /** + * A new Emoji was added to Guild + * @param guild Guild in which Emoji was added + * @param emoji The Emoji which was added + */ guildEmojiAdd: (guild: Guild, emoji: Emoji) => void + /** + * An Emoji was deleted from Guild + * @param guild Guild from which Emoji was deleted + * @param emoji Emoji which was deleted + */ guildEmojiDelete: (guild: Guild, emoji: Emoji) => void + /** + * An Emoji in a Guild was updated + * @param guild Guild in which Emoji was updated + * @param before Emoji object before update + * @param after Emoji object after update + */ guildEmojiUpdate: (guild: Guild, before: Emoji, after: Emoji) => void + /** + * Guild's Integrations were updated + * @param guild The Guild object + */ guildIntegrationsUpdate: (guild: Guild) => void + /** + * A new Member has joined a Guild + * @param member The Member object + */ guildMemberAdd: (member: Member) => void + /** + * A Guild Member has either left or was kicked from Guild + * @param member The Member object + */ guildMemberRemove: (member: Member) => void + /** + * A Guild Member was updated. Nickname changed, role assigned, etc. + * @param before Member object before update + * @param after Meber object after update + */ guildMemberUpdate: (before: Member, after: Member) => void + /** + * A new Role was created in Guild + * @param role The new Role object + */ guildRoleCreate: (role: Role) => void + /** + * A Role was deleted from the Guild + * @param role The Role object + */ guildRoleDelete: (role: Role) => void + /** + * A Role was updated in a Guild + * @param before Role object before update + * @param after Role object after updated + */ guildRoleUpdate: (before: Role, after: Role) => void + /** + * A Guild has been updated. For example name, icon, etc. + * @param before Guild object before update + * @param after Guild object after update + */ guildUpdate: (before: Guild, after: Guild) => void + /** + * A new Message was created (sent) + * @param message The new Message object + */ messageCreate: (message: Message) => void + /** + * A Message was deleted. + * @param message The Message object + */ messageDelete: (message: Message) => void + /** + * Messages were bulk deleted in a Guild Text Channel + * @param channel Channel in which Messages were deleted + * @param messages Collection of Messages deleted + * @param uncached Set of Messages deleted's IDs which were not cached + */ messageDeleteBulk: ( channel: GuildTextChannel, messages: Collection, uncached: Set ) => void + /** + * A Message was updated. For example content, embed, etc. + * @param before Message object before update + * @param after Message object after update + */ messageUpdate: (before: Message, after: Message) => void + /** + * Reaction was added to a Message + * @param reaction Reaction object + * @param user User who added the reaction + */ messageReactionAdd: (reaction: MessageReaction, user: User) => void + /** + * Reaction was removed fro a Message + * @param reaction Reaction object + * @param user User to who removed the reaction + */ messageReactionRemove: (reaction: MessageReaction, user: User) => void + /** + * All reactions were removed from a Message + * @param message Message from which reactions were removed + */ messageReactionRemoveAll: (message: Message) => void + /** + * All reactions of a single Emoji were removed + * @param message The Message object + * @param emoji The Emoji object + */ messageReactionRemoveEmoji: (message: Message, emoji: Emoji) => void + /** + * A User has started typing in a Text Channel + */ typingStart: ( user: User, - channel: EveryChannelTypes, + channel: TextChannel, at: Date, guildData?: TypingStartGuildData ) => void + /** + * A new Invite was created + * @param invite New Invite object + */ inviteCreate: (invite: Invite) => void + /** + * An Invite was deleted + * @param invite Invite object + */ inviteDelete: (invite: Invite) => void + /** + * A User was updated. For example username, avatar, etc. + * @param before The User object before update + * @param after The User object after update + */ userUpdate: (before: User, after: User) => void + /** + * Client has received credentials for establishing connection to Voice Server + */ voiceServerUpdate: (data: VoiceServerUpdateData) => void + /** + * A User has joined a Voice Channel + */ voiceStateAdd: (state: VoiceState) => void + /** + * A User has left a Voice Channel + */ voiceStateRemove: (state: VoiceState) => void + /** + * Voice State of a User has been updated + * @param before Voice State object before update + * @param after Voice State object after update + */ voiceStateUpdate: (state: VoiceState, after: VoiceState) => void + /** + * A User's presence has been updated + * @param presence New Presence + */ presenceUpdate: (presence: Presence) => void + /** + * Webhooks of a Channel in a Guild has been updated + * @param guild Guild in which Webhooks were updated + * @param channel Channel of which Webhooks were updated + */ webhooksUpdate: (guild: Guild, channel: GuildTextChannel) => void } diff --git a/src/gateway/index.ts b/src/gateway/index.ts index 6311ee9..4f8dacb 100644 --- a/src/gateway/index.ts +++ b/src/gateway/index.ts @@ -252,9 +252,9 @@ class Gateway { const payload: IdentityPayload = { token: this.token, properties: { - $os: Deno.build.os, - $browser: 'harmony', - $device: 'harmony' + $os: this.client.clientProperties.os ?? Deno.build.os, + $browser: this.client.clientProperties.browser ?? 'harmony', + $device: this.client.clientProperties.device ?? 'harmony' }, compress: true, shard: [0, 1], // TODO: Make sharding possible diff --git a/src/models/client.ts b/src/models/client.ts index 13646eb..9e6705b 100644 --- a/src/models/client.ts +++ b/src/models/client.ts @@ -13,6 +13,13 @@ import { ActivityGame, ClientActivity } from '../types/presence.ts' import { ClientEvents } from '../gateway/handlers/index.ts' import { Extension } from './extensions.ts' +/** OS related properties sent with Gateway Identify */ +export interface ClientProperties { + os?: 'darwin' | 'windows' | 'linux' | 'custom_os' | string + browser?: 'harmony' | string + device?: 'harmony' | string +} + /** Some Client Options to modify behaviour */ export interface ClientOptions { /** Token of the Bot/User */ @@ -33,6 +40,8 @@ export interface ClientOptions { reactionCacheLifetime?: number /** Whether to fetch Uncached Message of Reaction or not? */ fetchUncachedReactions?: boolean + /** Client Properties */ + clientProperties?: ClientProperties } /** @@ -61,6 +70,8 @@ export class Client extends EventEmitter { reactionCacheLifetime: number = 3600000 /** Whether to fetch Uncached Message of Reaction or not? */ fetchUncachedReactions: boolean = false + /** Client Properties */ + clientProperties: ClientProperties users: UsersManager = new UsersManager(this) guilds: GuildManager = new GuildManager(this) @@ -113,6 +124,15 @@ export class Client extends EventEmitter { }) this._decoratedEvents = undefined } + + this.clientProperties = + options.clientProperties === undefined + ? { + os: Deno.build.os, + browser: 'harmony', + device: 'harmony' + } + : options.clientProperties } /** diff --git a/src/structures/presence.ts b/src/structures/presence.ts index 0fdac0d..85593fb 100644 --- a/src/structures/presence.ts +++ b/src/structures/presence.ts @@ -50,17 +50,23 @@ export class Presence extends Base { } } +interface StatusPayload extends StatusUpdatePayload { + client_status?: ClientStatus +} + export class ClientPresence { status: StatusType = 'online' activity?: ActivityGame | ActivityGame[] since?: number | null afk?: boolean + clientStatus?: ClientStatus - constructor(data?: ClientActivity | StatusUpdatePayload | ActivityGame) { + constructor(data?: ClientActivity | StatusPayload | ActivityGame) { if (data !== undefined) { if ((data as ClientActivity).activity !== undefined) { Object.assign(this, data) - } else if ((data as StatusUpdatePayload).activities !== undefined) { + } else if ((data as StatusPayload).activities !== undefined) { + this.parse(data as StatusPayload) } else if ((data as ActivityGame).name !== undefined) { if (this.activity === undefined) { this.activity = data as ActivityGame @@ -71,11 +77,12 @@ export class ClientPresence { } } - parse(payload: StatusUpdatePayload): ClientPresence { + parse(payload: StatusPayload): ClientPresence { this.afk = payload.afk this.activity = payload.activities ?? undefined this.since = payload.since this.status = payload.status + // this.clientStatus = payload.client_status return this } @@ -83,12 +90,14 @@ export class ClientPresence { return new ClientPresence().parse(payload) } - create(): StatusUpdatePayload { + create(): StatusPayload { + console.log(this) return { afk: this.afk === undefined ? false : this.afk, activities: this.createActivity(), since: this.since === undefined ? null : this.since, status: this.status === undefined ? 'online' : this.status + // client_status: this.clientStatus } } @@ -144,4 +153,13 @@ export class ClientPresence { this.since = since return this } + + // setClientStatus( + // client: 'desktop' | 'web' | 'mobile', + // status: StatusType + // ): ClientPresence { + // if (this.clientStatus === undefined) this.clientStatus = {} + // this.clientStatus[client] = status + // return this + // } } diff --git a/src/test/index.ts b/src/test/index.ts index 2da0845..3ae10f5 100644 --- a/src/test/index.ts +++ b/src/test/index.ts @@ -2,7 +2,6 @@ import { Client, Intents, Message, - ClientPresence, Member, Role, GuildChannel, @@ -15,10 +14,9 @@ import { import { TOKEN } from './config.ts' const client = new Client({ - presence: new ClientPresence({ - name: 'Pokémon Sword', - type: 'COMPETING' - }) + clientProperties: { + browser: 'Discord iOS' + } // bot: false, // cache: new RedisCacheAdapter({ // hostname: '127.0.0.1', diff --git a/src/types/gateway.ts b/src/types/gateway.ts index 9d9bc29..5a1dd3e 100644 --- a/src/types/gateway.ts +++ b/src/types/gateway.ts @@ -120,11 +120,11 @@ export interface IdentityPayload { } export interface IdentityConnection { - $os: 'darwin' | 'windows' | 'linux' | 'custom os' - $browser: 'harmony' | 'Firefox' - $device: 'harmony' | '' - $referrer?: '' - $referring_domain?: '' + $os: 'darwin' | 'windows' | 'linux' | 'custom os' | string + $browser: 'harmony' | 'Firefox' | string + $device: 'harmony' | string + $referrer?: '' | string + $referring_domain?: '' | string } export interface Resume { From d14fe15d683aaf4d5649f1a4add264023719117d Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Thu, 10 Dec 2020 10:06:36 +0530 Subject: [PATCH 02/10] feat: slash commands --- src/gateway/handlers/index.ts | 4 +- src/gateway/handlers/interactionCreate.ts | 11 +++ src/structures/presence.ts | 1 - src/structures/slash.ts | 60 +++++++++++++ src/test/slash-cmd.ts | 51 +++++++++++ src/test/slash.ts | 16 ++++ src/types/endpoint.ts | 100 ++++++---------------- src/types/gateway.ts | 3 +- src/types/slash.ts | 82 ++++++++++++++++++ 9 files changed, 252 insertions(+), 76 deletions(-) create mode 100644 src/gateway/handlers/interactionCreate.ts create mode 100644 src/structures/slash.ts create mode 100644 src/test/slash-cmd.ts create mode 100644 src/test/slash.ts create mode 100644 src/types/slash.ts diff --git a/src/gateway/handlers/index.ts b/src/gateway/handlers/index.ts index 85e1d75..dbb2737 100644 --- a/src/gateway/handlers/index.ts +++ b/src/gateway/handlers/index.ts @@ -53,6 +53,7 @@ import { EveryChannelTypes, EveryTextChannelTypes } from '../../utils/getChannelByType.ts' +import { interactionCreate } from './interactionCreate.ts' export const gatewayHandlers: { [eventCode in GatewayEvents]: GatewayEventHandler | undefined @@ -93,7 +94,8 @@ export const gatewayHandlers: { USER_UPDATE: userUpdate, VOICE_STATE_UPDATE: voiceStateUpdate, VOICE_SERVER_UPDATE: voiceServerUpdate, - WEBHOOKS_UPDATE: webhooksUpdate + WEBHOOKS_UPDATE: webhooksUpdate, + INTERACTION_CREATE: interactionCreate } export interface EventTypes { diff --git a/src/gateway/handlers/interactionCreate.ts b/src/gateway/handlers/interactionCreate.ts new file mode 100644 index 0000000..7ff74fd --- /dev/null +++ b/src/gateway/handlers/interactionCreate.ts @@ -0,0 +1,11 @@ +import { Interaction } from '../../structures/slash.ts' +import { InteractionPayload } from '../../types/slash.ts' +import { Gateway, GatewayEventHandler } from '../index.ts' + +export const interactionCreate: GatewayEventHandler = async ( + gateway: Gateway, + d: InteractionPayload +) => { + const interaction = new Interaction(gateway.client, d) + gateway.client.emit('interactionCreate', interaction) +} diff --git a/src/structures/presence.ts b/src/structures/presence.ts index 85593fb..a5c86af 100644 --- a/src/structures/presence.ts +++ b/src/structures/presence.ts @@ -91,7 +91,6 @@ export class ClientPresence { } create(): StatusPayload { - console.log(this) return { afk: this.afk === undefined ? false : this.afk, activities: this.createActivity(), diff --git a/src/structures/slash.ts b/src/structures/slash.ts new file mode 100644 index 0000000..c4ead4b --- /dev/null +++ b/src/structures/slash.ts @@ -0,0 +1,60 @@ +import { Client } from '../models/client.ts' +import { INTERACTION_CALLBACK } from '../types/endpoint.ts' +import { MemberPayload } from '../types/guild.ts' +import { + InteractionData, + InteractionPayload, + InteractionResponsePayload, + InteractionResponseType +} from '../types/slash.ts' +import { Embed } from './embed.ts' + +export interface InteractionResponse { + type?: InteractionResponseType + content?: string + embeds?: Embed[] + tts?: boolean + flags?: number +} + +export class Interaction { + client: Client + type: number + token: string + member: MemberPayload + id: string + data: InteractionData + + constructor(client: Client, data: InteractionPayload) { + this.client = client + this.type = data.type + this.token = data.token + this.member = data.member + this.id = data.id + this.data = data.data + } + + async respond(data: InteractionResponse): Promise { + const payload: InteractionResponsePayload = { + type: data.type ?? InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, + data: + data.type === undefined || + data.type === InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE || + data.type === InteractionResponseType.CHANNEL_MESSAGE + ? { + content: data.content ?? '', + embeds: data.embeds, + tts: data.tts ?? false, + flags: data.flags ?? undefined + } + : undefined + } + + await this.client.rest.post( + INTERACTION_CALLBACK(this.id, this.token), + payload + ) + + return this + } +} diff --git a/src/test/slash-cmd.ts b/src/test/slash-cmd.ts new file mode 100644 index 0000000..043b96d --- /dev/null +++ b/src/test/slash-cmd.ts @@ -0,0 +1,51 @@ +import { TOKEN } from './config.ts' + +export const CMD = { + name: 'blep', + description: 'Send a random adorable animal photo', + options: [ + { + name: 'animal', + description: 'The type of animal', + type: 3, + required: true, + choices: [ + { + name: 'Dog', + value: 'animal_dog' + }, + { + name: 'Cat', + value: 'animal_dog' + }, + { + name: 'Penguin', + value: 'animal_penguin' + } + ] + }, + { + name: 'only_smol', + description: 'Whether to show only baby animals', + type: 5, + required: false + } + ] +} + +// fetch('https://discord.com/api/v8/applications/783937840752099332/commands', { +fetch( + 'https://discord.com/api/v8/applications/783937840752099332/guilds/783319033205751809/commands', + { + method: 'POST', + body: JSON.stringify(CMD), + headers: { + 'Content-Type': 'application/json', + Authorization: + // eslint-disable-next-line @typescript-eslint/restrict-plus-operands + 'Bot ' + TOKEN + } + } +) + .then((r) => r.json()) + .then(console.log) diff --git a/src/test/slash.ts b/src/test/slash.ts new file mode 100644 index 0000000..55e65d4 --- /dev/null +++ b/src/test/slash.ts @@ -0,0 +1,16 @@ +import { Client, Intents } from '../../mod.ts' +import { TOKEN } from './config.ts' + +const client = new Client() + +client.on('ready', () => { + console.log('Logged in!') +}) + +client.on('interactionCreate', async (d) => { + await d.respond({ + content: `Hi, ${d.member.user.username}!` + }) +}) + +client.connect(TOKEN, Intents.None) diff --git a/src/types/endpoint.ts b/src/types/endpoint.ts index a34575a..2271340 100644 --- a/src/types/endpoint.ts +++ b/src/types/endpoint.ts @@ -191,81 +191,35 @@ const VOICE_REGIONS = (guildID: string): string => const CLIENT_USER = (): string => `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/users/@me` -export default [ - GUILDS, - GUILD, - GUILD_AUDIT_LOGS, - GUILD_WIDGET, - GUILD_EMOJI, - GUILD_ROLE, - GUILD_ROLES, - GUILD_INTEGRATION, - GUILD_INTEGRATIONS, - GUILD_INTEGARTION_SYNC, - GUILD_WIDGET_IMAGE, - GUILD_BAN, - GUILD_BANS, - GUILD_CHANNEL, - GUILD_CHANNELS, - GUILD_MEMBER, - CLIENT_USER, - GUILD_MEMBERS, - GUILD_MEMBER_ROLE, - GUILD_INVITES, - GUILD_LEAVE, - GUILD_PRUNE, - GUILD_VANITY_URL, - GUILD_NICK, - GUILD_PREVIEW, - CHANNEL, - CHANNELS, - CHANNEL_MESSAGE, - CHANNEL_MESSAGES, - CHANNEL_CROSSPOST, - MESSAGE_REACTIONS, - MESSAGE_REACTION, - MESSAGE_REACTION_ME, - MESSAGE_REACTION_USER, - CHANNEL_BULK_DELETE, - CHANNEL_FOLLOW, - CHANNEL_INVITES, - CHANNEL_PIN, - CHANNEL_PINS, - CHANNEL_PERMISSION, - CHANNEL_TYPING, - GROUP_RECIPIENT, - CURRENT_USER, - CURRENT_USER_GUILDS, - USER_DM, - USER_CONNECTIONS, - LEAVE_GUILD, - USER, - CHANNEL_WEBHOOKS, - GUILD_WEBHOOK, - WEBHOOK, - WEBHOOK_WITH_TOKEN, - SLACK_WEBHOOK, - GITHUB_WEBHOOK, - GATEWAY, - GATEWAY_BOT, - CUSTOM_EMOJI, - GUILD_ICON, - GUILD_SPLASH, - GUILD_DISCOVERY_SPLASH, - GUILD_BANNER, - DEFAULT_USER_AVATAR, - USER_AVATAR, - APPLICATION_ASSET, - ACHIEVEMENT_ICON, - TEAM_ICON, - EMOJI, - GUILD_EMOJIS, - TEMPLATE, - INVITE, - VOICE_REGIONS -] +const APPLICATION_COMMANDS = (id: string): string => + `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/applications/${id}/commands` + +const APPLICATION_COMMAND = (id: string, cmdID: string): string => + `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/applications/${id}/commands/${cmdID}` + +const APPLICATION_GUILD_COMMANDS = (id: string, guildID: string): string => + `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/applications/${id}/guilds/${guildID}/commands` + +const APPLICATION_GUILD_COMMAND = ( + id: string, + guildID: string, + cmdID: string +): string => + `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/applications/${id}/guilds/${guildID}/commands/${cmdID}` + +const WEBHOOK_MESSAGE = (id: string, token: string, msgID: string): string => + `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/webhooks/${id}/${token}/messages/${msgID}` + +const INTERACTION_CALLBACK = (id: string, token: string): string => + `${DISCORD_API_URL}/v${DISCORD_API_VERSION}/interactions/${id}/${token}/callback` export { + INTERACTION_CALLBACK, + APPLICATION_COMMAND, + APPLICATION_GUILD_COMMAND, + WEBHOOK_MESSAGE, + APPLICATION_COMMANDS, + APPLICATION_GUILD_COMMANDS, GUILDS, GUILD, GUILD_AUDIT_LOGS, diff --git a/src/types/gateway.ts b/src/types/gateway.ts index 5a1dd3e..bbd2e48 100644 --- a/src/types/gateway.ts +++ b/src/types/gateway.ts @@ -105,7 +105,8 @@ export enum GatewayEvents { User_Update = 'USER_UPDATE', Voice_Server_Update = 'VOICE_SERVER_UPDATE', Voice_State_Update = 'VOICE_STATE_UPDATE', - Webhooks_Update = 'WEBHOOKS_UPDATE' + Webhooks_Update = 'WEBHOOKS_UPDATE', + Interaction_Create = 'INTERACTION_CREATE' } export interface IdentityPayload { diff --git a/src/types/slash.ts b/src/types/slash.ts new file mode 100644 index 0000000..c296280 --- /dev/null +++ b/src/types/slash.ts @@ -0,0 +1,82 @@ +import { EmbedPayload } from './channel.ts' +import { MemberPayload } from './guild.ts' + +export interface InteractionOption { + name: string + value?: any + options?: any[] +} + +export interface InteractionData { + options: InteractionOption[] + name: string + id: string +} + +export enum InteractionType { + PING = 1, + APPLICATION_COMMAND = 2 +} + +export interface InteractionPayload { + type: InteractionType + token: string + member: MemberPayload + id: string + data: InteractionData +} + +export interface SlashCommandChoice { + name: string + value: string +} + +export enum SlashCommandOptionType { + SUB_COMMAND = 1, + SUB_COMMAND_GROUP = 2, + STRING = 3, + INTEGER = 4, + BOOLEAN = 5, + USER = 6, + CHANNEL = 7, + ROLE = 8 +} + +export interface SlashCommandOption { + name: string + description: string + type: SlashCommandOptionType + required: boolean + choices?: SlashCommandChoice[] +} + +export interface SlashCommandPayload { + name: string + description: string + options: SlashCommandOption[] +} + +export enum InteractionResponseType { + PONG = 1, + ACKNOWLEDGE = 2, + CHANNEL_MESSAGE = 3, + CHANNEL_MESSAGE_WITH_SOURCE = 4, + ACK_WITH_SOURCE = 5 +} + +export interface InteractionResponsePayload { + type: InteractionResponseType + data?: InteractionResponseDataPayload +} + +export interface InteractionResponseDataPayload { + tts?: boolean + content: string + embeds?: EmbedPayload[] + allowed_mentions?: { + parse?: 'everyone' | 'users' | 'roles' + roles?: string[] + users?: string[] + } + flags?: number +} From 6e014b353e79b69e9aee30bd11481bc4210eac08 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Thu, 10 Dec 2020 12:25:52 +0530 Subject: [PATCH 03/10] feat(slash): many new things --- src/gateway/handlers/index.ts | 5 + src/gateway/handlers/interactionCreate.ts | 20 ++- src/models/client.ts | 5 + src/models/slashClient.ts | 144 ++++++++++++++++++++++ src/structures/slash.ts | 31 ++++- src/test/slash.ts | 49 +++++++- src/types/slash.ts | 9 +- 7 files changed, 256 insertions(+), 7 deletions(-) create mode 100644 src/models/slashClient.ts diff --git a/src/gateway/handlers/index.ts b/src/gateway/handlers/index.ts index dbb2737..af1b69d 100644 --- a/src/gateway/handlers/index.ts +++ b/src/gateway/handlers/index.ts @@ -54,6 +54,7 @@ import { EveryTextChannelTypes } from '../../utils/getChannelByType.ts' import { interactionCreate } from './interactionCreate.ts' +import { Interaction } from '../../structures/slash.ts' export const gatewayHandlers: { [eventCode in GatewayEvents]: GatewayEventHandler | undefined @@ -328,4 +329,8 @@ export interface ClientEvents extends EventTypes { * @param channel Channel of which Webhooks were updated */ webhooksUpdate: (guild: Guild, channel: GuildTextChannel) => void + /** + * A Slash Command was triggered + */ + interactionCreate: (interaction: Interaction) => void } diff --git a/src/gateway/handlers/interactionCreate.ts b/src/gateway/handlers/interactionCreate.ts index 7ff74fd..fac882a 100644 --- a/src/gateway/handlers/interactionCreate.ts +++ b/src/gateway/handlers/interactionCreate.ts @@ -1,4 +1,6 @@ +import { Member } from '../../structures/member.ts' import { Interaction } from '../../structures/slash.ts' +import { GuildTextChannel } from '../../structures/textChannel.ts' import { InteractionPayload } from '../../types/slash.ts' import { Gateway, GatewayEventHandler } from '../index.ts' @@ -6,6 +8,22 @@ export const interactionCreate: GatewayEventHandler = async ( gateway: Gateway, d: InteractionPayload ) => { - const interaction = new Interaction(gateway.client, d) + const guild = await gateway.client.guilds.get(d.guild_id) + if (guild === undefined) return + + await guild.members.set(d.member.user.id, d.member) + const member = ((await guild.members.get( + d.member.user.id + )) as unknown) as Member + + const channel = + (await gateway.client.channels.get(d.channel_id)) ?? + (await gateway.client.channels.fetch(d.channel_id)) + + const interaction = new Interaction(gateway.client, d, { + member, + guild, + channel + }) gateway.client.emit('interactionCreate', interaction) } diff --git a/src/models/client.ts b/src/models/client.ts index 9e6705b..033cc1b 100644 --- a/src/models/client.ts +++ b/src/models/client.ts @@ -12,6 +12,7 @@ import { EmojisManager } from '../managers/emojis.ts' import { ActivityGame, ClientActivity } from '../types/presence.ts' import { ClientEvents } from '../gateway/handlers/index.ts' import { Extension } from './extensions.ts' +import { SlashClient } from './slashClient.ts' /** OS related properties sent with Gateway Identify */ export interface ClientProperties { @@ -72,6 +73,8 @@ export class Client extends EventEmitter { fetchUncachedReactions: boolean = false /** Client Properties */ clientProperties: ClientProperties + /** Slash-Commands Management client */ + slash: SlashClient users: UsersManager = new UsersManager(this) guilds: GuildManager = new GuildManager(this) @@ -133,6 +136,8 @@ export class Client extends EventEmitter { device: 'harmony' } : options.clientProperties + + this.slash = new SlashClient(this) } /** diff --git a/src/models/slashClient.ts b/src/models/slashClient.ts new file mode 100644 index 0000000..b6ce775 --- /dev/null +++ b/src/models/slashClient.ts @@ -0,0 +1,144 @@ +import { Guild } from '../structures/guild.ts' +import { Interaction } from '../structures/slash.ts' +import { + APPLICATION_COMMAND, + APPLICATION_COMMANDS, + APPLICATION_GUILD_COMMAND, + APPLICATION_GUILD_COMMANDS +} from '../types/endpoint.ts' +import { + SlashCommandOption, + SlashCommandPartial, + SlashCommandPayload +} from '../types/slash.ts' +import { Collection } from '../utils/collection.ts' +import { Client } from './client.ts' + +export interface SlashOptions { + enabled?: boolean +} + +export class SlashCommand { + id: string + applicationID: string + name: string + description: string + options: SlashCommandOption[] + + constructor(data: SlashCommandPayload) { + this.id = data.id + this.applicationID = data.application_id + this.name = data.name + this.description = data.description + this.options = data.options + } +} + +export class SlashCommands { + client: Client + slash: SlashClient + + constructor(client: Client) { + this.client = client + this.slash = client.slash + } + + /** Get all Global Slash Commands */ + async all(): Promise> { + const col = new Collection() + + const res = (await this.client.rest.get( + APPLICATION_COMMANDS(this.client.user?.id as string) + )) as SlashCommandPayload[] + if (!Array.isArray(res)) return col + + for (const raw of res) { + col.set(raw.id, new SlashCommand(raw)) + } + + return col + } + + /** Get a Guild's Slash Commands */ + async guild( + guild: Guild | string + ): Promise> { + const col = new Collection() + + const res = (await this.client.rest.get( + APPLICATION_GUILD_COMMANDS( + this.client.user?.id as string, + typeof guild === 'string' ? guild : guild.id + ) + )) as SlashCommandPayload[] + if (!Array.isArray(res)) return col + + for (const raw of res) { + col.set(raw.id, new SlashCommand(raw)) + } + + return col + } + + /** Create a Slash Command (global or Guild) */ + async create( + data: SlashCommandPartial, + guild?: Guild | string + ): Promise { + const payload = await this.client.rest.post( + guild === undefined + ? APPLICATION_COMMANDS(this.client.user?.id as string) + : APPLICATION_GUILD_COMMANDS( + this.client.user?.id as string, + typeof guild === 'string' ? guild : guild.id + ), + data + ) + + return new SlashCommand(payload) + } + + async edit( + id: string, + data: SlashCommandPayload, + guild?: Guild + ): Promise { + await this.client.rest.patch( + guild === undefined + ? APPLICATION_COMMAND(this.client.user?.id as string, id) + : APPLICATION_GUILD_COMMAND( + this.client.user?.id as string, + typeof guild === 'string' ? guild : guild.id, + id + ), + data + ) + return this + } +} + +export class SlashClient { + client: Client + enabled: boolean = true + commands: SlashCommands + + constructor(client: Client, options?: SlashOptions) { + this.client = client + this.commands = new SlashCommands(client) + + if (options !== undefined) { + this.enabled = options.enabled ?? true + } + + this.client.on('interactionCreate', (interaction) => + this.process(interaction) + ) + } + + process(interaction: Interaction): any {} + + handle(fn: (interaction: Interaction) => any): SlashClient { + this.process = fn + return this + } +} diff --git a/src/structures/slash.ts b/src/structures/slash.ts index c4ead4b..dd41e10 100644 --- a/src/structures/slash.ts +++ b/src/structures/slash.ts @@ -1,6 +1,5 @@ import { Client } from '../models/client.ts' import { INTERACTION_CALLBACK } from '../types/endpoint.ts' -import { MemberPayload } from '../types/guild.ts' import { InteractionData, InteractionPayload, @@ -8,6 +7,10 @@ import { InteractionResponseType } from '../types/slash.ts' import { Embed } from './embed.ts' +import { Guild } from './guild.ts' +import { Member } from './member.ts' +import { GuildTextChannel } from './textChannel.ts' +import { User } from './user.ts' export interface InteractionResponse { type?: InteractionResponseType @@ -21,17 +24,37 @@ export class Interaction { client: Client type: number token: string - member: MemberPayload id: string data: InteractionData + channel: GuildTextChannel + guild: Guild + member: Member - constructor(client: Client, data: InteractionPayload) { + constructor( + client: Client, + data: InteractionPayload, + others: { + channel: GuildTextChannel + guild: Guild + member: Member + } + ) { this.client = client this.type = data.type this.token = data.token - this.member = data.member + this.member = others.member this.id = data.id this.data = data.data + this.guild = others.guild + this.channel = others.channel + } + + get user(): User { + return this.member.user + } + + get name(): string { + return this.data.name } async respond(data: InteractionResponse): Promise { diff --git a/src/test/slash.ts b/src/test/slash.ts index 55e65d4..992ab3f 100644 --- a/src/test/slash.ts +++ b/src/test/slash.ts @@ -1,15 +1,62 @@ import { Client, Intents } from '../../mod.ts' +import { SlashCommandOptionType } from '../types/slash.ts' import { TOKEN } from './config.ts' const client = new Client() client.on('ready', () => { console.log('Logged in!') + client.slash.commands + .create( + { + name: 'eval', + description: 'Run some JS code!', + options: [ + { + name: 'code', + description: 'Code to run', + type: SlashCommandOptionType.STRING, + required: true + } + ] + }, + '783319033205751809' + ) + .then(console.log) }) client.on('interactionCreate', async (d) => { + if (d.name === 'eval') { + if (d.user.id !== '422957901716652033') { + d.respond({ + content: 'This command can only be used by owner!' + }) + } else { + const code = d.data.options.find((e) => e.name === 'code') + ?.value as string + try { + // eslint-disable-next-line no-eval + let evaled = eval(code) + if (evaled instanceof Promise) evaled = await evaled + if (typeof evaled === 'object') evaled = Deno.inspect(evaled) + let res = `${evaled}`.substring(0, 1990) + while (client.token !== undefined && res.includes(client.token)) { + res = res.replace(client.token, '[REMOVED]') + } + d.respond({ + content: '```js\n' + `${res}` + '\n```' + }) + } catch (e) { + d.respond({ + content: '```js\n' + `${e.stack}` + '\n```' + }) + } + } + return + } await d.respond({ - content: `Hi, ${d.member.user.username}!` + content: `Hi, ${d.member.user.username}!`, + flags: 64 }) }) diff --git a/src/types/slash.ts b/src/types/slash.ts index c296280..4ec0034 100644 --- a/src/types/slash.ts +++ b/src/types/slash.ts @@ -24,6 +24,8 @@ export interface InteractionPayload { member: MemberPayload id: string data: InteractionData + guild_id: string + channel_id: string } export interface SlashCommandChoice { @@ -50,12 +52,17 @@ export interface SlashCommandOption { choices?: SlashCommandChoice[] } -export interface SlashCommandPayload { +export interface SlashCommandPartial { name: string description: string options: SlashCommandOption[] } +export interface SlashCommandPayload extends SlashCommandPartial { + id: string + application_id: string +} + export enum InteractionResponseType { PONG = 1, ACKNOWLEDGE = 2, From b0d6092c25dd23e795dea52c93ab6a99c1bfa62a Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Thu, 10 Dec 2020 13:49:43 +0530 Subject: [PATCH 04/10] feat(slash): more... --- src/models/rest.ts | 4 ++-- src/test/slash.ts | 35 ++++++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/models/rest.ts b/src/models/rest.ts index bac7a56..0d9ccd0 100644 --- a/src/models/rest.ts +++ b/src/models/rest.ts @@ -277,11 +277,11 @@ export class RESTManager { message: body?.message, errors: Object.fromEntries( Object.entries( - body?.errors as { + (body?.errors as { [name: string]: { _errors: Array<{ code: string; message: string }> } - } + }) ?? {} ).map((entry) => { return [entry[0], entry[1]._errors] }) diff --git a/src/test/slash.ts b/src/test/slash.ts index 992ab3f..b4e6192 100644 --- a/src/test/slash.ts +++ b/src/test/slash.ts @@ -1,4 +1,5 @@ import { Client, Intents } from '../../mod.ts' +import { Embed } from '../structures/embed.ts' import { SlashCommandOptionType } from '../types/slash.ts' import { TOKEN } from './config.ts' @@ -9,12 +10,12 @@ client.on('ready', () => { client.slash.commands .create( { - name: 'eval', - description: 'Run some JS code!', + name: 'send', + description: 'Send a Message through Bot!', options: [ { - name: 'code', - description: 'Code to run', + name: 'content', + description: 'Message to send', type: SlashCommandOptionType.STRING, required: true } @@ -45,7 +46,7 @@ client.on('interactionCreate', async (d) => { } d.respond({ content: '```js\n' + `${res}` + '\n```' - }) + }).catch(() => {}) } catch (e) { d.respond({ content: '```js\n' + `${e.stack}` + '\n```' @@ -53,10 +54,30 @@ client.on('interactionCreate', async (d) => { } } return + } else if (d.name === 'hug') { + const id = d.data.options.find((e) => e.name === 'user')?.value as string + const user = (await client.users.get(id)) ?? (await client.users.fetch(id)) + const url = await fetch('https://nekos.life/api/v2/img/hug') + .then((r) => r.json()) + .then((e) => e.url) + + d.respond({ + embeds: [ + new Embed() + .setTitle(`${d.user.username} hugged ${user?.username}!`) + .setImage({ url }) + .setColor(0x2f3136) + ] + }) + return + } else if (d.name === 'send') { + d.respond({ + content: d.data.options.find((e) => e.name === 'content')?.value as string + }) + return } await d.respond({ - content: `Hi, ${d.member.user.username}!`, - flags: 64 + content: `Hi, ${d.member.user.username}! You used /${d.name}` }) }) From e1281736ec577b4bba2091a029148ab7a1d22e4b Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Thu, 10 Dec 2020 14:40:00 +0530 Subject: [PATCH 05/10] feat(slash): add @slash decorator support --- mod.ts | 3 ++ src/models/client.ts | 23 ++++++++- src/models/slashClient.ts | 102 ++++++++++++++++++++++++++++++++------ src/test/slash.ts | 58 ++++++++-------------- 4 files changed, 134 insertions(+), 52 deletions(-) diff --git a/mod.ts b/mod.ts index 9606984..40c4ccb 100644 --- a/mod.ts +++ b/mod.ts @@ -4,6 +4,7 @@ export { Base } from './src/structures/base.ts' export { Gateway } from './src/gateway/index.ts' export type { ClientEvents } from './src/gateway/handlers/index.ts' export * from './src/models/client.ts' +export * from './src/models/slashClient.ts' export { RESTManager } from './src/models/rest.ts' export * from './src/models/cacheAdapter.ts' export { @@ -29,6 +30,8 @@ export { GatewayCache } from './src/managers/gatewayCache.ts' export { GuildChannelsManager } from './src/managers/guildChannels.ts' export type { GuildChannel } from './src/managers/guildChannels.ts' export { GuildManager } from './src/managers/guilds.ts' +export * from './src/structures/slash.ts' +export * from './src/types/slash.ts' export { GuildEmojisManager } from './src/managers/guildEmojis.ts' export { MembersManager } from './src/managers/members.ts' export { MessageReactionsManager } from './src/managers/messageReactions.ts' diff --git a/src/models/client.ts b/src/models/client.ts index 033cc1b..98dda1d 100644 --- a/src/models/client.ts +++ b/src/models/client.ts @@ -13,6 +13,7 @@ import { ActivityGame, ClientActivity } from '../types/presence.ts' import { ClientEvents } from '../gateway/handlers/index.ts' import { Extension } from './extensions.ts' import { SlashClient } from './slashClient.ts' +import { Interaction } from '../structures/slash.ts' /** OS related properties sent with Gateway Identify */ export interface ClientProperties { @@ -43,6 +44,8 @@ export interface ClientOptions { fetchUncachedReactions?: boolean /** Client Properties */ clientProperties?: ClientProperties + /** Enable/Disable Slash Commands Integration (enabled by default) */ + enableSlash?: boolean } /** @@ -86,6 +89,11 @@ export class Client extends EventEmitter { /** Client's presence. Startup one if set before connecting */ presence: ClientPresence = new ClientPresence() _decoratedEvents?: { [name: string]: (...args: any[]) => any } + _decoratedSlash?: Array<{ + name: string + guild?: string + handler: (interaction: Interaction) => any + }> private readonly _untypedOn = this.on @@ -137,7 +145,9 @@ export class Client extends EventEmitter { } : options.clientProperties - this.slash = new SlashClient(this) + this.slash = new SlashClient(this, { + enabled: options.enableSlash + }) } /** @@ -200,3 +210,14 @@ export function event(name?: string) { client._decoratedEvents[name === undefined ? prop : name] = listener } } + +export function slash(name?: string, guild?: string) { + return function (client: Client, prop: string) { + if (client._decoratedSlash === undefined) client._decoratedSlash = [] + client._decoratedSlash.push({ + name: name ?? prop, + guild, + handler: (client as { [name: string]: any })[prop] + }) + } +} diff --git a/src/models/slashClient.ts b/src/models/slashClient.ts index b6ce775..b008b56 100644 --- a/src/models/slashClient.ts +++ b/src/models/slashClient.ts @@ -19,22 +19,33 @@ export interface SlashOptions { } export class SlashCommand { + slash: SlashCommandsManager id: string applicationID: string name: string description: string options: SlashCommandOption[] + _guild?: string - constructor(data: SlashCommandPayload) { + constructor(manager: SlashCommandsManager, data: SlashCommandPayload) { + this.slash = manager this.id = data.id this.applicationID = data.application_id this.name = data.name this.description = data.description this.options = data.options } + + async delete(): Promise { + await this.slash.delete(this.id, this._guild) + } + + async edit(data: SlashCommandPartial): Promise { + await this.slash.edit(this.id, data, this._guild) + } } -export class SlashCommands { +export class SlashCommandsManager { client: Client slash: SlashClient @@ -53,7 +64,8 @@ export class SlashCommands { if (!Array.isArray(res)) return col for (const raw of res) { - col.set(raw.id, new SlashCommand(raw)) + const cmd = new SlashCommand(this, raw) + col.set(raw.id, cmd) } return col @@ -74,7 +86,9 @@ export class SlashCommands { if (!Array.isArray(res)) return col for (const raw of res) { - col.set(raw.id, new SlashCommand(raw)) + const cmd = new SlashCommand(this, raw) + cmd._guild = typeof guild === 'string' ? guild : guild.id + col.set(raw.id, cmd) } return col @@ -95,14 +109,19 @@ export class SlashCommands { data ) - return new SlashCommand(payload) + const cmd = new SlashCommand(this, payload) + cmd._guild = + typeof guild === 'string' || guild === undefined ? guild : guild.id + + return cmd } + /** Edit a Slash Command (global or Guild) */ async edit( id: string, - data: SlashCommandPayload, - guild?: Guild - ): Promise { + data: SlashCommandPartial, + guild?: Guild | string + ): Promise { await this.client.rest.patch( guild === undefined ? APPLICATION_COMMAND(this.client.user?.id as string, id) @@ -115,30 +134,85 @@ export class SlashCommands { ) return this } + + /** Delete a Slash Command (global or Guild) */ + async delete( + id: string, + guild?: Guild | string + ): Promise { + await this.client.rest.delete( + guild === undefined + ? APPLICATION_COMMAND(this.client.user?.id as string, id) + : APPLICATION_GUILD_COMMAND( + this.client.user?.id as string, + typeof guild === 'string' ? guild : guild.id, + id + ) + ) + return this + } +} + +export type SlashCommandHandlerCallback = (interaction: Interaction) => any +export interface SlashCommandHandler { + name: string + guild?: string + handler: SlashCommandHandlerCallback } export class SlashClient { client: Client enabled: boolean = true - commands: SlashCommands + commands: SlashCommandsManager + handlers: SlashCommandHandler[] = [] constructor(client: Client, options?: SlashOptions) { this.client = client - this.commands = new SlashCommands(client) + this.commands = new SlashCommandsManager(client) if (options !== undefined) { this.enabled = options.enabled ?? true } + if (this.client._decoratedSlash !== undefined) { + this.client._decoratedSlash.forEach((e) => { + this.handlers.push(e) + }) + } + this.client.on('interactionCreate', (interaction) => this.process(interaction) ) } - process(interaction: Interaction): any {} - - handle(fn: (interaction: Interaction) => any): SlashClient { - this.process = fn + /** Adds a new Slash Command Handler */ + handle( + name: string, + handler: SlashCommandHandlerCallback, + guild?: string + ): SlashClient { + this.handlers.push({ + name, + guild, + handler + }) return this } + + process(interaction: Interaction): any { + if (!this.enabled) return + + let cmd + + if (interaction.guild !== undefined) + cmd = + this.handlers.find( + (e) => e.guild !== undefined && e.name === interaction.name + ) ?? this.handlers.find((e) => e.name === interaction.name) + else cmd = this.handlers.find((e) => e.name === interaction.name) + + if (cmd === undefined) return + + cmd.handler(interaction) + } } diff --git a/src/test/slash.ts b/src/test/slash.ts index b4e6192..688a855 100644 --- a/src/test/slash.ts +++ b/src/test/slash.ts @@ -1,33 +1,23 @@ -import { Client, Intents } from '../../mod.ts' +import { Client, Intents, event, slash } from '../../mod.ts' import { Embed } from '../structures/embed.ts' -import { SlashCommandOptionType } from '../types/slash.ts' +import { Interaction } from '../structures/slash.ts' import { TOKEN } from './config.ts' -const client = new Client() +export class MyClient extends Client { + @event() + ready(): void { + console.log(`Logged in as ${this.user?.tag}!`) + } -client.on('ready', () => { - console.log('Logged in!') - client.slash.commands - .create( - { - name: 'send', - description: 'Send a Message through Bot!', - options: [ - { - name: 'content', - description: 'Message to send', - type: SlashCommandOptionType.STRING, - required: true - } - ] - }, - '783319033205751809' - ) - .then(console.log) -}) + @slash() + send(d: Interaction): void { + d.respond({ + content: d.data.options.find((e) => e.name === 'content')?.value + }) + } -client.on('interactionCreate', async (d) => { - if (d.name === 'eval') { + @slash() + async eval(d: Interaction): Promise { if (d.user.id !== '422957901716652033') { d.respond({ content: 'This command can only be used by owner!' @@ -53,8 +43,10 @@ client.on('interactionCreate', async (d) => { }) } } - return - } else if (d.name === 'hug') { + } + + @slash() + async hug(d: Interaction): Promise { const id = d.data.options.find((e) => e.name === 'user')?.value as string const user = (await client.users.get(id)) ?? (await client.users.fetch(id)) const url = await fetch('https://nekos.life/api/v2/img/hug') @@ -69,16 +61,8 @@ client.on('interactionCreate', async (d) => { .setColor(0x2f3136) ] }) - return - } else if (d.name === 'send') { - d.respond({ - content: d.data.options.find((e) => e.name === 'content')?.value as string - }) - return } - await d.respond({ - content: `Hi, ${d.member.user.username}! You used /${d.name}` - }) -}) +} +const client = new MyClient() client.connect(TOKEN, Intents.None) From bb662267cbe92d5a3da10e6bbeaf38c54cb06120 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Thu, 10 Dec 2020 16:12:03 +0530 Subject: [PATCH 06/10] feat(slash): add temp?: boolean --- src/structures/slash.ts | 3 ++- src/test/slash.ts | 30 +++++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/structures/slash.ts b/src/structures/slash.ts index dd41e10..e8add8e 100644 --- a/src/structures/slash.ts +++ b/src/structures/slash.ts @@ -18,6 +18,7 @@ export interface InteractionResponse { embeds?: Embed[] tts?: boolean flags?: number + temp?: boolean } export class Interaction { @@ -68,7 +69,7 @@ export class Interaction { content: data.content ?? '', embeds: data.embeds, tts: data.tts ?? false, - flags: data.flags ?? undefined + flags: data.temp === true ? 64 : data.flags ?? undefined } : undefined } diff --git a/src/test/slash.ts b/src/test/slash.ts index 688a855..5a1c3b9 100644 --- a/src/test/slash.ts +++ b/src/test/slash.ts @@ -18,7 +18,10 @@ export class MyClient extends Client { @slash() async eval(d: Interaction): Promise { - if (d.user.id !== '422957901716652033') { + if ( + d.user.id !== '422957901716652033' && + d.user.id !== '682849186227552266' + ) { d.respond({ content: 'This command can only be used by owner!' }) @@ -62,6 +65,31 @@ export class MyClient extends Client { ] }) } + + @slash() + async kiss(d: Interaction): Promise { + const id = d.data.options.find((e) => e.name === 'user')?.value as string + const user = (await client.users.get(id)) ?? (await client.users.fetch(id)) + const url = await fetch('https://nekos.life/api/v2/img/kiss') + .then((r) => r.json()) + .then((e) => e.url) + + d.respond({ + embeds: [ + new Embed() + .setTitle(`${d.user.username} kissed ${user?.username}!`) + .setImage({ url }) + .setColor(0x2f3136) + ] + }) + } + + @slash('ping') + pingCmd(d: Interaction): void { + d.respond({ + content: `Pong!` + }) + } } const client = new MyClient() From 97fce78953bf9f44ef005adc462a383d29b1a4b5 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 11 Dec 2020 15:47:05 +0530 Subject: [PATCH 07/10] feat(slash): try add new methods --- src/{test => structures}/cmds/addemoji.ts | 0 src/{test => structures}/cmds/eval.ts | 0 src/{test => structures}/cmds/join.ts | 0 src/{test => structures}/cmds/leave.ts | 0 src/{test => structures}/cmds/mentions.ts | 0 src/{test => structures}/cmds/ping.ts | 0 src/{test => structures}/cmds/userinfo.ts | 0 src/structures/slash.ts | 145 +++++++++++++++++++++- src/structures/webhook.ts | 37 ++++++ 9 files changed, 179 insertions(+), 3 deletions(-) rename src/{test => structures}/cmds/addemoji.ts (100%) rename src/{test => structures}/cmds/eval.ts (100%) rename src/{test => structures}/cmds/join.ts (100%) rename src/{test => structures}/cmds/leave.ts (100%) rename src/{test => structures}/cmds/mentions.ts (100%) rename src/{test => structures}/cmds/ping.ts (100%) rename src/{test => structures}/cmds/userinfo.ts (100%) diff --git a/src/test/cmds/addemoji.ts b/src/structures/cmds/addemoji.ts similarity index 100% rename from src/test/cmds/addemoji.ts rename to src/structures/cmds/addemoji.ts diff --git a/src/test/cmds/eval.ts b/src/structures/cmds/eval.ts similarity index 100% rename from src/test/cmds/eval.ts rename to src/structures/cmds/eval.ts diff --git a/src/test/cmds/join.ts b/src/structures/cmds/join.ts similarity index 100% rename from src/test/cmds/join.ts rename to src/structures/cmds/join.ts diff --git a/src/test/cmds/leave.ts b/src/structures/cmds/leave.ts similarity index 100% rename from src/test/cmds/leave.ts rename to src/structures/cmds/leave.ts diff --git a/src/test/cmds/mentions.ts b/src/structures/cmds/mentions.ts similarity index 100% rename from src/test/cmds/mentions.ts rename to src/structures/cmds/mentions.ts diff --git a/src/test/cmds/ping.ts b/src/structures/cmds/ping.ts similarity index 100% rename from src/test/cmds/ping.ts rename to src/structures/cmds/ping.ts diff --git a/src/test/cmds/userinfo.ts b/src/structures/cmds/userinfo.ts similarity index 100% rename from src/test/cmds/userinfo.ts rename to src/structures/cmds/userinfo.ts diff --git a/src/structures/slash.ts b/src/structures/slash.ts index e8add8e..c3a5712 100644 --- a/src/structures/slash.ts +++ b/src/structures/slash.ts @@ -1,5 +1,6 @@ import { Client } from '../models/client.ts' -import { INTERACTION_CALLBACK } from '../types/endpoint.ts' +import { MessageOption } from '../types/channel.ts' +import { INTERACTION_CALLBACK, WEBHOOK_MESSAGE } from '../types/endpoint.ts' import { InteractionData, InteractionPayload, @@ -9,8 +10,18 @@ import { import { Embed } from './embed.ts' import { Guild } from './guild.ts' import { Member } from './member.ts' -import { GuildTextChannel } from './textChannel.ts' +import { Message } from './message.ts' +import { GuildTextChannel, TextChannel } from './textChannel.ts' import { User } from './user.ts' +import { Webhook } from './webhook.ts' + +interface WebhookMessageOptions extends MessageOption { + embeds?: Embed[] + name?: string + avatar?: string +} + +type AllWebhookMessageOptions = string | WebhookMessageOptions export interface InteractionResponse { type?: InteractionResponseType @@ -19,6 +30,12 @@ export interface InteractionResponse { tts?: boolean flags?: number temp?: boolean + allowedMentions?: { + parse?: string + roles?: string[] + users?: string[] + everyone?: boolean + } } export class Interaction { @@ -30,6 +47,7 @@ export class Interaction { channel: GuildTextChannel guild: Guild member: Member + _savedHook?: Webhook constructor( client: Client, @@ -58,6 +76,10 @@ export class Interaction { return this.data.name } + option(name: string): any { + return this.data.options.find((e) => e.name === name)?.value + } + async respond(data: InteractionResponse): Promise { const payload: InteractionResponsePayload = { type: data.type ?? InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, @@ -69,7 +91,8 @@ export class Interaction { content: data.content ?? '', embeds: data.embeds, tts: data.tts ?? false, - flags: data.temp === true ? 64 : data.flags ?? undefined + flags: data.temp === true ? 64 : data.flags ?? undefined, + allowed_mentions: (data.allowedMentions ?? undefined) as any } : undefined } @@ -81,4 +104,120 @@ export class Interaction { return this } + + async editResponse(data: { + content?: string + embeds?: Embed[] + }): Promise { + const url = WEBHOOK_MESSAGE( + this.client.user?.id as string, + this.token, + '@original' + ) + await this.client.rest.patch(url, { + content: data.content ?? '', + embeds: data.embeds ?? [] + }) + return this + } + + get url(): string { + return `https://discord.com/api/v8/webhooks/${this.client.user?.id}/${this.token}` + } + + async send( + text?: string | AllWebhookMessageOptions, + option?: AllWebhookMessageOptions + ): Promise { + if (typeof text === 'object') { + option = text + text = undefined + } + + if (text === undefined && option === undefined) { + throw new Error('Either text or option is necessary.') + } + + if (option instanceof Embed) + option = { + embeds: [option] + } + + const payload: any = { + content: text, + embeds: + (option as WebhookMessageOptions)?.embed !== undefined + ? [(option as WebhookMessageOptions).embed] + : (option as WebhookMessageOptions)?.embeds !== undefined + ? (option as WebhookMessageOptions).embeds + : undefined, + file: (option as WebhookMessageOptions)?.file, + tts: (option as WebhookMessageOptions)?.tts, + allowed_mentions: (option as WebhookMessageOptions)?.allowedMentions + } + + if ((option as WebhookMessageOptions)?.name !== undefined) { + payload.username = (option as WebhookMessageOptions)?.name + } + + if ((option as WebhookMessageOptions)?.avatar !== undefined) { + payload.avatar = (option as WebhookMessageOptions)?.avatar + } + + if ( + payload.embeds !== undefined && + payload.embeds instanceof Array && + payload.embeds.length > 10 + ) + throw new Error( + `Cannot send more than 10 embeds through Interaction Webhook` + ) + + const resp = await this.client.rest.post(`${this.url}?wait=true`, payload) + + const res = new Message( + this.client, + resp, + (this as unknown) as TextChannel, + (this as unknown) as User + ) + await res.mentions.fromPayload(resp) + return res + } + + async editMessage( + msg: Message | string, + data: { + content?: string + embeds?: Embed[] + file?: any + allowed_mentions?: { + parse?: string + roles?: string[] + users?: string[] + everyone?: boolean + } + } + ): Promise { + await this.client.rest.patch( + WEBHOOK_MESSAGE( + this.client.user?.id as string, + this.token ?? this.client.token, + typeof msg === 'string' ? msg : msg.id + ), + data + ) + return this + } + + async deleteMessage(msg: Message | string): Promise { + await this.client.rest.delete( + WEBHOOK_MESSAGE( + this.client.user?.id as string, + this.token ?? this.client.token, + typeof msg === 'string' ? msg : msg.id + ) + ) + return this + } } diff --git a/src/structures/webhook.ts b/src/structures/webhook.ts index ccc32f6..dd5dc8c 100644 --- a/src/structures/webhook.ts +++ b/src/structures/webhook.ts @@ -12,6 +12,7 @@ import { Message } from './message.ts' import { TextChannel } from './textChannel.ts' import { User } from './user.ts' import { fetchAuto } from 'https://raw.githubusercontent.com/DjDeveloperr/fetch-base64/main/mod.ts' +import { WEBHOOK_MESSAGE } from '../types/endpoint.ts' export interface WebhookMessageOptions extends MessageOption { embeds?: Embed[] @@ -191,4 +192,40 @@ export class Webhook { if (resp.response.status !== 204) return false else return true } + + async editMessage( + message: string | Message, + data: { + content?: string + embeds?: Embed[] + file?: any + allowed_mentions?: { + parse?: string + roles?: string[] + users?: string[] + everyone?: boolean + } + } + ): Promise { + await this.client?.rest.patch( + WEBHOOK_MESSAGE( + this.id, + (this.token ?? this.client.token) as string, + typeof message === 'string' ? message : message.id + ), + data + ) + return this + } + + async deleteMessage(message: string | Message): Promise { + await this.client?.rest.delete( + WEBHOOK_MESSAGE( + this.id, + (this.token ?? this.client.token) as string, + typeof message === 'string' ? message : message.id + ) + ) + return this + } } From e6be50cdc7ee50bd38103a706f043e2a0f15e6e0 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Fri, 11 Dec 2020 15:47:33 +0530 Subject: [PATCH 08/10] fix --- src/{structures => test}/cmds/addemoji.ts | 0 src/{structures => test}/cmds/eval.ts | 0 src/{structures => test}/cmds/join.ts | 0 src/{structures => test}/cmds/leave.ts | 0 src/{structures => test}/cmds/mentions.ts | 0 src/{structures => test}/cmds/ping.ts | 0 src/{structures => test}/cmds/userinfo.ts | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename src/{structures => test}/cmds/addemoji.ts (100%) rename src/{structures => test}/cmds/eval.ts (100%) rename src/{structures => test}/cmds/join.ts (100%) rename src/{structures => test}/cmds/leave.ts (100%) rename src/{structures => test}/cmds/mentions.ts (100%) rename src/{structures => test}/cmds/ping.ts (100%) rename src/{structures => test}/cmds/userinfo.ts (100%) diff --git a/src/structures/cmds/addemoji.ts b/src/test/cmds/addemoji.ts similarity index 100% rename from src/structures/cmds/addemoji.ts rename to src/test/cmds/addemoji.ts diff --git a/src/structures/cmds/eval.ts b/src/test/cmds/eval.ts similarity index 100% rename from src/structures/cmds/eval.ts rename to src/test/cmds/eval.ts diff --git a/src/structures/cmds/join.ts b/src/test/cmds/join.ts similarity index 100% rename from src/structures/cmds/join.ts rename to src/test/cmds/join.ts diff --git a/src/structures/cmds/leave.ts b/src/test/cmds/leave.ts similarity index 100% rename from src/structures/cmds/leave.ts rename to src/test/cmds/leave.ts diff --git a/src/structures/cmds/mentions.ts b/src/test/cmds/mentions.ts similarity index 100% rename from src/structures/cmds/mentions.ts rename to src/test/cmds/mentions.ts diff --git a/src/structures/cmds/ping.ts b/src/test/cmds/ping.ts similarity index 100% rename from src/structures/cmds/ping.ts rename to src/test/cmds/ping.ts diff --git a/src/structures/cmds/userinfo.ts b/src/test/cmds/userinfo.ts similarity index 100% rename from src/structures/cmds/userinfo.ts rename to src/test/cmds/userinfo.ts From 3dcf57c6587ef245203e06841e867478288bb9c0 Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Sat, 12 Dec 2020 17:57:35 +0530 Subject: [PATCH 09/10] feat(slash): add SlashModule and sub command support --- src/models/client.ts | 31 +++++++++++++++++++++++++------ src/models/commandClient.ts | 16 ++++++++++++---- src/models/slashClient.ts | 6 +++++- src/models/slashModule.ts | 18 ++++++++++++++++++ src/structures/slash.ts | 12 +++++++++++- src/types/slash.ts | 3 ++- 6 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 src/models/slashModule.ts diff --git a/src/models/client.ts b/src/models/client.ts index 98dda1d..0b3d4da 100644 --- a/src/models/client.ts +++ b/src/models/client.ts @@ -14,6 +14,7 @@ import { ClientEvents } from '../gateway/handlers/index.ts' import { Extension } from './extensions.ts' import { SlashClient } from './slashClient.ts' import { Interaction } from '../structures/slash.ts' +import { SlashModule } from './slashModule.ts' /** OS related properties sent with Gateway Identify */ export interface ClientProperties { @@ -95,6 +96,8 @@ export class Client extends EventEmitter { handler: (interaction: Interaction) => any }> + _decoratedSlashModules?: SlashModule[] + private readonly _untypedOn = this.on private readonly _untypedEmit = this.emit @@ -206,18 +209,34 @@ export function event(name?: string) { const listener = ((client as unknown) as { [name: string]: (...args: any[]) => any })[prop] + if (typeof listener !== 'function') + throw new Error('@event decorator requires a function') if (client._decoratedEvents === undefined) client._decoratedEvents = {} client._decoratedEvents[name === undefined ? prop : name] = listener } } export function slash(name?: string, guild?: string) { - return function (client: Client, prop: string) { + return function (client: Client | SlashModule, prop: string) { if (client._decoratedSlash === undefined) client._decoratedSlash = [] - client._decoratedSlash.push({ - name: name ?? prop, - guild, - handler: (client as { [name: string]: any })[prop] - }) + const item = (client as { [name: string]: any })[prop] + if (typeof item !== 'function') { + client._decoratedSlash.push(item) + } else + client._decoratedSlash.push({ + name: name ?? prop, + guild, + handler: item + }) + } +} + +export function slashModule() { + return function (client: Client, prop: string) { + if (client._decoratedSlashModules === undefined) + client._decoratedSlashModules = [] + + const mod = ((client as unknown) as { [key: string]: any })[prop] + client._decoratedSlashModules.push(mod) } } diff --git a/src/models/commandClient.ts b/src/models/commandClient.ts index 472e4f8..7fba315 100644 --- a/src/models/commandClient.ts +++ b/src/models/commandClient.ts @@ -383,18 +383,26 @@ export class CommandClient extends Client implements CommandClientOptions { export function command(options?: CommandOptions) { return function (target: CommandClient | Extension, name: string) { + if (target._decoratedCommands === undefined) target._decoratedCommands = {} + + const prop = ((target as unknown) as { + [name: string]: (ctx: CommandContext) => any + })[name] + + if (prop instanceof Command) { + target._decoratedCommands[prop.name] = prop + return + } + const command = new Command() command.name = name - command.execute = ((target as unknown) as { - [name: string]: (ctx: CommandContext) => any - })[name] + command.execute = prop if (options !== undefined) Object.assign(command, options) if (target instanceof Extension) command.extension = target - if (target._decoratedCommands === undefined) target._decoratedCommands = {} target._decoratedCommands[command.name] = command } } diff --git a/src/models/slashClient.ts b/src/models/slashClient.ts index b008b56..c397dde 100644 --- a/src/models/slashClient.ts +++ b/src/models/slashClient.ts @@ -7,6 +7,7 @@ import { APPLICATION_GUILD_COMMANDS } from '../types/endpoint.ts' import { + InteractionType, SlashCommandOption, SlashCommandPartial, SlashCommandPayload @@ -199,9 +200,12 @@ export class SlashClient { return this } - process(interaction: Interaction): any { + /** Process an incoming Slash Command (interaction) */ + private process(interaction: Interaction): void { if (!this.enabled) return + if (interaction.type !== InteractionType.APPLICATION_COMMAND) return + let cmd if (interaction.guild !== undefined) diff --git a/src/models/slashModule.ts b/src/models/slashModule.ts new file mode 100644 index 0000000..c44368e --- /dev/null +++ b/src/models/slashModule.ts @@ -0,0 +1,18 @@ +import { SlashCommandHandler } from './slashClient.ts' + +export class SlashModule { + name: string = '' + commands: SlashCommandHandler[] = [] + _decoratedSlash?: SlashCommandHandler[] + + constructor() { + if (this._decoratedSlash !== undefined) { + this.commands = this._decoratedSlash + } + } + + add(handler: SlashCommandHandler): SlashModule { + this.commands.push(handler) + return this + } +} diff --git a/src/structures/slash.ts b/src/structures/slash.ts index c3a5712..81666ff 100644 --- a/src/structures/slash.ts +++ b/src/structures/slash.ts @@ -76,7 +76,7 @@ export class Interaction { return this.data.name } - option(name: string): any { + option(name: string): T { return this.data.options.find((e) => e.name === name)?.value } @@ -121,6 +121,16 @@ export class Interaction { return this } + async deleteResponse(): Promise { + const url = WEBHOOK_MESSAGE( + this.client.user?.id as string, + this.token, + '@original' + ) + await this.client.rest.delete(url) + return this + } + get url(): string { return `https://discord.com/api/v8/webhooks/${this.client.user?.id}/${this.token}` } diff --git a/src/types/slash.ts b/src/types/slash.ts index 4ec0034..6e5290c 100644 --- a/src/types/slash.ts +++ b/src/types/slash.ts @@ -8,9 +8,9 @@ export interface InteractionOption { } export interface InteractionData { - options: InteractionOption[] name: string id: string + options: InteractionOption[] } export enum InteractionType { @@ -50,6 +50,7 @@ export interface SlashCommandOption { type: SlashCommandOptionType required: boolean choices?: SlashCommandChoice[] + options?: SlashCommandOption[] } export interface SlashCommandPartial { From 844a408c741a4d0d1d40863ef2f43ce2edd588da Mon Sep 17 00:00:00 2001 From: DjDeveloperr Date: Tue, 15 Dec 2020 11:08:37 +0530 Subject: [PATCH 10/10] export SlashModule --- .gitignore | 2 + mod.ts | 1 + src/structures/voiceState.ts | 8 +- src/test/music.ts | 137 +++++++++++++++++++++++++++++++++++ src/types/userFlags.ts | 1 - src/types/voice.ts | 3 +- 6 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 src/test/music.ts diff --git a/.gitignore b/.gitignore index c08a6ec..4f1ff5d 100644 --- a/.gitignore +++ b/.gitignore @@ -113,3 +113,5 @@ src/test/config.ts # macOS is shit xD **/.DS_Store + +src/test/music.mp3 \ No newline at end of file diff --git a/mod.ts b/mod.ts index 40c4ccb..91ddb18 100644 --- a/mod.ts +++ b/mod.ts @@ -20,6 +20,7 @@ export { ExtensionCommands, ExtensionsManager } from './src/models/extensions.ts' +export { SlashModule } from './src/models/slashModule.ts' export { CommandClient, command } from './src/models/commandClient.ts' export type { CommandClientOptions } from './src/models/commandClient.ts' export { BaseManager } from './src/managers/base.ts' diff --git a/src/structures/voiceState.ts b/src/structures/voiceState.ts index 92e58b5..cd3cc7f 100644 --- a/src/structures/voiceState.ts +++ b/src/structures/voiceState.ts @@ -15,6 +15,8 @@ export class VoiceState extends Base { sessionID: string deaf: boolean mute: boolean + selfDeaf: boolean + selfMute: boolean stream?: boolean video: boolean suppress: boolean @@ -38,8 +40,8 @@ export class VoiceState extends Base { this.guild = _data.guild this.deaf = data.deaf this.mute = data.mute - this.deaf = data.self_deaf - this.mute = data.self_mute + this.selfDeaf = data.self_deaf + this.selfMute = data.self_mute this.stream = data.self_stream this.video = data.self_video this.suppress = data.suppress @@ -52,6 +54,8 @@ export class VoiceState extends Base { this.mute = data.mute ?? this.mute this.deaf = data.self_deaf ?? this.deaf this.mute = data.self_mute ?? this.mute + this.selfDeaf = data.self_deaf ?? this.selfDeaf + this.selfMute = data.self_mute ?? this.selfMute 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/music.ts b/src/test/music.ts new file mode 100644 index 0000000..e43b2a4 --- /dev/null +++ b/src/test/music.ts @@ -0,0 +1,137 @@ +import { + CommandClient, + event, + Intents, + command, + CommandContext, + Extension, + Collection +} from '../../mod.ts' +import { LL_IP, LL_PASS, LL_PORT, TOKEN } from './config.ts' +import { + Manager, + Player +} from 'https://raw.githubusercontent.com/DjDeveloperr/lavaclient-deno/master/mod.ts' + +export const nodes = [ + { + id: 'main', + host: LL_IP, + port: LL_PORT, + password: LL_PASS + } +] + +class MyClient extends CommandClient { + manager: Manager + + constructor() { + super({ + prefix: ['.'], + caseSensitive: false + }) + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const client = this + + this.manager = new Manager(nodes, { + send(id, payload) { + // Sharding not added yet + client.gateway?.send(payload) + } + }) + + this.manager.on('socketError', ({ id }, error) => + console.error(`${id} ran into an error`, error) + ) + this.manager.on('socketReady', (node) => + console.log(`${node.id} connected.`) + ) + + this.on('raw', (evt: string, d: any) => { + if (evt === 'VOICE_SERVER_UPDATE') this.manager.serverUpdate(d) + else if (evt === 'VOICE_STATE_UPDATE') this.manager.stateUpdate(d) + }) + } + + @event() + ready(): void { + console.log(`Logged in as ${this.user?.tag}!`) + this.manager.init(this.user?.id as string) + } +} + +const players = new Collection() + +class VCExtension extends Extension { + name = 'VC' + subPrefix = 'vc' + + @command() + async join(ctx: CommandContext): Promise { + if (players.has(ctx.guild?.id as string) === true) + return ctx.message.reply(`Already playing in this server!`) + + ctx.argString = ctx.argString.slice(4).trim() + + if (ctx.argString === '') + return ctx.message.reply('You gave nothing to search.') + + const userVS = await ctx.guild?.voiceStates.get(ctx.author.id) + if (userVS === undefined) { + ctx.message.reply("You're not in VC.") + return + } + + const player = (ctx.client as MyClient).manager.create( + ctx.guild?.id as string + ) + + await player.connect(userVS.channel?.id as string, { selfDeaf: true }) + + ctx.message.reply(`Joined VC channel - ${userVS.channel?.name}!`) + + players.set(ctx.guild?.id as string, player) + + ctx.channel.send(`Loading...`) + + ctx.channel.send(`Searching for ${ctx.argString}...`) + + const { track, info } = await player.manager + .search(`ytsearch:${ctx.argString}`) + .then((e) => e.tracks[0]) + + await player.play(track) + + ctx.channel.send(`Now playing ${info.title}!`) + } + + @command() + async leave(ctx: CommandContext): Promise { + const userVS = await ctx.guild?.voiceStates.get( + (ctx.client.user?.id as unknown) as string + ) + if (userVS === undefined) { + ctx.message.reply("I'm not in VC.") + return + } + userVS.channel?.leave() + ctx.message.reply(`Left VC channel - ${userVS.channel?.name}!`) + + if (players.has(ctx.guild?.id as string) !== true) + return ctx.message.reply('Not playing anything in this server.') + + const player = (players.get(ctx.guild?.id as string) as unknown) as Player + await player.stop() + await player.destroy() + + players.delete(ctx.guild?.id as string) + ctx.message.reply('Stopped player') + } +} + +const client = new MyClient() + +client.extensions.load(VCExtension) + +client.connect(TOKEN, Intents.None) diff --git a/src/types/userFlags.ts b/src/types/userFlags.ts index a76dbe8..eebd1ef 100644 --- a/src/types/userFlags.ts +++ b/src/types/userFlags.ts @@ -1,7 +1,6 @@ export const UserFlags = { DISCORD_EMPLOYEE: 1 << 0, PARTNERED_SERVER_OWNER: 1 << 1, - DISCORD_PARTNER: 1 << 1, HYPESQUAD_EVENTS: 1 << 2, BUGHUNTER_LEVEL_1: 1 << 3, HOUSE_BRAVERY: 1 << 6, diff --git a/src/types/voice.ts b/src/types/voice.ts index 45f518c..66ea414 100644 --- a/src/types/voice.ts +++ b/src/types/voice.ts @@ -1,7 +1,6 @@ -// https://discord.com/developers/docs/topics/opcodes-and-status-codes#voice import { MemberPayload } from './guild.ts' -export enum VoiceOpcodes { // add VoiceOpcodes - UnderC - +export enum VoiceOpcodes { IDENTIFY = 0, SELECT_PROTOCOL = 1, READY = 2,