OpenBot

Workshop

Inputs

Event handler

Whenever this happens, do that

mod.when(Events.MessageCreate, event => await event.react('✨'))
// When this happens, do that once and never again:
mod.once(Events.ClientReady, event => console.log('module started!'))

Slash commands

Build a chat input command and validate/fulfill interactions

mod.slash('ping', 'Get a response from the bot',
            async intx => await intx.reply('pong!'))
// Fully customized:
mod.slash('quote', 'Short description', {
  build(builder) {
    return builder
      .addStringOption((opt) => opt
        .setName("content")
        .setDescription("The statement to quote")
        .setRequired(true))
    },
  check(intx) {
    if (intx.options.getString("content", true).trim().length === 0) {
      throw new Error('The quote cannot be empty')
    }
  },
  run(intx, resolved) {
    /* fulfill the interaction to completion */
    /* only called if "check" did not fail */
  },
  /* optional: limit this command to a given guild */
  guild: '1002274815270465607'
})

Context menu items

Attach a new context menu button to a message or a user, and do this when someone clicks it

mod.menu('Menu item label', ApplicationCommandType.User, intx => { /* what to do? */ })

mod.menu('Menu item label', ApplicationCommandType.Message, {
  build(builder) {
    /* customize the context menu item, e.g. by adding localized labels */
    /* optional */
  },
  check(intx) {
    /* validate the incoming interaction to see if it should be fulfilled or rejected */
    /* optional */
    /* return something resolved to pass it to "run" below */
    /* throw an error to indicate an invalid interaction */
  },
  run(intx, resolved) {
    /* fulfill the interaction to completion */
    /* only called if "check" did not fail */
  },
  /* optional: limit this command to a given guild */
  guild: '12345678901234567890'
})

Terms

❄️ Snowflake

Unique ID that represents an entity (message, user, channel, guild, etc.) on Discord. Example: 135824500603224064

Guild

Discord server (technical term for disambiguation)

Member vs User

A User is a human's Discord account (e.g. username#ID). A Member is a User's profile in a Guild (server profile, roles, etc.)

Command

Interactive feature provided by a bot. Types: Slash command, context menu command

Gateway Intent

Group of events that take place on Discord. Specify intents to receive their events from Discord

Queries

All guild members

List all the members and their profiles in a guild

const guild = await mod.client.guilds.resolve('1028759256750633062')!
const members = await guild.members.list({limit: 1000})
// members is a Collection<Snowflake, GuildMember>

Guild member & roles

List all the roles held by a guild member

const guild = await mod.client.guilds.resolve('1028759256750633062')!
const member = await guild.members.fetch({user: '135824500603224064'})
// member.roles.cache is a Collection<string, Role>

Resolve user

Get a User by ID for DMs

const user = await mod.client.users.fetch('135824500603224064')
// then, IF you want to DM them
await user.send('sliding into your DMs ;)')

Resolve channel

Get a channel by ID

const chan = await host.client.channels.fetch('1005324615503073360')
// then, IF you want to send something, reassure the type system that it's a text channel
if (chan?.isTextBased()) {
  await chan.send('this is the channel!')
  // IF you want to read the channel's history (requires the MessageContent privileged intent)
  const history = await chan.messages.fetch({limit: 100})
}

Outputs

Plain-text reply

Reply to the user that invoked the slash command

async (intx: ChatInputCommandInteraction) => {
  // everyone in the guild can see:
  await intx.reply('sus?')
  // only the user can see:
  await intx.reply({
    content: 'sus?',
    ephemeral: true
  })
  // DM the user
  await intx.user.send('sus?')
}

Reactions

React to any given message (e.g. from MessageCreate)

async (msg: MessageEvent) => {
  await msg.react('😎')
}

Embeds and buttons

Display embedded cards and buttons for more interaction

async (intx: ChatInputCommandInteraction) => {
  // Display a button "✅ Agree" under the message
  const buttons = new ActionRowBuilder<ButtonBuilder>()
    .addComponents(new ButtonBuilder()
      .setCustomId('agree')
      .setLabel('Agree')
      .setStyle(ButtonStyle.Primary)
      .setEmoji('✅'))
  await intx.reply({
    // Build and preview embeds at https://discohook.org
    embeds: [{
      title: 'Some title',
      description: "Embeds are cool because humans can't send them legally"
    }],
    components: [buttons]
  })
}

Modals

Display a form in a focused dialog to the user

async (intx: CommandInteraction) => {
  const modal = new ModalBuilder()
    .setCustomId('form-modal')
    .setTitle('My Modal')
  const question = new TextInputBuilder()
    .setCustomId('number-input')
    .setLabel('Enter any significant number')
    .setStyle(TextInputStyle.Short)
  const firstRow = new ActionRowBuilder<ModalActionRowComponentBuilder>()
    .addComponents(question)
  modal.addComponents(firstRow)
  await intx.showModal(modal)
}

Roles

Add or remove a GuildMember role

async (intx: CommandInteraction) => {
  if (intx.member !== null) {
    // find the role ID in server settings, then ADD
    await intx.member.roles?.add('1028749978870493234')
    // OR remove
    await intx.member.roles?.remove('1028749978870493234')
  }
}