descriptions and pings, new yaml crate

This commit is contained in:
Benji Dial 2026-01-13 11:41:07 -05:00
parent bc9b9959c5
commit bebc021a37
6 changed files with 179 additions and 106 deletions

99
Cargo.lock generated
View file

@ -8,15 +8,6 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
[[package]]
name = "aho-corasick"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
@ -26,6 +17,12 @@ dependencies = [
"libc",
]
[[package]]
name = "arraydeque"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236"
[[package]]
name = "arrayvec"
version = "0.7.6"
@ -85,9 +82,9 @@ version = "4.0.0-snapshot"
dependencies = [
"rand 0.9.2",
"rusqlite",
"rust-yaml",
"serenity",
"tokio",
"yaml-rust2",
]
[[package]]
@ -280,10 +277,13 @@ dependencies = [
]
[[package]]
name = "equivalent"
version = "1.0.2"
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "errno"
@ -724,18 +724,6 @@ dependencies = [
"icu_properties",
]
[[package]]
name = "indexmap"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown 0.16.1",
"serde",
"serde_core",
]
[[package]]
name = "ipnet"
version = "2.11.0"
@ -824,15 +812,6 @@ version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "memmap2"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490"
dependencies = [
"libc",
]
[[package]]
name = "mime"
version = "0.3.17"
@ -1135,35 +1114,6 @@ dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "reqwest"
version = "0.12.28"
@ -1235,18 +1185,6 @@ dependencies = [
"sqlite-wasm-rs",
]
[[package]]
name = "rust-yaml"
version = "0.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22075c416a33b2fc18e6502b3836023436ca39b67cff4ae8300dffa12bc05dfc"
dependencies = [
"base64",
"indexmap",
"memmap2",
"regex",
]
[[package]]
name = "rustc-hash"
version = "2.1.1"
@ -2362,6 +2300,17 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
[[package]]
name = "yaml-rust2"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "631a50d867fafb7093e709d75aaee9e0e0d5deb934021fcea25ac2fe09edc51e"
dependencies = [
"arraydeque",
"encoding_rs",
"hashlink",
]
[[package]]
name = "yoke"
version = "0.8.1"

View file

@ -4,7 +4,7 @@ version = "4.0.0-snapshot"
edition = "2024"
[dependencies]
rust-yaml = "0.0.5"
yaml-rust2 = "0.11.0"
rand = "0.9.2"
rusqlite = { version = "0.38.0", features = [ "bundled" ] }
tokio = { version = "1.49.0", features = [ "rt-multi-thread" ] }

View file

@ -2,24 +2,19 @@ use rand::seq::SliceRandom;
fn parse_yaml(yaml: &String) -> Option<bracket_bot_v4::CreateBracketInfo> {
let content = rust_yaml::Yaml::new().load_str(&yaml).ok()?;
let content = &yaml_rust2::YamlLoader::load_from_str(yaml).ok()?[0];
let bracket_name = String::from(content.get_str("name")?.as_str()?);
let channel_id: u64 = content.get_str("channel_id")?.as_str()?.parse().ok()?;
let role_id: u64 = content.get_str("role_id")?.as_str()?.parse().ok()?;
let entries = content.get_str("entries")?.as_sequence()?;
let bracket_name = String::from(content["name"].as_str()?);
let channel_id: u64 = content["channel_id"].as_str()?.parse::<u64>().ok()?;
let role_id: u64 = content["role_id"].as_str()?.parse::<u64>().ok()?;
let entries = content["entries"].as_vec()?;
let mut entry_infos = Vec::new();
for entry in entries {
let name = String::from(entry.get_str("name")?.as_str()?);
let description = match entry.get_str("description") {
None => None, Some(v) => Some(String::from(v.as_str()?)) };
let emoji = match entry.get_str("emoji") {
None => None, Some(v) => Some(String::from(v.as_str()?)) };
let name = String::from(entry["name"].as_str()?);
let description = entry["description"].as_str().and_then(|s| Some(String::from(s)));
let emoji= entry["emoji"].as_str().and_then(|s| Some(String::from(s)));
entry_infos.push(bracket_bot_v4::CreateBracketEntryInfo { name, description, emoji });
}
entry_infos.shuffle(&mut rand::rng());

4
src/bin/gateway.rs Normal file
View file

@ -0,0 +1,4 @@
#[tokio::main]
async fn main() {
bracket_bot_v4::run_gateway().await;
}

View file

@ -40,4 +40,6 @@ async fn main() {
bracket_bot_v4::post_poll(&mut dbc, &dc, bracket_id, channel_id, &poll).await;
}
bracket_bot_v4::ping_bracket_role(&dbc, &dc, bracket_id, channel_id).await;
}

View file

@ -21,7 +21,7 @@ pub struct CreateBracketInfo {
pub bracket_name: String,
pub channel_id: u64,
pub role_id: u64,
pub entries: Vec<CreateBracketEntryInfo>
pub entries: Vec<CreateBracketEntryInfo>
}
pub struct UnprocessedPoll {
@ -127,18 +127,74 @@ async fn create_discord_conn_core(bot_token: &String)
Ok(DiscordConn { client })
}
pub async fn create_discord_conn() -> DiscordConn {
fn get_token() -> String {
let token = std::env::var("BOT_TOKEN");
match token {
Err(e) => {
eprintln!("Could not get BOT_TOKEN variable: {e}");
std::process::exit(1);
},
Ok(t) => {
match create_discord_conn_core(&t).await {
Ok(dc) => dc, Err(e) => discord_error(&e)
Ok(t) => t
}
}
struct GatewayHandler;
#[serenity::async_trait]
impl serenity::client::EventHandler for GatewayHandler {
async fn interaction_create(
&self, ctx: serenity::client::Context, interaction: serenity::model::application::Interaction) {
if let serenity::model::application::Interaction::Component(cpt) = interaction {
let id = &cpt.data.custom_id;
if id.starts_with("toggle ") {
let role_id = serenity::model::id::RoleId::new(id[7..].parse::<u64>().unwrap());
let guild_id = cpt.guild_id.unwrap();
let member = guild_id.member(&ctx, cpt.user.id).await.unwrap();
let removing = cpt.user.has_role(&ctx, guild_id, role_id).await.unwrap();
let result =
if removing {
member.remove_role(&ctx, role_id).await
}
else {
member.add_role(&ctx, role_id).await
};
match result {
Ok(()) => {}, Err(e) => {
eprintln!("error toggling role {}: {}", role_id, e);
return;
}
}
let mut msg =
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);
cpt.create_response(&ctx, resp).await.unwrap();
}
}
}
}
async fn create_gateway_client() -> Result<serenity::Client, serenity::Error> {
let intents = serenity::model::gateway::GatewayIntents::empty();
let mut builder = serenity::Client::builder(get_token(), intents);
builder = builder.event_handler(GatewayHandler);
builder.await
}
pub async fn create_discord_conn() -> DiscordConn {
match create_discord_conn_core(&get_token()).await {
Ok(dc) => dc, Err(e) => discord_error(&e)
}
}
@ -149,14 +205,14 @@ async fn create_bracket_core_discord(dc: &DiscordConn, info: &CreateBracketInfo)
let mut message = serenity::builder::CreateMessage::new();
message = message.content(format!("# Bracket: {}", info.bracket_name));
let mut button = serenity::builder::CreateButton::new(info.role_id.to_string());
button = button.label("toggle pings");
let button_id = String::from("toggle ") + &info.role_id.to_string();
let mut button = serenity::builder::CreateButton::new(button_id);
button = button.label("Toggle pings");
//message = message.button(button);
message = message.button(button);
let sent = channel.send_message(&dc.client.http, message).await?;
//sent.pin(&dc.client.http).await
Ok(())
sent.pin(&dc.client.http).await
}
@ -405,7 +461,7 @@ pub fn get_channel_id(dbc: &mut DbConn, bracket_id: i32) -> u64 {
}
fn go_to_next_round_core_db(dbc: &mut DbConn, bracket_id: i32)
-> Result<(i32, Vec<String>), rusqlite::Error> {
-> Result<(i32, Vec<String>, Option<String>), rusqlite::Error> {
let trans = dbc.conn.transaction()?;
@ -436,12 +492,23 @@ fn go_to_next_round_core_db(dbc: &mut DbConn, bracket_id: i32)
last_entry_id = Some(row.get(0)?);
}
let winner_description: Option<String> =
if all_entries.len() == 1 {
trans.query_one(
"SELECT description FROM entries WHERE rowid = ?",
(last_entry_id,),
|r| r.get(0))?
}
else {
None
};
drop(rows);
drop(statement);
if all_entries.len() == 1 {
trans.commit()?;
return Ok((next_round, all_entries));
return Ok((next_round, all_entries, winner_description));
}
if all_entries.len() % 2 == 1 {
@ -451,13 +518,14 @@ fn go_to_next_round_core_db(dbc: &mut DbConn, bracket_id: i32)
}
trans.commit()?;
Ok((next_round, all_entries))
Ok((next_round, all_entries, winner_description))
}
async fn go_to_next_round_core_discord(dc: &DiscordConn, new_round: i32, channel_id: u64, names: &Vec<String>)
async fn go_to_next_round_core_discord(
dc: &DiscordConn, new_round: i32, channel_id: u64, names: &Vec<String>, winner_description: &Option<String>)
-> Result<(), serenity::Error> {
if names.len() == 0 {
@ -470,6 +538,11 @@ async fn go_to_next_round_core_discord(dc: &DiscordConn, new_round: i32, channel
channel.say(
&dc.client.http,
format!("# WINNER: {}", names[0])).await?;
if let Some(wd) = winner_description.as_ref() {
channel.say(
&dc.client.http,
wd).await?;
}
return Ok(())
}
@ -489,11 +562,11 @@ async fn go_to_next_round_core_discord(dc: &DiscordConn, new_round: i32, channel
}
pub async fn go_to_next_round(dbc: &mut DbConn, dc: &DiscordConn, bracket_id: i32, channel_id: u64) {
let (new_round, names) =
let (new_round, names, winner_description) =
match go_to_next_round_core_db(dbc, bracket_id) {
Ok(t) => t, Err(e) => db_error(&e)
};
match go_to_next_round_core_discord(&dc, new_round, channel_id, &names).await {
match go_to_next_round_core_discord(&dc, new_round, channel_id, &names, &winner_description).await {
Ok(()) => {}, Err(e) => discord_error(&e)
}
}
@ -539,7 +612,16 @@ fn create_poll_answer(entry: &EntryForPoll) -> serenity::builder::CreatePollAnsw
}
pub async fn post_poll_core_discord(
async fn maybe_post_description(dc: &DiscordConn, channel_id: u64, description: &Option<String>)
-> Result<(), serenity::Error> {
if let Some(s) = description {
let channel = serenity::model::id::ChannelId::new(channel_id);
channel.say(&dc.client.http, s).await?;
}
Ok(())
}
async fn post_poll_core_discord(
dc: &DiscordConn, channel_id: u64, entry_1: &EntryForPoll, entry_2: &EntryForPoll)
-> Result<PostedPoll, serenity::Error> {
@ -560,6 +642,8 @@ pub async fn post_poll_core_discord(
message = message.poll(pollr);
let posted = channel.send_message(&dc.client.http, message).await?;
maybe_post_description(dc, channel_id, &entry_1.description).await?;
maybe_post_description(dc, channel_id, &entry_2.description).await?;
let mut answer_id_1: Option<i32> = None;
let mut answer_id_2: Option<i32> = None;
@ -617,3 +701,42 @@ pub async fn post_poll(dbc: &mut DbConn, dc: &DiscordConn, bracket_id: i32, chan
}
}
fn get_bracket_role(dbc: &DbConn, bracket_id: i32) -> Result<String, rusqlite::Error> {
Ok(
dbc.conn.query_one(
"SELECT role_id FROM brackets WHERE rowid = ?",
(bracket_id,),
|r| r.get::<usize, String>(0))?)
}
pub async fn ping_bracket_role(dbc: &DbConn, dc: &DiscordConn, bracket_id: i32, channel_id: u64) {
let role_id =
match get_bracket_role(&dbc, bracket_id) {
Ok(r) => r, Err(e) => db_error(&e)
};
let mut m = serenity::builder::CreateAllowedMentions::new();
m = m.all_roles(true);
let mut msg = serenity::builder::CreateMessage::new();
msg = msg.allowed_mentions(m);
msg = msg.content(format!("<@&{}>", role_id));
let channel = serenity::model::id::ChannelId::new(channel_id);
match channel.send_message(&dc.client.http, msg).await {
Ok(_) => {}, Err(e) => discord_error(&e)
}
}
async fn run_gateway_core() -> Result<(), serenity::Error> {
let mut client = create_gateway_client().await?;
client.start().await
}
pub async fn run_gateway() {
match run_gateway_core().await {
Ok(()) => {}, Err(e) => discord_error(&e)
}
}