first commit, incomplete
This commit is contained in:
commit
414d6408b4
7 changed files with 3232 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
database.db
|
||||||
|
target
|
||||||
2472
Cargo.lock
generated
Normal file
2472
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "bracket_bot_v4"
|
||||||
|
version = "4.0.0-snapshot"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rust-yaml = "0.0.5"
|
||||||
|
rand = "0.9.2"
|
||||||
|
rusqlite = { version = "0.38.0", features = [ "bundled" ] }
|
||||||
|
tokio = { version = "1.49.0", features = [ "rt-multi-thread" ] }
|
||||||
|
|
||||||
|
[dependencies.serenity]
|
||||||
|
version = "0.12.5"
|
||||||
|
default-features = false
|
||||||
|
features = [ "builder", "cache", "chrono", "client", "gateway", "model", "rustls_backend" ]
|
||||||
63
src/bin/create-bracket.rs
Normal file
63
src/bin/create-bracket.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
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 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 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()?)) };
|
||||||
|
|
||||||
|
entry_infos.push(bracket_bot_v4::CreateBracketEntryInfo { name, description, emoji });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
entry_infos.shuffle(&mut rand::rng());
|
||||||
|
|
||||||
|
Some(bracket_bot_v4::CreateBracketInfo { bracket_name, channel_id, role_id, entries: entry_infos })
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
|
||||||
|
let discord_conn_future = bracket_bot_v4::create_discord_conn();
|
||||||
|
|
||||||
|
let mut db_conn = bracket_bot_v4::create_db_conn();
|
||||||
|
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
if args.len() != 2 {
|
||||||
|
eprintln!("This must be run with one argument: the path to the bracket info YAML file (see readme.txt for format).");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let content_str = match std::fs::read_to_string(&args[1]) {
|
||||||
|
Ok(s) => s, Err(e) => {
|
||||||
|
eprintln!("Failed to read YAML file: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let create_info = match parse_yaml(&content_str) {
|
||||||
|
Some(i) => i, None => {
|
||||||
|
eprintln!("Failed to parse YAML file.");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let discord_conn = discord_conn_future.await;
|
||||||
|
|
||||||
|
let bracket_id = bracket_bot_v4::create_bracket(&mut db_conn, &discord_conn, &create_info).await;
|
||||||
|
println!("Bracket ID: {}", bracket_id);
|
||||||
|
|
||||||
|
}
|
||||||
43
src/bin/post-polls.rs
Normal file
43
src/bin/post-polls.rs
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
|
||||||
|
let dcf = bracket_bot_v4::create_discord_conn();
|
||||||
|
let mut dbc = bracket_bot_v4::create_db_conn();
|
||||||
|
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
if args.len() != 3 {
|
||||||
|
eprintln!("This must be run with two arguments: the ID of the bracket, and the number of polls to post");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bracket_id: i32 =
|
||||||
|
match args[1].parse() {
|
||||||
|
Ok(v) => v, Err(e) => {
|
||||||
|
eprintln!("failed to parse first argument: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let poll_count: i32 =
|
||||||
|
match args[2].parse() {
|
||||||
|
Ok(v) => v, Err(e) => {
|
||||||
|
eprintln!("failed to parse second argument: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut polls = bracket_bot_v4::get_polls_to_post(&mut dbc, bracket_id, poll_count);
|
||||||
|
let channel_id = bracket_bot_v4::get_channel_id(&mut dbc, bracket_id);
|
||||||
|
|
||||||
|
let dc = dcf.await;
|
||||||
|
|
||||||
|
if polls.len() == 0 {
|
||||||
|
bracket_bot_v4::go_to_next_round(&mut dbc, &dc, bracket_id, channel_id).await;
|
||||||
|
polls = bracket_bot_v4::get_polls_to_post(&mut dbc, bracket_id, poll_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
for poll in polls {
|
||||||
|
bracket_bot_v4::post_poll(&mut dbc, &dc, bracket_id, channel_id, &poll).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
18
src/bin/process-polls.rs
Normal file
18
src/bin/process-polls.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
|
||||||
|
let dcf = bracket_bot_v4::create_discord_conn();
|
||||||
|
let mut dbc = bracket_bot_v4::create_db_conn();
|
||||||
|
let unprocessed = bracket_bot_v4::get_unprocessed_polls(&dbc);
|
||||||
|
|
||||||
|
let dc = dcf.await;
|
||||||
|
let mut result_futures = Vec::new();
|
||||||
|
for u in &unprocessed {
|
||||||
|
result_futures.push((u, bracket_bot_v4::get_poll_results(&dc, u)));
|
||||||
|
}
|
||||||
|
|
||||||
|
for rf in result_futures {
|
||||||
|
bracket_bot_v4::process_poll(&mut dbc, &rf.0, &rf.1.await, &mut rand::rng());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
619
src/lib.rs
Normal file
619
src/lib.rs
Normal file
|
|
@ -0,0 +1,619 @@
|
||||||
|
use rand::RngCore;
|
||||||
|
|
||||||
|
fn db_error(e: &rusqlite::Error) -> ! {
|
||||||
|
eprintln!("Database error: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discord_error(e: &serenity::Error) -> ! {
|
||||||
|
eprintln!("Discord error: {e}");
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CreateBracketEntryInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
//either an actual unicode emoji or a discord emoji id
|
||||||
|
pub emoji: Option<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct CreateBracketInfo {
|
||||||
|
pub bracket_name: String,
|
||||||
|
pub channel_id: u64,
|
||||||
|
pub role_id: u64,
|
||||||
|
pub entries: Vec<CreateBracketEntryInfo>
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct UnprocessedPoll {
|
||||||
|
pub poll_id: i32,
|
||||||
|
pub channel_id: u64,
|
||||||
|
pub message_id: u64,
|
||||||
|
pub bracket_id: i32,
|
||||||
|
pub from_round: i32,
|
||||||
|
pub entry_id_1: i32,
|
||||||
|
pub entry_id_2: i32,
|
||||||
|
pub entry_answer_id_1: i32,
|
||||||
|
pub entry_answer_id_2: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PollResults {
|
||||||
|
pub entry_1_votes: u64,
|
||||||
|
pub entry_2_votes: u64
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PollToPost {
|
||||||
|
pub bracket_id: i32,
|
||||||
|
pub from_round: i32,
|
||||||
|
pub entry_id_1: i32,
|
||||||
|
pub entry_id_2: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EntryForPoll {
|
||||||
|
pub name: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub emoji: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PostedPoll {
|
||||||
|
pub message_id: u64,
|
||||||
|
pub answer_id_1: i32,
|
||||||
|
pub answer_id_2: i32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DbConn {
|
||||||
|
conn: rusqlite::Connection
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DiscordConn {
|
||||||
|
client: serenity::Client
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_db_conn_core() -> Result<DbConn, rusqlite::Error> {
|
||||||
|
|
||||||
|
let mut conn = rusqlite::Connection::open("database.db")?;
|
||||||
|
|
||||||
|
if !conn.table_exists(None, "brackets")? {
|
||||||
|
|
||||||
|
let trans = conn.transaction()?;
|
||||||
|
|
||||||
|
trans.execute(
|
||||||
|
"CREATE TABLE brackets(
|
||||||
|
channel_id TEXT NOT NULL,
|
||||||
|
role_id TEXT NOT NULL,
|
||||||
|
on_round INTEGER,
|
||||||
|
next_entry_pos INTEGER NOT NULL)", ())?;
|
||||||
|
|
||||||
|
trans.execute(
|
||||||
|
"CREATE TABLE entries(
|
||||||
|
bracket_id INTEGER NOT NULL,
|
||||||
|
bracket_pos INTEGER NOT NULL,
|
||||||
|
in_round INTEGER,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
emoji TEXT)", ())?;
|
||||||
|
|
||||||
|
trans.execute(
|
||||||
|
"CREATE TABLE polls(
|
||||||
|
channel_id TEXT NOT NULL,
|
||||||
|
message_id TEXT UNIQUE NOT NULL,
|
||||||
|
timestamp INTEGER NOT NULL,
|
||||||
|
bracket_id INTEGER NOT NULL,
|
||||||
|
from_round INTEGER NOT NULL,
|
||||||
|
entry_id_1 INTEGER NOT NULL,
|
||||||
|
entry_id_2 INTEGER NOT NULL,
|
||||||
|
entry_answer_id_1 INTEGER NOT NULL,
|
||||||
|
entry_answer_id_2 INTEGER NOT NULL,
|
||||||
|
processed INTEGER NOT NULL)", ())?;
|
||||||
|
|
||||||
|
trans.commit()?;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DbConn { conn: conn })
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_db_conn() -> DbConn {
|
||||||
|
match create_db_conn_core() {
|
||||||
|
Ok(dbc) => dbc, Err(e) => db_error(&e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_discord_conn_core(bot_token: &String)
|
||||||
|
-> Result<DiscordConn, serenity::Error> {
|
||||||
|
let intents = serenity::model::gateway::GatewayIntents::empty();
|
||||||
|
let builder = serenity::Client::builder(bot_token, intents);
|
||||||
|
let client = builder.await?;
|
||||||
|
Ok(DiscordConn { client })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_discord_conn() -> DiscordConn {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_bracket_core_discord(dc: &DiscordConn, info: &CreateBracketInfo) -> Result<(), serenity::Error> {
|
||||||
|
|
||||||
|
let channel = serenity::model::id::ChannelId::new(info.channel_id);
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
//message = message.button(button);
|
||||||
|
|
||||||
|
let sent = channel.send_message(&dc.client.http, message).await?;
|
||||||
|
//sent.pin(&dc.client.http).await
|
||||||
|
Ok(())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_bracket_core_db(dbc: &mut DbConn, info: &CreateBracketInfo) -> Result<i32, rusqlite::Error> {
|
||||||
|
|
||||||
|
let trans = dbc.conn.transaction()?;
|
||||||
|
|
||||||
|
trans.execute(
|
||||||
|
"INSERT INTO brackets(channel_id, role_id, on_round, next_entry_pos) VALUES(?, ?, 0, ?)",
|
||||||
|
(&info.channel_id.to_string(), &info.role_id.to_string(), info.entries.len() as i32))?;
|
||||||
|
|
||||||
|
let bracket_id = trans.last_insert_rowid() as i32;
|
||||||
|
|
||||||
|
let mut next_pos: i32 = 0;
|
||||||
|
for entry in &info.entries {
|
||||||
|
trans.execute(
|
||||||
|
"INSERT INTO entries(bracket_id, bracket_pos, in_round, name, description, emoji) VALUES(?, ?, 1, ?, ?, ?)",
|
||||||
|
(bracket_id, next_pos, &entry.name, &entry.description, &entry.emoji))?;
|
||||||
|
next_pos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
trans.commit()?;
|
||||||
|
|
||||||
|
Ok(bracket_id)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn create_bracket(dbc: &mut DbConn, dc: &DiscordConn, info: &CreateBracketInfo) -> i32 {
|
||||||
|
|
||||||
|
let df = create_bracket_core_discord(dc, info);
|
||||||
|
|
||||||
|
match create_bracket_core_db(dbc, info) {
|
||||||
|
Err(e) => db_error(&e),
|
||||||
|
Ok(id) => {
|
||||||
|
match df.await {
|
||||||
|
Err(e) => discord_error(&e),
|
||||||
|
Ok(()) => id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_unprocessed_polls_core(dbc: &DbConn) -> Result<Vec<UnprocessedPoll>, rusqlite::Error> {
|
||||||
|
|
||||||
|
let mut statement = dbc.conn.prepare(
|
||||||
|
"SELECT rowid, channel_id, message_id, bracket_id, from_round, entry_id_1, entry_id_2, entry_answer_id_1, entry_answer_id_2 FROM polls WHERE processed = 0")?;
|
||||||
|
|
||||||
|
let mut rows = statement.query([])?;
|
||||||
|
|
||||||
|
let mut result = Vec::new();
|
||||||
|
while let Some(row) = rows.next()? {
|
||||||
|
result.push(UnprocessedPoll {
|
||||||
|
poll_id: row.get(0)?,
|
||||||
|
channel_id: row.get::<usize, String>(1)?.parse().unwrap(),
|
||||||
|
message_id: row.get::<usize, String>(2)?.parse().unwrap(),
|
||||||
|
bracket_id: row.get(3)?,
|
||||||
|
from_round: row.get(4)?,
|
||||||
|
entry_id_1: row.get(5)?,
|
||||||
|
entry_id_2: row.get(6)?,
|
||||||
|
entry_answer_id_1: row.get(7)?,
|
||||||
|
entry_answer_id_2: row.get(8)?});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_unprocessed_polls(dbc: &DbConn) -> Vec<UnprocessedPoll> {
|
||||||
|
match get_unprocessed_polls_core(dbc) {
|
||||||
|
Ok(p) => p, Err(e) => db_error(&e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_poll_results_core(dc: &DiscordConn, poll: &UnprocessedPoll)
|
||||||
|
-> Result<PollResults, serenity::Error> {
|
||||||
|
|
||||||
|
let mut sleep_interval = 60;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
|
||||||
|
let channel = serenity::model::id::ChannelId::new(poll.channel_id);
|
||||||
|
let message_in_channel = serenity::model::id::MessageId::new(poll.message_id);
|
||||||
|
let message = channel.message(&dc.client.http, message_in_channel).await?;
|
||||||
|
let results = message.poll.unwrap().results.unwrap();
|
||||||
|
|
||||||
|
if results.is_finalized {
|
||||||
|
|
||||||
|
let mut entry_1_votes = 0;
|
||||||
|
let mut entry_2_votes = 0;
|
||||||
|
for count in results.answer_counts {
|
||||||
|
if count.id.get() == poll.entry_answer_id_1 as u64 {
|
||||||
|
entry_1_votes = count.count;
|
||||||
|
}
|
||||||
|
else if count.id.get() == poll.entry_answer_id_2 as u64 {
|
||||||
|
entry_2_votes = count.count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(PollResults { entry_1_votes, entry_2_votes });
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
eprintln!("Poll {} not processed, sleeping for {} seconds...", poll.poll_id, sleep_interval);
|
||||||
|
tokio::time::sleep(core::time::Duration::from_secs(sleep_interval)).await;
|
||||||
|
sleep_interval *= 2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_poll_results(dc: &DiscordConn, poll: &UnprocessedPoll) -> PollResults {
|
||||||
|
match get_poll_results_core(dc, poll).await {
|
||||||
|
Ok(r) => r, Err(e) => discord_error(&e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_poll_core(dbc: &mut DbConn, poll: &UnprocessedPoll, results: &PollResults, rng: &mut rand::rngs::ThreadRng)
|
||||||
|
-> Result<(), rusqlite::Error> {
|
||||||
|
|
||||||
|
let trans = dbc.conn.transaction()?;
|
||||||
|
|
||||||
|
let next_round = poll.from_round + 1;
|
||||||
|
|
||||||
|
if results.entry_1_votes != results.entry_2_votes {
|
||||||
|
|
||||||
|
let (winner_id, loser_id) =
|
||||||
|
if results.entry_1_votes < results.entry_2_votes {
|
||||||
|
(poll.entry_id_2, poll.entry_id_1)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
(poll.entry_id_1, poll.entry_id_2)
|
||||||
|
};
|
||||||
|
|
||||||
|
trans.execute(
|
||||||
|
"UPDATE entries SET in_round = ? WHERE rowid = ?",
|
||||||
|
(next_round, winner_id))?;
|
||||||
|
trans.execute(
|
||||||
|
"UPDATE entries SET in_round = NULL WHERE rowid = ?",
|
||||||
|
(loser_id,))?;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
|
||||||
|
let next_pos: i32 =
|
||||||
|
trans.query_one(
|
||||||
|
"SELECT next_entry_pos FROM brackets WHERE rowid = ?",
|
||||||
|
(poll.bracket_id,),
|
||||||
|
|r| r.get(0))?;
|
||||||
|
trans.execute(
|
||||||
|
"UPDATE brackets SET next_entry_pos = ? WHERE rowid = ?",
|
||||||
|
(next_pos + 1, poll.bracket_id))?;
|
||||||
|
|
||||||
|
let (keep_pos_id, lose_pos_id) =
|
||||||
|
if rng.next_u32() % 2 == 0 {
|
||||||
|
(poll.entry_id_2, poll.entry_id_1)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
(poll.entry_id_1, poll.entry_id_2)
|
||||||
|
};
|
||||||
|
|
||||||
|
trans.execute(
|
||||||
|
"UPDATE entries SET in_round = ? WHERE rowid = ?",
|
||||||
|
(next_round, keep_pos_id))?;
|
||||||
|
trans.execute(
|
||||||
|
"UPDATE entries SET in_round = ?, bracket_pos = ? WHERE rowid = ?",
|
||||||
|
(next_round, next_pos, lose_pos_id))?;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
trans.execute(
|
||||||
|
"UPDATE polls SET processed = 1 WHERE rowid = ?",
|
||||||
|
(poll.poll_id,))?;
|
||||||
|
|
||||||
|
trans.commit()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_poll(dbc: &mut DbConn, poll: &UnprocessedPoll, results: &PollResults, rng: &mut rand::rngs::ThreadRng) {
|
||||||
|
match process_poll_core(dbc, poll, results, rng) {
|
||||||
|
Ok(()) => {}, Err(e) => db_error(&e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_polls_to_post_core(dbc: &mut DbConn, bracket_id: i32, count: i32)
|
||||||
|
-> Result<Vec<PollToPost>, rusqlite::Error> {
|
||||||
|
|
||||||
|
let mut polls = Vec::new();
|
||||||
|
|
||||||
|
let trans = dbc.conn.transaction()?;
|
||||||
|
|
||||||
|
let round: i32 =
|
||||||
|
trans.query_one(
|
||||||
|
"SELECT on_round FROM brackets WHERE rowid = ?",
|
||||||
|
(bracket_id,),
|
||||||
|
|r| r.get(0))?;
|
||||||
|
|
||||||
|
let mut statement =
|
||||||
|
trans.prepare(
|
||||||
|
"SELECT rowid FROM entries
|
||||||
|
WHERE bracket_id = ? AND in_round = ?
|
||||||
|
ORDER BY bracket_pos LIMIT ?")?;
|
||||||
|
|
||||||
|
let mut rows = statement.query([bracket_id as i64, round as i64, (count * 2) as i64])?;
|
||||||
|
|
||||||
|
let mut entry_ids: Vec<i32> = Vec::new();
|
||||||
|
while let Some(row) = rows.next()? {
|
||||||
|
entry_ids.push(row.get(0)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in 0..(entry_ids.len() / 2) {
|
||||||
|
polls.push(PollToPost {
|
||||||
|
bracket_id,
|
||||||
|
from_round: round,
|
||||||
|
entry_id_1: entry_ids[2 * i],
|
||||||
|
entry_id_2: entry_ids[2 * i + 1] });
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(polls)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_polls_to_post(dbc: &mut DbConn, bracket_id: i32, count: i32) -> Vec<PollToPost> {
|
||||||
|
match get_polls_to_post_core(dbc, bracket_id, count) {
|
||||||
|
Ok(v) => v, Err(e) => db_error(&e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_channel_id_core(dbc: &mut DbConn, bracket_id: i32) -> Result<u64, rusqlite::Error> {
|
||||||
|
|
||||||
|
let channel_id_string =
|
||||||
|
dbc.conn.query_one(
|
||||||
|
"SELECT channel_id FROM brackets WHERE rowid = ?",
|
||||||
|
(bracket_id,),
|
||||||
|
|r| r.get::<usize, String>(0));
|
||||||
|
|
||||||
|
Ok(channel_id_string?.parse().unwrap())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_channel_id(dbc: &mut DbConn, bracket_id: i32) -> u64 {
|
||||||
|
match get_channel_id_core(dbc, bracket_id) {
|
||||||
|
Ok(i) => i, Err(e) => db_error(&e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn go_to_next_round_core_db(dbc: &mut DbConn, bracket_id: i32)
|
||||||
|
-> Result<(i32, Vec<String>), rusqlite::Error> {
|
||||||
|
|
||||||
|
let trans = dbc.conn.transaction()?;
|
||||||
|
|
||||||
|
let current_round: i32 =
|
||||||
|
trans.query_one(
|
||||||
|
"SELECT on_round FROM brackets WHERE rowid = ?",
|
||||||
|
(bracket_id,),
|
||||||
|
|r| r.get(0))?;
|
||||||
|
|
||||||
|
let next_round = current_round + 1;
|
||||||
|
|
||||||
|
trans.execute(
|
||||||
|
"UPDATE brackets SET on_round = ? WHERE rowid = ?",
|
||||||
|
(next_round, bracket_id))?;
|
||||||
|
|
||||||
|
let mut all_entries: Vec<String> = Vec::new();
|
||||||
|
let mut last_entry_id: Option<i32> = None;
|
||||||
|
|
||||||
|
let mut statement =
|
||||||
|
trans.prepare(
|
||||||
|
"SELECT rowid, name FROM entries
|
||||||
|
WHERE bracket_id = ? AND in_round = ?
|
||||||
|
ORDER BY bracket_pos")?;
|
||||||
|
|
||||||
|
let mut rows = statement.query([bracket_id, next_round])?;
|
||||||
|
while let Some(row) = rows.next()? {
|
||||||
|
all_entries.push(row.get(1)?);
|
||||||
|
last_entry_id = Some(row.get(0)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(rows);
|
||||||
|
drop(statement);
|
||||||
|
|
||||||
|
if all_entries.len() == 1 {
|
||||||
|
trans.commit()?;
|
||||||
|
return Ok((next_round, all_entries));
|
||||||
|
}
|
||||||
|
|
||||||
|
if all_entries.len() % 2 == 1 {
|
||||||
|
trans.execute(
|
||||||
|
"UPDATE entries SET in_round = ? WHERE rowid = ?",
|
||||||
|
(next_round + 1, last_entry_id.unwrap()))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
trans.commit()?;
|
||||||
|
Ok((next_round, all_entries))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
async fn go_to_next_round_core_discord(dc: &DiscordConn, new_round: i32, channel_id: u64, names: &Vec<String>)
|
||||||
|
-> Result<(), serenity::Error> {
|
||||||
|
|
||||||
|
if names.len() == 0 {
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let channel = serenity::model::id::ChannelId::new(channel_id);
|
||||||
|
|
||||||
|
if names.len() == 1 {
|
||||||
|
channel.say(
|
||||||
|
&dc.client.http,
|
||||||
|
format!("# WINNER: {}", names[0])).await?;
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut msg = format!("# Round {}\nPolls this round:", new_round);
|
||||||
|
|
||||||
|
for i in 0..(names.len() / 2) {
|
||||||
|
msg += format!("\n* {} vs {}", names[2 * i], names[2 * i + 1]).as_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
if names.len() % 2 == 1 {
|
||||||
|
msg += format!("\nThere are an odd number, so {} will advance with no poll.", names[names.len() - 1]).as_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.say(&dc.client.http, msg).await?;
|
||||||
|
Ok(())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn go_to_next_round(dbc: &mut DbConn, dc: &DiscordConn, bracket_id: i32, channel_id: u64) {
|
||||||
|
let (new_round, names) =
|
||||||
|
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 {
|
||||||
|
Ok(()) => {}, Err(e) => discord_error(&e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_entry(dbc: &DbConn, entry_id: i32) -> Result<EntryForPoll, rusqlite::Error> {
|
||||||
|
dbc.conn.query_one(
|
||||||
|
"SELECT name, description, emoji FROM entries WHERE rowid = ?",
|
||||||
|
(entry_id,),
|
||||||
|
|r|
|
||||||
|
Ok(EntryForPoll {
|
||||||
|
name: r.get(0)?,
|
||||||
|
description: r.get(1)?,
|
||||||
|
emoji: r.get(2)?
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_digits(s: &String) -> bool {
|
||||||
|
for c in s.chars() {
|
||||||
|
if !char::is_ascii_digit(&c) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_poll_answer(entry: &EntryForPoll) -> serenity::builder::CreatePollAnswer {
|
||||||
|
|
||||||
|
let mut answer = serenity::builder::CreatePollAnswer::new();
|
||||||
|
answer = answer.text(entry.name.clone());
|
||||||
|
match entry.emoji.clone() {
|
||||||
|
None => answer,
|
||||||
|
Some(e) => {
|
||||||
|
answer.emoji(
|
||||||
|
if all_digits(&e) {
|
||||||
|
serenity::model::channel::PollMediaEmoji::Id(
|
||||||
|
serenity::model::id::EmojiId::new(e.parse().unwrap()))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
serenity::model::channel::PollMediaEmoji::Name(e)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn post_poll_core_discord(
|
||||||
|
dc: &DiscordConn, channel_id: u64, entry_1: &EntryForPoll, entry_2: &EntryForPoll)
|
||||||
|
-> Result<PostedPoll, serenity::Error> {
|
||||||
|
|
||||||
|
let channel = serenity::model::id::ChannelId::new(channel_id);
|
||||||
|
|
||||||
|
let mut message = serenity::builder::CreateMessage::new();
|
||||||
|
|
||||||
|
let pollq = serenity::builder::CreatePoll::new();
|
||||||
|
let polla = pollq.question("Poll");
|
||||||
|
|
||||||
|
let mut answers = Vec::new();
|
||||||
|
answers.push(create_poll_answer(&entry_1));
|
||||||
|
answers.push(create_poll_answer(&entry_2));
|
||||||
|
|
||||||
|
let polld = polla.answers(answers);
|
||||||
|
let pollr = polld.duration(std::time::Duration::from_hours(1));
|
||||||
|
|
||||||
|
message = message.poll(pollr);
|
||||||
|
|
||||||
|
let posted = channel.send_message(&dc.client.http, message).await?;
|
||||||
|
|
||||||
|
let mut answer_id_1: Option<i32> = None;
|
||||||
|
let mut answer_id_2: Option<i32> = None;
|
||||||
|
|
||||||
|
for answer in posted.poll.unwrap().answers {
|
||||||
|
let name = answer.poll_media.text.unwrap();
|
||||||
|
if name == entry_1.name {
|
||||||
|
answer_id_1 = Some(answer.answer_id.get() as i32);
|
||||||
|
}
|
||||||
|
else if name == entry_2.name {
|
||||||
|
answer_id_2 = Some(answer.answer_id.get() as i32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(PostedPoll {
|
||||||
|
message_id: posted.id.get(),
|
||||||
|
answer_id_1: answer_id_1.unwrap(),
|
||||||
|
answer_id_2: answer_id_2.unwrap()
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fn post_poll_core_db(
|
||||||
|
dbc: &mut DbConn, bracket_id: i32, channel_id: u64, to_post: &PollToPost, posted: &PostedPoll)
|
||||||
|
-> Result<(), rusqlite::Error> {
|
||||||
|
|
||||||
|
dbc.conn.execute(
|
||||||
|
"INSERT INTO polls(
|
||||||
|
channel_id, message_id, timestamp, bracket_id, from_round,
|
||||||
|
entry_id_1, entry_id_2, entry_answer_id_1, entry_answer_id_2, processed)
|
||||||
|
VALUES(?, ?, unixepoch('now'), ?, ?, ?, ?, ?, ?, 0)",
|
||||||
|
(channel_id.to_string(), posted.message_id.to_string(), bracket_id, to_post.from_round,
|
||||||
|
to_post.entry_id_1, to_post.entry_id_2, posted.answer_id_1, posted.answer_id_2))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn post_poll(dbc: &mut DbConn, dc: &DiscordConn, bracket_id: i32, channel_id: u64, poll: &PollToPost) {
|
||||||
|
|
||||||
|
let entry_1 = match get_entry(dbc, poll.entry_id_1) {
|
||||||
|
Ok(e) => e, Err(e) => db_error(&e)
|
||||||
|
};
|
||||||
|
|
||||||
|
let entry_2 = match get_entry(dbc, poll.entry_id_2) {
|
||||||
|
Ok(e) => e, Err(e) => db_error(&e)
|
||||||
|
};
|
||||||
|
|
||||||
|
let posted = match post_poll_core_discord(dc, channel_id, &entry_1, &entry_2).await {
|
||||||
|
Ok(p) => p, Err(e) => discord_error(&e)
|
||||||
|
};
|
||||||
|
|
||||||
|
match post_poll_core_db(dbc, bracket_id, channel_id, &poll, &posted) {
|
||||||
|
Ok(()) => (), Err(e) => db_error(&e)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue