Member Moderation
The Discord bot supports moderation actions that can be triggered either via Discord commands or from the Heimdall backend dashboard.
Overview
Available Actions
| Action | Description | Discord API |
|---|---|---|
| Warn | Send a warning DM to the user | DM message |
| Kick | Remove user from the server | Guild.kick_member |
| Ban | Permanently ban user | Guild.ban_member |
| Timeout | Temporarily restrict user | Member.edit (communication_disabled_until) |
| Remove Timeout | Remove an active timeout | Member.edit (clear timeout) |
Trigger Methods
- Discord Commands - Moderators use
/warn,/kick,/ban,/timeoutcommands - Backend Dashboard - Admins moderate from the Heimdall guild management UI
- API - Direct GraphQL mutation calls
Discord Commands
Warn Command
/warn @user [reason]
Sends a DM to the user with a warning message.
Permission Required: discord:guild.warn
Kick Command
/kick @user [reason]
Removes the user from the server. They can rejoin with a new invite.
Permission Required: discord:guild.kick
Ban Command
/ban @user [reason] [delete_days]
Permanently bans the user from the server.
| Parameter | Description |
|---|---|
reason | Reason for the ban (shown in audit log) |
delete_days | Days of message history to delete (0-7) |
Permission Required: discord:guild.ban
Timeout Command
/timeout @user <duration> [reason]
Temporarily restricts the user from sending messages, reacting, joining voice, etc.
| Parameter | Description |
|---|---|
duration | Duration: 60s, 5m, 1h, 1d, 7d, etc. |
reason | Reason for the timeout |
Permission Required: discord:guild.timeout
Remove Timeout Command
/untimeout @user
Removes an active timeout from the user.
Permission Required: discord:guild.timeout
Backend Moderation
The Heimdall backend can trigger moderation actions via WebSocket messages to the bot.
Moderation Flow
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
│ Backend Console │ │ Heimdall API │ │ Discord Bot │
└────────┬─────────┘ └────────┬─────────┘ └────────┬─────────┘
│ │ │
│ moderateDiscordMember() │ │
│─────────────────────────>│ │
│ │ │
│ │ ModerateDiscordMemberReq │
│ │─────────────────────────>│
│ │ │
│ │ (Bot executes action │
│ │ via Discord API) │
│ │ │
│ │ ModerateDiscordMemberResp │
│ │<─────────────────────────│
│ │ │
│ { success, action } │ │
│<─────────────────────────│ │
Protocol Messages
Moderation Action Enum
enum ModerationAction {
MODERATION_ACTION_UNSPECIFIED = 0;
MODERATION_ACTION_WARN = 1;
MODERATION_ACTION_KICK = 2;
MODERATION_ACTION_BAN = 3;
MODERATION_ACTION_TIMEOUT = 4;
MODERATION_ACTION_REMOVE_TIMEOUT = 5;
}
Request Message
message ModerateDiscordMemberRequest {
string request_id = 1; // Unique request ID
string guild_id = 2; // Discord guild ID (snowflake)
string user_id = 3; // Target user ID (snowflake)
ModerationAction action = 4; // Action to perform
optional string reason = 5; // Reason for the action
optional int32 duration_seconds = 6; // For timeout: duration in seconds
optional int32 delete_message_days = 7; // For ban: days of messages to delete
string actor_user_id = 8; // Heimdall user ID who initiated
}
Response Message
message ModerateDiscordMemberResponse {
string request_id = 1;
bool success = 2;
optional string error = 3;
ModerationAction action = 4;
string guild_id = 5;
string user_id = 6;
}
GraphQL API
Moderate Discord Member Mutation
mutation ModerateDiscordMember($input: ModerateDiscordMemberInput!) {
moderateDiscordMember(input: $input) {
success
error
action
}
}
Variables:
{
"input": {
"guildId": "123456789012345678",
"userId": "987654321098765432",
"action": "TIMEOUT",
"reason": "Spamming in general chat",
"durationSeconds": 3600
}
}
Input Fields
| Field | Type | Required | Description |
|---|---|---|---|
guildId | String | Yes | Discord guild ID (snowflake) |
userId | String | Yes | Target Discord user ID (snowflake) |
action | Enum | Yes | Moderation action to perform |
reason | String | No | Reason for the action |
durationSeconds | Int | For timeout | Timeout duration in seconds |
deleteMessageDays | Int | For ban | Days of messages to delete (0-7) |
Action Types
| Action | GraphQL Value | Description |
|---|---|---|
| Warn | WARN | Send warning DM |
| Kick | KICK | Kick from server |
| Ban | BAN | Ban from server |
| Timeout | TIMEOUT | Temporary communication disable |
| Remove Timeout | REMOVE_TIMEOUT | Remove active timeout |
Example: Timeout a User
mutation {
moderateDiscordMember(input: {
guildId: "123456789012345678"
userId: "987654321098765432"
action: TIMEOUT
reason: "Excessive caps usage"
durationSeconds: 1800 # 30 minutes
}) {
success
error
action
}
}
Example: Ban a User
mutation {
moderateDiscordMember(input: {
guildId: "123456789012345678"
userId: "987654321098765432"
action: BAN
reason: "Repeated rule violations"
deleteMessageDays: 1
}) {
success
error
action
}
}
Permissions
Permission Hierarchy
| Permission | Allows |
|---|---|
discord:guild.warn | Warn action only |
discord:guild.kick | Kick action only |
discord:guild.ban | Ban action only |
discord:guild.timeout | Timeout and remove timeout actions |
discord:guild.read | View Discord guild data, members, channels, and roles |
discord:guild.edit | Edit Discord guild data and member information |
discord:guild.sync | Resync data for a specific guild (channels, roles, members) |
Permission Check Flow
┌────────────────────────┐
│ Moderation Request │
└───────────┬────────────┘
│
▼
┌────────────────────────┐
│ Check action-specific │
│ permission │
└───────────┬────────────┘
│
┌──────┴──────┐
│ │
Yes No
│ │
▼ ▼
┌─────────┐ ┌──────────────┐
│ Execute │ │ Permission │
│ Action │ │ Denied Error │
└─────────┘ └──────────────┘
Audit Logging
All moderation actions are logged to the Heimdall audit system.
Audit Event Structure
| Field | Description |
|---|---|
event_type | bot_moderation_action |
actor_id | Heimdall user ID (if linked) or Discord user ID |
resource_type | discord_member |
resource_id | {guild_id}:{user_id} |
description | Human-readable action description |
metadata | JSON with action details |
Example Audit Event
{
"event_type": "bot_moderation_action",
"actor_id": "heimdall-user-uuid",
"resource_type": "discord_member",
"resource_id": "123456789:987654321",
"description": "Timed out @johndoe for 1 hour in My Server",
"metadata": {
"action": "timeout",
"guild_id": "123456789012345678",
"guild_name": "My Server",
"user_id": "987654321098765432",
"username": "johndoe",
"reason": "Spamming",
"duration_seconds": 3600,
"source_service": "discord_bot"
}
}
Bot Implementation
Handling Moderation Requests
The bot handles ModerateDiscordMemberRequest messages from the WebSocket:
async fn handle_moderation_request(
http: &Http,
cache: &Cache,
request: ModerateDiscordMemberRequest,
) -> ModerateDiscordMemberResponse {
let guild_id = GuildId::new(request.guild_id.parse().unwrap());
let user_id = UserId::new(request.user_id.parse().unwrap());
let result = match request.action() {
ModerationAction::Warn => {
send_warning_dm(http, user_id, &request.reason).await
}
ModerationAction::Kick => {
guild_id.kick_with_reason(http, user_id, &request.reason).await
}
ModerationAction::Ban => {
let delete_days = request.delete_message_days.unwrap_or(0) as u8;
guild_id.ban_with_reason(http, user_id, delete_days, &request.reason).await
}
ModerationAction::Timeout => {
let duration = Duration::from_secs(request.duration_seconds.unwrap_or(3600) as u64);
let until = Utc::now() + chrono::Duration::from_std(duration).unwrap();
guild_id.edit_member(http, user_id, EditMember::new()
.disable_communication_until(until.into())
).await
}
ModerationAction::RemoveTimeout => {
guild_id.edit_member(http, user_id, EditMember::new()
.enable_communication()
).await
}
_ => Err(SerenityError::Other("Unknown action"))
};
ModerateDiscordMemberResponse {
request_id: request.request_id,
success: result.is_ok(),
error: result.err().map(|e| e.to_string()),
action: request.action,
guild_id: request.guild_id,
user_id: request.user_id,
}
}
Error Handling
Common Errors
| Error | Cause | Resolution |
|---|---|---|
Missing Permissions | Bot lacks Discord permissions | Grant bot role with required permissions |
Cannot moderate higher role | Target has equal/higher role | Ensure bot role is above target |
User not found | Invalid user ID or left server | Verify user is in guild |
Cannot DM user | User has DMs disabled | Warn action will partially fail |
Rate limited | Too many actions | Automatic retry with backoff |
Error Response Example
{
"data": {
"moderateDiscordMember": {
"success": false,
"error": "Missing Permissions: Cannot kick members with equal or higher role",
"action": "KICK"
}
}
}
Best Practices
- Always provide a reason - Helps with audit trails and transparency
- Use appropriate timeout durations - Start small, escalate if needed
- Delete messages sparingly - Only when necessary to remove harmful content
- Check member roles - Ensure bot can moderate the target
- Review audit logs - Monitor for abuse or mistakes