Skip to main content

Overview

Faculty Bot provides moderation tools through Discord’s context menu system, allowing moderators to quickly manage messages and users without typing commands. All moderation actions are restricted to users with appropriate permissions.

Permission System

The bot implements a dual permission system:
async fn has_mod_or_semestermod(ctx: Context<'_>) -> Result<bool, Error> {
    let db = &ctx.data().db;
    let member = ctx.author_member().await.unwrap();

    // Check if user is in semestermods table
    let has_perms = sqlx::query("SELECT user_id FROM semestermods WHERE user_id = $1")
        .bind(member.user.id.0 as i64)
        .fetch_optional(db)
        .await?
        .is_some();

    if has_perms {
        Ok(true)
    } else if member
        .permissions(ctx)?
        .contains(Permissions::MANAGE_MESSAGES)
    {
        return Ok(true);
    } else {
        return Ok(false);
    }
}
Reference: src/commands/moderation.rs:5-27

Permission Levels

Semestermods

Custom role stored in database for student moderatorsCan use pin, delete, and basic moderation tools.

Discord Permissions

Standard Discord permissions like MANAGE_MESSAGESAllows staff to use moderation without database entry.

Message Management

Pin/Unpin Messages

Context menu command that toggles message pin state:
#[poise::command(
    context_menu_command = "Toggle Pin State",
    check = "has_mod_or_semestermod",
    ephemeral,
    guild_only
)]
pub async fn pin(
    ctx: Context<'_>,
    message: serenity::Message,
) -> Result<(), Error> {
    if message.pinned {
        message
            .unpin(&ctx.serenity_context())
            .await?;
        ctx.say("Unpinned message").await?;
    } else {
        message
            .pin(&ctx.serenity_context())
            .await?;
        ctx.say("Pinned message").await?;
    }

    Ok(())
}
Reference: src/commands/moderation.rs:29-54
1

Right-click Message

Open context menu on any message in the server
2

Select 'Toggle Pin State'

Choose the command from the Apps menu
3

Automatic Toggle

Bot pins unpinned messages or unpins pinned messages
4

Ephemeral Confirmation

Only the moderator sees the success message

Delete Messages

Permanently remove messages using the context menu:
#[poise::command(
    context_menu_command = "Delete Message",
    check = "has_mod_or_semestermod",
    guild_only,
    ephemeral
)]
pub async fn delete_message(
    ctx: Context<'_>,
    message: serenity::Message,
) -> Result<(), Error> {
    message
        .delete(&ctx.serenity_context())
        .await?;
    ctx.say("Deleted message").await?;

    Ok(())
}
Reference: src/commands/moderation.rs:56-73 Note: This action is irreversible. The message is permanently deleted from Discord.

User Role Management

Promote to Semestermod

Grant semestermod privileges to a user:
#[poise::command(
    context_menu_command = "Promote to Semestermod",
    required_permissions = "MANAGE_GUILD",
    guild_only
)]
pub async fn promote_user(
    ctx: Context<'_>,
    user: serenity::User,
) -> Result<(), Error> {
    let db = &ctx.data().db;
    let uid = user.id.0 as i64;

    sqlx::query("INSERT INTO semestermods (user_id) VALUES ($1)")
        .bind(uid)
        .execute(db)
        .await?;

    ctx.say(format!("Promoted {} to semestermod", user.tag()))
        .await?;

    Ok(())
}
Reference: src/commands/moderation.rs:75-98

Demote from Semestermod

Revoke semestermod privileges:
#[poise::command(
    context_menu_command = "Demote from Semestermod",
    required_permissions = "MANAGE_GUILD",
    guild_only
)]
pub async fn demote_user(
    ctx: Context<'_>,
    user: serenity::User,
) -> Result<(), Error> {
    let db = &ctx.data().db;
    let uid = user.id.0 as i64;

    sqlx::query("DELETE FROM semestermods WHERE user_id = $1")
        .bind(uid)
        .execute(db)
        .await?;

    ctx.say(format!("Demoted {} from semestermod", user.tag()))
        .await?;

    Ok(())
}
Reference: src/commands/moderation.rs:100-123 Requirements: MANAGE_GUILD permission (typically Administrator role)

Context Menu Commands

Toggle Pin State

Permission: Semestermod or MANAGE_MESSAGESRight-click any message to pin or unpin it

Delete Message

Permission: Semestermod or MANAGE_MESSAGESRight-click any message to delete it permanently

Promote to Semestermod

Permission: MANAGE_GUILDRight-click a user to grant moderation privileges

Demote from Semestermod

Permission: MANAGE_GUILDRight-click a user to revoke moderation privileges

Database Schema

Semestermods are tracked in a dedicated table:
pub struct Semestermods {
    pub user_id: i64,
}
Reference: src/structs.rs:49-51 This allows the bot to persist moderator status across restarts and query it efficiently.

Implementation Notes

Ephemeral Responses

All moderation commands use ephemeral responses, meaning:
  • Only the moderator sees the success/failure message
  • No clutter in the channel
  • Discreet moderation actions
epub async fn pin(
    ctx: Context<'_>,
    message: serenity::Message,
) -> Result<(), Error>
The ephemeral attribute ensures responses are private. Reference: src/commands/moderation.rs:32

Guild-Only Restriction

All moderation commands are restricted to guild (server) contexts:
#[poise::command(
    context_menu_command = "Delete Message",
    check = "has_mod_or_semestermod",
    guild_only,  // ← Prevents use in DMs
    ephemeral
)]
This prevents errors when trying to moderate messages in DMs.

Best Practices

Use Context Menus

Faster than typing commands for message actions

Grant Semestermod Carefully

Only promote trusted community members

Ephemeral by Default

Keeps moderation discreet and professional

Database Persistence

Semestermod status survives bot restarts

Security Features

  • Dual Permission Check: Must pass either database check or Discord permission check
  • Guild-Only Commands: Cannot be used in DMs or group chats
  • MANAGE_GUILD Requirement: Only administrators can promote/demote semestermods
  • Ephemeral Responses: Moderation actions are private to the moderator

Build docs developers (and LLMs) love