diff --git a/README.md b/README.md
index 1deca27..c97de53 100644
--- a/README.md
+++ b/README.md
@@ -3,22 +3,28 @@
Build good bots fast
-> This package is in an early Alpha stage and not yet ready for consumption
+> This package is in an early beta stage and not yet ready for consumption
A solid and simple Discord API wrapper.
## Installation
+
+Install with your package manager of choice.
+
```
-yarn add @jaronp/discord.ts
+npm install discord.ts
+yarn add discord.ts
+bun add discord.ts
```
## Quick Start
+
A small example implementation.
-```ts
-import { Client } from '@jaronp/discord.ts';
+```typescript
+import { Client, type Context } from '`discord.ts';
// Initialize and configure
-const bot = new Client({
+const client = new Client({
token: '',
application_id: '',
intents: [
@@ -26,26 +32,105 @@ const bot = new Client({
Intent.GUILD_MESSAGES,
Intent.GUILD_MESSAGE_REACTIONS,
Intent.MESSAGE_CONTENT
- ]
+ ],
+ prefix: '??'
});
-// Register a listener
-bot.handlers.register('READY', (client, context, data) => {
- console.log(`Logged in as ${client.user.tag}`);
+// Register arbitrary event handler to the client bus
+client.on('READY', async (_, payload) => {
+ console.log(`Logged in as ${payload.user.username}${payload.user.discriminator?.length > 0 ? `#${payload.user.discriminator}` : ''}`);
});
// Register a command
-bot.commands.register('ping', (client, context, args) => {
+client.chatCommands.register('ping', (context: Context, args) => {
context.reply('Pong!', true);
});
// Connect
-bot.connect();
+client.connect();
```
+## Type Resolution and Saftey
+
+This library is designed to be type safe and resolve types for you. Because of this you can write code that is both safe and easy to understand. With the `createCommandHandler` utility you can provide types for command arguments. The client will then check and resolve them for you.
+
+```typescript
+import { createCommandHandler, CommandOptionType, type Context } from 'discord.ts';
+
+// <...>
+
+// Define the command handler with the utility function
+const avatarCommand = createCommandHandler({
+ description: 'Get a users avatar',
+ args: [
+ {
+ id: 'user',
+ name: 'user',
+ type: CommandOptionType.User,
+ description: 'The user to get the avatar for',
+ required: true
+ }
+ ] as const,
+ execute: async (context: Context, args) => {
+ // Args will be typed here as { user: User }
+ context.reply(args.user.avatarURL);
+ }
+});
+
+// You can then register the command with the client
+// The class generated can be used for both chat and application commands
+client.chatCommands.register('avatar', avatarCommand);
+client.applicationCommands.register('avatar', ApplicationCommandType.Chat, avatarCommand);
+
+// <...>
+```
+
+> NOTE: The `as const` is required to ensure the types are resolved correctly. This is to work around some TypeScript weirdness.
+
+## Hydration
+
+Hydration allows you to fetch and fill data on Discord objects. This is useful for when you need to fetch additional data about an object.
+
+```typescript
+client.chatCommands.register('ping', async (context: Context, args: any[]) => {
+ // Hydrate the message id from the context
+ const { message } = await context.hydrate(context, [Expectation.Message]);
+ // Send a message to the channel
+ const response = await context.reply(`Pong!: got ${args.join(', ')}`, true);
+ // Calculate the time it took to send the message
+ const time = message.timestamp.getTime() - message.timestamp.getTime();
+ // Edit the message to include the time
+ await context.editMessage(response, `Pong! Latency: ${time}ms`);
+});
+```
+
+This is a powerful feature that allows you to fetch and use data from Discord objects in a type safe way. It also ensures the minimum amount of API calls are made and that no extra data is fetched or passed.
+
+You can also generate a hydration function that returns a type narrowing boolean. This can be used to check if the object is hydrated or not.
+
+```typescript
+client.chatCommands.register('me', async (context: Context, _args: any[]) => {
+ // Hydrate the message id from the context
+ const hydrate = await context.hydrator(hydrated, [Expectation.Guild]);
+ // Check if the object is hydrated
+ const hasGuild = hydrate(hydrated);
+
+ if (hasGuild) {
+ // Send a message stating the user that they are in a guild
+ await context.reply(`You are ${message.author.username} and this is ${message.guild.name}`, true);
+ } else {
+ // Send a message stating the user that they are not in a guild
+ await context.reply(`You are ${message.author.username} and we are not in a server`, true);
+ }
+});
+```
+
+Additionally you can hydrate repeatedly if you need to fetch multiple objects at different times.
+
+
\ No newline at end of file
diff --git a/src/services/client.ts b/src/services/client.ts
index 69283b7..33143d0 100644
--- a/src/services/client.ts
+++ b/src/services/client.ts
@@ -1,7 +1,7 @@
import { API } from './api';
import { Gateway } from './connectors/gateway';
import { Processor } from './processor';
-import { Expectation, fromDiscord, Intent, type ClientConfig } from '../types/common';
+import { Expectation, Intent, type ClientConfig } from '../types/common';
import { exists, isNil } from '../utils/index';
import { type DiscordMessage, Message } from '../types/message';
import { Guilds } from './caches/guilds';
@@ -42,7 +42,7 @@ export class Client {
this.guilds = new Guilds(this.api, this.processor);
this.users = new Users(this.api, this.processor);
this.messages = new Messages(this, this.api, this.processor);
- // Save references to the chat and application command handlers
+ // Save references to command and event handlers
this.chatCommands = this.processor.chat_commands;
this.applicationCommands = this.processor.application_commands;
}
diff --git a/testing/demo.test.ts b/testing/demo.test.ts
index 6dc6af5..9001653 100644
--- a/testing/demo.test.ts
+++ b/testing/demo.test.ts
@@ -85,11 +85,13 @@ client.chatCommands.register('me', async (context: Context, _args: any[]) => {
const hydrate = await context.hydrator(hydrated, [Expectation.Guild]);
const hasGuild = hydrate(hydrated);
- // if (hasGuild) {
- // hydrated.guild.name
- // } else {
- // hydrated
- // }
+ if (hasGuild) {
+ // Send a message stating the user that they are in a guild
+ await context.reply(`You are ${message.author.username} and this is ${message.guild.name}`, true);
+ } else {
+ // Send a message stating the user that they are not in a guild
+ await context.reply(`You are ${message.author.username} and we are not in a server`, true);
+ }
// await context.reply(`You are ${message.author.username} ${hasGuild ? message.guild.name : ''}`, true);
});