mirror of
https://github.com/dsec-hub/dsec-discord-bot.git
synced 2026-09-22 15:53:56 +00:00
COL-BOT-02: guard the honeypot, log failed bans, stop log_embed panicking
The honeypot banned whoever posted, unconditionally. Add guards so it never
bans a bot, a webhook, the bot itself, or anyone who can moderate
(ban_members / manage_messages / administrator, computed from the cached guild).
A failed ban now logs the user and error instead of being silent.
Remove the .expect("LOG FAIL") in log_embed: it runs inside a message handler,
so a transient Discord failure was a panic; it now eprintln!s and returns.
Decouple on_message so honeypot and the LeetCode thread each run and log
independently — a failure in one no longer skips the other, and neither
propagates out of the event handler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017XrE7F9ZuBWdQnS8CZvYDE
This commit is contained in:
parent
b960d84bfc
commit
717670bd27
2 changed files with 63 additions and 18 deletions
|
|
@ -62,8 +62,11 @@ pub async fn log_embed(
|
||||||
// Send the embed (logs_channel_id is parsed once at boot; see AppState).
|
// Send the embed (logs_channel_id is parsed once at boot; see AppState).
|
||||||
let builder = serenity::CreateMessage::new().embed(embed);
|
let builder = serenity::CreateMessage::new().embed(embed);
|
||||||
|
|
||||||
let send_log = logs_channel_id.send_message(ctx, builder).await;
|
// Do NOT .expect() here: this runs inside a message handler, and a panic on a
|
||||||
send_log.expect("LOG FAIL");
|
// transient Discord failure takes the whole handler down for that message.
|
||||||
|
if let Err(err) = logs_channel_id.send_message(ctx, builder).await {
|
||||||
|
eprintln!("[log_embed] failed to write to logs channel: {err}");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,33 @@ async fn honeypot(
|
||||||
|
|
||||||
if honeypot_channel_id.eq(current_channel_id) {
|
if honeypot_channel_id.eq(current_channel_id) {
|
||||||
let user = &new_message.author;
|
let user = &new_message.author;
|
||||||
|
|
||||||
|
// Never ban a bot, a webhook, or ourselves. The honeypot exists to catch
|
||||||
|
// spam accounts; banning another club integration (or the bot itself)
|
||||||
|
// because it posted in the wrong channel is a self-inflicted outage.
|
||||||
|
if user.bot || new_message.webhook_id.is_some() || user.id == ctx.cache.current_user().id {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Never ban someone who can moderate. A moderator checking whether the
|
||||||
|
// honeypot works should not be the person it catches. This reads the
|
||||||
|
// cached guild; if it is unavailable we fall through and let the honeypot
|
||||||
|
// act, rather than skipping the check silently for everyone.
|
||||||
|
if let Some(guild_id) = new_message.guild_id
|
||||||
|
&& let Ok(member) = guild_id.member(ctx, user.id).await
|
||||||
|
{
|
||||||
|
// Guild-level moderator identity is exactly what we want here; the
|
||||||
|
// deprecation is about per-channel permission overwrites, which are
|
||||||
|
// irrelevant to "can this person moderate at all".
|
||||||
|
#[allow(deprecated)]
|
||||||
|
let perms = member.permissions(ctx);
|
||||||
|
if let Ok(perms) = perms
|
||||||
|
&& (perms.ban_members() || perms.manage_messages() || perms.administrator())
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let username = &user.name;
|
let username = &user.name;
|
||||||
let user_id = &user.id;
|
let user_id = &user.id;
|
||||||
let avatar_url = user.avatar_url();
|
let avatar_url = user.avatar_url();
|
||||||
|
|
@ -21,20 +48,28 @@ async fn honeypot(
|
||||||
.ban_with_reason(ctx, user_id, 2, "Message sent in honeypot channel.")
|
.ban_with_reason(ctx, user_id, 2, "Message sent in honeypot channel.")
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if ban_user.is_ok() {
|
match ban_user {
|
||||||
log_embed(
|
Ok(()) => {
|
||||||
ctx,
|
log_embed(
|
||||||
data.state.logs_channel_id,
|
ctx,
|
||||||
Some("Honeypot activated!".to_string()),
|
data.state.logs_channel_id,
|
||||||
None,
|
Some("Honeypot activated!".to_string()),
|
||||||
Some(format!("User got banned: {}", username)),
|
None,
|
||||||
Some(format!("ID: {}", user_id)),
|
Some(format!("User got banned: {}", username)),
|
||||||
None,
|
Some(format!("ID: {}", user_id)),
|
||||||
avatar_url,
|
None,
|
||||||
None,
|
avatar_url,
|
||||||
Some(true),
|
None,
|
||||||
)
|
Some(true),
|
||||||
.await?;
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
// A honeypot that cannot ban is information the mods need.
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!(
|
||||||
|
"[honeypot] failed to ban {username} ({user_id}) in the honeypot channel: {err}"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -84,7 +119,14 @@ pub async fn on_message(
|
||||||
new_message: &serenity::Message,
|
new_message: &serenity::Message,
|
||||||
data: &Data,
|
data: &Data,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
honeypot(ctx, new_message, data).await?;
|
// Independent features. A failure in one must not skip the other, and neither
|
||||||
create_leetcode_thread(ctx, new_message, data).await?;
|
// should propagate out of the event handler, where the default on_error only
|
||||||
|
// eprintln!s (OPS-04).
|
||||||
|
if let Err(err) = honeypot(ctx, new_message, data).await {
|
||||||
|
eprintln!("[on_message] honeypot failed: {err}");
|
||||||
|
}
|
||||||
|
if let Err(err) = create_leetcode_thread(ctx, new_message, data).await {
|
||||||
|
eprintln!("[on_message] leetcode thread failed: {err}");
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue