Skip to main content

Overview

Administration commands provide powerful server management capabilities to staff members. These commands handle everything from user verification lookups to rule management and meal plan posting.

Permission Checks

Most admin commands use a custom permission check:
async fn executor_is_dev_or_admin(ctx: Context<'_>) -> Result<bool, Error> {
    let member = ctx.author_member().await.unwrap();

    // Check if user has semestermod or staff role
    let has_perms = member
        .roles
        .contains(&ctx.data().config.roles.semestermodrole)
        || member.roles.contains(&ctx.data().config.roles.staffrole);

    if has_perms {
        Ok(true)
    } else if member
        .permissions(ctx)?
        .contains(Permissions::ADMINISTRATOR)
    {
        return Ok(true);
    } else {
        return Ok(false);
    }
}
Reference: src/commands/administration.rs:4-24

User Management

Email Lookup

Retrieve the email address a user verified with:
#[poise::command(
    slash_command,
    prefix_command,
    track_edits,
    ephemeral,
    required_permissions = "MANAGE_GUILD",
    guild_only
)]
pub async fn getmail(
    ctx: Context<'_>,
    user: serenity::User,
) -> Result<(), Error> {
    let pool = &ctx.data().db;
    let uid = user.id.0 as i64;

    let db_user = sqlx::query_as::<_, structs::VerifiedUsers>(
        "select * from verified_users where user_id = $1"
    )
    .bind(uid)
    .fetch_optional(pool)
    .await?;

    let response = match db_user {
        Some(db_usr) => format!("{} is verified with {}", user.tag(), db_usr.user_email),
        None => format!("{} is not verified", user.tag()),
    };

    ctx.say(&response).await?;
    Ok(())
}
Reference: src/commands/administration.rs:27-70
This command is ephemeral and only visible to the administrator who runs it, protecting user privacy.

XP Management

Manually set a userโ€™s XP and level:
pub async fn set_xp(
    ctx: Context<'_>,
    user: serenity::User,
    xp: i64,
) -> Result<(), Error> {
    let pool = &ctx.data().db;
    let uid = user.id.0 as i64;
    
    // Calculate level (100 XP per level)
    let new_level = (xp as f64 / 100.0).floor() as i32;

    // Upsert user XP
    sqlx::query("INSERT INTO user_xp (user_id, user_xp, user_level) VALUES ($1, $2, $3)")
        .bind(uid)
        .bind(xp as f64)
        .bind(new_level)
        .execute(pool)
        .await?;

    ctx.say(&format!("Set XP of {} to {}", user.tag(), xp))
        .await?;

    Ok(())
}
Reference: src/commands/administration.rs:125-172 Command: /set-xp <user> <xp>

Meal Plan Management

Force Post Meal Plan

Manually trigger meal plan posting outside the automatic schedule:
pub async fn force_post_mensaplan(ctx: Context<'_>) -> Result<(), Error> {
    let mensaplan_url = &ctx.data().config.mealplan.url;
    let mensaplan_channel = &ctx.data().config.channels.mealplan;

    // Fetch meal plan image
    let mp_bytestream = utils::fetch_mensaplan(mensaplan_url).await?;
    let now = chrono::Local::now();
    let today = now.date_naive().format("%Y-%m-%d").to_string();

    // Post to channel
    let force_post = mensaplan_channel
        .send_message(&ctx, |msg| {
            msg.add_file(serenity::AttachmentType::Bytes {
                data: std::borrow::Cow::Borrowed(&mp_bytestream),
                filename: "mensaplan.png".to_string(),
            })
        })
        .await?;

    // Crosspost to announcement channel
    force_post.crosspost(&ctx).await?;

    // Update database
    sqlx::query("INSERT INTO mensaplan (date, posted) VALUES ($1, $2) ON CONFLICT DO NOTHING")
        .bind(&today)
        .bind(true)
        .execute(&ctx.data().db)
        .await?;

    ctx.say(&format!("Mensaplan fรผr {} gepostet", today))
        .await?;

    Ok(())
}
Reference: src/commands/administration.rs:184-221 Command: /force-post-mensaplan Requirements: MANAGE_GUILD permission

Rule Management

The bot includes a comprehensive rule management system with the following subcommands:

/rule add

Add a new rule to the databaseAutomatically assigns the next rule number.

/rule remove

Delete a rule by numberPermanently removes the rule from the database.

/rule edit

Modify an existing ruleโ€™s textUpdates the rule content while keeping the number.

/rule list

Show all rulesDisplays numbered list of all server rules.

/rule get

Retrieve a specific ruleShows a single rule by number.

/rule post

Post rules embed to a channelCreates formatted embed with all rules.

Add Rule

pub async fn add(
    ctx: Context<'_>,
    text: String,
) -> Result<(), Error> {
    let pool = &ctx.data().db;

    // Get highest rule number
    let number = sqlx::query_as::<sqlx::Postgres, structs::Rules>(
        "SELECT * FROM rules ORDER BY rule_number DESC LIMIT 1",
    )
    .fetch_optional(pool)
    .await?
    .map_or(1, |n| n.rule_number + 1);

    let rule = structs::Rules {
        rule_number: number,
        rule_text: text,
    };

    sqlx::query("INSERT INTO rules (number, rule_text) VALUES ($1, $2)")
        .bind(rule.rule_number)
        .bind(rule.rule_text)
        .execute(pool)
        .await?;

    ctx.send(|m| {
        m.embed(|e| {
            e.title("Regel hinzugefรผgt");
            e.description(format!("Regel {} hinzugefรผgt", rule.rule_number));
            e
        })
    })
    .await?;

    Ok(())
}
Reference: src/commands/administration.rs:244-290

Post Rules Embed

pub async fn post(
    ctx: Context<'_>,
    channel: Option<serenity::GuildChannel>,
) -> Result<(), Error> {
    let pool = &ctx.data().db;

    // Fetch all rules
    let rules = sqlx::query_as::<sqlx::Postgres, structs::Rules>("SELECT * FROM rules")
        .fetch_all(pool)
        .await?;

    // Format rule list
    let mut rule_list = String::new();
    for rule in rules {
        rule_list.push_str(&format!("{}: {}\n", rule.rule_number, rule.rule_text));
    }

    // Determine target channel
    let rules_channel = match channel {
        Some(channel) => channel.id,
        None => ctx.data().config.channels.rules,
    };

    // Post embed
    rules_channel
        .send_message(&ctx, |m| {
            m.embed(|e| {
                e.title("Regeln");
                e.description(rule_list);
                e
            })
        })
        .await?;

    Ok(())
}
Reference: src/commands/administration.rs:441-487

Re-verification Campaign

Trigger a mass re-verification for users who joined before a specific date:
pub async fn reverify(
    ctx: Context<'_>,
    cutoff_date: String
) -> Result<(), Error> {
    let cutoff_date = chrono::NaiveDate::parse_from_str(&cutoff_date, "%Y-%m-%d")?;

    // Get all users that joined before cutoff
    let users = ctx.guild().unwrap().members(&ctx, None, None).await?;
    let users_to_reverify = users.iter().filter_map(|m| {
        if m.joined_at.unwrap().date_naive() < cutoff_date && !m.user.bot {
            Some(m)
        } else {
            None
        }
    }).collect::<Vec<_>>();

    ctx.send(|m| {
        m.content("Reverification broadcast started")
        .embed(|e| {
            e.title("Reverification Broadcast");
            e.description(format!(
                "Reverification broadcast started for users that joined before {}, messaging {} users",
                cutoff_date, users_to_reverify.len()
            ));
            e
        })
    }).await?;

    // Send DM to all users
    for user in users_to_reverify {
        user.user
            .dm(&ctx, |message| {
                message
                    .content("๐ŸŽ“ **Reverification Required**")
                    .embed(|embed| {
                        embed
                            .title("Student Status Verification")
                            .description(
                                "Hello! To confirm that you're still enrolled, please complete the reverification process.",
                            )
                    })
                    .components(|c| {
                        c.create_action_row(|a| {
                            a.create_button(|b| {
                                b.style(serenity::ButtonStyle::Success)
                                    .label("Reverify")
                                    .custom_id("reverify")
                            })
                        })
                    })
            })
            .await?;
    }
    Ok(())
}
Reference: src/commands/administration.rs:490-556 Command: /reverify <cutoff_date> Format: YYYY-MM-DD (e.g., 2023-09-01)

System Commands

Run Shell Command

This command is restricted to bot owners only and can execute arbitrary shell commands.
#[poise::command(
    rename = "run",
    slash_command,
    owners_only,
    guild_only
)]
pub async fn run_command(
    ctx: Context<'_>,
    command: String,
) -> Result<(), Error> {
    ctx.defer_or_broadcast().await?;

    let output = tokio::process::Command::new("sh")
        .arg("-c")
        .arg(command)
        .output()
        .await?;

    let stdout = String::from_utf8_lossy(&output.stdout);
    let stderr = String::from_utf8_lossy(&output.stderr);

    ctx.send(|msg| {
        msg.embed(|embed| {
            embed
                .title("Output")
                .field("Stdout", format!("```\n{}\n```", stdout), false)
                .field("Stderr", format!("```\n{}\n```", stderr), false)
        })
    })
    .await?;

    Ok(())
}
Reference: src/commands/administration.rs:73-112

Command Summary

CommandPermissionDescription
/getmail <user>MANAGE_GUILDRetrieve userโ€™s verified email
/set-xp <user> <xp>Staff/AdminManually set user XP
/force-post-mensaplanMANAGE_GUILDPost meal plan immediately
/rule add <text>Staff/AdminAdd new rule
/rule remove <number>Staff/AdminDelete rule
/rule edit <number> <text>Staff/AdminModify rule
/rule listEveryoneShow all rules
/rule get <number>EveryoneGet specific rule
/rule post [channel]Staff/AdminPost rules embed
/reverify <date>Staff/AdminStart re-verification campaign
/run <command>Bot OwnerExecute shell command

Security Considerations

Permission Layers

Multiple permission checks prevent unauthorized access

Ephemeral Responses

Sensitive data shown only to the command executor

Database Validation

All queries use parameterized statements

Audit Trail

All admin actions can be tracked through Discord audit log

Build docs developers (and LLMs) love