public interface tweaks and such

This commit is contained in:
itsMapleLeaf
2023-10-28 14:34:09 -05:00
parent cdc22b7916
commit da1c62f2f0
4 changed files with 75 additions and 143 deletions

View File

@@ -25,43 +25,6 @@ import type { ReacordInstance } from "./instance"
import type { ReacordConfig } from "./reacord" import type { ReacordConfig } from "./reacord"
import { Reacord } from "./reacord" import { Reacord } from "./reacord"
/**
* Options for the channel message.
*
* @see https://reacord.mapleleaf.dev/guides/sending-messages
*/
export interface LegacyCreateChannelMessageOptions
extends CreateChannelMessageOptions {
/**
* Send message as a reply. Requires the use of message event instead of
* channel id provided as argument.
*
* @deprecated Use reacord.createMessageReply()
*/
reply?: boolean
}
/**
* Options for the channel message.
*
* @see https://reacord.mapleleaf.dev/guides/sending-messages
*/
export interface CreateChannelMessageOptions {}
/**
* Options for the message reply method.
*
* @see https://reacord.mapleleaf.dev/guides/sending-messages
*/
export interface CreateMessageReplyOptions {}
/**
* Custom options for the interaction reply method.
*
* @see https://reacord.mapleleaf.dev/guides/sending-messages
*/
export type CreateInteractionReplyOptions = ReplyInfo
/** /**
* The Reacord adapter for Discord.js. * The Reacord adapter for Discord.js.
* *
@@ -86,45 +49,31 @@ export class ReacordDiscordJs extends Reacord {
/** /**
* Sends a message to a channel. * Sends a message to a channel.
* *
* @param target - Discord channel object. * @param target Discord channel object.
* @param [options] - Options for the channel message * @param [options] Options for the channel message
* @see https://reacord.mapleleaf.dev/guides/sending-messages * @see https://reacord.mapleleaf.dev/guides/sending-messages
* @see {@link Discord.MessageCreateOptions}
*/ */
public createChannelMessage( public createChannelMessage(
target: Discord.Channel, target: Discord.ChannelResolvable,
options: CreateChannelMessageOptions = {}, options: Discord.MessageCreateOptions = {},
): ReacordInstance { ): ReacordInstance {
return this.createInstance( return this.createInstance(
this.createChannelMessageRenderer(target, options), this.createChannelMessageRenderer(target, options),
) )
} }
/**
* Replies to a message by sending a message.
*
* @param message - Discord message event object.
* @param [options] - Options for the message reply method.
* @see https://reacord.mapleleaf.dev/guides/sending-messages
*/
public createMessageReply(
message: Discord.Message,
options: CreateMessageReplyOptions = {},
): ReacordInstance {
return this.createInstance(
this.createMessageReplyRenderer(message, options),
)
}
/** /**
* Replies to a command interaction by sending a message. * Replies to a command interaction by sending a message.
* *
* @param interaction - Discord command interaction object. * @param interaction Discord command interaction object.
* @param [options] - Custom options for the interaction reply method. * @param [options] Custom options for the interaction reply method.
* @see https://reacord.mapleleaf.dev/guides/sending-messages * @see https://reacord.mapleleaf.dev/guides/sending-messages
* @see {@link Discord.InteractionReplyOptions}
*/ */
public createInteractionReply( public createInteractionReply(
interaction: Discord.CommandInteraction, interaction: Discord.CommandInteraction,
options: CreateInteractionReplyOptions = {}, options: Discord.InteractionReplyOptions = {},
): ReacordInstance { ): ReacordInstance {
return this.createInstance( return this.createInstance(
this.createInteractionReplyRenderer(interaction, options), this.createInteractionReplyRenderer(interaction, options),
@@ -132,19 +81,17 @@ export class ReacordDiscordJs extends Reacord {
} }
/** /**
* Sends a message to a channel. Alternatively replies to message event. * Sends a message to a channel.
* *
* @deprecated Use reacord.createChannelMessage() or * @deprecated Use reacord.createChannelMessage() instead.
* reacord.createMessageReply() instead.
* @see https://reacord.mapleleaf.dev/guides/sending-messages * @see https://reacord.mapleleaf.dev/guides/sending-messages
*/ */
public send( public send(
event: string | Discord.Message, channel: Discord.ChannelResolvable,
initialContent?: React.ReactNode, initialContent?: React.ReactNode,
options: LegacyCreateChannelMessageOptions = {},
): ReacordInstance { ): ReacordInstance {
return this.createInstance( return this.createInstance(
this.createMessageReplyRenderer(event, options), this.createChannelMessageRenderer(channel, {}),
initialContent, initialContent,
) )
} }
@@ -158,10 +105,9 @@ export class ReacordDiscordJs extends Reacord {
public reply( public reply(
interaction: Discord.CommandInteraction, interaction: Discord.CommandInteraction,
initialContent?: React.ReactNode, initialContent?: React.ReactNode,
options: CreateInteractionReplyOptions = {},
): ReacordInstance { ): ReacordInstance {
return this.createInstance( return this.createInstance(
this.createInteractionReplyRenderer(interaction, options), this.createInteractionReplyRenderer(interaction, {}),
initialContent, initialContent,
) )
} }
@@ -169,18 +115,16 @@ export class ReacordDiscordJs extends Reacord {
/** /**
* Sends an ephemeral message as a reply to a command interaction. * Sends an ephemeral message as a reply to a command interaction.
* *
* @deprecated Use reacord.createInteractionReply(interaction, content, { * @deprecated Use reacord.createInteractionReply(interaction, { ephemeral:
* ephemeral: true }) * true })
* @see https://reacord.mapleleaf.dev/guides/sending-messages * @see https://reacord.mapleleaf.dev/guides/sending-messages
*/ */
public ephemeralReply( public ephemeralReply(
interaction: Discord.CommandInteraction, interaction: Discord.CommandInteraction,
initialContent?: React.ReactNode, initialContent?: React.ReactNode,
options?: Omit<CreateInteractionReplyOptions, "ephemeral">,
): ReacordInstance { ): ReacordInstance {
return this.createInstance( return this.createInstance(
this.createInteractionReplyRenderer(interaction, { this.createInteractionReplyRenderer(interaction, {
...options,
ephemeral: true, ephemeral: true,
}), }),
initialContent, initialContent,
@@ -188,49 +132,32 @@ export class ReacordDiscordJs extends Reacord {
} }
private createChannelMessageRenderer( private createChannelMessageRenderer(
channel: Discord.Channel, channelResolvable: Discord.ChannelResolvable,
_opts?: CreateMessageReplyOptions, messageCreateOptions?: Discord.MessageCreateOptions,
) { ) {
return new ChannelMessageRenderer({ return new ChannelMessageRenderer({
send: async (options) => { send: async (messageOptions) => {
if (!channel.isTextBased()) { let channel = this.client.channels.resolve(channelResolvable)
raise(`Channel ${channel.id} is not a text channel`) if (!channel && typeof channelResolvable === "string") {
channel = await this.client.channels.fetch(channelResolvable)
} }
const message = await channel.send(getDiscordMessageOptions(options)) if (!channel) {
return createReacordMessage(message) const id =
}, typeof channelResolvable === "string"
? channelResolvable
: channelResolvable.id
raise(`Channel ${id} not found`)
}
if (!channel.isTextBased()) {
raise(`Channel ${channel.id} must be a text channel`)
}
const message = await channel.send({
...getDiscordMessageOptions(messageOptions),
...messageCreateOptions,
}) })
}
private createMessageReplyRenderer(
event: string | Discord.Message,
opts: CreateChannelMessageOptions | LegacyCreateChannelMessageOptions,
) {
return new ChannelMessageRenderer({
send: async (options) => {
// Backwards compatible channelId api
// `event` is treated as MessageEvent depending on its type
const channel =
typeof event === "string"
? this.client.channels.cache.get(event) ??
(await this.client.channels.fetch(event)) ??
raise(`Channel ${event} not found`)
: event.channel
if (!channel.isTextBased()) {
raise(`Channel ${channel.id} is not a text channel`)
}
if ("reply" in opts && opts.reply) {
if (typeof event === "string") {
raise("Cannot send reply with channel ID provided")
}
const message = await event.reply(getDiscordMessageOptions(options))
return createReacordMessage(message)
}
const message = await channel.send(getDiscordMessageOptions(options))
return createReacordMessage(message) return createReacordMessage(message)
}, },
}) })
@@ -240,23 +167,22 @@ export class ReacordDiscordJs extends Reacord {
interaction: interaction:
| Discord.CommandInteraction | Discord.CommandInteraction
| Discord.MessageComponentInteraction, | Discord.MessageComponentInteraction,
opts: CreateInteractionReplyOptions, interactionReplyOptions: Discord.InteractionReplyOptions,
) { ) {
return new InteractionReplyRenderer({ return new InteractionReplyRenderer({
type: "command", interactionId: interaction.id,
id: interaction.id, reply: async (messageOptions) => {
reply: async (options) => {
const message = await interaction.reply({ const message = await interaction.reply({
...getDiscordMessageOptions(options), ...getDiscordMessageOptions(messageOptions),
...opts, ...interactionReplyOptions,
fetchReply: true, fetchReply: true,
}) })
return createReacordMessage(message) return createReacordMessage(message)
}, },
followUp: async (options) => { followUp: async (messageOptions) => {
const message = await interaction.followUp({ const message = await interaction.followUp({
...getDiscordMessageOptions(options), ...getDiscordMessageOptions(messageOptions),
...opts, ...interactionReplyOptions,
fetchReply: true, fetchReply: true,
}) })
return createReacordMessage(message) return createReacordMessage(message)

View File

@@ -1,4 +1,3 @@
import type { Interaction } from "../interaction"
import type { Message, MessageOptions } from "../message" import type { Message, MessageOptions } from "../message"
import { Renderer } from "./renderer" import { Renderer } from "./renderer"
@@ -6,17 +5,23 @@ import { Renderer } from "./renderer"
// so we know whether to call reply() or followUp() // so we know whether to call reply() or followUp()
const repliedInteractionIds = new Set<string>() const repliedInteractionIds = new Set<string>()
export type InteractionReplyRendererImplementation = {
interactionId: string
reply: (options: MessageOptions) => Promise<Message>
followUp: (options: MessageOptions) => Promise<Message>
}
export class InteractionReplyRenderer extends Renderer { export class InteractionReplyRenderer extends Renderer {
constructor(private interaction: Interaction) { constructor(private implementation: InteractionReplyRendererImplementation) {
super() super()
} }
protected createMessage(options: MessageOptions): Promise<Message> { protected createMessage(options: MessageOptions): Promise<Message> {
if (repliedInteractionIds.has(this.interaction.id)) { if (repliedInteractionIds.has(this.implementation.interactionId)) {
return this.interaction.followUp(options) return this.implementation.followUp(options)
} }
repliedInteractionIds.add(this.interaction.id) repliedInteractionIds.add(this.implementation.interactionId)
return this.interaction.reply(options) return this.implementation.reply(options)
} }
} }

View File

@@ -6,7 +6,7 @@ slug: getting-started
# Getting Started # Getting Started
These guides assume some familiarity with JavaScript, [React](https://reactjs.org), [Discord.js](https://discord.js.org) and the [Discord API](https://discord.dev). Keep these pages as reference if you need it. These guides assume some familiarity with [JavaScript](https://developer.mozilla.org/en-US/docs/Web/javascript), [React](https://reactjs.org), [Discord.js](https://discord.js.org) and the [Discord API](https://discord.dev). Keep these pages as reference if you need it.
## Setup from template ## Setup from template
@@ -47,6 +47,13 @@ await client.login(process.env.BOT_TOKEN)
To use JSX in your code, run it with [tsx](https://npm.im/tsx): To use JSX in your code, run it with [tsx](https://npm.im/tsx):
```bash ```bash
npm install tsx npm install -D tsx
tsx main.tsx npx tsx main.tsx
```
For production, I recommend compiling it with [tsup](https://npm.im/tsup):
```bash
npm install -D tsup
npx tsup src/main.tsx --target node20
``` ```

View File

@@ -36,7 +36,6 @@ function Uptime() {
client.on("ready", () => { client.on("ready", () => {
const instance = reacord.createChannelMessage(channel) const instance = reacord.createChannelMessage(channel)
instance.render(<Uptime />) instance.render(<Uptime />)
}) })
``` ```
@@ -48,26 +47,25 @@ const Hello = ({ subject }) => <>Hello, {subject}!</>
client.on("ready", () => { client.on("ready", () => {
const instance = reacord.createChannelMessage(channel) const instance = reacord.createChannelMessage(channel)
instance.render(<Hello subject="World" />) instance.render(<Hello subject="World" />)
instance.render(<Hello subject="Moon" />) instance.render(<Hello subject="Moon" />)
}) })
``` ```
## Replying to Messages You can specify various options for the message:
Instead of sending messages to a channel, you may want to reply to a specific message instead. To do this, create an instance using `.createMessageReply()` instead:
```jsx ```jsx
const Hello = ({ username }) => <>Hello, {username}!</> const instance = reacord.createChannelMessage(channel, {
tts: true,
client.on("messageCreate", (message) => { reply: {
reacord messageReference: someMessage.id,
.createMessageReply(message) },
.render(<Hello username={message.author.displayName} />) flags: [MessageFlags.SuppressNotifications],
}) })
``` ```
See the [Discord.js docs](https://discord.js.org/#/docs/discord.js/main/typedef/MessageCreateOptions) for all of the available options.
## Cleaning Up Instances ## Cleaning Up Instances
If you no longer want to use the instance, you can clean it up in a few ways: If you no longer want to use the instance, you can clean it up in a few ways:
@@ -91,7 +89,7 @@ const reacord = new ReacordDiscordJs(client, {
This section also applies to other kinds of application commands, such as context menu commands. This section also applies to other kinds of application commands, such as context menu commands.
</aside> </aside>
To reply to a command interaction, use the `.createInteractionReply()` function. This function returns an instance that works the same way as the one from `.createChannelMessage()` and `.createMessageReply()`. Here's an example: To reply to a command interaction, use the `.createInteractionReply()` function. This function returns an instance that works the same way as the one from `.createChannelMessage()`. Here's an example:
```jsx ```jsx
import { Client } from "discord.js" import { Client } from "discord.js"
@@ -163,11 +161,7 @@ handleCommands(client, [
]) ])
``` ```
## Interaction Options ## Ephemeral Command Replies
Just like `.createChannelMessage()` and `.createMessageReply()`, interaction replies provide a way to specify certain `interaction.reply()` options.
### Ephemeral Command Replies
Ephemeral replies are replies that only appear for one user. To create them, use the `.createInteractionReply()` function and provide `ephemeral` option. Ephemeral replies are replies that only appear for one user. To create them, use the `.createInteractionReply()` function and provide `ephemeral` option.
@@ -185,7 +179,7 @@ handleCommands(client, [
]) ])
``` ```
### Text-to-Speech Command Replies ## Text-to-Speech Command Replies
Additionally interaction replies may have `tts` option to turn on text-to-speech ability for the reply. To create such reply, use `.createInteractionReply()` function and provide `tts` option. Additionally interaction replies may have `tts` option to turn on text-to-speech ability for the reply. To create such reply, use `.createInteractionReply()` function and provide `tts` option.