A system for creating interactive Discord messages based on interactions using a Reactive model.
The inspiration of using React directly came from a similar package reacord, however this package aims to be more of a full-fledged replacement for command handling systems.
- Slash commands modelled by functional React components (with JSX)
- Component initialisation, rendering, and expiring
- Slash command options passed through props (and maybe autocomplete?)
- Connector utilities to register slash commands
- Possibly a nextjs-like command organisation system based on folder & file structure
- React hook support, mainly
useState
,useEffect
anduseContext
- Hydration
- Connector utilities to dehydrate/hydrate
- External state management in redis, extensible with anything
- Static Message Generation, if it proves to be worthwhile for performance
- Custom hooks
-
useController
for general control utilities -
useHydrator
to enable hydration and hydration utilities -
useModal
for showing & collecting modal inputs -
useMessage
for showing additional messages such as an ephemeral response
-
Connector utilities library support:
- discord.js
- eris
- discordeno
- detritus
The following usage examples are intended to guide development and may change.
function MyCounter() {
const [count, setCount] = useState(0)
return <>
<Embed>
<Embed.Title>This button has been clicked {count} times</Embed.Title>
</Embed>
<Button onClick={() => setCount(count + 1)}>+1</Button>
</>
}
// Dehydration is when the bot goes offline and cannot respond to interactions.
// Rehydration is when the bot returns online and can respond again.
// Using hydration in a way like this would not be recommended for large bots, since you'd be editing
// heaps of messages and probably hit ratelimits.
function MyHydratedCancellableCounter() {
const { deactivate, isDeactivating } = useController()
const [count, setCount] = useState(0)
// A re-render would occur with isDehydrating set to true when the bot is going offline
const { isDehydrating } = useHydrator()
return <>
<Embed>
<Embed.Title>This button has been clicked {count} times</Embed.Title>
{botIsOffline && <Embed.Footer>Bot offline :(</Embed.Footer>}
{isDeactivating && <Embed.Footer>Message deactivated :(</Embed.Footer>}
</Embed>
<Button
onClick={() => setCount(count + 1)}
disabled={botIsOffline}
>
+1
</Button>
<Button
style="danger"
onClick={() => deactivate({ removeComponents: true })}
>
deactivate
</Button>
</>
}
Unless I think of a neat way to do it otherwise, slash command options and autocomplete would only be supported using the folder-file command structure system. That would look something like the following.
// src/commands/counter.js
// A command to count up to a given number, or infinitely. Autocomplete suggests possible powers of 2 which the user may want to count to.
export const name = 'counter'
export const description = 'count up to a number!'
export const options = [{
type: 'INTEGER',
name: 'number',
description: 'number to count to',
required: false,
autocomplete: true
}]
export const autocomplete = {
// Autocomplete functions are mapped by option name. Could also maybe just be put in the option 'autocomplete' value itself.
number: userValue => {
const powersOfTwo = Array.from({ length: 20 }, (_, i) => 2 ** i)
const userNumber = Number(userValue)
if (userNumber) {
return powersOfTwo.filter(powerOfTwo => powerOfTwo > userNumber)
}
}
}
export default function Counter({ options: { number: target } }) {
const { deactivate, isDeactivating } = useController()
const [count, setCount] = useState(0)
if (count === target) {
deactivate({ removeComponents: true })
}
return <>
<Embed>
<Embed.Title>This button has been clicked {count} times</Embed.Title>
{/* Change the foter depending on if it's been deactivated or the target has been reached */}
{isDeactivating
? (count >= target
? <Embed.Footer>You reached the goal!</Embed.Footer>
: <Embed.Footer>You stopped counting {target && 'to ' + target}</Embed.Footer>)
: <Embed.Footer>You're counting {target ? 'to ' + target : 'infinitely'}!</Embed.Footer>}
</Embed>
<Button onClick={() => setCount(count + 1)}>+1</Button>
<Button
style="danger"
onClick={() => deactivate({ removeComponents: true })}
>
deactivate
</Button>
</>
}