Scripting Guide
Lesson 4: Members, Roles, and Moderation
Reading member data, assigning roles, sending DMs, and the moderation toolkit: timeout, kick, and ban.
The member object
A member is a user inside a specific server. You meet members in guildMemberAdd/Remove/Update payloads, as message.member, and via guild.members.fetch(id). Useful data fields:
| Field | Meaning |
|---|---|
id | The user's ID. |
displayName | Nickname if set, otherwise username. |
nickname | Server nickname or null. |
roleIds | Array of role IDs the member has. |
permissions | Array of permission names, e.g. "ManageMessages". |
joinedTimestamp | When they joined (Unix ms). |
kickable / bannable / moderatable | Whether the bot is allowed to act on them. |
user | The underlying user (username, bot, avatarURL, ...). |
Autorole on join
const ROLE_ID = "123456789012345678"; // copy with Developer Mode
export async function onJoin({ member, guild }) {
// Give every new human member a role. Skip bots.
if (member.user.bot) return;
const ok = await member.roles.add(ROLE_ID, "Autorole on join");
// Role actions return true on success or { error } on failure
// (missing Manage Roles, or the role is above the bot's highest role).
if (ok !== true) {
// Optional: report somewhere instead of failing silently.
}
}The roles helper
member.roles.add(roleId, reason?)adds one role.member.roles.remove(roleId, reason?)removes one role.member.roles.set([id1, id2], reason?)replaces all roles (up to 100).member.roles.list()returns the member's roles as{ id, name, color, position }.member.roleIds.includes(id)is the quick membership check, no extra call needed.
Moderation actions
export async function onMessage(message) {
if (message.author.bot) return;
if (!message.content.startsWith("!")) return;
// Gate moderation behind real permissions, not role names.
const isMod = message.member.permissions.includes("ModerateMembers");
if (!isMod) return;
const [cmd, userId] = message.content.split(/\s+/);
if (!userId) return;
const target = await message.guild.members.fetch(userId);
if (!target || target.error) {
await message.reply("Could not find that member.");
return;
}
if (cmd === "!mute") {
// Timeout takes milliseconds. 10 minutes:
await target.timeout(10 * 60 * 1000, "Muted by " + message.author.username);
await message.reply("Muted for 10 minutes.");
}
if (cmd === "!unmute") {
await target.timeout(null); // null clears the timeout
await message.reply("Unmuted.");
}
if (cmd === "!kick" && target.kickable) {
await target.kick("Kicked by " + message.author.username);
await message.reply("Kicked.");
}
if (cmd === "!ban" && target.bannable) {
await target.ban({
reason: "Banned by " + message.author.username,
deleteMessageSeconds: 60 * 60 * 24, // also remove their last day of messages
});
await message.reply("Banned.");
}
}Bans can also be managed at the server level without fetching the member first: guild.members.ban(id, opts), guild.members.unban(id, reason), guild.members.kick(id, reason), and guild.bans.fetch() to list existing bans (up to 100).
Other member actions
member.setNickname("NewNick", reason?)changes the nickname (null clears it).member.send(content)sends a DM; returns{ error }if DMs are closed.member.permissionsIn(channelId)returns the member's permission names in a specific channel.member.voice.setChannel(id)/member.voice.disconnect()/setMute/setDeafcontrol voice state if the member is in a voice channel.guild.members.list({ limit })andguild.members.search({ query, limit })fetch up to 100 members.guild.members.prune({ days, dry: true })counts (or removes) members inactive for 1 to 30 days.
Expected behavior
With the moderation agent uploaded on messageCreate, a moderator typing !mute 123456789012345678 times the member out for 10 minutes and the bot confirms. Members without ModerateMembers get no response at all (the agent returns early).
Common pitfalls
- Role hierarchy. The bot cannot manage roles above its own highest role, or moderate members above it. Check
kickable,bannable, andmoderatablefirst. - Timeout units.
timeout()takes milliseconds, not seconds. The Discord maximum is 28 days. - Checking role names instead of permissions. Names change; permission flags do not. Prefer
permissions.includes("..."). - `guildMemberRemove` fires for kicks and leaves alike. If you need to distinguish, check the audit log (Lesson 13).
Exercise
Build a !warn <id> command agent that DMs the member a warning, and pairs with Lesson 9 later: for now reply with how many warnings you *would* have stored. Then add a !modinfo <id> command that replies with the member's display name, join date (new Date(joinedTimestamp).toDateString()), and role count.