mirror of
https://github.com/jaronpate/unicord.git
synced 2026-10-18 21:47:40 +00:00
starting to add slash commands
This commit is contained in:
+2
-2
@@ -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
@@ -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());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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 {
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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,15 +32,11 @@ export class InteractionContext {
|
|||||||
if (typeof message === 'string') {
|
if (typeof message === 'string') {
|
||||||
message = new Message().setContent(message);
|
message = new Message().setContent(message);
|
||||||
}
|
}
|
||||||
|
if (!this.is_loading) {
|
||||||
return this.ack(InteractionResponseType.UpdateMessage, {
|
return this.ack(InteractionResponseType.UpdateMessage, {
|
||||||
...message
|
...message
|
||||||
});
|
});
|
||||||
};
|
} else {
|
||||||
|
|
||||||
editOriginal = (message: Message | string) => {
|
|
||||||
if (typeof message === 'string') {
|
|
||||||
message = new Message().setContent(message);
|
|
||||||
}
|
|
||||||
return this.client.api.patch(
|
return this.client.api.patch(
|
||||||
`/webhooks/${this.client.application_id}/${this.interaction.token}/messages/@original`,
|
`/webhooks/${this.client.application_id}/${this.interaction.token}/messages/@original`,
|
||||||
{
|
{
|
||||||
@@ -46,6 +44,8 @@ export class InteractionContext {
|
|||||||
...message
|
...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 });
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-18
@@ -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,7 +85,7 @@ 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() {}
|
||||||
|
|
||||||
@@ -95,25 +93,25 @@ export class Message {
|
|||||||
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
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
@@ -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
@@ -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!',
|
||||||
|
|||||||
Reference in New Issue
Block a user