Skip to content

v0.x

  • Removed the class-based builder and switched to function-based builders
  • Consolidated builder code
  • Strengthened the specificity of the types generated by builders
  • Updated related code for builder optimization
new Command('autocomplete', 'auto').options(
new SubCommand('sub', 'sub command').options(
new Option('option', 'op').autocomplete().required(),
new Option('option2', 'op2').autocomplete(),
),
),
makeSlashCommand('autocomplete', 'auto').options([
makeSubCommand('sub', 'sub command').options([
makeStringOption('option', 'op').autocomplete(true).required(true),
makeStringOption('option2', 'op2').autocomplete(true),
]),
]),
  • Command -> makeSlashCommand, makeUserCommand, makeMessageCommand, or makeEntryPointCommand
  • SubCommand -> makeSubCommand
  • SubGroup -> makeSubCommandGroup
  • Option -> makeStringOption, makeIntegerOption, makeBooleanOption, makeUserOption, makeChannelOption, makeRoleOption, makeMentionableOption, makeNumberOption, or makeAttachmentOption
  • Some shorthand syntax has been changed to require explicit, precise declarations
    • .required() -> .required(true); automatic handling of true has been removed, so it must be written explicitly
    • .options(option, option, ...) -> .options([option, option, ...]); automatic array handling has been removed, so it must be written explicitly
c.resAutocomplete(
new Autocomplete(c.focused?.value).choices(
{ name: 'test1', value: 'v-test1' },
{ name: 'test2', value: 'v-test2' },
),
),
c.resAutocomplete([
{ name: 'test1', value: 'v-test1' },
{ name: 'test2', value: 'v-test2' },
]),

This is not exactly the same code.

  • Removed custom filtering via the Autocomplete class
  • Extended resAutocomplete to accept arrays
components: new Components().row(
new Button('https://discord-hono.luis.fun', ['📑', 'Docs'], 'Link'),
component_delete.component,
),
components: [
makeActionRow([
makeLinkButton('https://discord-hono.luis.fun', ['📑', 'Docs']),
component_delete.component,
]),
],
  • Components -> makeActionRow
  • Button -> makeButton, makeLinkButton, or makePremiumButton
await c.followup(
{
components: [
new Content('text top'),
new Layout('Container').components(
new Layout('Action Row').components(component_image_update2.component, component_delete.component),
new Layout('Separator'),
new Content('container - text'),
new Layout('Section')
.components(new Content('container - section - text'), new Content('container - section - text2'))
.accessory(new Content('image.webp', 'Thumbnail')),
new Content('container - text2'),
new Content('image.webp', 'Media Gallery'),
),
makeTextDisplay('text top'),
makeContainer([
makeActionRow([component_image_update2.component, component_delete.component]),
makeSeparator(),
makeTextDisplay('container - text'),
makeSection(
[makeTextDisplay('container - section - text'), makeTextDisplay('container - section - text2')],
makeThumbnail('attachment://image.webp'),
),
makeTextDisplay('container - text2'),
makeMediaGallery(['attachment://image.webp']),
]),
],
},
{ blob, name: 'image.webp' },
)
  • Content -> makeTextDisplay, makeThumbnail, makeMediaGallery, or makeFile
  • Layout -> makeActionRow, makeSection, makeSeparator, makeContainer, or makeLabel
new Modal('modal', 'Modal Test')
.row(new TextInput('modal_text', 'Modal Text').required())
.component('Channel Select', new Select('channel', 'Channel'))
makeModal('modal', 'Modal Test', [
makeActionRow([makeTextInput('modal_text', 'Modal Text').required(true)]),
makeLabel('Channel Select', makeChannelSelect('channel')),
]),
  • Modal -> makeModal, makeActionRow, and makeLabel
  • TextInput -> makeTextInput
