You're probably wondering "wow, what can I do with these cool new Message Content intents?"
Well, I'm excited to share that this article is not about that, let's learn what you can do even without the intent - how exciting.
For those who didn't catch our announcement, message content is officially becoming a privileged intent after August 31, 2022. Although message content can enable you and your bot to do a lot of things, there is still functionality that can take place without the intent. Here are some tips to help build common use cases without the need for the message content intent.
First things first, please keep in mind that bots will always have access to message information in the following locations:
- messages the bot receives in its DMs
- Want to submit a ticket to the bot's devs or support server? Let them know!
- messages the bot is @mentioned in
- Imagine a command prefix, but instead of a special character, it's the entire bot's name.
- messages the bot sends
- Want to edit a cool leaderboard embed the bot sends at regular intervals? You still can!
If your application is dealing with messages that fit any of the above three criteria, then it still has access to all of the content in the message.
Picture this: You have a crush on the bot. No matter how much you send messages in your server, the bot seems to be ignoring them. No problem, we're here to say you can ping them with @mentions or sneak into their DMs - that way you can really get their attention. Time to work up the courage and be more intentional!
As mentioned previously, if a bot is unapproved for the message intent, certain fields of the message object will return empty when receiving a message:
- content
- embeds
- attachments
- components
Information from other gateway events like guild members and presences are still locked behind those intents though.
We generally advise that if a function of your bot can be implemented in DMs or mentions without severely impacting the overall function or UX of your bot, to go for it instead of using the message content intent!
An example where the GUILD_MEMBERS gateway is inaccessible, thus returning null on member. The same happens with GUILD_PRESENCES.
Although not required, we are encouraging folks to look into alternatives and workarounds for their command functionalities wherever possible. Below are some examples on how some developers are still achieving common bot use cases without the intent!
Messages sent by the bot
Bots will always be able to read the data of messages that were sent by itself (not other bots, those are treated regular users). If you read your own message and didn't know who sent it, when you sent it, along with what it says.. bad example — okay you get my point.
So maybe you have a leaderboard that updates itself every couple minutes to check the latest top runs of Super Mario Sunshine (2002) speedruns on the GameCube.
// fetch the latest SMSS speedrun times from the API let leaderboard = new Discord.MessageEmbed() .setAuthor("Super Mario Sunshine Speedrun LB") .setFooter("Updates every so often") .setTimestamp() const lbData = await getStuff.fetch( => { for (let counter = 0; counter < result.length; ++counter) { const { speedrunnerName, time } = result[counter] let name = speedrunnerName leaderboard.addField(`${counter + 1}) ${time} time`, `${speedrunnerName}`) } }); const lbUpdate = async (client) => { // look for the previous leaderboard const results = await lbData.find({}) for (const result of results) { // edit the leaderboard with newest data } // set a timer to edit }
This is trying to demonstrate that if you were to log the embed data of all the runs currently in the leaderboard, the content (run times, speed runner names, etc.) would show up. So don't fret, your bot can still edit that message and it's contents with the fastest runs from the database of your choosing as long as the initial leaderboard message was sent by the bot!
Direct Messages
Talking about Direct Messaging and message intents. This may be important regardless of whether a bot has DM scripts set up or not.
A bot regardless of message intent approval will still have access to the data in messages received in DMs. Some of this data includes:
- author information (usernames, IDs, tags, avatar, etc.)
- message content
Features like modmail, ticket systems, etc. can alternatively, wherever possible - be made with slash commands but we understand the potential difficulties with transitioning these specific functions to interactions.
It is all the more important then that users know the implications of sending DMs to bot. We advise that bot developers' policies on this are very transparent, and that unless there are no workarounds, that there are opt-in mechanics to more safely handle message content.
Generally, the information obtained from DM'd messages can only be stored if they are encrypted and made anonymous. Any data considered Personally Identifiable Information (PII) should only be stored depending on how relevant it is to the major/proper function of the bot.
We generally recommend that this information not be stored for extended periods of time and that folks automatically delete unused data after 30 or so days.
Additionally, message contents can only be stored for long periods of time for specific use cases that would be subject to more thorough review by the review team.
Maybe a user wants to submit a ticket to your support server. Here's a simple way of doing something like that with direct messaging:
1. Listen for when someone wants to create ticket.
// createTicket slash command is fired up
if (interaction.commandName == "ticket") {
client.users.fetch(`${interaction.author.id}`).then(user => {
user.send('Type out your ticket below.');
}
)
interaction.reply('Check your DMs!')
}
2. Probably use some combination of message collectors to get ticket body, but let's keep it basic for now.
// get the bot's support server/channel to send the tickets to
const supportChannel = client.channels.cache.get(supportChannelID);
// on any message event that isn't sent by a bot
client.on('messageCreate', message => {
// author is still accessible
if (message.author.bot) return;
// DMs are a type of channel within the message
if (message.channel.type == "dm") {
// create an embed message for the ticket
const embed = new Discord.MessageEmbed().setTitle('Support Ticket');
embed.addField(
`Sent by ${message.author.tag}`,`${message.content}.toString()`
).catch(e => console.log(e));
// send the embed
supportChannel.send(embed)
}
});
This example is of a bot that is constantly listening for messages sent by DMs. The point here is to demonstrate that when a message is DM'd directly to the bot, it could still make an embed with the message content and send the embed to a support channel. With some additional configuration, it could only start collecting messages specifically after the /ticket
command is invoked, or even chained responses.
@Mentions
As indicated earlier, bots still have access to the contents and data of messages that they're mentioned in. Imagine being in a group chat with a bunch of people. You turn off your notifications because sometimes you just don't want or have any reason to read all of the messages sent within.
Think of it this way: your crush, the bot, seems to be ignoring every message you send in the server. So you work up the courage by @mentioning them by name to get their attention. But unlike your crush, the bot will open up and read the contents of your message every single time - how great is that!
client.on('message', async (message) => {
const args = message.content.slice(prefix.length).trim().split(/ +/g);
const command = args.shift().toLowerCase();
if (message.author.id == botID) return;
if (!message.content.startsWith(`<@botID>`)) {
return;
} else
if (command === 'ping') {
return message.channel.send('pong');
}
});
You know the drill. If you've got message-based prefixes for your commands, you most likely have some version of filter to make sure your bot is only reacting to the right things.
Congratulations, now you're on a first-name basis with your bot! (F in the chat for the folks out there too shy to @mention their crushes.)
Keep in mind that this example covers @mentions as a replacement for the command prefix, but as long as the bot is @mentioned in a message AT ALL, it'll have access to the contents within. From the front, to the back of the message, and every where in between.
Interaction Series: Slash Commands!
You can think of it this way: if your command doesn't need to know WHAT is being said or WHO sent the message, then it could likely be migrated to a slash command. These types of commands will likely have predefined results that do not change depending on data from the message.
Interactions give you access to things like:
- interactionID (the ID of the interaction/command)
- applicationID (the bot ID this command belongs to)
- member (user who sent command)
- channelID (channel the command was sent from)
- guildID (guild the command was sent from)
- message (message the component/button is attached to)
Note: Don't use user reactions as buttons. Use the built-in message components like buttons.
A magic 8ball
client.on('interactionCreate', async interaction => {
// not every interaction is a slash commmand
if (!interaction.isCommand()) return;
if (interaction.commandName == "8ball") {
// some random results from the 8ball
let replies = [
"Yes, of course!",
"Perhaps not.",
"Oh gosh, definitely not!",
"This might be good.",
]
let random = Math.floor(Math.random() * replies.length);
await interaction.reply(`8ball results: <@${interaction.member.id}>! ${replies}[random]`)
}
});
The reason this type of command function still operates properly is because the action is relatively static. The final result will always be one of four replies, there isn't any message content or other factors that are influencing the result. Contrary to popular belief, the magic 8ball answers questions independent from user input.. yeah.. I know right..
Interaction Series: Buttons!
Buttons can be used for a plethora of different things, but we'll focus on a specific use case that could replace the need for message intents.
Maybe you're thinking of beefing up the security/moderation of your server. Remember, even without the message intent, bots will know when someone sends a message, just not specifically what they said. Suppose you're picking up some suspicious behavior from a user then.
How do you verify that they're not a bad bot or up to no good? That's right.. everybody's favorite - CAPTCHA.
/* after too many messages, prompt the user to click the correct
button to verify they're human or something
*/
// pass in the message that triggered the captcha system
function verify(message) {
// get a random number between 1 - 10
const passBtn = Math.floor(Math.random() * 11);
// assign customIDs to ten buttons
var btn1 = new MessageButton().setStyle('PRIMARY').setLbael('1').setCustomId('1')..
..
..
;
// attach the buttons as components to a message
var row = new MessageActionRow().addComponents([btn1, ...])
// notify the user the bot is awaiting their input
interaction.editReply({
embeds:[new MessageEmbed()
.setTitle('Verify your identity')
.setDescription(`Click ${passBtn} button.`)
],
components: [row],
ephemeral: true
});
// collect clicks only from the user who initiated the interaction
const filter = async (i) => {
return i.user.id === interaction.user.id && i.isButton() && i.message.author.id == client.user.id
}
try {
const collector = interaction.channel.createMessageComponentCollector({
filter,
max: 1,
time: 1000 * 10,
})
collector.on('collect', async (i) => {
if (passBtn === icustomId) {
// user pressed the right button
} else {
// user did not press the right button
}
})
} catch (e) {
// something just borked
}
}
When a user clicks a button, the bot will receive an interaction (i) with data like:
- when a button was clicked
- which button was clicked
- who clicked the button
— from which the bot can do a bunch of stuff.
Suppose the captcha generated the number '5'. Instead of requiring the user to type in their response, the user is prompted to click the button with the same customID
(a developer-defined string of a max 100 characters) as the randomly generated number, in this case, '5'. The bot can then compare the clicked button's customID
with the "captcha" number.
In the above example, the message is invisible (ephemeral) to other users and there is a filter ensuring that it's only accepting clicks from the correct user. You could even achieve this entire interaction in DMs!
If you're making something that operates off multiple choices, maybe a questionnaire, poll or something - check out buttons. We've seen folks in the past use reactions and collectors in a similar way, consider switching to interactions to do the same thing.. but better!
Let your creativity go wild! We're excited to see what you come up with.
Interaction Series: Select Menus!
Similar to buttons - maybe you'd like for users to select from a list of roles to be a part of? Instead of having to join them into the roles manually, users are able to do it with commands following tagging the name of the role! Oh.. but that requires multiple text inputs...
Well, do we have good news for you.
client.on('interactionCreate', async interaction => {
// slash command roles
if (interaction.commandName === 'roles') {
await interaction.reply({
content: 'Loading roles list..',
ephemeral: true
});
// add three roles as the possible options
const row = new MessageActionRow()
.addComponents(
new MessageSelectMenu()
.setCustomId('roles')
.setPlaceholder('no role selected')
.addOptions([
{
label: 'House Bravery',
description: 'Unflinching, unyielding.',
value: 'brave',
emoji: {}
},
{
label: 'House Brilliance',
description: 'Bot development, research',
value: 'brilliant',
emoji: {}
},
{
label: 'House Balance',
description: 'Inner peace, outer harmony.',
value: 'balanced',
emoji: {}
},
]),
);
await interaction.editReply({
content: 'Select a role to join!',
components: [row]
});
}
// listen for an interaction with the customId for roles selection
if (interaction.customId === 'roles') {
await interaction.update({
content: 'A role was selected',
components: []
});
// reply to the interaction by changing the role
interaction.reply(someFunction)
return;
}
});
When firing up the /roles
command, the bot sends a message asking the user to make a selection from the dropdown box. In the dropdown will be a list of roles (up to 25) featuring the name of the role, a short description of the role, and maybe even a role icon for those looking for a little spice!
When the user finishes making their choice by clicking out of the dropdown, the bot will receive an interaction. The bot will then have the value, id, user, etc. - all of the data with which to assign the corresponding role to the user.
You could even allow users to make their selection and join roles from the privacy of their DMs by sending the interaction there as the interaction will also contain the guildID and even channelID from which the original command was used. Couple of additional steps with getting the role by guildID and voilà! — all that without having to type anything more than /roles
.
In Conclusion
..All this and we've only barely scratched the surface of what can be done with interactions. We hope this got some of those mind juices flowing - there are so many use cases that we didn't cover that are possible without message content. Of course, we can't just have all the fun ourselves so we're excited to see what you can come up with. We built interactions to help users more easily engage with bots and hope to continue to see the same creativity, effort, and passion put into making servers just that much more enjoyable.
You can read more about interactions/application commands: here.
You can read over our announcement about message content: here.