Scripting Guide

Lesson 3: Working with Messages

Reading message content, mentions, and attachments, and acting on messages: reply, react, edit, delete, pin, and bulk operations.

The message object

On messageCreate (and messageDelete) the payload is the message itself. The most useful fields:

FieldTypeNotes
contentstringRaw text. cleanContent has mentions resolved to names.
authorobjectThe user: id, username, displayName, bot, plus send() for DMs.
memberobjectThe author as a server member: roleIds, permissions, moderation methods (Lesson 4).
channelobjectWhere it was sent: send(), messages, bulkDelete() and more.
guildobjectThe server: channels, members, roles managers (Lesson 5).
mentionsobject{ everyone, users, roles, channels, repliedUserId }, all as ID arrays.
attachmentsarrayUploaded files: name, url, size, contentType.
referenceobject or nullSet when the message is a reply; fetchReference() loads the original.
createdTimestampnumberUnix milliseconds.

Replying and sending

message.reply(...) answers with a visible reference to the original; message.channel.send(...) posts plainly in the same channel. Both accept either a string or an options object:

reply-shapes.js (event: messageCreate)
export async function onMessage(message) {
  if (message.author.bot) return;

  if (message.content === "!simple") {
    await message.reply("Just text.");
  }

  if (message.content === "!fancy") {
    // The object form unlocks embeds, components, and files.
    await message.channel.send({
      content: "Text plus extras.",
      // embeds: [...]      -> Lesson 6
      // components: [...]  -> Lesson 7
      // files: ["https://example.com/image.png"]  (http/https URLs only)
    });
  }
}
Limits

Message content is capped at 4096 characters (longer strings are truncated). A send can carry at most 10 files, and file attachments must be public http(s) URLs; private or internal addresses are rejected.

A keyword auto-moderator

automod.js (event: messageCreate)
const BANNED = ["badword1", "badword2"];

export async function onMessage(message) {
  if (message.author.bot) return;

  const text = message.content.toLowerCase();
  if (!BANNED.some((w) => text.includes(w))) return;

  // Remove the message. delete() returns true, or { error } if it failed
  // (for example missing Manage Messages permission).
  const deleted = await message.delete();
  if (deleted !== true) return;

  // Warn the author privately. DMs can fail if the user blocks DMs,
  // so check the result instead of assuming.
  const dm = await message.author.send(
    "Your message in #" + message.channel.name + " was removed."
  );
  if (dm && dm.error) {
    await message.channel.send(
      message.author.username + ", watch your language (could not DM you)."
    );
  }
}

Reactions, pins, edits, threads

  • await message.react(emoji) adds a reaction. Use a Unicode emoji string or a custom emoji in name:id form.
  • await message.pin() / await message.unpin() manage pins.
  • await message.edit(content) edits the bot's own messages (you cannot edit other users' messages).
  • await message.startThread({ name: "Discussion" }) opens a thread on the message.
  • await message.fetchReference() returns the message this one replies to.
  • await message.forward(channelId) forwards the message to another channel in the same server.

Reading history and bulk deleting

purge.js (event: messageCreate)
export async function onMessage(message) {
  if (message.author.bot) return;
  if (message.content !== "!purge") return;

  // Only let moderators run this.
  if (!message.member.permissions.includes("ManageMessages")) {
    await message.reply("You need Manage Messages to do that.");
    return;
  }

  // Fetch recent messages (returns an array of light snapshots)...
  const recent = await message.channel.messages.fetch({ limit: 25 });

  // ...and bulk delete. Returns the number deleted. Messages older than
  // 14 days are skipped by Discord.
  const count = await message.channel.bulkDelete(25);
  await message.channel.send("Deleted " + count + " messages.");
}

Common pitfalls

  • Case-sensitive matching. Normalize with toLowerCase() before comparing user text.
  • Assuming DMs work. author.send() returns { error } when the user disallows DMs. Check it.
  • Editing other people's messages. Not possible on Discord; delete and repost instead.
  • Forgetting `cleanContent`. If you log messages, content shows raw mention markup like <@1234>; cleanContent shows @Name.
  • Bulk delete limits. Discord refuses to bulk delete messages older than two weeks; the helper silently skips them.

Exercise

Write a link-guard.js agent on messageCreate that deletes any message containing discord.gg/ unless the author has the ManageGuild permission, then posts a short notice in the channel that deletes itself when you have learned timers do not exist (hint: you cannot delete it later, so make the notice useful enough to keep, for example mentioning the rule it enforces).