new Embed()
makeEmbed()
new Poll()
.question('What is your favorite color?')
.answers(['🔴', 'Red'], ['🟢', 'Green'], 'Blue', 'Yellow')
.allow_multiselect()
.duration(1)
makePoll(
'What is your favorite color?',
[['🔴', 'Red'], ['🟢', 'Green'], 'Blue', 'Yellow']
)
.allow_multiselect(true)
.duration(1)
factory.command<{ text: string }>(...)
factory.command<any, { text: string }>(...)
  • The first argument’s type inference was changed, so part of the type parameters now needs to be handled with any or similar.
  • Add c.ref as a quick reference
  • Handle custom_id more consistently and reassign the previous role of custom_id to custom_value
  • Swap the $ and _ in the rest path
c.key
c.ref.key
c.var.custom_id
c.ref.custom_value
c.var.xxx (select values)
c.ref.values
(Button or Select).custom_id
(Button or Select).custom_value
await c.rest('POST', _channels_$_messages, [channel], { content: 'this is rest' })
await c.rest('POST', $channels$_$messages, [channel], { content: 'this is rest' })

GET methods are highly likely to be affected.

await c.rest(
'GET', _channels_$_messages,
[channel_id], { limit: 10 },
[channel_id, { limit: 10 }],
)
await c.rest('METHOD', 'PATH', ['PATH_VAR'], DATA_OBJ or QUERY_OBJ)
await c.rest('METHOD', 'PATH', ['PATH_VAR', QUERY_OBJ], DATA_OBJ)

Context integration

const app = new DiscordHono().cron("", async c => {
console.log(c.cronEvent)
console.log(c.interaction)
})

CronContext changed to type information only

Section titled “CronContext changed to type information only”

v0.17.0 -> v0.18.0

return c.update().resDefer(c.followupDelete)
return c.update().resDefer(c => c.followup())

Before v0.16.x -> v0.18.0

return c.resDeferUpdate(c.followupDelete)
return c.update().resDefer(c => c.followup())

Since c.req could be confused with Hono’s c.req, and because handling Request objects is not necessary for Discord Bots, it has been removed.
If you absolutely need this functionality, please consider using Hono middleware instead.
If you cannot find an alternative and require c.req, please create an issue.

c.waitUntil() has been removed as its functionality is included in c.resDefer(), and its usage frequency was low.
Instead, please use c.executionCtx.waitUntil().

c.waitUntil(/*process*/)
c.executionCtx.waitUntil(/*process*/)

c.resUpdate(), c.resDeferUpdate() -> c.update()

Section titled “c.resUpdate(), c.resDeferUpdate() -> c.update()”
return c.resUpdate("update text")
return c.update().res("update text")
return c.resDeferUpdate("update text")
return c.update().resDefer("update text")

c.suppressEmbeds(), c.ephemeral(), c.suppressNotifications() -> c.flags()

Section titled “c.suppressEmbeds(), c.ephemeral(), c.suppressNotifications() -> c.flags()”
return c.ephemeral().suppressNotifications().res("ephemeral text")
return c.flags("EPHEMERAL", "SUPPRESS_NOTIFICATIONS").res("ephemeral text")

Unintentional breaking change caused by a developer implementation mistake

In versions prior to v0.16.5, followups were handled via POST.
From v0.16.6 onwards, followups are handled via PATCH.
This is a breaking change only if you are intentionally calling followup multiple times.
In such cases, please use c.rest to create a new followup.

const rest = new Rest('token')
const rest = createRest('token')
const res = c.rest.post(_channels_$_messages, [channel_id], { content: 'this is rest' })
const res = c.rest('POST', _channels_$_messages, [channel_id], { content: 'this is rest' })
const app = factory.discord()
factory.loader(app, Object.values(handlers))
.loader(Object.values(handlers))
export default app
const { result } = await c.rest.get('/applications/@me', [])
const result = await c.rest.get('/applications/@me', []).then(r => r.json())

For .component() and .modal(), please use custom_id instead of regex keys.
If you absolutely need to use regex keys, please refer to this example.

const app = new DiscordHono()
.command(/regex/, c => c.res('regex'))
.command('regex', c => c.res('regex'))
const handler= (c: ComponentContext<Env, 'Button'>) => {
const handler= (c: ComponentContext<Env, Button>) => {
//...
}
const func = (c: CommandContext<Env> | ComponentContext<Env>) => {
//...
if (c instanceof CommandContext)
if (c.interaction.type === 2)
//...
}