Merge pull request #56 from DjDeveloperr/slash
feat: Slash Commands & Interactions [WIP?]
This commit is contained in:
		
						commit
						55ddc64187
					
				
					 23 changed files with 1282 additions and 105 deletions
				
			
		|  | @ -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' | ||||
|  | @ -53,6 +53,8 @@ import { | |||
|   EveryChannelTypes, | ||||
|   EveryTextChannelTypes | ||||
| } from '../../utils/getChannelByType.ts' | ||||
| import { interactionCreate } from './interactionCreate.ts' | ||||
| import { Interaction } from '../../structures/slash.ts' | ||||
| 
 | ||||
| export const gatewayHandlers: { | ||||
|   [eventCode in GatewayEvents]: GatewayEventHandler | undefined | ||||
|  | @ -93,7 +95,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 { | ||||
|  | @ -107,56 +110,227 @@ 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<string, Message>, | ||||
|     uncached: Set<string> | ||||
|   ) => 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 | ||||
|   /** | ||||
|    * A Slash Command was triggered | ||||
|    */ | ||||
|   interactionCreate: (interaction: Interaction) => void | ||||
| } | ||||
|  |  | |||
							
								
								
									
										29
									
								
								src/gateway/handlers/interactionCreate.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/gateway/handlers/interactionCreate.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,29 @@ | |||
| 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' | ||||
| 
 | ||||
| export const interactionCreate: GatewayEventHandler = async ( | ||||
|   gateway: Gateway, | ||||
|   d: InteractionPayload | ||||
| ) => { | ||||
|   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<GuildTextChannel>(d.channel_id)) ?? | ||||
|     (await gateway.client.channels.fetch<GuildTextChannel>(d.channel_id)) | ||||
| 
 | ||||
|   const interaction = new Interaction(gateway.client, d, { | ||||
|     member, | ||||
|     guild, | ||||
|     channel | ||||
|   }) | ||||
|   gateway.client.emit('interactionCreate', interaction) | ||||
| } | ||||
|  | @ -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
 | ||||
|  |  | |||
|  | @ -12,6 +12,16 @@ 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' | ||||
| import { Interaction } from '../structures/slash.ts' | ||||
| import { SlashModule } from './slashModule.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 { | ||||
|  | @ -33,6 +43,10 @@ export interface ClientOptions { | |||
|   reactionCacheLifetime?: number | ||||
|   /** Whether to fetch Uncached Message of Reaction or not? */ | ||||
|   fetchUncachedReactions?: boolean | ||||
|   /** Client Properties */ | ||||
|   clientProperties?: ClientProperties | ||||
|   /** Enable/Disable Slash Commands Integration (enabled by default) */ | ||||
|   enableSlash?: boolean | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  | @ -61,6 +75,10 @@ export class Client extends EventEmitter { | |||
|   reactionCacheLifetime: number = 3600000 | ||||
|   /** Whether to fetch Uncached Message of Reaction or not? */ | ||||
|   fetchUncachedReactions: boolean = false | ||||
|   /** Client Properties */ | ||||
|   clientProperties: ClientProperties | ||||
|   /** Slash-Commands Management client */ | ||||
|   slash: SlashClient | ||||
| 
 | ||||
|   users: UsersManager = new UsersManager(this) | ||||
|   guilds: GuildManager = new GuildManager(this) | ||||
|  | @ -72,6 +90,13 @@ 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 | ||||
|   }> | ||||
| 
 | ||||
|   _decoratedSlashModules?: SlashModule[] | ||||
| 
 | ||||
|   private readonly _untypedOn = this.on | ||||
| 
 | ||||
|  | @ -113,6 +138,19 @@ export class Client extends EventEmitter { | |||
|       }) | ||||
|       this._decoratedEvents = undefined | ||||
|     } | ||||
| 
 | ||||
|     this.clientProperties = | ||||
|       options.clientProperties === undefined | ||||
|         ? { | ||||
|             os: Deno.build.os, | ||||
|             browser: 'harmony', | ||||
|             device: 'harmony' | ||||
|           } | ||||
|         : options.clientProperties | ||||
| 
 | ||||
|     this.slash = new SlashClient(this, { | ||||
|       enabled: options.enableSlash | ||||
|     }) | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | @ -171,7 +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 | SlashModule, prop: string) { | ||||
|     if (client._decoratedSlash === undefined) client._decoratedSlash = [] | ||||
|     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) | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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 | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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] | ||||
|         }) | ||||
|  |  | |||
							
								
								
									
										222
									
								
								src/models/slashClient.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										222
									
								
								src/models/slashClient.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,222 @@ | |||
