working application commands?

This commit is contained in:
Jaron Pate
2022-10-07 01:12:31 -05:00
parent 230aee97fa
commit 57a787e7ec
7 changed files with 199 additions and 63 deletions
+75 -12
View File
@@ -1,6 +1,18 @@
import { ComponentStyle, Context, Gateway, Guilds, Message, Embed } from './lib'; import { ComponentStyle, Context, Gateway, Guilds, Message, Embed } from './lib';
import { Command, CommandFuntion, Intent, Presence } from './types'; import { SlashCommand, SlashCommandFunction, ChatCommand, Command, CommandFunction, Intent, Presence } from './types';
export { ComponentStyle, Context, Gateway, Guilds, Message, Embed, Command, CommandFuntion, Intent, Presence }; export {
ComponentStyle,
Context,
Gateway,
Guilds,
Message,
Embed,
Command,
CommandFunction,
ChatCommand,
Intent,
Presence
};
type Config = { type Config = {
token: string; token: string;
@@ -14,8 +26,8 @@ export class Client {
readonly token: string; readonly token: string;
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, ChatCommand | CommandFunction> = new Map();
private readonly slash_commands: Map<string, Command | CommandFuntion> = new Map(); private readonly slash_commands: Map<string, SlashCommand | SlashCommandFunction> = new Map();
readonly guilds: Guilds; readonly guilds: Guilds;
constructor({ token, application_id, prefix, intents }: Config) { constructor({ token, application_id, prefix, intents }: Config) {
@@ -67,7 +79,8 @@ export class Client {
}; };
public commands = { public commands = {
register: (command: Command | string, handler: CommandFuntion) => { chat: {
register: (command: ChatCommand | string, handler?: CommandFunction) => {
if (typeof command === 'string') { if (typeof command === 'string') {
this.client_commands.set(command, handler); this.client_commands.set(command, handler);
} else { } else {
@@ -80,7 +93,7 @@ export class Client {
execute: (command_name: string, context: Context, ...args: any[]) => { execute: (command_name: string, context: Context, ...args: any[]) => {
const command = this.client_commands.get(command_name); const command = this.client_commands.get(command_name);
if (command) { if (command) {
command instanceof Command command instanceof ChatCommand
? command.execute(this, context, args.slice(1)) ? command.execute(this, context, args.slice(1))
: command(this, context, args.slice(1)); : command(this, context, args.slice(1));
} }
@@ -89,26 +102,76 @@ export class Client {
this.client_commands.delete(command_name); this.client_commands.delete(command_name);
}, },
list: () => { list: () => {
return Array.from(this.client_commands.entries());
},
map: () => {
return this.client_commands; return this.client_commands;
} }
},
application: {
register: (command: SlashCommand | Record<string, string>, handler?: SlashCommandFunction) => {
const payload =
command instanceof SlashCommand
? command.toJSON()
: {
name: command.name,
description: command.description,
type: command.type ?? 1
};
this.api
.post(`/applications/${this.application_id}/commands`, payload)
.then((response) => {
console.log(response);
if (command instanceof SlashCommand) {
this.slash_commands.set(command.name, command);
} else {
this.slash_commands.set(command.name, handler);
}
})
.catch((error) => {
this.log.error('Error registering application command', error);
});
},
get: (command_name: string) => {
return this.slash_commands.get(command_name);
},
list: () => {
return Array.from(this.slash_commands.entries());
},
map: () => {
return this.slash_commands;
}
}
};
private log = {
error: (error_or_message: Error | string, error?: Error) => {
if (error && typeof error_or_message === 'string') {
console.error(`[discord.ts] ${error_or_message}:`, error);
} else {
console.error('[discord.ts] Error:', error_or_message);
}
}
}; };
private initialize = () => { private initialize = () => {
// Register default help command // Register default help command
this.commands.register('help', (client, context, _args) => { this.commands.chat.register('help', (client, context, _args) => {
const embed = new Embed().setTitle('Commands').setDescription('This is a list of commands you can use.'); const embed = new Embed()
.setTitle('Commands')
.setDescription('This is a list of commands you can use.')
.setColor('#f6c221');
const commands = Array.from(client.commands.list().entries()); const commands = client.commands.chat.list();
for (const [name, command] of commands) { for (const [name, command] of commands) {
embed.addField({ embed.addField({
name: name, name: name,
value: command instanceof Command ? command.description : 'No description provided' value: command instanceof ChatCommand ? command.description : 'No description provided'
}); });
} }
context.reply(embed.toMessage()); context.reply(embed.toMessage());
}); });
} };
} }
+2 -2
View File
@@ -68,8 +68,8 @@ export class Embed {
return this; return this;
}; };
setColor = (color: number) => { setColor = (color: number | string) => {
this.color = color; this.color = typeof color === 'string' ? parseInt(`0x${color.replace('#', '')}`) : color;
return this; return this;
}; };
+36 -9
View File
@@ -12,9 +12,10 @@ import {
Status, Status,
Presence, Presence,
ActivityType, ActivityType,
CommandFuntion, CommandFunction,
Interaction, Interaction,
InteractionFunction InteractionFunction,
SlashCommand
} from '../types'; } from '../types';
import { Context } from './context'; import { Context } from './context';
import { InteractionContext } from './interaction'; import { InteractionContext } from './interaction';
@@ -26,9 +27,11 @@ export class Gateway {
readonly token: string; readonly token: string;
readonly client: Client; readonly client: Client;
private intents: number = 513; private intents: number = 513;
private commandNotFound: CommandFuntion = null; private commandNotFound: CommandFunction = null;
// Discord gateway data // Discord gateway data
private readonly url: string = 'wss://gateway.discord.gg/?v=10&encoding=json'; private readonly url: string = 'wss://gateway.discord.gg/?v=10&encoding=json';
private heartbeat_timeout: NodeJS.Timeout;
private heartbeat_function: NodeJS.Timer;
private heartbeat_interval: number | null = null; private heartbeat_interval: number | null = null;
private session: Session = null; private session: Session = null;
public user: User = null; public user: User = null;
@@ -45,7 +48,7 @@ export class Gateway {
token: string; token: string;
intents?: Intent[]; intents?: Intent[];
client: Client; client: Client;
commandNotFound?: CommandFuntion; commandNotFound?: CommandFunction;
}) { }) {
// Copy config // Copy config
this.token = token; this.token = token;
@@ -80,6 +83,7 @@ export class Gateway {
}; };
public reconnect = () => { public reconnect = () => {
this.socket.terminate();
return this.connect(); return this.connect();
}; };
@@ -167,12 +171,21 @@ export class Gateway {
if (!this.heartbeat_interval) { if (!this.heartbeat_interval) {
throw new Error('No heartbeat interval set'); throw new Error('No heartbeat interval set');
} }
setInterval(() => { if (this.heartbeat_function) {
clearInterval(this.heartbeat_function);
}
if (this.heartbeat_timeout) {
clearTimeout(this.heartbeat_timeout);
}
this.heartbeat_function = setInterval(() => {
this.send({ this.send({
op: 1, op: 1,
d: {} d: {}
}); });
}, this.heartbeat_interval); }, this.heartbeat_interval);
this.heartbeat_timeout = setTimeout(() => {
this.reconnect();
}, this.heartbeat_interval * 2);
}; };
private initialize = (payload: Payload) => { private initialize = (payload: Payload) => {
@@ -244,7 +257,18 @@ export class Gateway {
} }
}; };
private handleShashInteraction = (payload: Payload) => {}; private handleShashInteraction = (payload: Payload) => {
console.log(payload.d);
const context = new InteractionContext({ client: this.client, interaction: payload.d });
const handler = this.client.commands.application.get(payload.d.data.name);
if (handler) {
if (handler instanceof SlashCommand) {
handler.execute(this.client, context, payload.d);
} else {
handler(this.client, context, payload.d);
}
}
};
private handleChatInteraction = (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 });
@@ -266,15 +290,16 @@ export class Gateway {
return; return;
} }
console.log(payload.d); console.log(payload.d);
const content = payload.d.content.trim();
// Is this a command? // Is this a command?
if (payload.d.content.startsWith(this.client.prefix)) { if (content.startsWith(this.client.prefix) | content.startsWith(`<@${this.client.user.id}>`)) {
// Parse command // Parse command
const args = payload.d.content const args = content
.slice(this.client.prefix.length) .slice(this.client.prefix.length)
.trim() .trim()
.split(/\s+(?=(?:[^\'"]*[\'"][^\'"]*[\'"])*[^\'"]*$)/g); .split(/\s+(?=(?:[^\'"]*[\'"][^\'"]*[\'"])*[^\'"]*$)/g);
// Check if command exists // Check if command exists
const handler = this.client.commands.get(args[0]); const handler = this.client.commands.chat.get(args[0]);
// Generate context // Generate context
const context = new Context({ client: this.client, message: payload.d }); const context = new Context({ client: this.client, message: payload.d });
if (handler) { if (handler) {
@@ -317,6 +342,7 @@ export class Gateway {
break; break;
case 11: case 11:
console.log('heartbeat acknowledged'); console.log('heartbeat acknowledged');
this.heartbeat_timeout.refresh();
break; break;
default: default:
break; break;
@@ -324,6 +350,7 @@ export class Gateway {
}; };
private onMessage = (packet: Data): void => { private onMessage = (packet: Data): void => {
console.log('============================');
let data: Payload | null = null; let data: Payload | null = null;
try { try {
data = JSON.parse(packet.toString()); data = JSON.parse(packet.toString());
+17 -6
View File
@@ -1,10 +1,21 @@
import { Client } from ".."; import { Client } from '..';
import { Context } from "../lib"; import { Context } from '../lib';
export abstract class Command { export abstract class Command {
name: string; readonly name: string;
description: string; readonly description: string;
execute: CommandFuntion; readonly execute: CommandFunction;
constructor(data: Command) {
this.name = data.name;
this.description = data.description;
this.execute = data.execute;
}
} }
export type CommandFuntion = (client: Client, context: Context, ...args: any[]) => void; export type CommandFunction = (client: Client, context: Context, ...args: any[]) => void;
export class ChatCommand extends Command {
constructor(data: ChatCommand) {
super(data);
}
}
+1
View File
@@ -1,4 +1,5 @@
export * from './common'; export * from './common';
export * from './command'; export * from './command';
export * from './slash_command';
export * from './handler'; export * from './handler';
export * from './interaction'; export * from './interaction';
+30 -7
View File
@@ -1,5 +1,5 @@
import { Client } from ".."; import { Client } from '..';
import { Context } from "../lib"; import { InteractionContext } from '../lib';
enum SlashCommandType { enum SlashCommandType {
ChatInput = 1, ChatInput = 1,
@@ -24,7 +24,7 @@ enum SlashCommandOptionType {
type SlashCommandOptionChoice = { type SlashCommandOptionChoice = {
name: string; name: string;
value: string | number; value: string | number;
} };
type SlashCommandOption = { type SlashCommandOption = {
type: SlashCommandOptionType; type: SlashCommandOptionType;
@@ -39,9 +39,9 @@ type SlashCommandOption = {
max_length?: number; max_length?: number;
autocomplete?: boolean; autocomplete?: boolean;
// channel_types?: number[]; // channel_types?: number[];
} };
export abstract class SlashCommand { export class SlashCommand {
type: SlashCommandType; type: SlashCommandType;
name: string; name: string;
description: string; description: string;
@@ -49,7 +49,30 @@ export abstract class SlashCommand {
guild_id?: string; guild_id?: string;
dm_permission?: boolean; dm_permission?: boolean;
default_member_permissions?: string; default_member_permissions?: string;
execute: SlashCommandFuntion; execute: SlashCommandFunction;
constructor(data: SlashCommand) {
this.type = data.type;
this.name = data.name;
this.description = data.description;
this.options = data.options;
this.guild_id = data.guild_id;
this.dm_permission = data.dm_permission;
this.default_member_permissions = data.default_member_permissions;
this.execute = data.execute;
}
public toJSON() {
return {
type: this.type,
name: this.name,
description: this.description,
options: this.options,
guild_id: this.guild_id,
dm_permission: this.dm_permission,
default_member_permissions: this.default_member_permissions
};
}
} }
export type SlashCommandFuntion = (client: Client, context: Context, ...args: any[]) => void; export type SlashCommandFunction = (client: Client, context: InteractionContext, ...args: any[]) => void;
+18 -7
View File
@@ -1,7 +1,7 @@
import * as dotenv from 'dotenv'; import * as dotenv from 'dotenv';
dotenv.config(); dotenv.config();
import { Client, ComponentStyle, Intent, Message } from '../src'; import { Client, ChatCommand, ComponentStyle, Intent, Message } from '../src';
const bot = new Client({ const bot = new Client({
token: process.env.BOT_TOKEN!, token: process.env.BOT_TOKEN!,
@@ -9,15 +9,26 @@ const bot = new Client({
intents: [Intent.GUILDS, Intent.GUILD_MESSAGES, Intent.GUILD_MESSAGE_REACTIONS, Intent.MESSAGE_CONTENT] intents: [Intent.GUILDS, Intent.GUILD_MESSAGES, Intent.GUILD_MESSAGE_REACTIONS, Intent.MESSAGE_CONTENT]
}); });
bot.commands.register('ping', (client, context, args) => { bot.commands.chat.register(new ChatCommand({
name: 'ping',
description: 'Check if he is alive',
execute: (client, context, args) => {
context.reply('Pong!', true); context.reply('Pong!', true);
}
}))
bot.commands.application.register({
name: 'ping',
description: 'Check if he is alive'
}, (client, context, args) => {
context.reply('Pong!');
}); });
bot.commands.register('help', (client, context, args) => { // bot.commands.register('help', (client, context, args) => {
context.reply('Help yourself.'); // context.reply('Help yourself.');
}); // });
bot.commands.register('demo', (_client, context, _args) => { bot.commands.chat.register('demo', (_client, context, _args) => {
const msg = new Message() const msg = new Message()
.setContent('Hello World!') .setContent('Hello World!')
.addComponent( .addComponent(
@@ -94,7 +105,7 @@ bot.commands.register('demo', (_client, context, _args) => {
context.reply(msg); context.reply(msg);
}); });
bot.commands.register('test', (_client, context, _args) => { bot.commands.chat.register('test', (_client, context, _args) => {
const msg = new Message().setContent('This is a test interaction!').addComponent( const msg = new Message().setContent('This is a test interaction!').addComponent(
'This is an action row', 'This is an action row',
Message.button({ Message.button({