starting to add slash commands

This commit is contained in:
Jaron Pate
2022-09-20 19:32:41 -05:00
parent a346d56f9b
commit 230aee97fa
10 changed files with 276 additions and 47 deletions
+2 -2
View File
@@ -10,8 +10,8 @@
"/dist" "/dist"
], ],
"scripts": { "scripts": {
"start": "npx ts-node-dev src/index.ts", "start": "npx ts-node test/main.test.ts",
"start:watch": "npx ts-node-dev --respawn src/index.ts", "start:watch": "npx ts-node-dev --respawn test/main.test.ts",
"build": "npx tsc" "build": "npx tsc"
}, },
"devDependencies": { "devDependencies": {
+27 -2
View File
@@ -1,6 +1,6 @@
import { ComponentStyle, Context, Gateway, Guilds, Message } from './lib'; import { ComponentStyle, Context, Gateway, Guilds, Message, Embed } from './lib';
import { Command, CommandFuntion, Intent, Presence } from './types'; import { Command, CommandFuntion, Intent, Presence } from './types';
export { ComponentStyle, Context, Gateway, Guilds, Message, Command, CommandFuntion, Intent, Presence }; export { ComponentStyle, Context, Gateway, Guilds, Message, Embed, Command, CommandFuntion, Intent, Presence };
type Config = { type Config = {
token: string; token: string;
@@ -15,6 +15,7 @@ export class Client {
readonly application_id: string; readonly application_id: string;
private readonly gateway: Gateway; private readonly gateway: Gateway;
private readonly client_commands: Map<string, Command | CommandFuntion> = new Map(); private readonly client_commands: Map<string, Command | CommandFuntion> = new Map();
private readonly slash_commands: Map<string, Command | CommandFuntion> = new Map();
readonly guilds: Guilds; readonly guilds: Guilds;
constructor({ token, application_id, prefix, intents }: Config) { constructor({ token, application_id, prefix, intents }: Config) {
@@ -23,6 +24,8 @@ export class Client {
this.prefix = prefix ?? '!'; this.prefix = prefix ?? '!';
this.gateway = new Gateway({ token, intents, client: this }); this.gateway = new Gateway({ token, intents, client: this });
this.guilds = new Guilds({ token, gateway: this.gateway }); this.guilds = new Guilds({ token, gateway: this.gateway });
// Do some default stuff
this.initialize();
} }
get user() { get user() {
@@ -84,6 +87,28 @@ export class Client {
}, },
delete: (command_name: string) => { delete: (command_name: string) => {
this.client_commands.delete(command_name); this.client_commands.delete(command_name);
},
list: () => {
return this.client_commands;
} }
}; };
private initialize = () => {
// Register default help command
this.commands.register('help', (client, context, _args) => {
const embed = new Embed().setTitle('Commands').setDescription('This is a list of commands you can use.');
const commands = Array.from(client.commands.list().entries());
for (const [name, command] of commands) {
embed.addField({
name: name,
value: command instanceof Command ? command.description : 'No description provided'
});
}
context.reply(embed.toMessage());
});
}
} }
+136
View File
@@ -0,0 +1,136 @@
import { Message } from '.';
type EmbedFooter = {
text: string;
icon_url?: string;
proxy_icon_url?: string;
};
type EmbedImage = {
url?: string;
proxy_url?: string;
height?: number;
width?: number;
};
type EmbedVideo = {
url?: string;
height?: number;
width?: number;
};
type EmbedField = {
name: string;
value: string;
inline?: boolean;
};
export class Embed {
title?: string;
description?: string;
url?: string;
timestamp?: string;
color?: number;
footer?: EmbedFooter;
image?: EmbedImage;
thumbnail?: EmbedImage;
video?: EmbedVideo;
provider?: {
name?: string;
url?: string;
};
author?: EmbedImage & {
name?: string;
};
fields?: EmbedField[];
constructor() {
this.fields = [];
}
setTitle = (title: string) => {
this.title = title;
return this;
};
setDescription = (description: string) => {
this.description = description;
return this;
};
setUrl = (url: string) => {
this.url = url;
return this;
};
setTimestamp = (timestamp: string) => {
this.timestamp = timestamp;
return this;
};
setColor = (color: number) => {
this.color = color;
return this;
};
setFooter = (footer: EmbedFooter) => {
this.footer = footer;
return this;
};
setImage = (image: EmbedImage) => {
this.image = image;
return this;
};
setThumbnail = (thumbnail: EmbedImage) => {
this.thumbnail = thumbnail;
return this;
};
setVideo = (video: EmbedVideo) => {
this.video = video;
return this;
};
setProvider = (provider: { name?: string; url?: string }) => {
this.provider = provider;
return this;
};
setAuthor = (author: EmbedImage & { name?: string }) => {
this.author = author;
return this;
};
addField = (field: EmbedField) => {
this.fields.push(field);
return this;
};
addFields = (fields: EmbedField[]) => {
this.fields.push(...fields);
return this;
};
toMessage = () => {
return new Message().addEmbed(this);
};
toJSON = () => {
return {
title: this.title,
description: this.description,
url: this.url,
timestamp: this.timestamp,
color: this.color,
footer: this.footer,
image: this.image,
thumbnail: this.thumbnail,
video: this.video,
provider: this.provider,
author: this.author,
fields: this.fields
};
};
}
+11 -1
View File
@@ -237,6 +237,16 @@ export class Gateway {
}; };
private handleInteractionCreate = (payload: Payload) => { private handleInteractionCreate = (payload: Payload) => {
if (payload.d.type === 2) {
this.handleShashInteraction(payload);
} else {
this.handleChatInteraction(payload);
}
};
private handleShashInteraction = (payload: Payload) => {};
private handleChatInteraction = (payload: Payload) => {
const context = new InteractionContext({ client: this.client, interaction: payload.d }); const context = new InteractionContext({ client: this.client, interaction: payload.d });
const handlers = this.client.interactions.get(payload.d.data.custom_id); const handlers = this.client.interactions.get(payload.d.data.custom_id);
if (handlers) { if (handlers) {
@@ -322,7 +332,7 @@ export class Gateway {
} }
if (data) { if (data) {
console.log('received: %s', data); console.log('received: %s', data);
console.log('received: %s', data.d); console.log('received data: %s', data.d);
if (this.session && data.s && data.s > this.session.s) { if (this.session && data.s && data.s > this.session.s) {
this.session.s = data.s; this.session.s = data.s;
} else { } else {
+1
View File
@@ -3,4 +3,5 @@ export * from './cache';
export * from './guilds'; export * from './guilds';
export * from './context'; export * from './context';
export * from './message'; export * from './message';
export * from './embed';
export * from './interaction'; export * from './interaction';
+17 -17
View File
@@ -5,9 +5,11 @@ import { Message } from "./message";
export class InteractionContext { export class InteractionContext {
public client: Client; public client: Client;
public interaction: Record<string, any>; public interaction: Record<string, any>;
constructor({ client, interaction }: { client: Client; interaction: Record<string, any> }) { private is_loading: boolean;
constructor({ client, interaction, is_loading = false }: { client: Client; interaction: Record<string, any>, is_loading?: boolean }) {
this.client = client; this.client = client;
this.interaction = interaction; this.interaction = interaction;
this.is_loading = is_loading;
} }
ack = (type: InteractionResponseType = InteractionResponseType.Pong, data?: Record<string, any>) => { ack = (type: InteractionResponseType = InteractionResponseType.Pong, data?: Record<string, any>) => {
@@ -30,22 +32,20 @@ export class InteractionContext {
if (typeof message === 'string') { if (typeof message === 'string') {
message = new Message().setContent(message); message = new Message().setContent(message);
} }
return this.ack(InteractionResponseType.UpdateMessage, { if (!this.is_loading) {
...message return this.ack(InteractionResponseType.UpdateMessage, {
});
};
editOriginal = (message: Message | string) => {
if (typeof message === 'string') {
message = new Message().setContent(message);
}
return this.client.api.patch(
`/webhooks/${this.client.application_id}/${this.interaction.token}/messages/@original`,
{
type: InteractionResponseType.DeferredUpdateMessage,
...message ...message
} });
); } else {
return this.client.api.patch(
`/webhooks/${this.client.application_id}/${this.interaction.token}/messages/@original`,
{
type: InteractionResponseType.DeferredUpdateMessage,
...message
}
);
}
}; };
deleteOriginal = () => { deleteOriginal = () => {
@@ -56,6 +56,6 @@ export class InteractionContext {
loading = async () => { loading = async () => {
await this.ack(InteractionResponseType.DeferredReply); await this.ack(InteractionResponseType.DeferredReply);
return new InteractionContext({ client: this.client, interaction: this.interaction }); return new InteractionContext({ client: this.client, interaction: this.interaction, is_loading: true });
}; };
} }
+18 -20
View File
@@ -1,6 +1,4 @@
export type Embed = { import { Embed } from '.';
}
export type Component = Partial<Button | SelectMenu> & { export type Component = Partial<Button | SelectMenu> & {
type: ComponentType; type: ComponentType;
@@ -35,16 +33,16 @@ export type Emoji = {
name: string | null; name: string | null;
id: string | null; id: string | null;
animated?: boolean; animated?: boolean;
} };
export type Button = { export type Button = {
style: ComponentStyle; style: ComponentStyle;
label: string; label: string;
emoji?: Emoji emoji?: Emoji;
custom_id?: string; custom_id?: string;
url?: string; url?: string;
disabled?: boolean; disabled?: boolean;
} };
export type SelectMenu = { export type SelectMenu = {
custom_id: string; custom_id: string;
@@ -52,13 +50,13 @@ export type SelectMenu = {
label: string; label: string;
value: string; value: string;
description?: string; description?: string;
emoji?: Emoji emoji?: Emoji;
default?: boolean; default?: boolean;
}[]; }[];
placeholder?: string; placeholder?: string;
min_values?: number; min_values?: number;
max_values?: number; max_values?: number;
} };
export enum ComponentStyle { export enum ComponentStyle {
Primary = 1, Primary = 1,
@@ -87,33 +85,33 @@ export class Message {
channel_id?: string; channel_id?: string;
guild_id?: string; guild_id?: string;
fail_if_not_exists?: boolean; fail_if_not_exists?: boolean;
} };
constructor() {} constructor() {}
public static button(config: Button): Component { public static button(config: Button): Component {
return { return {
type: ComponentType.Button, type: ComponentType.Button,
...config ...config
} };
} }
public static selectMenu(config: SelectMenu): Component { public static selectMenu(config: SelectMenu): Component {
return { return {
type: ComponentType.SelectMenu, type: ComponentType.SelectMenu,
...config ...config
} };
} }
public setContent = (content: string) => { public setContent = (content: string) => {
this.content = content; this.content = content;
return this; return this;
} };
public addEmbed = (embed: Embed) => { public addEmbed = (embed: Embed) => {
this.embeds.push(embed); this.embeds.push(embed);
return this; return this;
} };
public addComponent = (content: Component | string, ...components: Component[]) => { public addComponent = (content: Component | string, ...components: Component[]) => {
if (typeof content === 'string') { if (typeof content === 'string') {
@@ -130,7 +128,7 @@ export class Message {
}); });
} }
return this; return this;
} };
public setReference = (message: Partial<Message>) => { public setReference = (message: Partial<Message>) => {
this.message_reference = { this.message_reference = {
@@ -138,9 +136,9 @@ export class Message {
channel_id: message.channel_id, channel_id: message.channel_id,
guild_id: message.guild_id, guild_id: message.guild_id,
fail_if_not_exists: false fail_if_not_exists: false
} };
return this; return this;
} };
public toJSON = () => { public toJSON = () => {
return { return {
@@ -148,6 +146,6 @@ export class Message {
embeds: this.embeds, embeds: this.embeds,
components: this.components, components: this.components,
message_reference: this.message_reference message_reference: this.message_reference
} };
} };
} }
+3 -3
View File
@@ -2,9 +2,9 @@ import { Client } from "..";
import { Context } from "../lib"; import { Context } from "../lib";
export abstract class Command { export abstract class Command {
abstract name: string; name: string;
abstract description: string; description: string;
abstract execute: CommandFuntion; execute: CommandFuntion;
} }
export type CommandFuntion = (client: Client, context: Context, ...args: any[]) => void; export type CommandFuntion = (client: Client, context: Context, ...args: any[]) => void;
+55
View File
@@ -0,0 +1,55 @@
import { Client } from "..";
import { Context } from "../lib";
enum SlashCommandType {
ChatInput = 1,
User,
Message
}
enum SlashCommandOptionType {
SubCommand = 1,
SubCommandGroup,
String,
Integer,
Boolean,
User,
Channel,
Role,
Mentionable,
Number,
ATTACHMENT
}
type SlashCommandOptionChoice = {
name: string;
value: string | number;
}
type SlashCommandOption = {
type: SlashCommandOptionType;
name: string;
description: string;
required?: boolean;
choices?: SlashCommandOptionChoice[];
options?: SlashCommandOption[];
min_value?: number;
max_value?: number;
min_length?: number;
max_length?: number;
autocomplete?: boolean;
// channel_types?: number[];
}
export abstract class SlashCommand {
type: SlashCommandType;
name: string;
description: string;
options: SlashCommandOption[];
guild_id?: string;
dm_permission?: boolean;
default_member_permissions?: string;
execute: SlashCommandFuntion;
}
export type SlashCommandFuntion = (client: Client, context: Context, ...args: any[]) => void;
+6 -2
View File
@@ -13,6 +13,10 @@ bot.commands.register('ping', (client, context, args) => {
context.reply('Pong!', true); context.reply('Pong!', true);
}); });
bot.commands.register('help', (client, context, args) => {
context.reply('Help yourself.');
});
bot.commands.register('demo', (_client, context, _args) => { bot.commands.register('demo', (_client, context, _args) => {
const msg = new Message() const msg = new Message()
.setContent('Hello World!') .setContent('Hello World!')
@@ -125,14 +129,14 @@ bot.handlers.register('READY', (client, context, data) => {
bot.interactions.register('never_me', async (client, context, interaction) => { bot.interactions.register('never_me', async (client, context, interaction) => {
const reply = await context.loading(); const reply = await context.loading();
setTimeout(() => { setTimeout(() => {
reply.editOriginal(`You clicked me! Thank you ${interaction.member.user.username} <3`); reply.edit(`You clicked me! Thank you ${interaction.member.user.username} <3`);
}, 1000); }, 1000);
}); });
bot.interactions.register('click_me', async (client, context, interaction) => { bot.interactions.register('click_me', async (client, context, interaction) => {
const reply = await context.loading(); const reply = await context.loading();
setTimeout(() => { setTimeout(() => {
reply.editOriginal( reply.edit(
new Message().addComponent( new Message().addComponent(
Message.button({ Message.button({
label: 'Click me!', label: 'Click me!',