mirror of
https://github.com/jaronpate/unicord.git
synced 2026-10-18 21:47:40 +00:00
init for rewrite
This commit is contained in:
+5
-177
@@ -1,177 +1,5 @@
|
||||
import { ComponentStyle, Context, Gateway, Guilds, Message, Embed } from './lib';
|
||||
import { SlashCommand, SlashCommandFunction, ChatCommand, Command, CommandFunction, Intent, Presence } from './types';
|
||||
export {
|
||||
ComponentStyle,
|
||||
Context,
|
||||
Gateway,
|
||||
Guilds,
|
||||
Message,
|
||||
Embed,
|
||||
Command,
|
||||
CommandFunction,
|
||||
ChatCommand,
|
||||
Intent,
|
||||
Presence
|
||||
};
|
||||
|
||||
type Config = {
|
||||
token: string;
|
||||
application_id: string;
|
||||
prefix?: string;
|
||||
intents?: Intent[];
|
||||
};
|
||||
|
||||
export class Client {
|
||||
readonly prefix: string;
|
||||
readonly token: string;
|
||||
readonly application_id: string;
|
||||
private readonly gateway: Gateway;
|
||||
private readonly client_commands: Map<string, ChatCommand | CommandFunction> = new Map();
|
||||
private readonly slash_commands: Map<string, SlashCommand | SlashCommandFunction> = new Map();
|
||||
readonly guilds: Guilds;
|
||||
|
||||
constructor({ token, application_id, prefix, intents }: Config) {
|
||||
this.token = token;
|
||||
this.application_id = application_id;
|
||||
this.prefix = prefix ?? '!';
|
||||
this.gateway = new Gateway({ token, intents, client: this });
|
||||
this.guilds = new Guilds({ token, gateway: this.gateway });
|
||||
// Do some default stuff
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
get user() {
|
||||
return this.gateway.user;
|
||||
}
|
||||
|
||||
get handlers() {
|
||||
return this.gateway.handlers;
|
||||
}
|
||||
|
||||
get interactions() {
|
||||
return this.gateway.interactions;
|
||||
}
|
||||
|
||||
get api() {
|
||||
return this.gateway.api;
|
||||
}
|
||||
|
||||
public connect = () => {
|
||||
this.gateway.connect();
|
||||
return this;
|
||||
};
|
||||
|
||||
public reconnect = () => {
|
||||
this.gateway.reconnect();
|
||||
return this;
|
||||
};
|
||||
|
||||
public setPresence = (presence: Presence) => {
|
||||
this.gateway.setPresence(presence);
|
||||
return this;
|
||||
};
|
||||
|
||||
send = (channel_id: string, message: Message | string) => {
|
||||
if (typeof message === 'string') {
|
||||
message = new Message().setContent(message);
|
||||
}
|
||||
return this.api.post(`/channels/${channel_id}/messages`, message);
|
||||
};
|
||||
|
||||
public commands = {
|
||||
chat: {
|
||||
register: (command: ChatCommand | string, handler?: CommandFunction) => {
|
||||
if (typeof command === 'string') {
|
||||
this.client_commands.set(command, handler);
|
||||
} else {
|
||||
this.client_commands.set(command.name, command);
|
||||
}
|
||||
},
|
||||
get: (command_name: string) => {
|
||||
return this.client_commands.get(command_name);
|
||||
},
|
||||
execute: (command_name: string, context: Context, ...args: any[]) => {
|
||||
const command = this.client_commands.get(command_name);
|
||||
if (command) {
|
||||
command instanceof ChatCommand
|
||||
? command.execute(this, context, args.slice(1))
|
||||
: command(this, context, args.slice(1));
|
||||
}
|
||||
},
|
||||
delete: (command_name: string) => {
|
||||
this.client_commands.delete(command_name);
|
||||
},
|
||||
list: () => {
|
||||
return Array.from(this.client_commands.entries());
|
||||
},
|
||||
map: () => {
|
||||
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 = () => {
|
||||
// Register default help command
|
||||
this.commands.chat.register('help', (client, context, _args) => {
|
||||
const embed = new Embed()
|
||||
.setTitle('Commands')
|
||||
.setDescription('This is a list of commands you can use.')
|
||||
.setColor('#f6c221');
|
||||
|
||||
const commands = client.commands.chat.list();
|
||||
|
||||
for (const [name, command] of commands) {
|
||||
embed.addField({
|
||||
name: name,
|
||||
value: command instanceof ChatCommand ? command.description : 'No description provided'
|
||||
});
|
||||
}
|
||||
|
||||
context.reply(embed.toMessage());
|
||||
});
|
||||
};
|
||||
}
|
||||
export { Client } from './services/client';
|
||||
export { Intent } from './types/common';
|
||||
export { BaseHandler, type Handler } from './types/hander';
|
||||
export type { ClientConfig } from './types/common';
|
||||
export type { Context } from './types/context';
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Context, Gateway } from '.';
|
||||
|
||||
export class ObjectCache<T> {
|
||||
cache = new Map<string, T>();
|
||||
private readonly token: string;
|
||||
private readonly type: string;
|
||||
private readonly gateway: Gateway;
|
||||
|
||||
constructor({ token, gateway, type, events }: { token: string; gateway: Gateway; type: string; events: string[] }) {
|
||||
this.token = token;
|
||||
this.type = type;
|
||||
this.gateway = gateway;
|
||||
gateway.handlers.register(events, (_client, _context: Context, e: Record<string, any>) => {
|
||||
this.cache.set(e.id, e as T);
|
||||
});
|
||||
}
|
||||
|
||||
async fetch(object_id: string): Promise<T> {
|
||||
return this.gateway.api.get(`${this.type}/${object_id}`).then((res) => {
|
||||
const object: T & { id: string } = res.data;
|
||||
this.cache.set(object.id, object);
|
||||
return object;
|
||||
});
|
||||
}
|
||||
|
||||
get(object_id: string): T | Promise<T> {
|
||||
if (this.cache.has(object_id)) {
|
||||
return Promise.resolve(this.cache.get(object_id)!);
|
||||
} else {
|
||||
return this.fetch(object_id);
|
||||
}
|
||||
}
|
||||
|
||||
list(): Map<string, T> {
|
||||
return this.cache;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Client } from '..';
|
||||
import { Message } from '../lib';
|
||||
|
||||
export class Context {
|
||||
private readonly client: Client;
|
||||
// Public properties
|
||||
message_id: string;
|
||||
channel_id: string;
|
||||
guild_id: string;
|
||||
|
||||
constructor({ client, message }: { client: Client; message: Partial<Message> }) {
|
||||
this.client = client;
|
||||
this.message_id = message.id;
|
||||
this.channel_id = message.channel_id;
|
||||
this.guild_id = message.guild_id;
|
||||
}
|
||||
|
||||
get guild() {
|
||||
return this.client.guilds.get(this.guild_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply to the message that this command
|
||||
* @param message The message to send to the channel
|
||||
* @param reference If true the message will be a reference the the message that triggered the command
|
||||
* @returns The message that was sent
|
||||
*/
|
||||
public reply = (message: Message | string, reference: boolean = false) => {
|
||||
if (typeof message === 'string') {
|
||||
message = new Message().setContent(message);
|
||||
}
|
||||
if (reference) {
|
||||
message.message_reference = {
|
||||
message_id: this.message_id,
|
||||
channel_id: this.channel_id,
|
||||
guild_id: this.guild_id
|
||||
};
|
||||
}
|
||||
return this.client.send(this.channel_id, message);
|
||||
};
|
||||
|
||||
public delete = (message_id?: string) => {
|
||||
return this.client.api.delete(`/channels/${this.channel_id}/messages/${message_id ?? this.message_id}`);
|
||||
};
|
||||
|
||||
public edit = (message: Message | string) => {
|
||||
if (typeof message === 'string') {
|
||||
message = new Message().setContent(message);
|
||||
}
|
||||
return this.client.api.patch(`/channels/${this.channel_id}/messages/${this.message_id}`, message);
|
||||
};
|
||||
}
|
||||
@@ -1,374 +0,0 @@
|
||||
import { WebSocket, Data } from 'ws';
|
||||
import axios, { AxiosInstance } from 'axios';
|
||||
import { Client } from '..';
|
||||
import {
|
||||
Session,
|
||||
User,
|
||||
Payload,
|
||||
Intent,
|
||||
Handler,
|
||||
HandlerFunction,
|
||||
Command,
|
||||
Status,
|
||||
Presence,
|
||||
ActivityType,
|
||||
CommandFunction,
|
||||
Interaction,
|
||||
InteractionFunction,
|
||||
SlashCommand
|
||||
} from '../types';
|
||||
import { Context } from './context';
|
||||
import { InteractionContext } from './interaction';
|
||||
|
||||
export class Gateway {
|
||||
socket: WebSocket;
|
||||
axios: AxiosInstance;
|
||||
// Config
|
||||
readonly token: string;
|
||||
readonly client: Client;
|
||||
private intents: number = 513;
|
||||
private commandNotFound: CommandFunction = null;
|
||||
// Discord gateway data
|
||||
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 session: Session = null;
|
||||
public user: User = null;
|
||||
// Utils
|
||||
public event_handlers: Map<string, Array<Handler | HandlerFunction>> = new Map();
|
||||
public interaction_handlers: Map<string, Array<Interaction | InteractionFunction>> = new Map();
|
||||
|
||||
constructor({
|
||||
token,
|
||||
intents,
|
||||
client,
|
||||
commandNotFound
|
||||
}: {
|
||||
token: string;
|
||||
intents?: Intent[];
|
||||
client: Client;
|
||||
commandNotFound?: CommandFunction;
|
||||
}) {
|
||||
// Copy config
|
||||
this.token = token;
|
||||
this.client = client;
|
||||
this.commandNotFound = commandNotFound;
|
||||
// Initialize socket
|
||||
this.socket = new WebSocket(this.url);
|
||||
// Initialize axios
|
||||
this.axios = axios.create({
|
||||
headers: {
|
||||
'User-Agent': 'DiscordBot (discord.ts, 0.0.1)',
|
||||
Authorization: `Bot ${this.token}`
|
||||
},
|
||||
baseURL: 'https://discord.com/api/v10'
|
||||
});
|
||||
this.axios.interceptors.response.use((response) => response.data);
|
||||
// Calculate intents
|
||||
if (intents) {
|
||||
this.intents = intents.reduce((acc, intent) => acc | intent, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public connect = () => {
|
||||
// Initialize socket
|
||||
this.socket = new WebSocket(this.url);
|
||||
// Register the listeners
|
||||
this.socket.on('open', this.onOpen);
|
||||
this.socket.on('message', this.onMessage);
|
||||
// this.socket.on('close', this.onClose);
|
||||
// this.socket.on('error', this.onError);
|
||||
return this;
|
||||
};
|
||||
|
||||
public reconnect = () => {
|
||||
this.socket.terminate();
|
||||
return this.connect();
|
||||
};
|
||||
|
||||
public setPresence = (presence: Presence) => {
|
||||
this.send({
|
||||
op: 3,
|
||||
d: presence
|
||||
});
|
||||
};
|
||||
|
||||
public handlers = {
|
||||
register: (events: string | string[], handler: Handler | HandlerFunction) => {
|
||||
this.registerHandler(events, handler, 'command');
|
||||
},
|
||||
get: (event: string) => {
|
||||
return this.event_handlers.get(event);
|
||||
}
|
||||
};
|
||||
|
||||
public interactions = {
|
||||
register: (custom_ids: string | string[], handler: Interaction | InteractionFunction) => {
|
||||
this.registerHandler(custom_ids, handler, 'interaction');
|
||||
},
|
||||
get: (event: string) => {
|
||||
return this.interaction_handlers.get(event);
|
||||
}
|
||||
};
|
||||
|
||||
public api = {
|
||||
get: (url: string, params?: any) => {
|
||||
return this.axios.get<unknown, Record<string, any>>(url, { params });
|
||||
},
|
||||
post: (url: string, data?: any) => {
|
||||
return this.axios.post<unknown, Record<string, any>>(url, data);
|
||||
},
|
||||
patch: (url: string, data?: any) => {
|
||||
return this.axios.patch<unknown, Record<string, any>>(url, data);
|
||||
},
|
||||
delete: (url: string, data?: any) => {
|
||||
return this.axios.delete<unknown, Record<string, any>>(url, data);
|
||||
}
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
private registerHandler(events: string | string[], handler: Handler | HandlerFunction, type: 'command'): void;
|
||||
// prettier-ignore
|
||||
private registerHandler(events: string | string[], handler: Interaction | InteractionFunction, type: 'interaction'): void;
|
||||
|
||||
private registerHandler(events: string | string[], handler: any, type: 'command' | 'interaction') {
|
||||
const handler_map = type === 'command' ? this.event_handlers : this.interaction_handlers;
|
||||
if (Array.isArray(events)) {
|
||||
// If Array register handler for each event
|
||||
for (const event of events) {
|
||||
const current_handlers = handler_map.get(event) ?? [];
|
||||
handler_map.set(event, [...current_handlers, handler]);
|
||||
}
|
||||
} else {
|
||||
// Otherwise register for the single event
|
||||
const current_handlers = handler_map.get(events) ?? [];
|
||||
handler_map.set(events, [...current_handlers, handler]);
|
||||
}
|
||||
}
|
||||
private send = (payload: Partial<Payload>) => {
|
||||
payload.s = this.session?.s ?? null;
|
||||
console.log('sending: %s', payload);
|
||||
this.socket.send(JSON.stringify(payload));
|
||||
};
|
||||
|
||||
private indentify = () => {
|
||||
this.send({
|
||||
op: 2,
|
||||
d: {
|
||||
token: this.token,
|
||||
intents: this.intents,
|
||||
properties: {
|
||||
os: process.platform,
|
||||
browser: 'discord.ts',
|
||||
device: 'discord.ts'
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
private heartbeat = () => {
|
||||
if (!this.heartbeat_interval) {
|
||||
throw new Error('No heartbeat interval set');
|
||||
}
|
||||
if (this.heartbeat_function) {
|
||||
clearInterval(this.heartbeat_function);
|
||||
}
|
||||
if (this.heartbeat_timeout) {
|
||||
clearTimeout(this.heartbeat_timeout);
|
||||
}
|
||||
this.heartbeat_function = setInterval(() => {
|
||||
this.send({
|
||||
op: 1,
|
||||
d: {}
|
||||
});
|
||||
}, this.heartbeat_interval);
|
||||
this.heartbeat_timeout = setTimeout(() => {
|
||||
this.reconnect();
|
||||
}, this.heartbeat_interval * 2);
|
||||
};
|
||||
|
||||
private initialize = (payload: Payload) => {
|
||||
this.session = payload.d.session;
|
||||
this.user = payload.d.user;
|
||||
this.user.tag = `${this.user.username}#${this.user.discriminator}`;
|
||||
// Construct presence
|
||||
const status: Presence = {
|
||||
status: Status.Online,
|
||||
since: null,
|
||||
activities: [
|
||||
{
|
||||
name: payload.d.guilds.length + ' servers',
|
||||
type: ActivityType.Watching,
|
||||
created_at: Date.now()
|
||||
}
|
||||
],
|
||||
afk: false
|
||||
};
|
||||
// Set presence
|
||||
this.setPresence(status);
|
||||
// Cache guilds? (these are not whole guilds, just guild ids)
|
||||
// for (const guild of payload.d.guilds) {
|
||||
// this.guilds.set(guild.id, guild);
|
||||
// }
|
||||
console.log('session: %s', this.session);
|
||||
console.log('user: %s', this.user);
|
||||
};
|
||||
|
||||
private parseEvent = (payload: Payload) => {
|
||||
// If not a dispatch event, return
|
||||
if (!payload.t) {
|
||||
return;
|
||||
}
|
||||
// Run default handlers
|
||||
switch (payload.t) {
|
||||
case 'READY':
|
||||
this.initialize(payload);
|
||||
break;
|
||||
case 'INTERACTION_CREATE':
|
||||
this.handleInteractionCreate(payload);
|
||||
break;
|
||||
case 'MESSAGE_CREATE':
|
||||
this.handleMessageCreate(payload);
|
||||
break;
|
||||
case 'MESSAGE_UPDATE':
|
||||
this.handleMessageCreate(payload);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Call event handlers
|
||||
const handlers = this.handlers.get(payload.t);
|
||||
const context = new Context({ client: this.client, message: payload.d });
|
||||
if (handlers) {
|
||||
for (const handler of handlers) {
|
||||
handler instanceof Handler
|
||||
? handler.execute(this.client, context, payload.d)
|
||||
: handler(this.client, context, payload.d);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleInteractionCreate = (payload: Payload) => {
|
||||
if (payload.d.type === 2) {
|
||||
this.handleShashInteraction(payload);
|
||||
} else {
|
||||
this.handleChatInteraction(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) => {
|
||||
const context = new InteractionContext({ client: this.client, interaction: payload.d });
|
||||
const handlers = this.client.interactions.get(payload.d.data.custom_id);
|
||||
if (handlers) {
|
||||
for (const handler of handlers) {
|
||||
if (handler instanceof Interaction) {
|
||||
handler.execute(this.client, context, payload.d);
|
||||
} else {
|
||||
handler(this.client, context, payload.d);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private handleMessageCreate = async (payload: Payload) => {
|
||||
// Ignore bots
|
||||
if (payload.d.author.bot) {
|
||||
return;
|
||||
}
|
||||
console.log(payload.d);
|
||||
const content = payload.d.content.trim();
|
||||
// Is this a command?
|
||||
if (content.startsWith(this.client.prefix) | content.startsWith(`<@${this.client.user.id}>`)) {
|
||||
// Parse command
|
||||
const args = content
|
||||
.slice(this.client.prefix.length)
|
||||
.trim()
|
||||
.split(/\s+(?=(?:[^\'"]*[\'"][^\'"]*[\'"])*[^\'"]*$)/g);
|
||||
// Check if command exists
|
||||
const handler = this.client.commands.chat.get(args[0]);
|
||||
// Generate context
|
||||
const context = new Context({ client: this.client, message: payload.d });
|
||||
if (handler) {
|
||||
// Execute command
|
||||
handler instanceof Command
|
||||
? handler.execute(this.client, context, args.slice(1))
|
||||
: handler(this.client, context, args.slice(1));
|
||||
} else {
|
||||
let reply = null;
|
||||
// Send error message if command does not exist
|
||||
if (this.commandNotFound) {
|
||||
// If custom command not found handler is set, use that
|
||||
reply = this.commandNotFound(this.client, context);
|
||||
} else {
|
||||
// Otherwise send default message
|
||||
reply = await context.reply('Command not found :(', true);
|
||||
}
|
||||
if (reply) {
|
||||
// If reply is set, delete it after 3 seconds
|
||||
setTimeout(() => {
|
||||
context.delete(reply.id);
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private parseOpCode = (payload: Payload) => {
|
||||
switch (payload.op) {
|
||||
case 0:
|
||||
this.parseEvent(payload);
|
||||
break;
|
||||
case 9:
|
||||
setTimeout(this.reconnect, 500);
|
||||
break;
|
||||
case 10:
|
||||
this.heartbeat_interval = payload.d.heartbeat_interval;
|
||||
this.heartbeat();
|
||||
this.indentify();
|
||||
break;
|
||||
case 11:
|
||||
console.log('heartbeat acknowledged');
|
||||
this.heartbeat_timeout.refresh();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
private onMessage = (packet: Data): void => {
|
||||
console.log('============================');
|
||||
let data: Payload | null = null;
|
||||
try {
|
||||
data = JSON.parse(packet.toString());
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
if (data) {
|
||||
console.log('received: %s', data);
|
||||
console.log('received data: %s', data.d);
|
||||
if (this.session && data.s && data.s > this.session.s) {
|
||||
this.session.s = data.s;
|
||||
} else {
|
||||
this.parseOpCode(data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onOpen = () => {
|
||||
console.log('Socket open');
|
||||
};
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Gateway, ObjectCache } from ".";
|
||||
import { Guild } from "../types";
|
||||
|
||||
export class Guilds extends ObjectCache<Guild> {
|
||||
constructor ({ token, gateway }: { token: string, gateway: Gateway }) {
|
||||
super({token, gateway, type: 'guilds', events: ['GUILD_CREATE', 'GUILD_UPDATE']});
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
export * from './gateway';
|
||||
export * from './cache';
|
||||
export * from './guilds';
|
||||
export * from './context';
|
||||
export * from './message';
|
||||
export * from './embed';
|
||||
export * from './interaction';
|
||||
@@ -1,61 +0,0 @@
|
||||
import { Client } from "..";
|
||||
import { InteractionResponseType } from "../types";
|
||||
import { Message } from "./message";
|
||||
|
||||
export class InteractionContext {
|
||||
public client: Client;
|
||||
public 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.interaction = interaction;
|
||||
this.is_loading = is_loading;
|
||||
}
|
||||
|
||||
ack = (type: InteractionResponseType = InteractionResponseType.Pong, data?: Record<string, any>) => {
|
||||
return this.client.api.post(`/interactions/${this.interaction.id}/${this.interaction.token}/callback`, {
|
||||
type,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
reply = (message: Message | string) => {
|
||||
if (typeof message === 'string') {
|
||||
message = new Message().setContent(message);
|
||||
}
|
||||
return this.ack(InteractionResponseType.Reply, {
|
||||
...message
|
||||
});
|
||||
};
|
||||
|
||||
edit = (message: Message | string) => {
|
||||
if (typeof message === 'string') {
|
||||
message = new Message().setContent(message);
|
||||
}
|
||||
if (!this.is_loading) {
|
||||
return this.ack(InteractionResponseType.UpdateMessage, {
|
||||
...message
|
||||
});
|
||||
} else {
|
||||
return this.client.api.patch(
|
||||
`/webhooks/${this.client.application_id}/${this.interaction.token}/messages/@original`,
|
||||
{
|
||||
type: InteractionResponseType.DeferredUpdateMessage,
|
||||
...message
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
deleteOriginal = () => {
|
||||
return this.client.api.delete(
|
||||
`/webhooks/${this.client.application_id}/${this.interaction.token}/messages/@original`
|
||||
);
|
||||
};
|
||||
|
||||
loading = async () => {
|
||||
await this.ack(InteractionResponseType.DeferredReply);
|
||||
return new InteractionContext({ client: this.client, interaction: this.interaction, is_loading: true });
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { Client } from "./client";
|
||||
|
||||
export class API {
|
||||
private readonly base_url: string = 'https://discord.com/api/v10';
|
||||
|
||||
constructor(private client: Client) {}
|
||||
|
||||
async request<T = any>(method: string, path: string, data?: any): Promise<T> {
|
||||
const url = `${this.base_url}${path}`;
|
||||
const headers = {
|
||||
Authorization: `Bot ${this.client.token}`,
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
console.log(`API Request: ${method} ${url}`);
|
||||
|
||||
const response = await fetch(url, {
|
||||
method,
|
||||
headers,
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error(`Failed to fetch: ${response.statusText} ${response.url} ${response.status}`);
|
||||
console.error(response.json());
|
||||
throw new Error(`Failed to fetch: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const body = await response.json();
|
||||
|
||||
console.log('API Response:', JSON.stringify(body, null, 4));
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
async get<T = any>(path: string): Promise<T> {
|
||||
return this.request('GET', path);
|
||||
}
|
||||
|
||||
async post<T = any>(path: string, data: any): Promise<T> {
|
||||
return this.request('POST', path, data);
|
||||
}
|
||||
|
||||
async patch<T = any>(path: string, data: any): Promise<T> {
|
||||
return this.request('PATCH', path, data);
|
||||
}
|
||||
|
||||
async delete<T = any>(path: string): Promise<T> {
|
||||
return this.request('DELETE', path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { Context } from "../types/context";
|
||||
import { exists } from "../utils";
|
||||
import type { API } from "./api";
|
||||
import type { Processor } from "./processor";
|
||||
|
||||
|
||||
export class ObjectCache<T> {
|
||||
cache = new Map<string, T>();
|
||||
private readonly type: string;
|
||||
|
||||
constructor(private api: API, processor: Processor, type: string, events: string[]) {
|
||||
this.type = type;
|
||||
processor.events.register(events, (_context: Context, e: Record<string, any>) => {
|
||||
this.cache.set(e.id, e as T);
|
||||
});
|
||||
}
|
||||
|
||||
set(object_id: string, object: T): void {
|
||||
this.cache.set(object_id, object);
|
||||
}
|
||||
|
||||
// update(object_id: string, object: Partial<T>): void {
|
||||
// const existing = this.cache.get(object_id);
|
||||
|
||||
// if (exists(existing)) {
|
||||
// // TODO: Idk wtf happens if this is called on two classes
|
||||
// this.cache.set(object_id, { ...existing, ...object });
|
||||
// } else {
|
||||
// this.cache.set(object_id, object as T);
|
||||
// }
|
||||
// }
|
||||
|
||||
async fetch(object_id: string): Promise<T> {
|
||||
return this.api.get(`${this.type}/${object_id}`).then((res) => {
|
||||
const object: T & { id: string } = res.data;
|
||||
this.cache.set(object.id, object);
|
||||
return object;
|
||||
});
|
||||
}
|
||||
|
||||
get(object_id: string): T | Promise<T> {
|
||||
if (this.cache.has(object_id)) {
|
||||
return Promise.resolve(this.cache.get(object_id)!);
|
||||
} else {
|
||||
return this.fetch(object_id);
|
||||
}
|
||||
}
|
||||
|
||||
list(): Map<string, T> {
|
||||
return this.cache;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { API } from './api';
|
||||
import { Gateway } from './gateway';
|
||||
import { Processor } from './processor';
|
||||
import { Intent, type ClientConfig } from '../types/common';
|
||||
import { exists, isNil } from '../utils/index';
|
||||
import type { Handler } from '../types/hander';
|
||||
import { Message } from '../types/message';
|
||||
import { Guilds } from './guilds';
|
||||
import { Users } from './users';
|
||||
|
||||
export class Client {
|
||||
readonly token: string;
|
||||
readonly application_id: string;
|
||||
readonly intents: Intent[] = [Intent.DEFAULT];
|
||||
prefix: string;
|
||||
// Internal services
|
||||
protected _api: API;
|
||||
protected _processor: Processor;
|
||||
protected _gateway: Gateway;
|
||||
guilds: Guilds;
|
||||
users: Users;
|
||||
|
||||
constructor(config: ClientConfig) {
|
||||
// Validate the configuration
|
||||
Client.validateConfig(config);
|
||||
// Save the configuration
|
||||
this.token = config.token;
|
||||
this.application_id = config.application_id;
|
||||
this.intents = config.intents ?? this.intents;
|
||||
this.prefix = config.prefix ?? '!';
|
||||
// Initialize internal services
|
||||
this._api = new API(this);
|
||||
this._processor = new Processor(this);
|
||||
this._gateway = new Gateway(this, this.processor, this.api);
|
||||
this.guilds = new Guilds(this.api, this.processor);
|
||||
this.users = new Users(this.api, this.processor);
|
||||
}
|
||||
|
||||
private get api() {
|
||||
return this._api;
|
||||
}
|
||||
|
||||
private get gateway() {
|
||||
return this._gateway;
|
||||
}
|
||||
|
||||
private get processor() {
|
||||
return this._processor;
|
||||
}
|
||||
|
||||
private static validateConfig(config: ClientConfig) {
|
||||
if (isNil(config.token)) {
|
||||
throw new Error('Token is required');
|
||||
}
|
||||
|
||||
if (isNil(config.application_id)) {
|
||||
throw new Error('Application ID is required');
|
||||
}
|
||||
|
||||
if (exists(config.prefix) && typeof config.prefix !== 'string') {
|
||||
throw new Error('Prefix must be a string');
|
||||
}
|
||||
|
||||
if (exists(config.intents)) {
|
||||
if (!Array.isArray(config.intents)) {
|
||||
throw new Error('Intents must be an array');
|
||||
}
|
||||
|
||||
for (const intent of config.intents) {
|
||||
if (!Intent[intent]) {
|
||||
throw new Error(`Invalid intent: ${intent}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage = async (channel_id: string, message: Message | string) => {
|
||||
if (typeof message === 'string') {
|
||||
message = new Message().setContent(message);
|
||||
}
|
||||
|
||||
return Message.fromAPIResponse(await this.api.post(`/channels/${channel_id}/messages`, message.toJSON()));
|
||||
}
|
||||
|
||||
chatCommands = {
|
||||
register: (name: string | string[], handler: Handler) => this.processor.commands.register(name, handler),
|
||||
unregister: (name: string | string[]) => this.processor.commands.unregister(name),
|
||||
has: (name: string) => this.processor.commands.has(name)
|
||||
}
|
||||
|
||||
connect = () => this.gateway.connect();
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
import { WebSocket, type Data } from 'ws';
|
||||
import type { Client } from "./client";
|
||||
import type { Payload } from "../types/common";
|
||||
import { exists, isNil, isObject, log } from "../utils";
|
||||
import type { API } from "./api";
|
||||
import type { Processor } from './processor';
|
||||
import { Context } from '../types/context';
|
||||
import { Message } from '../types/message';
|
||||
import { User } from '../types/user';
|
||||
|
||||
export class Gateway {
|
||||
private socket: WebSocket | null = null;
|
||||
private heartbeat_interval: number | null = null;
|
||||
private heartbeat_timeout: Timer | null = null;
|
||||
private sequence: number | null = null;
|
||||
// Internal state
|
||||
private user: any;
|
||||
|
||||
constructor(private client: Client, private processor: Processor, private api: API) {}
|
||||
|
||||
private onOpen = () => {
|
||||
log('Connected to gateway');
|
||||
}
|
||||
|
||||
private onMessage = (packet: Data) => {
|
||||
// console.log(packet);
|
||||
// Allocate a variable to store the parsed payload
|
||||
let data: Payload | null = null;
|
||||
|
||||
try {
|
||||
// Attempt to parse the packet data
|
||||
data = JSON.parse(packet.toString());
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
if (data) {
|
||||
log('='.repeat(25));
|
||||
log('received: ', data.op);
|
||||
log('received event: ', data.t ?? 'no event');
|
||||
log('received data: ', exists(data.d) ? Object.keys(data.d) : 'no data');
|
||||
log('='.repeat(25));
|
||||
// If the data is successfully parsed check the opcode
|
||||
const code = data.op;
|
||||
if (code === 0) {
|
||||
// Event received
|
||||
this.parseEvent(data);
|
||||
} else if (code === 1) {
|
||||
// Heartbeat requested
|
||||
this.send({ op: 1, d: this.sequence });
|
||||
} else if (code === 9) {
|
||||
// Reconnect requested
|
||||
} else if (code === 10) {
|
||||
// Hello
|
||||
if (!isObject(data.d)) {
|
||||
throw new Error('Invalid HELLO payload received');
|
||||
}
|
||||
// Store the heartbeat interval
|
||||
this.heartbeat_interval = data.d?.heartbeat_interval;
|
||||
// Kick off the heartbeat loop
|
||||
this.heartbeat();
|
||||
setTimeout(this.indentify, 2500);
|
||||
} else if (code === 11) {
|
||||
// Heartbeat ACK
|
||||
log('Heartbeat ACK');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private heartbeat() {
|
||||
log('Sending heartbeat');
|
||||
if (exists(this.heartbeat_interval)) {
|
||||
// If a previous heartbeat timeout exists, clear it
|
||||
if (exists(this.heartbeat_timeout)) {
|
||||
clearTimeout(this.heartbeat_timeout);
|
||||
}
|
||||
// Set a timeout to ensure the heartbeat is sent within the interval
|
||||
this.heartbeat_timeout = setTimeout(() => {
|
||||
this.send({ op: 1, d: this.sequence });
|
||||
this.heartbeat();
|
||||
}, this.heartbeat_interval * Math.random());
|
||||
} else {
|
||||
throw new Error('Heartbeat interval not set');
|
||||
}
|
||||
}
|
||||
|
||||
protected send = (payload: Partial<Payload>) => {
|
||||
if (isNil(this.socket)) {
|
||||
throw new Error('Socket is not connected');
|
||||
}
|
||||
|
||||
log('='.repeat(25));
|
||||
log('sending: ', JSON.stringify(payload, null, 4));
|
||||
log('='.repeat(25));
|
||||
|
||||
this.socket.send(JSON.stringify(payload));
|
||||
};
|
||||
|
||||
private indentify = () => {
|
||||
this.send({
|
||||
op: 2,
|
||||
d: {
|
||||
token: this.client.token,
|
||||
intents: this.client.intents.reduce((acc, intent) => acc | intent, 0),
|
||||
properties: {
|
||||
os: process.platform,
|
||||
browser: 'discord.ts',
|
||||
device: 'discord.ts'
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
private parseEvent(payload: Payload) {
|
||||
if (isNil(payload.t)) {
|
||||
throw new Error('Event name is required');
|
||||
}
|
||||
|
||||
log('Event:', payload.t);
|
||||
|
||||
const event_name = payload.t;
|
||||
const event_data = payload.d;
|
||||
|
||||
if (!isObject(event_data)) {
|
||||
throw new Error('Invalid event data received');
|
||||
}
|
||||
|
||||
if (event_name === 'READY') {
|
||||
this.user = event_data.user;
|
||||
log('User:', this.user);
|
||||
} else if (event_name === 'INTERACTION_CREATE') {
|
||||
// this.handleInteractionCreate(payload);
|
||||
} else if (event_name === 'MESSAGE_CREATE') {
|
||||
this.handleMessageCreate(payload as Payload);
|
||||
} else if (event_name === 'MESSAGE_UPDATE') {
|
||||
// this.handleMessageCreate(payload);
|
||||
}
|
||||
|
||||
// Allocate a var to store the context
|
||||
let context = null;
|
||||
|
||||
if (['MESSAGE_CREATE', 'MESSAGE_UPDATE'].includes(event_name)) {
|
||||
// If the event is a message event, create a message object
|
||||
const message = Message.fromAPIResponse(event_data);
|
||||
// And then generate the context
|
||||
context = new Context(this.client, this.api, message);
|
||||
}
|
||||
|
||||
// Call event handlers
|
||||
this.processor.events.execute(event_name, context, event_data);
|
||||
}
|
||||
|
||||
private handleMessageCreate = async (payload: Payload) => {
|
||||
if (!isObject(payload.d)) {
|
||||
throw new Error('Invalid MESSAGE_CREATE payload received');
|
||||
}
|
||||
|
||||
// Ignore bots
|
||||
if (payload.d.author.bot) {
|
||||
return;
|
||||
}
|
||||
// Ignore self
|
||||
if (payload.d.author.id === this.user.id) {
|
||||
return;
|
||||
}
|
||||
// Extract the author
|
||||
const author = User.fromAPIResponse(payload.d.author);
|
||||
// Store the author in the cache
|
||||
this.client.users.set(author.id, author);
|
||||
// Extract the content
|
||||
const content = payload.d.content.trim();
|
||||
// Is this a command?
|
||||
if (content.startsWith(this.client.prefix) || content.startsWith(`<@${this.user.id}>`)) {
|
||||
// Determine the prefix
|
||||
const prefix = content.startsWith(this.client.prefix) ? this.client.prefix : `<@${this.user.id}> `;
|
||||
// Parse command
|
||||
const args = content
|
||||
.slice(prefix.length)
|
||||
.trim()
|
||||
.split(/\s+(?=(?:[^\'"]*[\'"][^\'"]*[\'"])*[^\'"]*$)/g);
|
||||
// Ignore empty commands
|
||||
if (args.length === 0) {
|
||||
return;
|
||||
}
|
||||
// Extract the command name
|
||||
// TODO: Case insensitive config?
|
||||
const command = args.shift()!;
|
||||
// Create a new message object from the payload
|
||||
const message = Message.fromAPIResponse(payload.d);
|
||||
// Generate context
|
||||
const context = new Context(this.client, this.api, message);
|
||||
// Check if command exists
|
||||
if (this.processor.commands.has(command)) {
|
||||
// Execute the handler
|
||||
this.processor.commands.execute(command, context, args);
|
||||
} else {
|
||||
// TODO: Custom unknown command handler
|
||||
context.reply(`Unknown command: ${command}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async connect() {
|
||||
// Fetch the Discord gateway URL
|
||||
const response = await this.api.request('GET', '/gateway/bot');
|
||||
// Append the query parameters to the URL
|
||||
const url = new URL(response.url);
|
||||
url.searchParams.append('v', '10');
|
||||
url.searchParams.append('encoding', 'json');
|
||||
// Open a new WebSocket connection to the Discord gateway
|
||||
this.socket = new WebSocket(url);
|
||||
// Register event listeners
|
||||
this.socket.on('open', this.onOpen);
|
||||
this.socket.on('message', this.onMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { Guild } from "../types/guild";
|
||||
import type { API } from "./api";
|
||||
import { ObjectCache } from "./cache";
|
||||
import type { Processor } from "./processor";
|
||||
|
||||
export class Guilds extends ObjectCache<Guild> {
|
||||
constructor (api: API, processor: Processor) {
|
||||
super(api, processor, 'guilds', ['GUILD_CREATE', 'GUILD_UPDATE']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { Client } from "./client";
|
||||
import type { Context } from "../types/context";
|
||||
import { HandlerType, type EventPayload, type Payload } from "../types/common";
|
||||
import { BaseHandler, type Handler } from "../types/hander";
|
||||
|
||||
export class Processor {
|
||||
private handlers = {
|
||||
[HandlerType.Events]: new Map<string, Handler[]>(),
|
||||
[HandlerType.Commands]: new Map<string, Handler[]>(),
|
||||
[HandlerType.Interactions]: new Map<string, Handler[]>()
|
||||
};
|
||||
|
||||
constructor(private client: Client) {};
|
||||
|
||||
public register(type: HandlerType, event: string | string[], handler: Handler) {
|
||||
// Normalize events into an array
|
||||
const events = Array.isArray(event) ? event : [event];
|
||||
// Register the handler for each event
|
||||
for (const e of events) {
|
||||
const handlers = this.handlers[type].get(e) || [];
|
||||
handlers.push(handler);
|
||||
this.handlers[type].set(e, handlers);
|
||||
}
|
||||
};
|
||||
|
||||
public unregister(type: HandlerType, event: string | string[], handler?: Handler) {
|
||||
// Normalize events into an array
|
||||
const events = Array.isArray(event) ? event : [event];
|
||||
// Unregister the handler for each event
|
||||
for (const e of events) {
|
||||
if (handler) {
|
||||
// Get the handlers for the event
|
||||
const handlers = this.handlers[type].get(e);
|
||||
if (handlers) {
|
||||
// Find the index of the handler
|
||||
const index = handlers.indexOf(handler);
|
||||
if (index !== -1) {
|
||||
// Remove the handler from the array
|
||||
handlers.splice(index, 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If no handler is provided, clear all handlers for the event
|
||||
this.handlers[type].clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public execute(type: HandlerType.Events, event: string, context: Context | null, payload: EventPayload): void
|
||||
public execute(type: Omit<HandlerType, HandlerType.Events>, event: string, context: Context, args: any[]): void
|
||||
public execute(type: HandlerType, event: string, context: Context | null, argsOrPayload: any[] | EventPayload) {
|
||||
const handlers = this.handlers[type].get(event);
|
||||
|
||||
if (handlers) {
|
||||
for (const handler of handlers) {
|
||||
if (handler instanceof BaseHandler) {
|
||||
handler.execute(context, argsOrPayload);
|
||||
} else {
|
||||
// TODO: Fix ts error
|
||||
handler(context, argsOrPayload);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HandlerType.Events] = {
|
||||
register: (event: string | string[], handler: Handler) => this.register(HandlerType.Events, event, handler),
|
||||
unregister: (event: string | string[], handler?: Handler) => this.unregister(HandlerType.Events, event, handler),
|
||||
execute: (event: string, context: Context | null, payload: EventPayload) => this.execute(HandlerType.Events, event, context, payload),
|
||||
has: (event: string) => this.handlers[HandlerType.Events].has(event) && this.handlers[HandlerType.Events].get(event)?.length! > 0
|
||||
};
|
||||
|
||||
[HandlerType.Commands] = {
|
||||
register: (event: string | string[], handler: Handler) => this.register(HandlerType.Commands, event, handler),
|
||||
unregister: (event: string | string[], handler?: Handler) => this.unregister(HandlerType.Commands, event, handler),
|
||||
execute: (event: string, context: Context, args: any[]) => this.execute(HandlerType.Commands, event, context, args),
|
||||
has: (event: string) => this.handlers[HandlerType.Commands].has(event) ?? this.handlers[HandlerType.Commands].get(event)?.length! > 0
|
||||
};
|
||||
|
||||
[HandlerType.Interactions] = {
|
||||
register: (event: string | string[], handler: Handler) => this.register(HandlerType.Interactions, event, handler),
|
||||
unregister: (event: string | string[], handler?: Handler) => this.unregister(HandlerType.Interactions, event, handler),
|
||||
execute: (event: string, context: Context, args: any[]) => this.execute(HandlerType.Interactions, event, context, args),
|
||||
has: (event: string) => this.handlers[HandlerType.Interactions].has(event) ?? this.handlers[HandlerType.Interactions].get(event)?.length! > 0
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import type { User } from "../types/user";
|
||||
import type { API } from "./api";
|
||||
import { ObjectCache } from "./cache";
|
||||
import type { Processor } from "./processor";
|
||||
|
||||
export class Users extends ObjectCache<User> {
|
||||
constructor (api: API, processor: Processor) {
|
||||
super(api, processor, 'users', []);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
export class Channel {
|
||||
version: number;
|
||||
type: number;
|
||||
position: number;
|
||||
permission_overwrites: any[];
|
||||
name: string;
|
||||
id: string;
|
||||
flags: number;
|
||||
topic?: string | null;
|
||||
rate_limit_per_user?: number;
|
||||
parent_id?: string;
|
||||
last_message_id?: string | null;
|
||||
user_limit?: number;
|
||||
rtc_region?: string | null;
|
||||
bitrate?: number;
|
||||
last_pin_timestamp?: string;
|
||||
|
||||
constructor(data: any) {
|
||||
this.version = data.version;
|
||||
this.type = data.type;
|
||||
this.position = data.position;
|
||||
this.permission_overwrites = data.permission_overwrites;
|
||||
this.name = data.name;
|
||||
this.id = data.id;
|
||||
this.flags = data.flags;
|
||||
this.topic = data.topic;
|
||||
this.rate_limit_per_user = data.rate_limit_per_user;
|
||||
this.parent_id = data.parent_id;
|
||||
this.last_message_id = data.last_message_id;
|
||||
this.user_limit = data.user_limit;
|
||||
this.rtc_region = data.rtc_region;
|
||||
this.bitrate = data.bitrate;
|
||||
this.last_pin_timestamp = data.last_pin_timestamp;
|
||||
}
|
||||
|
||||
static fromAPIResponse(data: any): Channel {
|
||||
return new Channel(data);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import { Client } from '..';
|
||||
import { Context } from '../lib';
|
||||
|
||||
export abstract class Command {
|
||||
readonly name: string;
|
||||
readonly description: string;
|
||||
readonly execute: CommandFunction;
|
||||
constructor(data: Command) {
|
||||
this.name = data.name;
|
||||
this.description = data.description;
|
||||
this.execute = data.execute;
|
||||
}
|
||||
}
|
||||
|
||||
export type CommandFunction = (client: Client, context: Context, ...args: any[]) => void;
|
||||
|
||||
export class ChatCommand extends Command {
|
||||
constructor(data: ChatCommand) {
|
||||
super(data);
|
||||
}
|
||||
}
|
||||
+35
-76
@@ -1,81 +1,40 @@
|
||||
import type { Client } from "../services/client";
|
||||
import type { Context } from "./context";
|
||||
import type { BaseHandler } from "./hander";
|
||||
|
||||
export type ClientConfig = {
|
||||
token: string;
|
||||
application_id: string;
|
||||
prefix?: string;
|
||||
intents?: Intent[];
|
||||
}
|
||||
export enum HandlerType {
|
||||
Events = 'events',
|
||||
Commands = 'commands',
|
||||
Interactions = 'interactions'
|
||||
}
|
||||
|
||||
|
||||
export type EventPayload = any;
|
||||
|
||||
export type Primitive = string | number | boolean | null;
|
||||
|
||||
/**
|
||||
* Represents a payload structure used in the Discord API.
|
||||
*
|
||||
* @typedef {Object} Payload
|
||||
* @property {number} op - The opcode of the payload.
|
||||
* @property {Record<string, any>} d - The data associated with the payload.
|
||||
* @property {number | null} s - The sequence number, or null if not applicable.
|
||||
* @property {string | null} t - The event name, or null if not applicable.
|
||||
*/
|
||||
export type Payload = {
|
||||
op: number; // opcode
|
||||
d: Record<string, any>; // data
|
||||
s: number | null; // sequence number
|
||||
t: string | null; // event name
|
||||
op: number;
|
||||
d: Record<string, any> | Primitive | null;
|
||||
s: number | null;
|
||||
t: string | null;
|
||||
};
|
||||
|
||||
export type Session = {
|
||||
s: number; // sequence number
|
||||
session_id: string;
|
||||
session_type: string;
|
||||
resume_gateway_url: string;
|
||||
};
|
||||
|
||||
export type Guild = Record<string, any>;
|
||||
|
||||
export type User = Record<string, any>;
|
||||
|
||||
export enum Status {
|
||||
Online = "online",
|
||||
DND = "dnd",
|
||||
Idle = "idle",
|
||||
Invisible = "invisible",
|
||||
Offline = "offline"
|
||||
}
|
||||
|
||||
export type Presence = {
|
||||
since: number | null;
|
||||
activities: Activity[];
|
||||
status: Status;
|
||||
afk: boolean;
|
||||
}
|
||||
|
||||
export type Activity = {
|
||||
name: string;
|
||||
type: number;
|
||||
created_at: number;
|
||||
url?: string;
|
||||
timestamps?: {
|
||||
start?: number;
|
||||
end?: number;
|
||||
};
|
||||
application_id?: string;
|
||||
details?: string;
|
||||
state?: string;
|
||||
emoji?: {
|
||||
name: string;
|
||||
id?: string;
|
||||
animated?: boolean;
|
||||
};
|
||||
party?: {
|
||||
id?: string;
|
||||
size?: [number, number];
|
||||
};
|
||||
assets?: {
|
||||
large_image?: string;
|
||||
large_text?: string;
|
||||
small_image?: string;
|
||||
small_text?: string;
|
||||
};
|
||||
secrets?: {
|
||||
join?: string;
|
||||
spectate?: string;
|
||||
match?: string;
|
||||
};
|
||||
instance?: boolean;
|
||||
flags?: number;
|
||||
}
|
||||
|
||||
export enum ActivityType {
|
||||
Game,
|
||||
Streaming,
|
||||
Listening,
|
||||
Watching,
|
||||
Custom ,
|
||||
Competing
|
||||
}
|
||||
|
||||
export enum Intent {
|
||||
GUILDS = 1 << 0,
|
||||
GUILD_MEMBERS = 1 << 1,
|
||||
@@ -97,7 +56,7 @@ export enum Intent {
|
||||
ALL = 32767
|
||||
}
|
||||
|
||||
export const IntentGroup : {[key in Intent]: [string[], number]} = {
|
||||
export const IntentGroup : {[key in Intent]?: [string[], number]} = {
|
||||
[Intent.GUILDS]: [[
|
||||
"GUILD_CREATE",
|
||||
"GUILD_UPDATE",
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
import type { API } from "../services/api";
|
||||
import type { Client } from "../services/client";
|
||||
import { isNil } from "../utils";
|
||||
import { Message } from "./message";
|
||||
/**
|
||||
* Represents the context of a message in a Discord channel.
|
||||
* Provides methods to interact with the message and the channel it was sent in.
|
||||
*
|
||||
* @class Context
|
||||
*
|
||||
* @property {string} message_id - The ID of the message that this context is associated with.
|
||||
* @property {string} channel_id - The ID of the channel where the message was sent.
|
||||
* @property {string} [guild_id] - The ID of the guild where the message was sent, if applicable.
|
||||
*
|
||||
* @constructor
|
||||
* @param {Client} client - The client instance that is interacting with the Discord API.
|
||||
* @param {API} api - The API instance used to make requests to the Discord API.
|
||||
* @param {Message} message - The message object that this context is based on.
|
||||
*
|
||||
* @throws {Error} If the message ID is not provided.
|
||||
* @throws {Error} If the channel ID is not provided.
|
||||
*/
|
||||
export class Context {
|
||||
// Public properties
|
||||
message_id: string;
|
||||
channel_id: string;
|
||||
guild_id?: string;
|
||||
|
||||
constructor(private client: Client, private api: API, private _message: Message) {
|
||||
if (isNil(_message.id)) {
|
||||
throw new Error('Message ID is required to create a context');
|
||||
}
|
||||
|
||||
if (isNil(_message.channel_id)) {
|
||||
throw new Error('Channel ID is required to create a context');
|
||||
}
|
||||
|
||||
this.client = client;
|
||||
this.message_id = _message.id;
|
||||
this.channel_id = _message.channel_id;
|
||||
this.guild_id = _message.guild_id;
|
||||
}
|
||||
|
||||
// get author() {
|
||||
// return this.client.users.get(this.author_id);
|
||||
// };
|
||||
|
||||
get message(): Message & { getReference: () => Promise<Message | null> } {
|
||||
return Object.assign(this._message, {
|
||||
getReference: async () => {
|
||||
const reference = this._message.message_reference;
|
||||
if (isNil(reference)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return Message.fromAPIResponse(await this.api.get(`/channels/${reference.channel_id}/messages/${reference.message_id}`));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fetchGuild() {
|
||||
if (isNil(this.guild_id)) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
return this.client.guilds.get(this.guild_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply to the message that this command
|
||||
* @param message The message to send to the channel
|
||||
* @param reference If true the message will be a reference the the message that triggered the command
|
||||
* @returns The message that was sent
|
||||
*/
|
||||
public reply = (message: Message | string, reference: boolean = true) => {
|
||||
if (typeof message === 'string') {
|
||||
message = new Message().setContent(message);
|
||||
}
|
||||
|
||||
if (reference) {
|
||||
message.message_reference = {
|
||||
message_id: this.message_id,
|
||||
channel_id: this.channel_id,
|
||||
guild_id: this.guild_id
|
||||
};
|
||||
}
|
||||
|
||||
return this.client.sendMessage(this.channel_id, message);
|
||||
};
|
||||
|
||||
/**
|
||||
* @name sendMessage
|
||||
* @description Sends a message to the channel that this context is associated with.
|
||||
* @param {Message | string} message - The message to send to the channel.
|
||||
* @returns {Promise<Message>} The message that was sent.
|
||||
*/
|
||||
public sendMessage = (message: Message | string) => {
|
||||
if (typeof message === 'string') {
|
||||
message = new Message().setContent(message);
|
||||
}
|
||||
return this.client.sendMessage(this.channel_id, message);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @name delete
|
||||
* @description Deletes a message in the channel that this context is associated with.
|
||||
* @param {string} [message_id] - The ID of the message to delete. If not provided, the message ID of this context will be used.
|
||||
* @returns {Promise<void>} A promise that resolves when the message is deleted.
|
||||
*/
|
||||
public delete = (message_id?: string) => {
|
||||
return this.api.delete(`/channels/${this.channel_id}/messages/${message_id ?? this.message_id}`);
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @name edit
|
||||
* @description Edits a message in the channel that this context is associated with.
|
||||
* @param {Message | string} message - The updated message content.
|
||||
* @returns {Promise<Message>} The updated message.
|
||||
*/
|
||||
public edit = async (message: Message | string) => {
|
||||
if (typeof message === 'string') {
|
||||
message = new Message().setContent(message);
|
||||
}
|
||||
return Message.fromAPIResponse(await this.api.patch(`/channels/${this.channel_id}/messages/${this.message_id}`, message));
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Message } from '.';
|
||||
import type { Message } from './message';
|
||||
|
||||
type EmbedFooter = {
|
||||
text: string;
|
||||
@@ -0,0 +1,25 @@
|
||||
export class Emoji {
|
||||
version: number;
|
||||
roles: string[];
|
||||
require_colons: boolean;
|
||||
name: string;
|
||||
managed: boolean;
|
||||
id: string;
|
||||
available: boolean;
|
||||
animated: boolean;
|
||||
|
||||
constructor(data: any) {
|
||||
this.version = data.version;
|
||||
this.roles = data.roles;
|
||||
this.require_colons = data.require_colons;
|
||||
this.name = data.name;
|
||||
this.managed = data.managed;
|
||||
this.id = data.id;
|
||||
this.available = data.available;
|
||||
this.animated = data.animated;
|
||||
}
|
||||
|
||||
static fromAPIResponse(data: any): Emoji {
|
||||
return new Emoji(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import { Channel } from "./channel";
|
||||
import { Emoji } from "./emoji";
|
||||
import { Member } from "./member";
|
||||
import { Role } from "./role";
|
||||
|
||||
export class Guild {
|
||||
system_channel_id: string | null;
|
||||
premium_progress_bar_enabled: boolean;
|
||||
region: string;
|
||||
application_id: string | null;
|
||||
premium_tier: number;
|
||||
presences: any[];
|
||||
discovery_splash: string | null;
|
||||
latest_onboarding_question_id: string | null;
|
||||
unavailable: boolean;
|
||||
afk_channel_id: string | null;
|
||||
large: boolean;
|
||||
nsfw: boolean;
|
||||
max_video_channel_users: number;
|
||||
stickers: any[];
|
||||
id: string;
|
||||
premium_subscription_count: number;
|
||||
stage_instances: any[];
|
||||
preferred_locale: string;
|
||||
guild_scheduled_events: any[];
|
||||
member_count: number;
|
||||
name: string;
|
||||
default_message_notifications: number;
|
||||
system_channel_flags: number;
|
||||
hub_type: string | null;
|
||||
lazy: boolean;
|
||||
soundboard_sounds: any[];
|
||||
threads: any[];
|
||||
banner: string | null;
|
||||
description: string | null;
|
||||
clan: string | null;
|
||||
incidents_data: string | null;
|
||||
application_command_counts: any;
|
||||
embedded_activities: any[];
|
||||
icon: string | null;
|
||||
home_header: string | null;
|
||||
joined_at: string;
|
||||
safety_alerts_channel_id: string | null;
|
||||
verification_level: number;
|
||||
max_stage_video_channel_users: number;
|
||||
emojis: Emoji[];
|
||||
owner_id: string;
|
||||
public_updates_channel_id: string | null;
|
||||
features: string[];
|
||||
rules_channel_id: string | null;
|
||||
roles: Role[];
|
||||
afk_timeout: number;
|
||||
inventory_settings: any;
|
||||
activity_instances: any[];
|
||||
max_members: number;
|
||||
channels: Channel[];
|
||||
voice_states: any[];
|
||||
splash: string | null;
|
||||
mfa_level: number;
|
||||
members: Member[];
|
||||
version: number;
|
||||
vanity_url_code: string | null;
|
||||
nsfw_level: number;
|
||||
explicit_content_filter: number;
|
||||
|
||||
constructor(data: any) {
|
||||
this.system_channel_id = data.system_channel_id;
|
||||
this.premium_progress_bar_enabled = data.premium_progress_bar_enabled;
|
||||
this.region = data.region;
|
||||
this.application_id = data.application_id;
|
||||
this.premium_tier = data.premium_tier;
|
||||
this.presences = data.presences;
|
||||
this.discovery_splash = data.discovery_splash;
|
||||
this.latest_onboarding_question_id = data.latest_onboarding_question_id;
|
||||
this.unavailable = data.unavailable;
|
||||
this.afk_channel_id = data.afk_channel_id;
|
||||
this.large = data.large;
|
||||
this.nsfw = data.nsfw;
|
||||
this.max_video_channel_users = data.max_video_channel_users;
|
||||
this.stickers = data.stickers;
|
||||
this.id = data.id;
|
||||
this.premium_subscription_count = data.premium_subscription_count;
|
||||
this.stage_instances = data.stage_instances;
|
||||
this.preferred_locale = data.preferred_locale;
|
||||
this.guild_scheduled_events = data.guild_scheduled_events;
|
||||
this.member_count = data.member_count;
|
||||
this.name = data.name;
|
||||
this.default_message_notifications = data.default_message_notifications;
|
||||
this.system_channel_flags = data.system_channel_flags;
|
||||
this.hub_type = data.hub_type;
|
||||
this.lazy = data.lazy;
|
||||
this.soundboard_sounds = data.soundboard_sounds;
|
||||
this.threads = data.threads;
|
||||
this.banner = data.banner;
|
||||
this.description = data.description;
|
||||
this.clan = data.clan;
|
||||
this.incidents_data = data.incidents_data;
|
||||
this.application_command_counts = data.application_command_counts;
|
||||
this.embedded_activities = data.embedded_activities;
|
||||
this.icon = data.icon;
|
||||
this.home_header = data.home_header;
|
||||
this.joined_at = data.joined_at;
|
||||
this.safety_alerts_channel_id = data.safety_alerts_channel_id;
|
||||
this.verification_level = data.verification_level;
|
||||
this.max_stage_video_channel_users = data.max_stage_video_channel_users;
|
||||
this.emojis = data.emojis.map((emoji: any) => Emoji.fromAPIResponse(emoji));
|
||||
this.owner_id = data.owner_id;
|
||||
this.public_updates_channel_id = data.public_updates_channel_id;
|
||||
this.features = data.features;
|
||||
this.rules_channel_id = data.rules_channel_id;
|
||||
this.roles = data.roles.map((role: any) => Role.fromAPIResponse(role));
|
||||
this.afk_timeout = data.afk_timeout;
|
||||
this.inventory_settings = data.inventory_settings;
|
||||
this.activity_instances = data.activity_instances;
|
||||
this.max_members = data.max_members;
|
||||
this.channels = data.channels.map((channel: any) => Channel.fromAPIResponse(channel));
|
||||
this.voice_states = data.voice_states;
|
||||
this.splash = data.splash;
|
||||
this.mfa_level = data.mfa_level;
|
||||
this.members = data.members.map((member: any) => Member.fromAPIResponse(member));
|
||||
this.version = data.version;
|
||||
this.vanity_url_code = data.vanity_url_code;
|
||||
this.nsfw_level = data.nsfw_level;
|
||||
this.explicit_content_filter = data.explicit_content_filter;
|
||||
}
|
||||
|
||||
static fromAPIResponse(data: any): Guild {
|
||||
return new Guild(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { EventPayload } from "./common";
|
||||
import type { Context } from "./context";
|
||||
|
||||
export class BaseHandler {
|
||||
constructor() {};
|
||||
|
||||
public execute(context: Context | null, args: any[]) {};
|
||||
}
|
||||
|
||||
export type CommandHandlerFunction = (context: Context, args: any[]) => void;
|
||||
export type EventHandlerFunction = (context: Context | null, payload: EventPayload) => void;
|
||||
|
||||
export type HandlerFunction = CommandHandlerFunction | EventHandlerFunction;
|
||||
export type Handler = CommandHandlerFunction | EventHandlerFunction | BaseHandler;
|
||||
@@ -1,8 +0,0 @@
|
||||
import { Client, Context } from '..';
|
||||
|
||||
export abstract class Handler {
|
||||
abstract events: string | string[];
|
||||
abstract execute: HandlerFunction;
|
||||
}
|
||||
|
||||
export type HandlerFunction = (client: Client, context: Context, data: Record<string, any>) => void;
|
||||
@@ -1,5 +0,0 @@
|
||||
export * from './common';
|
||||
export * from './command';
|
||||
export * from './slash_command';
|
||||
export * from './handler';
|
||||
export * from './interaction';
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Client } from '..';
|
||||
import { InteractionContext } from '../lib';
|
||||
|
||||
export abstract class Interaction {
|
||||
abstract custom_id: string | string[];
|
||||
abstract execute: InteractionFunction;
|
||||
}
|
||||
|
||||
export type InteractionFunction = (client: Client, context: InteractionContext, data: Record<string, any>) => void;
|
||||
|
||||
export enum InteractionResponseType {
|
||||
Pong = 1,
|
||||
Reply = 4,
|
||||
DeferredReply = 5,
|
||||
DeferredUpdateMessage = 6,
|
||||
UpdateMessage = 7,
|
||||
AutocompleteResult = 8,
|
||||
Modal = 9
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
export class Member {
|
||||
user: any;
|
||||
roles: string[];
|
||||
premium_since: string | null;
|
||||
pending: boolean;
|
||||
nick: string | null;
|
||||
mute: boolean;
|
||||
joined_at: string;
|
||||
flags: number;
|
||||
deaf: boolean;
|
||||
communication_disabled_until: string | null;
|
||||
banner: string | null;
|
||||
avatar: string | null;
|
||||
|
||||
constructor(data: any) {
|
||||
this.user = data.user;
|
||||
this.roles = data.roles;
|
||||
this.premium_since = data.premium_since;
|
||||
this.pending = data.pending;
|
||||
this.nick = data.nick;
|
||||
this.mute = data.mute;
|
||||
this.joined_at = data.joined_at;
|
||||
this.flags = data.flags;
|
||||
this.deaf = data.deaf;
|
||||
this.communication_disabled_until = data.communication_disabled_until;
|
||||
this.banner = data.banner;
|
||||
this.avatar = data.avatar;
|
||||
}
|
||||
|
||||
static fromAPIResponse(data: any): Member {
|
||||
return new Member(data);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Embed } from '.';
|
||||
import { isNil } from '../utils';
|
||||
import { Embed } from './embed';
|
||||
import { User } from './user';
|
||||
|
||||
export type Component = Partial<Button | SelectMenu> & {
|
||||
type: ComponentType;
|
||||
@@ -70,24 +72,50 @@ export enum ComponentType {
|
||||
ActionRow = 1,
|
||||
Button,
|
||||
SelectMenu,
|
||||
TextInput
|
||||
TextInput,
|
||||
UserSelect,
|
||||
RoleSelect,
|
||||
MentionableSelect,
|
||||
ChannelSelect
|
||||
}
|
||||
|
||||
export class Message {
|
||||
id?: string;
|
||||
author?: User;
|
||||
channel_id?: string;
|
||||
guild_id?: string;
|
||||
public content?: string;
|
||||
public embeds: Embed[] = [];
|
||||
public components: Component[] = [];
|
||||
public message_reference?: {
|
||||
message_id?: string;
|
||||
channel_id?: string;
|
||||
type?: number;
|
||||
message_id: string;
|
||||
channel_id: string;
|
||||
guild_id?: string;
|
||||
fail_if_not_exists?: boolean;
|
||||
};
|
||||
|
||||
constructor() {}
|
||||
constructor(data?: Partial<Message>) {
|
||||
if (data) {
|
||||
this.id = data.id;
|
||||
this.author = User.fromAPIResponse(data.author);
|
||||
this.channel_id = data.channel_id;
|
||||
this.guild_id = data.guild_id;
|
||||
this.content = data.content;
|
||||
this.message_reference = data.message_reference;
|
||||
}
|
||||
}
|
||||
|
||||
public static fromAPIResponse = (data: any) => {
|
||||
return new Message({
|
||||
id: data.id,
|
||||
author: data.author,
|
||||
channel_id: data.channel_id,
|
||||
guild_id: data.guild_id,
|
||||
content: data.content,
|
||||
message_reference: data.message_reference
|
||||
});
|
||||
}
|
||||
|
||||
public static button(config: Button): Component {
|
||||
return {
|
||||
@@ -115,7 +143,6 @@ export class Message {
|
||||
|
||||
public addComponent = (content: Component | string, ...components: Component[]) => {
|
||||
if (typeof content === 'string') {
|
||||
console.log('string');
|
||||
this.components.push({
|
||||
type: ComponentType.ActionRow,
|
||||
content,
|
||||
@@ -131,6 +158,10 @@ export class Message {
|
||||
};
|
||||
|
||||
public setReference = (message: Partial<Message>) => {
|
||||
if (isNil(message.id) || isNil(message.channel_id)) {
|
||||
throw new Error('Message ID and Channel ID are required');
|
||||
}
|
||||
|
||||
this.message_reference = {
|
||||
message_id: message.id,
|
||||
channel_id: message.channel_id,
|
||||
@@ -0,0 +1,35 @@
|
||||
export class Role {
|
||||
version: number;
|
||||
unicode_emoji: string | null;
|
||||
tags: any;
|
||||
position: number;
|
||||
permissions: string;
|
||||
name: string;
|
||||
mentionable: boolean;
|
||||
managed: boolean;
|
||||
id: string;
|
||||
icon: string | null;
|
||||
hoist: boolean;
|
||||
flags: number;
|
||||
color: number;
|
||||
|
||||
constructor(data: any) {
|
||||
this.version = data.version;
|
||||
this.unicode_emoji = data.unicode_emoji;
|
||||
this.tags = data.tags;
|
||||
this.position = data.position;
|
||||
this.permissions = data.permissions;
|
||||
this.name = data.name;
|
||||
this.mentionable = data.mentionable;
|
||||
this.managed = data.managed;
|
||||
this.id = data.id;
|
||||
this.icon = data.icon;
|
||||
this.hoist = data.hoist;
|
||||
this.flags = data.flags;
|
||||
this.color = data.color;
|
||||
}
|
||||
|
||||
static fromAPIResponse(data: any): Role {
|
||||
return new Role(data);
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
import { Client } from '..';
|
||||
import { InteractionContext } 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 class SlashCommand {
|
||||
type: SlashCommandType;
|
||||
name: string;
|
||||
description: string;
|
||||
options: SlashCommandOption[];
|
||||
guild_id?: string;
|
||||
dm_permission?: boolean;
|
||||
default_member_permissions?: string;
|
||||
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 SlashCommandFunction = (client: Client, context: InteractionContext, ...args: any[]) => void;
|
||||
@@ -0,0 +1,79 @@
|
||||
// id snowflake the user's id identify
|
||||
// username string the user's username, not unique across the platform identify
|
||||
// discriminator string the user's Discord-tag identify
|
||||
// global_name ?string the user's display name, if it is set. For bots, this is the application name identify
|
||||
// avatar ?string the user's avatar hash identify
|
||||
// bot? boolean whether the user belongs to an OAuth2 application identify
|
||||
// system? boolean whether the user is an Official Discord System user (part of the urgent message system) identify
|
||||
// mfa_enabled? boolean whether the user has two factor enabled on their account identify
|
||||
// banner? ?string the user's banner hash identify
|
||||
// accent_color? ?integer the user's banner color encoded as an integer representation of hexadecimal color code identify
|
||||
// locale? string the user's chosen language option identify
|
||||
// verified? boolean whether the email on this account has been verified email
|
||||
// email? ?string the user's email email
|
||||
// flags? integer the flags on a user's account identify
|
||||
// premium_type? integer the type of Nitro subscription on a user's account identify
|
||||
// public_flags? integer the public flags on a user's account identify
|
||||
// avatar_decoration_data?
|
||||
|
||||
export class User {
|
||||
id: string;
|
||||
username: string;
|
||||
discriminator: string;
|
||||
global_name: string;
|
||||
avatar: string;
|
||||
bot?: boolean;
|
||||
system?: boolean;
|
||||
mfa_enabled?: boolean;
|
||||
banner?: string;
|
||||
accent_color?: number;
|
||||
locale?: string;
|
||||
verified?: boolean;
|
||||
email?: string;
|
||||
flags?: number;
|
||||
premium_type?: number;
|
||||
public_flags?: number;
|
||||
avatar_decoration_data?: Record<string, any>;
|
||||
|
||||
constructor(data: User) {
|
||||
this.id = data.id;
|
||||
this.username = data.username;
|
||||
this.discriminator = data.discriminator;
|
||||
this.global_name = data.global_name;
|
||||
this.avatar = data.avatar;
|
||||
this.bot = data.bot;
|
||||
this.system = data.system;
|
||||
this.mfa_enabled = data.mfa_enabled;
|
||||
this.banner = data.banner;
|
||||
this.accent_color = data.accent_color;
|
||||
this.locale = data.locale;
|
||||
this.verified = data.verified;
|
||||
this.email = data.email;
|
||||
this.flags = data.flags;
|
||||
this.premium_type = data.premium_type;
|
||||
this.public_flags = data.public_flags;
|
||||
this.avatar_decoration_data = data.avatar_decoration_data;
|
||||
}
|
||||
|
||||
static fromAPIResponse(data: any): User {
|
||||
return new User({
|
||||
id: data.id,
|
||||
username: data.username,
|
||||
discriminator: data.discriminator,
|
||||
global_name: data.global_name,
|
||||
avatar: data.avatar,
|
||||
bot: data.bot,
|
||||
system: data.system,
|
||||
mfa_enabled: data.mfa_enabled,
|
||||
banner: data.banner,
|
||||
accent_color: data.accent_color,
|
||||
locale: data.locale,
|
||||
verified: data.verified,
|
||||
email: data.email,
|
||||
flags: data.flags,
|
||||
premium_type: data.premium_type,
|
||||
public_flags: data.public_flags,
|
||||
avatar_decoration_data: data.avatar_decoration_data
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export const isNil = (value: any): value is null | undefined => value === null || value === undefined;
|
||||
export const exists = <T>(value: T | null | undefined): value is T => !isNil(value);
|
||||
export const isObject = (value: any): value is Record<string, any> => typeof value === 'object' && !Array.isArray(value);
|
||||
export const log = (...args: any[]) => console.log('\x1b[35m[discord.ts]\x1b[0m', ...args);
|
||||
Reference in New Issue
Block a user