From eef2cb7558f63e7dc03079dcff1c50ea8ab27909 Mon Sep 17 00:00:00 2001 From: Phoenix/HotaruBlaze Date: Wed, 3 Jun 2026 21:40:02 +0100 Subject: [PATCH] Fix woodpecker missing branch and fix cargo fmt --- .woodpecker/build.yaml | 2 +- src/commands.rs | 268 ++++++++++++++++++++--------------------- src/db.rs | 29 +++-- src/handlers.rs | 54 +++++++-- src/main.rs | 2 +- 5 files changed, 194 insertions(+), 161 deletions(-) diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml index 52a92bd..d9ac211 100644 --- a/.woodpecker/build.yaml +++ b/.woodpecker/build.yaml @@ -34,4 +34,4 @@ steps: platforms: linux/amd64 when: - event: push - branch: main \ No newline at end of file + branch: master \ No newline at end of file diff --git a/src/commands.rs b/src/commands.rs index 0b1c465..508faab 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -17,99 +17,92 @@ use serenity::{ use std::sync::Arc; pub async fn register_commands(ctx: &Context, guild_id: GuildId) -> Result<(), serenity::Error> { - let commands = vec![ - CreateCommand::new("config") - .kind(CommandType::ChatInput) - .description("Configure the anti-spam bot") - .default_member_permissions(Permissions::ADMINISTRATOR) - .add_option( - CreateCommandOption::new( - CommandOptionType::SubCommand, - "mod-channel", - "Set the mod notification channel", - ) - .add_sub_option( - CreateCommandOption::new( - CommandOptionType::Channel, - "channel", - "The channel for notifications", - ) - .required(true), - ), + let commands = vec![CreateCommand::new("config") + .kind(CommandType::ChatInput) + .description("Configure the anti-spam bot") + .default_member_permissions(Permissions::ADMINISTRATOR) + .add_option( + CreateCommandOption::new( + CommandOptionType::SubCommand, + "mod-channel", + "Set the mod notification channel", ) - .add_option( + .add_sub_option( CreateCommandOption::new( - CommandOptionType::SubCommand, - "watched-channel-add", - "Add a channel to the watchlist", + CommandOptionType::Channel, + "channel", + "The channel for notifications", ) - .add_sub_option( - CreateCommandOption::new( - CommandOptionType::Channel, - "channel", - "The channel to watch", - ) - .required(true), - ), + .required(true), + ), + ) + .add_option( + CreateCommandOption::new( + CommandOptionType::SubCommand, + "watched-channel-add", + "Add a channel to the watchlist", ) - .add_option( + .add_sub_option( CreateCommandOption::new( - CommandOptionType::SubCommand, - "watched-channel-remove", - "Remove a channel from the watchlist", + CommandOptionType::Channel, + "channel", + "The channel to watch", ) - .add_sub_option( - CreateCommandOption::new( - CommandOptionType::Channel, - "channel", - "The channel to stop watching", - ) - .required(true), - ), + .required(true), + ), + ) + .add_option( + CreateCommandOption::new( + CommandOptionType::SubCommand, + "watched-channel-remove", + "Remove a channel from the watchlist", ) - .add_option( + .add_sub_option( CreateCommandOption::new( - CommandOptionType::SubCommand, - "watched-channel-list", - "List all watched channels", - ), - ) - .add_option( - CreateCommandOption::new( - CommandOptionType::SubCommand, - "ban-reason", - "Set the ban reason", + CommandOptionType::Channel, + "channel", + "The channel to stop watching", ) - .add_sub_option( - CreateCommandOption::new( - CommandOptionType::String, - "reason", - "The ban reason text", - ) - .required(true), - ), + .required(true), + ), + ) + .add_option(CreateCommandOption::new( + CommandOptionType::SubCommand, + "watched-channel-list", + "List all watched channels", + )) + .add_option( + CreateCommandOption::new( + CommandOptionType::SubCommand, + "ban-reason", + "Set the ban reason", ) - .add_option( + .add_sub_option( CreateCommandOption::new( - CommandOptionType::SubCommand, - "immune-role", - "Set a role that makes users immune from auto-ban", + CommandOptionType::String, + "reason", + "The ban reason text", ) - .add_sub_option( - CreateCommandOption::new( - CommandOptionType::Role, - "role", - "The immune role (or none to clear)", - ) - .required(false), - ), + .required(true), + ), + ) + .add_option( + CreateCommandOption::new( + CommandOptionType::SubCommand, + "immune-role", + "Set a role that makes users immune from auto-ban", ) - .add_option( + .add_sub_option( CreateCommandOption::new( - CommandOptionType::SubCommand, - "unban", - "Unban a user by ID", + CommandOptionType::Role, + "role", + "The immune role (or none to clear)", ) + .required(false), + ), + ) + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommand, "unban", "Unban a user by ID") .add_sub_option( CreateCommandOption::new( CommandOptionType::String, @@ -118,8 +111,7 @@ pub async fn register_commands(ctx: &Context, guild_id: GuildId) -> Result<(), s ) .required(true), ), - ), - ]; + )]; guild_id.set_commands(&ctx.http, commands).await?; Ok(()) @@ -148,66 +140,85 @@ pub async fn handle_interaction( let content = match name { "mod-channel" => get_channel_option(sub_opts, "channel") - .and_then(|cid| db.set_mod_channel(guild_id, cid).ok().map(|_| "Mod notification channel set!".to_string())) + .and_then(|cid| { + db.set_mod_channel(guild_id, cid) + .ok() + .map(|_| "Mod notification channel set!".to_string()) + }) .unwrap_or_else(|| "Failed to set mod channel.".to_string()), "watched-channel-add" => get_channel_option(sub_opts, "channel") - .and_then(|cid| db.add_watched_channel(guild_id, cid).ok().map(|_| "Channel added to watchlist!".to_string())) + .and_then(|cid| { + db.add_watched_channel(guild_id, cid) + .ok() + .map(|_| "Channel added to watchlist!".to_string()) + }) .unwrap_or_else(|| "Failed to add channel.".to_string()), "watched-channel-remove" => get_channel_option(sub_opts, "channel") - .and_then(|cid| db.remove_watched_channel(guild_id, cid).ok().map(|_| "Channel removed from watchlist!".to_string())) + .and_then(|cid| { + db.remove_watched_channel(guild_id, cid) + .ok() + .map(|_| "Channel removed from watchlist!".to_string()) + }) .unwrap_or_else(|| "Failed to remove channel.".to_string()), "watched-channel-list" => { let channels = db.get_watched_channels(guild_id).unwrap_or_default(); if channels.is_empty() { "No watched channels.".to_string() } else { - format!("Watched channels: {}", channels.iter().map(|c| format!("<#{}>", c)).collect::>().join(", ")) + format!( + "Watched channels: {}", + channels + .iter() + .map(|c| format!("<#{}>", c)) + .collect::>() + .join(", ") + ) } } "ban-reason" => get_string_option(sub_opts, "reason") - .and_then(|r| db.set_ban_reason(guild_id, &r).ok().map(|_| format!("Ban reason set to: {}", r))) + .and_then(|r| { + db.set_ban_reason(guild_id, &r) + .ok() + .map(|_| format!("Ban reason set to: {}", r)) + }) .unwrap_or_else(|| "Failed to set ban reason.".to_string()), - "immune-role" => { - match get_role_option(sub_opts, "role") { - Some(rid) => db.set_immune_role(guild_id, rid) - .map(|_| format!("Immune role set to <@&{}>", rid)) - .unwrap_or_else(|_| "Failed to set immune role.".to_string()), - None => db.clear_immune_role(guild_id) - .map(|_| "Immune role cleared. All users will be auto-banned.".to_string()) - .unwrap_or_else(|_| "Failed to clear immune role.".to_string()), - } - } - "unban" => { - match get_string_option(sub_opts, "user_id") { - Some(uid_str) => { - let uid: u64 = match uid_str.parse() { - Ok(id) => id, - Err(_) => { - respond(ctx, interaction, "Invalid user ID. Must be a number.").await; - return; - } - }; - let _ = db.remove_ban_logs(guild_id, uid); - match guild_id.unban(&ctx.http, UserId::new(uid)).await { - Ok(_) => format!("User <@{}> has been unbanned and ban logs cleared.", uid), - Err(e) => { - eprintln!("Unban error: {:?}", e); - "Ban logs cleared, but Discord unban failed. The user may not be currently banned.".to_string() - } + "immune-role" => match get_role_option(sub_opts, "role") { + Some(rid) => db + .set_immune_role(guild_id, rid) + .map(|_| format!("Immune role set to <@&{}>", rid)) + .unwrap_or_else(|_| "Failed to set immune role.".to_string()), + None => db + .clear_immune_role(guild_id) + .map(|_| "Immune role cleared. All users will be auto-banned.".to_string()) + .unwrap_or_else(|_| "Failed to clear immune role.".to_string()), + }, + "unban" => match get_string_option(sub_opts, "user_id") { + Some(uid_str) => { + let uid: u64 = match uid_str.parse() { + Ok(id) => id, + Err(_) => { + respond(ctx, interaction, "Invalid user ID. Must be a number.").await; + return; + } + }; + let _ = db.remove_ban_logs(guild_id, uid); + match guild_id.unban(&ctx.http, UserId::new(uid)).await { + Ok(_) => format!("User <@{}> has been unbanned and ban logs cleared.", uid), + Err(e) => { + eprintln!("Unban error: {:?}", e); + "Ban logs cleared, but Discord unban failed. The user may not be currently banned.".to_string() } } - None => "No user ID provided.".to_string(), } - } + None => "No user ID provided.".to_string(), + }, _ => "Unknown subcommand. Use /config to see available options.".to_string(), }; respond(ctx, interaction, &content).await; } -fn extract_subcommand( - options: &[CommandDataOption], -) -> Option<(&str, &[CommandDataOption])> { +fn extract_subcommand(options: &[CommandDataOption]) -> Option<(&str, &[CommandDataOption])> { let opt = options.first()?; match &opt.value { CommandDataOptionValue::SubCommand(opts) => Some((&opt.name, opts.as_slice())), @@ -215,10 +226,7 @@ fn extract_subcommand( } } -fn get_channel_option( - options: &[CommandDataOption], - name: &str, -) -> Option { +fn get_channel_option(options: &[CommandDataOption], name: &str) -> Option { options.iter().find(|o| o.name == name).and_then(|opt| { if let CommandDataOptionValue::Channel(c) = &opt.value { Some(*c) @@ -228,10 +236,7 @@ fn get_channel_option( }) } -fn get_role_option( - options: &[CommandDataOption], - name: &str, -) -> Option { +fn get_role_option(options: &[CommandDataOption], name: &str) -> Option { options.iter().find(|o| o.name == name).and_then(|opt| { if let CommandDataOptionValue::Role(r) = &opt.value { Some(*r) @@ -241,10 +246,7 @@ fn get_role_option( }) } -fn get_string_option( - options: &[CommandDataOption], - name: &str, -) -> Option { +fn get_string_option(options: &[CommandDataOption], name: &str) -> Option { options.iter().find(|o| o.name == name).and_then(|opt| { if let CommandDataOptionValue::String(s) = &opt.value { Some(s.clone()) @@ -254,11 +256,7 @@ fn get_string_option( }) } -async fn respond( - ctx: &Context, - interaction: &CommandInteraction, - content: &str, -) { +async fn respond(ctx: &Context, interaction: &CommandInteraction, content: &str) { let _ = interaction .create_response( &ctx.http, @@ -267,4 +265,4 @@ async fn respond( ), ) .await; -} \ No newline at end of file +} diff --git a/src/db.rs b/src/db.rs index 3d82b0d..d4d2086 100644 --- a/src/db.rs +++ b/src/db.rs @@ -13,7 +13,9 @@ impl Database { rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE | rusqlite::OpenFlags::SQLITE_OPEN_CREATE, )?; conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?; - Ok(Database { conn: Mutex::new(conn) }) + Ok(Database { + conn: Mutex::new(conn), + }) } pub fn init(&self) -> Result<()> { @@ -73,9 +75,8 @@ impl Database { pub fn get_watched_channels(&self, guild_id: GuildId) -> Result> { let gid = guild_id.get() as i64; let guard = self.conn.lock().unwrap(); - let mut stmt = guard.prepare( - "SELECT channel_id FROM watched_channels WHERE guild_id = ?", - )?; + let mut stmt = + guard.prepare("SELECT channel_id FROM watched_channels WHERE guild_id = ?")?; let rows = stmt.query_map([gid], |row| row.get::<_, i64>(0))?; let channels: Vec = rows .filter_map(|r| r.ok()) @@ -152,7 +153,13 @@ impl Database { } } - pub fn log_ban(&self, guild_id: GuildId, user_id: u64, channel_id: ChannelId, message_snippet: &str) -> Result<()> { + pub fn log_ban( + &self, + guild_id: GuildId, + user_id: u64, + channel_id: ChannelId, + message_snippet: &str, + ) -> Result<()> { let gid = guild_id.get() as i64; let uid = user_id as i64; let cid = channel_id.get() as i64; @@ -164,11 +171,11 @@ impl Database { } pub fn total_bans(&self) -> Result { - let count: i64 = self.conn.lock().unwrap().query_row( - "SELECT COUNT(*) FROM ban_log", - [], - |row| row.get(0), - )?; + let count: i64 = + self.conn + .lock() + .unwrap() + .query_row("SELECT COUNT(*) FROM ban_log", [], |row| row.get(0))?; Ok(count) } @@ -181,4 +188,4 @@ impl Database { )?; Ok(()) } -} \ No newline at end of file +} diff --git a/src/handlers.rs b/src/handlers.rs index ca6766a..99f0452 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -1,20 +1,16 @@ use crate::commands::{handle_interaction, register_commands}; use crate::db::Database; use serenity::all::ActivityData; +use serenity::model::channel::MessageFlags; use serenity::{ async_trait, builder::{CreateAttachment, CreateMessage}, client::Context, model::{ - application::Interaction, - channel::Message, - gateway::Ready, - guild::Guild, - id::GuildId, + application::Interaction, channel::Message, gateway::Ready, guild::Guild, id::GuildId, user::OnlineStatus, }, }; -use serenity::model::channel::MessageFlags; use std::sync::Arc; pub struct Handler { @@ -24,7 +20,9 @@ pub struct Handler { #[async_trait] impl serenity::client::EventHandler for Handler { async fn ready(&self, _ctx: Context, ready: Ready) { - let shard_info = ready.shard.map_or("N/A".to_string(), |s| format!("{}/{}", s.id.0, s.total)); + let shard_info = ready + .shard + .map_or("N/A".to_string(), |s| format!("{}/{}", s.id.0, s.total)); println!("{} is connected! [Shard {}]", ready.user.name, shard_info); } @@ -41,7 +39,10 @@ impl serenity::client::EventHandler for Handler { guild_count, total_bans ); ctx.set_presence( - Some(ActivityData::watching(format!("{} guilds | {} bans", guild_count, total_bans))), + Some(ActivityData::watching(format!( + "{} guilds | {} bans", + guild_count, total_bans + ))), OnlineStatus::Online, ); } @@ -61,7 +62,10 @@ impl serenity::client::EventHandler for Handler { _ => return, } - if let Err(e) = self.handle_watched_channel_message(&ctx, &msg, guild_id).await { + if let Err(e) = self + .handle_watched_channel_message(&ctx, &msg, guild_id) + .await + { eprintln!("Error handling watched channel message: {:?}", e); } } @@ -121,16 +125,40 @@ impl Handler { if member.roles.is_empty() { String::new() } else { - format!("\nRoles: {}", member.roles.iter().map(|r| format!("<@&{}>", r)).collect::>().join(" ")) + format!( + "\nRoles: {}", + member + .roles + .iter() + .map(|r| format!("<@&{}>", r)) + .collect::>() + .join(" ") + ) } } else { String::new() }; - let copy = format!("**{}** ({}):{}{}", msg.author.name, msg.author.id, roles_line, if msg.content.is_empty() { String::new() } else { format!("\n{}", msg.content) }); + let copy = format!( + "**{}** ({}):{}{}", + msg.author.name, + msg.author.id, + roles_line, + if msg.content.is_empty() { + String::new() + } else { + format!("\n{}", msg.content) + } + ); let _ = mod_channel_id - .send_message(&ctx.http, CreateMessage::new().content(©).files(files).flags(MessageFlags::SUPPRESS_EMBEDS)) + .send_message( + &ctx.http, + CreateMessage::new() + .content(©) + .files(files) + .flags(MessageFlags::SUPPRESS_EMBEDS), + ) .await; let _ = msg.delete(&ctx.http).await; @@ -153,4 +181,4 @@ impl Handler { Ok(()) } -} \ No newline at end of file +} diff --git a/src/main.rs b/src/main.rs index c16943c..207de9a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,4 +37,4 @@ async fn main() { if let Err(why) = client.start().await { println!("Client error: {:?}", why); } -} \ No newline at end of file +}