wtf
This commit is contained in:
		
						commit
						2ca5005517
					
				
					 12 changed files with 429 additions and 217 deletions
				
			
		
							
								
								
									
										1
									
								
								mod.ts
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								mod.ts
									
										
									
									
									
								
							|  | @ -136,3 +136,4 @@ export { UserFlags } from './src/types/userFlags.ts' | |||
| export type { VoiceStatePayload } from './src/types/voice.ts' | ||||
| export type { WebhookPayload } from './src/types/webhook.ts' | ||||
| export * from './src/models/collectors.ts' | ||||
| export type { Dict } from './src/utils/dict.ts' | ||||
|  |  | |||
|  | @ -1,29 +1,110 @@ | |||
| import { Guild } from '../../structures/guild.ts' | ||||
| import { Member } from '../../structures/member.ts' | ||||
| import { Interaction } from '../../structures/slash.ts' | ||||
| import { Role } from '../../structures/role.ts' | ||||
| import { | ||||
|   Interaction, | ||||
|   InteractionApplicationCommandResolved, | ||||
|   InteractionChannel | ||||
| } from '../../structures/slash.ts' | ||||
| import { GuildTextChannel } from '../../structures/textChannel.ts' | ||||
| import { User } from '../../structures/user.ts' | ||||
| import { InteractionPayload } from '../../types/slash.ts' | ||||
| import { UserPayload } from '../../types/user.ts' | ||||
| import { Permissions } from '../../utils/permissions.ts' | ||||
| import { Gateway, GatewayEventHandler } from '../index.ts' | ||||
| 
 | ||||
