Merge pull request #106 from DjDeveloperr/slash
BREAKING: DM Slash Commands, new Interactions API changes
This commit is contained in:
commit
7dc316c76f
22 changed files with 973 additions and 426 deletions
106
deploy.ts
Normal file
106
deploy.ts
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import {
|
||||||
|
SlashCommandsManager,
|
||||||
|
SlashClient,
|
||||||
|
SlashCommandHandlerCallback
|
||||||
|
} from './src/models/slashClient.ts'
|
||||||
|
import { InteractionResponseType, InteractionType } from './src/types/slash.ts'
|
||||||
|
|
||||||
|
export interface DeploySlashInitOptions {
|
||||||
|
env?: boolean
|
||||||
|
publicKey?: string
|
||||||
|
token?: string
|
||||||
|
id?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
let client: SlashClient
|
||||||
|
let commands: SlashCommandsManager
|
||||||
|
|
||||||
|
export function init(options: DeploySlashInitOptions): void {
|
||||||
|
if (client !== undefined) throw new Error('Already initialized')
|
||||||
|
if (options.env === true) {
|
||||||
|
options.publicKey = Deno.env.get('PUBLIC_KEY')
|
||||||
|
options.token = Deno.env.get('TOKEN')
|
||||||
|
options.id = Deno.env.get('ID')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.publicKey === undefined)
|
||||||
|
throw new Error('Public Key not provided')
|
||||||
|
|
||||||
|
client = new SlashClient({
|
||||||
|
id: options.id,
|
||||||
|
token: options.token,
|
||||||
|
publicKey: options.publicKey
|
||||||
|
})
|
||||||
|
|
||||||
|
commands = client.commands
|
||||||
|
|
||||||
|
const cb = async (evt: {
|
||||||
|
respondWith: CallableFunction
|
||||||
|
request: Request
|
||||||
|
}): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const d = await client.verifyFetchEvent({
|
||||||
|
respondWith: (...args: any[]) => evt.respondWith(...args),
|
||||||
|
request: evt.request,
|
||||||
|
})
|
||||||
|
if (d === false) {
|
||||||
|
await evt.respondWith(
|
||||||
|
new Response('Not Authorized', {
|
||||||
|
status: 400
|
||||||
|
})
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (d.type === InteractionType.PING) {
|
||||||
|
await d.respond({ type: InteractionResponseType.PONG })
|
||||||
|
client.emit('ping')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await (client as any)._process(d)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
await client.emit('interactionError', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addEventListener('fetch', cb as any)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function handle(
|
||||||
|
cmd:
|
||||||
|
| string
|
||||||
|
| {
|
||||||
|
name: string
|
||||||
|
parent?: string
|
||||||
|
group?: string
|
||||||
|
guild?: string
|
||||||
|
},
|
||||||
|
handler: SlashCommandHandlerCallback
|
||||||
|
): void {
|
||||||
|
const handle = {
|
||||||
|
name: typeof cmd === 'string' ? cmd : cmd.name,
|
||||||
|
handler,
|
||||||
|
...(typeof cmd === 'string' ? {} : cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof handle.name === 'string' && handle.name.includes(' ') && handle.parent === undefined && handle.group === undefined) {
|
||||||
|
const parts = handle.name.split(/ +/).filter(e => e !== '')
|
||||||
|
if (parts.length > 3 || parts.length < 1) throw new Error('Invalid command name')
|
||||||
|
const root = parts.shift() as string
|
||||||
|
const group = parts.length === 2 ? parts.shift() : undefined
|
||||||
|
const sub = parts.shift()
|
||||||
|
|
||||||
|
handle.name = sub ?? root
|
||||||
|
handle.group = group
|
||||||
|
handle.parent = sub === undefined ? undefined : root
|
||||||
|
}
|
||||||
|
|
||||||
|
client.handle(handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { commands, client }
|
||||||
|
export * from './src/types/slash.ts'
|
||||||
|
export * from './src/structures/slash.ts'
|
||||||
|
export * from './src/models/slashClient.ts'
|
5
deps.ts
5
deps.ts
|
@ -1,11 +1,6 @@
|
||||||
export { EventEmitter } from 'https://deno.land/x/event@0.2.1/mod.ts'
|
export { EventEmitter } from 'https://deno.land/x/event@0.2.1/mod.ts'
|
||||||
export { unzlib } from 'https://denopkg.com/DjDeveloperr/denoflate@1.2/mod.ts'
|
export { unzlib } from 'https://denopkg.com/DjDeveloperr/denoflate@1.2/mod.ts'
|
||||||
export { fetchAuto } from 'https://deno.land/x/fetchbase64@1.0.0/mod.ts'
|
export { fetchAuto } from 'https://deno.land/x/fetchbase64@1.0.0/mod.ts'
|
||||||
export { connect } from 'https://deno.land/x/redis@v0.14.1/mod.ts'
|
|
||||||
export type {
|
|
||||||
Redis,
|
|
||||||
RedisConnectOptions
|
|
||||||
} from 'https://deno.land/x/redis@v0.14.1/mod.ts'
|
|
||||||
export { walk } from 'https://deno.land/std@0.86.0/fs/walk.ts'
|
export { walk } from 'https://deno.land/std@0.86.0/fs/walk.ts'
|
||||||
export { join } from 'https://deno.land/std@0.86.0/path/mod.ts'
|
export { join } from 'https://deno.land/std@0.86.0/path/mod.ts'
|
||||||
export { Mixin } from 'https://esm.sh/ts-mixer@5.4.0'
|
export { Mixin } from 'https://esm.sh/ts-mixer@5.4.0'
|
||||||
|
|
26
mod.ts
26
mod.ts
|
@ -5,7 +5,13 @@ export type { GatewayTypedEvents } from './src/gateway/index.ts'
|
||||||
export type { ClientEvents } from './src/gateway/handlers/index.ts'
|
export type { ClientEvents } from './src/gateway/handlers/index.ts'
|
||||||
export * from './src/models/client.ts'
|
export * from './src/models/client.ts'
|
||||||
export * from './src/models/slashClient.ts'
|
export * from './src/models/slashClient.ts'
|
||||||
export { RESTManager, TokenType, HttpResponseCode } from './src/models/rest.ts'
|
export {
|
||||||
|
RESTManager,
|
||||||
|
TokenType,
|
||||||
|
HttpResponseCode,
|
||||||
|
DiscordAPIError
|
||||||
|
} from './src/models/rest.ts'
|
||||||
|
export type { APIMap, DiscordAPIErrorPayload } from './src/models/rest.ts'
|
||||||
export type { RequestHeaders } from './src/models/rest.ts'
|
export type { RequestHeaders } from './src/models/rest.ts'
|
||||||
export type { RESTOptions } from './src/models/rest.ts'
|
export type { RESTOptions } from './src/models/rest.ts'
|
||||||
export * from './src/models/cacheAdapter.ts'
|
export * from './src/models/cacheAdapter.ts'
|
||||||
|
@ -63,7 +69,11 @@ export { NewsChannel } from './src/structures/guildNewsChannel.ts'
|
||||||
export { VoiceChannel } from './src/structures/guildVoiceChannel.ts'
|
export { VoiceChannel } from './src/structures/guildVoiceChannel.ts'
|
||||||
export { Invite } from './src/structures/invite.ts'
|
export { Invite } from './src/structures/invite.ts'
|
||||||
export * from './src/structures/member.ts'
|
export * from './src/structures/member.ts'
|
||||||
export { Message, MessageAttachment } from './src/structures/message.ts'
|
export {
|
||||||
|
Message,
|
||||||
|
MessageAttachment,
|
||||||
|
MessageInteraction
|
||||||
|
} from './src/structures/message.ts'
|
||||||
export { MessageMentions } from './src/structures/messageMentions.ts'
|
export { MessageMentions } from './src/structures/messageMentions.ts'
|
||||||
export {
|
export {
|
||||||
Presence,
|
Presence,
|
||||||
|
@ -110,6 +120,16 @@ export type {
|
||||||
GuildVoiceChannelPayload,
|
GuildVoiceChannelPayload,
|
||||||
GroupDMChannelPayload,
|
GroupDMChannelPayload,
|
||||||
MessageOptions,
|
MessageOptions,
|
||||||
|
MessagePayload,
|
||||||
|
MessageInteractionPayload,
|
||||||
|
MessageReference,
|
||||||
|
MessageActivity,
|
||||||
|
MessageActivityTypes,
|
||||||
|
MessageApplication,
|
||||||
|
MessageFlags,
|
||||||
|
MessageStickerFormatTypes,
|
||||||
|
MessageStickerPayload,
|
||||||
|
MessageTypes,
|
||||||
OverwriteAsArg,
|
OverwriteAsArg,
|
||||||
Overwrite,
|
Overwrite,
|
||||||
OverwriteAsOptions
|
OverwriteAsOptions
|
||||||
|
@ -146,5 +166,7 @@ export { UserFlags } from './src/types/userFlags.ts'
|
||||||
export type { VoiceStatePayload } from './src/types/voice.ts'
|
export type { VoiceStatePayload } from './src/types/voice.ts'
|
||||||
export type { WebhookPayload } from './src/types/webhook.ts'
|
export type { WebhookPayload } from './src/types/webhook.ts'
|
||||||
export * from './src/models/collectors.ts'
|
export * from './src/models/collectors.ts'
|
||||||
|
export type { Dict } from './src/utils/dict.ts'
|
||||||
|
export * from './src/models/redisCache.ts'
|
||||||
export { ColorUtil } from './src/utils/colorutil.ts'
|
export { ColorUtil } from './src/utils/colorutil.ts'
|
||||||
export type { Colors } from './src/utils/colorutil.ts'
|
export type { Colors } from './src/utils/colorutil.ts'
|
||||||
|
|
|
@ -414,4 +414,5 @@ export type ClientEvents = {
|
||||||
commandMissingArgs: [ctx: CommandContext]
|
commandMissingArgs: [ctx: CommandContext]
|
||||||
commandUsed: [ctx: CommandContext]
|
commandUsed: [ctx: CommandContext]
|
||||||
commandError: [ctx: CommandContext, err: Error]
|
commandError: [ctx: CommandContext, err: Error]
|
||||||
|
gatewayError: [err: ErrorEvent, shards: [number, number]]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,110 @@
|
||||||
|
import { Guild } from '../../structures/guild.ts'
|
||||||
import { Member } from '../../structures/member.ts'
|
import { Member } from '../../structures/member.ts'
|
||||||
import { Interaction } from '../../structures/slash.ts'
|
import {
|
||||||
|
Interaction,
|
||||||
|
InteractionApplicationCommandResolved,
|
||||||
|
InteractionChannel
|
||||||
|
} from '../../structures/slash.ts'
|
||||||
import { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
|
import { GuildTextBasedChannel } from '../../structures/guildTextChannel.ts'
|
||||||
import { InteractionPayload } from '../../types/slash.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'
|
import { Gateway, GatewayEventHandler } from '../index.ts'
|
||||||
|
import { User } from '../../structures/user.ts'
|
||||||
|
import { Role } from '../../structures/role.ts'
|
||||||
|
|
||||||
export const interactionCreate: GatewayEventHandler = async (
|
export const interactionCreate: GatewayEventHandler = async (
|
||||||
gateway: Gateway,
|
gateway: Gateway,
|
||||||
d: InteractionPayload
|
d: InteractionPayload
|
||||||
) => {
|
) => {
|
||||||
const guild = await gateway.client.guilds.get(d.guild_id)
|
// NOTE(DjDeveloperr): Mason once mentioned that channel_id can be optional in Interaction.
|
||||||
if (guild === undefined) return
|
// 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 guild =
|
||||||
const member = ((await guild.members.get(
|
d.guild_id === undefined
|
||||||
d.member.user.id
|
? undefined
|
||||||
)) as unknown) as Member
|
: 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 =
|
const channel =
|
||||||
(await gateway.client.channels.get<GuildTextBasedChannel>(d.channel_id)) ??
|
(await gateway.client.channels.get<GuildTextBasedChannel>(d.channel_id)) ??
|
||||||
(await gateway.client.channels.fetch<GuildTextBasedChannel>(d.channel_id))
|
(await gateway.client.channels.fetch<GuildTextBasedChannel>(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, {
|
const interaction = new Interaction(gateway.client, d, {
|
||||||
member,
|
member,
|
||||||
guild,
|
guild,
|
||||||
channel
|
channel,
|
||||||
|
user,
|
||||||
|
resolved
|
||||||
})
|
})
|
||||||
gateway.client.emit('interactionCreate', interaction)
|
gateway.client.emit('interactionCreate', interaction)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
import { GatewayResponse } from '../types/gatewayResponse.ts'
|
import { GatewayResponse } from '../types/gatewayResponse.ts'
|
||||||
import {
|
import {
|
||||||
GatewayOpcodes,
|
GatewayOpcodes,
|
||||||
GatewayIntents,
|
|
||||||
GatewayCloseCodes,
|
GatewayCloseCodes,
|
||||||
IdentityPayload,
|
IdentityPayload,
|
||||||
StatusUpdatePayload,
|
StatusUpdatePayload,
|
||||||
|
@ -19,6 +18,7 @@ import { delay } from '../utils/delay.ts'
|
||||||
import { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
import { VoiceChannel } from '../structures/guildVoiceChannel.ts'
|
||||||
import { Guild } from '../structures/guild.ts'
|
import { Guild } from '../structures/guild.ts'
|
||||||
import { HarmonyEventEmitter } from '../utils/events.ts'
|
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||||
|
import { decodeText } from '../utils/encoding.ts'
|
||||||
|
|
||||||
export interface RequestMembersOptions {
|
export interface RequestMembersOptions {
|
||||||
limit?: number
|
limit?: number
|
||||||
|
@ -57,8 +57,6 @@ export type GatewayTypedEvents = {
|
||||||
*/
|
*/
|
||||||
export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
websocket?: WebSocket
|
websocket?: WebSocket
|
||||||
token?: string
|
|
||||||
intents?: GatewayIntents[]
|
|
||||||
connected = false
|
connected = false
|
||||||
initialized = false
|
initialized = false
|
||||||
heartbeatInterval = 0
|
heartbeatInterval = 0
|
||||||
|
@ -92,7 +90,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
}
|
}
|
||||||
if (data instanceof Uint8Array) {
|
if (data instanceof Uint8Array) {
|
||||||
data = unzlib(data)
|
data = unzlib(data)
|
||||||
data = new TextDecoder('utf-8').decode(data)
|
data = decodeText(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { op, d, s, t }: GatewayResponse = JSON.parse(data)
|
const { op, d, s, t }: GatewayResponse = JSON.parse(data)
|
||||||
|
@ -157,7 +155,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
|
|
||||||
const handler = gatewayHandlers[t]
|
const handler = gatewayHandlers[t]
|
||||||
|
|
||||||
if (handler !== undefined) {
|
if (handler !== undefined && d !== null) {
|
||||||
handler(this, d)
|
handler(this, d)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -177,8 +175,8 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
}
|
}
|
||||||
case GatewayOpcodes.RECONNECT: {
|
case GatewayOpcodes.RECONNECT: {
|
||||||
this.emit('reconnectRequired')
|
this.emit('reconnectRequired')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
this.debug('Received OpCode RECONNECT')
|
||||||
this.reconnect()
|
await this.reconnect()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -194,8 +192,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case GatewayCloseCodes.UNKNOWN_ERROR:
|
case GatewayCloseCodes.UNKNOWN_ERROR:
|
||||||
this.debug('API has encountered Unknown Error. Reconnecting...')
|
this.debug('API has encountered Unknown Error. Reconnecting...')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
await this.reconnect()
|
||||||
this.reconnect()
|
|
||||||
break
|
break
|
||||||
case GatewayCloseCodes.UNKNOWN_OPCODE:
|
case GatewayCloseCodes.UNKNOWN_OPCODE:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
@ -209,20 +206,17 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
throw new Error('Invalid Token provided!')
|
throw new Error('Invalid Token provided!')
|
||||||
case GatewayCloseCodes.INVALID_SEQ:
|
case GatewayCloseCodes.INVALID_SEQ:
|
||||||
this.debug('Invalid Seq was sent. Reconnecting.')
|
this.debug('Invalid Seq was sent. Reconnecting.')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
await this.reconnect()
|
||||||
this.reconnect()
|
|
||||||
break
|
break
|
||||||
case GatewayCloseCodes.RATE_LIMITED:
|
case GatewayCloseCodes.RATE_LIMITED:
|
||||||
throw new Error("You're ratelimited. Calm down.")
|
throw new Error("You're ratelimited. Calm down.")
|
||||||
case GatewayCloseCodes.SESSION_TIMED_OUT:
|
case GatewayCloseCodes.SESSION_TIMED_OUT:
|
||||||
this.debug('Session Timeout. Reconnecting.')
|
this.debug('Session Timeout. Reconnecting.')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
await this.reconnect(true)
|
||||||
this.reconnect(true)
|
|
||||||
break
|
break
|
||||||
case GatewayCloseCodes.INVALID_SHARD:
|
case GatewayCloseCodes.INVALID_SHARD:
|
||||||
this.debug('Invalid Shard was sent. Reconnecting.')
|
this.debug('Invalid Shard was sent. Reconnecting.')
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
await this.reconnect()
|
||||||
this.reconnect()
|
|
||||||
break
|
break
|
||||||
case GatewayCloseCodes.SHARDING_REQUIRED:
|
case GatewayCloseCodes.SHARDING_REQUIRED:
|
||||||
throw new Error("Couldn't connect. Sharding is required!")
|
throw new Error("Couldn't connect. Sharding is required!")
|
||||||
|
@ -260,6 +254,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
error.name = 'ErrorEvent'
|
error.name = 'ErrorEvent'
|
||||||
console.log(error)
|
console.log(error)
|
||||||
this.emit('error', error, event)
|
this.emit('error', error, event)
|
||||||
|
this.client.emit('gatewayError', event, this.shards)
|
||||||
}
|
}
|
||||||
|
|
||||||
private enqueueIdentify(forceNew?: boolean): void {
|
private enqueueIdentify(forceNew?: boolean): void {
|
||||||
|
@ -269,8 +264,9 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendIdentify(forceNewSession?: boolean): Promise<void> {
|
private async sendIdentify(forceNewSession?: boolean): Promise<void> {
|
||||||
if (typeof this.token !== 'string') throw new Error('Token not specified')
|
if (typeof this.client.token !== 'string')
|
||||||
if (typeof this.intents !== 'object')
|
throw new Error('Token not specified')
|
||||||
|
if (typeof this.client.intents !== 'object')
|
||||||
throw new Error('Intents not specified')
|
throw new Error('Intents not specified')
|
||||||
|
|
||||||
if (this.client.fetchGatewayInfo === true) {
|
if (this.client.fetchGatewayInfo === true) {
|
||||||
|
@ -300,7 +296,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
}
|
}
|
||||||
|
|
||||||
const payload: IdentityPayload = {
|
const payload: IdentityPayload = {
|
||||||
token: this.token,
|
token: this.client.token,
|
||||||
properties: {
|
properties: {
|
||||||
$os: this.client.clientProperties.os ?? Deno.build.os,
|
$os: this.client.clientProperties.os ?? Deno.build.os,
|
||||||
$browser: this.client.clientProperties.browser ?? 'harmony',
|
$browser: this.client.clientProperties.browser ?? 'harmony',
|
||||||
|
@ -311,7 +307,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
this.shards === undefined
|
this.shards === undefined
|
||||||
? [0, 1]
|
? [0, 1]
|
||||||
: [this.shards[0] ?? 0, this.shards[1] ?? 1],
|
: [this.shards[0] ?? 0, this.shards[1] ?? 1],
|
||||||
intents: this.intents.reduce(
|
intents: this.client.intents.reduce(
|
||||||
(previous, current) => previous | current,
|
(previous, current) => previous | current,
|
||||||
0
|
0
|
||||||
),
|
),
|
||||||
|
@ -327,9 +323,8 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async sendResume(): Promise<void> {
|
private async sendResume(): Promise<void> {
|
||||||
if (typeof this.token !== 'string') throw new Error('Token not specified')
|
if (typeof this.client.token !== 'string')
|
||||||
if (typeof this.intents !== 'object')
|
throw new Error('Token not specified')
|
||||||
throw new Error('Intents not specified')
|
|
||||||
|
|
||||||
if (this.sessionID === undefined) {
|
if (this.sessionID === undefined) {
|
||||||
this.sessionID = await this.cache.get(
|
this.sessionID = await this.cache.get(
|
||||||
|
@ -348,7 +343,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
const resumePayload = {
|
const resumePayload = {
|
||||||
op: GatewayOpcodes.RESUME,
|
op: GatewayOpcodes.RESUME,
|
||||||
d: {
|
d: {
|
||||||
token: this.token,
|
token: this.client.token,
|
||||||
session_id: this.sessionID,
|
session_id: this.sessionID,
|
||||||
seq: this.sequenceID ?? null
|
seq: this.sequenceID ?? null
|
||||||
}
|
}
|
||||||
|
@ -405,6 +400,7 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
|
|
||||||
async reconnect(forceNew?: boolean): Promise<void> {
|
async reconnect(forceNew?: boolean): Promise<void> {
|
||||||
this.emit('reconnecting')
|
this.emit('reconnecting')
|
||||||
|
this.debug('Reconnecting... (force new: ' + String(forceNew) + ')')
|
||||||
|
|
||||||
clearInterval(this.heartbeatIntervalID)
|
clearInterval(this.heartbeatIntervalID)
|
||||||
if (forceNew === true) {
|
if (forceNew === true) {
|
||||||
|
@ -432,6 +428,11 @@ export class Gateway extends HarmonyEventEmitter<GatewayTypedEvents> {
|
||||||
}
|
}
|
||||||
|
|
||||||
close(code: number = 1000, reason?: string): void {
|
close(code: number = 1000, reason?: string): void {
|
||||||
|
this.debug(
|
||||||
|
`Closing with code ${code}${
|
||||||
|
reason !== undefined && reason !== '' ? ` and reason ${reason}` : ''
|
||||||
|
}`
|
||||||
|
)
|
||||||
return this.websocket?.close(code, reason)
|
return this.websocket?.close(code, reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import { Collection } from '../utils/collection.ts'
|
import { Collection } from '../utils/collection.ts'
|
||||||
import { connect, Redis, RedisConnectOptions } from '../../deps.ts'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ICacheAdapter is the interface to be implemented by Cache Adapters for them to be usable with Harmony.
|
* ICacheAdapter is the interface to be implemented by Cache Adapters for them to be usable with Harmony.
|
||||||
|
@ -71,106 +70,3 @@ export class DefaultCacheAdapter implements ICacheAdapter {
|
||||||
return delete this.data[cacheName]
|
return delete this.data[cacheName]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Redis Cache Adapter for using Redis as a cache-provider. */
|
|
||||||
export class RedisCacheAdapter implements ICacheAdapter {
|
|
||||||
_redis: Promise<Redis>
|
|
||||||
redis?: Redis
|
|
||||||
ready: boolean = false
|
|
||||||
readonly _expireIntervalTimer: number = 5000
|
|
||||||
private _expireInterval?: number
|
|
||||||
|
|
||||||
constructor(options: RedisConnectOptions) {
|
|
||||||
this._redis = connect(options)
|
|
||||||
this._redis.then(
|
|
||||||
(redis) => {
|
|
||||||
this.redis = redis
|
|
||||||
this.ready = true
|
|
||||||
this._startExpireInterval()
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
// TODO: Make error for this
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private _startExpireInterval(): void {
|
|
||||||
this._expireInterval = setInterval(() => {
|
|
||||||
this.redis?.scan(0, { pattern: '*:expires' }).then(([_, names]) => {
|
|
||||||
for (const name of names) {
|
|
||||||
this.redis?.hvals(name).then((vals) => {
|
|
||||||
for (const val of vals) {
|
|
||||||
const expireVal: {
|
|
||||||
name: string
|
|
||||||
key: string
|
|
||||||
at: number
|
|
||||||
} = JSON.parse(val)
|
|
||||||
const expired = new Date().getTime() > expireVal.at
|
|
||||||
if (expired) this.redis?.hdel(expireVal.name, expireVal.key)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, this._expireIntervalTimer)
|
|
||||||
}
|
|
||||||
|
|
||||||
async _checkReady(): Promise<void> {
|
|
||||||
if (!this.ready) await this._redis
|
|
||||||
}
|
|
||||||
|
|
||||||
async get(cacheName: string, key: string): Promise<string | undefined> {
|
|
||||||
await this._checkReady()
|
|
||||||
const cache = await this.redis?.hget(cacheName, key)
|
|
||||||
if (cache === undefined) return
|
|
||||||
try {
|
|
||||||
return JSON.parse(cache)
|
|
||||||
} catch (e) {
|
|
||||||
return cache
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async set(
|
|
||||||
cacheName: string,
|
|
||||||
key: string,
|
|
||||||
value: any,
|
|
||||||
expire?: number
|
|
||||||
): Promise<number | undefined> {
|
|
||||||
await this._checkReady()
|
|
||||||
const result = await this.redis?.hset(
|
|
||||||
cacheName,
|
|
||||||
key,
|
|
||||||
typeof value === 'object' ? JSON.stringify(value) : value
|
|
||||||
)
|
|
||||||
if (expire !== undefined) {
|
|
||||||
await this.redis?.hset(
|
|
||||||
`${cacheName}:expires`,
|
|
||||||
key,
|
|
||||||
JSON.stringify({
|
|
||||||
name: cacheName,
|
|
||||||
key,
|
|
||||||
at: new Date().getTime() + expire
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(cacheName: string, key: string): Promise<boolean> {
|
|
||||||
await this._checkReady()
|
|
||||||
const exists = await this.redis?.hexists(cacheName, key)
|
|
||||||
if (exists === 0) return false
|
|
||||||
await this.redis?.hdel(cacheName, key)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
async array(cacheName: string): Promise<any[] | undefined> {
|
|
||||||
await this._checkReady()
|
|
||||||
const data = await this.redis?.hvals(cacheName)
|
|
||||||
return data?.map((e: string) => JSON.parse(e))
|
|
||||||
}
|
|
||||||
|
|
||||||
async deleteCache(cacheName: string): Promise<boolean> {
|
|
||||||
await this._checkReady()
|
|
||||||
return (await this.redis?.del(cacheName)) !== 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { ActivityGame, ClientActivity } from '../types/presence.ts'
|
||||||
import { Extension } from './extensions.ts'
|
import { Extension } from './extensions.ts'
|
||||||
import { SlashClient } from './slashClient.ts'
|
import { SlashClient } from './slashClient.ts'
|
||||||
import { Interaction } from '../structures/slash.ts'
|
import { Interaction } from '../structures/slash.ts'
|
||||||
import { SlashModule } from './slashModule.ts'
|
|
||||||
import { ShardManager } from './shard.ts'
|
import { ShardManager } from './shard.ts'
|
||||||
import { Application } from '../structures/application.ts'
|
import { Application } from '../structures/application.ts'
|
||||||
import { Invite } from '../structures/invite.ts'
|
import { Invite } from '../structures/invite.ts'
|
||||||
|
@ -190,10 +189,10 @@ export class Client extends HarmonyEventEmitter<ClientEvents> {
|
||||||
this.clientProperties =
|
this.clientProperties =
|
||||||
options.clientProperties === undefined
|
options.clientProperties === undefined
|
||||||
? {
|
? {
|
||||||
os: Deno.build.os,
|
os: Deno.build.os,
|
||||||
browser: 'harmony',
|
browser: 'harmony',
|
||||||
device: 'harmony'
|
device: 'harmony'
|
||||||
}
|
}
|
||||||
: options.clientProperties
|
: options.clientProperties
|
||||||
|
|
||||||
if (options.shard !== undefined) this.shard = options.shard
|
if (options.shard !== undefined) this.shard = options.shard
|
||||||
|
@ -208,7 +207,7 @@ export class Client extends HarmonyEventEmitter<ClientEvents> {
|
||||||
this.token = token
|
this.token = token
|
||||||
this.debug('Info', 'Found token in ENV')
|
this.debug('Info', 'Found token in ENV')
|
||||||
}
|
}
|
||||||
} catch (e) {}
|
} catch (e) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
const restOptions: RESTOptions = {
|
const restOptions: RESTOptions = {
|
||||||
|
@ -436,59 +435,3 @@ export function event(name?: keyof ClientEvents) {
|
||||||
client._decoratedEvents[key] = listener
|
client._decoratedEvents[key] = listener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Decorator to create a Slash Command handler */
|
|
||||||
export function slash(name?: string, guild?: string) {
|
|
||||||
return function (client: Client | SlashClient | SlashModule, prop: string) {
|
|
||||||
if (client._decoratedSlash === undefined) client._decoratedSlash = []
|
|
||||||
const item = (client as { [name: string]: any })[prop]
|
|
||||||
if (typeof item !== 'function') {
|
|
||||||
throw new Error('@slash decorator requires a function')
|
|
||||||
} else
|
|
||||||
client._decoratedSlash.push({
|
|
||||||
name: name ?? prop,
|
|
||||||
guild,
|
|
||||||
handler: item
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Decorator to create a Sub-Slash Command handler */
|
|
||||||
export function subslash(parent: string, name?: string, guild?: string) {
|
|
||||||
return function (client: Client | SlashModule | SlashClient, prop: string) {
|
|
||||||
if (client._decoratedSlash === undefined) client._decoratedSlash = []
|
|
||||||
const item = (client as { [name: string]: any })[prop]
|
|
||||||
if (typeof item !== 'function') {
|
|
||||||
throw new Error('@subslash decorator requires a function')
|
|
||||||
} else
|
|
||||||
client._decoratedSlash.push({
|
|
||||||
parent,
|
|
||||||
name: name ?? prop,
|
|
||||||
guild,
|
|
||||||
handler: item
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Decorator to create a Grouped Slash Command handler */
|
|
||||||
export function groupslash(
|
|
||||||
parent: string,
|
|
||||||
group: string,
|
|
||||||
name?: string,
|
|
||||||
guild?: string
|
|
||||||
) {
|
|
||||||
return function (client: Client | SlashModule | SlashClient, prop: string) {
|
|
||||||
if (client._decoratedSlash === undefined) client._decoratedSlash = []
|
|
||||||
const item = (client as { [name: string]: any })[prop]
|
|
||||||
if (typeof item !== 'function') {
|
|
||||||
throw new Error('@groupslash decorator requires a function')
|
|
||||||
} else
|
|
||||||
client._decoratedSlash.push({
|
|
||||||
group,
|
|
||||||
parent,
|
|
||||||
name: name ?? prop,
|
|
||||||
guild,
|
|
||||||
handler: item
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
105
src/models/redisCache.ts
Normal file
105
src/models/redisCache.ts
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
import { ICacheAdapter } from './cacheAdapter.ts'
|
||||||
|
import { connect, Redis, RedisConnectOptions } from 'https://deno.land/x/redis@v0.14.1/mod.ts'
|
||||||
|
|
||||||
|
/** Redis Cache Adapter for using Redis as a cache-provider. */
|
||||||
|
export class RedisCacheAdapter implements ICacheAdapter {
|
||||||
|
_redis: Promise<Redis>
|
||||||
|
redis?: Redis
|
||||||
|
ready: boolean = false
|
||||||
|
readonly _expireIntervalTimer: number = 5000
|
||||||
|
private _expireInterval?: number
|
||||||
|
|
||||||
|
constructor(options: RedisConnectOptions) {
|
||||||
|
this._redis = connect(options)
|
||||||
|
this._redis.then(
|
||||||
|
(redis) => {
|
||||||
|
this.redis = redis
|
||||||
|
this.ready = true
|
||||||
|
this._startExpireInterval()
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
// TODO: Make error for this
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private _startExpireInterval(): void {
|
||||||
|
this._expireInterval = setInterval(() => {
|
||||||
|
this.redis?.scan(0, { pattern: '*:expires' }).then(([_, names]) => {
|
||||||
|
for (const name of names) {
|
||||||
|
this.redis?.hvals(name).then((vals) => {
|
||||||
|
for (const val of vals) {
|
||||||
|
const expireVal: {
|
||||||
|
name: string
|
||||||
|
key: string
|
||||||
|
at: number
|
||||||
|
} = JSON.parse(val)
|
||||||
|
const expired = new Date().getTime() > expireVal.at
|
||||||
|
if (expired) this.redis?.hdel(expireVal.name, expireVal.key)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, this._expireIntervalTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
async _checkReady(): Promise<void> {
|
||||||
|
if (!this.ready) await this._redis
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(cacheName: string, key: string): Promise<string | undefined> {
|
||||||
|
await this._checkReady()
|
||||||
|
const cache = await this.redis?.hget(cacheName, key)
|
||||||
|
if (cache === undefined) return
|
||||||
|
try {
|
||||||
|
return JSON.parse(cache)
|
||||||
|
} catch (e) {
|
||||||
|
return cache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async set(
|
||||||
|
cacheName: string,
|
||||||
|
key: string,
|
||||||
|
value: any,
|
||||||
|
expire?: number
|
||||||
|
): Promise<number | undefined> {
|
||||||
|
await this._checkReady()
|
||||||
|
const result = await this.redis?.hset(
|
||||||
|
cacheName,
|
||||||
|
key,
|
||||||
|
typeof value === 'object' ? JSON.stringify(value) : value
|
||||||
|
)
|
||||||
|
if (expire !== undefined) {
|
||||||
|
await this.redis?.hset(
|
||||||
|
`${cacheName}:expires`,
|
||||||
|
key,
|
||||||
|
JSON.stringify({
|
||||||
|
name: cacheName,
|
||||||
|
key,
|
||||||
|
at: new Date().getTime() + expire
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(cacheName: string, key: string): Promise<boolean> {
|
||||||
|
await this._checkReady()
|
||||||
|
const exists = await this.redis?.hexists(cacheName, key)
|
||||||
|
if (exists === 0) return false
|
||||||
|
await this.redis?.hdel(cacheName, key)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
async array(cacheName: string): Promise<any[] | undefined> {
|
||||||
|
await this._checkReady()
|
||||||
|
const data = await this.redis?.hvals(cacheName)
|
||||||
|
return data?.map((e: string) => JSON.parse(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteCache(cacheName: string): Promise<boolean> {
|
||||||
|
await this._checkReady()
|
||||||
|
return (await this.redis?.del(cacheName)) !== 0
|
||||||
|
}
|
||||||
|
}
|
|
@ -79,8 +79,6 @@ export class ShardManager extends HarmonyEventEmitter<ShardManagerEvents> {
|
||||||
const shardCount = await this.getShardCount()
|
const shardCount = await this.getShardCount()
|
||||||
|
|
||||||
const gw = new Gateway(this.client, [Number(id), shardCount])
|
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)
|
this.list.set(id.toString(), gw)
|
||||||
|
|
||||||
gw.initWebsocket()
|
gw.initWebsocket()
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
import { Guild } from '../structures/guild.ts'
|
import type { Guild } from '../structures/guild.ts'
|
||||||
import { Interaction } from '../structures/slash.ts'
|
|
||||||
import {
|
import {
|
||||||
|
Interaction,
|
||||||
|
InteractionApplicationCommandResolved
|
||||||
|
} from '../structures/slash.ts'
|
||||||
|
import {
|
||||||
|
InteractionPayload,
|
||||||
|
InteractionResponsePayload,
|
||||||
InteractionType,
|
InteractionType,
|
||||||
SlashCommandChoice,
|
SlashCommandChoice,
|
||||||
SlashCommandOption,
|
SlashCommandOption,
|
||||||
|
@ -9,11 +14,13 @@ import {
|
||||||
SlashCommandPayload
|
SlashCommandPayload
|
||||||
} from '../types/slash.ts'
|
} from '../types/slash.ts'
|
||||||
import { Collection } from '../utils/collection.ts'
|
import { Collection } from '../utils/collection.ts'
|
||||||
import { Client } from './client.ts'
|
import type { Client } from './client.ts'
|
||||||
import { RESTManager } from './rest.ts'
|
import { RESTManager } from './rest.ts'
|
||||||
import { SlashModule } from './slashModule.ts'
|
import { SlashModule } from './slashModule.ts'
|
||||||
import { verify as edverify } from 'https://deno.land/x/ed25519/mod.ts'
|
import { verify as edverify } from 'https://deno.land/x/ed25519@1.0.1/mod.ts'
|
||||||
import { Buffer } from 'https://deno.land/std@0.80.0/node/buffer.ts'
|
import { User } from '../structures/user.ts'
|
||||||
|
import { HarmonyEventEmitter } from '../utils/events.ts'
|
||||||
|
import { encodeText, decodeText } from '../utils/encoding.ts'
|
||||||
|
|
||||||
export class SlashCommand {
|
export class SlashCommand {
|
||||||
slash: SlashCommandsManager
|
slash: SlashCommandsManager
|
||||||
|
@ -155,6 +162,7 @@ function buildOptionsArray(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Slash Command Builder */
|
||||||
export class SlashBuilder {
|
export class SlashBuilder {
|
||||||
data: SlashCommandPartial
|
data: SlashCommandPartial
|
||||||
|
|
||||||
|
@ -200,6 +208,7 @@ export class SlashBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Manages Slash Commands, allows fetching/modifying/deleting/creating Slash Commands. */
|
||||||
export class SlashCommandsManager {
|
export class SlashCommandsManager {
|
||||||
slash: SlashClient
|
slash: SlashClient
|
||||||
rest: RESTManager
|
rest: RESTManager
|
||||||
|
@ -351,7 +360,7 @@ export class SlashCommandsManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SlashCommandHandlerCallback = (interaction: Interaction) => any
|
export type SlashCommandHandlerCallback = (interaction: Interaction) => unknown
|
||||||
export interface SlashCommandHandler {
|
export interface SlashCommandHandler {
|
||||||
name: string
|
name: string
|
||||||
guild?: string
|
guild?: string
|
||||||
|
@ -360,6 +369,7 @@ export interface SlashCommandHandler {
|
||||||
handler: SlashCommandHandlerCallback
|
handler: SlashCommandHandlerCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Options for SlashClient */
|
||||||
export interface SlashOptions {
|
export interface SlashOptions {
|
||||||
id?: string | (() => string)
|
id?: string | (() => string)
|
||||||
client?: Client
|
client?: Client
|
||||||
|
@ -369,7 +379,15 @@ export interface SlashOptions {
|
||||||
publicKey?: string
|
publicKey?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SlashClient {
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||||
|
export type SlashClientEvents = {
|
||||||
|
interaction: [Interaction]
|
||||||
|
interactionError: [Error]
|
||||||
|
ping: []
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Slash Client represents an Interactions Client which can be used without Harmony Client. */
|
||||||
|
export class SlashClient extends HarmonyEventEmitter<SlashClientEvents> {
|
||||||
id: string | (() => string)
|
id: string | (() => string)
|
||||||
client?: Client
|
client?: Client
|
||||||
token?: string
|
token?: string
|
||||||
|
@ -389,6 +407,7 @@ export class SlashClient {
|
||||||
}>
|
}>
|
||||||
|
|
||||||
constructor(options: SlashOptions) {
|
constructor(options: SlashOptions) {
|
||||||
|
super()
|
||||||
let id = options.id
|
let id = options.id
|
||||||
if (options.token !== undefined) id = atob(options.token?.split('.')[0])
|
if (options.token !== undefined) id = atob(options.token?.split('.')[0])
|
||||||
if (id === undefined)
|
if (id === undefined)
|
||||||
|
@ -423,8 +442,9 @@ export class SlashClient {
|
||||||
: options.rest
|
: options.rest
|
||||||
: options.client.rest
|
: options.client.rest
|
||||||
|
|
||||||
this.client?.on('interactionCreate', (interaction) =>
|
this.client?.on(
|
||||||
this._process(interaction)
|
'interactionCreate',
|
||||||
|
async (interaction) => await this._process(interaction)
|
||||||
)
|
)
|
||||||
|
|
||||||
this.commands = new SlashCommandsManager(this)
|
this.commands = new SlashCommandsManager(this)
|
||||||
|
@ -469,12 +489,20 @@ export class SlashClient {
|
||||||
const groupMatched =
|
const groupMatched =
|
||||||
e.group !== undefined && e.parent !== undefined
|
e.group !== undefined && e.parent !== undefined
|
||||||
? i.options
|
? 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
|
?.options?.find((o) => o.name === e.name) !== undefined
|
||||||
: true
|
: true
|
||||||
const subMatched =
|
const subMatched =
|
||||||
e.group === undefined && e.parent !== undefined
|
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
|
: true
|
||||||
const nameMatched1 = e.name === i.name
|
const nameMatched1 = e.name === i.name
|
||||||
const parentMatched = hasGroupOrParent ? e.parent === i.name : true
|
const parentMatched = hasGroupOrParent ? e.parent === i.name : true
|
||||||
|
@ -485,11 +513,15 @@ export class SlashClient {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Process an incoming Slash Command (interaction) */
|
/** Process an incoming Interaction */
|
||||||
private _process(interaction: Interaction): void {
|
private async _process(interaction: Interaction): Promise<void> {
|
||||||
if (!this.enabled) return
|
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)
|
const cmd = this._getCommand(interaction)
|
||||||
if (cmd?.group !== undefined)
|
if (cmd?.group !== undefined)
|
||||||
|
@ -499,28 +531,113 @@ export class SlashClient {
|
||||||
|
|
||||||
if (cmd === undefined) return
|
if (cmd === undefined) return
|
||||||
|
|
||||||
cmd.handler(interaction)
|
await this.emit('interaction', interaction)
|
||||||
|
try {
|
||||||
|
await cmd.handler(interaction)
|
||||||
|
} catch (e) {
|
||||||
|
await this.emit('interactionError', e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Verify HTTP based Interaction */
|
||||||
async verifyKey(
|
async verifyKey(
|
||||||
rawBody: string | Uint8Array | Buffer,
|
rawBody: string | Uint8Array,
|
||||||
signature: string,
|
signature: string | Uint8Array,
|
||||||
timestamp: string
|
timestamp: string | Uint8Array
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (this.publicKey === undefined)
|
if (this.publicKey === undefined)
|
||||||
throw new Error('Public Key is not present')
|
throw new Error('Public Key is not present')
|
||||||
return edverify(
|
|
||||||
signature,
|
const fullBody = new Uint8Array([
|
||||||
Buffer.concat([
|
...(typeof timestamp === 'string' ? encodeText(timestamp) : timestamp),
|
||||||
Buffer.from(timestamp, 'utf-8'),
|
...(typeof rawBody === 'string' ? encodeText(rawBody) : rawBody)
|
||||||
Buffer.from(
|
])
|
||||||
rawBody instanceof Uint8Array
|
|
||||||
? new TextDecoder().decode(rawBody)
|
return edverify(signature, fullBody, this.publicKey).catch(() => false)
|
||||||
: rawBody
|
}
|
||||||
|
|
||||||
|
/** Verify [Deno Std HTTP Server Request](https://deno.land/std/http/server.ts) and return Interaction. **Data present in Interaction returned by this method is very different from actual typings as there is no real `Client` behind the scenes to cache things.** */
|
||||||
|
async verifyServerRequest(req: {
|
||||||
|
headers: Headers
|
||||||
|
method: string
|
||||||
|
body: Deno.Reader | Uint8Array
|
||||||
|
respond: (options: {
|
||||||
|
status?: number
|
||||||
|
headers?: Headers
|
||||||
|
body?: string | Uint8Array | FormData
|
||||||
|
}) => Promise<void>
|
||||||
|
}): Promise<false | Interaction> {
|
||||||
|
if (req.method.toLowerCase() !== 'post') return false
|
||||||
|
|
||||||
|
const signature = req.headers.get('x-signature-ed25519')
|
||||||
|
const timestamp = req.headers.get('x-signature-timestamp')
|
||||||
|
if (signature === null || timestamp === null) return false
|
||||||
|
|
||||||
|
const rawbody =
|
||||||
|
req.body instanceof Uint8Array ? req.body : await Deno.readAll(req.body)
|
||||||
|
const verify = await this.verifyKey(rawbody, signature, timestamp)
|
||||||
|
if (!verify) return false
|
||||||
|
|
||||||
|
try {
|
||||||
|
const payload: InteractionPayload = JSON.parse(decodeText(rawbody))
|
||||||
|
|
||||||
|
// TODO: Maybe fix all this hackery going on here?
|
||||||
|
const res = new Interaction(this as any, payload, {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
user: new User(this as any, (payload.member?.user ?? payload.user)!),
|
||||||
|
member: payload.member as any,
|
||||||
|
guild: payload.guild_id as any,
|
||||||
|
channel: payload.channel_id as any,
|
||||||
|
resolved: ((payload.data
|
||||||
|
?.resolved as unknown) as InteractionApplicationCommandResolved) ?? {
|
||||||
|
users: {},
|
||||||
|
members: {},
|
||||||
|
roles: {},
|
||||||
|
channels: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
res._httpRespond = async (d: InteractionResponsePayload | FormData) =>
|
||||||
|
await req.respond({
|
||||||
|
status: 200,
|
||||||
|
headers: new Headers({
|
||||||
|
'content-type':
|
||||||
|
d instanceof FormData ? 'multipart/form-data' : 'application/json'
|
||||||
|
}),
|
||||||
|
body: d instanceof FormData ? d : JSON.stringify(d)
|
||||||
|
})
|
||||||
|
|
||||||
|
return res
|
||||||
|
} catch (e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Verify FetchEvent (for Service Worker usage) and return Interaction if valid */
|
||||||
|
async verifyFetchEvent({
|
||||||
|
request: req,
|
||||||
|
respondWith
|
||||||
|
}: {
|
||||||
|
respondWith: CallableFunction
|
||||||
|
request: Request
|
||||||
|
}): Promise<false | Interaction> {
|
||||||
|
if (req.bodyUsed === true) throw new Error('Request Body already used')
|
||||||
|
if (req.body === null) return false
|
||||||
|
const body = (await req.body.getReader().read()).value
|
||||||
|
if (body === undefined) return false
|
||||||
|
|
||||||
|
return await this.verifyServerRequest({
|
||||||
|
headers: req.headers,
|
||||||
|
body,
|
||||||
|
method: req.method,
|
||||||
|
respond: async (options) => {
|
||||||
|
await respondWith(
|
||||||
|
new Response(options.body, {
|
||||||
|
headers: options.headers,
|
||||||
|
status: options.status
|
||||||
|
})
|
||||||
)
|
)
|
||||||
]),
|
}
|
||||||
this.publicKey
|
})
|
||||||
).catch(() => false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async verifyOpineRequest(req: any): Promise<boolean> {
|
async verifyOpineRequest(req: any): Promise<boolean> {
|
||||||
|
@ -576,3 +693,59 @@ export class SlashClient {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Decorator to create a Slash Command handler */
|
||||||
|
export function slash(name?: string, guild?: string) {
|
||||||
|
return function (client: Client | SlashClient | SlashModule, prop: string) {
|
||||||
|
if (client._decoratedSlash === undefined) client._decoratedSlash = []
|
||||||
|
const item = (client as { [name: string]: any })[prop]
|
||||||
|
if (typeof item !== 'function') {
|
||||||
|
throw new Error('@slash decorator requires a function')
|
||||||
|
} else
|
||||||
|
client._decoratedSlash.push({
|
||||||
|
name: name ?? prop,
|
||||||
|
guild,
|
||||||
|
handler: item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Decorator to create a Sub-Slash Command handler */
|
||||||
|
export function subslash(parent: string, name?: string, guild?: string) {
|
||||||
|
return function (client: Client | SlashModule | SlashClient, prop: string) {
|
||||||
|
if (client._decoratedSlash === undefined) client._decoratedSlash = []
|
||||||
|
const item = (client as { [name: string]: any })[prop]
|
||||||
|
if (typeof item !== 'function') {
|
||||||
|
throw new Error('@subslash decorator requires a function')
|
||||||
|
} else
|
||||||
|
client._decoratedSlash.push({
|
||||||
|
parent,
|
||||||
|
name: name ?? prop,
|
||||||
|
guild,
|
||||||
|
handler: item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Decorator to create a Grouped Slash Command handler */
|
||||||
|
export function groupslash(
|
||||||
|
parent: string,
|
||||||
|
group: string,
|
||||||
|
name?: string,
|
||||||
|
guild?: string
|
||||||
|
) {
|
||||||
|
return function (client: Client | SlashModule | SlashClient, prop: string) {
|
||||||
|
if (client._decoratedSlash === undefined) client._decoratedSlash = []
|
||||||
|
const item = (client as { [name: string]: any })[prop]
|
||||||
|
if (typeof item !== 'function') {
|
||||||
|
throw new Error('@groupslash decorator requires a function')
|
||||||
|
} else
|
||||||
|
client._decoratedSlash.push({
|
||||||
|
group,
|
||||||
|
parent,
|
||||||
|
name: name ?? prop,
|
||||||
|
guild,
|
||||||
|
handler: item
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,10 +2,10 @@ import { Client } from '../models/client.ts'
|
||||||
import { Snowflake } from '../utils/snowflake.ts'
|
import { Snowflake } from '../utils/snowflake.ts'
|
||||||
|
|
||||||
export class Base {
|
export class Base {
|
||||||
client: Client
|
client!: Client
|
||||||
|
|
||||||
constructor(client: Client, _data?: any) {
|
constructor(client: Client, _data?: any) {
|
||||||
this.client = client
|
Object.defineProperty(this, 'client', { value: client, enumerable: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import {
|
||||||
Attachment,
|
Attachment,
|
||||||
MessageActivity,
|
MessageActivity,
|
||||||
MessageApplication,
|
MessageApplication,
|
||||||
|
MessageInteractionPayload,
|
||||||
MessageOptions,
|
MessageOptions,
|
||||||
MessagePayload,
|
MessagePayload,
|
||||||
MessageReference
|
MessageReference
|
||||||
|
@ -19,9 +20,26 @@ import { Guild } from './guild.ts'
|
||||||
import { MessageReactionsManager } from '../managers/messageReactions.ts'
|
import { MessageReactionsManager } from '../managers/messageReactions.ts'
|
||||||
import { MessageSticker } from './messageSticker.ts'
|
import { MessageSticker } from './messageSticker.ts'
|
||||||
import { Emoji } from './emoji.ts'
|
import { Emoji } from './emoji.ts'
|
||||||
|
import { InteractionType } from '../types/slash.ts'
|
||||||
|
import { encodeText } from '../utils/encoding.ts'
|
||||||
|
|
||||||
type AllMessageOptions = MessageOptions | Embed
|
type AllMessageOptions = MessageOptions | Embed
|
||||||
|
|
||||||
|
export class MessageInteraction extends SnowflakeBase {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
type: InteractionType
|
||||||
|
user: User
|
||||||
|
|
||||||
|
constructor(client: Client, data: MessageInteractionPayload) {
|
||||||
|
super(client)
|
||||||
|
this.id = data.id
|
||||||
|
this.name = data.name
|
||||||
|
this.type = data.type
|
||||||
|
this.user = new User(this.client, data.user)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Message extends SnowflakeBase {
|
export class Message extends SnowflakeBase {
|
||||||
id: string
|
id: string
|
||||||
channelID: string
|
channelID: string
|
||||||
|
@ -46,6 +64,7 @@ export class Message extends SnowflakeBase {
|
||||||
messageReference?: MessageReference
|
messageReference?: MessageReference
|
||||||
flags?: number
|
flags?: number
|
||||||
stickers?: MessageSticker[]
|
stickers?: MessageSticker[]
|
||||||
|
interaction?: MessageInteraction
|
||||||
|
|
||||||
get createdAt(): Date {
|
get createdAt(): Date {
|
||||||
return new Date(this.timestamp)
|
return new Date(this.timestamp)
|
||||||
|
@ -87,6 +106,10 @@ export class Message extends SnowflakeBase {
|
||||||
(payload) => new MessageSticker(this.client, payload)
|
(payload) => new MessageSticker(this.client, payload)
|
||||||
)
|
)
|
||||||
: undefined
|
: undefined
|
||||||
|
this.interaction =
|
||||||
|
data.interaction === undefined
|
||||||
|
? undefined
|
||||||
|
: new MessageInteraction(this.client, data.interaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
readFromData(data: MessagePayload): void {
|
readFromData(data: MessagePayload): void {
|
||||||
|
@ -195,8 +218,6 @@ export class Message extends SnowflakeBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const encoder = new TextEncoder()
|
|
||||||
|
|
||||||
/** Message Attachment that can be sent while Creating Message */
|
/** Message Attachment that can be sent while Creating Message */
|
||||||
export class MessageAttachment {
|
export class MessageAttachment {
|
||||||
name: string
|
name: string
|
||||||
|
@ -206,7 +227,7 @@ export class MessageAttachment {
|
||||||
this.name = name
|
this.name = name
|
||||||
this.blob =
|
this.blob =
|
||||||
typeof blob === 'string'
|
typeof blob === 'string'
|
||||||
? new Blob([encoder.encode(blob)])
|
? new Blob([encodeText(blob)])
|
||||||
: blob instanceof Uint8Array
|
: blob instanceof Uint8Array
|
||||||
? new Blob([blob])
|
? new Blob([blob])
|
||||||
: blob
|
: blob
|
||||||
|
|
|
@ -1,95 +1,178 @@
|
||||||
import { Client } from '../models/client.ts'
|
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 { INTERACTION_CALLBACK, WEBHOOK_MESSAGE } from '../types/endpoint.ts'
|
||||||
import {
|
import {
|
||||||
InteractionData,
|
InteractionApplicationCommandData,
|
||||||
InteractionOption,
|
InteractionApplicationCommandOption,
|
||||||
|
InteractionChannelPayload,
|
||||||
InteractionPayload,
|
InteractionPayload,
|
||||||
|
InteractionResponseFlags,
|
||||||
InteractionResponsePayload,
|
InteractionResponsePayload,
|
||||||
InteractionResponseType
|
InteractionResponseType,
|
||||||
|
InteractionType,
|
||||||
|
SlashCommandOptionType
|
||||||
} from '../types/slash.ts'
|
} from '../types/slash.ts'
|
||||||
|
import { Dict } from '../utils/dict.ts'
|
||||||
|
import { Permissions } from '../utils/permissions.ts'
|
||||||
import { SnowflakeBase } from './base.ts'
|
import { SnowflakeBase } from './base.ts'
|
||||||
|
import { Channel } from './channel.ts'
|
||||||
import { Embed } from './embed.ts'
|
import { Embed } from './embed.ts'
|
||||||
import { Guild } from './guild.ts'
|
import { Guild } from './guild.ts'
|
||||||
|
import { GuildTextChannel } from './guildTextChannel.ts'
|
||||||
import { Member } from './member.ts'
|
import { Member } from './member.ts'
|
||||||
import { Message } from './message.ts'
|
import { Message } from './message.ts'
|
||||||
|
import { Role } from './role.ts'
|
||||||
import { TextChannel } from './textChannel.ts'
|
import { TextChannel } from './textChannel.ts'
|
||||||
import { GuildTextBasedChannel } from './guildTextChannel.ts'
|
|
||||||
import { User } from './user.ts'
|
import { User } from './user.ts'
|
||||||
import { Webhook } from './webhook.ts'
|
|
||||||
|
|
||||||
interface WebhookMessageOptions extends MessageOptions {
|
interface WebhookMessageOptions extends MessageOptions {
|
||||||
embeds?: Embed[]
|
embeds?: Array<Embed | EmbedPayload>
|
||||||
name?: string
|
name?: string
|
||||||
avatar?: string
|
avatar?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
type AllWebhookMessageOptions = string | WebhookMessageOptions
|
type AllWebhookMessageOptions = string | WebhookMessageOptions
|
||||||
|
|
||||||
export interface InteractionResponse {
|
/** Interaction Message related Options */
|
||||||
type?: InteractionResponseType
|
export interface InteractionMessageOptions {
|
||||||
content?: string
|
content?: string
|
||||||
embeds?: Embed[]
|
embeds?: Array<Embed | EmbedPayload>
|
||||||
tts?: boolean
|
tts?: boolean
|
||||||
flags?: number
|
flags?: number | InteractionResponseFlags[]
|
||||||
temp?: boolean
|
allowedMentions?: AllowedMentionsPayload
|
||||||
allowedMentions?: {
|
/** Whether the Message Response should be Ephemeral (only visible to User) or not */
|
||||||
parse?: string
|
ephemeral?: boolean
|
||||||
roles?: string[]
|
}
|
||||||
users?: string[]
|
|
||||||
everyone?: boolean
|
export interface InteractionResponse extends InteractionMessageOptions {
|
||||||
|
/** Type of Interaction Response */
|
||||||
|
type?: InteractionResponseType
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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.id = data.id
|
||||||
|
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 {
|
export class Interaction extends SnowflakeBase {
|
||||||
client: Client
|
/** Type of Interaction */
|
||||||
type: number
|
type: InteractionType
|
||||||
|
/** Interaction Token */
|
||||||
token: string
|
token: string
|
||||||
|
/** Interaction ID */
|
||||||
id: string
|
id: string
|
||||||
data: InteractionData
|
/** Data sent with Interaction. Only applies to Application Command */
|
||||||
channel: GuildTextBasedChannel
|
data?: InteractionApplicationCommandData
|
||||||
guild: Guild
|
/** Channel in which Interaction was initiated */
|
||||||
member: Member
|
channel?: TextChannel | GuildTextChannel
|
||||||
_savedHook?: Webhook
|
/** 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
|
||||||
|
_httpRespond?: (d: InteractionResponsePayload) => unknown
|
||||||
|
_httpResponded?: boolean
|
||||||
|
applicationID: string
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
client: Client,
|
client: Client,
|
||||||
data: InteractionPayload,
|
data: InteractionPayload,
|
||||||
others: {
|
others: {
|
||||||
channel: GuildTextBasedChannel
|
channel?: TextChannel | GuildTextChannel
|
||||||
guild: Guild
|
guild?: Guild
|
||||||
member: Member
|
member?: Member
|
||||||
|
user: User
|
||||||
|
resolved: InteractionApplicationCommandResolved
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
super(client)
|
super(client)
|
||||||
this.client = client
|
|
||||||
this.type = data.type
|
this.type = data.type
|
||||||
this.token = data.token
|
this.token = data.token
|
||||||
this.member = others.member
|
this.member = others.member
|
||||||
this.id = data.id
|
this.id = data.id
|
||||||
|
this.applicationID = data.application_id
|
||||||
|
this.user = others.user
|
||||||
this.data = data.data
|
this.data = data.data
|
||||||
this.guild = others.guild
|
this.guild = others.guild
|
||||||
this.channel = others.channel
|
this.channel = others.channel
|
||||||
|
this.resolved = others.resolved
|
||||||
}
|
}
|
||||||
|
|
||||||
get user(): User {
|
/** Name of the Command Used (may change with future additions to Interactions!) */
|
||||||
return this.member.user
|
get name(): string | undefined {
|
||||||
|
return this.data?.name
|
||||||
}
|
}
|
||||||
|
|
||||||
get name(): string {
|
get options(): InteractionApplicationCommandOption[] {
|
||||||
return this.data.name
|
return this.data?.options ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
get options(): InteractionOption[] {
|
/** Get an option by name */
|
||||||
return this.data.options ?? []
|
option<T>(name: string): T {
|
||||||
}
|
const op = this.options.find((e) => e.name === name)
|
||||||
|
if (op === undefined || op.value === undefined) return undefined as any
|
||||||
option<T = any>(name: string): T {
|
if (op.type === SlashCommandOptionType.USER) {
|
||||||
return this.options.find((e) => e.name === name)?.value
|
const u: InteractionUser = this.resolved.users[op.value] as any
|
||||||
|
if (this.resolved.members[op.value] !== undefined)
|
||||||
|
u.member = this.resolved.members[op.value]
|
||||||
|
return u 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 */
|
/** Respond to an Interaction */
|
||||||
async respond(data: InteractionResponse): Promise<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 = {
|
const payload: InteractionResponsePayload = {
|
||||||
type: data.type ?? InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
type: data.type ?? InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||||
data:
|
data:
|
||||||
|
@ -100,16 +183,70 @@ export class Interaction extends SnowflakeBase {
|
||||||
content: data.content ?? '',
|
content: data.content ?? '',
|
||||||
embeds: data.embeds,
|
embeds: data.embeds,
|
||||||
tts: data.tts ?? false,
|
tts: data.tts ?? false,
|
||||||
flags: data.temp === true ? 64 : data.flags ?? undefined,
|
flags,
|
||||||
allowed_mentions: (data.allowedMentions ?? undefined) as any
|
allowed_mentions: data.allowedMentions ?? undefined
|
||||||
}
|
}
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.client.rest.post(
|
if (this._httpRespond !== undefined && this._httpResponded !== true) {
|
||||||
INTERACTION_CALLBACK(this.id, this.token),
|
this._httpResponded = true
|
||||||
payload
|
await this._httpRespond(payload)
|
||||||
|
} else
|
||||||
|
await this.client.rest.post(
|
||||||
|
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(ephemeral = false): Promise<Interaction> {
|
||||||
|
await this.respond({
|
||||||
|
type: InteractionResponseType.DEFERRED_CHANNEL_MESSAGE,
|
||||||
|
flags: ephemeral ? 1 << 6 : 0
|
||||||
|
})
|
||||||
|
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
|
return this
|
||||||
}
|
}
|
||||||
|
@ -117,33 +254,32 @@ export class Interaction extends SnowflakeBase {
|
||||||
/** Edit the original Interaction response */
|
/** Edit the original Interaction response */
|
||||||
async editResponse(data: {
|
async editResponse(data: {
|
||||||
content?: string
|
content?: string
|
||||||
embeds?: Embed[]
|
embeds?: Array<Embed | EmbedPayload>
|
||||||
|
flags?: number | number[]
|
||||||
|
allowedMentions?: AllowedMentionsPayload
|
||||||
}): Promise<Interaction> {
|
}): Promise<Interaction> {
|
||||||
const url = WEBHOOK_MESSAGE(
|
const url = WEBHOOK_MESSAGE(this.applicationID, this.token, '@original')
|
||||||
this.client.user?.id as string,
|
|
||||||
this.token,
|
|
||||||
'@original'
|
|
||||||
)
|
|
||||||
await this.client.rest.patch(url, {
|
await this.client.rest.patch(url, {
|
||||||
content: data.content ?? '',
|
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
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Delete the original Interaction Response */
|
/** Delete the original Interaction Response */
|
||||||
async deleteResponse(): Promise<Interaction> {
|
async deleteResponse(): Promise<Interaction> {
|
||||||
const url = WEBHOOK_MESSAGE(
|
const url = WEBHOOK_MESSAGE(this.applicationID, this.token, '@original')
|
||||||
this.client.user?.id as string,
|
|
||||||
this.token,
|
|
||||||
'@original'
|
|
||||||
)
|
|
||||||
await this.client.rest.delete(url)
|
await this.client.rest.delete(url)
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
get url(): string {
|
get url(): string {
|
||||||
return `https://discord.com/api/v8/webhooks/${this.client.user?.id}/${this.token}`
|
return `https://discord.com/api/v8/webhooks/${this.applicationID}/${this.token}`
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Send a followup message */
|
/** Send a followup message */
|
||||||
|
@ -174,6 +310,7 @@ export class Interaction extends SnowflakeBase {
|
||||||
? (option as WebhookMessageOptions).embeds
|
? (option as WebhookMessageOptions).embeds
|
||||||
: undefined,
|
: undefined,
|
||||||
file: (option as WebhookMessageOptions)?.file,
|
file: (option as WebhookMessageOptions)?.file,
|
||||||
|
files: (option as WebhookMessageOptions)?.files,
|
||||||
tts: (option as WebhookMessageOptions)?.tts,
|
tts: (option as WebhookMessageOptions)?.tts,
|
||||||
allowed_mentions: (option as WebhookMessageOptions)?.allowedMentions
|
allowed_mentions: (option as WebhookMessageOptions)?.allowedMentions
|
||||||
}
|
}
|
||||||
|
@ -212,7 +349,7 @@ export class Interaction extends SnowflakeBase {
|
||||||
msg: Message | string,
|
msg: Message | string,
|
||||||
data: {
|
data: {
|
||||||
content?: string
|
content?: string
|
||||||
embeds?: Embed[]
|
embeds?: Array<Embed | EmbedPayload>
|
||||||
file?: any
|
file?: any
|
||||||
allowed_mentions?: {
|
allowed_mentions?: {
|
||||||
parse?: string
|
parse?: string
|
||||||
|
@ -224,7 +361,7 @@ export class Interaction extends SnowflakeBase {
|
||||||
): Promise<Interaction> {
|
): Promise<Interaction> {
|
||||||
await this.client.rest.patch(
|
await this.client.rest.patch(
|
||||||
WEBHOOK_MESSAGE(
|
WEBHOOK_MESSAGE(
|
||||||
this.client.user?.id as string,
|
this.applicationID,
|
||||||
this.token ?? this.client.token,
|
this.token ?? this.client.token,
|
||||||
typeof msg === 'string' ? msg : msg.id
|
typeof msg === 'string' ? msg : msg.id
|
||||||
),
|
),
|
||||||
|
@ -233,10 +370,11 @@ export class Interaction extends SnowflakeBase {
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Delete a follow-up Message */
|
||||||
async deleteMessage(msg: Message | string): Promise<Interaction> {
|
async deleteMessage(msg: Message | string): Promise<Interaction> {
|
||||||
await this.client.rest.delete(
|
await this.client.rest.delete(
|
||||||
WEBHOOK_MESSAGE(
|
WEBHOOK_MESSAGE(
|
||||||
this.client.user?.id as string,
|
this.applicationID,
|
||||||
this.token ?? this.client.token,
|
this.token ?? this.client.token,
|
||||||
typeof msg === 'string' ? msg : msg.id
|
typeof msg === 'string' ? msg : msg.id
|
||||||
)
|
)
|
||||||
|
|
28
src/test/debug.ts
Normal file
28
src/test/debug.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { Client, event } from '../../mod.ts'
|
||||||
|
import { TOKEN } from './config.ts'
|
||||||
|
|
||||||
|
class MyClient extends Client {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
token: TOKEN,
|
||||||
|
intents: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@event()
|
||||||
|
ready(): void {
|
||||||
|
console.log('Connected!')
|
||||||
|
}
|
||||||
|
|
||||||
|
debug(title: string, msg: string): void {
|
||||||
|
console.log(`[${title}] ${msg}`)
|
||||||
|
if (title === 'Gateway' && msg === 'Initializing WebSocket...') {
|
||||||
|
try { throw new Error("Stack") } catch (e) {
|
||||||
|
console.log(e.stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new MyClient()
|
||||||
|
client.connect()
|
|
@ -119,16 +119,7 @@ client.on('messageCreate', async (msg: Message) => {
|
||||||
msg.channel.send('Failed...')
|
msg.channel.send('Failed...')
|
||||||
}
|
}
|
||||||
} else if (msg.content === '!react') {
|
} else if (msg.content === '!react') {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
msg.addReaction('a:programming:785013658257195008')
|
||||||
msg.addReaction('😂')
|
|
||||||
msg.channel.send('x'.repeat(6969), {
|
|
||||||
embed: new Embed()
|
|
||||||
.setTitle('pepega'.repeat(6969))
|
|
||||||
.setDescription('pepega'.repeat(6969))
|
|
||||||
.addField('uwu', 'uwu'.repeat(6969))
|
|
||||||
.addField('uwu', 'uwu'.repeat(6969))
|
|
||||||
.setFooter('uwu'.repeat(6969))
|
|
||||||
})
|
|
||||||
} else if (msg.content === '!wait_for') {
|
} else if (msg.content === '!wait_for') {
|
||||||
msg.channel.send('Send anything!')
|
msg.channel.send('Send anything!')
|
||||||
const [receivedMsg] = await client.waitFor(
|
const [receivedMsg] = await client.waitFor(
|
||||||
|
@ -211,13 +202,12 @@ client.on('messageCreate', async (msg: Message) => {
|
||||||
)
|
)
|
||||||
.join('\n\n')}`
|
.join('\n\n')}`
|
||||||
)
|
)
|
||||||
} else if (msg.content === '!getPermissions') {
|
} else if (msg.content === '!perms') {
|
||||||
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
if (msg.channel.type !== ChannelTypes.GUILD_TEXT) {
|
||||||
if (!checkGuildTextBasedChannel(msg.channel)) {
|
|
||||||
return msg.channel.send("This isn't a guild text channel!")
|
return msg.channel.send("This isn't a guild text channel!")
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||||
const permissions = await (msg.channel as GuildTextChannel).permissionsFor(
|
const permissions = await ((msg.channel as unknown) as GuildTextChannel).permissionsFor(
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
|
||||||
msg.member as Member
|
msg.member as Member
|
||||||
)
|
)
|
||||||
|
|
27
src/test/slash-http.ts
Normal file
27
src/test/slash-http.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { SlashClient } from '../../mod.ts'
|
||||||
|
import { SLASH_ID, SLASH_PUB_KEY, SLASH_TOKEN } from './config.ts'
|
||||||
|
import { listenAndServe } from 'https://deno.land/std@0.90.0/http/server.ts'
|
||||||
|
|
||||||
|
const slash = new SlashClient({
|
||||||
|
id: SLASH_ID,
|
||||||
|
token: SLASH_TOKEN,
|
||||||
|
publicKey: SLASH_PUB_KEY
|
||||||
|
})
|
||||||
|
|
||||||
|
await slash.commands.bulkEdit([
|
||||||
|
{
|
||||||
|
name: 'ping',
|
||||||
|
description: 'Just ping!'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
const options = { port: 8000 }
|
||||||
|
console.log('Listen on port: ' + options.port.toString())
|
||||||
|
listenAndServe(options, async (req) => {
|
||||||
|
const d = await slash.verifyServerRequest(req)
|
||||||
|
if (d === false) return req.respond({ status: 401, body: 'not authorized' })
|
||||||
|
|
||||||
|
console.log(d)
|
||||||
|
if (d.type === 1) return d.respond({ type: 1 })
|
||||||
|
d.reply('Pong!')
|
||||||
|
})
|
|
@ -1,100 +1,56 @@
|
||||||
import { Client, Intents, event, slash } from '../../mod.ts'
|
import {
|
||||||
import { Embed } from '../structures/embed.ts'
|
Client,
|
||||||
|
Intents,
|
||||||
|
event,
|
||||||
|
slash,
|
||||||
|
SlashCommandOptionType as Type
|
||||||
|
} from '../../mod.ts'
|
||||||
import { Interaction } from '../structures/slash.ts'
|
import { Interaction } from '../structures/slash.ts'
|
||||||
import { TOKEN } from './config.ts'
|
import { TOKEN } from './config.ts'
|
||||||
|
|
||||||
export class MyClient extends Client {
|
export class MyClient extends Client {
|
||||||
@event()
|
@event() ready(): void {
|
||||||
ready(): void {
|
|
||||||
console.log(`Logged in as ${this.user?.tag}!`)
|
console.log(`Logged in as ${this.user?.tag}!`)
|
||||||
this.slash.commands.bulkEdit([{ name: 'send', description: 'idk' }])
|
this.slash.commands.bulkEdit(
|
||||||
}
|
[
|
||||||
|
{
|
||||||
@event('debug')
|
name: 'test',
|
||||||
debugEvt(txt: string): void {
|
description: 'Test command.',
|
||||||
console.log(txt)
|
options: [
|
||||||
}
|
{
|
||||||
|
name: 'user',
|
||||||
@slash()
|
type: Type.USER,
|
||||||
send(d: Interaction): void {
|
description: 'User'
|
||||||
d.respond({
|
},
|
||||||
content: d.data.options?.find((e) => e.name === 'content')?.value
|
{
|
||||||
})
|
name: 'role',
|
||||||
}
|
type: Type.ROLE,
|
||||||
|
description: 'Role'
|
||||||
@slash()
|
},
|
||||||
async eval(d: Interaction): Promise<void> {
|
{
|
||||||
if (
|
name: 'channel',
|
||||||
d.user.id !== '422957901716652033' &&
|
type: Type.CHANNEL,
|
||||||
d.user.id !== '682849186227552266'
|
description: 'Channel'
|
||||||
) {
|
},
|
||||||
d.respond({
|
{
|
||||||
content: 'This command can only be used by owner!'
|
name: 'string',
|
||||||
})
|
type: Type.STRING,
|
||||||
} else {
|
description: 'String'
|
||||||
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```'
|
'807935370556866560'
|
||||||
}).catch(() => {})
|
)
|
||||||
} catch (e) {
|
this.slash.commands.bulkEdit([])
|
||||||
d.respond({
|
|
||||||
content: '```js\n' + `${e.stack}` + '\n```'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@slash()
|
@slash() test(d: Interaction): void {
|
||||||
async hug(d: Interaction): Promise<void> {
|
console.log(d.resolved)
|
||||||
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()
|
@event() raw(evt: string, d: any): void {
|
||||||
async kiss(d: Interaction): Promise<void> {
|
if (evt === 'INTERACTION_CREATE') console.log(evt, d?.data?.resolved)
|
||||||
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!`
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { Role } from '../structures/role.ts'
|
||||||
import { Permissions } from '../utils/permissions.ts'
|
import { Permissions } from '../utils/permissions.ts'
|
||||||
import { EmojiPayload } from './emoji.ts'
|
import { EmojiPayload } from './emoji.ts'
|
||||||
import { MemberPayload } from './guild.ts'
|
import { MemberPayload } from './guild.ts'
|
||||||
|
import { InteractionType } from './slash.ts'
|
||||||
import { UserPayload } from './user.ts'
|
import { UserPayload } from './user.ts'
|
||||||
|
|
||||||
export interface ChannelPayload {
|
export interface ChannelPayload {
|
||||||
|
@ -185,6 +186,7 @@ export interface MessagePayload {
|
||||||
message_reference?: MessageReference
|
message_reference?: MessageReference
|
||||||
flags?: number
|
flags?: number
|
||||||
stickers?: MessageStickerPayload[]
|
stickers?: MessageStickerPayload[]
|
||||||
|
interaction?: MessageInteractionPayload
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum AllowedMentionType {
|
export enum AllowedMentionType {
|
||||||
|
@ -373,3 +375,10 @@ export interface MessageStickerPayload {
|
||||||
preview_asset: string | null
|
preview_asset: string | null
|
||||||
format_type: MessageStickerFormatTypes
|
format_type: MessageStickerFormatTypes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MessageInteractionPayload {
|
||||||
|
id: string
|
||||||
|
type: InteractionType
|
||||||
|
name: string
|
||||||
|
user: UserPayload
|
||||||
|
}
|
||||||
|
|
|
@ -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 { MemberPayload } from './guild.ts'
|
||||||
|
import { RolePayload } from './role.ts'
|
||||||
|
import { UserPayload } from './user.ts'
|
||||||
|
|
||||||
export interface InteractionOption {
|
export interface InteractionApplicationCommandOption {
|
||||||
/** Option name */
|
/** Option name */
|
||||||
name: string
|
name: string
|
||||||
|
/** Type of Option */
|
||||||
|
type: SlashCommandOptionType
|
||||||
/** Value of the option */
|
/** Value of the option */
|
||||||
value?: any
|
value?: any
|
||||||
/** Sub options */
|
/** 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 of the Slash Command */
|
||||||
name: string
|
name: string
|
||||||
/** Unique ID of the Slash Command */
|
/** Unique ID of the Slash Command */
|
||||||
id: string
|
id: string
|
||||||
/** Options (arguments) sent with Interaction */
|
/** Options (arguments) sent with Interaction */
|
||||||
options: InteractionOption[]
|
options: InteractionApplicationCommandOption[]
|
||||||
|
/** Resolved data for options in Slash Command */
|
||||||
|
resolved?: InteractionApplicationCommandResolvedPayload
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum InteractionType {
|
export enum InteractionType {
|
||||||
|
@ -26,27 +51,31 @@ export enum InteractionType {
|
||||||
APPLICATION_COMMAND = 2
|
APPLICATION_COMMAND = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface InteractionMemberPayload extends MemberPayload {
|
||||||
|
/** Permissions of the Member who initiated Interaction (Guild-only) */
|
||||||
|
permissions: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface InteractionPayload {
|
export interface InteractionPayload {
|
||||||
/** Type of the Interaction */
|
/** Type of the Interaction */
|
||||||
type: InteractionType
|
type: InteractionType
|
||||||
/** Token of the Interaction to respond */
|
/** Token of the Interaction to respond */
|
||||||
token: string
|
token: string
|
||||||
/** Member object of user who invoked */
|
/** Member object of user who invoked */
|
||||||
member: MemberPayload & {
|
member?: InteractionMemberPayload
|
||||||
/** Total permissions of the member in the channel, including overrides */
|
/** User who initiated Interaction (only in DMs) */
|
||||||
permissions: string
|
user?: UserPayload
|
||||||
}
|
|
||||||
/** ID of the Interaction */
|
/** ID of the Interaction */
|
||||||
id: string
|
id: string
|
||||||
/**
|
/**
|
||||||
* Data sent with the interaction
|
* Data sent with the interaction. Undefined only when Interaction is not Slash Command.*
|
||||||
* **This can be undefined only when Interaction is not a Slash Command**
|
|
||||||
*/
|
*/
|
||||||
data: InteractionData
|
data?: InteractionApplicationCommandData
|
||||||
/** ID of the Guild in which Interaction was invoked */
|
/** ID of the Guild in which Interaction was invoked */
|
||||||
guild_id: string
|
guild_id?: string
|
||||||
/** ID of the Channel in which Interaction was invoked */
|
/** ID of the Channel in which Interaction was invoked */
|
||||||
channel_id: string
|
channel_id?: string
|
||||||
|
application_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SlashCommandChoice {
|
export interface SlashCommandChoice {
|
||||||
|
@ -57,7 +86,9 @@ export interface SlashCommandChoice {
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SlashCommandOptionType {
|
export enum SlashCommandOptionType {
|
||||||
|
/** A sub command that is either a part of a root command or Sub Command Group */
|
||||||
SUB_COMMAND = 1,
|
SUB_COMMAND = 1,
|
||||||
|
/** A sub command group that is present in root command's options */
|
||||||
SUB_COMMAND_GROUP = 2,
|
SUB_COMMAND_GROUP = 2,
|
||||||
STRING = 3,
|
STRING = 3,
|
||||||
INTEGER = 4,
|
INTEGER = 4,
|
||||||
|
@ -68,58 +99,71 @@ export enum SlashCommandOptionType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SlashCommandOption {
|
export interface SlashCommandOption {
|
||||||
|
/** Name of the option. */
|
||||||
name: string
|
name: string
|
||||||
/** Description not required in Sub-Command or Sub-Command-Group */
|
/** Description of the Option. Not required in Sub-Command-Group */
|
||||||
description?: string
|
description?: string
|
||||||
|
/** Option type */
|
||||||
type: SlashCommandOptionType
|
type: SlashCommandOptionType
|
||||||
|
/** Whether the option is required or not, false by default */
|
||||||
required?: boolean
|
required?: boolean
|
||||||
default?: boolean
|
default?: boolean
|
||||||
|
/** Optional choices out of which User can choose value */
|
||||||
choices?: SlashCommandChoice[]
|
choices?: SlashCommandChoice[]
|
||||||
|
/** Nested options for Sub-Command or Sub-Command-Groups */
|
||||||
options?: SlashCommandOption[]
|
options?: SlashCommandOption[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Represents the Slash Command (Application Command) payload sent for creating/bulk editing. */
|
||||||
export interface SlashCommandPartial {
|
export interface SlashCommandPartial {
|
||||||
|
/** Name of the Slash Command */
|
||||||
name: string
|
name: string
|
||||||
|
/** Description of the Slash Command */
|
||||||
description: string
|
description: string
|
||||||
|
/** Options (arguments, sub commands or group) of the Slash Command */
|
||||||
options?: SlashCommandOption[]
|
options?: SlashCommandOption[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Represents a fully qualified Slash Command (Application Command) payload. */
|
||||||
export interface SlashCommandPayload extends SlashCommandPartial {
|
export interface SlashCommandPayload extends SlashCommandPartial {
|
||||||
|
/** ID of the Slash Command */
|
||||||
id: string
|
id: string
|
||||||
|
/** Application ID */
|
||||||
application_id: string
|
application_id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum InteractionResponseType {
|
export enum InteractionResponseType {
|
||||||
/** Just ack a ping, Http-only. */
|
/** Just ack a ping, Http-only. */
|
||||||
PONG = 1,
|
PONG = 1,
|
||||||
/** Do nothing, just acknowledge the Interaction */
|
/** @deprecated **DEPRECATED:** Do nothing, just acknowledge the Interaction */
|
||||||
ACKNOWLEDGE = 2,
|
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,
|
CHANNEL_MESSAGE = 3,
|
||||||
/** Send a channel message with "<User> used /<Command> with <Bot>" */
|
/** Send a channel message as response. */
|
||||||
CHANNEL_MESSAGE_WITH_SOURCE = 4,
|
CHANNEL_MESSAGE_WITH_SOURCE = 4,
|
||||||
/** Send nothing further, but send "<User> used /<Command> with <Bot>" */
|
/** Let the user know bot is processing ("thinking") and you can edit the response later */
|
||||||
ACK_WITH_SOURCE = 5
|
DEFERRED_CHANNEL_MESSAGE = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InteractionResponsePayload {
|
export interface InteractionResponsePayload {
|
||||||
|
/** Type of the response */
|
||||||
type: InteractionResponseType
|
type: InteractionResponseType
|
||||||
|
/** Data to be sent with response. Optional for types: Pong, Acknowledge, Ack with Source */
|
||||||
data?: InteractionResponseDataPayload
|
data?: InteractionResponseDataPayload
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InteractionResponseDataPayload {
|
export interface InteractionResponseDataPayload {
|
||||||
tts?: boolean
|
tts?: boolean
|
||||||
|
/** Text content of the Response (Message) */
|
||||||
content: string
|
content: string
|
||||||
|
/** Upto 10 Embed Objects to send with Response */
|
||||||
embeds?: EmbedPayload[]
|
embeds?: EmbedPayload[]
|
||||||
allowed_mentions?: {
|
/** Allowed Mentions object */
|
||||||
parse?: 'everyone' | 'users' | 'roles'
|
allowed_mentions?: AllowedMentionsPayload
|
||||||
roles?: string[]
|
|
||||||
users?: string[]
|
|
||||||
}
|
|
||||||
flags?: number
|
flags?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum InteractionResponseFlags {
|
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
|
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
|
||||||
|
}
|
10
src/utils/encoding.ts
Normal file
10
src/utils/encoding.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
const encoder = new TextEncoder()
|
||||||
|
const decoder = new TextDecoder('utf-8')
|
||||||
|
|
||||||
|
export function encodeText(str: string): Uint8Array {
|
||||||
|
return encoder.encode(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeText(bytes: Uint8Array): string {
|
||||||
|
return decoder.decode(bytes)
|
||||||
|
}
|
Loading…
Reference in a new issue