| 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 { | ||||
|   InteractionType, | ||||
|   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 { | ||||
|   slash: SlashCommandsManager | ||||
|   id: string | ||||
|   applicationID: string | ||||
|   name: string | ||||
|   description: string | ||||
|   options: SlashCommandOption[] | ||||
|   _guild?: string | ||||
| 
 | ||||
|   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<void> { | ||||
|     await this.slash.delete(this.id, this._guild) | ||||
|   } | ||||
| 
 | ||||
|   async edit(data: SlashCommandPartial): Promise<void> { | ||||
|     await this.slash.edit(this.id, data, this._guild) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class SlashCommandsManager { | ||||
|   client: Client | ||||
|   slash: SlashClient | ||||
| 
 | ||||
|   constructor(client: Client) { | ||||
|     this.client = client | ||||
|     this.slash = client.slash | ||||
|   } | ||||
| 
 | ||||
|   /** Get all Global Slash Commands */ | ||||
|   async all(): Promise<Collection<string, SlashCommand>> { | ||||
|     const col = new Collection<string, SlashCommand>() | ||||
| 
 | ||||
|     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) { | ||||
|       const cmd = new SlashCommand(this, raw) | ||||
|       col.set(raw.id, cmd) | ||||
|     } | ||||
| 
 | ||||
|     return col | ||||
|   } | ||||
| 
 | ||||
|   /** Get a Guild's Slash Commands */ | ||||
|   async guild( | ||||
|     guild: Guild | string | ||||
|   ): Promise<Collection<string, SlashCommand>> { | ||||
|     const col = new Collection<string, SlashCommand>() | ||||
| 
 | ||||
|     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) { | ||||
|       const cmd = new SlashCommand(this, raw) | ||||
|       cmd._guild = typeof guild === 'string' ? guild : guild.id | ||||
|       col.set(raw.id, cmd) | ||||
|     } | ||||
| 
 | ||||
|     return col | ||||
|   } | ||||
| 
 | ||||
|   /** Create a Slash Command (global or Guild) */ | ||||
|   async create( | ||||
|     data: SlashCommandPartial, | ||||
|     guild?: Guild | string | ||||
|   ): Promise<SlashCommand> { | ||||
|     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 | ||||
|     ) | ||||
| 
 | ||||
|     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: SlashCommandPartial, | ||||
|     guild?: Guild | string | ||||
|   ): Promise<SlashCommandsManager> { | ||||
|     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 | ||||
|   } | ||||
| 
 | ||||
|   /** Delete a Slash Command (global or Guild) */ | ||||
|   async delete( | ||||
|     id: string, | ||||
|     guild?: Guild | string | ||||
|   ): Promise<SlashCommandsManager> { | ||||
|     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: SlashCommandsManager | ||||
|   handlers: SlashCommandHandler[] = [] | ||||
| 
 | ||||
|   constructor(client: Client, options?: SlashOptions) { | ||||
|     this.client = 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) | ||||
|     ) | ||||
|   } | ||||
| 
 | ||||
|   /** Adds a new Slash Command Handler */ | ||||
|   handle( | ||||
|     name: string, | ||||
|     handler: SlashCommandHandlerCallback, | ||||
|     guild?: string | ||||
|   ): SlashClient { | ||||
|     this.handlers.push({ | ||||
|       name, | ||||
|       guild, | ||||
|       handler | ||||
|     }) | ||||
|     return this | ||||
|   } | ||||
| 
 | ||||
|   /** 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) | ||||
|       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) | ||||
|   } | ||||
| } | ||||
							
								
								
									
										18
									
								
								src/models/slashModule.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/models/slashModule.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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 | ||||
|   } | ||||
| } | ||||
|  | @ -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,13 @@ export class ClientPresence { | |||
|     return new ClientPresence().parse(payload) | ||||
|   } | ||||
| 
 | ||||
|   create(): StatusUpdatePayload { | ||||
|   create(): StatusPayload { | ||||
|     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 +152,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
 | ||||
|   // }
 | ||||
| } | ||||
|  |  | |||
							
								
								
									
										233
									
								
								src/structures/slash.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								src/structures/slash.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,233 @@ | |||
| import { Client } from '../models/client.ts' | ||||
| import { MessageOption } from '../types/channel.ts' | ||||
| import { INTERACTION_CALLBACK, WEBHOOK_MESSAGE } from '../types/endpoint.ts' | ||||
| import { | ||||
|   InteractionData, | ||||
|   InteractionPayload, | ||||
|   InteractionResponsePayload, | ||||
|   InteractionResponseType | ||||
| } from '../types/slash.ts' | ||||
| import { Embed } from './embed.ts' | ||||
| import { Guild } from './guild.ts' | ||||
| import { Member } from './member.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 | ||||
|   content?: string | ||||
|   embeds?: Embed[] | ||||
|   tts?: boolean | ||||
|   flags?: number | ||||
|   temp?: boolean | ||||
|   allowedMentions?: { | ||||
|     parse?: string | ||||
|     roles?: string[] | ||||
|     users?: string[] | ||||
|     everyone?: boolean | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export class Interaction { | ||||
|   client: Client | ||||
|   type: number | ||||
|   token: string | ||||
|   id: string | ||||
|   data: InteractionData | ||||
|   channel: GuildTextChannel | ||||
|   guild: Guild | ||||
|   member: Member | ||||
|   _savedHook?: Webhook | ||||
| 
 | ||||
|   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 = 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 | ||||
|   } | ||||
| 
 | ||||
|   option<T = any>(name: string): T { | ||||
|     return this.data.options.find((e) => e.name === name)?.value | ||||
|   } | ||||
| 
 | ||||
|   async respond(data: InteractionResponse): Promise<Interaction> { | ||||
|     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.temp === true ? 64 : data.flags ?? undefined, | ||||
|               allowed_mentions: (data.allowedMentions ?? undefined) as any | ||||
|             } | ||||
|           : undefined | ||||
|     } | ||||
| 
 | ||||
|     await this.client.rest.post( | ||||
|       INTERACTION_CALLBACK(this.id, this.token), | ||||
|       payload | ||||
|     ) | ||||
| 
 | ||||
|     return this | ||||
|   } | ||||
| 
 | ||||
|   async editResponse(data: { | ||||
|     content?: string | ||||
|     embeds?: Embed[] | ||||
|   }): Promise<Interaction> { | ||||
|     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 | ||||
|   } | ||||
| 
 | ||||
|   async deleteResponse(): Promise<Interaction> { | ||||
|     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}` | ||||
|   } | ||||
| 
 | ||||
|   async send( | ||||
|     text?: string | AllWebhookMessageOptions, | ||||
|     option?: AllWebhookMessageOptions | ||||
|   ): Promise<Message> { | ||||
|     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<Interaction> { | ||||
|     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<Interaction> { | ||||
|     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 | ||||
|   } | ||||
| } | ||||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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<Webhook> { | ||||
|     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<Webhook> { | ||||
|     await this.client?.rest.delete( | ||||
|       WEBHOOK_MESSAGE( | ||||
|         this.id, | ||||
|         (this.token ?? this.client.token) as string, | ||||
|         typeof message === 'string' ? message : message.id | ||||
|       ) | ||||
|     ) | ||||
|     return this | ||||
|   } | ||||
| } | ||||
|  |  | |||
|  | @ -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',
 | ||||
|  |  | |||
							
								
								
									
										137
									
								
								src/test/music.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										137
									
								
								src/test/music.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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<string, Player>() | ||||
| 
 | ||||
| class VCExtension extends Extension { | ||||
|   name = 'VC' | ||||
|   subPrefix = 'vc' | ||||
| 
 | ||||
|   @command() | ||||
|   async join(ctx: CommandContext): Promise<any> { | ||||
|     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<any> { | ||||
|     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) | ||||
							
								
								
									
										51
									
								
								src/test/slash-cmd.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/test/slash-cmd.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -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) | ||||
							
								
								
									
										96
									
								
								src/test/slash.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/test/slash.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,96 @@ | |||
| import { Client, Intents, event, slash } from '../../mod.ts' | ||||
| import { Embed } from '../structures/embed.ts' | ||||
| import { Interaction } from '../structures/slash.ts' | ||||
| import { TOKEN } from './config.ts' | ||||
| 
 | ||||
| export class MyClient extends Client { | ||||
|   @event() | ||||
|   ready(): void { | ||||
|     console.log(`Logged in as ${this.user?.tag}!`) | ||||
|   } | ||||
| 
 | ||||
|   @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]') | ||||
|         } | ||||
|         d.respond({ | ||||
|           content: '```js\n' + `${res}` + '\n```' | ||||
|         }).catch(() => {}) | ||||
|       } catch (e) { | ||||
|         d.respond({ | ||||
|           content: '```js\n' + `${e.stack}` + '\n```' | ||||
|         }) | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @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() | ||||
|   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!` | ||||
|     }) | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| const client = new MyClient() | ||||
| client.connect(TOKEN, Intents.None) | ||||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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 { | ||||
|  | @ -120,11 +121,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 { | ||||
|  |  | |||
							
								
								
									
										90
									
								
								src/types/slash.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/types/slash.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,90 @@ | |||
| import { EmbedPayload } from './channel.ts' | ||||
| import { MemberPayload } from './guild.ts' | ||||
| 
 | ||||
| export interface InteractionOption { | ||||
|   name: string | ||||
|   value?: any | ||||
|   options?: any[] | ||||
| } | ||||
| 
 | ||||
| export interface InteractionData { | ||||
|   name: string | ||||
|   id: string | ||||
|   options: InteractionOption[] | ||||
| } | ||||
| 
 | ||||
| export enum InteractionType { | ||||
|   PING = 1, | ||||
|   APPLICATION_COMMAND = 2 | ||||
| } | ||||
| 
 | ||||
| export interface InteractionPayload { | ||||
|   type: InteractionType | ||||
|   token: string | ||||
|   member: MemberPayload | ||||
|   id: string | ||||
|   data: InteractionData | ||||
|   guild_id: string | ||||
|   channel_id: string | ||||
| } | ||||
| 
 | ||||
| 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[] | ||||
|   options?: SlashCommandOption[] | ||||
| } | ||||
| 
 | ||||
| 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, | ||||
|   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 | ||||
| } | ||||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue