Skip to content

Commit

Permalink
Merge pull request #40 from domin-mnd/main
Browse files Browse the repository at this point in the history
  • Loading branch information
itsMapleLeaf authored Oct 28, 2023
2 parents 9aec87a + 7fee69c commit cdc22b7
Show file tree
Hide file tree
Showing 19 changed files with 299 additions and 148 deletions.
10 changes: 9 additions & 1 deletion packages/reacord/library/core/component-event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,23 @@ export interface ComponentEvent {
guild?: GuildInfo

/** Create a new reply to this event. */
reply(content?: ReactNode): ReacordInstance
reply(content?: ReactNode, options?: ReplyInfo): ReacordInstance

/**
* Create an ephemeral reply to this event, shown only to the user who
* triggered it.
*
* @deprecated Use event.reply(content, { ephemeral: true })
*/
ephemeralReply(content?: ReactNode): ReacordInstance
}

/** @category Component Event */
export interface ReplyInfo {
ephemeral?: boolean
tts?: boolean
}

/** @category Component Event */
export interface ChannelInfo {
id: string
Expand Down
2 changes: 1 addition & 1 deletion packages/reacord/library/core/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { ReactNode } from "react"
*/
export interface ReacordInstance {
/** Render some JSX to this instance (edits the message) */
render: (content: ReactNode) => void
render: (content: ReactNode) => ReacordInstance

/** Remove this message */
destroy: () => void
Expand Down
141 changes: 120 additions & 21 deletions packages/reacord/library/core/reacord-discord-js.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,49 @@ import type {
GuildInfo,
GuildMemberInfo,
MessageInfo,
ReplyInfo,
UserInfo,
} from "./component-event"
import type { ReacordInstance } from "./instance"
import type { ReacordConfig } from "./reacord"
import { Reacord } from "./reacord"

interface SendOptions {
/**
* 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
}

interface ReplyOptions {
ephemeral?: 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.
Expand All @@ -53,31 +83,82 @@ export class ReacordDiscordJs extends Reacord {
})
}

/**
* Sends a message to a channel.
*
* @param target - Discord channel object.
* @param [options] - Options for the channel message
* @see https://reacord.mapleleaf.dev/guides/sending-messages
*/
public createChannelMessage(
target: Discord.Channel,
options: CreateChannelMessageOptions = {},
): ReacordInstance {
return this.createInstance(
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.
*
* @param interaction - Discord command interaction object.
* @param [options] - Custom options for the interaction reply method.
* @see https://reacord.mapleleaf.dev/guides/sending-messages
*/
public createInteractionReply(
interaction: Discord.CommandInteraction,
options: CreateInteractionReplyOptions = {},
): ReacordInstance {
return this.createInstance(
this.createInteractionReplyRenderer(interaction, options),
)
}

/**
* Sends a message to a channel. Alternatively replies to message event.
*
* @deprecated Use reacord.createChannelMessage() or
* reacord.createMessageReply() instead.
* @see https://reacord.mapleleaf.dev/guides/sending-messages
*/
override send(
channelId: string,
public send(
event: string | Discord.Message,
initialContent?: React.ReactNode,
options?: SendOptions,
options: LegacyCreateChannelMessageOptions = {},
): ReacordInstance {
return this.createInstance(
this.createChannelRenderer(channelId, options),
this.createMessageReplyRenderer(event, options),
initialContent,
)
}

/**
* Sends a message as a reply to a command interaction.
*
* @deprecated Use reacord.createInteractionReply() instead.
* @see https://reacord.mapleleaf.dev/guides/sending-messages
*/
override reply(
public reply(
interaction: Discord.CommandInteraction,
initialContent?: React.ReactNode,
options?: ReplyOptions,
options: CreateInteractionReplyOptions = {},
): ReacordInstance {
return this.createInstance(
this.createInteractionReplyRenderer(interaction, options),
Expand All @@ -88,13 +169,14 @@ export class ReacordDiscordJs extends Reacord {
/**
* Sends an ephemeral message as a reply to a command interaction.
*
* @deprecated Use reacord.reply(interaction, content, { ephemeral: true })
* @deprecated Use reacord.createInteractionReply(interaction, content, {
* ephemeral: true })
* @see https://reacord.mapleleaf.dev/guides/sending-messages
*/
override ephemeralReply(
public ephemeralReply(
interaction: Discord.CommandInteraction,
initialContent?: React.ReactNode,
options?: Omit<ReplyOptions, "ephemeral">,
options?: Omit<CreateInteractionReplyOptions, "ephemeral">,
): ReacordInstance {
return this.createInstance(
this.createInteractionReplyRenderer(interaction, {
Expand All @@ -105,9 +187,25 @@ export class ReacordDiscordJs extends Reacord {
)
}

private createChannelRenderer(
private createChannelMessageRenderer(
channel: Discord.Channel,
_opts?: CreateMessageReplyOptions,
) {
return new ChannelMessageRenderer({
send: async (options) => {
if (!channel.isTextBased()) {
raise(`Channel ${channel.id} is not a text channel`)
}

const message = await channel.send(getDiscordMessageOptions(options))
return createReacordMessage(message)
},
})
}

private createMessageReplyRenderer(
event: string | Discord.Message,
opts?: SendOptions,
opts: CreateChannelMessageOptions | LegacyCreateChannelMessageOptions,
) {
return new ChannelMessageRenderer({
send: async (options) => {
Expand All @@ -124,7 +222,7 @@ export class ReacordDiscordJs extends Reacord {
raise(`Channel ${channel.id} is not a text channel`)
}

if (opts?.reply) {
if ("reply" in opts && opts.reply) {
if (typeof event === "string") {
raise("Cannot send reply with channel ID provided")
}
Expand All @@ -142,24 +240,24 @@ export class ReacordDiscordJs extends Reacord {
interaction:
| Discord.CommandInteraction
| Discord.MessageComponentInteraction,
opts?: ReplyOptions,
opts: CreateInteractionReplyOptions,
) {
return new InteractionReplyRenderer({
type: "command",
id: interaction.id,
reply: async (options) => {
const message = await interaction.reply({
...getDiscordMessageOptions(options),
...opts,
fetchReply: true,
ephemeral: opts?.ephemeral,
})
return createReacordMessage(message)
},
followUp: async (options) => {
const message = await interaction.followUp({
...getDiscordMessageOptions(options),
...opts,
fetchReply: true,
ephemeral: opts?.ephemeral,
})
return createReacordMessage(message)
},
Expand Down Expand Up @@ -283,12 +381,13 @@ export class ReacordDiscordJs extends Reacord {
user,
guild,

reply: (content?: ReactNode) =>
reply: (content?: ReactNode, options?: ReplyInfo) =>
this.createInstance(
this.createInteractionReplyRenderer(interaction),
this.createInteractionReplyRenderer(interaction, options ?? {}),
content,
),

/** @deprecated Use event.reply(content, { ephemeral: true }) */
ephemeralReply: (content: ReactNode) =>
this.createInstance(
this.createInteractionReplyRenderer(interaction, {
Expand Down
5 changes: 1 addition & 4 deletions packages/reacord/library/core/reacord.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@ export abstract class Reacord {

constructor(private readonly config: ReacordConfig = {}) {}

abstract send(...args: unknown[]): ReacordInstance
abstract reply(...args: unknown[]): ReacordInstance
abstract ephemeralReply(...args: unknown[]): ReacordInstance

protected handleComponentInteraction(interaction: ComponentInteraction) {
for (const renderer of this.renderers) {
if (renderer.handleComponentInteraction(interaction)) return
Expand Down Expand Up @@ -61,6 +57,7 @@ export abstract class Reacord {
<InstanceProvider value={instance}>{content}</InstanceProvider>,
container,
)
return instance
},
deactivate: () => {
this.deactivate(renderer)
Expand Down
18 changes: 9 additions & 9 deletions packages/reacord/scripts/discordjs-manual-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ const createTest = async (
}

await createTest("basic", (channel) => {
reacord.send(channel.id, "Hello, world!")
reacord.createChannelMessage(channel).render("Hello, world!")
})

await createTest("counter", (channel) => {
Expand All @@ -73,7 +73,7 @@ await createTest("counter", (channel) => {
</>
)
}
reacord.send(channel.id, <Counter />)
reacord.createChannelMessage(channel).render(<Counter />)
})

await createTest("select", (channel) => {
Expand Down Expand Up @@ -102,8 +102,7 @@ await createTest("select", (channel) => {
)
}

const instance = reacord.send(
channel.id,
const instance = reacord.createChannelMessage(channel).render(
<FruitSelect
onConfirm={(value) => {
instance.render(`you chose ${value}`)
Expand All @@ -114,8 +113,7 @@ await createTest("select", (channel) => {
})

await createTest("ephemeral button", (channel) => {
reacord.send(
channel.id,
reacord.createChannelMessage(channel).render(
<>
<Button
label="public clic"
Expand All @@ -125,7 +123,7 @@ await createTest("ephemeral button", (channel) => {
/>
<Button
label="clic"
onClick={(event) => event.ephemeralReply("you clic")}
onClick={(event) => event.reply("you clic", { ephemeral: true })}
/>
</>,
)
Expand All @@ -136,9 +134,11 @@ await createTest("delete this", (channel) => {
const instance = useInstance()
return <Button label="delete this" onClick={() => instance.destroy()} />
}
reacord.send(channel.id, <DeleteThis />)
reacord.createChannelMessage(channel).render(<DeleteThis />)
})

await createTest("link", (channel) => {
reacord.send(channel.id, <Link label="hi" url="https://mapleleaf.dev" />)
reacord
.createChannelMessage(channel)
.render(<Link label="hi" url="https://mapleleaf.dev" />)
})
8 changes: 4 additions & 4 deletions packages/reacord/test/reacord.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { test } from "vitest"
test("rendering behavior", async () => {
const tester = new ReacordTester()

const reply = tester.reply()
reply.render(<KitchenSinkCounter onDeactivate={() => reply.deactivate()} />)
const reply = tester
.createInteractionReply()
.render(<KitchenSinkCounter onDeactivate={() => reply.deactivate()} />)

await tester.assertMessages([
{
Expand Down Expand Up @@ -244,8 +245,7 @@ test("rendering behavior", async () => {
test("delete", async () => {
const tester = new ReacordTester()

const reply = tester.reply()
reply.render(
const reply = tester.createInteractionReply().render(
<>
some text
<Embed>some embed</Embed>
Expand Down
Loading

0 comments on commit cdc22b7

Please sign in to comment.