| export const interactionCreate: GatewayEventHandler = async ( | ||||
|   gateway: Gateway, | ||||
|   d: InteractionPayload | ||||
| ) => { | ||||
|   const guild = await gateway.client.guilds.get(d.guild_id) | ||||
|   if (guild === undefined) return | ||||
|   // NOTE(DjDeveloperr): Mason once mentioned that channel_id can be optional in Interaction.
 | ||||
|   // This case can be seen in future proofing Interactions, and one he mentioned was
 | ||||
|   // that bots will be able to add custom context menus. In that case, Interaction will not have it.
 | ||||
|   // Ref: https://github.com/discord/discord-api-docs/pull/2568/files#r569025697
 | ||||
|   if (d.channel_id === 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 guild = | ||||
|     d.guild_id === undefined | ||||
|       ? undefined | ||||
|       : await gateway.client.guilds.get(d.guild_id) | ||||
| 
 | ||||
|   if (d.member !== undefined) | ||||
|     await guild?.members.set(d.member.user.id, d.member) | ||||
|   const member = | ||||
|     d.member !== undefined | ||||
|       ? (((await guild?.members.get(d.member.user.id)) as unknown) as Member) | ||||
|       : undefined | ||||
|   if (d.user !== undefined) await gateway.client.users.set(d.user.id, d.user) | ||||
|   const dmUser = | ||||
|     d.user !== undefined ? await gateway.client.users.get(d.user.id) : undefined | ||||
| 
 | ||||
|   const user = member !== undefined ? member.user : dmUser | ||||
|   if (user === undefined) return | ||||
| 
 | ||||
|   const channel = | ||||
|     (await gateway.client.channels.get<GuildTextChannel>(d.channel_id)) ?? | ||||
|     (await gateway.client.channels.fetch<GuildTextChannel>(d.channel_id)) | ||||
| 
 | ||||
|   const resolved: InteractionApplicationCommandResolved = { | ||||
|     users: {}, | ||||
|     channels: {}, | ||||
|     members: {}, | ||||
|     roles: {} | ||||
|   } | ||||
| 
 | ||||
|   if (d.data?.resolved !== undefined) { | ||||
|     for (const [id, data] of Object.entries(d.data.resolved.users ?? {})) { | ||||
|       await gateway.client.users.set(id, data) | ||||
|       resolved.users[id] = ((await gateway.client.users.get( | ||||
|         id | ||||
|       )) as unknown) as User | ||||
|       if (resolved.members[id] !== undefined) | ||||
|         resolved.users[id].member = resolved.members[id] | ||||
|     } | ||||
| 
 | ||||
|     for (const [id, data] of Object.entries(d.data.resolved.members ?? {})) { | ||||
|       const roles = await guild?.roles.array() | ||||
|       let permissions = new Permissions(Permissions.DEFAULT) | ||||
|       if (roles !== undefined) { | ||||
|         const mRoles = roles.filter( | ||||
|           (r) => (data?.roles?.includes(r.id) as boolean) || r.id === guild?.id | ||||
|         ) | ||||
|         permissions = new Permissions(mRoles.map((r) => r.permissions)) | ||||
|       } | ||||
|       data.user = (d.data.resolved.users?.[id] as unknown) as UserPayload | ||||
|       resolved.members[id] = new Member( | ||||
|         gateway.client, | ||||
|         data, | ||||
|         resolved.users[id], | ||||
|         guild as Guild, | ||||
|         permissions | ||||
|       ) | ||||
|     } | ||||
| 
 | ||||
|     for (const [id, data] of Object.entries(d.data.resolved.roles ?? {})) { | ||||
|       if (guild !== undefined) { | ||||
|         await guild.roles.set(id, data) | ||||
|         resolved.roles[id] = ((await guild.roles.get(id)) as unknown) as Role | ||||
|       } else { | ||||
|         resolved.roles[id] = new Role( | ||||
|           gateway.client, | ||||
|           data, | ||||
|           (guild as unknown) as Guild | ||||
|         ) | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     for (const [id, data] of Object.entries(d.data.resolved.channels ?? {})) { | ||||
|       resolved.channels[id] = new InteractionChannel(gateway.client, data) | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   const interaction = new Interaction(gateway.client, d, { | ||||
|     member, | ||||
|     guild, | ||||
|     channel | ||||
|     channel, | ||||
|     user, | ||||
|     resolved | ||||
|   }) | ||||
|   gateway.client.emit('interactionCreate', interaction) | ||||
| } | ||||
|  |  | |||
|  | @ -7,7 +7,6 @@ import { | |||
| import { GatewayResponse } from '../types/gatewayResponse.ts' | ||||
| import { | ||||
|   GatewayOpcodes, | ||||
|   GatewayIntents, | ||||
|   GatewayCloseCodes, | ||||
|   IdentityPayload, | ||||
|   StatusUpdatePayload, | ||||
|  | @ -57,8 +56,6 @@ export type GatewayTypedEvents = { | |||
|  */ | ||||
| export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> { | ||||
|   websocket?: WebSocket | ||||
|   token?: string | ||||
|   intents?: GatewayIntents[] | ||||
|   connected = false | ||||
|   initialized = false | ||||
|   heartbeatInterval = 0 | ||||
|  | @ -157,7 +154,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> { | |||
| 
 | ||||
|           const handler = gatewayHandlers[t] | ||||
| 
 | ||||
|           if (handler !== undefined) { | ||||
|           if (handler !== undefined && d !== null) { | ||||
|             handler(this, d) | ||||
|           } | ||||
|         } | ||||
|  | @ -269,8 +266,9 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> { | |||
|   } | ||||
| 
 | ||||
|   private async sendIdentify(forceNewSession?: boolean): Promise<void> { | ||||
|     if (typeof this.token !== 'string') throw new Error('Token not specified') | ||||
|     if (typeof this.intents !== 'object') | ||||
|     if (typeof this.client.token !== 'string') | ||||
|       throw new Error('Token not specified') | ||||
|     if (typeof this.client.intents !== 'object') | ||||
|       throw new Error('Intents not specified') | ||||
| 
 | ||||
|     if (this.client.fetchGatewayInfo === true) { | ||||
|  | @ -300,7 +298,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> { | |||
|     } | ||||
| 
 | ||||
|     const payload: IdentityPayload = { | ||||
|       token: this.token, | ||||
|       token: this.client.token, | ||||
|       properties: { | ||||
|         $os: this.client.clientProperties.os ?? Deno.build.os, | ||||
|         $browser: this.client.clientProperties.browser ?? 'harmony', | ||||
|  | @ -311,7 +309,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> { | |||
|         this.shards === undefined | ||||
|           ? [0, 1] | ||||
|           : [this.shards[0] ?? 0, this.shards[1] ?? 1], | ||||
|       intents: this.intents.reduce( | ||||
|       intents: this.client.intents.reduce( | ||||
|         (previous, current) => previous | current, | ||||
|         0 | ||||
|       ), | ||||
|  | @ -327,9 +325,8 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> { | |||
|   } | ||||
| 
 | ||||
|   private async sendResume(): Promise<void> { | ||||
|     if (typeof this.token !== 'string') throw new Error('Token not specified') | ||||
|     if (typeof this.intents !== 'object') | ||||
|       throw new Error('Intents not specified') | ||||
|     if (typeof this.client.token !== 'string') | ||||
|       throw new Error('Token not specified') | ||||
| 
 | ||||
|     if (this.sessionID === undefined) { | ||||
|       this.sessionID = await this.cache.get( | ||||
|  | @ -348,7 +345,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> { | |||
|     const resumePayload = { | ||||
|       op: GatewayOpcodes.RESUME, | ||||
|       d: { | ||||
|         token: this.token, | ||||
|         token: this.client.token, | ||||
|         session_id: this.sessionID, | ||||
|         seq: this.sequenceID ?? null | ||||
|       } | ||||
|  |  | |||
|  | @ -79,8 +79,6 @@ export class ShardManager extends HarmonyEventEmitter<ShardManagerEvents> { | |||
|     const shardCount = await this.getShardCount() | ||||
| 
 | ||||
|     const gw = new Gateway(this.client, [Number(id), shardCount]) | ||||
|     gw.token = this.client.token | ||||
|     gw.intents = this.client.intents | ||||
|     this.list.set(id.toString(), gw) | ||||
| 
 | ||||
|     gw.initWebsocket() | ||||
|  |  | |||
|  | @ -155,6 +155,7 @@ function buildOptionsArray( | |||
|       ) | ||||
| } | ||||
| 
 | ||||
| /** Slash Command Builder */ | ||||
| export class SlashBuilder { | ||||
|   data: SlashCommandPartial | ||||
| 
 | ||||
|  | @ -200,6 +201,7 @@ export class SlashBuilder { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| /** Manages Slash Commands, allows fetching/modifying/deleting/creating Slash Commands. */ | ||||
| export class SlashCommandsManager { | ||||
|   slash: SlashClient | ||||
|   rest: RESTManager | ||||
|  | @ -351,7 +353,7 @@ export class SlashCommandsManager { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| export type SlashCommandHandlerCallback = (interaction: Interaction) => any | ||||
| export type SlashCommandHandlerCallback = (interaction: Interaction) => unknown | ||||
| export interface SlashCommandHandler { | ||||
|   name: string | ||||
|   guild?: string | ||||
|  | @ -360,6 +362,7 @@ export interface SlashCommandHandler { | |||
|   handler: SlashCommandHandlerCallback | ||||
| } | ||||
| 
 | ||||
| /** Options for SlashClient */ | ||||
| export interface SlashOptions { | ||||
|   id?: string | (() => string) | ||||
|   client?: Client | ||||
|  | @ -369,9 +372,7 @@ export interface SlashOptions { | |||
|   publicKey?: string | ||||
| } | ||||
| 
 | ||||
| const encoder = new TextEncoder() | ||||
| const decoder = new TextDecoder('utf-8') | ||||
| 
 | ||||
| /** Slash Client represents an Interactions Client which can be used without Harmony Client. */ | ||||
| export class SlashClient { | ||||
|   id: string | (() => string) | ||||
|   client?: Client | ||||
|  | @ -472,12 +473,20 @@ export class SlashClient { | |||
|       const groupMatched = | ||||
|         e.group !== undefined && e.parent !== undefined | ||||
|           ? i.options | ||||
|               .find((o) => o.name === e.group) | ||||
|               .find( | ||||
|                 (o) => | ||||
|                   o.name === e.group && | ||||
|                   o.type === SlashCommandOptionType.SUB_COMMAND_GROUP | ||||
|               ) | ||||
|               ?.options?.find((o) => o.name === e.name) !== undefined | ||||
|           : true | ||||
|       const subMatched = | ||||
|         e.group === undefined && e.parent !== undefined | ||||
|           ? i.options.find((o) => o.name === e.name) !== undefined | ||||
|           ? i.options.find( | ||||
|               (o) => | ||||
|                 o.name === e.name && | ||||
|                 o.type === SlashCommandOptionType.SUB_COMMAND | ||||
|             ) !== undefined | ||||
|           : true | ||||
|       const nameMatched1 = e.name === i.name | ||||
|       const parentMatched = hasGroupOrParent ? e.parent === i.name : true | ||||
|  | @ -488,11 +497,15 @@ export class SlashClient { | |||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   /** Process an incoming Slash Command (interaction) */ | ||||
|   /** Process an incoming Interaction */ | ||||
|   private _process(interaction: Interaction): void { | ||||
|     if (!this.enabled) return | ||||
| 
 | ||||
|     if (interaction.type !== InteractionType.APPLICATION_COMMAND) return | ||||
|     if ( | ||||
|       interaction.type !== InteractionType.APPLICATION_COMMAND || | ||||
|       interaction.data === undefined | ||||
|     ) | ||||
|       return | ||||
| 
 | ||||
|     const cmd = this._getCommand(interaction) | ||||
|     if (cmd?.group !== undefined) | ||||
|  |  | |||
|  | @ -2,10 +2,10 @@ import { Client } from '../models/client.ts' | |||
| import { Snowflake } from '../utils/snowflake.ts' | ||||
| 
 | ||||
| export class Base { | ||||
|   client: Client | ||||
|   client!: Client | ||||
| 
 | ||||
|   constructor(client: Client, _data?: any) { | ||||
|     this.client = client | ||||
|     Object.defineProperty(this, 'client', { value: client, enumerable: false }) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,21 +1,33 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { MessageOptions } from '../types/channel.ts' | ||||
| import { | ||||
|   AllowedMentionsPayload, | ||||
|   ChannelTypes, | ||||
|   EmbedPayload, | ||||
|   MessageOptions | ||||
| } from '../types/channel.ts' | ||||
| import { INTERACTION_CALLBACK, WEBHOOK_MESSAGE } from '../types/endpoint.ts' | ||||
| import { | ||||
|   InteractionData, | ||||
|   InteractionOption, | ||||
|   InteractionApplicationCommandData, | ||||
|   InteractionApplicationCommandOption, | ||||
|   InteractionChannelPayload, | ||||
|   InteractionPayload, | ||||
|   InteractionResponseFlags, | ||||
|   InteractionResponsePayload, | ||||
|   InteractionResponseType | ||||
|   InteractionResponseType, | ||||
|   InteractionType, | ||||
|   SlashCommandOptionType | ||||
| } from '../types/slash.ts' | ||||
| import { Dict } from '../utils/dict.ts' | ||||
| import { Permissions } from '../utils/permissions.ts' | ||||
| import { SnowflakeBase } from './base.ts' | ||||
| import { Channel } from './channel.ts' | ||||
| import { Embed } from './embed.ts' | ||||
| import { Guild } from './guild.ts' | ||||
| import { Member } from './member.ts' | ||||
| import { Message } from './message.ts' | ||||
| import { Role } from './role.ts' | ||||
| import { GuildTextChannel, TextChannel } from './textChannel.ts' | ||||
| import { User } from './user.ts' | ||||
| import { Webhook } from './webhook.ts' | ||||
| 
 | ||||
| interface WebhookMessageOptions extends MessageOptions { | ||||
|   embeds?: Embed[] | ||||
|  | @ -25,72 +37,133 @@ interface WebhookMessageOptions extends MessageOptions { | |||
| 
 | ||||
| type AllWebhookMessageOptions = string | WebhookMessageOptions | ||||
| 
 | ||||
| export interface InteractionResponse { | ||||
|   type?: InteractionResponseType | ||||
| /** Interaction Message related Options */ | ||||
| export interface InteractionMessageOptions { | ||||
|   content?: string | ||||
|   embeds?: Embed[] | ||||
|   embeds?: EmbedPayload[] | ||||
|   tts?: boolean | ||||
|   flags?: number | ||||
|   temp?: boolean | ||||
|   allowedMentions?: { | ||||
|     parse?: string | ||||
|     roles?: string[] | ||||
|     users?: string[] | ||||
|     everyone?: boolean | ||||
|   flags?: number | InteractionResponseFlags[] | ||||
|   allowedMentions?: AllowedMentionsPayload | ||||
| } | ||||
| 
 | ||||
| export interface InteractionResponse extends InteractionMessageOptions { | ||||
|   /** Type of Interaction Response */ | ||||
|   type?: InteractionResponseType | ||||
|   /** Whether the Message Response should be Ephemeral (only visible to User) or not */ | ||||
|   ephemeral?: boolean | ||||
| } | ||||
| 
 | ||||
| /** Represents a Channel Object for an Option in Slash Command */ | ||||
| export class InteractionChannel extends SnowflakeBase { | ||||
|   /** Name of the Channel */ | ||||
|   name: string | ||||
|   /** Channel Type */ | ||||
|   type: ChannelTypes | ||||
|   permissions: Permissions | ||||
| 
 | ||||
|   constructor(client: Client, data: InteractionChannelPayload) { | ||||
|     super(client) | ||||
|     this.name = data.name | ||||
|     this.type = data.type | ||||
|     this.permissions = new Permissions(data.permissions) | ||||
|   } | ||||
| 
 | ||||
|   /** Resolve to actual Channel object if present in Cache */ | ||||
|   async resolve<T = Channel>(): Promise<T | undefined> { | ||||
|     return this.client.channels.get<T>(this.id) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export interface InteractionApplicationCommandResolved { | ||||
|   users: Dict<InteractionUser> | ||||
|   members: Dict<Member> | ||||
|   channels: Dict<InteractionChannel> | ||||
|   roles: Dict<Role> | ||||
| } | ||||
| 
 | ||||
| export class InteractionUser extends User { | ||||
|   member?: Member | ||||
| } | ||||
| 
 | ||||
| export class Interaction extends SnowflakeBase { | ||||
|   /** This will be `SlashClient` in case of `SlashClient#verifyServerRequest` */ | ||||
|   client: Client | ||||
|   type: number | ||||
|   /** Type of Interaction */ | ||||
|   type: InteractionType | ||||
|   /** Interaction Token */ | ||||
|   token: string | ||||
|   /** Interaction ID */ | ||||
|   id: string | ||||
|   data: InteractionData | ||||
|   channel: GuildTextChannel | ||||
|   guild: Guild | ||||
|   member: Member | ||||
|   _savedHook?: Webhook | ||||
|   _respond?: (data: InteractionResponsePayload) => unknown | ||||
|   /** Data sent with Interaction. Only applies to Application Command */ | ||||
|   data?: InteractionApplicationCommandData | ||||
|   /** Channel in which Interaction was initiated */ | ||||
|   channel?: TextChannel | GuildTextChannel | ||||
|   /** Guild in which Interaction was initiated */ | ||||
|   guild?: Guild | ||||
|   /** Member object of who initiated the Interaction */ | ||||
|   member?: Member | ||||
|   /** User object of who invoked Interaction */ | ||||
|   user: User | ||||
|   /** Whether we have responded to Interaction or not */ | ||||
|   responded: boolean = false | ||||
|   /** Resolved data for Snowflakes in Slash Command Arguments */ | ||||
|   resolved: InteractionApplicationCommandResolved | ||||
|   /** Whether response was deferred or not */ | ||||
|   deferred: boolean = false | ||||
| 
 | ||||
|   constructor( | ||||
|     client: Client, | ||||
|     data: InteractionPayload, | ||||
|     others: { | ||||
|       channel: GuildTextChannel | ||||
|       guild: Guild | ||||
|       member: Member | ||||
|       channel?: TextChannel | GuildTextChannel | ||||
|       guild?: Guild | ||||
|       member?: Member | ||||
|       user: User | ||||
|       resolved: InteractionApplicationCommandResolved | ||||
|     } | ||||
|   ) { | ||||
|     super(client) | ||||
|     this.client = client | ||||
|     this.type = data.type | ||||
|     this.token = data.token | ||||
|     this.member = others.member | ||||
|     this.id = data.id | ||||
|     this.user = others.user | ||||
|     this.data = data.data | ||||
|     this.guild = others.guild | ||||
|     this.channel = others.channel | ||||
|     this.resolved = others.resolved | ||||
|   } | ||||
| 
 | ||||
|   get user(): User { | ||||
|     return this.member.user | ||||
|   /** Name of the Command Used (may change with future additions to Interactions!) */ | ||||
|   get name(): string | undefined { | ||||
|     return this.data?.name | ||||
|   } | ||||
| 
 | ||||
|   get name(): string { | ||||
|     return this.data.name | ||||
|   get options(): InteractionApplicationCommandOption[] { | ||||
|     return this.data?.options ?? [] | ||||
|   } | ||||
| 
 | ||||
|   get options(): InteractionOption[] { | ||||
|     return this.data.options ?? [] | ||||
|   } | ||||
| 
 | ||||
|   option<T = any>(name: string): T { | ||||
|     return this.options.find((e) => e.name === name)?.value | ||||
|   /** Get an option by name */ | ||||
|   option<T>(name: string): T { | ||||
|     const op = this.options.find((e) => e.name === name) | ||||
|     if (op === undefined || op.value === undefined) return undefined as any | ||||
|     if (op.type === SlashCommandOptionType.USER) | ||||
|       return this.resolved.users[op.value] as any | ||||
|     else if (op.type === SlashCommandOptionType.ROLE) | ||||
|       return this.resolved.roles[op.value] as any | ||||
|     else if (op.type === SlashCommandOptionType.CHANNEL) | ||||
|       return this.resolved.channels[op.value] as any | ||||
|     else return op.value | ||||
|   } | ||||
| 
 | ||||
|   /** Respond to an Interaction */ | ||||
|   async respond(data: InteractionResponse): Promise<Interaction> { | ||||
|     if (this.responded) throw new Error('Already responded to Interaction') | ||||
|     let flags = 0 | ||||
|     if (data.ephemeral === true) flags |= InteractionResponseFlags.EPHEMERAL | ||||
|     if (data.flags !== undefined) { | ||||
|       if (Array.isArray(data.flags)) | ||||
|         flags = data.flags.reduce((p, a) => p | a, flags) | ||||
|       else if (typeof data.flags === 'number') flags |= data.flags | ||||
|     } | ||||
|     const payload: InteractionResponsePayload = { | ||||
|       type: data.type ?? InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, | ||||
|       data: | ||||
|  | @ -101,8 +174,8 @@ export class Interaction extends SnowflakeBase { | |||
|               content: data.content ?? '', | ||||
|               embeds: data.embeds, | ||||
|               tts: data.tts ?? false, | ||||
|               flags: data.temp === true ? 64 : data.flags ?? undefined, | ||||
|               allowed_mentions: (data.allowedMentions ?? undefined) as any | ||||
|               flags, | ||||
|               allowed_mentions: data.allowedMentions ?? undefined | ||||
|             } | ||||
|           : undefined | ||||
|     } | ||||
|  | @ -111,6 +184,55 @@ export class Interaction extends SnowflakeBase { | |||
|       INTERACTION_CALLBACK(this.id, this.token), | ||||
|       payload | ||||
|     ) | ||||
|     this.responded = true | ||||
| 
 | ||||
|     return this | ||||
|   } | ||||
| 
 | ||||
|   /** Defer the Interaction i.e. let the user know bot is processing and will respond later. You only have 15 minutes to edit the response! */ | ||||
|   async defer(): Promise<Interaction> { | ||||
|     await this.respond({ | ||||
|       type: InteractionResponseType.DEFERRED_CHANNEL_MESSAGE | ||||
|     }) | ||||
|     this.deferred = true | ||||
|     return this | ||||
|   } | ||||
| 
 | ||||
|   /** Reply with a Message to the Interaction */ | ||||
|   async reply(content: string): Promise<Interaction> | ||||
|   async reply(options: InteractionMessageOptions): Promise<Interaction> | ||||
|   async reply( | ||||
|     content: string, | ||||
|     options: InteractionMessageOptions | ||||
|   ): Promise<Interaction> | ||||
|   async reply( | ||||
|     content: string | InteractionMessageOptions, | ||||
|     messageOptions?: InteractionMessageOptions | ||||
|   ): Promise<Interaction> { | ||||
|     let options: InteractionMessageOptions | undefined = | ||||
|       typeof content === 'object' ? content : messageOptions | ||||
|     if ( | ||||
|       typeof content === 'object' && | ||||
|       messageOptions !== undefined && | ||||
|       options !== undefined | ||||
|     ) | ||||
|       Object.assign(options, messageOptions) | ||||
|     if (options === undefined) options = {} | ||||
|     if (typeof content === 'string') Object.assign(options, { content }) | ||||
| 
 | ||||
|     if (this.deferred && this.responded) { | ||||
|       await this.editResponse({ | ||||
|         content: options.content, | ||||
|         embeds: options.embeds, | ||||
|         flags: options.flags, | ||||
|         allowedMentions: options.allowedMentions | ||||
|       }) | ||||
|     } else | ||||
|       await this.respond( | ||||
|         Object.assign(options, { | ||||
|           type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE | ||||
|         }) | ||||
|       ) | ||||
| 
 | ||||
|     return this | ||||
|   } | ||||
|  | @ -118,7 +240,9 @@ export class Interaction extends SnowflakeBase { | |||
|   /** Edit the original Interaction response */ | ||||
|   async editResponse(data: { | ||||
|     content?: string | ||||
|     embeds?: Embed[] | ||||
|     embeds?: EmbedPayload[] | ||||
|     flags?: number | number[] | ||||
|     allowedMentions?: AllowedMentionsPayload | ||||
|   }): Promise<Interaction> { | ||||
|     const url = WEBHOOK_MESSAGE( | ||||
|       this.client.user?.id as string, | ||||
|  | @ -127,7 +251,12 @@ export class Interaction extends SnowflakeBase { | |||
|     ) | ||||
|     await this.client.rest.patch(url, { | ||||
|       content: data.content ?? '', | ||||
|       embeds: data.embeds ?? [] | ||||
|       embeds: data.embeds ?? [], | ||||
|       flags: | ||||
|         typeof data.flags === 'object' | ||||
|           ? data.flags.reduce((p, a) => p | a, 0) | ||||
|           : data.flags, | ||||
|       allowed_mentions: data.allowedMentions | ||||
|     }) | ||||
|     return this | ||||
|   } | ||||
|  | @ -234,6 +363,7 @@ export class Interaction extends SnowflakeBase { | |||
|     return this | ||||
|   } | ||||
| 
 | ||||
|   /** Delete a follow-up Message */ | ||||
|   async deleteMessage(msg: Message | string): Promise<Interaction> { | ||||
|     await this.client.rest.delete( | ||||
|       WEBHOOK_MESSAGE( | ||||
|  |  | |||
|  | @ -201,7 +201,7 @@ client.on('messageCreate', async (msg: Message) => { | |||
|         ) | ||||
|         .join('\n\n')}` | ||||
|     ) | ||||
|   } else if (msg.content === '!getPermissions') { | ||||
|   } else if (msg.content === '!perms') { | ||||
|     if (msg.channel.type !== ChannelTypes.GUILD_TEXT) { | ||||
|       return msg.channel.send("This isn't a guild text channel!") | ||||
|     } | ||||
|  | @ -210,30 +210,11 @@ client.on('messageCreate', async (msg: Message) => { | |||
|       // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
 | ||||
|       msg.member as Member | ||||
|     ) | ||||
|     msg.channel.send(`Your permissions:\n${permissions.toArray().join('\n')}`) | ||||
|   } else if (msg.content === '!addAllRoles') { | ||||
|     const roles = await msg.guild?.roles.array() | ||||
|     if (roles !== undefined) { | ||||
|       roles.forEach(async (role) => { | ||||
|         await msg.member?.roles.add(role) | ||||
|         console.log(role) | ||||
|       }) | ||||
|     } | ||||
|   } else if (msg.content === '!createAndAddRole') { | ||||
|     if (msg.guild !== undefined) { | ||||
|       const role = await msg.guild.roles.create({ | ||||
|         name: 'asdf', | ||||
|         permissions: 0 | ||||
|       }) | ||||
|       await msg.member?.roles.add(role) | ||||
|     } | ||||
|   } else if (msg.content === '!roles') { | ||||
|     let buf = 'Roles:' | ||||
|     if (msg.member === undefined) return | ||||
|     for await (const role of msg.member.roles) { | ||||
|       buf += `\n${role.name}` | ||||
|     } | ||||
|     msg.reply(buf) | ||||
|     msg.channel.send( | ||||
|       Object.entries(permissions.serialize()) | ||||
|         .map((e) => `${e[0]}: ${e[1] === true ? '`✅`' : '`❌`'}`) | ||||
|         .join('\n') | ||||
|     ) | ||||
|   } | ||||
| }) | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,100 +1,56 @@ | |||
| import { Client, Intents, event, slash } from '../../mod.ts' | ||||
| import { Embed } from '../structures/embed.ts' | ||||
| import { | ||||
|   Client, | ||||
|   Intents, | ||||
|   event, | ||||
|   slash, | ||||
|   SlashCommandOptionType as Type | ||||
| } from '../../mod.ts' | ||||
| import { Interaction } from '../structures/slash.ts' | ||||
| import { TOKEN } from './config.ts' | ||||
| 
 | ||||
| export class MyClient extends Client { | ||||
|   @event() | ||||
|   ready(): void { | ||||
|   @event() ready(): void { | ||||
|     console.log(`Logged in as ${this.user?.tag}!`) | ||||
|     this.slash.commands.bulkEdit([{ name: 'send', description: 'idk' }]) | ||||
|   } | ||||
| 
 | ||||
|   @event('debug') | ||||
|   debugEvt(txt: string): void { | ||||
|     console.log(txt) | ||||
|   } | ||||
| 
 | ||||
|   @slash() | ||||
|   send(d: Interaction): void { | ||||
|     d.respond({ | ||||
|       content: d.data.options?.find((e) => e.name === 'content')?.value | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   @slash() | ||||
|   async eval(d: Interaction): Promise<void> { | ||||
|     if ( | ||||
|       d.user.id !== '422957901716652033' && | ||||
|       d.user.id !== '682849186227552266' | ||||
|     ) { | ||||
|       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]') | ||||
|     this.slash.commands.bulkEdit( | ||||
|       [ | ||||
|         { | ||||
|           name: 'test', | ||||
|           description: 'Test command.', | ||||
|           options: [ | ||||
|             { | ||||
|               name: 'user', | ||||
|               type: Type.USER, | ||||
|               description: 'User' | ||||
|             }, | ||||
|             { | ||||
|               name: 'role', | ||||
|               type: Type.ROLE, | ||||
|               description: 'Role' | ||||
|             }, | ||||
|             { | ||||
|               name: 'channel', | ||||
|               type: Type.CHANNEL, | ||||
|               description: 'Channel' | ||||
|             }, | ||||
|             { | ||||
|               name: 'string', | ||||
|               type: Type.STRING, | ||||
|               description: 'String' | ||||
|             } | ||||
|           ] | ||||
|         } | ||||
|         d.respond({ | ||||
|           content: '```js\n' + `${res}` + '\n```' | ||||
|         }).catch(() => {}) | ||||
|       } catch (e) { | ||||
|         d.respond({ | ||||
|           content: '```js\n' + `${e.stack}` + '\n```' | ||||
|         }) | ||||
|       } | ||||
|     } | ||||
|       ], | ||||
|       '807935370556866560' | ||||
|     ) | ||||
|     this.slash.commands.bulkEdit([]) | ||||
|   } | ||||
| 
 | ||||
|   @slash() | ||||
|   async hug(d: Interaction): Promise<void> { | ||||
|     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) | ||||
|       ] | ||||
|     }) | ||||
|   @slash() test(d: Interaction): void { | ||||
|     console.log(d.resolved) | ||||
|   } | ||||
| 
 | ||||
|   @slash() | ||||
|   async kiss(d: Interaction): Promise<void> { | ||||
|     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!` | ||||
|     }) | ||||
|   @event() raw(evt: string, d: any): void { | ||||
|     if (evt === 'INTERACTION_CREATE') console.log(evt, d?.data?.resolved) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -156,16 +156,25 @@ export interface MessagePayload { | |||
|   stickers?: MessageStickerPayload[] | ||||
| } | ||||
| 
 | ||||
| export enum AllowedMentionType { | ||||
|   Roles = 'roles', | ||||
|   Users = 'users', | ||||
|   Everyone = 'everyone' | ||||
| } | ||||
| 
 | ||||
| export interface AllowedMentionsPayload { | ||||
|   parse?: AllowedMentionType[] | ||||
|   users?: string[] | ||||
|   roles?: string[] | ||||
|   replied_user?: boolean | ||||
| } | ||||
| 
 | ||||
| export interface MessageOptions { | ||||
|   tts?: boolean | ||||
|   embed?: Embed | ||||
|   file?: MessageAttachment | ||||
|   files?: MessageAttachment[] | ||||
|   allowedMentions?: { | ||||
|     parse?: 'everyone' | 'users' | 'roles' | ||||
|     roles?: string[] | ||||
|     users?: string[] | ||||
|   } | ||||
|   allowedMentions?: AllowedMentionsPayload | ||||
| } | ||||
| 
 | ||||
| export interface ChannelMention { | ||||
|  |  | |||
|  | @ -1,22 +1,47 @@ | |||
| import { EmbedPayload } from './channel.ts' | ||||
| import { Dict } from '../utils/dict.ts' | ||||
| import { | ||||
|   AllowedMentionsPayload, | ||||
|   ChannelTypes, | ||||
|   EmbedPayload | ||||
| } from './channel.ts' | ||||
| import { MemberPayload } from './guild.ts' | ||||
| import { RolePayload } from './role.ts' | ||||
| import { UserPayload } from './user.ts' | ||||
| 
 | ||||
| export interface InteractionOption { | ||||
| export interface InteractionApplicationCommandOption { | ||||
|   /** Option name */ | ||||
|   name: string | ||||
|   /** Type of Option */ | ||||
|   type: SlashCommandOptionType | ||||
|   /** Value of the option */ | ||||
|   value?: any | ||||
|   /** Sub options */ | ||||
|   options?: any[] | ||||
|   options?: InteractionApplicationCommandOption[] | ||||
| } | ||||
| 
 | ||||
| export interface InteractionData { | ||||
| export interface InteractionChannelPayload { | ||||
|   id: string | ||||
|   name: string | ||||
|   permissions: string | ||||
|   type: ChannelTypes | ||||
| } | ||||
| 
 | ||||
| export interface InteractionApplicationCommandResolvedPayload { | ||||
|   users?: Dict<UserPayload> | ||||
|   members?: Dict<MemberPayload> | ||||
|   channels?: Dict<InteractionChannelPayload> | ||||
|   roles?: Dict<RolePayload> | ||||
| } | ||||
| 
 | ||||
| export interface InteractionApplicationCommandData { | ||||
|   /** Name of the Slash Command */ | ||||
|   name: string | ||||
|   /** Unique ID of the Slash Command */ | ||||
|   id: string | ||||
|   /** Options (arguments) sent with Interaction */ | ||||
|   options: InteractionOption[] | ||||
|   options: InteractionApplicationCommandOption[] | ||||
|   /** Resolved data for options in Slash Command */ | ||||
|   resolved?: InteractionApplicationCommandResolvedPayload | ||||
| } | ||||
| 
 | ||||
| export enum InteractionType { | ||||
|  | @ -26,27 +51,30 @@ export enum InteractionType { | |||
|   APPLICATION_COMMAND = 2 | ||||
| } | ||||
| 
 | ||||
| export interface InteractionMemberPayload extends MemberPayload { | ||||
|   /** Permissions of the Member who initiated Interaction (Guild-only) */ | ||||
|   permissions: string | ||||
| } | ||||
| 
 | ||||
| export interface InteractionPayload { | ||||
|   /** Type of the Interaction */ | ||||
|   type: InteractionType | ||||
|   /** Token of the Interaction to respond */ | ||||
|   token: string | ||||
|   /** Member object of user who invoked */ | ||||
|   member: MemberPayload & { | ||||
|     /** Total permissions of the member in the channel, including overrides */ | ||||
|     permissions: string | ||||
|   } | ||||
|   member?: InteractionMemberPayload | ||||
|   /** User who initiated Interaction (only in DMs) */ | ||||
|   user?: UserPayload | ||||
|   /** ID of the Interaction */ | ||||
|   id: string | ||||
|   /** | ||||
|    * Data sent with the interaction | ||||
|    * **This can be undefined only when Interaction is not a Slash Command** | ||||
|    * Data sent with the interaction. Undefined only when Interaction is not Slash Command.* | ||||
|    */ | ||||
|   data: InteractionData | ||||
|   data?: InteractionApplicationCommandData | ||||
|   /** ID of the Guild in which Interaction was invoked */ | ||||
|   guild_id: string | ||||
|   guild_id?: string | ||||
|   /** ID of the Channel in which Interaction was invoked */ | ||||
|   channel_id: string | ||||
|   channel_id?: string | ||||
| } | ||||
| 
 | ||||
| export interface SlashCommandChoice { | ||||
|  | @ -57,7 +85,9 @@ export interface SlashCommandChoice { | |||
| } | ||||
| 
 | ||||
| export enum SlashCommandOptionType { | ||||
|   /** A sub command that is either a part of a root command or Sub Command Group */ | ||||
|   SUB_COMMAND = 1, | ||||
|   /** A sub command group that is present in root command's options */ | ||||
|   SUB_COMMAND_GROUP = 2, | ||||
|   STRING = 3, | ||||
|   INTEGER = 4, | ||||
|  | @ -68,58 +98,71 @@ export enum SlashCommandOptionType { | |||
| } | ||||
| 
 | ||||
| export interface SlashCommandOption { | ||||
|   /** Name of the option. */ | ||||
|   name: string | ||||
|   /** Description not required in Sub-Command or Sub-Command-Group */ | ||||
|   /** Description of the Option. Not required in Sub-Command-Group */ | ||||
|   description?: string | ||||
|   /** Option type */ | ||||
|   type: SlashCommandOptionType | ||||
|   /** Whether the option is required or not, false by default */ | ||||
|   required?: boolean | ||||
|   default?: boolean | ||||
|   /** Optional choices out of which User can choose value */ | ||||
|   choices?: SlashCommandChoice[] | ||||
|   /** Nested options for Sub-Command or Sub-Command-Groups */ | ||||
|   options?: SlashCommandOption[] | ||||
| } | ||||
| 
 | ||||
| /** Represents the Slash Command (Application Command) payload sent for creating/bulk editing. */ | ||||
| export interface SlashCommandPartial { | ||||
|   /** Name of the Slash Command */ | ||||
|   name: string | ||||
|   /** Description of the Slash Command */ | ||||
|   description: string | ||||
|   /** Options (arguments, sub commands or group) of the Slash Command */ | ||||
|   options?: SlashCommandOption[] | ||||
| } | ||||
| 
 | ||||
| /** Represents a fully qualified Slash Command (Application Command) payload. */ | ||||
| export interface SlashCommandPayload extends SlashCommandPartial { | ||||
|   /** ID of the Slash Command */ | ||||
|   id: string | ||||
|   /** Application ID */ | ||||
|   application_id: string | ||||
| } | ||||
| 
 | ||||
| export enum InteractionResponseType { | ||||
|   /** Just ack a ping, Http-only. */ | ||||
|   PONG = 1, | ||||
|   /** Do nothing, just acknowledge the Interaction */ | ||||
|   /** @deprecated **DEPRECATED:** Do nothing, just acknowledge the Interaction */ | ||||
|   ACKNOWLEDGE = 2, | ||||
|   /** Send a channel message without "<User> used /<Command> with <Bot>" */ | ||||
|   /** @deprecated **DEPRECATED:** Send a channel message without "<User> used /<Command> with <Bot>" */ | ||||
|   CHANNEL_MESSAGE = 3, | ||||
|   /** Send a channel message with "<User> used /<Command> with <Bot>" */ | ||||
|   /** Send a channel message as response. */ | ||||
|   CHANNEL_MESSAGE_WITH_SOURCE = 4, | ||||
|   /** Send nothing further, but send "<User> used /<Command> with <Bot>" */ | ||||
|   ACK_WITH_SOURCE = 5 | ||||
|   /** Let the user know bot is processing ("thinking") and you can edit the response later */ | ||||
|   DEFERRED_CHANNEL_MESSAGE = 5 | ||||
| } | ||||
| 
 | ||||
| export interface InteractionResponsePayload { | ||||
|   /** Type of the response */ | ||||
|   type: InteractionResponseType | ||||
|   /** Data to be sent with response. Optional for types: Pong, Acknowledge, Ack with Source */ | ||||
|   data?: InteractionResponseDataPayload | ||||
| } | ||||
| 
 | ||||
| export interface InteractionResponseDataPayload { | ||||
|   tts?: boolean | ||||
|   /** Text content of the Response (Message) */ | ||||
|   content: string | ||||
|   /** Upto 10 Embed Objects to send with Response */ | ||||
|   embeds?: EmbedPayload[] | ||||
|   allowed_mentions?: { | ||||
|     parse?: 'everyone' | 'users' | 'roles' | ||||
|     roles?: string[] | ||||
|     users?: string[] | ||||
|   } | ||||
|   /** Allowed Mentions object */ | ||||
|   allowed_mentions?: AllowedMentionsPayload | ||||
|   flags?: number | ||||
| } | ||||
| 
 | ||||
| export enum InteractionResponseFlags { | ||||
|   /** A Message which is only visible to Interaction User, and is not saved on backend */ | ||||
|   /** A Message which is only visible to Interaction User. */ | ||||
|   EPHEMERAL = 1 << 6 | ||||
| } | ||||
|  |  | |||
							
								
								
									
										3
									
								
								src/utils/dict.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/utils/dict.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | |||
| export interface Dict<T> { | ||||
|   [name: string]: T | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue