From f57ffaf076b7c236dec90322ee57139647f6a05b Mon Sep 17 00:00:00 2001 From: Benji Dial Date: Tue, 13 Jan 2026 14:35:38 -0500 Subject: [PATCH] suggestion form --- readme.txt | 23 ++++--- src/bin/gateway.rs | 18 ++++- src/bin/post-suggestion-form.rs | 22 ++++++ src/lib.rs | 116 ++++++++++++++++++++++++++++++-- 4 files changed, 165 insertions(+), 14 deletions(-) create mode 100644 src/bin/post-suggestion-form.rs diff --git a/readme.txt b/readme.txt index 54ce167..845c594 100644 --- a/readme.txt +++ b/readme.txt @@ -8,9 +8,9 @@ will be used to ping users about brackets. === Commands All of these commands must be run with an environment variable BOT_TOKEN set -to the token of the Discord bot. All except for gateway interact with a SQLite3 -database named database.db, which will be created if it does not exist. To run -any of these from the source repository, use the command: +to the token of the Discord bot. Some of these interact with a SQLite3 database +named database.db, which will be created if it does not exist. To run any of +these from the source repository, use the command: BOT_TOKEN='' cargo run --release --bin [arguments] == create-bracket @@ -39,13 +39,13 @@ Each entry has these keys: entirely of decimal digits, then it is interpreted as an ID of a Discord emoji. Otherwise, this is interpreted as a literal Unicode emoji. -== gateway +== gateway -This runs the "gateway" portion of the bot, which receives notifications -from Discord about the toggle role buttons being pressed, and toggles those -roles. This will continue running until there is some error communicating -with Discord. If this is not running, attempting to toggle roles will -display "interaction failed" under the button. +This runs the "gateway" portion of the bot, which receives notifications from +Discord about the toggle role and suggested bracket buttons being pressed, and +handles those interactions. This will continue running until there is some +error communicating with Discord. The "shredder channel id" argument is +the ID of a Discord channel to post bracket suggestions into. == post-polls @@ -59,6 +59,11 @@ bracket after its winner has been announced. This should not be run on the same bracket more than once without running process-polls in between. All polls are posted with a length of 23 hours. +== post-suggestion-form + +This posts a button in the specified channel that users can click to suggest +new brackets. The suggestions are posted in another channel (see gateway). + == process-polls This reads all of the polls that have been posted since the last time this was diff --git a/src/bin/gateway.rs b/src/bin/gateway.rs index f6209c8..c100f59 100644 --- a/src/bin/gateway.rs +++ b/src/bin/gateway.rs @@ -1,4 +1,20 @@ #[tokio::main] async fn main() { - bracket_bot_v4::run_gateway().await; + + let args: Vec = std::env::args().collect(); + if args.len() != 2 { + eprintln!("This must be run with one argument: the channel to post suggested brackets into."); + std::process::exit(1); + } + + let shredder_id = + match args[1].parse::() { + Ok(c) => c, Err(e) => { + eprintln!("Failed to parse argument: {e}"); + std::process::exit(1); + } + }; + + bracket_bot_v4::run_gateway(shredder_id).await; + } diff --git a/src/bin/post-suggestion-form.rs b/src/bin/post-suggestion-form.rs new file mode 100644 index 0000000..f46be04 --- /dev/null +++ b/src/bin/post-suggestion-form.rs @@ -0,0 +1,22 @@ +#[tokio::main] +async fn main() { + + let dcf = bracket_bot_v4::create_discord_conn(); + + let args: Vec = std::env::args().collect(); + if args.len() != 2 { + eprintln!("This must be run with one argument: the channel to post the suggestion form in."); + std::process::exit(1); + } + + let channel_id = + match args[1].parse::() { + Ok(c) => c, Err(e) => { + eprintln!("Failed to parse argument: {e}"); + std::process::exit(1); + } + }; + + bracket_bot_v4::post_suggestion_form(&dcf.await, channel_id).await; + +} diff --git a/src/lib.rs b/src/lib.rs index 46d5a0c..6ccf11e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,6 +140,11 @@ fn get_token() -> String { struct GatewayHandler; +struct ShredderChannelID; +impl serenity::prelude::TypeMapKey for ShredderChannelID { + type Value = u64; +} + #[serenity::async_trait] impl serenity::client::EventHandler for GatewayHandler { @@ -174,11 +179,84 @@ impl serenity::client::EventHandler for GatewayHandler { serenity::builder::CreateInteractionResponseMessage::default(); msg = msg.content(if removing { "Removed role." } else { "Added role." }); msg = msg.ephemeral(true); - let resp= serenity::builder::CreateInteractionResponse::Message(msg); + let resp = serenity::builder::CreateInteractionResponse::Message(msg); cpt.create_response(&ctx, resp).await.unwrap(); } + else if id == "suggest" { + + let mut form = serenity::builder::CreateModal::new("suggest-form", "Suggest a bracket"); + + let name = + serenity::builder::CreateInputText::new( + serenity::model::application::InputTextStyle::Short, + "Bracket name", "name"); + let name_row = serenity::builder::CreateActionRow::InputText(name); + + let mut details= + serenity::builder::CreateInputText::new( + serenity::model::application::InputTextStyle::Paragraph, + "Details (suggested entries, etc)", "details"); + details = details.required(false); + let details_row = serenity::builder::CreateActionRow::InputText(details); + + form = form.components(vec![name_row, details_row]); + + let response = serenity::builder::CreateInteractionResponse::Modal(form); + cpt.create_response(&ctx, response).await.unwrap(); + + } + + } + + else if let serenity::model::application::Interaction::Modal(md) = interaction { + let id = &md.data.custom_id; + + if id == "suggest-form" { + + let name = + if let + serenity::model::application::ActionRowComponent::InputText(name_text) = + &md.data.components[0].components[0] { name_text.value.clone() } else { None }; + + let details = + if let + serenity::model::application::ActionRowComponent::InputText(details_text) = + &md.data.components[1].components[0] { details_text.value.clone() } else { None }; + + let data = ctx.data.read().await; + let shredder_channel_id = data.get::().unwrap(); + let shredder = serenity::model::id::ChannelId::new(*shredder_channel_id); + drop(data); + + let mut msg = format!("## Bracket suggestion: {}", name.unwrap()); + if let Some(d) = details { + let mut sand = d; + while sand.ends_with('`') { + sand.remove(sand.len() - 1); + } + while let Some(i) = sand.find("```") { + sand.remove(i); + } + while sand.starts_with('`') { + sand.remove(0); + } + if sand != "" { + msg = msg + format!("\n```{}```", sand).as_str(); + } + } + shredder.say(&ctx, msg).await.unwrap(); + + let mut msg2 = + serenity::builder::CreateInteractionResponseMessage::default(); + msg2 = msg2.content("Suggestion submitted."); + msg2 = msg2.ephemeral(true); + let r = serenity::builder::CreateInteractionResponse::Message(msg2); + md.create_response(&ctx, r).await.unwrap(); + + } + } } @@ -198,6 +276,29 @@ pub async fn create_discord_conn() -> DiscordConn { } } +async fn post_suggestion_form_core(dc: &DiscordConn, channel_id: u64) -> Result<(), serenity::Error> { + + let channel = serenity::model::id::ChannelId::new(channel_id); + + let mut message = serenity::builder::CreateMessage::new(); + message = message.content("Click the button to suggest a new bracket."); + + let mut button = serenity::builder::CreateButton::new("suggest"); + button = button.label("Suggest a bracket"); + + message = message.button(button); + channel.send_message(&dc.client.http, message).await?; + + Ok(()) + +} + +pub async fn post_suggestion_form(dc: &DiscordConn, channel_id: u64) { + match post_suggestion_form_core(&dc, channel_id).await { + Ok(()) => {}, Err(e) => discord_error(&e) + } +} + async fn create_bracket_core_discord(dc: &DiscordConn, info: &CreateBracketInfo) -> Result<(), serenity::Error> { let channel = serenity::model::id::ChannelId::new(info.channel_id); @@ -730,13 +831,20 @@ pub async fn ping_bracket_role(dbc: &DbConn, dc: &DiscordConn, bracket_id: i32, } -async fn run_gateway_core() -> Result<(), serenity::Error> { +async fn run_gateway_core(shredder_id: u64) -> Result<(), serenity::Error> { + let mut client = create_gateway_client().await?; + + let mut data = client.data.write().await; + data.insert::(shredder_id); + drop(data); + client.start().await + } -pub async fn run_gateway() { - match run_gateway_core().await { +pub async fn run_gateway(shredder_id: u64) { + match run_gateway_core(shredder_id).await { Ok(()) => {}, Err(e) => discord_error(&e) } }