From 04a487cd8fc693af777dc7a69a808548fb9a36a6 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Sep 2024 15:07:29 +0100 Subject: [PATCH 001/170] fix: rename ``get_wolves`` to be just for clubs/socs --- src/bin/update_data.rs | 5 +++-- src/commands/add_server.rs | 4 ++-- src/lib.rs | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index 22bd5bd..ec3713a 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -4,7 +4,7 @@ use serenity::{ model::gateway::{GatewayIntents, Ready}, Client, }; -use skynet_discord_bot::{db_init, get_config, get_data::get_wolves, Config, DataBase}; +use skynet_discord_bot::{db_init, get_config, get_data::get_wolves_cns, Config, DataBase}; use std::{process, sync::Arc}; use tokio::sync::RwLock; @@ -43,7 +43,8 @@ impl EventHandler for Handler { let ctx = Arc::new(ctx); println!("{} is connected!", ready.user.name); - get_wolves(&ctx).await; + // get the data for each individual club/soc + get_wolves_cns(&ctx).await; // finish up process::exit(0); diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index e1e7ca4..817d61a 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -6,7 +6,7 @@ use serenity::{ prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, }, }; -use skynet_discord_bot::get_data::get_wolves; +use skynet_discord_bot::get_data::get_wolves_cns; use skynet_discord_bot::{get_server_config, is_admin, set_roles::update_server, DataBase, Servers}; use sqlx::{Error, Pool, Sqlite}; @@ -147,7 +147,7 @@ async fn add_server(db: &Pool, ctx: &Context, server: &Servers) -> Resul // update all users if update { // handle wolves api here - get_wolves(ctx).await; + get_wolves_cns(ctx).await; let mut roles_remove = vec![]; if current_remove { diff --git a/src/lib.rs b/src/lib.rs index c0d980a..a247d33 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -523,7 +523,7 @@ pub mod get_data { pub email: String, pub expiry: String, } - pub async fn get_wolves(ctx: &Context) { + pub async fn get_wolves_cns(ctx: &Context) { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Database in TypeMap.").clone() @@ -548,7 +548,7 @@ pub mod get_data { // list of users that need to be updated for this server let mut user_to_update = vec![]; - for user in get_wolves_sub(&config, wolves_api).await { + for user in get_wolves_cns_sub(&config, wolves_api).await { let id = user.member_id.parse::().unwrap_or_default(); match existing.get(&(id as i64)) { None => { @@ -594,7 +594,7 @@ pub mod get_data { .unwrap_or_default() } - async fn get_wolves_sub(config: &Config, wolves_api: &str) -> Vec { + async fn get_wolves_cns_sub(config: &Config, wolves_api: &str) -> Vec { if config.wolves_url.is_empty() { return vec![]; } From fd32adb138d505954dcdb6421522dad2a3942e5b Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Sep 2024 17:02:37 +0100 Subject: [PATCH 002/170] db: delete teh old table and recreate a new one with teh right fields --- db/migrations/4_committee-mk-ii.sql | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 db/migrations/4_committee-mk-ii.sql diff --git a/db/migrations/4_committee-mk-ii.sql b/db/migrations/4_committee-mk-ii.sql new file mode 100644 index 0000000..be59f61 --- /dev/null +++ b/db/migrations/4_committee-mk-ii.sql @@ -0,0 +1,9 @@ +-- No longer using the previous committee table +DROP TABLE committee; + +-- new table pulling from teh api +CREATE TABLE IF NOT EXISTS committee ( + id integer PRIMARY KEY, + name text not null, + members text not null +); From 0e1a7d56b6c86a4346253eed310ddbab23674b30 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Sep 2024 17:07:03 +0100 Subject: [PATCH 003/170] feat: remove teh temp setup --- src/commands/committee.rs | 311 -------------------------------------- src/commands/mod.rs | 1 - src/lib.rs | 22 +-- src/main.rs | 6 - 4 files changed, 4 insertions(+), 336 deletions(-) delete mode 100644 src/commands/committee.rs diff --git a/src/commands/committee.rs b/src/commands/committee.rs deleted file mode 100644 index 3de0ec9..0000000 --- a/src/commands/committee.rs +++ /dev/null @@ -1,311 +0,0 @@ -use lettre::{ - message::{header, MultiPart, SinglePart}, - transport::smtp::{self, authentication::Credentials}, - Message, SmtpTransport, Transport, -}; -use maud::html; -use serenity::{ - builder::CreateApplicationCommand, - client::Context, - model::{ - application::interaction::application_command::ApplicationCommandInteraction, - id::UserId, - prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, - }, -}; -use skynet_discord_bot::{random_string, Config, DataBase}; -use sqlx::{Pool, Sqlite}; - -pub mod link { - use super::*; - use serenity::model::id::GuildId; - use skynet_discord_bot::Committee; - - pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { - let committee_server = GuildId(1220150752656363520); - match command.guild_id { - None => { - return "Not in correct discord server.".to_string(); - } - Some(x) => { - if x != committee_server { - return "Not in correct discord server.".to_string(); - } - } - } - - let option = command - .data - .options - .first() - .expect("Expected email option") - .resolved - .as_ref() - .expect("Expected email object"); - - let email = if let CommandDataOptionValue::String(email) = option { - email.trim() - } else { - return "Please provide a valid committee email.".to_string(); - }; - - // fail early - if !email.ends_with("@ulwolves.ie") { - return "Please use a @ulwolves.ie address you have access to.".to_string(); - } - - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Databse in TypeMap.").clone() - }; - let db = db_lock.read().await; - - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config = config_lock.read().await; - - if get_server_member_discord(&db, &command.user.id).await.is_some() { - return "Already linked".to_string(); - } - - if get_verify_from_db(&db, &command.user.id).await.is_some() { - return "Linking already in process, please check email.".to_string(); - } - - // generate a auth key - let auth = random_string(20); - match send_mail(&config, email, &auth, &command.user.name) { - Ok(_) => match save_to_db(&db, email, &auth, &command.user.id).await { - Ok(_) => {} - Err(e) => { - return format!("Unable to save to db {} {e:?}", email); - } - }, - Err(e) => { - return format!("Unable to send mail to {} {e:?}", email); - } - } - - format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email) - } - - pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command - .name("link_committee") - .description("Verify you are a committee member") - .create_option(|option| { - option - .name("email") - .description("UL Wolves Committee Email") - .kind(CommandOptionType::String) - .required(true) - }) - } - - pub async fn get_server_member_discord(db: &Pool, user: &UserId) -> Option { - sqlx::query_as::<_, Committee>( - r#" - SELECT * - FROM committee - WHERE discord = ? - "#, - ) - .bind(*user.as_u64() as i64) - .fetch_one(db) - .await - .ok() - } - - fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result { - let sender = format!("UL Computer Society <{}>", &config.mail_user); - - // Create the html we want to send. - let html = html! { - head { - title { "Hello from Skynet!" } - style type="text/css" { - "h2, h4 { font-family: Arial, Helvetica, sans-serif; }" - } - } - div style="display: flex; flex-direction: column; align-items: center;" { - h2 { "Hello from Skynet!" } - // Substitute in the name of our recipient. - p { "Hi " (user) "," } - p { - "Please use " pre { "/verify_committee code: " (auth)} " to verify your discord account." - } - p { - "Skynet Team" - br; - "UL Computer Society" - } - } - }; - - let body_text = format!( - r#" - Hi {user} - - Please use "/verify_committee code: {auth}" to verify your discord account. - - Skynet Team - UL Computer Society - "# - ); - - // Build the message. - let email = Message::builder() - .from(sender.parse().unwrap()) - .to(mail.parse().unwrap()) - .subject("Skynet-Discord: Link Committee.") - .multipart( - // This is composed of two parts. - // also helps not trip spam settings (uneven number of url's - MultiPart::alternative() - .singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text)) - .singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())), - ) - .expect("failed to build email"); - - let creds = Credentials::new(config.mail_user.clone(), config.mail_pass.clone()); - - // Open a remote connection to gmail using STARTTLS - let mailer = SmtpTransport::starttls_relay(&config.mail_smtp).unwrap().credentials(creds).build(); - - // Send the email - mailer.send(&email) - } - - pub async fn get_verify_from_db(db: &Pool, user: &UserId) -> Option { - sqlx::query_as::<_, Committee>( - r#" - SELECT * - FROM committee - WHERE discord = ? - "#, - ) - .bind(*user.as_u64() as i64) - .fetch_one(db) - .await - .ok() - } - - async fn save_to_db(db: &Pool, email: &str, auth: &str, user: &UserId) -> Result, sqlx::Error> { - sqlx::query_as::<_, Committee>( - " - INSERT INTO committee (email, discord, auth_code) - VALUES (?1, ?2, ?3) - ", - ) - .bind(email.to_owned()) - .bind(*user.as_u64() as i64) - .bind(auth.to_owned()) - .fetch_optional(db) - .await - } -} - -pub mod verify { - use super::*; - use crate::commands::committee::link::get_verify_from_db; - use serenity::model::id::{GuildId, RoleId}; - use serenity::model::user::User; - use skynet_discord_bot::Committee; - use sqlx::Error; - - pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { - let committee_server = GuildId(1220150752656363520); - match command.guild_id { - None => { - return "Not in correct discord server.".to_string(); - } - Some(x) => { - if x != committee_server { - return "Not in correct discord server.".to_string(); - } - } - } - - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Database in TypeMap.").clone() - }; - let db = db_lock.read().await; - - // check if user has used /link_committee - let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await { - x - } else { - return "Please use /link_committee first".to_string(); - }; - - let option = command - .data - .options - .first() - .expect("Expected code option") - .resolved - .as_ref() - .expect("Expected code object"); - - let code = if let CommandDataOptionValue::String(code) = option { - code - } else { - return "Please provide a verification code".to_string(); - }; - - if &details.auth_code != code { - return "Invalid verification code".to_string(); - } - - match set_discord(&db, &command.user.id).await { - Ok(_) => { - // get teh right roles for the user - set_server_roles(&command.user, ctx).await; - "Discord username linked to Wolves for committee".to_string() - } - Err(e) => { - println!("{:?}", e); - "Failed to save, please try /link_committee again".to_string() - } - } - } - - pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command - .name("verify_committee") - .description("Verify Wolves Committee Email") - .create_option(|option| { - option - .name("code") - .description("Code from verification email") - .kind(CommandOptionType::String) - .required(true) - }) - } - - async fn set_discord(db: &Pool, discord: &UserId) -> Result, Error> { - sqlx::query_as::<_, Committee>( - " - UPDATE committee - SET committee = 1 - WHERE discord = ? - ", - ) - .bind(*discord.as_u64() as i64) - .fetch_optional(db) - .await - } - - async fn set_server_roles(discord: &User, ctx: &Context) { - let committee_server = GuildId(1220150752656363520); - if let Ok(mut member) = committee_server.member(&ctx.http, &discord.id).await { - let committee_member = RoleId(1226602779968274573); - if let Err(e) = member.add_role(&ctx, committee_member).await { - println!("{:?}", e); - } - } - } -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 3acfe81..9d3bea5 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,4 +1,3 @@ pub mod add_server; -pub mod committee; pub mod link_email; pub mod minecraft; diff --git a/src/lib.rs b/src/lib.rs index a247d33..c6c8d68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -202,25 +202,11 @@ impl<'r> FromRow<'r, SqliteRow> for WolvesVerify { } } -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, sqlx::FromRow)] pub struct Committee { - pub email: String, - pub discord: UserId, - pub auth_code: String, - pub committee: i64, -} -impl<'r> FromRow<'r, SqliteRow> for Committee { - fn from_row(row: &'r SqliteRow) -> Result { - let user_tmp: i64 = row.try_get("discord")?; - let discord = UserId::from(user_tmp as u64); - - Ok(Self { - email: row.try_get("email")?, - discord, - auth_code: row.try_get("auth_code")?, - committee: row.try_get("committee")?, - }) - } + pub id: i64, + pub name: UserId, + pub members: Vec, } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/src/main.rs b/src/main.rs index cbe8d5d..d1ebf2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,9 +64,6 @@ impl EventHandler for Handler { .create_application_command(|command| commands::minecraft::server::list::register(command)) .create_application_command(|command| commands::minecraft::server::delete::register(command)) .create_application_command(|command| commands::minecraft::user::add::register(command)) - // for committee server, temp - .create_application_command(|command| commands::committee::link::register(command)) - .create_application_command(|command| commands::committee::verify::register(command)) }) .await { @@ -92,9 +89,6 @@ impl EventHandler for Handler { "minecraft_add" => commands::minecraft::server::add::run(&command, &ctx).await, "minecraft_list" => commands::minecraft::server::list::run(&command, &ctx).await, "minecraft_delete" => commands::minecraft::server::delete::run(&command, &ctx).await, - // for teh committee server, temporary - "link_committee" => commands::committee::link::run(&command, &ctx).await, - "verify_committee" => commands::committee::verify::run(&command, &ctx).await, _ => "not implemented :(".to_string(), }; From ec74dc0aa71ccae6ded8271fca10481840596bb3 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 29 Sep 2024 21:04:08 +0100 Subject: [PATCH 004/170] prep: skeleton to handle roles changing from other means --- src/main.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index f1c70f3..413dc6f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,7 @@ use serenity::{ Client, }; use std::sync::Arc; - +use serenity::model::guild::Member; use skynet_discord_bot::{db_init, get_config, get_server_config, get_server_member, Config, DataBase}; use tokio::sync::RwLock; @@ -21,6 +21,7 @@ struct Handler; #[async_trait] impl EventHandler for Handler { + // handles previously linked accounts joining the server async fn guild_member_addition(&self, ctx: Context, mut new_member: guild::Member) { let db_lock = { let data_read = ctx.data.read().await; @@ -120,6 +121,25 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } } } + + // handles role updates + async fn guild_member_update(&self, _ctx: Context, _old_if_available: Option, _new: Member){ + if let Some(x) = _old_if_available { + if x.roles.len() != _new.roles.len() { + return; + } + //do we need to do more comparison here? + } + + + // get config/db + + // check if the role changed is part of the oens for this server + + // if so add or remove the resultant one + + // TODO: Finish + } } #[tokio::main] From 2aad895bb3fac3b94798986c19c8217c11474554 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 29 Sep 2024 21:25:58 +0100 Subject: [PATCH 005/170] feat: new table for the role adder --- db/migrations/6_role-adder.sql | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 db/migrations/6_role-adder.sql diff --git a/db/migrations/6_role-adder.sql b/db/migrations/6_role-adder.sql new file mode 100644 index 0000000..7206eca --- /dev/null +++ b/db/migrations/6_role-adder.sql @@ -0,0 +1,11 @@ +CREATE TABLE IF NOT EXISTS roles_adder ( + server integer not null, + role_a integer not null, + role_b integer not null, + role_c integer not null, + PRIMARY KEY(server,role_a,role_b,role_c) +); +CREATE INDEX IF NOT EXISTS index_roles_adder_server ON roles_adder (server); +CREATE INDEX IF NOT EXISTS index_roles_adder_from ON roles_adder (role_a,role_b); +CREATE INDEX IF NOT EXISTS index_roles_adder_to ON roles_adder (role_c); +CREATE INDEX IF NOT EXISTS index_roles_adder_search ON roles_adder (server,role_a,role_b); \ No newline at end of file From 7980739627652a6a2c0f3badc2b60cabce79ad5a Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 29 Sep 2024 21:39:27 +0100 Subject: [PATCH 006/170] feat: new struct to mirror the databse --- src/lib.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 3eda4aa..c6ba064 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -293,6 +293,38 @@ impl<'r> FromRow<'r, SqliteRow> for Minecraft { } } +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct RoleAdder { + pub server: GuildId, + pub role_a: RoleId, + pub role_b: RoleId, + pub role_c: RoleId, +} +impl<'r> FromRow<'r, SqliteRow> for RoleAdder { + fn from_row(row: &'r SqliteRow) -> Result { + let server_tmp: i64 = row.try_get("server_discord")?; + let server = GuildId::from(server_tmp as u64); + + Ok(Self { + server, + role_a: get_role_from_row(row, "role_a"), + role_b: get_role_from_row(row, "role_b"), + role_c: get_role_from_row(row, "role_c"), + }) + } +} + +fn get_role_from_row(row: &SqliteRow, col: &str)-> RoleId{ + match row.try_get(col) { + Ok(x) => { + let tmp: i64 = x; + RoleId(tmp as u64) + } + _ => RoleId::from(0u64), + } +} + + pub async fn db_init(config: &Config) -> Result, Error> { let database = format!("{}/{}", &config.home, &config.database); From ffce78a10de8c733c5251e5bd4fecdea0139ff81 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 29 Sep 2024 22:19:58 +0100 Subject: [PATCH 007/170] feat: command config for setting it up (lsit command can come later to see the active ones) --- src/commands/mod.rs | 1 + src/commands/role_adder.rs | 172 +++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 src/commands/role_adder.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 3acfe81..a11ffd5 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -2,3 +2,4 @@ pub mod add_server; pub mod committee; pub mod link_email; pub mod minecraft; +pub mod role_adder; diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs new file mode 100644 index 0000000..ef7effb --- /dev/null +++ b/src/commands/role_adder.rs @@ -0,0 +1,172 @@ +use serenity::{ + builder::CreateApplicationCommand, + client::Context, + model::{ + application::interaction::application_command::ApplicationCommandInteraction, + prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, + }, +}; + +use skynet_discord_bot::{is_admin, DataBase, RoleAdder}; +use sqlx::{Error, Pool, Sqlite}; + +pub mod edit { + use super::*; + + pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { + // check if user has high enough permisssions + if let Some(msg) = is_admin(command, ctx).await { + return msg; + } + + let role_a = if let CommandDataOptionValue::Role(role) = command + .data + .options + .get(0) + .expect("Expected role option") + .resolved + .as_ref() + .expect("Expected role object") + { + role.id.to_owned() + } else { + return "Please provide a valid role for ``Role Current``".to_string(); + }; + + let role_b = if let CommandDataOptionValue::Role(role) = command + .data + .options + .get(1) + .expect("Expected role option") + .resolved + .as_ref() + .expect("Expected role object") + { + role.id.to_owned() + } else { + return "Please provide a valid role for ``Role Current``".to_string(); + }; + + let role_c = if let CommandDataOptionValue::Role(role) = command + .data + .options + .get(2) + .expect("Expected role option") + .resolved + .as_ref() + .expect("Expected role object") + { + role.id.to_owned() + } else { + return "Please provide a valid role for ``Role Current``".to_string(); + }; + + let delete = if let CommandDataOptionValue::Boolean(delete) = command + .data + .options + .get(3) + .expect("Expected true/false option") + .resolved + .as_ref() + .expect("Expected true/False object") + { + delete.to_owned() + } else { + false + }; + + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Databse in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let server_data = RoleAdder { + server: command.guild_id.unwrap_or_default(), + role_a, + role_b, + role_c, + }; + + match add_server(&db, &server_data, delete).await { + Ok(_) => {} + Err(e) => { + println!("{:?}", e); + return format!("Failure to insert into Servers {:?}", server_data); + } + } + + "Added/Updated server info".to_string() + } + + pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { + command + .name("roles_adder") + .description("Combine roles together to an new one") + .create_option(|option| { + option + .name("role_a") + .description("A role you want to add to Role B") + .kind(CommandOptionType::Role) + .required(true) + }) + .create_option(|option| { + option + .name("role_b") + .description("A role you want to add to Role A") + .kind(CommandOptionType::Role) + .required(true) + }) + .create_option(|option| { + option + .name("role_c") + .description("Sum of A and B") + .kind(CommandOptionType::Role) + .required(true) + }) + .create_option(|option| { + option + .name("delete") + .description("Delete this entry.") + .kind(CommandOptionType::Boolean) + .required(false) + }) + } + + async fn add_server(db: &Pool, server: &RoleAdder, delete: bool) -> Result, Error> { + let action; + if delete { + action = sqlx::query_as::<_, RoleAdder>( + " + DELETE FROM roles_adder + WHERE server = ?1 AND role_a = ?2 AND role_b = ?3 AND role_c = ?4 + ", + ) + .bind(*server.server.as_u64() as i64) + .bind(*server.role_a.as_u64() as i64) + .bind(*server.role_b.as_u64() as i64) + .bind(*server.role_c.as_u64() as i64) + .fetch_optional(db) + .await; + } else { + action = sqlx::query_as::<_, RoleAdder>( + " + INSERT OR REPLACE INTO roles_adder (server, role_a, role_b, role_c) + VALUES (?1, ?2, ?3, ?4) + ", + ) + .bind(*server.server.as_u64() as i64) + .bind(*server.role_a.as_u64() as i64) + .bind(*server.role_b.as_u64() as i64) + .bind(*server.role_c.as_u64() as i64) + .fetch_optional(db) + .await; + } + + action + } + +} + +// TODO +pub mod list {} \ No newline at end of file From 32292a3c0b47aaee26396c968ef46d9c231eecdb Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 29 Sep 2024 23:47:33 +0100 Subject: [PATCH 008/170] feat: tested out command and gt rid of the last few kinks --- src/commands/role_adder.rs | 92 +++++++++++++++++++++++++++++++++----- src/lib.rs | 2 +- src/main.rs | 26 +++++------ 3 files changed, 93 insertions(+), 27 deletions(-) diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index ef7effb..3f224ef 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -61,19 +61,24 @@ pub mod edit { return "Please provide a valid role for ``Role Current``".to_string(); }; - let delete = if let CommandDataOptionValue::Boolean(delete) = command + + let mut delete = false; + + if let Some(x) = command .data .options - .get(3) - .expect("Expected true/false option") - .resolved - .as_ref() - .expect("Expected true/False object") - { - delete.to_owned() - } else { - false - }; + .get(3) { + + let tmp = x.to_owned(); + if let Some(y) = tmp.resolved { + match y { + CommandDataOptionValue::Boolean(z) => { + delete = z; + } + _ => {} + } + } + } let db_lock = { let data_read = ctx.data.read().await; @@ -169,4 +174,67 @@ pub mod edit { } // TODO -pub mod list {} \ No newline at end of file +pub mod list {} + + +pub mod tools { + use serenity::client::Context; + use serenity::model::guild::Member; + use sqlx::{Pool, Sqlite}; + use skynet_discord_bot::RoleAdder; + + pub async fn on_role_change(db: &Pool, ctx: &Context, mut new_data: Member){ + + + // check if the role changed is part of the oens for this server + if let Some(role_adders) = sqlx::query_as::<_, RoleAdder>( + r#" + SELECT * + FROM roles_adder + WHERE server = ? + "#, + ) + .bind(*new_data.guild_id.as_u64() as i64) + .fetch_all(db) + .await + .ok() { + + + let mut roles_add = vec![]; + let mut roles_remove = vec![]; + + for role_adder in role_adders { + // if the user has both A dnd B give them C + if new_data.roles.contains(&role_adder.role_a) && + new_data.roles.contains(&role_adder.role_b) && + !new_data.roles.contains(&role_adder.role_c) { + roles_add.push(role_adder.role_c); + } + + // If the suer has C but not A or B remove C + if new_data.roles.contains(&role_adder.role_c) && + ( + !new_data.roles.contains(&role_adder.role_a) || !new_data.roles.contains(&role_adder.role_b) + ) { + roles_remove.push(role_adder.role_c); + } + } + + + + if !roles_add.is_empty(){ + if let Err(e) = new_data.add_roles(&ctx, &roles_add).await { + println!("{:?}", e); + } + } + + if !roles_remove.is_empty(){ + if let Err(e) = new_data.remove_roles(&ctx, &roles_remove).await{ + println!("{:?}", e); + } + } + } + } + + +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index c6ba064..09315f0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -302,7 +302,7 @@ pub struct RoleAdder { } impl<'r> FromRow<'r, SqliteRow> for RoleAdder { fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server_discord")?; + let server_tmp: i64 = row.try_get("server")?; let server = GuildId::from(server_tmp as u64); Ok(Self { diff --git a/src/main.rs b/src/main.rs index 413dc6f..275212d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,6 @@ use serenity::{ model::{ application::{command::Command, interaction::Interaction}, gateway::{GatewayIntents, Ready}, - guild, prelude::Activity, user::OnlineStatus, }, @@ -16,13 +15,14 @@ use std::sync::Arc; use serenity::model::guild::Member; use skynet_discord_bot::{db_init, get_config, get_server_config, get_server_member, Config, DataBase}; use tokio::sync::RwLock; +use crate::commands::role_adder::tools::on_role_change; struct Handler; #[async_trait] impl EventHandler for Handler { // handles previously linked accounts joining the server - async fn guild_member_addition(&self, ctx: Context, mut new_member: guild::Member) { + async fn guild_member_addition(&self, ctx: Context, mut new_member: Member) { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() @@ -76,6 +76,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use match Command::set_global_application_commands(&ctx.http, |commands| { commands .create_application_command(|command| commands::add_server::register(command)) + .create_application_command(|command| commands::role_adder::edit::register(command)) .create_application_command(|command| commands::link_email::link::register(command)) .create_application_command(|command| commands::link_email::verify::register(command)) .create_application_command(|command| commands::minecraft::server::add::register(command)) @@ -107,6 +108,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use "link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await, // admin commands "add" => commands::add_server::run(&command, &ctx).await, + "roles_adder" => commands::role_adder::edit::run(&command, &ctx).await, "minecraft_add" => commands::minecraft::server::add::run(&command, &ctx).await, "minecraft_list" => commands::minecraft::server::list::run(&command, &ctx).await, "minecraft_delete" => commands::minecraft::server::delete::run(&command, &ctx).await, @@ -123,22 +125,18 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } // handles role updates - async fn guild_member_update(&self, _ctx: Context, _old_if_available: Option, _new: Member){ - if let Some(x) = _old_if_available { - if x.roles.len() != _new.roles.len() { - return; - } - //do we need to do more comparison here? - } - + async fn guild_member_update(&self, ctx: Context, _old_data: Option, new_data: Member){ // get config/db + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + + let db = db_lock.read().await; // check if the role changed is part of the oens for this server - - // if so add or remove the resultant one - - // TODO: Finish + on_role_change(&db, &ctx, new_data).await; } } From 5e7964ae26b97ba668deb725fc303bda295c3f95 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 30 Sep 2024 00:03:03 +0100 Subject: [PATCH 009/170] feat: some cleanup in messages and added some handrails so folks wont add stupid combos --- src/commands/role_adder.rs | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index 3f224ef..08e0286 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -61,6 +61,13 @@ pub mod edit { return "Please provide a valid role for ``Role Current``".to_string(); }; + if role_a == role_b { + return "Roles A and B must be different".to_string(); + } + + if (role_c == role_a)|| (role_c == role_b) { + return "Role C cannot be same as A or B".to_string(); + } let mut delete = false; @@ -86,8 +93,9 @@ pub mod edit { }; let db = db_lock.read().await; + let server = command.guild_id.unwrap_or_default(); let server_data = RoleAdder { - server: command.guild_id.unwrap_or_default(), + server, role_a, role_b, role_c, @@ -101,7 +109,29 @@ pub mod edit { } } - "Added/Updated server info".to_string() + + let mut role_a_name = String::new(); + let mut role_b_name = String::new(); + let mut role_c_name = String::new(); + + if let Ok(x) = server.roles(&ctx).await { + if let Some(y) = x.get(&role_a){ + role_a_name = y.to_owned().name; + } + if let Some(y) = x.get(&role_b){ + role_b_name = y.to_owned().name; + } + if let Some(y) = x.get(&role_b){ + role_c_name = y.to_owned().name; + } + } + + + if delete { + format!("Removed {} + {} = {}", role_a_name, role_b_name, role_c_name) + } else { + format!("Added {} + {} = {}", role_a_name, role_b_name, role_c_name) + } } pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { From 0d9ce2de7f61cd7d400087686631f4b5e7bb4e4b Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 30 Sep 2024 00:09:29 +0100 Subject: [PATCH 010/170] fmt: fmt and clippy --- src/commands/role_adder.rs | 181 ++++++++++++++++--------------------- src/lib.rs | 3 +- src/main.rs | 9 +- 3 files changed, 84 insertions(+), 109 deletions(-) diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index 08e0286..a740194 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -20,13 +20,13 @@ pub mod edit { } let role_a = if let CommandDataOptionValue::Role(role) = command - .data - .options - .get(0) - .expect("Expected role option") - .resolved - .as_ref() - .expect("Expected role object") + .data + .options + .get(0) + .expect("Expected role option") + .resolved + .as_ref() + .expect("Expected role object") { role.id.to_owned() } else { @@ -34,13 +34,13 @@ pub mod edit { }; let role_b = if let CommandDataOptionValue::Role(role) = command - .data - .options - .get(1) - .expect("Expected role option") - .resolved - .as_ref() - .expect("Expected role object") + .data + .options + .get(1) + .expect("Expected role option") + .resolved + .as_ref() + .expect("Expected role object") { role.id.to_owned() } else { @@ -48,13 +48,13 @@ pub mod edit { }; let role_c = if let CommandDataOptionValue::Role(role) = command - .data - .options - .get(2) - .expect("Expected role option") - .resolved - .as_ref() - .expect("Expected role object") + .data + .options + .get(2) + .expect("Expected role option") + .resolved + .as_ref() + .expect("Expected role object") { role.id.to_owned() } else { @@ -65,17 +65,13 @@ pub mod edit { return "Roles A and B must be different".to_string(); } - if (role_c == role_a)|| (role_c == role_b) { + if (role_c == role_a) || (role_c == role_b) { return "Role C cannot be same as A or B".to_string(); } let mut delete = false; - if let Some(x) = command - .data - .options - .get(3) { - + if let Some(x) = command.data.options.get(3) { let tmp = x.to_owned(); if let Some(y) = tmp.resolved { match y { @@ -109,63 +105,55 @@ pub mod edit { } } - let mut role_a_name = String::new(); let mut role_b_name = String::new(); let mut role_c_name = String::new(); if let Ok(x) = server.roles(&ctx).await { - if let Some(y) = x.get(&role_a){ + if let Some(y) = x.get(&role_a) { role_a_name = y.to_owned().name; } - if let Some(y) = x.get(&role_b){ + if let Some(y) = x.get(&role_b) { role_b_name = y.to_owned().name; } - if let Some(y) = x.get(&role_b){ + if let Some(y) = x.get(&role_b) { role_c_name = y.to_owned().name; } } - if delete { format!("Removed {} + {} = {}", role_a_name, role_b_name, role_c_name) } else { - format!("Added {} + {} = {}", role_a_name, role_b_name, role_c_name) + format!("Added {} + {} = {}", role_a_name, role_b_name, role_c_name) } } pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command - .name("roles_adder") - .description("Combine roles together to an new one") - .create_option(|option| { - option - .name("role_a") - .description("A role you want to add to Role B") - .kind(CommandOptionType::Role) - .required(true) - }) - .create_option(|option| { - option - .name("role_b") - .description("A role you want to add to Role A") - .kind(CommandOptionType::Role) - .required(true) - }) - .create_option(|option| { - option - .name("role_c") - .description("Sum of A and B") - .kind(CommandOptionType::Role) - .required(true) - }) - .create_option(|option| { - option - .name("delete") - .description("Delete this entry.") - .kind(CommandOptionType::Boolean) - .required(false) - }) + .name("roles_adder") + .description("Combine roles together to an new one") + .create_option(|option| { + option + .name("role_a") + .description("A role you want to add to Role B") + .kind(CommandOptionType::Role) + .required(true) + }) + .create_option(|option| { + option + .name("role_b") + .description("A role you want to add to Role A") + .kind(CommandOptionType::Role) + .required(true) + }) + .create_option(|option| option.name("role_c").description("Sum of A and B").kind(CommandOptionType::Role).required(true)) + .create_option(|option| { + option + .name("delete") + .description("Delete this entry.") + .kind(CommandOptionType::Boolean) + .required(false) + }) } async fn add_server(db: &Pool, server: &RoleAdder, delete: bool) -> Result, Error> { @@ -177,12 +165,12 @@ pub mod edit { WHERE server = ?1 AND role_a = ?2 AND role_b = ?3 AND role_c = ?4 ", ) - .bind(*server.server.as_u64() as i64) - .bind(*server.role_a.as_u64() as i64) - .bind(*server.role_b.as_u64() as i64) - .bind(*server.role_c.as_u64() as i64) - .fetch_optional(db) - .await; + .bind(*server.server.as_u64() as i64) + .bind(*server.role_a.as_u64() as i64) + .bind(*server.role_b.as_u64() as i64) + .bind(*server.role_c.as_u64() as i64) + .fetch_optional(db) + .await; } else { action = sqlx::query_as::<_, RoleAdder>( " @@ -190,32 +178,28 @@ pub mod edit { VALUES (?1, ?2, ?3, ?4) ", ) - .bind(*server.server.as_u64() as i64) - .bind(*server.role_a.as_u64() as i64) - .bind(*server.role_b.as_u64() as i64) - .bind(*server.role_c.as_u64() as i64) - .fetch_optional(db) - .await; + .bind(*server.server.as_u64() as i64) + .bind(*server.role_a.as_u64() as i64) + .bind(*server.role_b.as_u64() as i64) + .bind(*server.role_c.as_u64() as i64) + .fetch_optional(db) + .await; } action } - } // TODO pub mod list {} - pub mod tools { use serenity::client::Context; use serenity::model::guild::Member; - use sqlx::{Pool, Sqlite}; use skynet_discord_bot::RoleAdder; + use sqlx::{Pool, Sqlite}; - pub async fn on_role_change(db: &Pool, ctx: &Context, mut new_data: Member){ - - + pub async fn on_role_change(db: &Pool, ctx: &Context, mut new_data: Member) { // check if the role changed is part of the oens for this server if let Some(role_adders) = sqlx::query_as::<_, RoleAdder>( r#" @@ -224,47 +208,40 @@ pub mod tools { WHERE server = ? "#, ) - .bind(*new_data.guild_id.as_u64() as i64) - .fetch_all(db) - .await - .ok() { - - + .bind(*new_data.guild_id.as_u64() as i64) + .fetch_all(db) + .await + .ok() + { let mut roles_add = vec![]; let mut roles_remove = vec![]; for role_adder in role_adders { // if the user has both A dnd B give them C - if new_data.roles.contains(&role_adder.role_a) && - new_data.roles.contains(&role_adder.role_b) && - !new_data.roles.contains(&role_adder.role_c) { + if new_data.roles.contains(&role_adder.role_a) && new_data.roles.contains(&role_adder.role_b) && !new_data.roles.contains(&role_adder.role_c) + { roles_add.push(role_adder.role_c); } // If the suer has C but not A or B remove C - if new_data.roles.contains(&role_adder.role_c) && - ( - !new_data.roles.contains(&role_adder.role_a) || !new_data.roles.contains(&role_adder.role_b) - ) { + if new_data.roles.contains(&role_adder.role_c) + && (!new_data.roles.contains(&role_adder.role_a) || !new_data.roles.contains(&role_adder.role_b)) + { roles_remove.push(role_adder.role_c); } } - - - if !roles_add.is_empty(){ + if !roles_add.is_empty() { if let Err(e) = new_data.add_roles(&ctx, &roles_add).await { println!("{:?}", e); } } - if !roles_remove.is_empty(){ - if let Err(e) = new_data.remove_roles(&ctx, &roles_remove).await{ + if !roles_remove.is_empty() { + if let Err(e) = new_data.remove_roles(&ctx, &roles_remove).await { println!("{:?}", e); } } } } - - -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 09315f0..5923151 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -314,7 +314,7 @@ impl<'r> FromRow<'r, SqliteRow> for RoleAdder { } } -fn get_role_from_row(row: &SqliteRow, col: &str)-> RoleId{ +fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId { match row.try_get(col) { Ok(x) => { let tmp: i64 = x; @@ -324,7 +324,6 @@ fn get_role_from_row(row: &SqliteRow, col: &str)-> RoleId{ } } - pub async fn db_init(config: &Config) -> Result, Error> { let database = format!("{}/{}", &config.home, &config.database); diff --git a/src/main.rs b/src/main.rs index 275212d..a7735e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,7 @@ pub mod commands; +use crate::commands::role_adder::tools::on_role_change; +use serenity::model::guild::Member; use serenity::{ async_trait, client::{Context, EventHandler}, @@ -11,11 +13,9 @@ use serenity::{ }, Client, }; -use std::sync::Arc; -use serenity::model::guild::Member; use skynet_discord_bot::{db_init, get_config, get_server_config, get_server_member, Config, DataBase}; +use std::sync::Arc; use tokio::sync::RwLock; -use crate::commands::role_adder::tools::on_role_change; struct Handler; @@ -125,8 +125,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } // handles role updates - async fn guild_member_update(&self, ctx: Context, _old_data: Option, new_data: Member){ - + async fn guild_member_update(&self, ctx: Context, _old_data: Option, new_data: Member) { // get config/db let db_lock = { let data_read = ctx.data.read().await; From 1c3ccbecf52ede03734784880121c20cfd15fbb1 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 30 Sep 2024 00:12:48 +0100 Subject: [PATCH 011/170] fmt: not sure how this one slipped by --- src/commands/role_adder.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index a740194..bb33667 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -201,7 +201,7 @@ pub mod tools { pub async fn on_role_change(db: &Pool, ctx: &Context, mut new_data: Member) { // check if the role changed is part of the oens for this server - if let Some(role_adders) = sqlx::query_as::<_, RoleAdder>( + if let Ok(role_adders) = sqlx::query_as::<_, RoleAdder>( r#" SELECT * FROM roles_adder @@ -211,7 +211,6 @@ pub mod tools { .bind(*new_data.guild_id.as_u64() as i64) .fetch_all(db) .await - .ok() { let mut roles_add = vec![]; let mut roles_remove = vec![]; From 80c9191eeec29ba20ef4084713eca7fe0cab7412 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 30 Sep 2024 00:19:58 +0100 Subject: [PATCH 012/170] fmt: more clippy that got missed --- src/commands/role_adder.rs | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index bb33667..949bf32 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -22,7 +22,7 @@ pub mod edit { let role_a = if let CommandDataOptionValue::Role(role) = command .data .options - .get(0) + .first() .expect("Expected role option") .resolved .as_ref() @@ -73,13 +73,8 @@ pub mod edit { if let Some(x) = command.data.options.get(3) { let tmp = x.to_owned(); - if let Some(y) = tmp.resolved { - match y { - CommandDataOptionValue::Boolean(z) => { - delete = z; - } - _ => {} - } + if let Some(CommandDataOptionValue::Boolean(z)) = tmp.resolved { + delete = z; } } @@ -157,9 +152,8 @@ pub mod edit { } async fn add_server(db: &Pool, server: &RoleAdder, delete: bool) -> Result, Error> { - let action; if delete { - action = sqlx::query_as::<_, RoleAdder>( + sqlx::query_as::<_, RoleAdder>( " DELETE FROM roles_adder WHERE server = ?1 AND role_a = ?2 AND role_b = ?3 AND role_c = ?4 @@ -170,9 +164,9 @@ pub mod edit { .bind(*server.role_b.as_u64() as i64) .bind(*server.role_c.as_u64() as i64) .fetch_optional(db) - .await; + .await } else { - action = sqlx::query_as::<_, RoleAdder>( + sqlx::query_as::<_, RoleAdder>( " INSERT OR REPLACE INTO roles_adder (server, role_a, role_b, role_c) VALUES (?1, ?2, ?3, ?4) @@ -183,10 +177,8 @@ pub mod edit { .bind(*server.role_b.as_u64() as i64) .bind(*server.role_c.as_u64() as i64) .fetch_optional(db) - .await; + .await } - - action } } From a7e8f5698ee9ab852f3048c49382b6677f32fdc2 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 27 Oct 2024 23:21:30 +0000 Subject: [PATCH 013/170] git: expand out the .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2405fba..8b68dcb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,11 +2,13 @@ /.idea .env +*.env result /result *.db +*.db* tmp/ tmp.* From ceade9b97261f6f7d7f0892918996dfeb4b9caeb Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 00:03:52 +0000 Subject: [PATCH 014/170] sql: slight reordering of the migrations --- db/migrations/{4_committee-mk-ii.sql => 7_committee-mk-ii.sql} | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename db/migrations/{4_committee-mk-ii.sql => 7_committee-mk-ii.sql} (74%) diff --git a/db/migrations/4_committee-mk-ii.sql b/db/migrations/7_committee-mk-ii.sql similarity index 74% rename from db/migrations/4_committee-mk-ii.sql rename to db/migrations/7_committee-mk-ii.sql index be59f61..2ddf316 100644 --- a/db/migrations/4_committee-mk-ii.sql +++ b/db/migrations/7_committee-mk-ii.sql @@ -2,8 +2,9 @@ DROP TABLE committee; -- new table pulling from teh api -CREATE TABLE IF NOT EXISTS committee ( +CREATE TABLE IF NOT EXISTS committees ( id integer PRIMARY KEY, name text not null, + link text not null, members text not null ); From 79f880daeae6ce9002bb74ea99a29e52780f3aaa Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 00:51:39 +0000 Subject: [PATCH 015/170] feat: splitting up lib.rs into subfiles, starting with anythign taht interacts with teh api --- src/bin/update_data.rs | 3 +- src/commands/add_server.rs | 2 +- src/common/database.rs | 0 src/common/mod.rs | 1 + src/common/wolves.rs | 187 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 182 +----------------------------------- 6 files changed, 194 insertions(+), 181 deletions(-) create mode 100644 src/common/database.rs create mode 100644 src/common/mod.rs create mode 100644 src/common/wolves.rs diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index ec3713a..b5d89a5 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -4,9 +4,10 @@ use serenity::{ model::gateway::{GatewayIntents, Ready}, Client, }; -use skynet_discord_bot::{db_init, get_config, get_data::get_wolves_cns, Config, DataBase}; +use skynet_discord_bot::{db_init, get_config, Config, DataBase}; use std::{process, sync::Arc}; use tokio::sync::RwLock; +use skynet_discord_bot::common::wolves::get_data::get_wolves_cns; #[tokio::main] async fn main() { diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index 1889257..8c7e9e5 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -6,7 +6,7 @@ use serenity::{ prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, }, }; -use skynet_discord_bot::get_data::get_wolves_cns; +use skynet_discord_bot::common::wolves::get_data::get_wolves_cns; use skynet_discord_bot::{get_server_config, is_admin, set_roles::update_server, DataBase, Servers}; use sqlx::{Error, Pool, Sqlite}; diff --git a/src/common/database.rs b/src/common/database.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/common/mod.rs b/src/common/mod.rs new file mode 100644 index 0000000..5d73638 --- /dev/null +++ b/src/common/mod.rs @@ -0,0 +1 @@ +pub mod wolves; \ No newline at end of file diff --git a/src/common/wolves.rs b/src/common/wolves.rs new file mode 100644 index 0000000..d92c34d --- /dev/null +++ b/src/common/wolves.rs @@ -0,0 +1,187 @@ +/** + This file relates to anything that directly interacts with teh wolves API + */ + + +// #[derive(Debug, Clone, sqlx::FromRow)] +// pub struct Committees { +// pub id: i64, +// pub name: String, +// pub link: String, +// pub members: Vec, +// } + +pub mod get_data { + use crate::set_roles::update_server; + use std::collections::BTreeMap; + use serde::{Deserialize, Serialize}; + use serenity::client::Context; + use serenity::model::id::GuildId; + use sqlx::{Pool, Sqlite}; + use crate::{get_server_config_bulk, Config, DataBase, ServerMembers, ServerMembersWolves, Servers, Wolves}; + + #[derive(Deserialize, Serialize, Debug)] + struct WolvesResultUser { + committee: String, + member_id: String, + first_name: String, + last_name: String, + contact_email: String, + opt_in_email: String, + student_id: Option, + note: Option, + expiry: String, + requested: String, + approved: String, + sitename: String, + domain: String, + } + + #[derive(Deserialize, Serialize, Debug)] + struct WolvesResult { + success: i8, + result: Vec, + } + + #[derive(Deserialize, Serialize, Debug)] + struct WolvesResultLocal { + pub id_wolves: String, + pub email: String, + pub expiry: String, + } + pub async fn get_wolves_cns(ctx: &Context) { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Database in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config = config_lock.read().await; + + for server_config in get_server_config_bulk(&db).await { + let Servers { + server, + wolves_api, + .. + } = &server_config; + + let existing_tmp = get_server_member(&db, server).await; + let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::>(); + + // list of users that need to be updated for this server + let mut user_to_update = vec![]; + for user in get_wolves_cns_sub(&config, wolves_api).await { + let id = user.member_id.parse::().unwrap_or_default(); + match existing.get(&(id as i64)) { + None => { + // user does not exist already, add everything + add_users_wolves(&db, &user).await; + add_users_server_members(&db, server, &user).await; + } + Some(old) => { + // always update wolves table, in case data has changed + add_users_wolves(&db, &user).await; + if old.expiry != user.expiry { + add_users_server_members(&db, server, &user).await; + + if let Some(discord_id) = old.discord { + user_to_update.push(discord_id); + } + } + } + } + } + + if !user_to_update.is_empty() { + update_server(ctx, &server_config, &[], &user_to_update).await; + } + } + } + + pub async fn get_server_member(db: &Pool, server: &GuildId) -> Vec { + sqlx::query_as::<_, ServerMembersWolves>( + r#" + SELECT * + FROM server_members + JOIN wolves USING (id_wolves) + WHERE ( + server = ? + AND discord IS NOT NULL + ) + "#, + ) + .bind(*server.as_u64() as i64) + .fetch_all(db) + .await + .unwrap_or_default() + } + + async fn get_wolves_cns_sub(config: &Config, wolves_api: &str) -> Vec { + if config.wolves_url.is_empty() { + return vec![]; + } + + // get wolves data + if let Ok(mut res) = surf::post(&config.wolves_url).header("X-AM-Identity", wolves_api).await { + if let Ok(WolvesResult { + success, + result, + }) = res.body_json().await + { + if success != 1 { + return vec![]; + } + + return result; + } + } + + vec![] + } + + async fn add_users_wolves(db: &Pool, user: &WolvesResultUser) { + // expiry + match sqlx::query_as::<_, Wolves>( + " + INSERT INTO wolves (id_wolves, email) + VALUES ($1, $2) + ON CONFLICT(id_wolves) DO UPDATE SET email = $2 + ", + ) + .bind(&user.member_id) + .bind(&user.contact_email) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + println!("Failure to insert into Wolves {:?}", user); + println!("{:?}", e); + } + } + } + async fn add_users_server_members(db: &Pool, server: &GuildId, user: &WolvesResultUser) { + match sqlx::query_as::<_, ServerMembers>( + " + INSERT OR REPLACE INTO server_members (server, id_wolves, expiry) + VALUES (?1, ?2, ?3) + ", + ) + .bind(*server.as_u64() as i64) + .bind(&user.member_id) + .bind(&user.expiry) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + println!("Failure to insert into ServerMembers {} {:?}", server.as_u64(), user); + println!("{:?}", e); + } + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 6c6ded9..9e1c599 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +pub mod common; + use dotenvy::dotenv; use serde::{Deserialize, Serialize}; use serenity::{ @@ -202,13 +204,6 @@ impl<'r> FromRow<'r, SqliteRow> for WolvesVerify { } } -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct Committee { - pub id: i64, - pub name: UserId, - pub members: Vec, -} - #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Servers { pub server: GuildId, @@ -218,7 +213,7 @@ pub struct Servers { pub member_past: i64, pub member_current: i64, pub bot_channel_id: ChannelId, - // these can be removed in teh future with an API update + // TODO: these can be removed in teh future with an API update pub server_name: String, pub wolves_link: String, } @@ -508,177 +503,6 @@ pub mod set_roles { } } -pub mod get_data { - use super::*; - use crate::set_roles::update_server; - use std::collections::BTreeMap; - - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResultUser { - committee: String, - member_id: String, - first_name: String, - last_name: String, - contact_email: String, - opt_in_email: String, - student_id: Option, - note: Option, - expiry: String, - requested: String, - approved: String, - sitename: String, - domain: String, - } - - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResult { - success: i8, - result: Vec, - } - - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResultLocal { - pub id_wolves: String, - pub email: String, - pub expiry: String, - } - pub async fn get_wolves_cns(ctx: &Context) { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Database in TypeMap.").clone() - }; - let db = db_lock.read().await; - - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config = config_lock.read().await; - - for server_config in get_server_config_bulk(&db).await { - let Servers { - server, - wolves_api, - .. - } = &server_config; - - let existing_tmp = get_server_member(&db, server).await; - let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::>(); - - // list of users that need to be updated for this server - let mut user_to_update = vec![]; - for user in get_wolves_cns_sub(&config, wolves_api).await { - let id = user.member_id.parse::().unwrap_or_default(); - match existing.get(&(id as i64)) { - None => { - // user does not exist already, add everything - add_users_wolves(&db, &user).await; - add_users_server_members(&db, server, &user).await; - } - Some(old) => { - // always update wolves table, in case data has changed - add_users_wolves(&db, &user).await; - if old.expiry != user.expiry { - add_users_server_members(&db, server, &user).await; - - if let Some(discord_id) = old.discord { - user_to_update.push(discord_id); - } - } - } - } - } - - if !user_to_update.is_empty() { - update_server(ctx, &server_config, &[], &user_to_update).await; - } - } - } - - pub async fn get_server_member(db: &Pool, server: &GuildId) -> Vec { - sqlx::query_as::<_, ServerMembersWolves>( - r#" - SELECT * - FROM server_members - JOIN wolves USING (id_wolves) - WHERE ( - server = ? - AND discord IS NOT NULL - ) - "#, - ) - .bind(*server.as_u64() as i64) - .fetch_all(db) - .await - .unwrap_or_default() - } - - async fn get_wolves_cns_sub(config: &Config, wolves_api: &str) -> Vec { - if config.wolves_url.is_empty() { - return vec![]; - } - - // get wolves data - if let Ok(mut res) = surf::post(&config.wolves_url).header("X-AM-Identity", wolves_api).await { - if let Ok(WolvesResult { - success, - result, - }) = res.body_json().await - { - if success != 1 { - return vec![]; - } - - return result; - } - } - - vec![] - } - - async fn add_users_wolves(db: &Pool, user: &WolvesResultUser) { - // expiry - match sqlx::query_as::<_, Wolves>( - " - INSERT INTO wolves (id_wolves, email) - VALUES ($1, $2) - ON CONFLICT(id_wolves) DO UPDATE SET email = $2 - ", - ) - .bind(&user.member_id) - .bind(&user.contact_email) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into Wolves {:?}", user); - println!("{:?}", e); - } - } - } - async fn add_users_server_members(db: &Pool, server: &GuildId, user: &WolvesResultUser) { - match sqlx::query_as::<_, ServerMembers>( - " - INSERT OR REPLACE INTO server_members (server, id_wolves, expiry) - VALUES (?1, ?2, ?3) - ", - ) - .bind(*server.as_u64() as i64) - .bind(&user.member_id) - .bind(&user.expiry) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into ServerMembers {} {:?}", server.as_u64(), user); - println!("{:?}", e); - } - } - } -} - /** For any time ye need to check if a user who calls a command has admin privlages */ From 41407ecefb68e56338c1c593d02a75eb72b73f8a Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 00:59:04 +0000 Subject: [PATCH 016/170] feat: split out all the databse interactions into their own file --- src/bin/update_data.rs | 11 +- src/bin/update_minecraft.rs | 3 +- src/bin/update_users.rs | 11 +- src/commands/add_server.rs | 15 +- src/commands/link_email.rs | 7 +- src/commands/minecraft.rs | 14 +- src/commands/role_adder.rs | 5 +- src/common/database.rs | 288 ++++++++++++++++++++++++++++++++++++ src/common/mod.rs | 3 +- src/common/wolves.rs | 3 +- src/lib.rs | 285 +---------------------------------- src/main.rs | 21 +-- 12 files changed, 348 insertions(+), 318 deletions(-) diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index b5d89a5..e4b662d 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -1,12 +1,13 @@ use serenity::{ - async_trait, - client::{Context, EventHandler}, - model::gateway::{GatewayIntents, Ready}, - Client, + async_trait, + client::{Context, EventHandler}, + model::gateway::{GatewayIntents, Ready}, + Client, }; -use skynet_discord_bot::{db_init, get_config, Config, DataBase}; +use skynet_discord_bot::{get_config, Config}; use std::{process, sync::Arc}; use tokio::sync::RwLock; +use skynet_discord_bot::common::database::{db_init, DataBase}; use skynet_discord_bot::common::wolves::get_data::get_wolves_cns; #[tokio::main] diff --git a/src/bin/update_minecraft.rs b/src/bin/update_minecraft.rs index 72aad1c..f959d99 100644 --- a/src/bin/update_minecraft.rs +++ b/src/bin/update_minecraft.rs @@ -1,5 +1,6 @@ -use skynet_discord_bot::{db_init, get_config, get_minecraft_config, update_server, whitelist_wipe}; +use skynet_discord_bot::{get_config, get_minecraft_config, update_server, whitelist_wipe}; use std::collections::HashSet; +use skynet_discord_bot::common::database::db_init; #[tokio::main] async fn main() { diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index d46c0e5..cf48650 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -1,12 +1,13 @@ use serenity::{ - async_trait, - client::{Context, EventHandler}, - model::gateway::{GatewayIntents, Ready}, - Client, + async_trait, + client::{Context, EventHandler}, + model::gateway::{GatewayIntents, Ready}, + Client, }; -use skynet_discord_bot::{db_init, get_config, get_server_config_bulk, set_roles, Config, DataBase}; +use skynet_discord_bot::{get_config, set_roles, Config}; use std::{process, sync::Arc}; use tokio::sync::RwLock; +use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase}; #[tokio::main] async fn main() { diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index 8c7e9e5..dace04e 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -1,14 +1,15 @@ use serenity::{ - builder::CreateApplicationCommand, - client::Context, - model::{ - application::interaction::application_command::ApplicationCommandInteraction, - prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, - }, + builder::CreateApplicationCommand, + client::Context, + model::{ + application::interaction::application_command::ApplicationCommandInteraction, + prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, + }, }; use skynet_discord_bot::common::wolves::get_data::get_wolves_cns; -use skynet_discord_bot::{get_server_config, is_admin, set_roles::update_server, DataBase, Servers}; +use skynet_discord_bot::{is_admin, set_roles::update_server}; use sqlx::{Error, Pool, Sqlite}; +use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers}; pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { // check if user has high enough permisssions diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 4961dbb..224c6da 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -13,8 +13,10 @@ use serenity::{ prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, }, }; -use skynet_discord_bot::{get_now_iso, random_string, Config, DataBase, Wolves, WolvesVerify}; +use skynet_discord_bot::{get_now_iso, random_string, Config}; use sqlx::{Pool, Sqlite}; +use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify}; + pub mod link { use super::*; @@ -238,8 +240,9 @@ pub mod verify { use super::*; use crate::commands::link_email::link::{db_pending_clear_expired, get_verify_from_db}; use serenity::model::user::User; - use skynet_discord_bot::{get_server_config, ServerMembersWolves, Servers}; + use skynet_discord_bot::common::database::get_server_config; use sqlx::Error; + use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { let db_lock = { diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index b6b6dd5..fb87326 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -7,7 +7,7 @@ use serenity::{ }, }; -use skynet_discord_bot::DataBase; +use skynet_discord_bot::common::database::DataBase; use sqlx::{Pool, Sqlite}; pub(crate) mod user { @@ -16,8 +16,9 @@ pub(crate) mod user { use super::*; use crate::commands::link_email::link::get_server_member_discord; use serenity::model::id::UserId; - use skynet_discord_bot::{whitelist_update, Config, Minecraft, Wolves}; + use skynet_discord_bot::{whitelist_update, Config}; use sqlx::Error; + use skynet_discord_bot::common::database::{Minecraft, Wolves}; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("link_minecraft").description("Link your minecraft account").create_option(|option| { @@ -122,7 +123,8 @@ pub(crate) mod server { use sqlx::Error; // this is to managfe the server side of commands related to minecraft use super::*; - use skynet_discord_bot::{is_admin, update_server, Config, Minecraft}; + use skynet_discord_bot::{is_admin, update_server, Config}; + use skynet_discord_bot::common::database::Minecraft; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("minecraft_add").description("Add a minecraft server").create_option(|option| { @@ -201,7 +203,8 @@ pub(crate) mod server { use serenity::builder::CreateApplicationCommand; use serenity::client::Context; use serenity::model::prelude::application_command::ApplicationCommandInteraction; - use skynet_discord_bot::{get_minecraft_config_server, is_admin, server_information, Config, DataBase}; + use skynet_discord_bot::{get_minecraft_config_server, is_admin, server_information, Config}; + use skynet_discord_bot::common::database::DataBase; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("minecraft_list").description("List your minecraft servers") @@ -262,8 +265,9 @@ pub(crate) mod server { use serenity::model::application::command::CommandOptionType; use serenity::model::id::GuildId; use serenity::model::prelude::application_command::{ApplicationCommandInteraction, CommandDataOptionValue}; - use skynet_discord_bot::{is_admin, DataBase, Minecraft}; + use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; + use skynet_discord_bot::common::database::{DataBase, Minecraft}; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("minecraft_delete").description("Delete a minecraft server").create_option(|option| { diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index 949bf32..89f9441 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -7,8 +7,9 @@ use serenity::{ }, }; -use skynet_discord_bot::{is_admin, DataBase, RoleAdder}; +use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; +use skynet_discord_bot::common::database::{DataBase, RoleAdder}; pub mod edit { use super::*; @@ -188,7 +189,7 @@ pub mod list {} pub mod tools { use serenity::client::Context; use serenity::model::guild::Member; - use skynet_discord_bot::RoleAdder; + use skynet_discord_bot::common::database::RoleAdder; use sqlx::{Pool, Sqlite}; pub async fn on_role_change(db: &Pool, ctx: &Context, mut new_data: Member) { diff --git a/src/common/database.rs b/src/common/database.rs index e69de29..6eb1734 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -0,0 +1,288 @@ +use serenity::prelude::TypeMapKey; +use std::sync::Arc; +use tokio::sync::RwLock; +use sqlx::{Error, FromRow, Pool, Row, Sqlite}; +use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow}; +use serenity::model::id::{ChannelId, GuildId, RoleId, UserId}; +use serde::{Deserialize, Serialize}; +use serenity::model::guild; +use std::str::FromStr; +use crate::Config; + +pub struct DataBase; +impl TypeMapKey for DataBase { + type Value = Arc>>; +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ServerMembers { + pub server: GuildId, + pub id_wolves: i64, + pub expiry: String, +} + +impl<'r> FromRow<'r, SqliteRow> for ServerMembers { + fn from_row(row: &'r SqliteRow) -> Result { + let server_tmp: i64 = row.try_get("server")?; + let server = GuildId::from(server_tmp as u64); + + Ok(Self { + server, + id_wolves: row.try_get("id_wolves")?, + expiry: row.try_get("expiry")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ServerMembersWolves { + pub server: GuildId, + pub id_wolves: i64, + pub expiry: String, + pub email: String, + pub discord: Option, + pub minecraft: Option, +} + +impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves { + fn from_row(row: &'r SqliteRow) -> Result { + let server_tmp: i64 = row.try_get("server")?; + let server = GuildId::from(server_tmp as u64); + let discord = match row.try_get("discord") { + Ok(x) => { + let tmp: i64 = x; + if tmp == 0 { + None + } else { + Some(UserId::from(tmp as u64)) + } + } + _ => None, + }; + + Ok(Self { + server, + id_wolves: row.try_get("id_wolves")?, + expiry: row.try_get("expiry")?, + email: row.try_get("email")?, + discord, + minecraft: row.try_get("minecraft")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Wolves { + pub id_wolves: i64, + pub email: String, + pub discord: Option, + pub minecraft: Option, +} + +impl<'r> FromRow<'r, SqliteRow> for Wolves { + fn from_row(row: &'r SqliteRow) -> Result { + let discord = match row.try_get("discord") { + Ok(x) => { + let tmp: i64 = x; + if tmp == 0 { + None + } else { + Some(UserId::from(tmp as u64)) + } + } + _ => None, + }; + + Ok(Self { + id_wolves: row.try_get("id_wolves")?, + email: row.try_get("email")?, + discord, + minecraft: row.try_get("minecraft")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct WolvesVerify { + pub email: String, + pub discord: UserId, + pub auth_code: String, + pub date_expiry: String, +} + +impl<'r> FromRow<'r, SqliteRow> for WolvesVerify { + fn from_row(row: &'r SqliteRow) -> Result { + let user_tmp: i64 = row.try_get("discord")?; + let discord = UserId::from(user_tmp as u64); + + Ok(Self { + email: row.try_get("email")?, + discord, + auth_code: row.try_get("auth_code")?, + date_expiry: row.try_get("date_expiry")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Servers { + pub server: GuildId, + pub wolves_api: String, + pub role_past: Option, + pub role_current: RoleId, + pub member_past: i64, + pub member_current: i64, + pub bot_channel_id: ChannelId, + // TODO: these can be removed in teh future with an API update + pub server_name: String, + pub wolves_link: String, +} + +impl<'r> FromRow<'r, SqliteRow> for Servers { + fn from_row(row: &'r SqliteRow) -> Result { + let server_tmp: i64 = row.try_get("server")?; + let server = GuildId::from(server_tmp as u64); + let role_past = match row.try_get("role_past") { + Ok(x) => { + let tmp: i64 = x; + if tmp == 0 { + None + } else { + Some(RoleId::from(tmp as u64)) + } + } + _ => None, + }; + let role_current = match row.try_get("role_current") { + Ok(x) => { + let tmp: i64 = x; + RoleId::from(tmp as u64) + } + _ => RoleId::from(0u64), + }; + + let bot_channel_tmp: i64 = row.try_get("bot_channel_id")?; + let bot_channel_id = ChannelId::from(bot_channel_tmp as u64); + + Ok(Self { + server, + wolves_api: row.try_get("wolves_api")?, + role_past, + role_current, + member_past: row.try_get("member_past")?, + member_current: row.try_get("member_current")?, + bot_channel_id, + server_name: row.try_get("server_name")?, + wolves_link: row.try_get("wolves_link")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Minecraft { + pub discord: GuildId, + pub minecraft: String, +} + +impl<'r> FromRow<'r, SqliteRow> for Minecraft { + fn from_row(row: &'r SqliteRow) -> Result { + let server_tmp: i64 = row.try_get("server_discord")?; + let discord = GuildId::from(server_tmp as u64); + + Ok(Self { + discord, + minecraft: row.try_get("server_minecraft")?, + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct RoleAdder { + pub server: GuildId, + pub role_a: RoleId, + pub role_b: RoleId, + pub role_c: RoleId, +} + +impl<'r> FromRow<'r, SqliteRow> for RoleAdder { + fn from_row(row: &'r SqliteRow) -> Result { + let server_tmp: i64 = row.try_get("server")?; + let server = GuildId::from(server_tmp as u64); + + Ok(Self { + server, + role_a: get_role_from_row(row, "role_a"), + role_b: get_role_from_row(row, "role_b"), + role_c: get_role_from_row(row, "role_c"), + }) + } +} + +fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId { + match row.try_get(col) { + Ok(x) => { + let tmp: i64 = x; + RoleId(tmp as u64) + } + _ => RoleId::from(0u64), + } +} + +pub async fn db_init(config: &Config) -> Result, Error> { + let database = format!("{}/{}", &config.home, &config.database); + + let pool = SqlitePoolOptions::new() + .max_connections(5) + .connect_with( + SqliteConnectOptions::from_str(&format!("sqlite://{}", database))? + .foreign_keys(true) + .create_if_missing(true), + ) + .await?; + + // migrations are amazing! + sqlx::migrate!("./db/migrations").run(&pool).await?; + + Ok(pool) +} + +pub async fn get_server_config(db: &Pool, server: &GuildId) -> Option { + sqlx::query_as::<_, Servers>( + r#" + SELECT * + FROM servers + WHERE server = ? + "#, + ) + .bind(*server.as_u64() as i64) + .fetch_one(db) + .await + .ok() +} + +pub async fn get_server_member(db: &Pool, server: &GuildId, member: &guild::Member) -> Result { + sqlx::query_as::<_, ServerMembersWolves>( + r#" + SELECT * + FROM server_members + JOIN wolves USING (id_wolves) + WHERE server = ? AND discord = ? + "#, + ) + .bind(*server.as_u64() as i64) + .bind(*member.user.id.as_u64() as i64) + .fetch_one(db) + .await +} + +pub async fn get_server_config_bulk(db: &Pool) -> Vec { + sqlx::query_as::<_, Servers>( + r#" + SELECT * + FROM servers + "#, + ) + .fetch_all(db) + .await + .unwrap_or_default() +} \ No newline at end of file diff --git a/src/common/mod.rs b/src/common/mod.rs index 5d73638..a992f8b 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1 +1,2 @@ -pub mod wolves; \ No newline at end of file +pub mod wolves; +pub mod database; \ No newline at end of file diff --git a/src/common/wolves.rs b/src/common/wolves.rs index d92c34d..99e7818 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -18,7 +18,8 @@ pub mod get_data { use serenity::client::Context; use serenity::model::id::GuildId; use sqlx::{Pool, Sqlite}; - use crate::{get_server_config_bulk, Config, DataBase, ServerMembers, ServerMembersWolves, Servers, Wolves}; + use crate::Config; + use crate::common::database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers, Wolves}; #[derive(Deserialize, Serialize, Debug)] struct WolvesResultUser { diff --git a/src/lib.rs b/src/lib.rs index 9e1c599..dfc7c4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,11 +3,8 @@ pub mod common; use dotenvy::dotenv; use serde::{Deserialize, Serialize}; use serenity::{ - model::{ - guild, - id::{ChannelId, GuildId, RoleId}, - }, - prelude::TypeMapKey, + model::id::{GuildId, RoleId}, + prelude::TypeMapKey, }; use crate::set_roles::get_server_member_bulk; @@ -18,11 +15,11 @@ use serenity::client::Context; use serenity::model::id::UserId; use serenity::model::prelude::application_command::ApplicationCommandInteraction; use sqlx::{ - sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow}, - Error, FromRow, Pool, Row, Sqlite, + Pool, Sqlite, }; -use std::{env, str::FromStr, sync::Arc}; +use std::{env, sync::Arc}; use tokio::sync::RwLock; +use common::database::{Minecraft, ServerMembersWolves, Servers}; pub struct Config { // manages where teh database is stored @@ -45,11 +42,6 @@ impl TypeMapKey for Config { type Value = Arc>; } -pub struct DataBase; -impl TypeMapKey for DataBase { - type Value = Arc>>; -} - pub fn get_config() -> Config { dotenv().ok(); @@ -98,272 +90,6 @@ pub fn get_config() -> Config { config } -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct ServerMembers { - pub server: GuildId, - pub id_wolves: i64, - pub expiry: String, -} -impl<'r> FromRow<'r, SqliteRow> for ServerMembers { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server")?; - let server = GuildId::from(server_tmp as u64); - - Ok(Self { - server, - id_wolves: row.try_get("id_wolves")?, - expiry: row.try_get("expiry")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct ServerMembersWolves { - pub server: GuildId, - pub id_wolves: i64, - pub expiry: String, - pub email: String, - pub discord: Option, - pub minecraft: Option, -} -impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server")?; - let server = GuildId::from(server_tmp as u64); - let discord = match row.try_get("discord") { - Ok(x) => { - let tmp: i64 = x; - if tmp == 0 { - None - } else { - Some(UserId::from(tmp as u64)) - } - } - _ => None, - }; - - Ok(Self { - server, - id_wolves: row.try_get("id_wolves")?, - expiry: row.try_get("expiry")?, - email: row.try_get("email")?, - discord, - minecraft: row.try_get("minecraft")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Wolves { - pub id_wolves: i64, - pub email: String, - pub discord: Option, - pub minecraft: Option, -} -impl<'r> FromRow<'r, SqliteRow> for Wolves { - fn from_row(row: &'r SqliteRow) -> Result { - let discord = match row.try_get("discord") { - Ok(x) => { - let tmp: i64 = x; - if tmp == 0 { - None - } else { - Some(UserId::from(tmp as u64)) - } - } - _ => None, - }; - - Ok(Self { - id_wolves: row.try_get("id_wolves")?, - email: row.try_get("email")?, - discord, - minecraft: row.try_get("minecraft")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct WolvesVerify { - pub email: String, - pub discord: UserId, - pub auth_code: String, - pub date_expiry: String, -} -impl<'r> FromRow<'r, SqliteRow> for WolvesVerify { - fn from_row(row: &'r SqliteRow) -> Result { - let user_tmp: i64 = row.try_get("discord")?; - let discord = UserId::from(user_tmp as u64); - - Ok(Self { - email: row.try_get("email")?, - discord, - auth_code: row.try_get("auth_code")?, - date_expiry: row.try_get("date_expiry")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Servers { - pub server: GuildId, - pub wolves_api: String, - pub role_past: Option, - pub role_current: RoleId, - pub member_past: i64, - pub member_current: i64, - pub bot_channel_id: ChannelId, - // TODO: these can be removed in teh future with an API update - pub server_name: String, - pub wolves_link: String, -} -impl<'r> FromRow<'r, SqliteRow> for Servers { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server")?; - let server = GuildId::from(server_tmp as u64); - let role_past = match row.try_get("role_past") { - Ok(x) => { - let tmp: i64 = x; - if tmp == 0 { - None - } else { - Some(RoleId::from(tmp as u64)) - } - } - _ => None, - }; - let role_current = match row.try_get("role_current") { - Ok(x) => { - let tmp: i64 = x; - RoleId::from(tmp as u64) - } - _ => RoleId::from(0u64), - }; - - let bot_channel_tmp: i64 = row.try_get("bot_channel_id")?; - let bot_channel_id = ChannelId::from(bot_channel_tmp as u64); - - Ok(Self { - server, - wolves_api: row.try_get("wolves_api")?, - role_past, - role_current, - member_past: row.try_get("member_past")?, - member_current: row.try_get("member_current")?, - bot_channel_id, - server_name: row.try_get("server_name")?, - wolves_link: row.try_get("wolves_link")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Minecraft { - pub discord: GuildId, - pub minecraft: String, -} -impl<'r> FromRow<'r, SqliteRow> for Minecraft { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server_discord")?; - let discord = GuildId::from(server_tmp as u64); - - Ok(Self { - discord, - minecraft: row.try_get("server_minecraft")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct RoleAdder { - pub server: GuildId, - pub role_a: RoleId, - pub role_b: RoleId, - pub role_c: RoleId, -} -impl<'r> FromRow<'r, SqliteRow> for RoleAdder { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server")?; - let server = GuildId::from(server_tmp as u64); - - Ok(Self { - server, - role_a: get_role_from_row(row, "role_a"), - role_b: get_role_from_row(row, "role_b"), - role_c: get_role_from_row(row, "role_c"), - }) - } -} - -fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId { - match row.try_get(col) { - Ok(x) => { - let tmp: i64 = x; - RoleId(tmp as u64) - } - _ => RoleId::from(0u64), - } -} - -pub async fn db_init(config: &Config) -> Result, Error> { - let database = format!("{}/{}", &config.home, &config.database); - - let pool = SqlitePoolOptions::new() - .max_connections(5) - .connect_with( - SqliteConnectOptions::from_str(&format!("sqlite://{}", database))? - .foreign_keys(true) - .create_if_missing(true), - ) - .await?; - - // migrations are amazing! - sqlx::migrate!("./db/migrations").run(&pool).await?; - - Ok(pool) -} - -pub async fn get_server_config(db: &Pool, server: &GuildId) -> Option { - sqlx::query_as::<_, Servers>( - r#" - SELECT * - FROM servers - WHERE server = ? - "#, - ) - .bind(*server.as_u64() as i64) - .fetch_one(db) - .await - .ok() -} - -pub async fn get_server_member(db: &Pool, server: &GuildId, member: &guild::Member) -> Result { - sqlx::query_as::<_, ServerMembersWolves>( - r#" - SELECT * - FROM server_members - JOIN wolves USING (id_wolves) - WHERE server = ? AND discord = ? - "#, - ) - .bind(*server.as_u64() as i64) - .bind(*member.user.id.as_u64() as i64) - .fetch_one(db) - .await -} - -pub async fn get_server_config_bulk(db: &Pool) -> Vec { - sqlx::query_as::<_, Servers>( - r#" - SELECT * - FROM servers - "#, - ) - .fetch_all(db) - .await - .unwrap_or_default() -} - pub fn get_now_iso(short: bool) -> String { let now = Utc::now(); if short { @@ -378,6 +104,7 @@ pub fn random_string(len: usize) -> String { } pub mod set_roles { + use crate::common::database::{DataBase, Wolves}; use super::*; pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option], members_changed: &[UserId]) { let db_lock = { diff --git a/src/main.rs b/src/main.rs index 1b80a6d..6f86f86 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,19 +3,20 @@ pub mod commands; use crate::commands::role_adder::tools::on_role_change; use serenity::model::guild::Member; use serenity::{ - async_trait, - client::{Context, EventHandler}, - model::{ - application::{command::Command, interaction::Interaction}, - gateway::{GatewayIntents, Ready}, - prelude::Activity, - user::OnlineStatus, - }, - Client, + async_trait, + client::{Context, EventHandler}, + model::{ + application::{command::Command, interaction::Interaction}, + gateway::{GatewayIntents, Ready}, + prelude::Activity, + user::OnlineStatus, + }, + Client, }; -use skynet_discord_bot::{db_init, get_config, get_server_config, get_server_member, Config, DataBase}; +use skynet_discord_bot::{get_config, Config}; use std::sync::Arc; use tokio::sync::RwLock; +use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_member, DataBase}; struct Handler; From 39277340839c85efb67ed6d7494aefa2da201ab6 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 01:06:21 +0000 Subject: [PATCH 017/170] feat: split out minecraft --- src/bin/update_minecraft.rs | 3 +- src/commands/minecraft.rs | 16 ++-- src/common/database.rs | 18 ---- src/common/minecraft.rs | 167 ++++++++++++++++++++++++++++++++++++ src/common/mod.rs | 3 +- src/lib.rs | 140 +----------------------------- 6 files changed, 182 insertions(+), 165 deletions(-) create mode 100644 src/common/minecraft.rs diff --git a/src/bin/update_minecraft.rs b/src/bin/update_minecraft.rs index f959d99..cae6b62 100644 --- a/src/bin/update_minecraft.rs +++ b/src/bin/update_minecraft.rs @@ -1,6 +1,7 @@ -use skynet_discord_bot::{get_config, get_minecraft_config, update_server, whitelist_wipe}; +use skynet_discord_bot::get_config; use std::collections::HashSet; use skynet_discord_bot::common::database::db_init; +use skynet_discord_bot::common::minecraft::{get_minecraft_config, update_server, whitelist_wipe}; #[tokio::main] async fn main() { diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index fb87326..962fe1f 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -16,9 +16,10 @@ pub(crate) mod user { use super::*; use crate::commands::link_email::link::get_server_member_discord; use serenity::model::id::UserId; - use skynet_discord_bot::{whitelist_update, Config}; + use skynet_discord_bot::Config; use sqlx::Error; - use skynet_discord_bot::common::database::{Minecraft, Wolves}; + use skynet_discord_bot::common::database::Wolves; + use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft}; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("link_minecraft").description("Link your minecraft account").create_option(|option| { @@ -123,8 +124,9 @@ pub(crate) mod server { use sqlx::Error; // this is to managfe the server side of commands related to minecraft use super::*; - use skynet_discord_bot::{is_admin, update_server, Config}; - use skynet_discord_bot::common::database::Minecraft; + use skynet_discord_bot::{is_admin, Config}; + use skynet_discord_bot::common::minecraft::Minecraft; + use skynet_discord_bot::common::minecraft::update_server; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("minecraft_add").description("Add a minecraft server").create_option(|option| { @@ -203,8 +205,9 @@ pub(crate) mod server { use serenity::builder::CreateApplicationCommand; use serenity::client::Context; use serenity::model::prelude::application_command::ApplicationCommandInteraction; - use skynet_discord_bot::{get_minecraft_config_server, is_admin, server_information, Config}; + use skynet_discord_bot::{is_admin, Config}; use skynet_discord_bot::common::database::DataBase; + use skynet_discord_bot::common::minecraft::{get_minecraft_config_server, server_information}; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("minecraft_list").description("List your minecraft servers") @@ -267,7 +270,8 @@ pub(crate) mod server { use serenity::model::prelude::application_command::{ApplicationCommandInteraction, CommandDataOptionValue}; use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; - use skynet_discord_bot::common::database::{DataBase, Minecraft}; + use skynet_discord_bot::common::database::DataBase; + use skynet_discord_bot::common::minecraft::Minecraft; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("minecraft_delete").description("Delete a minecraft server").create_option(|option| { diff --git a/src/common/database.rs b/src/common/database.rs index 6eb1734..9674499 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -178,24 +178,6 @@ impl<'r> FromRow<'r, SqliteRow> for Servers { } } -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Minecraft { - pub discord: GuildId, - pub minecraft: String, -} - -impl<'r> FromRow<'r, SqliteRow> for Minecraft { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server_discord")?; - let discord = GuildId::from(server_tmp as u64); - - Ok(Self { - discord, - minecraft: row.try_get("server_minecraft")?, - }) - } -} - #[derive(Debug, Clone, Deserialize, Serialize)] pub struct RoleAdder { pub server: GuildId, diff --git a/src/common/minecraft.rs b/src/common/minecraft.rs new file mode 100644 index 0000000..55ce53b --- /dev/null +++ b/src/common/minecraft.rs @@ -0,0 +1,167 @@ +use serde::{Deserialize, Serialize}; +use serde::de::DeserializeOwned; +use sqlx::{Error, FromRow, Pool, Row, Sqlite}; +use serenity::model::id::GuildId; +use sqlx::sqlite::SqliteRow; +use crate::Config; +use crate::set_roles::get_server_member_bulk; + + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Minecraft { + pub discord: GuildId, + pub minecraft: String, +} + +impl<'r> FromRow<'r, SqliteRow> for Minecraft { + fn from_row(row: &'r SqliteRow) -> Result { + let server_tmp: i64 = row.try_get("server_discord")?; + let discord = GuildId::from(server_tmp as u64); + + Ok(Self { + discord, + minecraft: row.try_get("server_minecraft")?, + }) + } +} + + +/** +loop through all members of server +get a list of folks with mc accounts that are members +and a list that arent members + */ +pub async fn update_server(server_id: &str, db: &Pool, g_id: &GuildId, config: &Config) { + let mut usernames = vec![]; + for member in get_server_member_bulk(db, g_id).await { + if let Some(x) = member.minecraft { + usernames.push(x); + } + } + if !usernames.is_empty() { + whitelist_update(&usernames, server_id, &config.discord_token_minecraft).await; + } +} + +async fn post(url: &str, bearer: &str, data: &T) { + match surf::post(url) + .header("Authorization", bearer) + .header("Content-Type", "application/json") + .header("Accept", "Application/vnd.pterodactyl.v1+json") + .body_json(&data) + { + Ok(req) => { + req.await.ok(); + } + Err(e) => { + dbg!(e); + } + } +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct ServerDetailsResSub { + pub identifier: String, + pub name: String, + pub description: String, + pub is_suspended: bool, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct ServerDetailsRes { + pub attributes: ServerDetailsResSub, +} + +async fn get(url: &str, bearer: &str) -> Option { + match surf::get(url) + .header("Authorization", bearer) + .header("Content-Type", "application/json") + .header("Accept", "Application/vnd.pterodactyl.v1+json") + .recv_json() + .await + { + Ok(res) => Some(res), + Err(e) => { + dbg!(e); + + None + } + } +} + +#[derive(Deserialize, Serialize, Debug)] +struct BodyCommand { + command: String, +} + +#[derive(Deserialize, Serialize, Debug)] +struct BodyDelete { + root: String, + files: Vec, +} + +pub async fn whitelist_update(add: &Vec, server: &str, token: &str) { + let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); + let bearer = format!("Bearer {token}"); + + for name in add { + let data = BodyCommand { + command: format!("whitelist add {name}"), + }; + post(&format!("{url_base}/command"), &bearer, &data).await; + } +} + +pub async fn whitelist_wipe(server: &str, token: &str) { + let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); + let bearer = format!("Bearer {token}"); + + // delete whitelist + let deletion = BodyDelete { + root: "/".to_string(), + files: vec!["whitelist.json".to_string()], + }; + post(&format!("{url_base}/files/delete"), &bearer, &deletion).await; + + // recreate teh file, passing in the type here so the compiler knows what type of vec it is + post::>(&format!("{url_base}/files/write?file=%2Fwhitelist.json"), &bearer, &vec![]).await; + + // reload the whitelist + let data = BodyCommand { + command: "whitelist reload".to_string(), + }; + post(&format!("{url_base}/command"), &bearer, &data).await; +} + +pub async fn server_information(server: &str, token: &str) -> Option { + let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); + let bearer = format!("Bearer {token}"); + get::(&format!("{url_base}/"), &bearer).await +} + +pub async fn get_minecraft_config(db: &Pool) -> Vec { + sqlx::query_as::<_, Minecraft>( + r#" + SELECT * + FROM minecraft + "#, + ) + .fetch_all(db) + .await + .unwrap_or_default() +} + +pub async fn get_minecraft_config_server(db: &Pool, g_id: GuildId) -> Vec { + sqlx::query_as::<_, Minecraft>( + r#" + SELECT * + FROM minecraft + WHERE server_discord = ?1 + "#, + ) + .bind(*g_id.as_u64() as i64) + .fetch_all(db) + .await + .unwrap_or_default() +} + diff --git a/src/common/mod.rs b/src/common/mod.rs index a992f8b..3302619 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,2 +1,3 @@ pub mod wolves; -pub mod database; \ No newline at end of file +pub mod database; +pub mod minecraft; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index dfc7c4f..5dbbe0d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,7 +19,7 @@ use sqlx::{ }; use std::{env, sync::Arc}; use tokio::sync::RwLock; -use common::database::{Minecraft, ServerMembersWolves, Servers}; +use common::database::{ServerMembersWolves, Servers}; pub struct Config { // manages where teh database is stored @@ -267,141 +267,3 @@ pub async fn is_admin(command: &ApplicationCommandInteraction, ctx: &Context) -> None } } - -/** -loop through all members of server -get a list of folks with mc accounts that are members -and a list that arent members - */ -pub async fn update_server(server_id: &str, db: &Pool, g_id: &GuildId, config: &Config) { - let mut usernames = vec![]; - for member in get_server_member_bulk(db, g_id).await { - if let Some(x) = member.minecraft { - usernames.push(x); - } - } - if !usernames.is_empty() { - whitelist_update(&usernames, server_id, &config.discord_token_minecraft).await; - } -} - -async fn post(url: &str, bearer: &str, data: &T) { - match surf::post(url) - .header("Authorization", bearer) - .header("Content-Type", "application/json") - .header("Accept", "Application/vnd.pterodactyl.v1+json") - .body_json(&data) - { - Ok(req) => { - req.await.ok(); - } - Err(e) => { - dbg!(e); - } - } -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct ServerDetailsResSub { - pub identifier: String, - pub name: String, - pub description: String, - pub is_suspended: bool, -} -#[derive(Deserialize, Serialize, Debug)] -pub struct ServerDetailsRes { - pub attributes: ServerDetailsResSub, -} - -async fn get(url: &str, bearer: &str) -> Option { - match surf::get(url) - .header("Authorization", bearer) - .header("Content-Type", "application/json") - .header("Accept", "Application/vnd.pterodactyl.v1+json") - .recv_json() - .await - { - Ok(res) => Some(res), - Err(e) => { - dbg!(e); - - None - } - } -} - -#[derive(Deserialize, Serialize, Debug)] -struct BodyCommand { - command: String, -} - -#[derive(Deserialize, Serialize, Debug)] -struct BodyDelete { - root: String, - files: Vec, -} - -pub async fn whitelist_update(add: &Vec, server: &str, token: &str) { - let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); - let bearer = format!("Bearer {token}"); - - for name in add { - let data = BodyCommand { - command: format!("whitelist add {name}"), - }; - post(&format!("{url_base}/command"), &bearer, &data).await; - } -} - -pub async fn whitelist_wipe(server: &str, token: &str) { - let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); - let bearer = format!("Bearer {token}"); - - // delete whitelist - let deletion = BodyDelete { - root: "/".to_string(), - files: vec!["whitelist.json".to_string()], - }; - post(&format!("{url_base}/files/delete"), &bearer, &deletion).await; - - // recreate teh file, passing in the type here so the compiler knows what type of vec it is - post::>(&format!("{url_base}/files/write?file=%2Fwhitelist.json"), &bearer, &vec![]).await; - - // reload the whitelist - let data = BodyCommand { - command: "whitelist reload".to_string(), - }; - post(&format!("{url_base}/command"), &bearer, &data).await; -} - -pub async fn server_information(server: &str, token: &str) -> Option { - let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); - let bearer = format!("Bearer {token}"); - get::(&format!("{url_base}/"), &bearer).await -} - -pub async fn get_minecraft_config(db: &Pool) -> Vec { - sqlx::query_as::<_, Minecraft>( - r#" - SELECT * - FROM minecraft - "#, - ) - .fetch_all(db) - .await - .unwrap_or_default() -} - -pub async fn get_minecraft_config_server(db: &Pool, g_id: GuildId) -> Vec { - sqlx::query_as::<_, Minecraft>( - r#" - SELECT * - FROM minecraft - WHERE server_discord = ?1 - "#, - ) - .bind(*g_id.as_u64() as i64) - .fetch_all(db) - .await - .unwrap_or_default() -} From 273c58d035bbbf1669d30547565c17caf721dae1 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 01:29:01 +0000 Subject: [PATCH 018/170] fmt: re-organise the regular data request --- src/bin/update_data.rs | 4 ++-- src/commands/add_server.rs | 4 ++-- src/common/wolves.rs | 13 ++++++++----- 3 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index e4b662d..310b97a 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -8,7 +8,7 @@ use skynet_discord_bot::{get_config, Config}; use std::{process, sync::Arc}; use tokio::sync::RwLock; use skynet_discord_bot::common::database::{db_init, DataBase}; -use skynet_discord_bot::common::wolves::get_data::get_wolves_cns; +use skynet_discord_bot::common::wolves::cns::get_wolves; #[tokio::main] async fn main() { @@ -46,7 +46,7 @@ impl EventHandler for Handler { println!("{} is connected!", ready.user.name); // get the data for each individual club/soc - get_wolves_cns(&ctx).await; + get_wolves(&ctx).await; // finish up process::exit(0); diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index dace04e..1886e56 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -6,7 +6,7 @@ use serenity::{ prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, }, }; -use skynet_discord_bot::common::wolves::get_data::get_wolves_cns; +use skynet_discord_bot::common::wolves::cns::get_wolves; use skynet_discord_bot::{is_admin, set_roles::update_server}; use sqlx::{Error, Pool, Sqlite}; use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers}; @@ -216,7 +216,7 @@ async fn add_server(db: &Pool, ctx: &Context, server: &Servers) -> Resul // update all users if update { // handle wolves api here - get_wolves_cns(ctx).await; + get_wolves(ctx).await; let mut roles_remove = vec![]; if current_remove { diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 99e7818..71f30c0 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -11,7 +11,10 @@ // pub members: Vec, // } -pub mod get_data { +/** + This is getting data for Clubs and Socs +*/ +pub mod cns { use crate::set_roles::update_server; use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; @@ -50,7 +53,7 @@ pub mod get_data { pub email: String, pub expiry: String, } - pub async fn get_wolves_cns(ctx: &Context) { + pub async fn get_wolves(ctx: &Context) { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Database in TypeMap.").clone() @@ -75,7 +78,7 @@ pub mod get_data { // list of users that need to be updated for this server let mut user_to_update = vec![]; - for user in get_wolves_cns_sub(&config, wolves_api).await { + for user in get_wolves_sub(&config, wolves_api).await { let id = user.member_id.parse::().unwrap_or_default(); match existing.get(&(id as i64)) { None => { @@ -103,7 +106,7 @@ pub mod get_data { } } - pub async fn get_server_member(db: &Pool, server: &GuildId) -> Vec { + async fn get_server_member(db: &Pool, server: &GuildId) -> Vec { sqlx::query_as::<_, ServerMembersWolves>( r#" SELECT * @@ -121,7 +124,7 @@ pub mod get_data { .unwrap_or_default() } - async fn get_wolves_cns_sub(config: &Config, wolves_api: &str) -> Vec { + async fn get_wolves_sub(config: &Config, wolves_api: &str) -> Vec { if config.wolves_url.is_empty() { return vec![]; } From fe5aa5b2526f719e8940b9c5ba0eb8c229160eed Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 01:34:23 +0000 Subject: [PATCH 019/170] prep: rough format for requesting data for an indivual and committee --- src/common/wolves.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 71f30c0..766ee92 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -188,4 +188,14 @@ pub mod cns { } } } -} \ No newline at end of file +} + +/** + Get and store the data on C&S committees +*/ +pub mod committees {} + +/** +get the data for an individual user +*/ +pub mod individual {} \ No newline at end of file From bd80bda22fcf3adeb3787531d124dc6ef1e0736e Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 02:06:24 +0000 Subject: [PATCH 020/170] feat: added rough code to get an individuals member_id --- src/common/wolves.rs | 176 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 151 insertions(+), 25 deletions(-) diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 766ee92..c13b163 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -1,3 +1,7 @@ +use serde::{Deserialize, Serialize}; +use sqlx::{Pool, Sqlite}; +use crate::common::database::Wolves; + /** This file relates to anything that directly interacts with teh wolves API */ @@ -11,6 +15,45 @@ // pub members: Vec, // } +#[derive(Deserialize, Serialize, Debug)] +struct WolvesResultUserMin { + // committee: String, + member_id: String, + // first_name: String, + // last_name: String, + contact_email: String, + // opt_in_email: String, + // student_id: Option, + // note: Option, + // expiry: String, + // requested: String, + // approved: String, + // sitename: String, + // domain: String, +} +async fn add_users_wolves(db: &Pool, user: &WolvesResultUserMin) { + // expiry + match sqlx::query_as::<_, Wolves>( + " + INSERT INTO wolves (id_wolves, email) + VALUES ($1, $2) + ON CONFLICT(id_wolves) DO UPDATE SET email = $2 + ", + ) + .bind(&user.member_id) + .bind(&user.contact_email) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + println!("Failure to insert into Wolves {:?}", user); + println!("{:?}", e); + } + } +} + + /** This is getting data for Clubs and Socs */ @@ -23,6 +66,7 @@ pub mod cns { use sqlx::{Pool, Sqlite}; use crate::Config; use crate::common::database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers, Wolves}; + use crate::common::wolves::{add_users_wolves, WolvesResultUserMin}; #[derive(Deserialize, Serialize, Debug)] struct WolvesResultUser { @@ -41,6 +85,15 @@ pub mod cns { domain: String, } + impl From<&WolvesResultUser> for WolvesResultUserMin { + fn from(value: &WolvesResultUser) -> Self { + Self { + member_id: value.member_id.to_owned(), + contact_email: value.contact_email.to_owned(), + } + } + } + #[derive(Deserialize, Serialize, Debug)] struct WolvesResult { success: i8, @@ -83,12 +136,12 @@ pub mod cns { match existing.get(&(id as i64)) { None => { // user does not exist already, add everything - add_users_wolves(&db, &user).await; + add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await; add_users_server_members(&db, server, &user).await; } Some(old) => { // always update wolves table, in case data has changed - add_users_wolves(&db, &user).await; + add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await; if old.expiry != user.expiry { add_users_server_members(&db, server, &user).await; @@ -129,8 +182,10 @@ pub mod cns { return vec![]; } + let url = format!("{}/get_members", &config.wolves_url); + // get wolves data - if let Ok(mut res) = surf::post(&config.wolves_url).header("X-AM-Identity", wolves_api).await { + if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await { if let Ok(WolvesResult { success, result, @@ -147,27 +202,6 @@ pub mod cns { vec![] } - async fn add_users_wolves(db: &Pool, user: &WolvesResultUser) { - // expiry - match sqlx::query_as::<_, Wolves>( - " - INSERT INTO wolves (id_wolves, email) - VALUES ($1, $2) - ON CONFLICT(id_wolves) DO UPDATE SET email = $2 - ", - ) - .bind(&user.member_id) - .bind(&user.contact_email) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into Wolves {:?}", user); - println!("{:?}", e); - } - } - } async fn add_users_server_members(db: &Pool, server: &GuildId, user: &WolvesResultUser) { match sqlx::query_as::<_, ServerMembers>( " @@ -198,4 +232,96 @@ pub mod committees {} /** get the data for an individual user */ -pub mod individual {} \ No newline at end of file +pub mod individual { + use serde::{Deserialize, Serialize}; + use serenity::client::Context; + use crate::common::database::DataBase; + use crate::common::wolves::{add_users_wolves, WolvesResultUserMin}; + use crate::Config; + + + #[derive(Deserialize, Serialize, Debug)] + struct WolvesResultUser { + // committee: String, + member_id: String, + // first_name: String, + // last_name: String, + contact_email: String, + // opt_in_email: String, + // student_id: Option, + // note: Option, + // expiry: String, + // requested: String, + // approved: String, + // sitename: String, + // domain: String, + } + + impl From<&WolvesResultUser> for WolvesResultUserMin { + fn from(value: &WolvesResultUser) -> Self { + Self { + member_id: value.member_id.to_owned(), + contact_email: value.contact_email.to_owned(), + } + } + } + + #[derive(Deserialize, Serialize, Debug)] + struct WolvesResult { + success: i8, + result: WolvesResultUser, + } + + + pub async fn get_user(ctx: &Context, email: &str) -> bool{ + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Database in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config = config_lock.read().await; + + // TODO: proper api key management + let api_key = ""; + // request data from wolves + match get_user_sub(&config, api_key, email).await { + None => { + false + } + // if exists save it and return true + Some(user) => { + // add to db + add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await; + + true + } + } + } + + async fn get_user_sub(config: &Config, wolves_api: &str, email: &str) -> Option { + if config.wolves_url.is_empty() { + return None; + } + + // TODO: Change teh stored env value to teh base domain + let url = format!("{}/get_member", &config.wolves_url); + + // get wolves data + if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await { + if let Ok(WolvesResult { success, result, }) = res.body_json().await { + if success != 1 { + return None; + } + + return Some(result); + } + } + + None + } +} \ No newline at end of file From aff6299ac7ecea32367b7d08476be9c5fb53f635 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 12:49:35 +0000 Subject: [PATCH 021/170] feat: added the committee request from wolves --- Cargo.lock | 1 + Cargo.toml | 1 + db/migrations/7_committee-mk-ii.sql | 8 +- src/common/wolves.rs | 121 ++++++++++++++++++++++++++-- 4 files changed, 119 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8c9113..af01bd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2419,6 +2419,7 @@ dependencies = [ "maud", "rand 0.8.5", "serde", + "serde_json", "serenity", "sqlx", "surf", diff --git a/Cargo.toml b/Cargo.toml index a26023a..2128303 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ dotenvy = "0.15.7" # For sqlite sqlx = { version = "0.7.1", features = [ "runtime-tokio", "sqlite", "migrate" ] } +serde_json = { version = "1.0", features = ["raw_value"] } # create random strings rand = "0.8.5" diff --git a/db/migrations/7_committee-mk-ii.sql b/db/migrations/7_committee-mk-ii.sql index 2ddf316..c086f3d 100644 --- a/db/migrations/7_committee-mk-ii.sql +++ b/db/migrations/7_committee-mk-ii.sql @@ -3,8 +3,8 @@ DROP TABLE committee; -- new table pulling from teh api CREATE TABLE IF NOT EXISTS committees ( - id integer PRIMARY KEY, - name text not null, - link text not null, - members text not null + id integer PRIMARY KEY, + name text not null, + link text not null, + committee text not null ); diff --git a/src/common/wolves.rs b/src/common/wolves.rs index c13b163..380d25a 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -7,13 +7,6 @@ use crate::common::database::Wolves; */ -// #[derive(Debug, Clone, sqlx::FromRow)] -// pub struct Committees { -// pub id: i64, -// pub name: String, -// pub link: String, -// pub members: Vec, -// } #[derive(Deserialize, Serialize, Debug)] struct WolvesResultUserMin { @@ -227,7 +220,119 @@ pub mod cns { /** Get and store the data on C&S committees */ -pub mod committees {} +pub mod committees { + use serde::{Deserialize, Serialize}; + use serenity::client::Context; + use sqlx::{ Pool, Sqlite}; + use crate::common::database::{DataBase}; + use crate::Config; + + // This is what Wolves returns to us + #[derive(Deserialize, Serialize, Debug)] + struct WolvesResult { + success: i8, + result: Vec, + } + + // this is teh actual data we care about + #[derive(Deserialize, Serialize, Debug)] + struct WolvesResultCNS { + id: String, + name: String, + // Link to their page such as https://ulwolves.ie/society/computer + link: String, + // array of Committee members member_id's + committee: Vec + } + + // Database entry for it + #[derive(Debug, Clone, sqlx::FromRow)] + pub struct Committees { + pub id: i64, + pub name: String, + pub link: String, + #[sqlx(json)] + pub committee: Vec, + } + + impl From for Committees { + fn from(value: WolvesResultCNS) -> Self { + Self{ + id: value.id.parse().unwrap_or(0), + name: value.name, + link: value.link, + committee: value.committee.iter().map(|x| x.parse::().unwrap_or(0)).collect(), + } + } + } + + pub async fn get_cns(ctx: &Context){ + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Database in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config = config_lock.read().await; + + // TODO: proper api key management + let api_key = ""; + // request data from wolves + for committee in get_committees(&config, api_key).await { + let tmp = Committees::from(committee); + add_committee(&db, &tmp).await; + } + } + + async fn get_committees(config: &Config, wolves_api: &str) -> Vec { + if config.wolves_url.is_empty() { + return vec![]; + } + + // TODO: Change teh stored env value to teh base domain + let url = format!("{}/get_cns", &config.wolves_url); + + // get wolves data + if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await { + if let Ok(WolvesResult { success, result, }) = res.body_json().await { + if success != 1 { + return vec![]; + } + + return result; + } + } + + vec![] + } + + async fn add_committee(db: &Pool, committee: &Committees) { + match sqlx::query_as::<_, Committees>( + " + INSERT INTO committees (id, name, link, committee) + VALUES ($1, $2, $3, $4) + ON CONFLICT(id) DO UPDATE SET committee = $4 + ", + ) + .bind(committee.id) + .bind(&committee.name) + .bind(&committee.link) + .bind(serde_json::to_string(&committee.committee).unwrap_or_default()) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + println!("Failure to insert into Committees {:?}", committee); + println!("{:?}", e); + } + } + } +} /** get the data for an individual user From 3e6dc9d56028178b4107267ab4162b349b4fcaf1 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 14:20:36 +0000 Subject: [PATCH 022/170] feat: actually get the data for teh committees and pop it in the database --- src/bin/update_data.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index 310b97a..3151080 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -9,6 +9,7 @@ use std::{process, sync::Arc}; use tokio::sync::RwLock; use skynet_discord_bot::common::database::{db_init, DataBase}; use skynet_discord_bot::common::wolves::cns::get_wolves; +use skynet_discord_bot::common::wolves::committees::get_cns; #[tokio::main] async fn main() { @@ -47,6 +48,9 @@ impl EventHandler for Handler { // get the data for each individual club/soc get_wolves(&ctx).await; + + // get teh data for the clubs/socs committees + get_cns(&ctx).await; // finish up process::exit(0); From 61e76db8ddebbd6efbdd20bdf96af4621074d5d1 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 21:17:44 +0000 Subject: [PATCH 023/170] feat: this should be able to update teh roles of commettee members on teh discord server --- src/bin/update_users.rs | 145 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 142 insertions(+), 3 deletions(-) diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index cf48650..133dd13 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -6,8 +6,14 @@ use serenity::{ }; use skynet_discord_bot::{get_config, set_roles, Config}; use std::{process, sync::Arc}; +use std::collections::HashMap; +use serenity::builder::EditRole; +use serenity::model::guild::{Member, Role}; +use serenity::model::id::{GuildId, RoleId, UserId}; +use sqlx::{Pool, Sqlite}; use tokio::sync::RwLock; -use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase}; +use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase, Servers, Wolves}; +use skynet_discord_bot::common::wolves::committees::Committees; #[tokio::main] async fn main() { @@ -44,14 +50,18 @@ impl EventHandler for Handler { let ctx = Arc::new(ctx); println!("{} is connected!", ready.user.name); - bulk_check(Arc::clone(&ctx)).await; + // this goes into each server and sets roles for each wolves member + check_bulk(Arc::clone(&ctx)).await; + + // u[date committee server + committee::check_committee(Arc::clone(&ctx)).await; // finish up process::exit(0); } } -async fn bulk_check(ctx: Arc) { +async fn check_bulk(ctx: Arc) { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() @@ -63,3 +73,132 @@ async fn bulk_check(ctx: Arc) { set_roles::update_server(&ctx, &server_config, &[], &[]).await; } } + +// for updating committee members +pub mod committee { + use super::*; + + pub(crate) async fn check_committee(ctx: Arc) { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + + let db = db_lock.read().await; + + let server = GuildId(1220150752656363520); + let mut members = server.members(&ctx, None, None).await.unwrap_or_default(); + + update_committees(&db, &ctx, &mut members).await; + } + + pub async fn update_committees(db: &Pool, ctx: &Context, members: &mut Vec){ + let server = GuildId(1220150752656363520); + let committee_member = RoleId(1226602779968274573); + let committees = get_committees(db).await; + + // information about the server + let roles = server.roles(&ctx).await.unwrap_or_default(); + + // make a hashmap of the nameof roles to quickly get them out again + let mut roles_name = HashMap::new(); + for role in roles.values() { + roles_name.insert(role.name.to_owned(), role.to_owned()); + } + + + // a map of users and the roles they are goign to be getting + let mut users_roles = HashMap::new(); + + // a list of all the roles that can be removed from folks who should have them + let mut committee_roles = vec![ + committee_member + ]; + + for committee in &committees { + // get the role for this committee/club/soc + let role = match roles_name.get(&committee.name) { + Some(x) => {Some(x.to_owned())} + None => { + // create teh role if it does not exist + match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name)).await{ + Ok(x) => { Some(x) } + Err(_) => {None} + } + } + }; + + // so if the role exists + if let Some(r) = role { + committee_roles.push(r.id); + + for id_wolves in &committee.committee { + // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) + if let Some(x) = get_server_member_discord(&db, id_wolves).await { + if let Some(member_tmp) = x.discord { + let values = users_roles.entry(member_tmp).or_insert(vec![]); + values.push(r.id); + } + } + } + } + } + + // now we have a map of all users that should get roles time to go through all the folks on teh server + for member in members { + let roles_required = match users_roles.get(&member.user.id) { + None => { + vec![] + } + Some(x) => { + let mut combined = x.to_owned(); + // this is the main role, since it provides access to everything. + combined.push(committee_member); + combined + } + }; + + // get a list of all the roles to remove from someone + let mut roles_rem = vec![]; + for role in &committee_roles { + if !roles_required.contains(role) { + roles_rem.push(role.to_owned()); + } + } + if !roles_rem.is_empty() { + member.remove_roles(&ctx, &roles_rem).await.unwrap_or_default(); + } + + if !roles_required.is_empty() { + // these roles are flavor roles, only there to make folks mentionable + member.add_roles(&ctx, &roles_required).await.unwrap_or_default(); + } + } + } + + async fn get_committees(db: &Pool) -> Vec { + sqlx::query_as::<_, Committees>( + r#" + SELECT * + FROM committees + "#, + ) + .fetch_all(db) + .await + .unwrap_or_default() + } + + async fn get_server_member_discord(db: &Pool, user: &i64) -> Option { + sqlx::query_as::<_, Wolves>( + r#" + SELECT * + FROM wolves + WHERE id_wolves = ? + "#, + ) + .bind(user) + .fetch_one(db) + .await + .ok() + } +} \ No newline at end of file From f1138a3c81a8095053f31c1269f63c4af0eda403 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 21:34:21 +0000 Subject: [PATCH 024/170] fix: moved the methods that changes role into their own module folder --- src/bin/update_users.rs | 142 +------------------- src/commands/add_server.rs | 15 ++- src/common/minecraft.rs | 2 +- src/common/mod.rs | 3 +- src/common/set_roles.rs | 266 +++++++++++++++++++++++++++++++++++++ src/common/wolves.rs | 2 +- src/lib.rs | 139 +------------------ 7 files changed, 283 insertions(+), 286 deletions(-) create mode 100644 src/common/set_roles.rs diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index 133dd13..9b98c79 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -4,16 +4,11 @@ use serenity::{ model::gateway::{GatewayIntents, Ready}, Client, }; -use skynet_discord_bot::{get_config, set_roles, Config}; +use skynet_discord_bot::{get_config, Config}; use std::{process, sync::Arc}; -use std::collections::HashMap; -use serenity::builder::EditRole; -use serenity::model::guild::{Member, Role}; -use serenity::model::id::{GuildId, RoleId, UserId}; -use sqlx::{Pool, Sqlite}; use tokio::sync::RwLock; -use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase, Servers, Wolves}; -use skynet_discord_bot::common::wolves::committees::Committees; +use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase}; +use skynet_discord_bot::common::set_roles::{committee, normal}; #[tokio::main] async fn main() { @@ -70,135 +65,6 @@ async fn check_bulk(ctx: Arc) { let db = db_lock.read().await; for server_config in get_server_config_bulk(&db).await { - set_roles::update_server(&ctx, &server_config, &[], &[]).await; - } -} - -// for updating committee members -pub mod committee { - use super::*; - - pub(crate) async fn check_committee(ctx: Arc) { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - - let db = db_lock.read().await; - - let server = GuildId(1220150752656363520); - let mut members = server.members(&ctx, None, None).await.unwrap_or_default(); - - update_committees(&db, &ctx, &mut members).await; - } - - pub async fn update_committees(db: &Pool, ctx: &Context, members: &mut Vec){ - let server = GuildId(1220150752656363520); - let committee_member = RoleId(1226602779968274573); - let committees = get_committees(db).await; - - // information about the server - let roles = server.roles(&ctx).await.unwrap_or_default(); - - // make a hashmap of the nameof roles to quickly get them out again - let mut roles_name = HashMap::new(); - for role in roles.values() { - roles_name.insert(role.name.to_owned(), role.to_owned()); - } - - - // a map of users and the roles they are goign to be getting - let mut users_roles = HashMap::new(); - - // a list of all the roles that can be removed from folks who should have them - let mut committee_roles = vec![ - committee_member - ]; - - for committee in &committees { - // get the role for this committee/club/soc - let role = match roles_name.get(&committee.name) { - Some(x) => {Some(x.to_owned())} - None => { - // create teh role if it does not exist - match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name)).await{ - Ok(x) => { Some(x) } - Err(_) => {None} - } - } - }; - - // so if the role exists - if let Some(r) = role { - committee_roles.push(r.id); - - for id_wolves in &committee.committee { - // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) - if let Some(x) = get_server_member_discord(&db, id_wolves).await { - if let Some(member_tmp) = x.discord { - let values = users_roles.entry(member_tmp).or_insert(vec![]); - values.push(r.id); - } - } - } - } - } - - // now we have a map of all users that should get roles time to go through all the folks on teh server - for member in members { - let roles_required = match users_roles.get(&member.user.id) { - None => { - vec![] - } - Some(x) => { - let mut combined = x.to_owned(); - // this is the main role, since it provides access to everything. - combined.push(committee_member); - combined - } - }; - - // get a list of all the roles to remove from someone - let mut roles_rem = vec![]; - for role in &committee_roles { - if !roles_required.contains(role) { - roles_rem.push(role.to_owned()); - } - } - if !roles_rem.is_empty() { - member.remove_roles(&ctx, &roles_rem).await.unwrap_or_default(); - } - - if !roles_required.is_empty() { - // these roles are flavor roles, only there to make folks mentionable - member.add_roles(&ctx, &roles_required).await.unwrap_or_default(); - } - } - } - - async fn get_committees(db: &Pool) -> Vec { - sqlx::query_as::<_, Committees>( - r#" - SELECT * - FROM committees - "#, - ) - .fetch_all(db) - .await - .unwrap_or_default() - } - - async fn get_server_member_discord(db: &Pool, user: &i64) -> Option { - sqlx::query_as::<_, Wolves>( - r#" - SELECT * - FROM wolves - WHERE id_wolves = ? - "#, - ) - .bind(user) - .fetch_one(db) - .await - .ok() + normal::update_server(&ctx, &server_config, &[], &[]).await; } } \ No newline at end of file diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index 1886e56..f049028 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -1,15 +1,16 @@ use serenity::{ - builder::CreateApplicationCommand, - client::Context, - model::{ - application::interaction::application_command::ApplicationCommandInteraction, - prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, - }, + builder::CreateApplicationCommand, + client::Context, + model::{ + application::interaction::application_command::ApplicationCommandInteraction, + prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, + }, }; use skynet_discord_bot::common::wolves::cns::get_wolves; -use skynet_discord_bot::{is_admin, set_roles::update_server}; +use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers}; +use skynet_discord_bot::common::set_roles::normal::update_server; pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { // check if user has high enough permisssions diff --git a/src/common/minecraft.rs b/src/common/minecraft.rs index 55ce53b..40fbb4f 100644 --- a/src/common/minecraft.rs +++ b/src/common/minecraft.rs @@ -4,7 +4,7 @@ use sqlx::{Error, FromRow, Pool, Row, Sqlite}; use serenity::model::id::GuildId; use sqlx::sqlite::SqliteRow; use crate::Config; -use crate::set_roles::get_server_member_bulk; +use crate::common::set_roles::normal::get_server_member_bulk; #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/src/common/mod.rs b/src/common/mod.rs index 3302619..6b1adc9 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,3 +1,4 @@ pub mod wolves; pub mod database; -pub mod minecraft; \ No newline at end of file +pub mod minecraft; +pub mod set_roles; \ No newline at end of file diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs new file mode 100644 index 0000000..20c8e6d --- /dev/null +++ b/src/common/set_roles.rs @@ -0,0 +1,266 @@ +pub mod normal { + use serenity::client::Context; + use serenity::model::id::{GuildId, RoleId, UserId}; + use sqlx::{Pool, Sqlite}; + use crate::common::database::{DataBase, ServerMembersWolves, Servers, Wolves}; + use crate::get_now_iso; + + pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option], members_changed: &[UserId]) { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Database in TypeMap.").clone() + }; + + let db = db_lock.read().await; + + let Servers { + server, + role_past, + role_current, + .. + } = server; + + let mut roles_set = [0, 0, 0]; + let mut members = vec![]; + + for member in get_server_member_bulk(&db, server).await { + if let Some(x) = member.discord { + members.push(x); + } + } + let mut members_all = members.len(); + + if let Ok(x) = server.members(ctx, None, None).await { + for mut member in x { + // members_changed acts as an override to only deal with teh users in it + if !members_changed.is_empty() && !members_changed.contains(&member.user.id) { + continue; + } + + if members.contains(&member.user.id) { + let mut roles = vec![]; + + if let Some(role) = &role_past { + if !member.roles.contains(role) { + roles_set[0] += 1; + roles.push(role.to_owned()); + } + } + + if !member.roles.contains(role_current) { + roles_set[1] += 1; + roles.push(role_current.to_owned()); + } + + if let Err(e) = member.add_roles(ctx, &roles).await { + println!("{:?}", e); + } + } else { + // old and never + + if let Some(role) = &role_past { + if member.roles.contains(role) { + members_all += 1; + } + } + + if member.roles.contains(role_current) { + roles_set[2] += 1; + // if theya re not a current member and have the role then remove it + if let Err(e) = member.remove_role(ctx, role_current).await { + println!("{:?}", e); + } + } + } + for role in remove_roles.iter().flatten() { + if let Err(e) = member.remove_role(ctx, role).await { + println!("{:?}", e); + } + } + } + } + + set_server_numbers(&db, server, members_all as i64, members.len() as i64).await; + + // small bit of logging to note changes over time + println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.as_u64(), roles_set[0], roles_set[1], roles_set[2]); + } + + pub async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec { + sqlx::query_as::<_, ServerMembersWolves>( + r#" + SELECT * + FROM server_members + JOIN wolves USING (id_wolves) + WHERE ( + server = ? + AND discord IS NOT NULL + AND expiry > ? + ) + "#, + ) + .bind(*server.as_u64() as i64) + .bind(get_now_iso(true)) + .fetch_all(db) + .await + .unwrap_or_default() + } + + async fn set_server_numbers(db: &Pool, server: &GuildId, past: i64, current: i64) { + match sqlx::query_as::<_, Wolves>( + " + UPDATE servers + SET member_past = ?, member_current = ? + WHERE server = ? + ", + ) + .bind(past) + .bind(current) + .bind(*server.as_u64() as i64) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + println!("Failure to insert into {}", server.as_u64()); + println!("{:?}", e); + } + } + } +} + +// for updating committee members +pub mod committee { + use std::collections::HashMap; + use std::sync::Arc; + use serenity::client::Context; + use serenity::model::guild::Member; + use serenity::model::id::{GuildId, RoleId}; + use sqlx::{Pool, Sqlite}; + use crate::common::database::{DataBase, Wolves}; + use crate::common::wolves::committees::Committees; + + pub async fn check_committee(ctx: Arc) { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + + let db = db_lock.read().await; + + let server = GuildId(1220150752656363520); + let mut members = server.members(&ctx, None, None).await.unwrap_or_default(); + + update_committees(&db, &ctx, &mut members).await; + } + + pub async fn update_committees(db: &Pool, ctx: &Context, members: &mut Vec){ + let server = GuildId(1220150752656363520); + let committee_member = RoleId(1226602779968274573); + let committees = get_committees(db).await; + + // information about the server + let roles = server.roles(&ctx).await.unwrap_or_default(); + + // make a hashmap of the nameof roles to quickly get them out again + let mut roles_name = HashMap::new(); + for role in roles.values() { + roles_name.insert(role.name.to_owned(), role.to_owned()); + } + + + // a map of users and the roles they are goign to be getting + let mut users_roles = HashMap::new(); + + // a list of all the roles that can be removed from folks who should have them + let mut committee_roles = vec![ + committee_member + ]; + + for committee in &committees { + // get the role for this committee/club/soc + let role = match roles_name.get(&committee.name) { + Some(x) => {Some(x.to_owned())} + None => { + // create teh role if it does not exist + match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name)).await{ + Ok(x) => { Some(x) } + Err(_) => {None} + } + } + }; + + // so if the role exists + if let Some(r) = role { + committee_roles.push(r.id); + + for id_wolves in &committee.committee { + // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) + if let Some(x) = get_server_member_discord(&db, id_wolves).await { + if let Some(member_tmp) = x.discord { + let values = users_roles.entry(member_tmp).or_insert(vec![]); + values.push(r.id); + } + } + } + } + } + + // now we have a map of all users that should get roles time to go through all the folks on teh server + for member in members { + let roles_required = match users_roles.get(&member.user.id) { + None => { + vec![] + } + Some(x) => { + let mut combined = x.to_owned(); + // this is the main role, since it provides access to everything. + combined.push(committee_member); + combined + } + }; + + // get a list of all the roles to remove from someone + let mut roles_rem = vec![]; + for role in &committee_roles { + if !roles_required.contains(role) { + roles_rem.push(role.to_owned()); + } + } + if !roles_rem.is_empty() { + member.remove_roles(&ctx, &roles_rem).await.unwrap_or_default(); + } + + if !roles_required.is_empty() { + // these roles are flavor roles, only there to make folks mentionable + member.add_roles(&ctx, &roles_required).await.unwrap_or_default(); + } + } + } + + async fn get_committees(db: &Pool) -> Vec { + sqlx::query_as::<_, Committees>( + r#" + SELECT * + FROM committees + "#, + ) + .fetch_all(db) + .await + .unwrap_or_default() + } + + async fn get_server_member_discord(db: &Pool, user: &i64) -> Option { + sqlx::query_as::<_, Wolves>( + r#" + SELECT * + FROM wolves + WHERE id_wolves = ? + "#, + ) + .bind(user) + .fetch_one(db) + .await + .ok() + } +} \ No newline at end of file diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 380d25a..0560b27 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -51,7 +51,7 @@ async fn add_users_wolves(db: &Pool, user: &WolvesResultUserMin) { This is getting data for Clubs and Socs */ pub mod cns { - use crate::set_roles::update_server; + use crate::common::set_roles::normal::update_server; use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; use serenity::client::Context; diff --git a/src/lib.rs b/src/lib.rs index 5dbbe0d..335f51e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,25 +2,15 @@ pub mod common; use dotenvy::dotenv; use serde::{Deserialize, Serialize}; -use serenity::{ - model::id::{GuildId, RoleId}, - prelude::TypeMapKey, -}; +use serenity::prelude::TypeMapKey; -use crate::set_roles::get_server_member_bulk; use chrono::{Datelike, SecondsFormat, Utc}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use serde::de::DeserializeOwned; use serenity::client::Context; -use serenity::model::id::UserId; use serenity::model::prelude::application_command::ApplicationCommandInteraction; -use sqlx::{ - Pool, Sqlite, -}; use std::{env, sync::Arc}; use tokio::sync::RwLock; -use common::database::{ServerMembersWolves, Servers}; - pub struct Config { // manages where teh database is stored pub home: String, @@ -103,133 +93,6 @@ pub fn random_string(len: usize) -> String { thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect() } -pub mod set_roles { - use crate::common::database::{DataBase, Wolves}; - use super::*; - pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option], members_changed: &[UserId]) { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Database in TypeMap.").clone() - }; - - let db = db_lock.read().await; - - let Servers { - server, - role_past, - role_current, - .. - } = server; - - let mut roles_set = [0, 0, 0]; - let mut members = vec![]; - - for member in get_server_member_bulk(&db, server).await { - if let Some(x) = member.discord { - members.push(x); - } - } - let mut members_all = members.len(); - - if let Ok(x) = server.members(ctx, None, None).await { - for mut member in x { - // members_changed acts as an override to only deal with teh users in it - if !members_changed.is_empty() && !members_changed.contains(&member.user.id) { - continue; - } - - if members.contains(&member.user.id) { - let mut roles = vec![]; - - if let Some(role) = &role_past { - if !member.roles.contains(role) { - roles_set[0] += 1; - roles.push(role.to_owned()); - } - } - - if !member.roles.contains(role_current) { - roles_set[1] += 1; - roles.push(role_current.to_owned()); - } - - if let Err(e) = member.add_roles(ctx, &roles).await { - println!("{:?}", e); - } - } else { - // old and never - - if let Some(role) = &role_past { - if member.roles.contains(role) { - members_all += 1; - } - } - - if member.roles.contains(role_current) { - roles_set[2] += 1; - // if theya re not a current member and have the role then remove it - if let Err(e) = member.remove_role(ctx, role_current).await { - println!("{:?}", e); - } - } - } - for role in remove_roles.iter().flatten() { - if let Err(e) = member.remove_role(ctx, role).await { - println!("{:?}", e); - } - } - } - } - - set_server_numbers(&db, server, members_all as i64, members.len() as i64).await; - - // small bit of logging to note changes over time - println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.as_u64(), roles_set[0], roles_set[1], roles_set[2]); - } - - pub async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec { - sqlx::query_as::<_, ServerMembersWolves>( - r#" - SELECT * - FROM server_members - JOIN wolves USING (id_wolves) - WHERE ( - server = ? - AND discord IS NOT NULL - AND expiry > ? - ) - "#, - ) - .bind(*server.as_u64() as i64) - .bind(get_now_iso(true)) - .fetch_all(db) - .await - .unwrap_or_default() - } - - async fn set_server_numbers(db: &Pool, server: &GuildId, past: i64, current: i64) { - match sqlx::query_as::<_, Wolves>( - " - UPDATE servers - SET member_past = ?, member_current = ? - WHERE server = ? - ", - ) - .bind(past) - .bind(current) - .bind(*server.as_u64() as i64) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into {}", server.as_u64()); - println!("{:?}", e); - } - } - } -} - /** For any time ye need to check if a user who calls a command has admin privlages */ From 32249364ff4250c7d528f1bf7131566f06a015f2 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 21:40:48 +0000 Subject: [PATCH 025/170] feat: new committee member joins the committee server they automagically get roles --- src/main.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main.rs b/src/main.rs index 6f86f86..a274a8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,8 +15,10 @@ use serenity::{ }; use skynet_discord_bot::{get_config, Config}; use std::sync::Arc; +use serenity::model::id::GuildId; use tokio::sync::RwLock; use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_member, DataBase}; +use skynet_discord_bot::common::set_roles::committee::update_committees; struct Handler; @@ -35,6 +37,13 @@ impl EventHandler for Handler { Some(x) => x, }; + // committee server takes priority + if new_member.guild_id.eq(&GuildId(1220150752656363520)) { + let mut member = vec![new_member.clone()]; + update_committees(&db, &ctx, &mut member).await; + return; + } + if get_server_member(&db, &new_member.guild_id, &new_member).await.is_ok() { let mut roles = vec![]; From b7161e26143a29c8693c5c2544f83d8bb7c480f9 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 21:51:14 +0000 Subject: [PATCH 026/170] todo: added note --- src/common/wolves.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 0560b27..5759104 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -63,6 +63,7 @@ pub mod cns { #[derive(Deserialize, Serialize, Debug)] struct WolvesResultUser { + // TODO: Might be worth trying to get this replaced with the club/soc ID? committee: String, member_id: String, first_name: String, From 344d6d3585bd0be34369086e428c7ee17c093419 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 28 Oct 2024 21:53:04 +0000 Subject: [PATCH 027/170] fmt: formatting and clippy --- src/bin/update_data.rs | 16 +++---- src/bin/update_minecraft.rs | 4 +- src/bin/update_users.rs | 14 +++--- src/commands/add_server.rs | 4 +- src/commands/link_email.rs | 4 +- src/commands/minecraft.rs | 14 +++--- src/commands/role_adder.rs | 2 +- src/common/database.rs | 16 +++---- src/common/minecraft.rs | 11 ++--- src/common/mod.rs | 4 +- src/common/set_roles.rs | 53 +++++++++++----------- src/common/wolves.rs | 89 +++++++++++++++++++------------------ src/lib.rs | 7 +-- src/main.rs | 30 ++++++------- 14 files changed, 130 insertions(+), 138 deletions(-) diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index 3151080..4385a1c 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -1,15 +1,15 @@ use serenity::{ - async_trait, - client::{Context, EventHandler}, - model::gateway::{GatewayIntents, Ready}, - Client, + async_trait, + client::{Context, EventHandler}, + model::gateway::{GatewayIntents, Ready}, + Client, }; -use skynet_discord_bot::{get_config, Config}; -use std::{process, sync::Arc}; -use tokio::sync::RwLock; use skynet_discord_bot::common::database::{db_init, DataBase}; use skynet_discord_bot::common::wolves::cns::get_wolves; use skynet_discord_bot::common::wolves::committees::get_cns; +use skynet_discord_bot::{get_config, Config}; +use std::{process, sync::Arc}; +use tokio::sync::RwLock; #[tokio::main] async fn main() { @@ -48,7 +48,7 @@ impl EventHandler for Handler { // get the data for each individual club/soc get_wolves(&ctx).await; - + // get teh data for the clubs/socs committees get_cns(&ctx).await; diff --git a/src/bin/update_minecraft.rs b/src/bin/update_minecraft.rs index cae6b62..f5d3634 100644 --- a/src/bin/update_minecraft.rs +++ b/src/bin/update_minecraft.rs @@ -1,7 +1,7 @@ -use skynet_discord_bot::get_config; -use std::collections::HashSet; use skynet_discord_bot::common::database::db_init; use skynet_discord_bot::common::minecraft::{get_minecraft_config, update_server, whitelist_wipe}; +use skynet_discord_bot::get_config; +use std::collections::HashSet; #[tokio::main] async fn main() { diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index 9b98c79..e3170eb 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -1,14 +1,14 @@ use serenity::{ - async_trait, - client::{Context, EventHandler}, - model::gateway::{GatewayIntents, Ready}, - Client, + async_trait, + client::{Context, EventHandler}, + model::gateway::{GatewayIntents, Ready}, + Client, }; +use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase}; +use skynet_discord_bot::common::set_roles::{committee, normal}; use skynet_discord_bot::{get_config, Config}; use std::{process, sync::Arc}; use tokio::sync::RwLock; -use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase}; -use skynet_discord_bot::common::set_roles::{committee, normal}; #[tokio::main] async fn main() { @@ -67,4 +67,4 @@ async fn check_bulk(ctx: Arc) { for server_config in get_server_config_bulk(&db).await { normal::update_server(&ctx, &server_config, &[], &[]).await; } -} \ No newline at end of file +} diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index f049028..52284ac 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -6,11 +6,11 @@ use serenity::{ prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, }, }; +use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers}; +use skynet_discord_bot::common::set_roles::normal::update_server; use skynet_discord_bot::common::wolves::cns::get_wolves; use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; -use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers}; -use skynet_discord_bot::common::set_roles::normal::update_server; pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { // check if user has high enough permisssions diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 224c6da..25e0262 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -13,9 +13,9 @@ use serenity::{ prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, }, }; +use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify}; use skynet_discord_bot::{get_now_iso, random_string, Config}; use sqlx::{Pool, Sqlite}; -use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify}; pub mod link { use super::*; @@ -241,8 +241,8 @@ pub mod verify { use crate::commands::link_email::link::{db_pending_clear_expired, get_verify_from_db}; use serenity::model::user::User; use skynet_discord_bot::common::database::get_server_config; - use sqlx::Error; use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; + use sqlx::Error; pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { let db_lock = { diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 962fe1f..61b304d 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -16,10 +16,10 @@ pub(crate) mod user { use super::*; use crate::commands::link_email::link::get_server_member_discord; use serenity::model::id::UserId; - use skynet_discord_bot::Config; - use sqlx::Error; use skynet_discord_bot::common::database::Wolves; use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft}; + use skynet_discord_bot::Config; + use sqlx::Error; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("link_minecraft").description("Link your minecraft account").create_option(|option| { @@ -124,9 +124,9 @@ pub(crate) mod server { use sqlx::Error; // this is to managfe the server side of commands related to minecraft use super::*; - use skynet_discord_bot::{is_admin, Config}; - use skynet_discord_bot::common::minecraft::Minecraft; use skynet_discord_bot::common::minecraft::update_server; + use skynet_discord_bot::common::minecraft::Minecraft; + use skynet_discord_bot::{is_admin, Config}; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("minecraft_add").description("Add a minecraft server").create_option(|option| { @@ -205,9 +205,9 @@ pub(crate) mod server { use serenity::builder::CreateApplicationCommand; use serenity::client::Context; use serenity::model::prelude::application_command::ApplicationCommandInteraction; - use skynet_discord_bot::{is_admin, Config}; use skynet_discord_bot::common::database::DataBase; use skynet_discord_bot::common::minecraft::{get_minecraft_config_server, server_information}; + use skynet_discord_bot::{is_admin, Config}; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("minecraft_list").description("List your minecraft servers") @@ -268,10 +268,10 @@ pub(crate) mod server { use serenity::model::application::command::CommandOptionType; use serenity::model::id::GuildId; use serenity::model::prelude::application_command::{ApplicationCommandInteraction, CommandDataOptionValue}; - use skynet_discord_bot::is_admin; - use sqlx::{Error, Pool, Sqlite}; use skynet_discord_bot::common::database::DataBase; use skynet_discord_bot::common::minecraft::Minecraft; + use skynet_discord_bot::is_admin; + use sqlx::{Error, Pool, Sqlite}; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { command.name("minecraft_delete").description("Delete a minecraft server").create_option(|option| { diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index 89f9441..d45d445 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -7,9 +7,9 @@ use serenity::{ }, }; +use skynet_discord_bot::common::database::{DataBase, RoleAdder}; use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; -use skynet_discord_bot::common::database::{DataBase, RoleAdder}; pub mod edit { use super::*; diff --git a/src/common/database.rs b/src/common/database.rs index 9674499..b3edc18 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -1,13 +1,13 @@ -use serenity::prelude::TypeMapKey; -use std::sync::Arc; -use tokio::sync::RwLock; -use sqlx::{Error, FromRow, Pool, Row, Sqlite}; -use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow}; -use serenity::model::id::{ChannelId, GuildId, RoleId, UserId}; +use crate::Config; use serde::{Deserialize, Serialize}; use serenity::model::guild; +use serenity::model::id::{ChannelId, GuildId, RoleId, UserId}; +use serenity::prelude::TypeMapKey; +use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow}; +use sqlx::{Error, FromRow, Pool, Row, Sqlite}; use std::str::FromStr; -use crate::Config; +use std::sync::Arc; +use tokio::sync::RwLock; pub struct DataBase; impl TypeMapKey for DataBase { @@ -267,4 +267,4 @@ pub async fn get_server_config_bulk(db: &Pool) -> Vec { .fetch_all(db) .await .unwrap_or_default() -} \ No newline at end of file +} diff --git a/src/common/minecraft.rs b/src/common/minecraft.rs index 40fbb4f..6ee11c8 100644 --- a/src/common/minecraft.rs +++ b/src/common/minecraft.rs @@ -1,11 +1,10 @@ -use serde::{Deserialize, Serialize}; +use crate::common::set_roles::normal::get_server_member_bulk; +use crate::Config; use serde::de::DeserializeOwned; -use sqlx::{Error, FromRow, Pool, Row, Sqlite}; +use serde::{Deserialize, Serialize}; use serenity::model::id::GuildId; use sqlx::sqlite::SqliteRow; -use crate::Config; -use crate::common::set_roles::normal::get_server_member_bulk; - +use sqlx::{Error, FromRow, Pool, Row, Sqlite}; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Minecraft { @@ -25,7 +24,6 @@ impl<'r> FromRow<'r, SqliteRow> for Minecraft { } } - /** loop through all members of server get a list of folks with mc accounts that are members @@ -164,4 +162,3 @@ pub async fn get_minecraft_config_server(db: &Pool, g_id: GuildId) -> Ve .await .unwrap_or_default() } - diff --git a/src/common/mod.rs b/src/common/mod.rs index 6b1adc9..38f457a 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -1,4 +1,4 @@ -pub mod wolves; pub mod database; pub mod minecraft; -pub mod set_roles; \ No newline at end of file +pub mod set_roles; +pub mod wolves; diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 20c8e6d..c3c80cb 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -1,11 +1,11 @@ pub mod normal { - use serenity::client::Context; - use serenity::model::id::{GuildId, RoleId, UserId}; - use sqlx::{Pool, Sqlite}; - use crate::common::database::{DataBase, ServerMembersWolves, Servers, Wolves}; - use crate::get_now_iso; + use crate::common::database::{DataBase, ServerMembersWolves, Servers, Wolves}; + use crate::get_now_iso; + use serenity::client::Context; + use serenity::model::id::{GuildId, RoleId, UserId}; + use sqlx::{Pool, Sqlite}; - pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option], members_changed: &[UserId]) { + pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option], members_changed: &[UserId]) { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Database in TypeMap.").clone() @@ -131,14 +131,14 @@ pub mod normal { // for updating committee members pub mod committee { - use std::collections::HashMap; - use std::sync::Arc; + use crate::common::database::{DataBase, Wolves}; + use crate::common::wolves::committees::Committees; use serenity::client::Context; use serenity::model::guild::Member; use serenity::model::id::{GuildId, RoleId}; use sqlx::{Pool, Sqlite}; - use crate::common::database::{DataBase, Wolves}; - use crate::common::wolves::committees::Committees; + use std::collections::HashMap; + use std::sync::Arc; pub async fn check_committee(ctx: Arc) { let db_lock = { @@ -154,7 +154,7 @@ pub mod committee { update_committees(&db, &ctx, &mut members).await; } - pub async fn update_committees(db: &Pool, ctx: &Context, members: &mut Vec){ + pub async fn update_committees(db: &Pool, ctx: &Context, members: &mut Vec) { let server = GuildId(1220150752656363520); let committee_member = RoleId(1226602779968274573); let committees = get_committees(db).await; @@ -168,24 +168,21 @@ pub mod committee { roles_name.insert(role.name.to_owned(), role.to_owned()); } - // a map of users and the roles they are goign to be getting let mut users_roles = HashMap::new(); // a list of all the roles that can be removed from folks who should have them - let mut committee_roles = vec![ - committee_member - ]; + let mut committee_roles = vec![committee_member]; for committee in &committees { // get the role for this committee/club/soc let role = match roles_name.get(&committee.name) { - Some(x) => {Some(x.to_owned())} + Some(x) => Some(x.to_owned()), None => { // create teh role if it does not exist - match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name)).await{ - Ok(x) => { Some(x) } - Err(_) => {None} + match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name)).await { + Ok(x) => Some(x), + Err(_) => None, } } }; @@ -196,7 +193,7 @@ pub mod committee { for id_wolves in &committee.committee { // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) - if let Some(x) = get_server_member_discord(&db, id_wolves).await { + if let Some(x) = get_server_member_discord(db, id_wolves).await { if let Some(member_tmp) = x.discord { let values = users_roles.entry(member_tmp).or_insert(vec![]); values.push(r.id); @@ -245,9 +242,9 @@ pub mod committee { FROM committees "#, ) - .fetch_all(db) - .await - .unwrap_or_default() + .fetch_all(db) + .await + .unwrap_or_default() } async fn get_server_member_discord(db: &Pool, user: &i64) -> Option { @@ -258,9 +255,9 @@ pub mod committee { WHERE id_wolves = ? "#, ) - .bind(user) - .fetch_one(db) - .await - .ok() + .bind(user) + .fetch_one(db) + .await + .ok() } -} \ No newline at end of file +} diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 5759104..7e29f1c 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -1,12 +1,10 @@ +use crate::common::database::Wolves; use serde::{Deserialize, Serialize}; use sqlx::{Pool, Sqlite}; -use crate::common::database::Wolves; /** - This file relates to anything that directly interacts with teh wolves API - */ - - + This file relates to anything that directly interacts with teh wolves API +*/ #[derive(Deserialize, Serialize, Debug)] struct WolvesResultUserMin { @@ -33,10 +31,10 @@ async fn add_users_wolves(db: &Pool, user: &WolvesResultUserMin) { ON CONFLICT(id_wolves) DO UPDATE SET email = $2 ", ) - .bind(&user.member_id) - .bind(&user.contact_email) - .fetch_optional(db) - .await + .bind(&user.member_id) + .bind(&user.contact_email) + .fetch_optional(db) + .await { Ok(_) => {} Err(e) => { @@ -46,20 +44,19 @@ async fn add_users_wolves(db: &Pool, user: &WolvesResultUserMin) { } } - /** This is getting data for Clubs and Socs */ pub mod cns { + use crate::common::database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers}; use crate::common::set_roles::normal::update_server; - use std::collections::BTreeMap; + use crate::common::wolves::{add_users_wolves, WolvesResultUserMin}; + use crate::Config; use serde::{Deserialize, Serialize}; use serenity::client::Context; use serenity::model::id::GuildId; use sqlx::{Pool, Sqlite}; - use crate::Config; - use crate::common::database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers, Wolves}; - use crate::common::wolves::{add_users_wolves, WolvesResultUserMin}; + use std::collections::BTreeMap; #[derive(Deserialize, Serialize, Debug)] struct WolvesResultUser { @@ -222,11 +219,11 @@ pub mod cns { Get and store the data on C&S committees */ pub mod committees { + use crate::common::database::DataBase; + use crate::Config; use serde::{Deserialize, Serialize}; use serenity::client::Context; - use sqlx::{ Pool, Sqlite}; - use crate::common::database::{DataBase}; - use crate::Config; + use sqlx::{Pool, Sqlite}; // This is what Wolves returns to us #[derive(Deserialize, Serialize, Debug)] @@ -243,22 +240,22 @@ pub mod committees { // Link to their page such as https://ulwolves.ie/society/computer link: String, // array of Committee members member_id's - committee: Vec + committee: Vec, } // Database entry for it #[derive(Debug, Clone, sqlx::FromRow)] pub struct Committees { - pub id: i64, - pub name: String, - pub link: String, - #[sqlx(json)] - pub committee: Vec, + pub id: i64, + pub name: String, + pub link: String, + #[sqlx(json)] + pub committee: Vec, } impl From for Committees { fn from(value: WolvesResultCNS) -> Self { - Self{ + Self { id: value.id.parse().unwrap_or(0), name: value.name, link: value.link, @@ -267,7 +264,7 @@ pub mod committees { } } - pub async fn get_cns(ctx: &Context){ + pub async fn get_cns(ctx: &Context) { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Database in TypeMap.").clone() @@ -283,7 +280,7 @@ pub mod committees { // TODO: proper api key management let api_key = ""; // request data from wolves - for committee in get_committees(&config, api_key).await { + for committee in get_committees(&config, api_key).await { let tmp = Committees::from(committee); add_committee(&db, &tmp).await; } @@ -299,7 +296,11 @@ pub mod committees { // get wolves data if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await { - if let Ok(WolvesResult { success, result, }) = res.body_json().await { + if let Ok(WolvesResult { + success, + result, + }) = res.body_json().await + { if success != 1 { return vec![]; } @@ -319,12 +320,12 @@ pub mod committees { ON CONFLICT(id) DO UPDATE SET committee = $4 ", ) - .bind(committee.id) - .bind(&committee.name) - .bind(&committee.link) - .bind(serde_json::to_string(&committee.committee).unwrap_or_default()) - .fetch_optional(db) - .await + .bind(committee.id) + .bind(&committee.name) + .bind(&committee.link) + .bind(serde_json::to_string(&committee.committee).unwrap_or_default()) + .fetch_optional(db) + .await { Ok(_) => {} Err(e) => { @@ -339,12 +340,11 @@ pub mod committees { get the data for an individual user */ pub mod individual { - use serde::{Deserialize, Serialize}; - use serenity::client::Context; use crate::common::database::DataBase; use crate::common::wolves::{add_users_wolves, WolvesResultUserMin}; use crate::Config; - + use serde::{Deserialize, Serialize}; + use serenity::client::Context; #[derive(Deserialize, Serialize, Debug)] struct WolvesResultUser { @@ -378,8 +378,7 @@ pub mod individual { result: WolvesResultUser, } - - pub async fn get_user(ctx: &Context, email: &str) -> bool{ + pub async fn get_user(ctx: &Context, email: &str) -> bool { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Database in TypeMap.").clone() @@ -396,9 +395,7 @@ pub mod individual { let api_key = ""; // request data from wolves match get_user_sub(&config, api_key, email).await { - None => { - false - } + None => false, // if exists save it and return true Some(user) => { // add to db @@ -409,7 +406,7 @@ pub mod individual { } } - async fn get_user_sub(config: &Config, wolves_api: &str, email: &str) -> Option { + async fn get_user_sub(config: &Config, wolves_api: &str, _email: &str) -> Option { if config.wolves_url.is_empty() { return None; } @@ -419,7 +416,11 @@ pub mod individual { // get wolves data if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await { - if let Ok(WolvesResult { success, result, }) = res.body_json().await { + if let Ok(WolvesResult { + success, + result, + }) = res.body_json().await + { if success != 1 { return None; } @@ -430,4 +431,4 @@ pub mod individual { None } -} \ No newline at end of file +} diff --git a/src/lib.rs b/src/lib.rs index 335f51e..6abc48a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,11 @@ pub mod common; -use dotenvy::dotenv; -use serde::{Deserialize, Serialize}; -use serenity::prelude::TypeMapKey; - use chrono::{Datelike, SecondsFormat, Utc}; +use dotenvy::dotenv; use rand::{distributions::Alphanumeric, thread_rng, Rng}; -use serde::de::DeserializeOwned; use serenity::client::Context; use serenity::model::prelude::application_command::ApplicationCommandInteraction; +use serenity::prelude::TypeMapKey; use std::{env, sync::Arc}; use tokio::sync::RwLock; pub struct Config { diff --git a/src/main.rs b/src/main.rs index a274a8b..17d6615 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,23 +2,23 @@ pub mod commands; use crate::commands::role_adder::tools::on_role_change; use serenity::model::guild::Member; -use serenity::{ - async_trait, - client::{Context, EventHandler}, - model::{ - application::{command::Command, interaction::Interaction}, - gateway::{GatewayIntents, Ready}, - prelude::Activity, - user::OnlineStatus, - }, - Client, -}; -use skynet_discord_bot::{get_config, Config}; -use std::sync::Arc; use serenity::model::id::GuildId; -use tokio::sync::RwLock; +use serenity::{ + async_trait, + client::{Context, EventHandler}, + model::{ + application::{command::Command, interaction::Interaction}, + gateway::{GatewayIntents, Ready}, + prelude::Activity, + user::OnlineStatus, + }, + Client, +}; use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_member, DataBase}; use skynet_discord_bot::common::set_roles::committee::update_committees; +use skynet_discord_bot::{get_config, Config}; +use std::sync::Arc; +use tokio::sync::RwLock; struct Handler; @@ -43,7 +43,7 @@ impl EventHandler for Handler { update_committees(&db, &ctx, &mut member).await; return; } - + if get_server_member(&db, &new_member.guild_id, &new_member).await.is_ok() { let mut roles = vec![]; From da4d006bc0984893f1a6db9975d5c7696c3a7a9d Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 29 Oct 2024 14:00:43 +0000 Subject: [PATCH 028/170] doc: update user docs --- README.md | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 068979a..1f5fe3a 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@ # Skynet Discord Bot -This bots core purpose is to give members roles based on their status on . -It uses an api key provided by wolves to get member lists. +This bots core purpose is to give members roles based on their status on https://ulwolves.ie. +It uses an api key provided by wolves to get member lists. Users are able to link their wolves account to the bot and that works across discord servers. For example is a user links on the CompSoc Discord then they will also get their roles (automagically) on Games Dev if they are a member there. -## Commands - Admin - +## Setup - Committee You need admin access to run any of the commands in this section. -Either the server owner or a suer with the ``Administrator`` permission +Either the server owner or a user with the ``Administrator`` permission ### Getting the Skynet Discord bot 1. Email ``keith@assurememberships.com`` from committee email and say ye want an api key for ``193.1.99.74`` @@ -42,29 +41,21 @@ This unlinks a minecraft server from your club/society. ``/minecraft_delete SERVER_ID`` -## Commands - User +## Setup - Users +This is to link your Discord account with your UL Wolves account. +**You will only need to do this once**. ### Setup -* Start the process using ``/link_wolves WOLVES_EMAIL`` - * The email that is in the Contact Email here: -* An email will be sent to them that they need to verify using ``/verify CODE`` -* This will only have to be done once. +1. in a server with the Skynet Bot enter ``/link_wolves WOLVES_EMAIL`` + * Your ``WOLVES_EMAIL`` is the email in the Contact Email here: + * This is most likely your student mail +2. An email will be sent to you with a verification code. +3. Verify the code using ``/verify CODE`` +4. Once complete your Wolves and Discord accounts will be linked. -* If the user is an active member on wolves - * If they are in any servers with teh Skynet Bot - * They will get relevant roles. - * If they Join a server with teh bot enabled. - * They will be granted the roles automatically -* If the user is **not** an active member on wolves. - * If they have no Roles - * No change - * If they have Past Member Role - * No change - * If they have both Roles - * The current-member role will be removed from them - * Past Member role will remain unchanged +You will get member roles on any Discord that is using the bot that you are a member of. ### Minecraft -Users can link their Minecraft username to grant them access to any servers where teh whitelist is managed by teh bot. +You can link your Minecraft username to grant you access to any Minecraft server run by UL Computer Society. ``/link_minecraft MINECRAFT_USERNAME`` From 2daa010d258f4ac47e2db99bcf3957a9a164af65 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 4 Nov 2024 12:43:09 +0000 Subject: [PATCH 029/170] doc: updated committee instructions --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1f5fe3a..7c75f3f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # Skynet Discord Bot -This bots core purpose is to give members roles based on their status on https://ulwolves.ie. +This bots core purpose is to give members roles based on their status on . It uses an api key provided by wolves to get member lists. Users are able to link their wolves account to the bot and that works across discord servers. @@ -7,19 +7,52 @@ For example is a user links on the CompSoc Discord then they will also get their ## Setup - Committee You need admin access to run any of the commands in this section. -Either the server owner or a user with the ``Administrator`` permission +Either the server owner or a user with the ``Administrator`` permission. -### Getting the Skynet Discord bot -1. Email ``keith@assurememberships.com`` from committee email and say ye want an api key for ``193.1.99.74`` -2. Create a role for current members (maybe call it ``current-member`` ?) -3. (Optional) create a role for all past and current members (ye can use the existing ``member`` role for this, ) -4. Invite the bot https://discord.com/api/oauth2/authorize?client_id=1145761669256069270&permissions=139855185984&scope=bot -5. Make sure the bot role ``@skynet`` is above these two roles (so it can manage them) -6. Make sure that you have a role that gives ye administrator powers -7. Use the command ``/add`` and insert the api key, role current and role all (desktop recommended) +### Get the API Key +The ``api_key`` is used by the Bot in order to request information, it will be used later in the process. + +1. Email ``keith@assurememberships.com`` from committee email and say you want an ``api_key`` for ``193.1.99.74`` + * The committee email is the one here: + * This may take up to a week to get the key. + +### Setup Server +The Bot reason for existing is being able to give members Roles. +So we have to create those. + +1. Create a role for Current Members. + * You can call it whatever you want. + * ``member-current`` is a good choice. + * This should be a new role +2. **Optional**: you can create a role that is given to folks who were but no longer a member. + * ``member`` would be a good choice for this + * If you have an existing member role this is also a good fit. The reason for both roles is ye have one for active members while the second is for all current and past members. +### Invite Bot +1. Invite the bot https://discord.com/api/oauth2/authorize?client_id=1145761669256069270&permissions=139855185984&scope=bot +2. Make sure the bot role ``@skynet`` is above these two roles created in the previous step + * This is so it can manage the roles (give and remove them from users) + +### Setup Bot +This is where the bot is configured. +You will need the ``api_key`` from the start of the process. +You (personally) will need a role with ``Administrator`` permission to be able to do this. + +1. Use the command ``/add`` and a list of options will pop up. +2. ``api_key`` is the key you got from Keith earlier. +3. ``role_current`` is the ``member-current`` that you created earlier. +4. ``role_past`` (optional) is the role for all current and past members. +5. ``bot_channel`` is a channel that folks are recommended to use the bot. + * You can have it so folks cannot see message history +6. ``server_name`` For example ``UL Computer Society`` + * Will be removed in the future +7. ``wolves_link`` for example + * Will be removed in the future + +At this point the bot is set up and no further action is required. + ### Minecraft The bot is able to manage the whitelist of a Minecraft server managed by the Computer Society. Talk to us to get a server. From 733827c3e6175fe64f0a74cd4dcb439172f1ec88 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 9 Nov 2024 01:17:43 +0000 Subject: [PATCH 030/170] feat: added support for teh new api key --- src/lib.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 6abc48a..a3b4edf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,6 +24,8 @@ pub struct Config { // wolves API base for clubs/socs pub wolves_url: String, + // API key for accessing more general resources + pub wolves_api: String, } impl TypeMapKey for Config { type Value = Arc>; @@ -44,6 +46,7 @@ pub fn get_config() -> Config { mail_user: "".to_string(), mail_pass: "".to_string(), wolves_url: "".to_string(), + wolves_api: "".to_string(), }; if let Ok(x) = env::var("DATABASE_HOME") { @@ -74,6 +77,10 @@ pub fn get_config() -> Config { config.wolves_url = x.trim().to_string(); } + if let Ok(x) = env::var("WOLVES_API") { + config.wolves_api = x.trim().to_string(); + } + config } From 7a6421469ca98c753fa7057464d794f71a4ba78d Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 9 Nov 2024 02:23:46 +0000 Subject: [PATCH 031/170] feat: now able to get the memebr_id from just email --- Cargo.lock | 340 ++++++++++++++++++++++++++++++++++--- Cargo.toml | 3 +- src/commands/link_email.rs | 81 ++++++++- 3 files changed, 402 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index af01bd3..cc8ef0d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1024,7 +1024,26 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes 1.7.1", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", "indexmap", "slab", "tokio", @@ -1147,6 +1166,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes 1.7.1", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -1154,7 +1184,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes 1.7.1", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes 1.7.1", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes 1.7.1", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -1216,9 +1269,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -1230,6 +1283,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +dependencies = [ + "bytes 1.7.1", + "futures-channel", + "futures-util", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -1237,13 +1310,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", - "hyper", + "http 0.2.12", + "hyper 0.14.30", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", ] +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.5.0", + "hyper-util", + "rustls 0.23.16", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes 1.7.1", + "http-body-util", + "hyper 1.5.0", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes 1.7.1", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.5.0", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -1330,7 +1455,7 @@ dependencies = [ "curl-sys", "flume 0.9.2", "futures-lite 1.13.0", - "http", + "http 0.2.12", "log", "once_cell", "slab", @@ -2014,11 +2139,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", @@ -2028,12 +2153,12 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.21.12", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", - "system-configuration", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", "tokio", "tokio-rustls 0.24.1", "tokio-util", @@ -2047,6 +2172,49 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64 0.22.1", + "bytes 1.7.1", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.0", + "hyper-rustls 0.27.3", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration 0.6.1", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + [[package]] name = "ring" version = "0.16.20" @@ -2145,10 +2313,23 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -2158,6 +2339,21 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -2168,6 +2364,17 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "ryu" version = "1.0.18" @@ -2321,7 +2528,7 @@ dependencies = [ "mime_guess", "parking_lot", "percent-encoding", - "reqwest", + "reqwest 0.11.27", "serde", "serde-value", "serde_json", @@ -2399,6 +2606,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "2.2.0" @@ -2418,6 +2634,7 @@ dependencies = [ "lettre", "maud", "rand 0.8.5", + "reqwest 0.12.9", "serde", "serde_json", "serenity", @@ -2835,6 +3052,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -2843,7 +3069,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "system-configuration-sys 0.6.0", ] [[package]] @@ -2856,6 +3093,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.12.0" @@ -2983,7 +3230,9 @@ dependencies = [ "bytes 1.7.1", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2 0.5.7", "tokio-macros", "windows-sys 0.52.0", @@ -3000,6 +3249,16 @@ dependencies = [ "syn 2.0.77", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.23.4" @@ -3021,6 +3280,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.16", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.16" @@ -3108,7 +3378,7 @@ dependencies = [ "base64 0.13.1", "byteorder", "bytes 1.7.1", - "http", + "http 0.2.12", "httparse", "log", "rand 0.8.5", @@ -3433,6 +3703,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 2128303..c94d0a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,8 @@ name = "update_minecraft" [dependencies] # discord library serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } -tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } +tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "full"] } +reqwest = { version = "0.12", features = ["json"] } # to make the http requests surf = "2.3.2" diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 25e0262..daa847c 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -19,6 +19,7 @@ use sqlx::{Pool, Sqlite}; pub mod link { use super::*; + use serde::{Deserialize, Serialize}; pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { let db_lock = { @@ -61,7 +62,32 @@ pub mod link { // check if email exists let details = match get_server_member_email(&db, email).await { None => { - return "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string() + let invalid_user = "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string(); + + // see if the user actually exists + let id = match get_user(&config, email).await { + None => { + return invalid_user; + } + Some(x) => x, + }; + + // save teh user id and email to teh db + match save_to_db_user(&db, id, email).await { + Ok(x) => x, + Err(x) => { + dbg!(x); + return "Error: unable to save user to teh database, contact Computer Society".to_string(); + } + }; + + // pull it back out (technically could do it in previous step but more explicit) + match get_server_member_email(&db, email).await { + None => { + return "Error: failed to read user from database.".to_string(); + } + Some(x) => x, + } } Some(x) => x, }; @@ -234,6 +260,59 @@ pub mod link { .fetch_optional(db) .await } + + #[derive(Serialize, Deserialize, Debug)] + #[serde(untagged)] + pub enum WolvesResultUserResult { + B(bool), + S(String), + } + + #[derive(Deserialize, Serialize, Debug)] + struct WolvesResultUser { + success: i64, + result: WolvesResultUserResult, + } + + async fn get_user(config: &Config, email: &str) -> Option { + let url = format!("{}/get_id_from_email", &config.wolves_url); + match reqwest::Client::new() + .post(&url) + .form(&[("email", email)]) + .header("X-AM-Identity", &config.wolves_api) + .send() + .await + { + Ok(x) => { + if let Ok(y) = x.json::().await { + // this is the only time we will get a positive response, the None at the end catches everything else + if let WolvesResultUserResult::S(z) = y.result { + if let Ok(id) = z.parse::() { + return Some(id); + } + } + } + } + Err(e) => { + dbg!(e); + } + } + + None + } + + async fn save_to_db_user(db: &Pool, id_wolves: i64, email: &str) -> Result, sqlx::Error> { + sqlx::query_as::<_, Wolves>( + " + INSERT INTO wolves (id_wolves, email) + VALUES (?1, ?2) + ", + ) + .bind(id_wolves) + .bind(email) + .fetch_optional(db) + .await + } } pub mod verify { From 015f23b9223014c942f3a951c1e9dca037932a2b Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 9 Nov 2024 12:51:41 +0000 Subject: [PATCH 032/170] feat: no longer using teh "hardcoded" api key --- src/common/wolves.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 7e29f1c..5a1e1b7 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -277,25 +277,24 @@ pub mod committees { }; let config = config_lock.read().await; - // TODO: proper api key management - let api_key = ""; // request data from wolves - for committee in get_committees(&config, api_key).await { - let tmp = Committees::from(committee); - add_committee(&db, &tmp).await; + for committee_wolves in get_committees(&config).await { + let committee = Committees::from(committee_wolves); + add_committee(&db, &committee).await; } } - async fn get_committees(config: &Config, wolves_api: &str) -> Vec { + async fn get_committees(config: &Config) -> Vec { if config.wolves_url.is_empty() { return vec![]; } // TODO: Change teh stored env value to teh base domain + // TODO: this address may change let url = format!("{}/get_cns", &config.wolves_url); // get wolves data - if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await { + if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", &config.wolves_api).await { if let Ok(WolvesResult { success, result, From d673dce6fa10e9d579bb18cec5fe571143b5523d Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 9 Nov 2024 12:53:53 +0000 Subject: [PATCH 033/170] fix: handle the just in case the user alrady exists as a different person --- src/commands/link_email.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index daa847c..ace4691 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -305,7 +305,8 @@ pub mod link { sqlx::query_as::<_, Wolves>( " INSERT INTO wolves (id_wolves, email) - VALUES (?1, ?2) + VALUES ($1, $2) + ON CONFLICT(id_wolves) DO UPDATE SET email = $2 ", ) .bind(id_wolves) From 6739c7e06860a0f50781a07563867dcceca0a0f6 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 9 Nov 2024 14:55:26 +0000 Subject: [PATCH 034/170] feat: now use env vars to get teh server and roles for committee --- src/common/set_roles.rs | 23 +++++++++++++++++------ src/lib.rs | 18 ++++++++++++++++++ src/main.rs | 11 ++++++++--- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index c3c80cb..a3cd2ba 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -133,9 +133,9 @@ pub mod normal { pub mod committee { use crate::common::database::{DataBase, Wolves}; use crate::common::wolves::committees::Committees; + use crate::Config; use serenity::client::Context; use serenity::model::guild::Member; - use serenity::model::id::{GuildId, RoleId}; use sqlx::{Pool, Sqlite}; use std::collections::HashMap; use std::sync::Arc; @@ -148,15 +148,26 @@ pub mod committee { let db = db_lock.read().await; - let server = GuildId(1220150752656363520); + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config_global = config_lock.read().await; + + let server = config_global.committee_server; + + // because to use it to update a single user we need to pre-get the members of teh server let mut members = server.members(&ctx, None, None).await.unwrap_or_default(); - update_committees(&db, &ctx, &mut members).await; + update_committees(&db, &ctx, &config_global, &mut members).await; } - pub async fn update_committees(db: &Pool, ctx: &Context, members: &mut Vec) { - let server = GuildId(1220150752656363520); - let committee_member = RoleId(1226602779968274573); + /** + This function can take a vec of members (or just one) and gives tehm the appropiate roles on teh committee server + */ + pub async fn update_committees(db: &Pool, ctx: &Context, config: &Config, members: &mut Vec) { + let server = config.committee_server; + let committee_member = config.committee_role; let committees = get_committees(db).await; // information about the server diff --git a/src/lib.rs b/src/lib.rs index a3b4edf..5a9d43d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,7 @@ use chrono::{Datelike, SecondsFormat, Utc}; use dotenvy::dotenv; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use serenity::client::Context; +use serenity::model::id::{GuildId, RoleId}; use serenity::model::prelude::application_command::ApplicationCommandInteraction; use serenity::prelude::TypeMapKey; use std::{env, sync::Arc}; @@ -26,6 +27,10 @@ pub struct Config { pub wolves_url: String, // API key for accessing more general resources pub wolves_api: String, + + // discord server for committee + pub committee_server: GuildId, + pub committee_role: RoleId, } impl TypeMapKey for Config { type Value = Arc>; @@ -47,6 +52,8 @@ pub fn get_config() -> Config { mail_pass: "".to_string(), wolves_url: "".to_string(), wolves_api: "".to_string(), + committee_server: GuildId(0), + committee_role: RoleId(0), }; if let Ok(x) = env::var("DATABASE_HOME") { @@ -81,6 +88,17 @@ pub fn get_config() -> Config { config.wolves_api = x.trim().to_string(); } + if let Ok(x) = env::var("COMMITTEE_DISCORD") { + if let Ok(x) = x.trim().parse::() { + config.committee_server = GuildId(x); + } + } + if let Ok(x) = env::var("COMMITTEE_DISCORD") { + if let Ok(x) = x.trim().parse::() { + config.committee_role = RoleId(x); + } + } + config } diff --git a/src/main.rs b/src/main.rs index 17d6615..be4eebb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,6 @@ pub mod commands; use crate::commands::role_adder::tools::on_role_change; use serenity::model::guild::Member; -use serenity::model::id::GuildId; use serenity::{ async_trait, client::{Context, EventHandler}, @@ -37,10 +36,16 @@ impl EventHandler for Handler { Some(x) => x, }; + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config_global = config_lock.read().await; + // committee server takes priority - if new_member.guild_id.eq(&GuildId(1220150752656363520)) { + if new_member.guild_id.eq(&config_global.committee_server) { let mut member = vec![new_member.clone()]; - update_committees(&db, &ctx, &mut member).await; + update_committees(&db, &ctx, &config_global, &mut member).await; return; } From 5b22f699d687fd41880c91a10938123fafccf85a Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 9 Nov 2024 14:59:05 +0000 Subject: [PATCH 035/170] fix: getting teh server config needs to happen after checking for committee --- src/main.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/main.rs b/src/main.rs index be4eebb..62c0350 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,10 +31,6 @@ impl EventHandler for Handler { }; let db = db_lock.read().await; - let config = match get_server_config(&db, &new_member.guild_id).await { - None => return, - Some(x) => x, - }; let config_lock = { let data_read = ctx.data.read().await; @@ -49,17 +45,22 @@ impl EventHandler for Handler { return; } + let config_server = match get_server_config(&db, &new_member.guild_id).await { + None => return, + Some(x) => x, + }; + if get_server_member(&db, &new_member.guild_id, &new_member).await.is_ok() { let mut roles = vec![]; - if let Some(role) = &config.role_past { + if let Some(role) = &config_server.role_past { if !new_member.roles.contains(role) { roles.push(role.to_owned()); } } - if !new_member.roles.contains(&config.role_current) { - roles.push(config.role_current.to_owned()); + if !new_member.roles.contains(&config_server.role_current) { + roles.push(config_server.role_current.to_owned()); } if let Err(e) = new_member.add_roles(&ctx, &roles).await { @@ -72,10 +73,10 @@ Welcome {} to the {} server! Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use ``/link_wolves`` to get full access. "#, new_member.display_name(), - &config.server_name, - &config.wolves_link, - &config.server, - &config.bot_channel_id + &config_server.server_name, + &config_server.wolves_link, + &config_server.server, + &config_server.bot_channel_id ); if let Err(err) = new_member.user.direct_message(&ctx, |m| m.content(&msg)).await { From e4a8cce725266ede78c523584c9628a567032d44 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 9 Nov 2024 16:17:43 +0000 Subject: [PATCH 036/170] feat: new env var for teh specific channel that the general chat stuff will be under --- src/lib.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 5a9d43d..144b5e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,7 +4,7 @@ use chrono::{Datelike, SecondsFormat, Utc}; use dotenvy::dotenv; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use serenity::client::Context; -use serenity::model::id::{GuildId, RoleId}; +use serenity::model::id::{ChannelId, GuildId, RoleId}; use serenity::model::prelude::application_command::ApplicationCommandInteraction; use serenity::prelude::TypeMapKey; use std::{env, sync::Arc}; @@ -31,6 +31,7 @@ pub struct Config { // discord server for committee pub committee_server: GuildId, pub committee_role: RoleId, + pub committee_category: ChannelId, } impl TypeMapKey for Config { type Value = Arc>; @@ -54,6 +55,7 @@ pub fn get_config() -> Config { wolves_api: "".to_string(), committee_server: GuildId(0), committee_role: RoleId(0), + committee_category: ChannelId(0), }; if let Ok(x) = env::var("DATABASE_HOME") { @@ -98,6 +100,11 @@ pub fn get_config() -> Config { config.committee_role = RoleId(x); } } + if let Ok(x) = env::var("COMMITTEE_CATEGORY") { + if let Ok(x) = x.trim().parse::() { + config.committee_category = ChannelId(x); + } + } config } From c98baa9d729b2090572f9031f878ac5ffb082bc4 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 9 Nov 2024 16:23:03 +0000 Subject: [PATCH 037/170] feat: will now create a channel for any new club/soc --- src/common/set_roles.rs | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index a3cd2ba..ced3ad5 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -135,6 +135,7 @@ pub mod committee { use crate::common::wolves::committees::Committees; use crate::Config; use serenity::client::Context; + use serenity::model::channel::ChannelType; use serenity::model::guild::Member; use sqlx::{Pool, Sqlite}; use std::collections::HashMap; @@ -172,6 +173,7 @@ pub mod committee { // information about the server let roles = server.roles(&ctx).await.unwrap_or_default(); + let channels = server.channels(&ctx).await.unwrap_or_default(); // make a hashmap of the nameof roles to quickly get them out again let mut roles_name = HashMap::new(); @@ -179,6 +181,11 @@ pub mod committee { roles_name.insert(role.name.to_owned(), role.to_owned()); } + let mut channels_name = HashMap::new(); + for channel in channels.values() { + channels_name.insert(channel.name.to_owned(), channel.to_owned()); + } + // a map of users and the roles they are goign to be getting let mut users_roles = HashMap::new(); @@ -198,6 +205,24 @@ pub mod committee { } }; + // create teh channel if it does nto exist + if !channels_name.contains_key(&committee.name) { + match server + .create_channel(&ctx, |c| c.name(&committee.name).kind(ChannelType::Text).category(config.committee_category)) + .await + { + Ok(x) => { + // update teh channels name list + channels_name.insert(x.name.to_owned(), x.to_owned()); + + println!("Created channel: {}", &committee.name); + } + Err(x) => { + dbg!("Unable to create channel: ", x); + } + } + }; + // so if the role exists if let Some(r) = role { committee_roles.push(r.id); From 2f75dc41c87d12bb04cde777b533bd6887302f5d Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 9 Nov 2024 16:47:48 +0000 Subject: [PATCH 038/170] feat: will properly re-order the channels created Also focuses on anything in teh right category --- src/common/set_roles.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index ced3ad5..9b26117 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -183,7 +183,12 @@ pub mod committee { let mut channels_name = HashMap::new(); for channel in channels.values() { - channels_name.insert(channel.name.to_owned(), channel.to_owned()); + // we only care about teh channels in teh category + if let Some(x) = channel.parent_id { + if x.eq(&config.committee_category) { + channels_name.insert(channel.name.to_owned(), channel.to_owned()); + } + } } // a map of users and the roles they are goign to be getting @@ -269,6 +274,32 @@ pub mod committee { member.add_roles(&ctx, &roles_required).await.unwrap_or_default(); } } + + // finally re-order teh channels to make them visually apealing + let mut channel_names = channels_name.clone().into_keys().collect::>(); + channel_names.sort(); + + // get a list of all teh new positions + let mut new_positions = vec![]; + for (i, name) in channel_names.iter().enumerate() { + if let Some(channel) = channels_name.get_mut(name) { + let position_new = i as u64; + if position_new != channel.position as u64 { + new_positions.push((channel.id.to_owned(), position_new)); + } + } + } + + if !new_positions.is_empty() { + match server.reorder_channels(&ctx, new_positions).await { + Ok(_) => { + println!("Successfully re-orderd the committee category"); + } + Err(e) => { + dbg!("Failed to re-order ", e); + } + } + } } async fn get_committees(db: &Pool) -> Vec { From 77a7b7b81d3b74c20fe214d8f1fa4b51ae0a08b5 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 9 Nov 2024 16:53:26 +0000 Subject: [PATCH 039/170] fix: slight change in env var used to get teh base URL --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 144b5e1..09f60bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -82,7 +82,7 @@ pub fn get_config() -> Config { config.mail_pass = x.trim().to_string(); } - if let Ok(x) = env::var("WOLVES_URL") { + if let Ok(x) = env::var("WOLVES_URL_BASE") { config.wolves_url = x.trim().to_string(); } From 94292fa388d9cb484210d0a668a27d0956898e29 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 18 Nov 2024 16:09:43 +0000 Subject: [PATCH 040/170] fix: style was causing issues --- src/commands/link_email.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 4961dbb..1b462f6 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -133,7 +133,7 @@ pub mod link { "h2, h4 { font-family: Arial, Helvetica, sans-serif; }" } } - div style="display: flex; flex-direction: column; align-items: center;" { + div { h2 { "Hello from Skynet!" } // Substitute in the name of our recipient. p { "Hi " (user) "," } From 96a61e6fc8fdb9b4b59399439b50e63830cb8dc6 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 23 Nov 2024 00:20:24 +0000 Subject: [PATCH 041/170] git: finally added a gitattributes --- .gitattributes | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..6c7adef --- /dev/null +++ b/.gitattributes @@ -0,0 +1,37 @@ +# Git config here +* text eol=lf + +############################################# +# Git lfs stuff +# Documents +*.pdf filter=lfs diff=lfs merge=lfs -text +*.doc filter=lfs diff=lfs merge=lfs -text +*.docx filter=lfs diff=lfs merge=lfs -text +# Excel +*.xls filter=lfs diff=lfs merge=lfs -text +*.xlsx filter=lfs diff=lfs merge=lfs -text +*.xlsm filter=lfs diff=lfs merge=lfs -text +# Powerpoints +*.ppt filter=lfs diff=lfs merge=lfs -text +*.pptx filter=lfs diff=lfs merge=lfs -text +*.ppsx filter=lfs diff=lfs merge=lfs -text +# Images +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +# Video +*.mkv filter=lfs diff=lfs merge=lfs -text +*.mp4 filter=lfs diff=lfs merge=lfs -text +*.wmv filter=lfs diff=lfs merge=lfs -text +# Misc +*.zip filter=lfs diff=lfs merge=lfs -text +# ET4011 +*.cbe filter=lfs diff=lfs merge=lfs -text +*.pbs filter=lfs diff=lfs merge=lfs -text +# Open/Libre office +# from https://www.libreoffice.org/discover/what-is-opendocument/ +*.odt filter=lfs diff=lfs merge=lfs -text +*.ods filter=lfs diff=lfs merge=lfs -text +*.odp filter=lfs diff=lfs merge=lfs -text +*.odg filter=lfs diff=lfs merge=lfs -text +# QT +*.ui filter=lfs diff=lfs merge=lfs -text From 5dee9acbaadc7cb499df5c018cd7d531e26ff2f1 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 18 Nov 2024 16:14:16 +0000 Subject: [PATCH 042/170] doc: added images to suer signup --- README.md | 7 +++++-- media/setup_user_01.png | 3 +++ media/setup_user_02.png | 3 +++ media/setup_user_03.png | 3 +++ 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 media/setup_user_01.png create mode 100644 media/setup_user_02.png create mode 100644 media/setup_user_03.png diff --git a/README.md b/README.md index 7c75f3f..b9aa348 100644 --- a/README.md +++ b/README.md @@ -79,11 +79,14 @@ This is to link your Discord account with your UL Wolves account. **You will only need to do this once**. ### Setup -1. in a server with the Skynet Bot enter ``/link_wolves WOLVES_EMAIL`` +1. In a Discord server with the Skynet Bot enter ``/link_wolves WOLVES_EMAIL`` + ![link start](media/setup_user_01.png) * Your ``WOLVES_EMAIL`` is the email in the Contact Email here: * This is most likely your student mail 2. An email will be sent to you with a verification code. -3. Verify the code using ``/verify CODE`` + ![signup email](media/setup_user_02.png) +3. Verify the code using ``/verify CODE`` in Discord + ![verify in discord](media/setup_user_03.png) 4. Once complete your Wolves and Discord accounts will be linked. You will get member roles on any Discord that is using the bot that you are a member of. diff --git a/media/setup_user_01.png b/media/setup_user_01.png new file mode 100644 index 0000000..b5e207b --- /dev/null +++ b/media/setup_user_01.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f6440b2d302c5a7e46493687bb0bbf941c29c5552c71869303397da3c4f0a52b +size 72163 diff --git a/media/setup_user_02.png b/media/setup_user_02.png new file mode 100644 index 0000000..34898b9 --- /dev/null +++ b/media/setup_user_02.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e64d8e2322990dcc6446a34e988c6754edc536c3ee1aa8c330e7dadb899152bc +size 72884 diff --git a/media/setup_user_03.png b/media/setup_user_03.png new file mode 100644 index 0000000..fda3bda --- /dev/null +++ b/media/setup_user_03.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f19d24686bb9da8857263d1f146e29413fd6bd316981281c18ef2b4ec3621596 +size 51740 From dda05d7ca15fc34417093dad36ac45be25fdb40a Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 18 Nov 2024 16:22:40 +0000 Subject: [PATCH 043/170] doc: resized images using html tags --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b9aa348..3fef46a 100644 --- a/README.md +++ b/README.md @@ -79,14 +79,14 @@ This is to link your Discord account with your UL Wolves account. **You will only need to do this once**. ### Setup -1. In a Discord server with the Skynet Bot enter ``/link_wolves WOLVES_EMAIL`` - ![link start](media/setup_user_01.png) +1. In a Discord server with the Skynet Bot enter ``/link_wolves WOLVES_EMAIL`` + link process start * Your ``WOLVES_EMAIL`` is the email in the Contact Email here: * This is most likely your student mail -2. An email will be sent to you with a verification code. - ![signup email](media/setup_user_02.png) -3. Verify the code using ``/verify CODE`` in Discord - ![verify in discord](media/setup_user_03.png) +2. An email will be sent to you with a verification code. + signup email +3. Verify the code using ``/verify CODE`` in Discord. + verify in discord 4. Once complete your Wolves and Discord accounts will be linked. You will get member roles on any Discord that is using the bot that you are a member of. From 93359698f0431a50b9a747adf4bd66b23c02c808 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 18 Nov 2024 16:38:33 +0000 Subject: [PATCH 044/170] doc: feedback --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3fef46a..5a0630b 100644 --- a/README.md +++ b/README.md @@ -79,13 +79,13 @@ This is to link your Discord account with your UL Wolves account. **You will only need to do this once**. ### Setup -1. In a Discord server with the Skynet Bot enter ``/link_wolves WOLVES_EMAIL`` +1. In a Discord server with the Skynet Bot enter ``/link_wolves YOUR_WOLVES_CONTACT_EMAIL`` link process start - * Your ``WOLVES_EMAIL`` is the email in the Contact Email here: + * Your ``YOUR_WOLVES_CONTACT_EMAIL`` is the email in the Contact Email here: * This is most likely your student mail 2. An email will be sent to you with a verification code. signup email -3. Verify the code using ``/verify CODE`` in Discord. +3. Verify the code using ``/verify CODE_FROM_EMAIL`` in Discord. verify in discord 4. Once complete your Wolves and Discord accounts will be linked. From ca55a78447089a2b04446d917777e25f1fd5f7f3 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 23 Nov 2024 21:53:30 +0000 Subject: [PATCH 045/170] feat: switched over to using a library to interact with teh wolves API --- Cargo.lock | 86 +++++++++++---- Cargo.toml | 5 +- src/commands/link_email.rs | 31 +----- src/common/wolves.rs | 218 ++----------------------------------- 4 files changed, 85 insertions(+), 255 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cc8ef0d..af719e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,6 +218,28 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "async-task" version = "4.7.1" @@ -232,7 +254,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -920,7 +942,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1803,7 +1825,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1900,7 +1922,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2026,9 +2048,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -2446,9 +2468,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -2465,13 +2487,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2634,13 +2656,13 @@ dependencies = [ "lettre", "maud", "rand 0.8.5", - "reqwest 0.12.9", "serde", "serde_json", "serenity", "sqlx", "surf", "tokio", + "wolves_oxidised", ] [[package]] @@ -3037,9 +3059,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -3133,7 +3155,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -3246,7 +3268,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -3302,6 +3324,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-test" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +dependencies = [ + "async-stream", + "bytes 1.7.1", + "futures-core", + "tokio", + "tokio-stream", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -3341,7 +3376,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -3569,7 +3604,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -3603,7 +3638,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3891,6 +3926,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wolves_oxidised" +version = "0.1.0" +source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#eee6a76c695a36f1fe220fdeafd5a43757e50527" +dependencies = [ + "reqwest 0.12.9", + "serde", + "serde_json", + "tokio-test", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -3909,7 +3955,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c94d0a0..92fa6c5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,10 @@ name = "update_minecraft" # discord library serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "full"] } -reqwest = { version = "0.12", features = ["json"] } + +# wolves api +# TODO: move off of unstable +wolves_oxidised = { git = "https://forgejo.skynet.ie/Skynet/wolves-oxidised.git", features = ["unstable"]} # to make the http requests surf = "2.3.2" diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index ace4691..9d6328f 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -64,8 +64,10 @@ pub mod link { None => { let invalid_user = "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string(); + let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); + // see if the user actually exists - let id = match get_user(&config, email).await { + let id = match wolves.get_member(email).await { None => { return invalid_user; } @@ -274,33 +276,6 @@ pub mod link { result: WolvesResultUserResult, } - async fn get_user(config: &Config, email: &str) -> Option { - let url = format!("{}/get_id_from_email", &config.wolves_url); - match reqwest::Client::new() - .post(&url) - .form(&[("email", email)]) - .header("X-AM-Identity", &config.wolves_api) - .send() - .await - { - Ok(x) => { - if let Ok(y) = x.json::().await { - // this is the only time we will get a positive response, the None at the end catches everything else - if let WolvesResultUserResult::S(z) = y.result { - if let Ok(id) = z.parse::() { - return Some(id); - } - } - } - } - Err(e) => { - dbg!(e); - } - } - - None - } - async fn save_to_db_user(db: &Pool, id_wolves: i64, email: &str) -> Result, sqlx::Error> { sqlx::query_as::<_, Wolves>( " diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 5a1e1b7..0f975e3 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -52,32 +52,13 @@ pub mod cns { use crate::common::set_roles::normal::update_server; use crate::common::wolves::{add_users_wolves, WolvesResultUserMin}; use crate::Config; - use serde::{Deserialize, Serialize}; use serenity::client::Context; use serenity::model::id::GuildId; use sqlx::{Pool, Sqlite}; use std::collections::BTreeMap; - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResultUser { - // TODO: Might be worth trying to get this replaced with the club/soc ID? - committee: String, - member_id: String, - first_name: String, - last_name: String, - contact_email: String, - opt_in_email: String, - student_id: Option, - note: Option, - expiry: String, - requested: String, - approved: String, - sitename: String, - domain: String, - } - - impl From<&WolvesResultUser> for WolvesResultUserMin { - fn from(value: &WolvesResultUser) -> Self { + impl From<&wolves_oxidised::WolvesUser> for WolvesResultUserMin { + fn from(value: &wolves_oxidised::WolvesUser) -> Self { Self { member_id: value.member_id.to_owned(), contact_email: value.contact_email.to_owned(), @@ -85,18 +66,6 @@ pub mod cns { } } - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResult { - success: i8, - result: Vec, - } - - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResultLocal { - pub id_wolves: String, - pub email: String, - pub expiry: String, - } pub async fn get_wolves(ctx: &Context) { let db_lock = { let data_read = ctx.data.read().await; @@ -110,9 +79,13 @@ pub mod cns { }; let config = config_lock.read().await; + // set up teh client + let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); + for server_config in get_server_config_bulk(&db).await { let Servers { server, + // this is the unique api key for each club/soc wolves_api, .. } = &server_config; @@ -122,7 +95,7 @@ pub mod cns { // list of users that need to be updated for this server let mut user_to_update = vec![]; - for user in get_wolves_sub(&config, wolves_api).await { + for user in wolves.get_members(wolves_api).await { let id = user.member_id.parse::().unwrap_or_default(); match existing.get(&(id as i64)) { None => { @@ -168,32 +141,7 @@ pub mod cns { .unwrap_or_default() } - async fn get_wolves_sub(config: &Config, wolves_api: &str) -> Vec { - if config.wolves_url.is_empty() { - return vec![]; - } - - let url = format!("{}/get_members", &config.wolves_url); - - // get wolves data - if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await { - if let Ok(WolvesResult { - success, - result, - }) = res.body_json().await - { - if success != 1 { - return vec![]; - } - - return result; - } - } - - vec![] - } - - async fn add_users_server_members(db: &Pool, server: &GuildId, user: &WolvesResultUser) { + async fn add_users_server_members(db: &Pool, server: &GuildId, user: &wolves_oxidised::WolvesUser) { match sqlx::query_as::<_, ServerMembers>( " INSERT OR REPLACE INTO server_members (server, id_wolves, expiry) @@ -221,28 +169,9 @@ pub mod cns { pub mod committees { use crate::common::database::DataBase; use crate::Config; - use serde::{Deserialize, Serialize}; use serenity::client::Context; use sqlx::{Pool, Sqlite}; - // This is what Wolves returns to us - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResult { - success: i8, - result: Vec, - } - - // this is teh actual data we care about - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResultCNS { - id: String, - name: String, - // Link to their page such as https://ulwolves.ie/society/computer - link: String, - // array of Committee members member_id's - committee: Vec, - } - // Database entry for it #[derive(Debug, Clone, sqlx::FromRow)] pub struct Committees { @@ -253,8 +182,8 @@ pub mod committees { pub committee: Vec, } - impl From for Committees { - fn from(value: WolvesResultCNS) -> Self { + impl From for Committees { + fn from(value: wolves_oxidised::WolvesCNS) -> Self { Self { id: value.id.parse().unwrap_or(0), name: value.name, @@ -277,40 +206,14 @@ pub mod committees { }; let config = config_lock.read().await; + let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); // request data from wolves - for committee_wolves in get_committees(&config).await { + for committee_wolves in wolves.get_committees().await { let committee = Committees::from(committee_wolves); add_committee(&db, &committee).await; } } - async fn get_committees(config: &Config) -> Vec { - if config.wolves_url.is_empty() { - return vec![]; - } - - // TODO: Change teh stored env value to teh base domain - // TODO: this address may change - let url = format!("{}/get_cns", &config.wolves_url); - - // get wolves data - if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", &config.wolves_api).await { - if let Ok(WolvesResult { - success, - result, - }) = res.body_json().await - { - if success != 1 { - return vec![]; - } - - return result; - } - } - - vec![] - } - async fn add_committee(db: &Pool, committee: &Committees) { match sqlx::query_as::<_, Committees>( " @@ -334,100 +237,3 @@ pub mod committees { } } } - -/** -get the data for an individual user -*/ -pub mod individual { - use crate::common::database::DataBase; - use crate::common::wolves::{add_users_wolves, WolvesResultUserMin}; - use crate::Config; - use serde::{Deserialize, Serialize}; - use serenity::client::Context; - - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResultUser { - // committee: String, - member_id: String, - // first_name: String, - // last_name: String, - contact_email: String, - // opt_in_email: String, - // student_id: Option, - // note: Option, - // expiry: String, - // requested: String, - // approved: String, - // sitename: String, - // domain: String, - } - - impl From<&WolvesResultUser> for WolvesResultUserMin { - fn from(value: &WolvesResultUser) -> Self { - Self { - member_id: value.member_id.to_owned(), - contact_email: value.contact_email.to_owned(), - } - } - } - - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResult { - success: i8, - result: WolvesResultUser, - } - - pub async fn get_user(ctx: &Context, email: &str) -> bool { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Database in TypeMap.").clone() - }; - let db = db_lock.read().await; - - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config = config_lock.read().await; - - // TODO: proper api key management - let api_key = ""; - // request data from wolves - match get_user_sub(&config, api_key, email).await { - None => false, - // if exists save it and return true - Some(user) => { - // add to db - add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await; - - true - } - } - } - - async fn get_user_sub(config: &Config, wolves_api: &str, _email: &str) -> Option { - if config.wolves_url.is_empty() { - return None; - } - - // TODO: Change teh stored env value to teh base domain - let url = format!("{}/get_member", &config.wolves_url); - - // get wolves data - if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await { - if let Ok(WolvesResult { - success, - result, - }) = res.body_json().await - { - if success != 1 { - return None; - } - - return Some(result); - } - } - - None - } -} From 37ea38f516e4f86c43b685fd365e078336c6e858 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 23 Nov 2024 22:17:57 +0000 Subject: [PATCH 046/170] feat: backport changes from the #17-automate-onboarding-mk-ii branch --- Cargo.lock | 413 +++++++++++++++++++++++++++++++++---- Cargo.toml | 5 +- src/commands/link_email.rs | 43 +++- src/lib.rs | 68 ++---- 4 files changed, 433 insertions(+), 96 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d8c9113..0f043b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,6 +218,28 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "async-task" version = "4.7.1" @@ -232,7 +254,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -920,7 +942,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1024,7 +1046,26 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccae279728d634d083c00f6099cb58f01cc99c145b84b8be2f6c74618d79922e" +dependencies = [ + "atomic-waker", + "bytes 1.7.1", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", "indexmap", "slab", "tokio", @@ -1147,6 +1188,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes 1.7.1", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -1154,7 +1206,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes 1.7.1", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes 1.7.1", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes 1.7.1", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", "pin-project-lite", ] @@ -1216,9 +1291,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -1230,6 +1305,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97818827ef4f364230e16705d4706e2897df2bb60617d6ca15d598025a3c481f" +dependencies = [ + "bytes 1.7.1", + "futures-channel", + "futures-util", + "h2 0.4.7", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -1237,13 +1332,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", - "hyper", + "http 0.2.12", + "hyper 0.14.30", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", ] +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.5.1", + "hyper-util", + "rustls 0.23.18", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes 1.7.1", + "http-body-util", + "hyper 1.5.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +dependencies = [ + "bytes 1.7.1", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.5.1", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -1330,7 +1477,7 @@ dependencies = [ "curl-sys", "flume 0.9.2", "futures-lite 1.13.0", - "http", + "http 0.2.12", "log", "once_cell", "slab", @@ -1678,7 +1825,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1775,7 +1922,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -1901,9 +2048,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -2014,11 +2161,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "hyper-rustls 0.24.2", "ipnet", "js-sys", "log", @@ -2028,12 +2175,12 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.21.12", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", - "system-configuration", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", "tokio", "tokio-rustls 0.24.1", "tokio-util", @@ -2047,6 +2194,49 @@ dependencies = [ "winreg", ] +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64 0.22.1", + "bytes 1.7.1", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.7", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.1", + "hyper-rustls 0.27.3", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.2.0", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.2", + "system-configuration 0.6.1", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + [[package]] name = "ring" version = "0.16.20" @@ -2145,10 +2335,23 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9cc1d47e243d655ace55ed38201c19ae02c148ae56412ab8750e8f0166ab7f" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -2158,6 +2361,21 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -2168,6 +2386,17 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "ryu" version = "1.0.18" @@ -2239,9 +2468,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] @@ -2258,13 +2487,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2321,7 +2550,7 @@ dependencies = [ "mime_guess", "parking_lot", "percent-encoding", - "reqwest", + "reqwest 0.11.27", "serde", "serde-value", "serde_json", @@ -2423,6 +2652,7 @@ dependencies = [ "sqlx", "surf", "tokio", + "wolves_oxidised", ] [[package]] @@ -2819,9 +3049,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.77" +version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ "proc-macro2", "quote", @@ -2834,6 +3064,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -2842,7 +3081,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "system-configuration-sys 0.6.0", ] [[package]] @@ -2855,6 +3105,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.12.0" @@ -2885,7 +3145,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -2996,7 +3256,17 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", ] [[package]] @@ -3020,6 +3290,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.18", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.16" @@ -3031,6 +3312,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-test" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +dependencies = [ + "async-stream", + "bytes 1.7.1", + "futures-core", + "tokio", + "tokio-stream", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -3070,7 +3364,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] @@ -3107,7 +3401,7 @@ dependencies = [ "base64 0.13.1", "byteorder", "bytes 1.7.1", - "http", + "http 0.2.12", "httparse", "log", "rand 0.8.5", @@ -3298,7 +3592,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-shared", ] @@ -3332,7 +3626,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3432,6 +3726,36 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -3590,6 +3914,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wolves_oxidised" +version = "0.1.0" +source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#eee6a76c695a36f1fe220fdeafd5a43757e50527" +dependencies = [ + "reqwest 0.12.9", + "serde", + "serde_json", + "tokio-test", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -3608,7 +3943,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.77", + "syn 2.0.89", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a26023a..2dc64c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,9 @@ name = "update_minecraft" serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } +# wolves api +wolves_oxidised = { git = "https://forgejo.skynet.ie/Skynet/wolves-oxidised.git" } + # to make the http requests surf = "2.3.2" @@ -37,4 +40,4 @@ chrono = "0.4.26" lettre = "0.10.4" maud = "0.25.0" -serde = "1.0.188" \ No newline at end of file +serde = "1.0" \ No newline at end of file diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 1b462f6..a2a6f5d 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -59,7 +59,34 @@ pub mod link { // check if email exists let details = match get_server_member_email(&db, email).await { None => { - return "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string() + let invalid_user = "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string(); + + let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); + + // see if the user actually exists + let id = match wolves.get_member(email).await { + None => { + return invalid_user; + } + Some(x) => x, + }; + + // save teh user id and email to teh db + match save_to_db_user(&db, id, email).await { + Ok(x) => x, + Err(x) => { + dbg!(x); + return "Error: unable to save user to teh database, contact Computer Society".to_string(); + } + }; + + // pull it back out (technically could do it in previous step but more explicit) + match get_server_member_email(&db, email).await { + None => { + return "Error: failed to read user from database.".to_string(); + } + Some(x) => x, + } } Some(x) => x, }; @@ -232,6 +259,20 @@ pub mod link { .fetch_optional(db) .await } + + async fn save_to_db_user(db: &Pool, id_wolves: i64, email: &str) -> Result, sqlx::Error> { + sqlx::query_as::<_, Wolves>( + " + INSERT INTO wolves (id_wolves, email) + VALUES ($1, $2) + ON CONFLICT(id_wolves) DO UPDATE SET email = $2 + ", + ) + .bind(id_wolves) + .bind(email) + .fetch_optional(db) + .await + } } pub mod verify { diff --git a/src/lib.rs b/src/lib.rs index 5923151..05f6125 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -38,6 +38,8 @@ pub struct Config { // wolves API base for clubs/socs pub wolves_url: String, + // API key for accessing more general resources + pub wolves_api: String, } impl TypeMapKey for Config { type Value = Arc>; @@ -63,6 +65,7 @@ pub fn get_config() -> Config { mail_user: "".to_string(), mail_pass: "".to_string(), wolves_url: "".to_string(), + wolves_api: "".to_string(), }; if let Ok(x) = env::var("DATABASE_HOME") { @@ -92,6 +95,9 @@ pub fn get_config() -> Config { if let Ok(x) = env::var("WOLVES_URL") { config.wolves_url = x.trim().to_string(); } + if let Ok(x) = env::var("WOLVES_API") { + config.wolves_api = x.trim().to_string(); + } config } @@ -526,36 +532,8 @@ pub mod get_data { use super::*; use crate::set_roles::update_server; use std::collections::BTreeMap; + use wolves_oxidised::WolvesUser; - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResultUser { - committee: String, - member_id: String, - first_name: String, - last_name: String, - contact_email: String, - opt_in_email: String, - student_id: Option, - note: Option, - expiry: String, - requested: String, - approved: String, - sitename: String, - domain: String, - } - - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResult { - success: i8, - result: Vec, - } - - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResultLocal { - pub id_wolves: String, - pub email: String, - pub expiry: String, - } pub async fn get_wolves(ctx: &Context) { let db_lock = { let data_read = ctx.data.read().await; @@ -569,6 +547,9 @@ pub mod get_data { }; let config = config_lock.read().await; + // set up teh client + let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); + for server_config in get_server_config_bulk(&db).await { let Servers { server, @@ -581,7 +562,7 @@ pub mod get_data { // list of users that need to be updated for this server let mut user_to_update = vec![]; - for user in get_wolves_sub(&config, wolves_api).await { + for user in wolves.get_members(wolves_api).await { let id = user.member_id.parse::().unwrap_or_default(); match existing.get(&(id as i64)) { None => { @@ -627,30 +608,7 @@ pub mod get_data { .unwrap_or_default() } - async fn get_wolves_sub(config: &Config, wolves_api: &str) -> Vec { - if config.wolves_url.is_empty() { - return vec![]; - } - - // get wolves data - if let Ok(mut res) = surf::post(&config.wolves_url).header("X-AM-Identity", wolves_api).await { - if let Ok(WolvesResult { - success, - result, - }) = res.body_json().await - { - if success != 1 { - return vec![]; - } - - return result; - } - } - - vec![] - } - - async fn add_users_wolves(db: &Pool, user: &WolvesResultUser) { + async fn add_users_wolves(db: &Pool, user: &WolvesUser) { // expiry match sqlx::query_as::<_, Wolves>( " @@ -671,7 +629,7 @@ pub mod get_data { } } } - async fn add_users_server_members(db: &Pool, server: &GuildId, user: &WolvesResultUser) { + async fn add_users_server_members(db: &Pool, server: &GuildId, user: &WolvesUser) { match sqlx::query_as::<_, ServerMembers>( " INSERT OR REPLACE INTO server_members (server, id_wolves, expiry) From f00db7ef5d4105ecee51b6893a327c84623867bf Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 23 Nov 2024 22:24:29 +0000 Subject: [PATCH 047/170] ci: update teh actions to take into account git-lfs --- .forgejo/workflows/push.yaml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/push.yaml b/.forgejo/workflows/push.yaml index 28f49f6..e62cb54 100644 --- a/.forgejo/workflows/push.yaml +++ b/.forgejo/workflows/push.yaml @@ -19,6 +19,10 @@ jobs: steps: # get the repo first - uses: https://code.forgejo.org/actions/checkout@v4 + - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v6 + with: + repository: ${{ gitea.repository }} + ref_name: ${{ gitea.ref_name }} - run: nix build .#fmt --verbose # clippy is incredibly useful for making yer code better @@ -30,6 +34,10 @@ jobs: steps: # get the repo first - uses: https://code.forgejo.org/actions/checkout@v4 + - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v6 + with: + repository: ${{ gitea.repository }} + ref_name: ${{ gitea.ref_name }} - run: nix build .#clippy --verbose build: @@ -39,6 +47,10 @@ jobs: steps: # get the repo first - uses: https://code.forgejo.org/actions/checkout@v4 + - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v6 + with: + repository: ${{ gitea.repository }} + ref_name: ${{ gitea.ref_name }} - name: "Build it locally" run: nix build --verbose @@ -49,7 +61,7 @@ jobs: needs: [ build ] steps: - name: "Deploy to Skynet" - uses: https://forgejo.skynet.ie/Skynet/actions-deploy-to-skynet@v2 + uses: https://forgejo.skynet.ie/Skynet/actions/deploy@v3 with: input: 'skynet_discord_bot' token: ${{ secrets.API_TOKEN_FORGEJO }} \ No newline at end of file From bab6e4fdec14b58112b2328bcc9f0ef809022734 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 23 Nov 2024 22:27:20 +0000 Subject: [PATCH 048/170] ci: use the nix compatable version --- .forgejo/workflows/push.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.forgejo/workflows/push.yaml b/.forgejo/workflows/push.yaml index e62cb54..984c5d6 100644 --- a/.forgejo/workflows/push.yaml +++ b/.forgejo/workflows/push.yaml @@ -19,7 +19,7 @@ jobs: steps: # get the repo first - uses: https://code.forgejo.org/actions/checkout@v4 - - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v6 + - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v3 with: repository: ${{ gitea.repository }} ref_name: ${{ gitea.ref_name }} @@ -34,7 +34,7 @@ jobs: steps: # get the repo first - uses: https://code.forgejo.org/actions/checkout@v4 - - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v6 + - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v3 with: repository: ${{ gitea.repository }} ref_name: ${{ gitea.ref_name }} @@ -47,7 +47,7 @@ jobs: steps: # get the repo first - uses: https://code.forgejo.org/actions/checkout@v4 - - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v6 + - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs@v3 with: repository: ${{ gitea.repository }} ref_name: ${{ gitea.ref_name }} From 1f3c33458e680bfb5ade7e4f5760ffeaff964c77 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 23 Nov 2024 23:50:25 +0000 Subject: [PATCH 049/170] dbg: excessive logging --- Cargo.lock | 4 ++-- src/lib.rs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f043b2..3a31650 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1298,7 +1298,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -3917,7 +3917,7 @@ dependencies = [ [[package]] name = "wolves_oxidised" version = "0.1.0" -source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#eee6a76c695a36f1fe220fdeafd5a43757e50527" +source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#0097761c6973b490ffd0ec5b5ca45d3ab9c7915a" dependencies = [ "reqwest 0.12.9", "serde", diff --git a/src/lib.rs b/src/lib.rs index 05f6125..b449861 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,6 +22,7 @@ use sqlx::{ use std::{env, str::FromStr, sync::Arc}; use tokio::sync::RwLock; +#[derive(Debug)] pub struct Config { // manages where teh database is stored pub home: String, @@ -98,6 +99,7 @@ pub fn get_config() -> Config { if let Ok(x) = env::var("WOLVES_API") { config.wolves_api = x.trim().to_string(); } + dbg!(&config); config } From ad94b197ae887d8b1ddefd4574b69ebd8d41cc00 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 24 Nov 2024 00:14:42 +0000 Subject: [PATCH 050/170] fix: pretty solid chance that this was due to using teh wrong base url --- Cargo.lock | 2 +- src/lib.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a31650..4375270 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3917,7 +3917,7 @@ dependencies = [ [[package]] name = "wolves_oxidised" version = "0.1.0" -source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#0097761c6973b490ffd0ec5b5ca45d3ab9c7915a" +source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#eee6a76c695a36f1fe220fdeafd5a43757e50527" dependencies = [ "reqwest 0.12.9", "serde", diff --git a/src/lib.rs b/src/lib.rs index b449861..d73b009 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,13 +93,12 @@ pub fn get_config() -> Config { config.mail_pass = x.trim().to_string(); } - if let Ok(x) = env::var("WOLVES_URL") { + if let Ok(x) = env::var("WOLVES_URL_BASE") { config.wolves_url = x.trim().to_string(); } if let Ok(x) = env::var("WOLVES_API") { config.wolves_api = x.trim().to_string(); } - dbg!(&config); config } From bf55dfe31e0f2e8e70382e8d00f99c8cd9965682 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 25 Nov 2024 17:45:04 +0000 Subject: [PATCH 051/170] dbg: excessive logging --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 4375270..799b277 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3917,7 +3917,7 @@ dependencies = [ [[package]] name = "wolves_oxidised" version = "0.1.0" -source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#eee6a76c695a36f1fe220fdeafd5a43757e50527" +source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#1c176d4f7338cb6c98831533a14b7391bce76481" dependencies = [ "reqwest 0.12.9", "serde", From 68d7b53905215d6ad752a57125fb10b2d7a4cda8 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 25 Nov 2024 18:07:20 +0000 Subject: [PATCH 052/170] dbg: excessive logging - moar --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 799b277..4929efa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3917,7 +3917,7 @@ dependencies = [ [[package]] name = "wolves_oxidised" version = "0.1.0" -source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#1c176d4f7338cb6c98831533a14b7391bce76481" +source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#867778128c1ef580ebfded7808bbd4e86f22903b" dependencies = [ "reqwest 0.12.9", "serde", From 4691869ae90839764d7a94cbc94cbc898a0e59ff Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Fri, 29 Nov 2024 22:16:31 +0000 Subject: [PATCH 053/170] doc: split out the documentation --- README.md | 101 ++++------------------------------------------- doc/Committee.md | 75 +++++++++++++++++++++++++++++++++++ doc/User.md | 26 ++++++++++++ 3 files changed, 108 insertions(+), 94 deletions(-) create mode 100644 doc/Committee.md create mode 100644 doc/User.md diff --git a/README.md b/README.md index 5a0630b..01543e6 100644 --- a/README.md +++ b/README.md @@ -1,97 +1,10 @@ # Skynet Discord Bot -This bots core purpose is to give members roles based on their status on . -It uses an api key provided by wolves to get member lists. +The Skynet bot is designed to manage users on Discord. +It allows users to link their UL Wolves account with Wolves in a GDPR compliant manner. +Skynet (bot) is hosted is hosted by the Computer Society on Skynet (computer cluster). -Users are able to link their wolves account to the bot and that works across discord servers. -For example is a user links on the CompSoc Discord then they will also get their roles (automagically) on Games Dev if they are a member there. +## Documentation +We have split up the documentation into different segments depending on who the user is. -## Setup - Committee -You need admin access to run any of the commands in this section. -Either the server owner or a user with the ``Administrator`` permission. - -### Get the API Key -The ``api_key`` is used by the Bot in order to request information, it will be used later in the process. - -1. Email ``keith@assurememberships.com`` from committee email and say you want an ``api_key`` for ``193.1.99.74`` - * The committee email is the one here: - * This may take up to a week to get the key. - -### Setup Server -The Bot reason for existing is being able to give members Roles. -So we have to create those. - -1. Create a role for Current Members. - * You can call it whatever you want. - * ``member-current`` is a good choice. - * This should be a new role -2. **Optional**: you can create a role that is given to folks who were but no longer a member. - * ``member`` would be a good choice for this - * If you have an existing member role this is also a good fit. - -The reason for both roles is ye have one for active members while the second is for all current and past members. - -### Invite Bot -1. Invite the bot https://discord.com/api/oauth2/authorize?client_id=1145761669256069270&permissions=139855185984&scope=bot -2. Make sure the bot role ``@skynet`` is above these two roles created in the previous step - * This is so it can manage the roles (give and remove them from users) - -### Setup Bot -This is where the bot is configured. -You will need the ``api_key`` from the start of the process. -You (personally) will need a role with ``Administrator`` permission to be able to do this. - -1. Use the command ``/add`` and a list of options will pop up. -2. ``api_key`` is the key you got from Keith earlier. -3. ``role_current`` is the ``member-current`` that you created earlier. -4. ``role_past`` (optional) is the role for all current and past members. -5. ``bot_channel`` is a channel that folks are recommended to use the bot. - * You can have it so folks cannot see message history -6. ``server_name`` For example ``UL Computer Society`` - * Will be removed in the future -7. ``wolves_link`` for example - * Will be removed in the future - -At this point the bot is set up and no further action is required. - -### Minecraft -The bot is able to manage the whitelist of a Minecraft server managed by the Computer Society. -Talk to us to get a server. - -#### Add -This links a minecraft server with your club/society. - -``/minecraft_add SERVER_ID`` - - -#### List -List the servers linked to your club/society. -It is possible to have more than one minecraft server - -``/minecraft_list`` - -#### Delete -This unlinks a minecraft server from your club/society. - -``/minecraft_delete SERVER_ID`` - -## Setup - Users -This is to link your Discord account with your UL Wolves account. -**You will only need to do this once**. - -### Setup -1. In a Discord server with the Skynet Bot enter ``/link_wolves YOUR_WOLVES_CONTACT_EMAIL`` - link process start - * Your ``YOUR_WOLVES_CONTACT_EMAIL`` is the email in the Contact Email here: - * This is most likely your student mail -2. An email will be sent to you with a verification code. - signup email -3. Verify the code using ``/verify CODE_FROM_EMAIL`` in Discord. - verify in discord -4. Once complete your Wolves and Discord accounts will be linked. - -You will get member roles on any Discord that is using the bot that you are a member of. - -### Minecraft -You can link your Minecraft username to grant you access to any Minecraft server run by UL Computer Society. - -``/link_minecraft MINECRAFT_USERNAME`` +* [Committees](./doc/Committee.md) +* [Member](./doc/User.md) \ No newline at end of file diff --git a/doc/Committee.md b/doc/Committee.md new file mode 100644 index 0000000..d53be13 --- /dev/null +++ b/doc/Committee.md @@ -0,0 +1,75 @@ +# Skynet Discord Bot +This bots core purpose is to give members roles based on their status on . +It uses an api key provided by wolves to get member lists. + +Users are able to link their wolves account to the bot and that works across discord servers. +For example is a user links on the CompSoc Discord then they will also get their roles (automagically) on Games Dev if they are a member there. + +## Setup - Committee +You need admin access to run any of the commands in this section. +Either the server owner or a user with the ``Administrator`` permission. + +### Get the API Key +The ``api_key`` is used by the Bot in order to request information, it will be used later in the process. + +1. Email ``keith@assurememberships.com`` from committee email and say you want an ``api_key`` for ``193.1.99.74`` + * The committee email is the one here: + * This may take up to a week to get the key. + +### Setup Server +The Bot reason for existing is being able to give members Roles. +So we have to create those. + +1. Create a role for Current Members. + * You can call it whatever you want. + * ``member-current`` is a good choice. + * This should be a new role +2. **Optional**: you can create a role that is given to folks who were but no longer a member. + * ``member`` would be a good choice for this + * If you have an existing member role this is also a good fit. + +The reason for both roles is ye have one for active members while the second is for all current and past members. + +### Invite Bot +1. Invite the bot https://discord.com/api/oauth2/authorize?client_id=1145761669256069270&permissions=139855185984&scope=bot +2. Make sure the bot role ``@skynet`` is above these two roles created in the previous step + * This is so it can manage the roles (give and remove them from users) + +### Setup Bot +This is where the bot is configured. +You will need the ``api_key`` from the start of the process. +You (personally) will need a role with ``Administrator`` permission to be able to do this. + +1. Use the command ``/add`` and a list of options will pop up. +2. ``api_key`` is the key you got from Keith earlier. +3. ``role_current`` is the ``member-current`` that you created earlier. +4. ``role_past`` (optional) is the role for all current and past members. +5. ``bot_channel`` is a channel that folks are recommended to use the bot. + * You can have it so folks cannot see message history +6. ``server_name`` For example ``UL Computer Society`` + * Will be removed in the future +7. ``wolves_link`` for example + * Will be removed in the future + +At this point the bot is set up and no further action is required. + +### Minecraft +The bot is able to manage the whitelist of a Minecraft server managed by the Computer Society. +Talk to us to get a server. + +#### Add +This links a minecraft server with your club/society. + +``/minecraft_add SERVER_ID`` + + +#### List +List the servers linked to your club/society. +It is possible to have more than one minecraft server + +``/minecraft_list`` + +#### Delete +This unlinks a minecraft server from your club/society. + +``/minecraft_delete SERVER_ID`` diff --git a/doc/User.md b/doc/User.md new file mode 100644 index 0000000..fec6abe --- /dev/null +++ b/doc/User.md @@ -0,0 +1,26 @@ +# Skynet Discord Bot +The Skynet bot is designed to make it easy to verify that you are a member of a Club/Society. +The bot will be able to give you member roles for any partnered servers. +It also provides secondary manifests such as granting access to minecraft servers managed by teh Computer Society. + +## Setup +This is to link your Discord account with your UL Wolves account. +**You will only need to do this once**. + +### Setup +1. In a Discord server with the Skynet Bot enter ``/link_wolves YOUR_WOLVES_CONTACT_EMAIL`` + link process start + * Your ``YOUR_WOLVES_CONTACT_EMAIL`` is the email in the Contact Email here: + * This is most likely your student mail +2. An email will be sent to you with a verification code. + signup email +3. Verify the code using ``/verify CODE_FROM_EMAIL`` in Discord. + verify in discord +4. Once complete your Wolves and Discord accounts will be linked. + +You will get member roles on any Discord that is using the bot that you are a member of. + +### Minecraft +You can link your Minecraft username to grant you access to any Minecraft server run by UL Computer Society. + +``/link_minecraft MINECRAFT_USERNAME`` From b55650b221add58d720f4a69b385a9394605b94b Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Fri, 29 Nov 2024 23:12:52 +0000 Subject: [PATCH 054/170] db: add another col to store the bedrock id For #26 --- db/migrations/7_minecraft-bedrock.sql | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 db/migrations/7_minecraft-bedrock.sql diff --git a/db/migrations/7_minecraft-bedrock.sql b/db/migrations/7_minecraft-bedrock.sql new file mode 100644 index 0000000..71d8976 --- /dev/null +++ b/db/migrations/7_minecraft-bedrock.sql @@ -0,0 +1,3 @@ + +-- Using this col we will be able to see if someone has a bedrock account (as well as a java one) +ALTER TABLE wolves ADD COLUMN minecraft_uid TEXT; \ No newline at end of file From ee0c8f09872d50038afdb7e08b86ed5f33a34986 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 30 Nov 2024 00:44:36 +0000 Subject: [PATCH 055/170] feat; can now handle bedrock in the command For #26 --- Cargo.lock | 48 ++++++++--------- Cargo.toml | 3 ++ src/commands/minecraft.rs | 105 +++++++++++++++++++++++++++++++++----- src/lib.rs | 5 ++ 4 files changed, 124 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4929efa..23bef46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -107,9 +107,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] name = "async-channel" @@ -143,7 +143,7 @@ dependencies = [ "async-task", "concurrent-queue", "fastrand 2.1.1", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "slab", ] @@ -158,21 +158,21 @@ dependencies = [ "async-io", "async-lock", "blocking", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "once_cell", ] [[package]] name = "async-io" -version = "2.3.4" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444b0228950ee6501b3568d3c93bf1176a1fdbc3b758dcd9475046d30f4dc7e8" +checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock", "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "parking", "polling", "rustix", @@ -206,7 +206,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "gloo-timers", "kv-log-macro", "log", @@ -381,7 +381,7 @@ dependencies = [ "async-channel 2.3.1", "async-task", "futures-io", - "futures-lite 2.3.0", + "futures-lite 2.5.0", "piper", ] @@ -586,9 +586,9 @@ dependencies = [ [[package]] name = "curl" -version = "0.4.46" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6" +checksum = "d9fb4d13a1be2b58f14d60adba57c9834b78c62fd86c3e76a148f732686e9265" dependencies = [ "curl-sys", "libc", @@ -601,9 +601,9 @@ dependencies = [ [[package]] name = "curl-sys" -version = "0.4.75+curl-8.10.0" +version = "0.4.78+curl-8.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4fd752d337342e4314717c0d9b6586b059a120c80029ebe4d49b11fec7875e" +checksum = "8eec768341c5c7789611ae51cf6c459099f22e64a5d5d0ce4892434e33821eaf" dependencies = [ "cc", "libc", @@ -923,9 +923,9 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" dependencies = [ "fastrand 2.1.1", "futures-core", @@ -1298,7 +1298,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -1907,18 +1907,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ "proc-macro2", "quote", @@ -1977,9 +1977,9 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "polling" -version = "3.7.3" +version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2790cd301dec6cd3b7a025e4815cf825724a51c98dccfe6a3e55f05ffb6511" +checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ "cfg-if", "concurrent-queue", @@ -3521,9 +3521,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "value-bag" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" [[package]] name = "vcpkg" diff --git a/Cargo.toml b/Cargo.toml index 2dc64c8..6e60066 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,9 @@ name = "update_users" [[bin]] name = "update_minecraft" +[[bin]] +name = "test" + [dependencies] # discord library serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index b6b6dd5..52f9a04 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -15,18 +15,29 @@ pub(crate) mod user { pub(crate) mod add { use super::*; use crate::commands::link_email::link::get_server_member_discord; + use serde::{Deserialize, Serialize}; use serenity::model::id::UserId; use skynet_discord_bot::{whitelist_update, Config, Minecraft, Wolves}; use sqlx::Error; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command.name("link_minecraft").description("Link your minecraft account").create_option(|option| { - option - .name("minecraft-username") - .description("Your Minecraft username") - .kind(CommandOptionType::String) - .required(true) - }) + command + .name("link_minecraft") + .description("Link your minecraft account") + .create_option(|option| { + option + .name("minecraft-username") + .description("Your Minecraft username") + .kind(CommandOptionType::String) + .required(true) + }) + .create_option(|option| { + option + .name("java-account") + .description("Is this a Java account?") + .kind(CommandOptionType::Boolean) + .required(false) + }) } pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { @@ -61,12 +72,35 @@ pub(crate) mod user { return "Please provide a valid username".to_string(); }; - // insert the username into the database - match add_minecraft(&db, &command.user.id, username).await { - Ok(_) => {} - Err(e) => { - dbg!("{:?}", e); - return format!("Failure to minecraft username {:?}", username); + // this is always true unless they state its not + let mut java = true; + if let Some(x) = command.data.options.get(1) { + if let Some(CommandDataOptionValue::Boolean(z)) = x.to_owned().resolved { + java = z; + } + } + + if java { + // insert the username into the database + match add_minecraft(&db, &command.user.id, username).await { + Ok(_) => {} + Err(e) => { + dbg!("{:?}", e); + return format!("Failure to minecraft username {:?}", username); + } + } + } else { + match get_minecraft_bedrock(username, &config.minecraft_mcprofile).await { + None => { + return format!("No UID found for {:?}", username); + } + Some(x) => match add_minecraft_bedrock(&db, &command.user.id, &x.floodgateuid).await { + Ok(_) => {} + Err(e) => { + dbg!("{:?}", e); + return format!("Failure to minecraft UID {:?}", &x.floodgateuid); + } + }, } } @@ -94,6 +128,51 @@ pub(crate) mod user { .await } + #[derive(Serialize, Deserialize, Debug)] + struct BedrockDetails { + pub gamertag: String, + pub xuid: String, + pub floodgateuid: String, + pub icon: String, + pub gamescore: String, + pub accounttier: String, + pub textureid: String, + pub skin: String, + pub linked: bool, + pub java_uuid: String, + pub java_name: String, + } + + async fn get_minecraft_bedrock(username: &str, api_key: &str) -> Option { + let url = format!("https://mcprofile.io/api/v1/bedrock/gamertag/{username}/"); + match surf::get(url) + .header("x-api-key", api_key) + .header("User-Agent", "UL Computer Society") + .recv_json() + .await + { + Ok(res) => Some(res), + Err(e) => { + dbg!(e); + None + } + } + } + + async fn add_minecraft_bedrock(db: &Pool, user: &UserId, minecraft: &str) -> Result, Error> { + sqlx::query_as::<_, Wolves>( + " + UPDATE wolves + SET minecraft_uid = ?2 + WHERE discord = ?1; + ", + ) + .bind(*user.as_u64() as i64) + .bind(minecraft) + .fetch_optional(db) + .await + } + async fn get_servers(db: &Pool, discord: &UserId) -> Result, Error> { sqlx::query_as::<_, Minecraft>( " diff --git a/src/lib.rs b/src/lib.rs index d73b009..b2e5685 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -31,6 +31,7 @@ pub struct Config { // tokens for discord and other API's pub discord_token: String, pub discord_token_minecraft: String, + pub minecraft_mcprofile: String, // email settings pub mail_smtp: String, @@ -58,6 +59,7 @@ pub fn get_config() -> Config { let mut config = Config { discord_token: "".to_string(), discord_token_minecraft: "".to_string(), + minecraft_mcprofile: "".to_string(), home: ".".to_string(), database: "database.db".to_string(), @@ -82,6 +84,9 @@ pub fn get_config() -> Config { if let Ok(x) = env::var("DISCORD_TOKEN_MINECRAFT") { config.discord_token_minecraft = x.trim().to_string(); } + if let Ok(x) = env::var("MINECRAFT_MCPROFILE_KEY") { + config.minecraft_mcprofile = x.trim().to_string(); + } if let Ok(x) = env::var("EMAIL_SMTP") { config.mail_smtp = x.trim().to_string(); From 0478f634fa8122aa699695bf590709706eef4d5a Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 30 Nov 2024 00:55:23 +0000 Subject: [PATCH 056/170] feat; modified the command that interacts with teh server to accomodate bedrock players For #26 --- src/commands/minecraft.rs | 2 +- src/lib.rs | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 52f9a04..e8f132d 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -107,7 +107,7 @@ pub(crate) mod user { // get a list of servers that the user is a member of if let Ok(servers) = get_servers(&db, &command.user.id).await { for server in servers { - whitelist_update(&vec![username.to_string()], &server.minecraft, &config.discord_token_minecraft).await; + whitelist_update(&vec![(username.to_string(), java)], &server.minecraft, &config.discord_token_minecraft).await; } } diff --git a/src/lib.rs b/src/lib.rs index b2e5685..b5a0a4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,6 +135,7 @@ pub struct ServerMembersWolves { pub email: String, pub discord: Option, pub minecraft: Option, + pub minecraft_uid: Option, } impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves { fn from_row(row: &'r SqliteRow) -> Result { @@ -159,6 +160,7 @@ impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves { email: row.try_get("email")?, discord, minecraft: row.try_get("minecraft")?, + minecraft_uid: row.try_get("minecraft_uid")?, }) } } @@ -704,7 +706,10 @@ pub async fn update_server(server_id: &str, db: &Pool, g_id: &GuildId, c let mut usernames = vec![]; for member in get_server_member_bulk(db, g_id).await { if let Some(x) = member.minecraft { - usernames.push(x); + usernames.push((x, true)); + } + if let Some(x) = member.minecraft_uid { + usernames.push((x, true)); } } if !usernames.is_empty() { @@ -768,13 +773,19 @@ struct BodyDelete { files: Vec, } -pub async fn whitelist_update(add: &Vec, server: &str, token: &str) { +pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &str) { let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); let bearer = format!("Bearer {token}"); - for name in add { - let data = BodyCommand { - command: format!("whitelist add {name}"), + for (name, java) in add { + let data = if *java { + BodyCommand { + command: format!("whitelist add {name}"), + } + } else { + BodyCommand { + command: format!("fwhitelist add {name}"), + } }; post(&format!("{url_base}/command"), &bearer, &data).await; } From 9b3c71e3215b6f525bc25e91c0dc542f5fe7e9ba Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 30 Nov 2024 01:05:19 +0000 Subject: [PATCH 057/170] fix: had not intednded to leave the test config inthe cargo.toml --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6e60066..2dc64c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,9 +14,6 @@ name = "update_users" [[bin]] name = "update_minecraft" -[[bin]] -name = "test" - [dependencies] # discord library serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } From 68ffa55dc5b48933e38771983e0f87a11a2b6369 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 30 Nov 2024 01:21:40 +0000 Subject: [PATCH 058/170] fix: improve wording --- src/commands/minecraft.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index e8f132d..7e96b99 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -26,15 +26,15 @@ pub(crate) mod user { .description("Link your minecraft account") .create_option(|option| { option - .name("minecraft-username") + .name("Minecraft Username") .description("Your Minecraft username") .kind(CommandOptionType::String) .required(true) }) .create_option(|option| { option - .name("java-account") - .description("Is this a Java account?") + .name("Bedrock Account") + .description("Is this a Bedrock account?") .kind(CommandOptionType::Boolean) .required(false) }) @@ -76,7 +76,7 @@ pub(crate) mod user { let mut java = true; if let Some(x) = command.data.options.get(1) { if let Some(CommandDataOptionValue::Boolean(z)) = x.to_owned().resolved { - java = z; + java = !z; } } From b7d36de976ca8a0e2e9724043b1890e6eb4d3e10 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 30 Nov 2024 13:49:30 +0000 Subject: [PATCH 059/170] fix: argument names and some logic --- src/commands/minecraft.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 7e96b99..5c7772e 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -26,14 +26,14 @@ pub(crate) mod user { .description("Link your minecraft account") .create_option(|option| { option - .name("Minecraft Username") + .name("minecraft_username") .description("Your Minecraft username") .kind(CommandOptionType::String) .required(true) }) .create_option(|option| { option - .name("Bedrock Account") + .name("bedrock_account") .description("Is this a Bedrock account?") .kind(CommandOptionType::Boolean) .required(false) @@ -80,6 +80,7 @@ pub(crate) mod user { } } + let username_mc; if java { // insert the username into the database match add_minecraft(&db, &command.user.id, username).await { @@ -89,25 +90,30 @@ pub(crate) mod user { return format!("Failure to minecraft username {:?}", username); } } + username_mc = username.to_string(); } else { match get_minecraft_bedrock(username, &config.minecraft_mcprofile).await { None => { return format!("No UID found for {:?}", username); } - Some(x) => match add_minecraft_bedrock(&db, &command.user.id, &x.floodgateuid).await { - Ok(_) => {} - Err(e) => { - dbg!("{:?}", e); - return format!("Failure to minecraft UID {:?}", &x.floodgateuid); + Some(x) => { + match add_minecraft_bedrock(&db, &command.user.id, &x.floodgateuid).await { + Ok(_) => {} + Err(e) => { + dbg!("{:?}", e); + return format!("Failure to minecraft UID {:?}", &x.floodgateuid); + } } - }, + + username_mc = x.floodgateuid; + } } } // get a list of servers that the user is a member of if let Ok(servers) = get_servers(&db, &command.user.id).await { for server in servers { - whitelist_update(&vec![(username.to_string(), java)], &server.minecraft, &config.discord_token_minecraft).await; + whitelist_update(&vec![(username_mc.to_owned(), java)], &server.minecraft, &config.discord_token_minecraft).await; } } From a9d3af024ea16cc8f0db4acb42d4fdd2106e21b8 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Fri, 3 Jan 2025 19:58:00 +0000 Subject: [PATCH 060/170] feat: added some minor logging --- src/commands/minecraft.rs | 2 +- src/lib.rs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 5c7772e..83bb1e4 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -328,7 +328,7 @@ pub(crate) mod server { ID: {id} Online: {online} Info: {description} - Link: + Link: "#, name = &x.attributes.name, online = !x.attributes.is_suspended, diff --git a/src/lib.rs b/src/lib.rs index b5a0a4e..b32722b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -774,7 +774,8 @@ struct BodyDelete { } pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &str) { - let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); + println!("Update whitelist for {}", server); + let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); let bearer = format!("Bearer {token}"); for (name, java) in add { @@ -792,7 +793,8 @@ pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &s } pub async fn whitelist_wipe(server: &str, token: &str) { - let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); + println!("Wiping whitelist for {}", server); + let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); let bearer = format!("Bearer {token}"); // delete whitelist @@ -813,7 +815,8 @@ pub async fn whitelist_wipe(server: &str, token: &str) { } pub async fn server_information(server: &str, token: &str) -> Option { - let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); + println!("Get server information for {}", server); + let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); let bearer = format!("Bearer {token}"); get::(&format!("{url_base}/"), &bearer).await } From 0a4f5281a545281c11c68185836ded2c17453306 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Fri, 3 Jan 2025 21:01:27 +0000 Subject: [PATCH 061/170] fix: typo, always took users as being java --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index b32722b..b7894bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -709,7 +709,7 @@ pub async fn update_server(server_id: &str, db: &Pool, g_id: &GuildId, c usernames.push((x, true)); } if let Some(x) = member.minecraft_uid { - usernames.push((x, true)); + usernames.push((x, false)); } } if !usernames.is_empty() { From 5fcc24a867c98be772eec8c6a65eddfbe52ab070 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 26 Jan 2025 20:06:46 +0000 Subject: [PATCH 062/170] feat: run teh data update every 10 min --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index b48d90b..12ae36e 100644 --- a/flake.nix +++ b/flake.nix @@ -119,7 +119,7 @@ # modify these scripts = { # every 20 min - "update_data" = "*:0,20,40"; + "update_data" = "*:0,10,20,30,40,50"; # groups are updated every hour, offset from teh ldap "update_users" = "*:05:00"; # minecraft stuff is updated at 5am From cab04a068f158e9c31659fad452a48afd82698d2 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 18 Feb 2025 13:36:08 +0000 Subject: [PATCH 063/170] feat: fixed up the changes --- Cargo.lock | 16 ++++++++++++++-- Cargo.toml | 7 ++----- db/migrations/7_committee-mk-ii.sql | 10 ---------- db/migrations/8_committee-mk-ii.sql | 11 +++++++++++ src/commands/link_email.rs | 1 - src/commands/minecraft.rs | 4 ++-- src/common/database.rs | 2 ++ src/common/minecraft.rs | 19 +++++-------------- src/common/set_roles.rs | 10 +++++----- src/common/wolves.rs | 17 ++++++++++------- src/lib.rs | 12 +++++++++++- 11 files changed, 62 insertions(+), 47 deletions(-) delete mode 100644 db/migrations/7_committee-mk-ii.sql create mode 100644 db/migrations/8_committee-mk-ii.sql diff --git a/Cargo.lock b/Cargo.lock index 23bef46..a1c92c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1298,7 +1298,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -2628,6 +2628,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "signature" version = "2.2.0" @@ -2648,6 +2657,7 @@ dependencies = [ "maud", "rand 0.8.5", "serde", + "serde_json", "serenity", "sqlx", "surf", @@ -3242,7 +3252,9 @@ dependencies = [ "bytes 1.7.1", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2 0.5.7", "tokio-macros", "windows-sys 0.52.0", @@ -3917,7 +3929,7 @@ dependencies = [ [[package]] name = "wolves_oxidised" version = "0.1.0" -source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#867778128c1ef580ebfded7808bbd4e86f22903b" +source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#6101104c794c2dcf7100b057fe37fdf165b8b381" dependencies = [ "reqwest 0.12.9", "serde", diff --git a/Cargo.toml b/Cargo.toml index 19cb9ae..fa0ffdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,11 +20,8 @@ serenity = { version = "0.11.6", default-features = false, features = ["client", tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "full"] } # wolves api -# TODO: move off of unstable -wolves_oxidised = { git = "https://forgejo.skynet.ie/Skynet/wolves-oxidised.git", features = ["unstable"]} - -# wolves api -wolves_oxidised = { git = "https://forgejo.skynet.ie/Skynet/wolves-oxidised.git" } +wolves_oxidised = { git = "https://forgejo.skynet.ie/Skynet/wolves-oxidised.git", features = ["unstable"] } +# wolves_oxidised = { path = "../wolves-oxidised", features = ["unstable"] } # to make the http requests surf = "2.3.2" diff --git a/db/migrations/7_committee-mk-ii.sql b/db/migrations/7_committee-mk-ii.sql deleted file mode 100644 index c086f3d..0000000 --- a/db/migrations/7_committee-mk-ii.sql +++ /dev/null @@ -1,10 +0,0 @@ --- No longer using the previous committee table -DROP TABLE committee; - --- new table pulling from teh api -CREATE TABLE IF NOT EXISTS committees ( - id integer PRIMARY KEY, - name text not null, - link text not null, - committee text not null -); diff --git a/db/migrations/8_committee-mk-ii.sql b/db/migrations/8_committee-mk-ii.sql new file mode 100644 index 0000000..d2e1d7c --- /dev/null +++ b/db/migrations/8_committee-mk-ii.sql @@ -0,0 +1,11 @@ +-- No longer using the previous committee table +DROP TABLE committee; + +-- new table pulling from teh api +CREATE TABLE IF NOT EXISTS committees ( + id integer PRIMARY KEY, + name_profile text not null, + name_full text not null, + link text not null, + committee text not null +); diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index fdf82d4..a76769f 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -263,7 +263,6 @@ pub mod link { .await } - #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] pub enum WolvesResultUserResult { diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 45c4643..10ee284 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -18,8 +18,8 @@ pub(crate) mod user { use serde::{Deserialize, Serialize}; use serenity::model::id::UserId; use skynet_discord_bot::common::database::Wolves; - use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft}; - use skynet_discord_bot::Config; + use skynet_discord_bot::common::minecraft::Minecraft; + use skynet_discord_bot::{whitelist_update, Config}; use sqlx::Error; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { diff --git a/src/common/database.rs b/src/common/database.rs index b3edc18..5139cb5 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -42,6 +42,7 @@ pub struct ServerMembersWolves { pub email: String, pub discord: Option, pub minecraft: Option, + pub minecraft_uid: Option, } impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves { @@ -67,6 +68,7 @@ impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves { email: row.try_get("email")?, discord, minecraft: row.try_get("minecraft")?, + minecraft_uid: row.try_get("minecraft_uid")?, }) } } diff --git a/src/common/minecraft.rs b/src/common/minecraft.rs index 6ee11c8..000aa46 100644 --- a/src/common/minecraft.rs +++ b/src/common/minecraft.rs @@ -1,5 +1,5 @@ use crate::common::set_roles::normal::get_server_member_bulk; -use crate::Config; +use crate::{whitelist_update, Config}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serenity::model::id::GuildId; @@ -33,7 +33,10 @@ pub async fn update_server(server_id: &str, db: &Pool, g_id: &GuildId, c let mut usernames = vec![]; for member in get_server_member_bulk(db, g_id).await { if let Some(x) = member.minecraft { - usernames.push(x); + usernames.push((x, true)); + } + if let Some(x) = member.minecraft_uid { + usernames.push((x, false)); } } if !usernames.is_empty() { @@ -98,18 +101,6 @@ struct BodyDelete { files: Vec, } -pub async fn whitelist_update(add: &Vec, server: &str, token: &str) { - let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); - let bearer = format!("Bearer {token}"); - - for name in add { - let data = BodyCommand { - command: format!("whitelist add {name}"), - }; - post(&format!("{url_base}/command"), &bearer, &data).await; - } -} - pub async fn whitelist_wipe(server: &str, token: &str) { let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); let bearer = format!("Bearer {token}"); diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 9b26117..912d661 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -199,11 +199,11 @@ pub mod committee { for committee in &committees { // get the role for this committee/club/soc - let role = match roles_name.get(&committee.name) { + let role = match roles_name.get(&committee.name_profile) { Some(x) => Some(x.to_owned()), None => { // create teh role if it does not exist - match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name)).await { + match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name_profile)).await { Ok(x) => Some(x), Err(_) => None, } @@ -211,16 +211,16 @@ pub mod committee { }; // create teh channel if it does nto exist - if !channels_name.contains_key(&committee.name) { + if !channels_name.contains_key(&committee.name_profile) { match server - .create_channel(&ctx, |c| c.name(&committee.name).kind(ChannelType::Text).category(config.committee_category)) + .create_channel(&ctx, |c| c.name(&committee.name_profile).kind(ChannelType::Text).category(config.committee_category)) .await { Ok(x) => { // update teh channels name list channels_name.insert(x.name.to_owned(), x.to_owned()); - println!("Created channel: {}", &committee.name); + println!("Created channel: {}", &committee.name_profile); } Err(x) => { dbg!("Unable to create channel: ", x); diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 0f975e3..b2c6af1 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -176,7 +176,8 @@ pub mod committees { #[derive(Debug, Clone, sqlx::FromRow)] pub struct Committees { pub id: i64, - pub name: String, + pub name_full: String, + pub name_profile: String, pub link: String, #[sqlx(json)] pub committee: Vec, @@ -185,10 +186,11 @@ pub mod committees { impl From for Committees { fn from(value: wolves_oxidised::WolvesCNS) -> Self { Self { - id: value.id.parse().unwrap_or(0), - name: value.name, + id: value.id, + name_full: value.name_full, + name_profile: value.name_profile, link: value.link, - committee: value.committee.iter().map(|x| x.parse::().unwrap_or(0)).collect(), + committee: value.committee, } } } @@ -217,13 +219,14 @@ pub mod committees { async fn add_committee(db: &Pool, committee: &Committees) { match sqlx::query_as::<_, Committees>( " - INSERT INTO committees (id, name, link, committee) - VALUES ($1, $2, $3, $4) + INSERT INTO committees (id, name_profile, name_full, link, committee) + VALUES ($1, $2, $3, $4, $5) ON CONFLICT(id) DO UPDATE SET committee = $4 ", ) .bind(committee.id) - .bind(&committee.name) + .bind(&committee.name_profile) + .bind(&committee.name_full) .bind(&committee.link) .bind(serde_json::to_string(&committee.committee).unwrap_or_default()) .fetch_optional(db) diff --git a/src/lib.rs b/src/lib.rs index 1b5000d..5b8d073 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,12 +1,19 @@ pub mod common; +use crate::common::set_roles::normal::get_server_member_bulk; use chrono::{Datelike, SecondsFormat, Utc}; use dotenvy::dotenv; use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; use serenity::client::Context; -use serenity::model::id::{ChannelId, GuildId, RoleId}; +use serenity::model::guild; +use serenity::model::id::{ChannelId, GuildId, RoleId, UserId}; use serenity::model::prelude::application_command::ApplicationCommandInteraction; use serenity::prelude::TypeMapKey; +use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow}; +use sqlx::{Error, FromRow, Pool, Row, Sqlite}; +use std::str::FromStr; use std::{env, sync::Arc}; use tokio::sync::RwLock; @@ -56,6 +63,7 @@ pub fn get_config() -> Config { mail_user: "".to_string(), mail_pass: "".to_string(), wolves_url: "".to_string(), + wolves_api: "".to_string(), committee_server: GuildId(0), committee_role: RoleId(0), committee_category: ChannelId(0), @@ -418,6 +426,7 @@ pub fn random_string(len: usize) -> String { pub mod set_roles { use super::*; + use crate::common::database::DataBase; pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option], members_changed: &[UserId]) { let db_lock = { let data_read = ctx.data.read().await; @@ -544,6 +553,7 @@ pub mod set_roles { pub mod get_data { use super::*; + use crate::common::database::DataBase; use crate::set_roles::update_server; use std::collections::BTreeMap; use wolves_oxidised::WolvesUser; From 4eeb7f213584478838996190d5190155d448e456 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 18 Feb 2025 14:00:21 +0000 Subject: [PATCH 064/170] fix: grab the committee name since we can use that to match up against teh suers --- db/migrations/8_committee-mk-ii.sql | 1 + src/common/wolves.rs | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/db/migrations/8_committee-mk-ii.sql b/db/migrations/8_committee-mk-ii.sql index d2e1d7c..ef6d185 100644 --- a/db/migrations/8_committee-mk-ii.sql +++ b/db/migrations/8_committee-mk-ii.sql @@ -5,6 +5,7 @@ DROP TABLE committee; CREATE TABLE IF NOT EXISTS committees ( id integer PRIMARY KEY, name_profile text not null, + name_plain text not null, name_full text not null, link text not null, committee text not null diff --git a/src/common/wolves.rs b/src/common/wolves.rs index b2c6af1..ba0ff93 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -178,6 +178,7 @@ pub mod committees { pub id: i64, pub name_full: String, pub name_profile: String, + pub name_plain: String, pub link: String, #[sqlx(json)] pub committee: Vec, @@ -189,6 +190,7 @@ pub mod committees { id: value.id, name_full: value.name_full, name_profile: value.name_profile, + name_plain: value.name_plain, link: value.link, committee: value.committee, } @@ -219,14 +221,15 @@ pub mod committees { async fn add_committee(db: &Pool, committee: &Committees) { match sqlx::query_as::<_, Committees>( " - INSERT INTO committees (id, name_profile, name_full, link, committee) - VALUES ($1, $2, $3, $4, $5) + INSERT INTO committees (id, name_profile, name_full, name_plain, link, committee) + VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT(id) DO UPDATE SET committee = $4 ", ) .bind(committee.id) .bind(&committee.name_profile) .bind(&committee.name_full) + .bind(&committee.name_plain) .bind(&committee.link) .bind(serde_json::to_string(&committee.committee).unwrap_or_default()) .fetch_optional(db) From 1aef86ad01eda0a738ee768dcfcc5a588f0dc0c1 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 18 Feb 2025 21:14:05 +0000 Subject: [PATCH 065/170] feat: now properly sets and removes roles for committee members --- db/migrations/8_committee-mk-ii.sql | 2 + doc/Committee.md | 4 -- src/bin/update_data.rs | 5 +- src/commands/add_server.rs | 51 ++---------------- src/common/database.rs | 3 -- src/common/set_roles.rs | 84 ++++++++++++++++++++++------- src/common/wolves.rs | 35 +++++++++++- src/lib.rs | 2 - src/main.rs | 40 ++++++++++---- 9 files changed, 137 insertions(+), 89 deletions(-) diff --git a/db/migrations/8_committee-mk-ii.sql b/db/migrations/8_committee-mk-ii.sql index ef6d185..3c942cd 100644 --- a/db/migrations/8_committee-mk-ii.sql +++ b/db/migrations/8_committee-mk-ii.sql @@ -10,3 +10,5 @@ CREATE TABLE IF NOT EXISTS committees ( link text not null, committee text not null ); + +ALTER TABLE servers DROP COLUMN wolves_link; \ No newline at end of file diff --git a/doc/Committee.md b/doc/Committee.md index d53be13..cfe718e 100644 --- a/doc/Committee.md +++ b/doc/Committee.md @@ -46,10 +46,6 @@ You (personally) will need a role with ``Administrator`` permission to be able t 4. ``role_past`` (optional) is the role for all current and past members. 5. ``bot_channel`` is a channel that folks are recommended to use the bot. * You can have it so folks cannot see message history -6. ``server_name`` For example ``UL Computer Society`` - * Will be removed in the future -7. ``wolves_link`` for example - * Will be removed in the future At this point the bot is set up and no further action is required. diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index 4385a1c..2d36892 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -16,7 +16,10 @@ async fn main() { let config = get_config(); let db = match db_init(&config).await { Ok(x) => x, - Err(_) => return, + Err(e) => { + dbg!(e); + return; + } }; // Intents are a bitflag, bitwise operations can be used to dictate which intents to use diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index 52284ac..8f06527 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -67,34 +67,6 @@ pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> Stri return "Please provide a valid channel for ``Bot Channel``".to_string(); }; - let server_name = if let CommandDataOptionValue::String(name) = command - .data - .options - .get(3) - .expect("Expected Server Name option") - .resolved - .as_ref() - .expect("Expected Server Name object") - { - name - } else { - &"UL Computer Society".to_string() - }; - - let wolves_link = if let CommandDataOptionValue::String(wolves) = command - .data - .options - .get(4) - .expect("Expected Wolves Link option") - .resolved - .as_ref() - .expect("Expected Server Name object") - { - wolves - } else { - &"https://ulwolves.ie/society/computer".to_string() - }; - let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() @@ -109,8 +81,7 @@ pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> Stri member_past: 0, member_current: 0, bot_channel_id, - server_name: server_name.to_owned(), - wolves_link: wolves_link.to_string(), + server_name: "".to_string(), }; match add_server(&db, ctx, &server_data).await { @@ -149,20 +120,6 @@ pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicatio .kind(CommandOptionType::Channel) .required(true) }) - .create_option(|option| { - option - .name("server_name") - .description("Name of the Discord Server.") - .kind(CommandOptionType::String) - .required(true) - }) - .create_option(|option| { - option - .name("wolves_link") - .description("Link to the Club/Society on UL Wolves.") - .kind(CommandOptionType::String) - .required(true) - }) .create_option(|option| { option .name("role_past") @@ -178,8 +135,8 @@ async fn add_server(db: &Pool, ctx: &Context, server: &Servers) -> Resul let insert = sqlx::query_as::<_, Servers>( " - INSERT OR REPLACE INTO servers (server, wolves_api, role_past, role_current, bot_channel_id, server_name, wolves_link) - VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7) + INSERT OR REPLACE INTO servers (server, wolves_api, role_past, role_current, bot_channel_id) + VALUES (?1, ?2, ?3, ?4, ?5) ", ) .bind(*server.server.as_u64() as i64) @@ -187,8 +144,6 @@ async fn add_server(db: &Pool, ctx: &Context, server: &Servers) -> Resul .bind(role_past) .bind(*server.role_current.as_u64() as i64) .bind(*server.bot_channel_id.as_u64() as i64) - .bind(&server.server_name) - .bind(&server.wolves_link) .fetch_optional(db) .await; diff --git a/src/common/database.rs b/src/common/database.rs index 5139cb5..ace94ec 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -135,9 +135,7 @@ pub struct Servers { pub member_past: i64, pub member_current: i64, pub bot_channel_id: ChannelId, - // TODO: these can be removed in teh future with an API update pub server_name: String, - pub wolves_link: String, } impl<'r> FromRow<'r, SqliteRow> for Servers { @@ -175,7 +173,6 @@ impl<'r> FromRow<'r, SqliteRow> for Servers { member_current: row.try_get("member_current")?, bot_channel_id, server_name: row.try_get("server_name")?, - wolves_link: row.try_get("wolves_link")?, }) } } diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 912d661..f0a02cb 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -137,6 +137,8 @@ pub mod committee { use serenity::client::Context; use serenity::model::channel::ChannelType; use serenity::model::guild::Member; + use serenity::model::id::ChannelId; + use serenity::model::prelude::RoleId; use sqlx::{Pool, Sqlite}; use std::collections::HashMap; use std::sync::Arc; @@ -170,6 +172,7 @@ pub mod committee { let server = config.committee_server; let committee_member = config.committee_role; let committees = get_committees(db).await; + let categories = vec![config.committee_category, ChannelId(1341457244973305927), ChannelId(1341457509717639279)]; // information about the server let roles = server.roles(&ctx).await.unwrap_or_default(); @@ -185,8 +188,10 @@ pub mod committee { for channel in channels.values() { // we only care about teh channels in teh category if let Some(x) = channel.parent_id { - if x.eq(&config.committee_category) { - channels_name.insert(channel.name.to_owned(), channel.to_owned()); + for category in &categories { + if x.eq(category) { + channels_name.insert(channel.name.to_owned(), channel.to_owned()); + } } } } @@ -197,13 +202,21 @@ pub mod committee { // a list of all the roles that can be removed from folks who should have them let mut committee_roles = vec![committee_member]; - for committee in &committees { + let mut category_index = 0; + + let mut i = 0; + loop { + if i >= committees.len() { + break; + } + let committee = &committees[i]; + // get the role for this committee/club/soc - let role = match roles_name.get(&committee.name_profile) { + let role = match roles_name.get(&committee.name_full) { Some(x) => Some(x.to_owned()), None => { // create teh role if it does not exist - match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name_profile)).await { + match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name_full)).await { Ok(x) => Some(x), Err(_) => None, } @@ -213,7 +226,7 @@ pub mod committee { // create teh channel if it does nto exist if !channels_name.contains_key(&committee.name_profile) { match server - .create_channel(&ctx, |c| c.name(&committee.name_profile).kind(ChannelType::Text).category(config.committee_category)) + .create_channel(&ctx, |c| c.name(&committee.name_profile).kind(ChannelType::Text).category(categories[category_index])) .await { Ok(x) => { @@ -223,7 +236,14 @@ pub mod committee { println!("Created channel: {}", &committee.name_profile); } Err(x) => { - dbg!("Unable to create channel: ", x); + let tmp = x.to_string(); + + if x.to_string().contains("Maximum number of channels in category reached (50)") { + category_index += 1; + + continue; + } + dbg!("Unable to create channel: ", &tmp, &tmp.contains("Maximum number of channels in category reached (50)")); } } }; @@ -236,42 +256,60 @@ pub mod committee { // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) if let Some(x) = get_server_member_discord(db, id_wolves).await { if let Some(member_tmp) = x.discord { - let values = users_roles.entry(member_tmp).or_insert(vec![]); + let values = users_roles.entry(member_tmp).or_insert(vec![committee_member]); values.push(r.id); } } } } + + i += 1; } // now we have a map of all users that should get roles time to go through all the folks on teh server for member in members { + let roles_current = member.roles(ctx).unwrap_or_default(); + let roles_required = match users_roles.get(&member.user.id) { None => { vec![] } Some(x) => { - let mut combined = x.to_owned(); - // this is the main role, since it provides access to everything. - combined.push(committee_member); - combined + let mut tmp = x.to_owned(); + if !tmp.is_empty() { + tmp.push(RoleId(1226602779968274573)); + } + tmp } }; - // get a list of all the roles to remove from someone let mut roles_rem = vec![]; - for role in &committee_roles { - if !roles_required.contains(role) { - roles_rem.push(role.to_owned()); + let mut roles_add = vec![]; + // get a list of all the roles to remove from someone + + let mut roles_current_id = vec![]; + for role in &roles_current { + roles_current_id.push(role.id.to_owned()); + if !roles_required.contains(&role.id) { + roles_rem.push(role.id.to_owned()); } } + + for role in &roles_required { + if !roles_current_id.contains(role) { + roles_add.push(role.to_owned()); + } + } + if !roles_rem.is_empty() { member.remove_roles(&ctx, &roles_rem).await.unwrap_or_default(); } - if !roles_required.is_empty() { + if !roles_add.is_empty() { // these roles are flavor roles, only there to make folks mentionable - member.add_roles(&ctx, &roles_required).await.unwrap_or_default(); + member.add_roles(&ctx, &roles_add).await.unwrap_or_default(); + } else { + member.remove_roles(&ctx, &[RoleId(1226602779968274573)]).await.unwrap_or_default(); } } @@ -303,7 +341,7 @@ pub mod committee { } async fn get_committees(db: &Pool) -> Vec { - sqlx::query_as::<_, Committees>( + match sqlx::query_as::<_, Committees>( r#" SELECT * FROM committees @@ -311,7 +349,13 @@ pub mod committee { ) .fetch_all(db) .await - .unwrap_or_default() + { + Ok(a) => a, + Err(e) => { + dbg!(e); + vec![] + } + } } async fn get_server_member_discord(db: &Pool, user: &i64) -> Option { diff --git a/src/common/wolves.rs b/src/common/wolves.rs index ba0ff93..9141983 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -87,15 +87,22 @@ pub mod cns { server, // this is the unique api key for each club/soc wolves_api, + server_name, .. } = &server_config; + // dbg!(&server_config); let existing_tmp = get_server_member(&db, server).await; let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::>(); // list of users that need to be updated for this server let mut user_to_update = vec![]; + let mut server_name_tmp = None; for user in wolves.get_members(wolves_api).await { + // dbg!(&user.committee); + if server_name_tmp.is_none() { + server_name_tmp = Some(user.committee.to_owned()); + } let id = user.member_id.parse::().unwrap_or_default(); match existing.get(&(id as i64)) { None => { @@ -117,12 +124,38 @@ pub mod cns { } } + if let Some(name) = server_name_tmp { + if &name != server_name { + set_server_member(&db, server, &name).await; + } + } if !user_to_update.is_empty() { update_server(ctx, &server_config, &[], &user_to_update).await; } } } + async fn set_server_member(db: &Pool, server: &GuildId, name: &str) { + match sqlx::query_as::<_, Servers>( + " + UPDATE servers + SET server_name = ? + WHERE server = ? + ", + ) + .bind(name) + .bind(*server.as_u64() as i64) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + println!("Failure to set server name {}", server.as_u64()); + println!("{:?}", e); + } + } + } + async fn get_server_member(db: &Pool, server: &GuildId) -> Vec { sqlx::query_as::<_, ServerMembersWolves>( r#" @@ -223,7 +256,7 @@ pub mod committees { " INSERT INTO committees (id, name_profile, name_full, name_plain, link, committee) VALUES ($1, $2, $3, $4, $5, $6) - ON CONFLICT(id) DO UPDATE SET committee = $4 + ON CONFLICT(id) DO UPDATE SET committee = $6 ", ) .bind(committee.id) diff --git a/src/lib.rs b/src/lib.rs index 5b8d073..1e11ada 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -262,7 +262,6 @@ pub struct Servers { pub bot_channel_id: ChannelId, // these can be removed in teh future with an API update pub server_name: String, - pub wolves_link: String, } impl<'r> FromRow<'r, SqliteRow> for Servers { fn from_row(row: &'r SqliteRow) -> Result { @@ -299,7 +298,6 @@ impl<'r> FromRow<'r, SqliteRow> for Servers { member_current: row.try_get("member_current")?, bot_channel_id, server_name: row.try_get("server_name")?, - wolves_link: row.try_get("wolves_link")?, }) } } diff --git a/src/main.rs b/src/main.rs index 62c0350..e1fe422 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,7 +15,9 @@ use serenity::{ }; use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_member, DataBase}; use skynet_discord_bot::common::set_roles::committee::update_committees; +use skynet_discord_bot::common::wolves::committees::Committees; use skynet_discord_bot::{get_config, Config}; +use sqlx::{Pool, Sqlite}; use std::sync::Arc; use tokio::sync::RwLock; @@ -67,20 +69,24 @@ impl EventHandler for Handler { println!("{:?}", e); } } else { - let msg = format!( - r#" + let tmp = get_committee(&db, &config_server.server_name).await; + if !tmp.is_empty() { + let committee = &tmp[0]; + let msg = format!( + r#" Welcome {} to the {} server! Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use ``/link_wolves`` to get full access. "#, - new_member.display_name(), - &config_server.server_name, - &config_server.wolves_link, - &config_server.server, - &config_server.bot_channel_id - ); + new_member.display_name(), + committee.name_full, + committee.link, + &config_server.server, + &config_server.bot_channel_id + ); - if let Err(err) = new_member.user.direct_message(&ctx, |m| m.content(&msg)).await { - dbg!(err); + if let Err(err) = new_member.user.direct_message(&ctx, |m| m.content(&msg)).await { + dbg!(err); + } } } } @@ -149,6 +155,20 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } } +async fn get_committee(db: &Pool, committee: &str) -> Vec { + sqlx::query_as::<_, Committees>( + r#" + SELECT * + FROM committees + WHERE name_plain = ? + "#, + ) + .bind(committee) + .fetch_all(db) + .await + .unwrap_or_default() +} + #[tokio::main] async fn main() { let config = get_config(); From b6cffd8a226da98c9b8e77f53cec79c98598d01c Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 18 Feb 2025 21:41:28 +0000 Subject: [PATCH 066/170] fix: trimmed down teh duplicates --- src/commands/minecraft.rs | 4 +- src/common/minecraft.rs | 23 +- src/lib.rs | 697 +------------------------------------- 3 files changed, 24 insertions(+), 700 deletions(-) diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 10ee284..45c4643 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -18,8 +18,8 @@ pub(crate) mod user { use serde::{Deserialize, Serialize}; use serenity::model::id::UserId; use skynet_discord_bot::common::database::Wolves; - use skynet_discord_bot::common::minecraft::Minecraft; - use skynet_discord_bot::{whitelist_update, Config}; + use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft}; + use skynet_discord_bot::Config; use sqlx::Error; pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { diff --git a/src/common/minecraft.rs b/src/common/minecraft.rs index 000aa46..25bde46 100644 --- a/src/common/minecraft.rs +++ b/src/common/minecraft.rs @@ -1,5 +1,5 @@ use crate::common::set_roles::normal::get_server_member_bulk; -use crate::{whitelist_update, Config}; +use crate::Config; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use serenity::model::id::GuildId; @@ -44,7 +44,7 @@ pub async fn update_server(server_id: &str, db: &Pool, g_id: &GuildId, c } } -async fn post(url: &str, bearer: &str, data: &T) { +pub async fn post(url: &str, bearer: &str, data: &T) { match surf::post(url) .header("Authorization", bearer) .header("Content-Type", "application/json") @@ -153,3 +153,22 @@ pub async fn get_minecraft_config_server(db: &Pool, g_id: GuildId) -> Ve .await .unwrap_or_default() } + +pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &str) { + println!("Update whitelist for {}", server); + let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); + let bearer = format!("Bearer {token}"); + + for (name, java) in add { + let data = if *java { + BodyCommand { + command: format!("whitelist add {name}"), + } + } else { + BodyCommand { + command: format!("fwhitelist add {name}"), + } + }; + post(&format!("{url_base}/command"), &bearer, &data).await; + } +} diff --git a/src/lib.rs b/src/lib.rs index 1e11ada..5611a21 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,12 @@ pub mod common; -use crate::common::set_roles::normal::get_server_member_bulk; use chrono::{Datelike, SecondsFormat, Utc}; use dotenvy::dotenv; use rand::{distributions::Alphanumeric, thread_rng, Rng}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; use serenity::client::Context; -use serenity::model::guild; -use serenity::model::id::{ChannelId, GuildId, RoleId, UserId}; +use serenity::model::id::{ChannelId, GuildId, RoleId}; use serenity::model::prelude::application_command::ApplicationCommandInteraction; use serenity::prelude::TypeMapKey; -use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow}; -use sqlx::{Error, FromRow, Pool, Row, Sqlite}; -use std::str::FromStr; use std::{env, sync::Arc}; use tokio::sync::RwLock; @@ -122,293 +115,6 @@ pub fn get_config() -> Config { config } -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct ServerMembers { - pub server: GuildId, - pub id_wolves: i64, - pub expiry: String, -} -impl<'r> FromRow<'r, SqliteRow> for ServerMembers { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server")?; - let server = GuildId::from(server_tmp as u64); - - Ok(Self { - server, - id_wolves: row.try_get("id_wolves")?, - expiry: row.try_get("expiry")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct ServerMembersWolves { - pub server: GuildId, - pub id_wolves: i64, - pub expiry: String, - pub email: String, - pub discord: Option, - pub minecraft: Option, - pub minecraft_uid: Option, -} -impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server")?; - let server = GuildId::from(server_tmp as u64); - let discord = match row.try_get("discord") { - Ok(x) => { - let tmp: i64 = x; - if tmp == 0 { - None - } else { - Some(UserId::from(tmp as u64)) - } - } - _ => None, - }; - - Ok(Self { - server, - id_wolves: row.try_get("id_wolves")?, - expiry: row.try_get("expiry")?, - email: row.try_get("email")?, - discord, - minecraft: row.try_get("minecraft")?, - minecraft_uid: row.try_get("minecraft_uid")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Wolves { - pub id_wolves: i64, - pub email: String, - pub discord: Option, - pub minecraft: Option, -} -impl<'r> FromRow<'r, SqliteRow> for Wolves { - fn from_row(row: &'r SqliteRow) -> Result { - let discord = match row.try_get("discord") { - Ok(x) => { - let tmp: i64 = x; - if tmp == 0 { - None - } else { - Some(UserId::from(tmp as u64)) - } - } - _ => None, - }; - - Ok(Self { - id_wolves: row.try_get("id_wolves")?, - email: row.try_get("email")?, - discord, - minecraft: row.try_get("minecraft")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct WolvesVerify { - pub email: String, - pub discord: UserId, - pub auth_code: String, - pub date_expiry: String, -} -impl<'r> FromRow<'r, SqliteRow> for WolvesVerify { - fn from_row(row: &'r SqliteRow) -> Result { - let user_tmp: i64 = row.try_get("discord")?; - let discord = UserId::from(user_tmp as u64); - - Ok(Self { - email: row.try_get("email")?, - discord, - auth_code: row.try_get("auth_code")?, - date_expiry: row.try_get("date_expiry")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Committee { - pub email: String, - pub discord: UserId, - pub auth_code: String, - pub committee: i64, -} -impl<'r> FromRow<'r, SqliteRow> for Committee { - fn from_row(row: &'r SqliteRow) -> Result { - let user_tmp: i64 = row.try_get("discord")?; - let discord = UserId::from(user_tmp as u64); - - Ok(Self { - email: row.try_get("email")?, - discord, - auth_code: row.try_get("auth_code")?, - committee: row.try_get("committee")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Servers { - pub server: GuildId, - pub wolves_api: String, - pub role_past: Option, - pub role_current: RoleId, - pub member_past: i64, - pub member_current: i64, - pub bot_channel_id: ChannelId, - // these can be removed in teh future with an API update - pub server_name: String, -} -impl<'r> FromRow<'r, SqliteRow> for Servers { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server")?; - let server = GuildId::from(server_tmp as u64); - let role_past = match row.try_get("role_past") { - Ok(x) => { - let tmp: i64 = x; - if tmp == 0 { - None - } else { - Some(RoleId::from(tmp as u64)) - } - } - _ => None, - }; - let role_current = match row.try_get("role_current") { - Ok(x) => { - let tmp: i64 = x; - RoleId::from(tmp as u64) - } - _ => RoleId::from(0u64), - }; - - let bot_channel_tmp: i64 = row.try_get("bot_channel_id")?; - let bot_channel_id = ChannelId::from(bot_channel_tmp as u64); - - Ok(Self { - server, - wolves_api: row.try_get("wolves_api")?, - role_past, - role_current, - member_past: row.try_get("member_past")?, - member_current: row.try_get("member_current")?, - bot_channel_id, - server_name: row.try_get("server_name")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct Minecraft { - pub discord: GuildId, - pub minecraft: String, -} -impl<'r> FromRow<'r, SqliteRow> for Minecraft { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server_discord")?; - let discord = GuildId::from(server_tmp as u64); - - Ok(Self { - discord, - minecraft: row.try_get("server_minecraft")?, - }) - } -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -pub struct RoleAdder { - pub server: GuildId, - pub role_a: RoleId, - pub role_b: RoleId, - pub role_c: RoleId, -} -impl<'r> FromRow<'r, SqliteRow> for RoleAdder { - fn from_row(row: &'r SqliteRow) -> Result { - let server_tmp: i64 = row.try_get("server")?; - let server = GuildId::from(server_tmp as u64); - - Ok(Self { - server, - role_a: get_role_from_row(row, "role_a"), - role_b: get_role_from_row(row, "role_b"), - role_c: get_role_from_row(row, "role_c"), - }) - } -} - -fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId { - match row.try_get(col) { - Ok(x) => { - let tmp: i64 = x; - RoleId(tmp as u64) - } - _ => RoleId::from(0u64), - } -} - -pub async fn db_init(config: &Config) -> Result, Error> { - let database = format!("{}/{}", &config.home, &config.database); - - let pool = SqlitePoolOptions::new() - .max_connections(5) - .connect_with( - SqliteConnectOptions::from_str(&format!("sqlite://{}", database))? - .foreign_keys(true) - .create_if_missing(true), - ) - .await?; - - // migrations are amazing! - sqlx::migrate!("./db/migrations").run(&pool).await?; - - Ok(pool) -} - -pub async fn get_server_config(db: &Pool, server: &GuildId) -> Option { - sqlx::query_as::<_, Servers>( - r#" - SELECT * - FROM servers - WHERE server = ? - "#, - ) - .bind(*server.as_u64() as i64) - .fetch_one(db) - .await - .ok() -} - -pub async fn get_server_member(db: &Pool, server: &GuildId, member: &guild::Member) -> Result { - sqlx::query_as::<_, ServerMembersWolves>( - r#" - SELECT * - FROM server_members - JOIN wolves USING (id_wolves) - WHERE server = ? AND discord = ? - "#, - ) - .bind(*server.as_u64() as i64) - .bind(*member.user.id.as_u64() as i64) - .fetch_one(db) - .await -} - -pub async fn get_server_config_bulk(db: &Pool) -> Vec { - sqlx::query_as::<_, Servers>( - r#" - SELECT * - FROM servers - "#, - ) - .fetch_all(db) - .await - .unwrap_or_default() -} - pub fn get_now_iso(short: bool) -> String { let now = Utc::now(); if short { @@ -422,257 +128,6 @@ pub fn random_string(len: usize) -> String { thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect() } -pub mod set_roles { - use super::*; - use crate::common::database::DataBase; - pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option], members_changed: &[UserId]) { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Database in TypeMap.").clone() - }; - - let db = db_lock.read().await; - - let Servers { - server, - role_past, - role_current, - .. - } = server; - - let mut roles_set = [0, 0, 0]; - let mut members = vec![]; - - for member in get_server_member_bulk(&db, server).await { - if let Some(x) = member.discord { - members.push(x); - } - } - let mut members_all = members.len(); - - if let Ok(x) = server.members(ctx, None, None).await { - for mut member in x { - // members_changed acts as an override to only deal with teh users in it - if !members_changed.is_empty() && !members_changed.contains(&member.user.id) { - continue; - } - - if members.contains(&member.user.id) { - let mut roles = vec![]; - - if let Some(role) = &role_past { - if !member.roles.contains(role) { - roles_set[0] += 1; - roles.push(role.to_owned()); - } - } - - if !member.roles.contains(role_current) { - roles_set[1] += 1; - roles.push(role_current.to_owned()); - } - - if let Err(e) = member.add_roles(ctx, &roles).await { - println!("{:?}", e); - } - } else { - // old and never - - if let Some(role) = &role_past { - if member.roles.contains(role) { - members_all += 1; - } - } - - if member.roles.contains(role_current) { - roles_set[2] += 1; - // if theya re not a current member and have the role then remove it - if let Err(e) = member.remove_role(ctx, role_current).await { - println!("{:?}", e); - } - } - } - for role in remove_roles.iter().flatten() { - if let Err(e) = member.remove_role(ctx, role).await { - println!("{:?}", e); - } - } - } - } - - set_server_numbers(&db, server, members_all as i64, members.len() as i64).await; - - // small bit of logging to note changes over time - println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.as_u64(), roles_set[0], roles_set[1], roles_set[2]); - } - - pub async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec { - sqlx::query_as::<_, ServerMembersWolves>( - r#" - SELECT * - FROM server_members - JOIN wolves USING (id_wolves) - WHERE ( - server = ? - AND discord IS NOT NULL - AND expiry > ? - ) - "#, - ) - .bind(*server.as_u64() as i64) - .bind(get_now_iso(true)) - .fetch_all(db) - .await - .unwrap_or_default() - } - - async fn set_server_numbers(db: &Pool, server: &GuildId, past: i64, current: i64) { - match sqlx::query_as::<_, Wolves>( - " - UPDATE servers - SET member_past = ?, member_current = ? - WHERE server = ? - ", - ) - .bind(past) - .bind(current) - .bind(*server.as_u64() as i64) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into {}", server.as_u64()); - println!("{:?}", e); - } - } - } -} - -pub mod get_data { - use super::*; - use crate::common::database::DataBase; - use crate::set_roles::update_server; - use std::collections::BTreeMap; - use wolves_oxidised::WolvesUser; - - pub async fn get_wolves(ctx: &Context) { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Database in TypeMap.").clone() - }; - let db = db_lock.read().await; - - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config = config_lock.read().await; - - // set up teh client - let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); - - for server_config in get_server_config_bulk(&db).await { - let Servers { - server, - wolves_api, - .. - } = &server_config; - - let existing_tmp = get_server_member(&db, server).await; - let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::>(); - - // list of users that need to be updated for this server - let mut user_to_update = vec![]; - for user in wolves.get_members(wolves_api).await { - let id = user.member_id.parse::().unwrap_or_default(); - match existing.get(&(id as i64)) { - None => { - // user does not exist already, add everything - add_users_wolves(&db, &user).await; - add_users_server_members(&db, server, &user).await; - } - Some(old) => { - // always update wolves table, in case data has changed - add_users_wolves(&db, &user).await; - if old.expiry != user.expiry { - add_users_server_members(&db, server, &user).await; - - if let Some(discord_id) = old.discord { - user_to_update.push(discord_id); - } - } - } - } - } - - if !user_to_update.is_empty() { - update_server(ctx, &server_config, &[], &user_to_update).await; - } - } - } - - pub async fn get_server_member(db: &Pool, server: &GuildId) -> Vec { - sqlx::query_as::<_, ServerMembersWolves>( - r#" - SELECT * - FROM server_members - JOIN wolves USING (id_wolves) - WHERE ( - server = ? - AND discord IS NOT NULL - ) - "#, - ) - .bind(*server.as_u64() as i64) - .fetch_all(db) - .await - .unwrap_or_default() - } - - async fn add_users_wolves(db: &Pool, user: &WolvesUser) { - // expiry - match sqlx::query_as::<_, Wolves>( - " - INSERT INTO wolves (id_wolves, email) - VALUES ($1, $2) - ON CONFLICT(id_wolves) DO UPDATE SET email = $2 - ", - ) - .bind(&user.member_id) - .bind(&user.contact_email) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into Wolves {:?}", user); - println!("{:?}", e); - } - } - } - async fn add_users_server_members(db: &Pool, server: &GuildId, user: &WolvesUser) { - match sqlx::query_as::<_, ServerMembers>( - " - INSERT OR REPLACE INTO server_members (server, id_wolves, expiry) - VALUES (?1, ?2, ?3) - ", - ) - .bind(*server.as_u64() as i64) - .bind(&user.member_id) - .bind(&user.expiry) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - println!("Failure to insert into ServerMembers {} {:?}", server.as_u64(), user); - println!("{:?}", e); - } - } - } -} - /** For any time ye need to check if a user who calls a command has admin privlages */ @@ -710,153 +165,3 @@ pub async fn is_admin(command: &ApplicationCommandInteraction, ctx: &Context) -> None } } - -/** -loop through all members of server -get a list of folks with mc accounts that are members -and a list that arent members - */ -pub async fn update_server(server_id: &str, db: &Pool, g_id: &GuildId, config: &Config) { - let mut usernames = vec![]; - for member in get_server_member_bulk(db, g_id).await { - if let Some(x) = member.minecraft { - usernames.push((x, true)); - } - if let Some(x) = member.minecraft_uid { - usernames.push((x, false)); - } - } - if !usernames.is_empty() { - whitelist_update(&usernames, server_id, &config.discord_token_minecraft).await; - } -} - -async fn post(url: &str, bearer: &str, data: &T) { - match surf::post(url) - .header("Authorization", bearer) - .header("Content-Type", "application/json") - .header("Accept", "Application/vnd.pterodactyl.v1+json") - .body_json(&data) - { - Ok(req) => { - req.await.ok(); - } - Err(e) => { - dbg!(e); - } - } -} - -#[derive(Deserialize, Serialize, Debug)] -pub struct ServerDetailsResSub { - pub identifier: String, - pub name: String, - pub description: String, - pub is_suspended: bool, -} -#[derive(Deserialize, Serialize, Debug)] -pub struct ServerDetailsRes { - pub attributes: ServerDetailsResSub, -} - -async fn get(url: &str, bearer: &str) -> Option { - match surf::get(url) - .header("Authorization", bearer) - .header("Content-Type", "application/json") - .header("Accept", "Application/vnd.pterodactyl.v1+json") - .recv_json() - .await - { - Ok(res) => Some(res), - Err(e) => { - dbg!(e); - - None - } - } -} - -#[derive(Deserialize, Serialize, Debug)] -struct BodyCommand { - command: String, -} - -#[derive(Deserialize, Serialize, Debug)] -struct BodyDelete { - root: String, - files: Vec, -} - -pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &str) { - println!("Update whitelist for {}", server); - let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); - let bearer = format!("Bearer {token}"); - - for (name, java) in add { - let data = if *java { - BodyCommand { - command: format!("whitelist add {name}"), - } - } else { - BodyCommand { - command: format!("fwhitelist add {name}"), - } - }; - post(&format!("{url_base}/command"), &bearer, &data).await; - } -} - -pub async fn whitelist_wipe(server: &str, token: &str) { - println!("Wiping whitelist for {}", server); - let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); - let bearer = format!("Bearer {token}"); - - // delete whitelist - let deletion = BodyDelete { - root: "/".to_string(), - files: vec!["whitelist.json".to_string()], - }; - post(&format!("{url_base}/files/delete"), &bearer, &deletion).await; - - // recreate teh file, passing in the type here so the compiler knows what type of vec it is - post::>(&format!("{url_base}/files/write?file=%2Fwhitelist.json"), &bearer, &vec![]).await; - - // reload the whitelist - let data = BodyCommand { - command: "whitelist reload".to_string(), - }; - post(&format!("{url_base}/command"), &bearer, &data).await; -} - -pub async fn server_information(server: &str, token: &str) -> Option { - println!("Get server information for {}", server); - let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); - let bearer = format!("Bearer {token}"); - get::(&format!("{url_base}/"), &bearer).await -} - -pub async fn get_minecraft_config(db: &Pool) -> Vec { - sqlx::query_as::<_, Minecraft>( - r#" - SELECT * - FROM minecraft - "#, - ) - .fetch_all(db) - .await - .unwrap_or_default() -} - -pub async fn get_minecraft_config_server(db: &Pool, g_id: GuildId) -> Vec { - sqlx::query_as::<_, Minecraft>( - r#" - SELECT * - FROM minecraft - WHERE server_discord = ?1 - "#, - ) - .bind(*g_id.as_u64() as i64) - .fetch_all(db) - .await - .unwrap_or_default() -} From 8c81fb864a24bf468b421765a639ebeb6d68a6cb Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 18 Feb 2025 22:44:32 +0000 Subject: [PATCH 067/170] fix: slight duplicate removal --- src/common/database.rs | 41 ++++++++++++++++------------------------- 1 file changed, 16 insertions(+), 25 deletions(-) diff --git a/src/common/database.rs b/src/common/database.rs index ace94ec..fb413e2 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -49,30 +49,33 @@ impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves { fn from_row(row: &'r SqliteRow) -> Result { let server_tmp: i64 = row.try_get("server")?; let server = GuildId::from(server_tmp as u64); - let discord = match row.try_get("discord") { - Ok(x) => { - let tmp: i64 = x; - if tmp == 0 { - None - } else { - Some(UserId::from(tmp as u64)) - } - } - _ => None, - }; Ok(Self { server, id_wolves: row.try_get("id_wolves")?, expiry: row.try_get("expiry")?, email: row.try_get("email")?, - discord, + discord: get_discord_from_row(row), minecraft: row.try_get("minecraft")?, minecraft_uid: row.try_get("minecraft_uid")?, }) } } +fn get_discord_from_row(row: &SqliteRow) -> Option { + match row.try_get("discord") { + Ok(x) => { + let tmp: i64 = x; + if tmp == 0 { + None + } else { + Some(UserId::from(tmp as u64)) + } + } + _ => None, + } +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Wolves { pub id_wolves: i64, @@ -83,22 +86,10 @@ pub struct Wolves { impl<'r> FromRow<'r, SqliteRow> for Wolves { fn from_row(row: &'r SqliteRow) -> Result { - let discord = match row.try_get("discord") { - Ok(x) => { - let tmp: i64 = x; - if tmp == 0 { - None - } else { - Some(UserId::from(tmp as u64)) - } - } - _ => None, - }; - Ok(Self { id_wolves: row.try_get("id_wolves")?, email: row.try_get("email")?, - discord, + discord: get_discord_from_row(row), minecraft: row.try_get("minecraft")?, }) } From a8c1cc9cf1c3004f33420cbabad211fed5f6f2cf Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 18 Feb 2025 22:46:58 +0000 Subject: [PATCH 068/170] fix: slight duplicate removal --- src/common/minecraft.rs | 4 ++-- src/common/set_roles.rs | 16 ++++++---------- src/main.rs | 28 ++++++++++++++-------------- 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/src/common/minecraft.rs b/src/common/minecraft.rs index 25bde46..7b27d36 100644 --- a/src/common/minecraft.rs +++ b/src/common/minecraft.rs @@ -102,7 +102,7 @@ struct BodyDelete { } pub async fn whitelist_wipe(server: &str, token: &str) { - let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); + let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); let bearer = format!("Bearer {token}"); // delete whitelist @@ -123,7 +123,7 @@ pub async fn whitelist_wipe(server: &str, token: &str) { } pub async fn server_information(server: &str, token: &str) -> Option { - let url_base = format!("http://panel.games.skynet.ie/api/client/servers/{server}"); + let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); let bearer = format!("Bearer {token}"); get::(&format!("{url_base}/"), &bearer).await } diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index f0a02cb..9ebd39e 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -341,21 +341,17 @@ pub mod committee { } async fn get_committees(db: &Pool) -> Vec { - match sqlx::query_as::<_, Committees>( + sqlx::query_as::<_, Committees>( r#" SELECT * FROM committees "#, ) - .fetch_all(db) - .await - { - Ok(a) => a, - Err(e) => { - dbg!(e); - vec![] - } - } + .fetch_all(db) + .await.unwrap_or_else(|e| { + dbg!(e); + vec![] + }) } async fn get_server_member_discord(db: &Pool, user: &i64) -> Option { diff --git a/src/main.rs b/src/main.rs index e1fe422..8b1b20a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -91,6 +91,20 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } } + // handles role updates + async fn guild_member_update(&self, ctx: Context, _old_data: Option, new_data: Member) { + // get config/db + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + + let db = db_lock.read().await; + + // check if the role changed is part of the oens for this server + on_role_change(&db, &ctx, new_data).await; + } + async fn ready(&self, ctx: Context, ready: Ready) { println!("[Main] {} is connected!", ready.user.name); ctx.set_presence(Some(Activity::playing("with humanity's fate")), OnlineStatus::Online).await; @@ -139,20 +153,6 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } } } - - // handles role updates - async fn guild_member_update(&self, ctx: Context, _old_data: Option, new_data: Member) { - // get config/db - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - - let db = db_lock.read().await; - - // check if the role changed is part of the oens for this server - on_role_change(&db, &ctx, new_data).await; - } } async fn get_committee(db: &Pool, committee: &str) -> Vec { From 6b84f33d2e0e2bec6dd32d8dfa8d66deefdc618c Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 19 Feb 2025 00:17:02 +0000 Subject: [PATCH 069/170] feat: bumped serenity to the latest version Lots of changes to how it runs --- Cargo.lock | 255 +++++++++++++++++-------------------- Cargo.toml | 2 +- src/commands/add_server.rs | 114 ++++++----------- src/commands/link_email.rs | 79 +++++------- src/commands/minecraft.rs | 142 ++++++++------------- src/commands/role_adder.rs | 122 +++++++----------- src/common/database.rs | 8 +- src/common/minecraft.rs | 2 +- src/common/set_roles.rs | 44 +++++-- src/common/wolves.rs | 10 +- src/lib.rs | 16 +-- src/main.rs | 43 ++++--- 12 files changed, 352 insertions(+), 485 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a1c92c1..d0b769f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -111,6 +111,15 @@ version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +dependencies = [ + "serde", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -257,22 +266,6 @@ dependencies = [ "syn 2.0.89", ] -[[package]] -name = "async-tungstenite" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b71b31561643aa8e7df3effe284fa83ab1a840e52294c5f4bd7bfd8b2becbb" -dependencies = [ - "futures-io", - "futures-util", - "log", - "pin-project-lite", - "tokio", - "tokio-rustls 0.23.4", - "tungstenite", - "webpki-roots 0.22.6", -] - [[package]] name = "atoi" version = "2.0.0" @@ -629,6 +622,12 @@ dependencies = [ "serde", ] +[[package]] +name = "data-encoding" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" + [[package]] name = "der" version = "0.7.9" @@ -815,7 +814,7 @@ checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" dependencies = [ "futures-core", "futures-sink", - "spin 0.9.8", + "spin", ] [[package]] @@ -975,6 +974,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1298,7 +1306,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2 0.5.7", "tokio", "tower-service", "tracing", @@ -1518,7 +1526,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.9.8", + "spin", ] [[package]] @@ -1846,15 +1854,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "ordered-float" -version = "2.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" -dependencies = [ - "num-traits", -] - [[package]] name = "parking" version = "2.2.1" @@ -2237,21 +2236,6 @@ dependencies = [ "windows-registry", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.8" @@ -2262,8 +2246,8 @@ dependencies = [ "cfg-if", "getrandom 0.2.15", "libc", - "spin 0.9.8", - "untrusted 0.9.0", + "spin", + "untrusted", "windows-sys 0.52.0", ] @@ -2315,18 +2299,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "rustls" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" -dependencies = [ - "log", - "ring 0.16.20", - "sct", - "webpki", -] - [[package]] name = "rustls" version = "0.21.12" @@ -2334,11 +2306,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", + "ring", "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + [[package]] name = "rustls" version = "0.23.18" @@ -2382,8 +2368,8 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -2392,9 +2378,9 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "ring 0.17.8", + "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -2424,8 +2410,18 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "serde", + "zeroize", ] [[package]] @@ -2476,12 +2472,11 @@ dependencies = [ ] [[package]] -name = "serde-value" -version = "0.7.0" +name = "serde_cow" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +checksum = "1e7bbbec7196bfde255ab54b65e34087c0849629280028238e67ee25d6a4b7da" dependencies = [ - "ordered-float", "serde", ] @@ -2533,45 +2528,35 @@ dependencies = [ [[package]] name = "serenity" -version = "0.11.7" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a7a89cef23483fc9d4caf2df41e6d3928e18aada84c56abd237439d929622c6" +checksum = "3d72ec4323681bf9a3cabe40fd080abc2435859b502a1b5aa9bf693f125bfa76" dependencies = [ + "arrayvec", "async-trait", - "async-tungstenite", - "base64 0.21.7", - "bitflags 1.3.2", + "base64 0.22.1", + "bitflags 2.6.0", "bytes 1.7.1", - "cfg-if", "dashmap", "flate2", "futures", - "mime", + "fxhash", "mime_guess", "parking_lot", "percent-encoding", "reqwest 0.11.27", + "secrecy", "serde", - "serde-value", + "serde_cow", "serde_json", "time 0.3.36", "tokio", + "tokio-tungstenite", "tracing", "typemap_rev", "url", ] -[[package]] -name = "sha-1" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest 0.10.7", -] - [[package]] name = "sha1" version = "0.6.1" @@ -2711,12 +2696,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -3281,17 +3260,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.9", - "tokio", - "webpki", -] - [[package]] name = "tokio-rustls" version = "0.24.1" @@ -3302,6 +3270,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.4", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" @@ -3337,6 +3316,22 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "rustls 0.22.4", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.25.0", + "tungstenite", + "webpki-roots 0.26.8", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -3406,30 +3401,30 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.17.3" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" dependencies = [ - "base64 0.13.1", "byteorder", "bytes 1.7.1", - "http 0.2.12", + "data-encoding", + "http 1.1.0", "httparse", "log", "rand 0.8.5", - "rustls 0.20.9", - "sha-1", + "rustls 0.22.4", + "rustls-pki-types", + "sha1 0.10.6", "thiserror", "url", "utf-8", - "webpki", ] [[package]] name = "typemap_rev" -version = "0.1.5" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5b74f0a24b5454580a79abb6994393b09adf0ab8070f15827cb666255de155" +checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998" [[package]] name = "typenum" @@ -3495,12 +3490,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -3672,31 +3661,21 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" -dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", -] - -[[package]] -name = "webpki-roots" -version = "0.22.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" -dependencies = [ - "webpki", -] - [[package]] name = "webpki-roots" version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "whoami" version = "1.5.2" diff --git a/Cargo.toml b/Cargo.toml index fa0ffdc..d637ae8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ name = "update_minecraft" [dependencies] # discord library -serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } +serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "full"] } # wolves api diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index 8f06527..fdc0f23 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -1,68 +1,53 @@ -use serenity::{ - builder::CreateApplicationCommand, - client::Context, - model::{ - application::interaction::application_command::ApplicationCommandInteraction, - prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, - }, -}; +use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; +use serenity::{builder::CreateCommand, client::Context}; use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers}; use skynet_discord_bot::common::set_roles::normal::update_server; use skynet_discord_bot::common::wolves::cns::get_wolves; use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; -pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { +pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { // check if user has high enough permisssions if let Some(msg) = is_admin(command, ctx).await { return msg; } - let api_key = if let CommandDataOptionValue::String(key) = command - .data - .options - .first() - .expect("Expected user option") - .resolved - .as_ref() - .expect("Expected user object") + let wolves_api = if let Some(CommandDataOption { + value: CommandDataOptionValue::String(key), + .. + }) = command.data.options.first() { - key + key.to_string() } else { return "Please provide a wolves API key".to_string(); }; - let role_current = if let CommandDataOptionValue::Role(role) = command - .data - .options - .get(1) - .expect("Expected role option") - .resolved - .as_ref() - .expect("Expected role object") + let role_current = if let Some(CommandDataOption { + value: CommandDataOptionValue::Role(role), + .. + }) = command.data.options.get(1) { - role.id.to_owned() + role.to_owned() } else { return "Please provide a valid role for ``Role Current``".to_string(); }; - let mut role_past = None; - if let Some(x) = command.data.options.get(5) { - if let Some(CommandDataOptionValue::Role(role)) = &x.resolved { - role_past = Some(role.id.to_owned()); - } + let role_past = if let Some(CommandDataOption { + value: CommandDataOptionValue::Role(role), + .. + }) = command.data.options.get(5) + { + Some(role.to_owned()) + } else { + None }; - let bot_channel_id = if let CommandDataOptionValue::Channel(channel) = command - .data - .options - .get(2) - .expect("Expected channel option") - .resolved - .as_ref() - .expect("Expected channel object") + let bot_channel_id = if let Some(CommandDataOption { + value: CommandDataOptionValue::Channel(channel), + .. + }) = command.data.options.get(2) { - channel.id.to_owned() + channel.to_owned() } else { return "Please provide a valid channel for ``Bot Channel``".to_string(); }; @@ -75,7 +60,7 @@ pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> Stri let server_data = Servers { server: command.guild_id.unwrap_or_default(), - wolves_api: api_key.to_owned(), + wolves_api, role_past, role_current, member_past: 0, @@ -95,43 +80,18 @@ pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> Stri "Added/Updated server info".to_string() } -pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command - .name("add") +pub fn register() -> CreateCommand { + CreateCommand::new("add") .description("Enable the bot for this discord") - .create_option(|option| { - option - .name("api_key") - .description("UL Wolves API Key") - .kind(CommandOptionType::String) - .required(true) - }) - .create_option(|option| { - option - .name("role_current") - .description("Role for Current members") - .kind(CommandOptionType::Role) - .required(true) - }) - .create_option(|option| { - option - .name("bot_channel") - .description("Safe space for folks to use the bot commands.") - .kind(CommandOptionType::Channel) - .required(true) - }) - .create_option(|option| { - option - .name("role_past") - .description("Role for Past members") - .kind(CommandOptionType::Role) - .required(false) - }) + .add_option(CreateCommandOption::new(CommandOptionType::String, "api_key", "UL Wolves API Key").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_current", "Role for Current members").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Channel, "bot_channel", "Safe space for folks to use the bot commands.").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_past", "Role for Past members").required(false)) } async fn add_server(db: &Pool, ctx: &Context, server: &Servers) -> Result, Error> { let existing = get_server_config(db, &server.server).await; - let role_past = server.role_past.map(|x| *x.as_u64() as i64); + let role_past = server.role_past.map(|x| x.get() as i64); let insert = sqlx::query_as::<_, Servers>( " @@ -139,11 +99,11 @@ async fn add_server(db: &Pool, ctx: &Context, server: &Servers) -> Resul VALUES (?1, ?2, ?3, ?4, ?5) ", ) - .bind(*server.server.as_u64() as i64) + .bind(server.server.get() as i64) .bind(&server.wolves_api) .bind(role_past) - .bind(*server.role_current.as_u64() as i64) - .bind(*server.bot_channel_id.as_u64() as i64) + .bind(server.role_current.get() as i64) + .bind(server.bot_channel_id.get() as i64) .fetch_optional(db) .await; diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index a76769f..750e0ee 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -4,15 +4,7 @@ use lettre::{ Message, SmtpTransport, Transport, }; use maud::html; -use serenity::{ - builder::CreateApplicationCommand, - client::Context, - model::{ - application::interaction::application_command::ApplicationCommandInteraction, - id::UserId, - prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, - }, -}; +use serenity::{builder::CreateCommand, client::Context, model::id::UserId}; use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify}; use skynet_discord_bot::{get_now_iso, random_string, Config}; use sqlx::{Pool, Sqlite}; @@ -20,8 +12,9 @@ use sqlx::{Pool, Sqlite}; pub mod link { use super::*; use serde::{Deserialize, Serialize}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}; - pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() @@ -44,16 +37,11 @@ pub mod link { return "Linking already in process, please check email.".to_string(); } - let option = command - .data - .options - .first() - .expect("Expected email option") - .resolved - .as_ref() - .expect("Expected email object"); - - let email = if let CommandDataOptionValue::String(email) = option { + let email = if let Some(CommandDataOption { + value: CommandDataOptionValue::String(email), + .. + }) = command.data.options.first() + { email.trim() } else { return "Please provide a valid user".to_string(); @@ -115,11 +103,10 @@ pub mod link { format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email) } - pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command - .name("link_wolves") + pub fn register() -> CreateCommand { + CreateCommand::new("link_wolves") .description("Set Wolves Email") - .create_option(|option| option.name("email").description("UL Wolves Email").kind(CommandOptionType::String).required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::String, "email", "UL Wolves Email").required(true)) } pub async fn get_server_member_discord(db: &Pool, user: &UserId) -> Option { @@ -130,7 +117,7 @@ pub mod link { WHERE discord = ? "#, ) - .bind(*user.as_u64() as i64) + .bind(user.get() as i64) .fetch_one(db) .await .ok() @@ -242,7 +229,7 @@ pub mod link { WHERE discord = ? "#, ) - .bind(*user.as_u64() as i64) + .bind(user.get() as i64) .fetch_one(db) .await .ok() @@ -256,7 +243,7 @@ pub mod link { ", ) .bind(record.email.to_owned()) - .bind(*user.as_u64() as i64) + .bind(user.get() as i64) .bind(auth.to_owned()) .bind(get_now_iso(false)) .fetch_optional(db) @@ -294,12 +281,13 @@ pub mod link { pub mod verify { use super::*; use crate::commands::link_email::link::{db_pending_clear_expired, get_verify_from_db}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; use serenity::model::user::User; use skynet_discord_bot::common::database::get_server_config; use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; use sqlx::Error; - pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() @@ -313,16 +301,11 @@ pub mod verify { return "Please use /link_wolves first".to_string(); }; - let option = command - .data - .options - .first() - .expect("Expected code option") - .resolved - .as_ref() - .expect("Expected code object"); - - let code = if let CommandDataOptionValue::String(code) = option { + let code = if let Some(CommandDataOption { + value: CommandDataOptionValue::String(code), + .. + }) = command.data.options.first() + { code } else { return "Please provide a verification code".to_string(); @@ -354,14 +337,10 @@ pub mod verify { "Failed to verify".to_string() } - pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command.name("verify").description("Verify Wolves Email").create_option(|option| { - option - .name("code") - .description("Code from verification email") - .kind(CommandOptionType::String) - .required(true) - }) + pub fn register() -> CreateCommand { + CreateCommand::new("verify") + .description("Verify Wolves Email") + .add_option(CreateCommandOption::new(CommandOptionType::String, "code", "Code from verification email").required(true)) } async fn db_pending_clear_successful(pool: &Pool, user: &UserId) -> Result, Error> { @@ -372,7 +351,7 @@ pub mod verify { WHERE discord = ? "#, ) - .bind(*user.as_u64() as i64) + .bind(user.get() as i64) .fetch_optional(pool) .await } @@ -385,7 +364,7 @@ pub mod verify { WHERE email = ? ", ) - .bind(*discord.as_u64() as i64) + .bind(discord.get() as i64) .bind(email) .fetch_optional(db) .await @@ -394,7 +373,7 @@ pub mod verify { async fn set_server_roles(db: &Pool, discord: &User, ctx: &Context) { if let Ok(servers) = get_servers(db, &discord.id).await { for server in servers { - if let Ok(mut member) = server.server.member(&ctx.http, &discord.id).await { + if let Ok(member) = server.server.member(&ctx.http, &discord.id).await { if let Some(config) = get_server_config(db, &server.server).await { let Servers { role_past, @@ -432,7 +411,7 @@ pub mod verify { WHERE discord = ? ", ) - .bind(*discord.as_u64() as i64) + .bind(discord.get() as i64) .fetch_all(db) .await } diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 45c4643..54bf35c 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -1,11 +1,4 @@ -use serenity::{ - builder::CreateApplicationCommand, - client::Context, - model::{ - application::interaction::application_command::ApplicationCommandInteraction, - prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, - }, -}; +use serenity::{builder::CreateCommand, client::Context}; use skynet_discord_bot::common::database::DataBase; use sqlx::{Pool, Sqlite}; @@ -16,33 +9,21 @@ pub(crate) mod user { use super::*; use crate::commands::link_email::link::get_server_member_discord; use serde::{Deserialize, Serialize}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; use serenity::model::id::UserId; use skynet_discord_bot::common::database::Wolves; use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft}; use skynet_discord_bot::Config; use sqlx::Error; - pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command - .name("link_minecraft") + pub fn register() -> CreateCommand { + CreateCommand::new("link_minecraft") .description("Link your minecraft account") - .create_option(|option| { - option - .name("minecraft_username") - .description("Your Minecraft username") - .kind(CommandOptionType::String) - .required(true) - }) - .create_option(|option| { - option - .name("bedrock_account") - .description("Is this a Bedrock account?") - .kind(CommandOptionType::Boolean) - .required(false) - }) + .add_option(CreateCommandOption::new(CommandOptionType::String, "minecraft_username", "Your Minecraft username").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Boolean, "bedrock_account", "Is this a Bedrock account?").required(false)) } - pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() @@ -60,14 +41,10 @@ pub(crate) mod user { return "Not linked with wolves, please use ``/link_wolves`` with your wolves email.".to_string(); } - let username = if let CommandDataOptionValue::String(username) = command - .data - .options - .first() - .expect("Expected username option") - .resolved - .as_ref() - .expect("Expected username object") + let username = if let Some(CommandDataOption { + value: CommandDataOptionValue::String(username), + .. + }) = command.data.options.first() { username.trim() } else { @@ -75,12 +52,15 @@ pub(crate) mod user { }; // this is always true unless they state its not - let mut java = true; - if let Some(x) = command.data.options.get(1) { - if let Some(CommandDataOptionValue::Boolean(z)) = x.to_owned().resolved { - java = !z; - } - } + let java = if let Some(CommandDataOption { + value: CommandDataOptionValue::Boolean(z), + .. + }) = command.data.options.get(1) + { + !z + } else { + true + }; let username_mc; if java { @@ -130,7 +110,7 @@ pub(crate) mod user { WHERE discord = ?1; ", ) - .bind(*user.as_u64() as i64) + .bind(user.get() as i64) .bind(minecraft) .fetch_optional(db) .await @@ -175,7 +155,7 @@ pub(crate) mod user { WHERE discord = ?1; ", ) - .bind(*user.as_u64() as i64) + .bind(user.get() as i64) .bind(minecraft) .fetch_optional(db) .await @@ -194,7 +174,7 @@ pub(crate) mod user { ) sub on minecraft.server_discord = sub.server ", ) - .bind(*discord.as_u64() as i64) + .bind(discord.get() as i64) .fetch_all(db) .await } @@ -205,6 +185,7 @@ pub(crate) mod server { use super::*; pub(crate) mod add { + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}; use serenity::model::id::GuildId; use sqlx::Error; // this is to managfe the server side of commands related to minecraft @@ -213,17 +194,13 @@ pub(crate) mod server { use skynet_discord_bot::common::minecraft::Minecraft; use skynet_discord_bot::{is_admin, Config}; - pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command.name("minecraft_add").description("Add a minecraft server").create_option(|option| { - option - .name("server_id") - .description("ID of the Minecraft server hosted by the Computer Society") - .kind(CommandOptionType::String) - .required(true) - }) + pub fn register() -> CreateCommand { + CreateCommand::new("minecraft_add").description("Add a minecraft server").add_option( + CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society").required(true), + ) } - pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { // check if user has high enough permisssions if let Some(msg) = is_admin(command, ctx).await { return msg; @@ -233,16 +210,12 @@ pub(crate) mod server { Some(x) => x, }; - let server_minecraft = if let CommandDataOptionValue::String(id) = command - .data - .options - .first() - .expect("Expected server_id option") - .resolved - .as_ref() - .expect("Expected server_id object") + let server_minecraft = if let Some(CommandDataOption { + value: CommandDataOptionValue::String(id), + .. + }) = command.data.options.first() { - id.to_owned() + id.to_string() } else { return String::from("Expected Server ID"); }; @@ -279,7 +252,7 @@ pub(crate) mod server { VALUES (?1, ?2) ", ) - .bind(*discord.as_u64() as i64) + .bind(discord.get() as i64) .bind(minecraft) .fetch_optional(db) .await @@ -287,18 +260,18 @@ pub(crate) mod server { } pub(crate) mod list { - use serenity::builder::CreateApplicationCommand; + use serenity::all::CommandInteraction; + use serenity::builder::CreateCommand; use serenity::client::Context; - use serenity::model::prelude::application_command::ApplicationCommandInteraction; use skynet_discord_bot::common::database::DataBase; use skynet_discord_bot::common::minecraft::{get_minecraft_config_server, server_information}; use skynet_discord_bot::{is_admin, Config}; - pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command.name("minecraft_list").description("List your minecraft servers") + pub fn register() -> CreateCommand { + CreateCommand::new("minecraft_list").description("List your minecraft servers") } - pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { if let Some(msg) = is_admin(command, ctx).await { return msg; } @@ -348,27 +321,22 @@ pub(crate) mod server { } pub(crate) mod delete { - use serenity::builder::CreateApplicationCommand; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; + use serenity::builder::CreateCommand; use serenity::client::Context; - use serenity::model::application::command::CommandOptionType; use serenity::model::id::GuildId; - use serenity::model::prelude::application_command::{ApplicationCommandInteraction, CommandDataOptionValue}; use skynet_discord_bot::common::database::DataBase; use skynet_discord_bot::common::minecraft::Minecraft; use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; - pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command.name("minecraft_delete").description("Delete a minecraft server").create_option(|option| { - option - .name("server_id") - .description("ID of the Minecraft server hosted by the Computer Society") - .kind(CommandOptionType::String) - .required(true) - }) + pub fn register() -> CreateCommand { + CreateCommand::new("minecraft_delete").description("Delete a minecraft server").add_option( + CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society").required(true), + ) } - pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { // check if user has high enough permisssions if let Some(msg) = is_admin(command, ctx).await { return msg; @@ -378,16 +346,12 @@ pub(crate) mod server { Some(x) => x, }; - let server_minecraft = if let CommandDataOptionValue::String(id) = command - .data - .options - .first() - .expect("Expected server_id option") - .resolved - .as_ref() - .expect("Expected server_id object") + let server_minecraft = if let Some(CommandDataOption { + value: CommandDataOptionValue::String(id), + .. + }) = command.data.options.first() { - id.to_owned() + id.to_string() } else { return String::from("Expected Server ID"); }; @@ -418,7 +382,7 @@ pub(crate) mod server { WHERE server_discord = ?1 AND server_minecraft = ?2 ", ) - .bind(*discord.as_u64() as i64) + .bind(discord.get() as i64) .bind(minecraft) .fetch_optional(db) .await diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index d45d445..182eeb9 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -1,11 +1,4 @@ -use serenity::{ - builder::CreateApplicationCommand, - client::Context, - model::{ - application::interaction::application_command::ApplicationCommandInteraction, - prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, - }, -}; +use serenity::client::Context; use skynet_discord_bot::common::database::{DataBase, RoleAdder}; use skynet_discord_bot::is_admin; @@ -13,51 +6,40 @@ use sqlx::{Error, Pool, Sqlite}; pub mod edit { use super::*; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}; - pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { // check if user has high enough permisssions if let Some(msg) = is_admin(command, ctx).await { return msg; } - let role_a = if let CommandDataOptionValue::Role(role) = command - .data - .options - .first() - .expect("Expected role option") - .resolved - .as_ref() - .expect("Expected role object") + let role_a = if let Some(CommandDataOption { + value: CommandDataOptionValue::Role(role), + .. + }) = command.data.options.first() { - role.id.to_owned() + role.to_owned() } else { return "Please provide a valid role for ``Role Current``".to_string(); }; - let role_b = if let CommandDataOptionValue::Role(role) = command - .data - .options - .get(1) - .expect("Expected role option") - .resolved - .as_ref() - .expect("Expected role object") + let role_b = if let Some(CommandDataOption { + value: CommandDataOptionValue::Role(role), + .. + }) = command.data.options.get(1) { - role.id.to_owned() + role.to_owned() } else { return "Please provide a valid role for ``Role Current``".to_string(); }; - let role_c = if let CommandDataOptionValue::Role(role) = command - .data - .options - .get(2) - .expect("Expected role option") - .resolved - .as_ref() - .expect("Expected role object") + let role_c = if let Some(CommandDataOption { + value: CommandDataOptionValue::Role(role), + .. + }) = command.data.options.get(2) { - role.id.to_owned() + role.to_owned() } else { return "Please provide a valid role for ``Role Current``".to_string(); }; @@ -70,14 +52,15 @@ pub mod edit { return "Role C cannot be same as A or B".to_string(); } - let mut delete = false; - - if let Some(x) = command.data.options.get(3) { - let tmp = x.to_owned(); - if let Some(CommandDataOptionValue::Boolean(z)) = tmp.resolved { - delete = z; - } - } + let delete = if let Some(CommandDataOption { + value: CommandDataOptionValue::Boolean(z), + .. + }) = command.data.options.get(3) + { + *z + } else { + false + }; let db_lock = { let data_read = ctx.data.read().await; @@ -124,32 +107,13 @@ pub mod edit { } } - pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { - command - .name("roles_adder") + pub fn register() -> CreateCommand { + CreateCommand::new("roles_adder") .description("Combine roles together to an new one") - .create_option(|option| { - option - .name("role_a") - .description("A role you want to add to Role B") - .kind(CommandOptionType::Role) - .required(true) - }) - .create_option(|option| { - option - .name("role_b") - .description("A role you want to add to Role A") - .kind(CommandOptionType::Role) - .required(true) - }) - .create_option(|option| option.name("role_c").description("Sum of A and B").kind(CommandOptionType::Role).required(true)) - .create_option(|option| { - option - .name("delete") - .description("Delete this entry.") - .kind(CommandOptionType::Boolean) - .required(false) - }) + .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_a", "A role you want to add to Role B").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_b", "A role you want to add to Role A").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_c", "Sum of A and B").required(true)) + .add_option(CreateCommandOption::new(CommandOptionType::Boolean, "delete", "Delete this entry.").required(false)) } async fn add_server(db: &Pool, server: &RoleAdder, delete: bool) -> Result, Error> { @@ -160,10 +124,10 @@ pub mod edit { WHERE server = ?1 AND role_a = ?2 AND role_b = ?3 AND role_c = ?4 ", ) - .bind(*server.server.as_u64() as i64) - .bind(*server.role_a.as_u64() as i64) - .bind(*server.role_b.as_u64() as i64) - .bind(*server.role_c.as_u64() as i64) + .bind(server.server.get() as i64) + .bind(server.role_a.get() as i64) + .bind(server.role_b.get() as i64) + .bind(server.role_c.get() as i64) .fetch_optional(db) .await } else { @@ -173,10 +137,10 @@ pub mod edit { VALUES (?1, ?2, ?3, ?4) ", ) - .bind(*server.server.as_u64() as i64) - .bind(*server.role_a.as_u64() as i64) - .bind(*server.role_b.as_u64() as i64) - .bind(*server.role_c.as_u64() as i64) + .bind(server.server.get() as i64) + .bind(server.role_a.get() as i64) + .bind(server.role_b.get() as i64) + .bind(server.role_c.get() as i64) .fetch_optional(db) .await } @@ -192,7 +156,7 @@ pub mod tools { use skynet_discord_bot::common::database::RoleAdder; use sqlx::{Pool, Sqlite}; - pub async fn on_role_change(db: &Pool, ctx: &Context, mut new_data: Member) { + pub async fn on_role_change(db: &Pool, ctx: &Context, new_data: Member) { // check if the role changed is part of the oens for this server if let Ok(role_adders) = sqlx::query_as::<_, RoleAdder>( r#" @@ -201,7 +165,7 @@ pub mod tools { WHERE server = ? "#, ) - .bind(*new_data.guild_id.as_u64() as i64) + .bind(new_data.guild_id.get() as i64) .fetch_all(db) .await { diff --git a/src/common/database.rs b/src/common/database.rs index fb413e2..596b411 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -194,7 +194,7 @@ fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId { match row.try_get(col) { Ok(x) => { let tmp: i64 = x; - RoleId(tmp as u64) + RoleId::new(tmp as u64) } _ => RoleId::from(0u64), } @@ -226,7 +226,7 @@ pub async fn get_server_config(db: &Pool, server: &GuildId) -> Option, server: &GuildId, member: &gui WHERE server = ? AND discord = ? "#, ) - .bind(*server.as_u64() as i64) - .bind(*member.user.id.as_u64() as i64) + .bind(server.get() as i64) + .bind(member.user.id.get() as i64) .fetch_one(db) .await } diff --git a/src/common/minecraft.rs b/src/common/minecraft.rs index 7b27d36..4faa8ed 100644 --- a/src/common/minecraft.rs +++ b/src/common/minecraft.rs @@ -148,7 +148,7 @@ pub async fn get_minecraft_config_server(db: &Pool, g_id: GuildId) -> Ve WHERE server_discord = ?1 "#, ) - .bind(*g_id.as_u64() as i64) + .bind(g_id.get() as i64) .fetch_all(db) .await .unwrap_or_default() diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 9ebd39e..d47313d 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -31,7 +31,7 @@ pub mod normal { let mut members_all = members.len(); if let Ok(x) = server.members(ctx, None, None).await { - for mut member in x { + for member in x { // members_changed acts as an override to only deal with teh users in it if !members_changed.is_empty() && !members_changed.contains(&member.user.id) { continue; @@ -83,7 +83,7 @@ pub mod normal { set_server_numbers(&db, server, members_all as i64, members.len() as i64).await; // small bit of logging to note changes over time - println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.as_u64(), roles_set[0], roles_set[1], roles_set[2]); + println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.get(), roles_set[0], roles_set[1], roles_set[2]); } pub async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec { @@ -99,7 +99,7 @@ pub mod normal { ) "#, ) - .bind(*server.as_u64() as i64) + .bind(server.get() as i64) .bind(get_now_iso(true)) .fetch_all(db) .await @@ -116,13 +116,13 @@ pub mod normal { ) .bind(past) .bind(current) - .bind(*server.as_u64() as i64) + .bind(server.get() as i64) .fetch_optional(db) .await { Ok(_) => {} Err(e) => { - println!("Failure to insert into {}", server.as_u64()); + println!("Failure to insert into {}", server.get()); println!("{:?}", e); } } @@ -134,6 +134,8 @@ pub mod committee { use crate::common::database::{DataBase, Wolves}; use crate::common::wolves::committees::Committees; use crate::Config; + use serenity::all::EditRole; + use serenity::builder::CreateChannel; use serenity::client::Context; use serenity::model::channel::ChannelType; use serenity::model::guild::Member; @@ -170,9 +172,16 @@ pub mod committee { */ pub async fn update_committees(db: &Pool, ctx: &Context, config: &Config, members: &mut Vec) { let server = config.committee_server; - let committee_member = config.committee_role; + let committee_member = RoleId::new(1226602779968274573); let committees = get_committees(db).await; - let categories = vec![config.committee_category, ChannelId(1341457244973305927), ChannelId(1341457509717639279)]; + let categories = vec![ + // C&S Chats 1 + ChannelId::new(1226606560973815839), + // C&S Chats 2 + ChannelId::new(1341457244973305927), + // C&S Chats 3 + ChannelId::new(1341457509717639279), + ]; // information about the server let roles = server.roles(&ctx).await.unwrap_or_default(); @@ -216,7 +225,10 @@ pub mod committee { Some(x) => Some(x.to_owned()), None => { // create teh role if it does not exist - match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name_full)).await { + match server + .create_role(&ctx, EditRole::new().name(&committee.name_full).hoist(false).mentionable(true)) + .await + { Ok(x) => Some(x), Err(_) => None, } @@ -226,7 +238,12 @@ pub mod committee { // create teh channel if it does nto exist if !channels_name.contains_key(&committee.name_profile) { match server - .create_channel(&ctx, |c| c.name(&committee.name_profile).kind(ChannelType::Text).category(categories[category_index])) + .create_channel( + &ctx, + CreateChannel::new(&committee.name_profile) + .kind(ChannelType::Text) + .category(categories[category_index]), + ) .await { Ok(x) => { @@ -277,7 +294,7 @@ pub mod committee { Some(x) => { let mut tmp = x.to_owned(); if !tmp.is_empty() { - tmp.push(RoleId(1226602779968274573)); + tmp.push(committee_member); } tmp } @@ -309,7 +326,7 @@ pub mod committee { // these roles are flavor roles, only there to make folks mentionable member.add_roles(&ctx, &roles_add).await.unwrap_or_default(); } else { - member.remove_roles(&ctx, &[RoleId(1226602779968274573)]).await.unwrap_or_default(); + member.remove_roles(&ctx, &[committee_member]).await.unwrap_or_default(); } } @@ -347,8 +364,9 @@ pub mod committee { FROM committees "#, ) - .fetch_all(db) - .await.unwrap_or_else(|e| { + .fetch_all(db) + .await + .unwrap_or_else(|e| { dbg!(e); vec![] }) diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 9141983..ea64699 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -144,13 +144,13 @@ pub mod cns { ", ) .bind(name) - .bind(*server.as_u64() as i64) + .bind(server.get() as i64) .fetch_optional(db) .await { Ok(_) => {} Err(e) => { - println!("Failure to set server name {}", server.as_u64()); + println!("Failure to set server name {}", server.get()); println!("{:?}", e); } } @@ -168,7 +168,7 @@ pub mod cns { ) "#, ) - .bind(*server.as_u64() as i64) + .bind(server.get() as i64) .fetch_all(db) .await .unwrap_or_default() @@ -181,7 +181,7 @@ pub mod cns { VALUES (?1, ?2, ?3) ", ) - .bind(*server.as_u64() as i64) + .bind(server.get() as i64) .bind(&user.member_id) .bind(&user.expiry) .fetch_optional(db) @@ -189,7 +189,7 @@ pub mod cns { { Ok(_) => {} Err(e) => { - println!("Failure to insert into ServerMembers {} {:?}", server.as_u64(), user); + println!("Failure to insert into ServerMembers {} {:?}", server.get(), user); println!("{:?}", e); } } diff --git a/src/lib.rs b/src/lib.rs index 5611a21..ba86483 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,9 +3,9 @@ pub mod common; use chrono::{Datelike, SecondsFormat, Utc}; use dotenvy::dotenv; use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use serenity::all::CommandInteraction; use serenity::client::Context; use serenity::model::id::{ChannelId, GuildId, RoleId}; -use serenity::model::prelude::application_command::ApplicationCommandInteraction; use serenity::prelude::TypeMapKey; use std::{env, sync::Arc}; use tokio::sync::RwLock; @@ -57,9 +57,9 @@ pub fn get_config() -> Config { mail_pass: "".to_string(), wolves_url: "".to_string(), wolves_api: "".to_string(), - committee_server: GuildId(0), - committee_role: RoleId(0), - committee_category: ChannelId(0), + committee_server: GuildId::new(0), + committee_role: RoleId::new(0), + committee_category: ChannelId::new(0), }; if let Ok(x) = env::var("DATABASE_HOME") { @@ -98,17 +98,17 @@ pub fn get_config() -> Config { if let Ok(x) = env::var("COMMITTEE_DISCORD") { if let Ok(x) = x.trim().parse::() { - config.committee_server = GuildId(x); + config.committee_server = GuildId::new(x); } } if let Ok(x) = env::var("COMMITTEE_DISCORD") { if let Ok(x) = x.trim().parse::() { - config.committee_role = RoleId(x); + config.committee_role = RoleId::new(x); } } if let Ok(x) = env::var("COMMITTEE_CATEGORY") { if let Ok(x) = x.trim().parse::() { - config.committee_category = ChannelId(x); + config.committee_category = ChannelId::new(x); } } @@ -131,7 +131,7 @@ pub fn random_string(len: usize) -> String { /** For any time ye need to check if a user who calls a command has admin privlages */ -pub async fn is_admin(command: &ApplicationCommandInteraction, ctx: &Context) -> Option { +pub async fn is_admin(command: &CommandInteraction, ctx: &Context) -> Option { let mut admin = false; let g_id = match command.guild_id { diff --git a/src/main.rs b/src/main.rs index 8b1b20a..36316d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,13 @@ pub mod commands; use crate::commands::role_adder::tools::on_role_change; +use serenity::all::{ActivityData, Command, CreateMessage, EditInteractionResponse, GuildMemberUpdateEvent, Interaction}; use serenity::model::guild::Member; use serenity::{ async_trait, client::{Context, EventHandler}, model::{ - application::{command::Command, interaction::Interaction}, gateway::{GatewayIntents, Ready}, - prelude::Activity, user::OnlineStatus, }, Client, @@ -26,7 +25,7 @@ struct Handler; #[async_trait] impl EventHandler for Handler { // handles previously linked accounts joining the server - async fn guild_member_addition(&self, ctx: Context, mut new_member: Member) { + async fn guild_member_addition(&self, ctx: Context, new_member: Member) { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() @@ -84,7 +83,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use &config_server.bot_channel_id ); - if let Err(err) = new_member.user.direct_message(&ctx, |m| m.content(&msg)).await { + if let Err(err) = new_member.user.direct_message(&ctx, CreateMessage::new().content(&msg)).await { dbg!(err); } } @@ -92,7 +91,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } // handles role updates - async fn guild_member_update(&self, ctx: Context, _old_data: Option, new_data: Member) { + async fn guild_member_update(&self, ctx: Context, _old_data: Option, new_data: Option, _: GuildMemberUpdateEvent) { // get config/db let db_lock = { let data_read = ctx.data.read().await; @@ -102,24 +101,28 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use let db = db_lock.read().await; // check if the role changed is part of the oens for this server - on_role_change(&db, &ctx, new_data).await; + if let Some(x) = new_data { + on_role_change(&db, &ctx, x).await; + } } async fn ready(&self, ctx: Context, ready: Ready) { println!("[Main] {} is connected!", ready.user.name); - ctx.set_presence(Some(Activity::playing("with humanity's fate")), OnlineStatus::Online).await; + ctx.set_presence(Some(ActivityData::playing("with humanity's fate")), OnlineStatus::Online); - match Command::set_global_application_commands(&ctx.http, |commands| { - commands - .create_application_command(|command| commands::add_server::register(command)) - .create_application_command(|command| commands::role_adder::edit::register(command)) - .create_application_command(|command| commands::link_email::link::register(command)) - .create_application_command(|command| commands::link_email::verify::register(command)) - .create_application_command(|command| commands::minecraft::server::add::register(command)) - .create_application_command(|command| commands::minecraft::server::list::register(command)) - .create_application_command(|command| commands::minecraft::server::delete::register(command)) - .create_application_command(|command| commands::minecraft::user::add::register(command)) - }) + match Command::set_global_commands( + &ctx.http, + vec![ + commands::add_server::register(), + commands::role_adder::edit::register(), + commands::link_email::link::register(), + commands::link_email::verify::register(), + commands::minecraft::server::add::register(), + commands::minecraft::server::list::register(), + commands::minecraft::server::delete::register(), + commands::minecraft::user::add::register(), + ], + ) .await { Ok(_) => {} @@ -130,7 +133,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } async fn interaction_create(&self, ctx: Context, interaction: Interaction) { - if let Interaction::ApplicationCommand(command) = interaction { + if let Interaction::Command(command) = interaction { let _ = command.defer_ephemeral(&ctx.http).await; //println!("Received command interaction: {:#?}", command); @@ -148,7 +151,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use _ => "not implemented :(".to_string(), }; - if let Err(why) = command.edit_original_interaction_response(&ctx.http, |response| response.content(content)).await { + if let Err(why) = command.edit_response(&ctx.http, EditInteractionResponse::new().content(content)).await { println!("Cannot respond to slash command: {}", why); } } From 86f71f0fa445bba48b2c3781152c955e0236ef19 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 19 Feb 2025 00:26:54 +0000 Subject: [PATCH 070/170] feat: updated more dependencies --- Cargo.lock | 689 ++++++++++++++++++++++++++++++++++++++++------------- Cargo.toml | 16 +- src/lib.rs | 4 +- 3 files changed, 533 insertions(+), 176 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d0b769f..e4adde8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,10 +78,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", - "getrandom 0.2.15", "once_cell", "version_check", - "zerocopy", + "zerocopy 0.7.35", ] [[package]] @@ -404,9 +403,9 @@ checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" -version = "1.1.19" +version = "1.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d74707dde2ba56f86ae90effb3b43ddd369504387e718014de010cec7959800" +checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" dependencies = [ "shlex", ] @@ -419,9 +418,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", @@ -431,6 +430,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "chumsky" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" +dependencies = [ + "hashbrown 0.14.5", + "stacker", +] + [[package]] name = "cipher" version = "0.2.5" @@ -588,7 +597,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "socket2 0.5.7", + "socket2", "windows-sys 0.52.0", ] @@ -615,7 +624,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -676,6 +685,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -693,9 +713,9 @@ dependencies = [ [[package]] name = "email-encoding" -version = "0.2.1" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a87260449b06739ee78d6281c68d2a0ff3e3af64a78df63d3a1aeb3c06997c8a" +checksum = "ea3d894bbbab314476b265f9b2d46bf24b123a36dd0e96b06a1b49545b9d9dcc" dependencies = [ "base64 0.22.1", "memchr", @@ -823,6 +843,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" + [[package]] name = "foreign-types" version = "0.3.2" @@ -1015,6 +1041,18 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", +] + [[package]] name = "ghash" version = "0.3.1" @@ -1092,22 +1130,30 @@ dependencies = [ ] [[package]] -name = "hashlink" -version = "0.8.4" +name = "hashbrown" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" dependencies = [ - "hashbrown", + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.2", ] [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -1176,13 +1222,13 @@ dependencies = [ [[package]] name = "hostname" -version = "0.3.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" dependencies = [ + "cfg-if", "libc", - "match_cfg", - "winapi", + "windows", ] [[package]] @@ -1306,7 +1352,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.7", + "socket2", "tokio", "tower-service", "tracing", @@ -1393,7 +1439,7 @@ dependencies = [ "http-body 1.0.1", "hyper 1.5.1", "pin-project-lite", - "socket2 0.5.7", + "socket2", "tokio", "tower-service", "tracing", @@ -1423,13 +1469,121 @@ dependencies = [ ] [[package]] -name = "idna" -version = "0.3.0" +name = "icu_collections" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", ] [[package]] @@ -1442,6 +1596,27 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.5.0" @@ -1449,7 +1624,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.5", ] [[package]] @@ -1531,25 +1706,27 @@ dependencies = [ [[package]] name = "lettre" -version = "0.10.4" +version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bd09637ae3ec7bd605b8e135e757980b3968430ff2b1a4a94fb7769e50166d" +checksum = "504273f23d9f3d2fd09c6e5fa94fafd5177ae6b83ed0df1f3b0e180052c076a9" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", + "chumsky", "email-encoding", "email_address", - "fastrand 1.9.0", + "fastrand 2.1.1", "futures-util", "hostname", "httpdate", - "idna 0.3.0", + "idna 1.0.3", "mime", "native-tls", "nom", - "once_cell", + "percent-encoding", "quoted_printable", - "socket2 0.4.10", + "socket2", "tokio", + "url", ] [[package]] @@ -1576,9 +1753,9 @@ dependencies = [ [[package]] name = "libsqlite3-sys" -version = "0.27.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ "cc", "pkg-config", @@ -1603,6 +1780,12 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "lock_api" version = "0.4.12" @@ -1622,17 +1805,11 @@ dependencies = [ "value-bag", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "maud" -version = "0.25.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0bab19cef8a7fe1c18a43e881793bfc9d4ea984befec3ae5bd0415abf3ecf00" +checksum = "8156733e27020ea5c684db5beac5d1d611e1272ab17901a49466294b84fc217e" dependencies = [ "itoa", "maud_macros", @@ -1640,14 +1817,14 @@ dependencies = [ [[package]] name = "maud_macros" -version = "0.25.0" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be95d66c3024ffce639216058e5bae17a83ecaf266ffc6e4d060ad447c9eed2" +checksum = "7261b00f3952f617899bc012e3dbd56e4f0110a038175929fa5d18e5a19913ca" dependencies = [ - "proc-macro-error", "proc-macro2", + "proc-macro2-diagnostics", "quote", - "syn 1.0.109", + "syn 2.0.89", ] [[package]] @@ -1883,12 +2060,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2012,31 +2183,7 @@ version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", + "zerocopy 0.7.35", ] [[package]] @@ -2054,6 +2201,27 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", + "version_check", +] + +[[package]] +name = "psm" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f58e5423e24c18cc840e1c98370b3993c6649cd1678b4d24318bcf0a083cbe88" +dependencies = [ + "cc", +] + [[package]] name = "quote" version = "1.0.37" @@ -2065,9 +2233,9 @@ dependencies = [ [[package]] name = "quoted_printable" -version = "0.4.8" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3866219251662ec3b26fc217e3e05bf9c4f84325234dfb96bf0bf840889e49" +checksum = "640c9bd8497b02465aeef5375144c26062e0dcd5939dfcbb0f5db76cb8c17c73" [[package]] name = "rand" @@ -2093,6 +2261,17 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.1", + "zerocopy 0.8.18", +] + [[package]] name = "rand_chacha" version = "0.2.2" @@ -2113,6 +2292,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.1", +] + [[package]] name = "rand_core" version = "0.5.1" @@ -2131,6 +2320,16 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3" +dependencies = [ + "getrandom 0.3.1", + "zerocopy 0.8.18", +] + [[package]] name = "rand_hc" version = "0.2.0" @@ -2464,9 +2663,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] @@ -2482,9 +2681,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", @@ -2493,9 +2692,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.138" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" dependencies = [ "itoa", "memchr", @@ -2511,7 +2710,7 @@ checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" dependencies = [ "percent-encoding", "serde", - "thiserror", + "thiserror 1.0.63", ] [[package]] @@ -2640,7 +2839,7 @@ dependencies = [ "dotenvy", "lettre", "maud", - "rand 0.8.5", + "rand 0.9.0", "serde", "serde_json", "serenity", @@ -2675,15 +2874,8 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ - "libc", - "winapi", + "serde", ] [[package]] @@ -2724,21 +2916,11 @@ dependencies = [ "der", ] -[[package]] -name = "sqlformat" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" -dependencies = [ - "nom", - "unicode_categories", -] - [[package]] name = "sqlx" -version = "0.7.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" +checksum = "4410e73b3c0d8442c5f99b425d7a435b5ee0ae4167b3196771dd3f7a01be745f" dependencies = [ "sqlx-core", "sqlx-macros", @@ -2749,37 +2931,31 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.7.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" +checksum = "6a007b6936676aa9ab40207cde35daab0a04b823be8ae004368c0793b96a61e0" dependencies = [ - "ahash", - "atoi", - "byteorder", "bytes 1.7.1", "crc", "crossbeam-queue", "either", - "event-listener 2.5.3", - "futures-channel", + "event-listener 5.3.1", "futures-core", "futures-intrusive", "futures-io", "futures-util", + "hashbrown 0.15.2", "hashlink", - "hex", "indexmap", "log", "memchr", "once_cell", - "paste", "percent-encoding", "serde", "serde_json", "sha2 0.10.8", "smallvec", - "sqlformat", - "thiserror", + "thiserror 2.0.11", "tokio", "tokio-stream", "tracing", @@ -2788,22 +2964,22 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.7.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 1.0.109", + "syn 2.0.89", ] [[package]] name = "sqlx-macros-core" -version = "0.7.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" +checksum = "4e9f90acc5ab146a99bf5061a7eb4976b573f560bc898ef3bf8435448dd5e7ad" dependencies = [ "dotenvy", "either", @@ -2817,8 +2993,9 @@ dependencies = [ "sha2 0.10.8", "sqlx-core", "sqlx-mysql", + "sqlx-postgres", "sqlx-sqlite", - "syn 1.0.109", + "syn 2.0.89", "tempfile", "tokio", "url", @@ -2826,12 +3003,12 @@ dependencies = [ [[package]] name = "sqlx-mysql" -version = "0.7.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" +checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233" dependencies = [ "atoi", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.6.0", "byteorder", "bytes 1.7.1", @@ -2861,19 +3038,19 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.11", "tracing", "whoami", ] [[package]] name = "sqlx-postgres" -version = "0.7.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613" dependencies = [ "atoi", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.6.0", "byteorder", "crc", @@ -2881,7 +3058,6 @@ dependencies = [ "etcetera", "futures-channel", "futures-core", - "futures-io", "futures-util", "hex", "hkdf 0.12.4", @@ -2899,16 +3075,16 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.11", "tracing", "whoami", ] [[package]] name = "sqlx-sqlite" -version = "0.7.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +checksum = "f85ca71d3a5b24e64e1d08dd8fe36c6c95c339a896cc33068148906784620540" dependencies = [ "atoi", "flume 0.11.0", @@ -2921,10 +3097,29 @@ dependencies = [ "log", "percent-encoding", "serde", + "serde_urlencoded", "sqlx-core", "tracing", "url", - "urlencoding", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "stacker" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d08feb8f695b465baed819b03c128dc23f57a694510ab1f06c77f763975685e" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "windows-sys 0.59.0", ] [[package]] @@ -3062,6 +3257,17 @@ dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -3123,7 +3329,16 @@ version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.63", +] + +[[package]] +name = "thiserror" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" +dependencies = [ + "thiserror-impl 2.0.11", ] [[package]] @@ -3137,6 +3352,17 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "thiserror-impl" +version = "2.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + [[package]] name = "time" version = "0.2.27" @@ -3206,6 +3432,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -3223,9 +3459,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" dependencies = [ "backtrace", "bytes 1.7.1", @@ -3234,7 +3470,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.7", + "socket2", "tokio-macros", "windows-sys 0.52.0", ] @@ -3415,7 +3651,7 @@ dependencies = [ "rustls 0.22.4", "rustls-pki-types", "sha1 0.10.6", - "thiserror", + "thiserror 1.0.63", "url", "utf-8", ] @@ -3468,18 +3704,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" - [[package]] name = "universal-hash" version = "0.4.0" @@ -3508,18 +3732,24 @@ dependencies = [ "serde", ] -[[package]] -name = "urlencoding" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" - [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "value-bag" version = "1.10.0" @@ -3565,6 +3795,15 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasite" version = "0.1.0" @@ -3708,6 +3947,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + [[package]] name = "windows-core" version = "0.52.0" @@ -3905,6 +4154,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.6.0", +] + [[package]] name = "wolves_oxidised" version = "0.1.0" @@ -3916,6 +4174,42 @@ dependencies = [ "tokio-test", ] +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -3923,7 +4217,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", - "zerocopy-derive", + "zerocopy-derive 0.7.35", +] + +[[package]] +name = "zerocopy" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79386d31a42a4996e3336b0919ddb90f81112af416270cff95b5f5af22b839c2" +dependencies = [ + "zerocopy-derive 0.8.18", ] [[package]] @@ -3937,8 +4240,62 @@ dependencies = [ "syn 2.0.89", ] +[[package]] +name = "zerocopy-derive" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76331675d372f91bf8d17e13afbd5fe639200b73d01f0fc748bb059f9cca2db7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "zerofrom" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", + "synstructure", +] + [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.89", +] diff --git a/Cargo.toml b/Cargo.toml index d637ae8..aa0cd6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,29 +17,29 @@ name = "update_minecraft" [dependencies] # discord library serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } -tokio = { version = "1.0", features = ["macros", "rt-multi-thread", "full"] } +tokio = { version = "1", features = ["macros", "rt-multi-thread", "full"] } # wolves api wolves_oxidised = { git = "https://forgejo.skynet.ie/Skynet/wolves-oxidised.git", features = ["unstable"] } # wolves_oxidised = { path = "../wolves-oxidised", features = ["unstable"] } # to make the http requests -surf = "2.3.2" +surf = "2.3" -dotenvy = "0.15.7" +dotenvy = "0.15" # For sqlite -sqlx = { version = "0.7.1", features = [ "runtime-tokio", "sqlite", "migrate" ] } +sqlx = { version = "0.8", features = [ "runtime-tokio", "sqlite", "migrate" ] } serde_json = { version = "1.0", features = ["raw_value"] } # create random strings -rand = "0.8.5" +rand = "0.9" # fancy time stuff -chrono = "0.4.26" +chrono = "0.4" # for email -lettre = "0.10.4" -maud = "0.25.0" +lettre = "0.11" +maud = "0.27" serde = "1.0" \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index ba86483..53ae26b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ pub mod common; use chrono::{Datelike, SecondsFormat, Utc}; use dotenvy::dotenv; -use rand::{distributions::Alphanumeric, thread_rng, Rng}; +use rand::{distr::Alphanumeric, rng, Rng}; use serenity::all::CommandInteraction; use serenity::client::Context; use serenity::model::id::{ChannelId, GuildId, RoleId}; @@ -125,7 +125,7 @@ pub fn get_now_iso(short: bool) -> String { } pub fn random_string(len: usize) -> String { - thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect() + rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect() } /** From 30287466cbf5b31ab2b25588eeed9c71e4f67a24 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 19 Feb 2025 00:42:26 +0000 Subject: [PATCH 071/170] fix: cannot have an ID of 0 for any role, channel or server --- src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 53ae26b..0152205 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -57,9 +57,9 @@ pub fn get_config() -> Config { mail_pass: "".to_string(), wolves_url: "".to_string(), wolves_api: "".to_string(), - committee_server: GuildId::new(0), - committee_role: RoleId::new(0), - committee_category: ChannelId::new(0), + committee_server: GuildId::new(1), + committee_role: RoleId::new(1), + committee_category: ChannelId::new(1), }; if let Ok(x) = env::var("DATABASE_HOME") { From 262eb0c991b4050988bfe86f4e27cb8317021f8a Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 19 Feb 2025 00:51:40 +0000 Subject: [PATCH 072/170] fix: ensure that all committee members get teh right role --- src/common/set_roles.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index d47313d..e47b95c 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -312,6 +312,11 @@ pub mod committee { } } + if !roles_required.is_empty() { + // if there are committee roles then give the general purporse role + roles_add.push(committee_member); + } + for role in &roles_required { if !roles_current_id.contains(role) { roles_add.push(role.to_owned()); From c79944bd6e20bde77f6366bee83b7ff70bb00e4b Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 19 Feb 2025 01:18:28 +0000 Subject: [PATCH 073/170] fix: actually enabled the option to get a persons ID if they arent on one of teh servers with teh bot enabled --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index e4adde8..416c3c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4166,7 +4166,7 @@ dependencies = [ [[package]] name = "wolves_oxidised" version = "0.1.0" -source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#6101104c794c2dcf7100b057fe37fdf165b8b381" +source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#74f00e3dcfd52744583b0ded08efb8bb27fdcec8" dependencies = [ "reqwest 0.12.9", "serde", From 0eba54b6f247e472d188837a2daaa0b44d90637e Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 19 Feb 2025 12:29:53 +0000 Subject: [PATCH 074/170] feat: split up the committee refresh from teh regular user refresh --- Cargo.toml | 3 +++ flake.nix | 2 ++ src/bin/update_committee.rs | 54 +++++++++++++++++++++++++++++++++++++ src/bin/update_users.rs | 3 --- src/common/set_roles.rs | 2 +- 5 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 src/bin/update_committee.rs diff --git a/Cargo.toml b/Cargo.toml index aa0cd6b..d999267 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,9 @@ name = "update_data" [[bin]] name = "update_users" +[[bin]] +name = "update_committee" + [[bin]] name = "update_minecraft" diff --git a/flake.nix b/flake.nix index 12ae36e..92eac43 100644 --- a/flake.nix +++ b/flake.nix @@ -122,6 +122,8 @@ "update_data" = "*:0,10,20,30,40,50"; # groups are updated every hour, offset from teh ldap "update_users" = "*:05:00"; + # Committee server has its own timer + "update_committee" = "*:15:00"; # minecraft stuff is updated at 5am "update_minecraft" = "5:10:00"; }; diff --git a/src/bin/update_committee.rs b/src/bin/update_committee.rs new file mode 100644 index 0000000..eae4eec --- /dev/null +++ b/src/bin/update_committee.rs @@ -0,0 +1,54 @@ +use serenity::{ + async_trait, + client::{Context, EventHandler}, + model::gateway::{GatewayIntents, Ready}, + Client, +}; +use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase}; +use skynet_discord_bot::common::set_roles::{committee, normal}; +use skynet_discord_bot::{get_config, Config}; +use std::{process, sync::Arc}; +use tokio::sync::RwLock; + +#[tokio::main] +async fn main() { + let config = get_config(); + let db = match db_init(&config).await { + Ok(x) => x, + Err(_) => return, + }; + + // Intents are a bitflag, bitwise operations can be used to dictate which intents to use + let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; + // Build our client. + let mut client = Client::builder(&config.discord_token, intents) + .event_handler(Handler {}) + .await + .expect("Error creating client"); + + { + let mut data = client.data.write().await; + + data.insert::(Arc::new(RwLock::new(config))); + data.insert::(Arc::new(RwLock::new(db))); + } + + if let Err(why) = client.start().await { + println!("Client error: {:?}", why); + } +} + +struct Handler; +#[async_trait] +impl EventHandler for Handler { + async fn ready(&self, ctx: Context, ready: Ready) { + let ctx = Arc::new(ctx); + println!("{} is connected!", ready.user.name); + + // u[date committee server + committee::check_committee(Arc::clone(&ctx)).await; + + // finish up + process::exit(0); + } +} diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index e3170eb..301fcc4 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -48,9 +48,6 @@ impl EventHandler for Handler { // this goes into each server and sets roles for each wolves member check_bulk(Arc::clone(&ctx)).await; - // u[date committee server - committee::check_committee(Arc::clone(&ctx)).await; - // finish up process::exit(0); } diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index e47b95c..5710fb3 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -273,7 +273,7 @@ pub mod committee { // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) if let Some(x) = get_server_member_discord(db, id_wolves).await { if let Some(member_tmp) = x.discord { - let values = users_roles.entry(member_tmp).or_insert(vec![committee_member]); + let values = users_roles.entry(member_tmp).or_insert(vec![]); values.push(r.id); } } From 8645a9b3ce01129d2db4981c83493e0acac2c14a Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 19 Feb 2025 12:38:40 +0000 Subject: [PATCH 075/170] fix: clippy and fmt --- src/bin/update_committee.rs | 4 ++-- src/bin/update_users.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bin/update_committee.rs b/src/bin/update_committee.rs index eae4eec..d2026c0 100644 --- a/src/bin/update_committee.rs +++ b/src/bin/update_committee.rs @@ -4,8 +4,8 @@ use serenity::{ model::gateway::{GatewayIntents, Ready}, Client, }; -use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase}; -use skynet_discord_bot::common::set_roles::{committee, normal}; +use skynet_discord_bot::common::database::{db_init, DataBase}; +use skynet_discord_bot::common::set_roles::committee; use skynet_discord_bot::{get_config, Config}; use std::{process, sync::Arc}; use tokio::sync::RwLock; diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index 301fcc4..3617d14 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -5,7 +5,7 @@ use serenity::{ Client, }; use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase}; -use skynet_discord_bot::common::set_roles::{committee, normal}; +use skynet_discord_bot::common::set_roles::normal; use skynet_discord_bot::{get_config, Config}; use std::{process, sync::Arc}; use tokio::sync::RwLock; From 348020ecfee0963ef97458db25d16f5ddf1653a9 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 19 Feb 2025 22:36:39 +0000 Subject: [PATCH 076/170] feat: will check if a person needs a committee role on teh committee server --- src/commands/link_email.rs | 40 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 750e0ee..35a29cc 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -280,11 +280,12 @@ pub mod link { pub mod verify { use super::*; - use crate::commands::link_email::link::{db_pending_clear_expired, get_verify_from_db}; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; + use crate::commands::link_email::link::{db_pending_clear_expired, get_server_member_discord, get_verify_from_db}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption, GuildId, RoleId}; use serenity::model::user::User; use skynet_discord_bot::common::database::get_server_config; use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; + use skynet_discord_bot::common::wolves::committees::Committees; use sqlx::Error; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { @@ -323,6 +324,10 @@ pub mod verify { Ok(_) => { // get teh right roles for the user set_server_roles(&db, &command.user, ctx).await; + + // check if they are a committee member, and on that server + set_server_roles_committee(&db, &command.user, ctx).await; + "Discord username linked to Wolves".to_string() } Err(e) => { @@ -402,6 +407,37 @@ pub mod verify { } } + async fn get_committees_id(db: &Pool, wolves_id: i64) -> Vec { + sqlx::query_as::<_, Committees>( + r#" + SELECT * + committee like '%?1%' + "#, + ) + .bind(wolves_id) + .fetch_all(db) + .await + .unwrap_or_else(|e| { + dbg!(e); + vec![] + }) + } + + async fn set_server_roles_committee(db: &Pool, discord: &User, ctx: &Context) { + if let Some(x) = get_server_member_discord(db, &discord.id).await { + // if they are a member of one or more committees, and in teh committee server then give the teh general committee role + // they will get teh more specific vanity role later + if !get_committees_id(db, x.id_wolves).await.is_empty() { + let server = GuildId::new(1220150752656363520); + let committee_member = RoleId::new(1226602779968274573); + + if let Ok(member) = server.member(ctx, &discord.id).await { + member.add_roles(&ctx, &[committee_member]).await.unwrap_or_default(); + } + } + } + } + async fn get_servers(db: &Pool, discord: &UserId) -> Result, Error> { sqlx::query_as::<_, ServerMembersWolves>( " From 9ce5b8136b42b5a390381e5aa08f9ec2fb7a053f Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 24 Feb 2025 16:44:22 +0000 Subject: [PATCH 077/170] fix: bump to use the new fieldname for teh API get_members --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 416c3c4..47a42f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4166,7 +4166,7 @@ dependencies = [ [[package]] name = "wolves_oxidised" version = "0.1.0" -source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#74f00e3dcfd52744583b0ded08efb8bb27fdcec8" +source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#265c8c81d1eb870a6149da5ce72556d44f57937f" dependencies = [ "reqwest 0.12.9", "serde", From 6481fcb89fa7684cb22d083812ce829730f1f309 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 24 Feb 2025 17:07:26 +0000 Subject: [PATCH 078/170] feat: updated teh bot to use the new wolves_id for clubs/socs --- db/migrations/9_member_committee-id.sql | 6 ++++++ src/commands/add_server.rs | 2 +- src/common/database.rs | 4 ++-- src/common/wolves.rs | 16 ++++++++-------- src/main.rs | 8 ++++---- 5 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 db/migrations/9_member_committee-id.sql diff --git a/db/migrations/9_member_committee-id.sql b/db/migrations/9_member_committee-id.sql new file mode 100644 index 0000000..77552ce --- /dev/null +++ b/db/migrations/9_member_committee-id.sql @@ -0,0 +1,6 @@ +-- No need for this col since it is goign to be in "committees" anyways +ALTER TABLE servers DROP COLUMN server_name; + +-- we do care about teh ID of the club/soc though +ALTER TABLE servers ADD COLUMN wolves_id integer DEFAULT 0; + diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index fdc0f23..5a9dbee 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -61,12 +61,12 @@ pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { let server_data = Servers { server: command.guild_id.unwrap_or_default(), wolves_api, + wolves_id: 0, role_past, role_current, member_past: 0, member_current: 0, bot_channel_id, - server_name: "".to_string(), }; match add_server(&db, ctx, &server_data).await { diff --git a/src/common/database.rs b/src/common/database.rs index 596b411..e4856fa 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -121,12 +121,12 @@ impl<'r> FromRow<'r, SqliteRow> for WolvesVerify { pub struct Servers { pub server: GuildId, pub wolves_api: String, + pub wolves_id: i64, pub role_past: Option, pub role_current: RoleId, pub member_past: i64, pub member_current: i64, pub bot_channel_id: ChannelId, - pub server_name: String, } impl<'r> FromRow<'r, SqliteRow> for Servers { @@ -158,12 +158,12 @@ impl<'r> FromRow<'r, SqliteRow> for Servers { Ok(Self { server, wolves_api: row.try_get("wolves_api")?, + wolves_id: row.try_get("wolves_id").unwrap_or(0), role_past, role_current, member_past: row.try_get("member_past")?, member_current: row.try_get("member_current")?, bot_channel_id, - server_name: row.try_get("server_name")?, }) } } diff --git a/src/common/wolves.rs b/src/common/wolves.rs index ea64699..6f73842 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -87,7 +87,7 @@ pub mod cns { server, // this is the unique api key for each club/soc wolves_api, - server_name, + wolves_id, .. } = &server_config; // dbg!(&server_config); @@ -101,7 +101,7 @@ pub mod cns { for user in wolves.get_members(wolves_api).await { // dbg!(&user.committee); if server_name_tmp.is_none() { - server_name_tmp = Some(user.committee.to_owned()); + server_name_tmp = Some(user.committee_id); } let id = user.member_id.parse::().unwrap_or_default(); match existing.get(&(id as i64)) { @@ -124,9 +124,9 @@ pub mod cns { } } - if let Some(name) = server_name_tmp { - if &name != server_name { - set_server_member(&db, server, &name).await; + if let Some(cs_id) = server_name_tmp { + if &cs_id != wolves_id { + set_server_member(&db, server, cs_id).await; } } if !user_to_update.is_empty() { @@ -135,15 +135,15 @@ pub mod cns { } } - async fn set_server_member(db: &Pool, server: &GuildId, name: &str) { + async fn set_server_member(db: &Pool, server: &GuildId, wolves_id: i64) { match sqlx::query_as::<_, Servers>( " UPDATE servers - SET server_name = ? + SET wolves_id = ? WHERE server = ? ", ) - .bind(name) + .bind(wolves_id) .bind(server.get() as i64) .fetch_optional(db) .await diff --git a/src/main.rs b/src/main.rs index 36316d1..5dab0f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -68,7 +68,7 @@ impl EventHandler for Handler { println!("{:?}", e); } } else { - let tmp = get_committee(&db, &config_server.server_name).await; + let tmp = get_committee(&db, config_server.wolves_id).await; if !tmp.is_empty() { let committee = &tmp[0]; let msg = format!( @@ -158,15 +158,15 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } } -async fn get_committee(db: &Pool, committee: &str) -> Vec { +async fn get_committee(db: &Pool, wolves_id: i64) -> Vec { sqlx::query_as::<_, Committees>( r#" SELECT * FROM committees - WHERE name_plain = ? + WHERE id = ? "#, ) - .bind(committee) + .bind(wolves_id) .fetch_all(db) .await .unwrap_or_default() From b67894fc6e7d883aa24a004a4f19fcb47dc40f5e Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 25 Feb 2025 17:34:33 +0000 Subject: [PATCH 079/170] fixL had bad query --- src/commands/link_email.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 35a29cc..8201923 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -411,10 +411,11 @@ pub mod verify { sqlx::query_as::<_, Committees>( r#" SELECT * - committee like '%?1%' + FROM committees + WHERE committee LIKE ?1 "#, ) - .bind(wolves_id) + .bind(format!("%{}%", wolves_id)) .fetch_all(db) .await .unwrap_or_else(|e| { From 09ce45f70fcb088baccef85535af5ffd86d336ff Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 26 Feb 2025 14:36:48 +0000 Subject: [PATCH 080/170] feat: improved the email --- src/commands/link_email.rs | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 8201923..86e8c8e 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -139,7 +139,7 @@ pub mod link { fn send_mail(config: &Config, email: &Wolves, auth: &str, user: &str) -> Result { let mail = &email.email; - let discord = "https://discord.skynet.ie"; + let discord = "https://computer.discord.skynet.ie"; let sender = format!("UL Computer Society <{}>", &config.mail_user); // Create the html we want to send. @@ -152,13 +152,19 @@ pub mod link { } div { h2 { "Hello from Skynet!" } + + h3 { "Link your Account" } // Substitute in the name of our recipient. p { "Hi " (user) "," } p { - "Please use " pre { "/verify code: " (auth)} " to verify your discord account." + "Please paste this line into Discord (and press enter) to verify your discord account:" + br; + pre { "/verify code: " (auth)} } + hr; + h3 { "Help & Support" } p { - "If you have issues please refer to our Discord server:" + "If you have issues please refer to our Computer Society Discord Server:" br; a href=(discord) { (discord) } } @@ -172,15 +178,24 @@ pub mod link { let body_text = format!( r#" - Hi {user} + Hello from Skynet! + + Link your Account - Please use "/verify code: {auth}" to verify your discord account. + Hi {user}, + + Please paste this line into Discord (and press enter) to verify your Discord account: + /verify code: {auth} - If you have issues please refer to our Discord server: - {discord} - - Skynet Team - UL Computer Society + ------------------------------------------------------------------------- + + Help & Support + + If you have issues please refer to our Computer Society Discord Server: + {discord} + + Skynet Team + UL Computer Society "# ); From 7406f0e6206b3fcec4c471949d6c428503fa9e91 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 26 Feb 2025 15:55:42 +0000 Subject: [PATCH 081/170] feat: change commands to use the "Manage Server" permission instead of just admin. This measn that the commands only show up if you can manage teh server --- src/commands/add_server.rs | 7 +------ src/commands/minecraft.rs | 40 ++++++++++++++++++-------------------- src/commands/role_adder.rs | 7 +------ src/lib.rs | 40 -------------------------------------- 4 files changed, 21 insertions(+), 73 deletions(-) diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index 5a9dbee..70ec3c8 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -3,15 +3,9 @@ use serenity::{builder::CreateCommand, client::Context}; use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers}; use skynet_discord_bot::common::set_roles::normal::update_server; use skynet_discord_bot::common::wolves::cns::get_wolves; -use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - // check if user has high enough permisssions - if let Some(msg) = is_admin(command, ctx).await { - return msg; - } - let wolves_api = if let Some(CommandDataOption { value: CommandDataOptionValue::String(key), .. @@ -83,6 +77,7 @@ pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { pub fn register() -> CreateCommand { CreateCommand::new("add") .description("Enable the bot for this discord") + .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) .add_option(CreateCommandOption::new(CommandOptionType::String, "api_key", "UL Wolves API Key").required(true)) .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_current", "Role for Current members").required(true)) .add_option(CreateCommandOption::new(CommandOptionType::Channel, "bot_channel", "Safe space for folks to use the bot commands.").required(true)) diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 54bf35c..5f1321a 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -192,19 +192,19 @@ pub(crate) mod server { use super::*; use skynet_discord_bot::common::minecraft::update_server; use skynet_discord_bot::common::minecraft::Minecraft; - use skynet_discord_bot::{is_admin, Config}; + use skynet_discord_bot::Config; pub fn register() -> CreateCommand { - CreateCommand::new("minecraft_add").description("Add a minecraft server").add_option( - CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society").required(true), - ) + CreateCommand::new("minecraft_add") + .description("Add a minecraft server") + .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) + .add_option( + CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society") + .required(true), + ) } pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - // check if user has high enough permisssions - if let Some(msg) = is_admin(command, ctx).await { - return msg; - } let g_id = match command.guild_id { None => return "Not in a server".to_string(), Some(x) => x, @@ -265,16 +265,15 @@ pub(crate) mod server { use serenity::client::Context; use skynet_discord_bot::common::database::DataBase; use skynet_discord_bot::common::minecraft::{get_minecraft_config_server, server_information}; - use skynet_discord_bot::{is_admin, Config}; + use skynet_discord_bot::Config; pub fn register() -> CreateCommand { - CreateCommand::new("minecraft_list").description("List your minecraft servers") + CreateCommand::new("minecraft_list") + .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) + .description("List your minecraft servers") } pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - if let Some(msg) = is_admin(command, ctx).await { - return msg; - } let g_id = match command.guild_id { None => return "Not in a server".to_string(), Some(x) => x, @@ -327,20 +326,19 @@ pub(crate) mod server { use serenity::model::id::GuildId; use skynet_discord_bot::common::database::DataBase; use skynet_discord_bot::common::minecraft::Minecraft; - use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; pub fn register() -> CreateCommand { - CreateCommand::new("minecraft_delete").description("Delete a minecraft server").add_option( - CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society").required(true), - ) + CreateCommand::new("minecraft_delete") + .description("Delete a minecraft server") + .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) + .add_option( + CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society") + .required(true), + ) } pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - // check if user has high enough permisssions - if let Some(msg) = is_admin(command, ctx).await { - return msg; - } let g_id = match command.guild_id { None => return "Not in a server".to_string(), Some(x) => x, diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index 182eeb9..b0d28e2 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -1,7 +1,6 @@ use serenity::client::Context; use skynet_discord_bot::common::database::{DataBase, RoleAdder}; -use skynet_discord_bot::is_admin; use sqlx::{Error, Pool, Sqlite}; pub mod edit { @@ -9,11 +8,6 @@ pub mod edit { use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - // check if user has high enough permisssions - if let Some(msg) = is_admin(command, ctx).await { - return msg; - } - let role_a = if let Some(CommandDataOption { value: CommandDataOptionValue::Role(role), .. @@ -110,6 +104,7 @@ pub mod edit { pub fn register() -> CreateCommand { CreateCommand::new("roles_adder") .description("Combine roles together to an new one") + .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_a", "A role you want to add to Role B").required(true)) .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_b", "A role you want to add to Role A").required(true)) .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_c", "Sum of A and B").required(true)) diff --git a/src/lib.rs b/src/lib.rs index 0152205..762dc69 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,6 @@ pub mod common; use chrono::{Datelike, SecondsFormat, Utc}; use dotenvy::dotenv; use rand::{distr::Alphanumeric, rng, Rng}; -use serenity::all::CommandInteraction; -use serenity::client::Context; use serenity::model::id::{ChannelId, GuildId, RoleId}; use serenity::prelude::TypeMapKey; use std::{env, sync::Arc}; @@ -127,41 +125,3 @@ pub fn get_now_iso(short: bool) -> String { pub fn random_string(len: usize) -> String { rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect() } - -/** - For any time ye need to check if a user who calls a command has admin privlages -*/ -pub async fn is_admin(command: &CommandInteraction, ctx: &Context) -> Option { - let mut admin = false; - - let g_id = match command.guild_id { - None => return Some("Not in a server".to_string()), - Some(x) => x, - }; - - let roles_server = g_id.roles(&ctx.http).await.unwrap_or_default(); - - if let Ok(member) = g_id.member(&ctx.http, command.user.id).await { - if let Some(permissions) = member.permissions { - if permissions.administrator() { - admin = true; - } - } - - for role_id in member.roles { - if admin { - break; - } - if let Some(role) = roles_server.get(&role_id) { - if role.permissions.administrator() { - admin = true; - } - } - } - } - if !admin { - Some("Administrator permission required".to_string()) - } else { - None - } -} From 44bb40d96d0dc289c42cc95652c3b71e5f4583fb Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 26 Feb 2025 16:20:28 +0000 Subject: [PATCH 082/170] fix: removing some deadweight --- src/commands/link_email.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 86e8c8e..44001d3 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -11,7 +11,6 @@ use sqlx::{Pool, Sqlite}; pub mod link { use super::*; - use serde::{Deserialize, Serialize}; use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { @@ -265,19 +264,6 @@ pub mod link { .await } - #[derive(Serialize, Deserialize, Debug)] - #[serde(untagged)] - pub enum WolvesResultUserResult { - B(bool), - S(String), - } - - #[derive(Deserialize, Serialize, Debug)] - struct WolvesResultUser { - success: i64, - result: WolvesResultUserResult, - } - async fn save_to_db_user(db: &Pool, id_wolves: i64, email: &str) -> Result, sqlx::Error> { sqlx::query_as::<_, Wolves>( " From f046410cdc2dea2e42abae5e01719b85d8b53bff Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 26 Feb 2025 17:02:47 +0000 Subject: [PATCH 083/170] fix: feedback on teh email users get sent when verifying their discord accounts --- src/commands/link_email.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 44001d3..71cfbb0 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -87,7 +87,7 @@ pub mod link { // generate a auth key let auth = random_string(20); - match send_mail(&config, &details, &auth, &command.user.name) { + match send_mail(&config, &details.email, &auth, &command.user.name) { Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await { Ok(_) => {} Err(e) => { @@ -136,23 +136,22 @@ pub mod link { .ok() } - fn send_mail(config: &Config, email: &Wolves, auth: &str, user: &str) -> Result { - let mail = &email.email; + fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result { let discord = "https://computer.discord.skynet.ie"; let sender = format!("UL Computer Society <{}>", &config.mail_user); // Create the html we want to send. let html = html! { head { - title { "Hello from Skynet!" } + title { "UL Wolves Discord Linker" } style type="text/css" { "h2, h4 { font-family: Arial, Helvetica, sans-serif; }" } } div { - h2 { "Hello from Skynet!" } + h2 { "UL Wolves Discord Linker" } - h3 { "Link your Account" } + h3 { "Link your UL Wolves Account to Discord" } // Substitute in the name of our recipient. p { "Hi " (user) "," } p { @@ -166,9 +165,6 @@ pub mod link { "If you have issues please refer to our Computer Society Discord Server:" br; a href=(discord) { (discord) } - } - p { - "Skynet Team" br; "UL Computer Society" } @@ -177,8 +173,9 @@ pub mod link { let body_text = format!( r#" - Hello from Skynet! - + UL Wolves Discord Linker + Link your UL Wolves Account to Discord + Link your Account Hi {user}, @@ -192,9 +189,7 @@ pub mod link { If you have issues please refer to our Computer Society Discord Server: {discord} - - Skynet Team - UL Computer Society + UL Computer Society "# ); @@ -202,7 +197,7 @@ pub mod link { let email = Message::builder() .from(sender.parse().unwrap()) .to(mail.parse().unwrap()) - .subject("Skynet-Discord: Link Wolves.") + .subject("Skynet: Link Discord to Wolves.") .multipart( // This is composed of two parts. // also helps not trip spam settings (uneven number of url's From 6a5f651ba2419639323e6c7d7219b82d4b8a7f9d Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 26 Feb 2025 17:06:58 +0000 Subject: [PATCH 084/170] doc: update to reflect that ye no longer need admin to setup and modify the config --- doc/Committee.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/Committee.md b/doc/Committee.md index cfe718e..a8a12a7 100644 --- a/doc/Committee.md +++ b/doc/Committee.md @@ -7,7 +7,7 @@ For example is a user links on the CompSoc Discord then they will also get their ## Setup - Committee You need admin access to run any of the commands in this section. -Either the server owner or a user with the ``Administrator`` permission. +Either the server owner or a user with the ``Manage Server`` permission. ### Get the API Key The ``api_key`` is used by the Bot in order to request information, it will be used later in the process. @@ -38,7 +38,7 @@ The reason for both roles is ye have one for active members while the second is ### Setup Bot This is where the bot is configured. You will need the ``api_key`` from the start of the process. -You (personally) will need a role with ``Administrator`` permission to be able to do this. +You (personally) will need a role with ``Manage Server`` permission to be able to do this. 1. Use the command ``/add`` and a list of options will pop up. 2. ``api_key`` is the key you got from Keith earlier. From 555e38ee26c40b5c2728032bf8daed6484dbea18 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 27 Feb 2025 01:19:40 +0000 Subject: [PATCH 085/170] feat: now store the roles and channel ids in teh database --- db/migrations/10_member_committee-roles.sql | 10 + src/common/database.rs | 24 +- src/common/set_roles.rs | 258 ++++++++++++++------ 3 files changed, 209 insertions(+), 83 deletions(-) create mode 100644 db/migrations/10_member_committee-roles.sql diff --git a/db/migrations/10_member_committee-roles.sql b/db/migrations/10_member_committee-roles.sql new file mode 100644 index 0000000..03264cd --- /dev/null +++ b/db/migrations/10_member_committee-roles.sql @@ -0,0 +1,10 @@ + +CREATE TABLE IF NOT EXISTS committee_roles ( + id_wolves integer PRIMARY KEY, + id_role integer DEFAULT 1, + id_channel integer DEFAULT 1, + -- not strictly required but for readability and debugging + name_role text NOT NULL DEFAULT '', + name_channel text NOT NULL DEFAULT '', + count integer DEFAULT 0 +); diff --git a/src/common/database.rs b/src/common/database.rs index e4856fa..0663799 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -190,14 +190,28 @@ impl<'r> FromRow<'r, SqliteRow> for RoleAdder { } } -fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId { - match row.try_get(col) { +pub(crate) fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId { + let id = match row.try_get(col) { Ok(x) => { let tmp: i64 = x; - RoleId::new(tmp as u64) + tmp as u64 } - _ => RoleId::from(0u64), - } + _ => 0, + }; + + RoleId::from(id) +} + +pub(crate) fn get_channel_from_row(row: &SqliteRow, col: &str) -> ChannelId { + let id = match row.try_get(col) { + Ok(x) => { + let tmp: i64 = x; + tmp as u64 + } + _ => 0, + }; + + ChannelId::from(id) } pub async fn db_init(config: &Config) -> Result, Error> { diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 5710fb3..da8d9d1 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -131,9 +131,10 @@ pub mod normal { // for updating committee members pub mod committee { - use crate::common::database::{DataBase, Wolves}; + use crate::common::database::{get_channel_from_row, get_role_from_row, DataBase, Wolves}; use crate::common::wolves::committees::Committees; use crate::Config; + use serde::{Deserialize, Serialize}; use serenity::all::EditRole; use serenity::builder::CreateChannel; use serenity::client::Context; @@ -141,7 +142,8 @@ pub mod committee { use serenity::model::guild::Member; use serenity::model::id::ChannelId; use serenity::model::prelude::RoleId; - use sqlx::{Pool, Sqlite}; + use sqlx::sqlite::SqliteRow; + use sqlx::{Error, FromRow, Pool, Row, Sqlite}; use std::collections::HashMap; use std::sync::Arc; @@ -174,8 +176,7 @@ pub mod committee { let server = config.committee_server; let committee_member = RoleId::new(1226602779968274573); let committees = get_committees(db).await; - let categories = vec![ - // C&S Chats 1 + let categories = [ ChannelId::new(1226606560973815839), // C&S Chats 2 ChannelId::new(1341457244973305927), @@ -184,35 +185,30 @@ pub mod committee { ]; // information about the server - let roles = server.roles(&ctx).await.unwrap_or_default(); - let channels = server.channels(&ctx).await.unwrap_or_default(); - - // make a hashmap of the nameof roles to quickly get them out again - let mut roles_name = HashMap::new(); - for role in roles.values() { - roles_name.insert(role.name.to_owned(), role.to_owned()); + let mut roles_db = HashMap::new(); + for role in db_roles_get(db).await { + roles_db.insert( + role.id_wolves, + CommitteeRoles { + id_wolves: role.id_wolves, + id_role: role.id_role, + id_channel: role.id_channel, + name_role: role.name_role, + name_channel: role.name_channel, + // always start at 0 + count: 0, + }, + ); } - let mut channels_name = HashMap::new(); - for channel in channels.values() { - // we only care about teh channels in teh category - if let Some(x) = channel.parent_id { - for category in &categories { - if x.eq(category) { - channels_name.insert(channel.name.to_owned(), channel.to_owned()); - } - } - } - } + let mut channels = server.channels(&ctx).await.unwrap_or_default(); // a map of users and the roles they are goign to be getting let mut users_roles = HashMap::new(); - // a list of all the roles that can be removed from folks who should have them - let mut committee_roles = vec![committee_member]; - + let mut re_order = false; + // we need to create roles and channels if tehy dont already exist let mut category_index = 0; - let mut i = 0; loop { if i >= committees.len() { @@ -220,24 +216,17 @@ pub mod committee { } let committee = &committees[i]; - // get the role for this committee/club/soc - let role = match roles_name.get(&committee.name_full) { - Some(x) => Some(x.to_owned()), - None => { - // create teh role if it does not exist - match server - .create_role(&ctx, EditRole::new().name(&committee.name_full).hoist(false).mentionable(true)) - .await - { - Ok(x) => Some(x), - Err(_) => None, - } - } - }; - - // create teh channel if it does nto exist - if !channels_name.contains_key(&committee.name_profile) { - match server + // if a club/soc ever changes their name + if let Some(x) = roles_db.get_mut(&committee.id) { + x.name_role = committee.name_full.to_owned(); + x.name_channel = committee.name_profile.to_owned(); + } + + // handle new clubs/socs + if let std::collections::hash_map::Entry::Vacant(e) = roles_db.entry(committee.id) { + // create channel + // channel is first as the categories can only contain 50 channels + let channel = match server .create_channel( &ctx, CreateChannel::new(&committee.name_profile) @@ -247,40 +236,72 @@ pub mod committee { .await { Ok(x) => { - // update teh channels name list - channels_name.insert(x.name.to_owned(), x.to_owned()); - println!("Created channel: {}", &committee.name_profile); + + x.id } Err(x) => { let tmp = x.to_string(); - + dbg!("Unable to create channel: ", &tmp, &tmp.contains("Maximum number of channels in category reached (50)")); if x.to_string().contains("Maximum number of channels in category reached (50)") { category_index += 1; continue; } - dbg!("Unable to create channel: ", &tmp, &tmp.contains("Maximum number of channels in category reached (50)")); + ChannelId::new(1) } - } + }; + + // create role + let role = match server + .create_role(&ctx, EditRole::new().name(&committee.name_full).hoist(false).mentionable(true)) + .await + { + Ok(x) => x.id, + Err(_) => RoleId::new(1), + }; + + let tmp = CommitteeRoles { + id_wolves: committee.id, + id_role: role, + id_channel: channel, + name_role: committee.name_full.to_owned(), + name_channel: committee.name_profile.to_owned(), + count: 0, + }; + + // save it to the db in case of crash or error + db_role_set(db, &tmp).await; + + // insert it into teh local cache + e.insert(tmp); + + re_order = true; + } + + i += 1; + } + + for committee in &committees { + let r = if let Some(x) = roles_db.get(&committee.id) { + x.id_role + } else { + continue; }; - // so if the role exists - if let Some(r) = role { - committee_roles.push(r.id); + for id_wolves in &committee.committee { + // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) + if let Some(x) = get_server_member_discord(db, id_wolves).await { + if let Some(member_tmp) = x.discord { + let values = users_roles.entry(member_tmp).or_insert(vec![]); + values.push(r); - for id_wolves in &committee.committee { - // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) - if let Some(x) = get_server_member_discord(db, id_wolves).await { - if let Some(member_tmp) = x.discord { - let values = users_roles.entry(member_tmp).or_insert(vec![]); - values.push(r.id); + if let Some(x) = roles_db.get_mut(&committee.id) { + x.count += 1; } } } } - - i += 1; } // now we have a map of all users that should get roles time to go through all the folks on teh server @@ -315,6 +336,10 @@ pub mod committee { if !roles_required.is_empty() { // if there are committee roles then give the general purporse role roles_add.push(committee_member); + + if let Some(x) = roles_db.get_mut(&0) { + x.count += 1; + } } for role in &roles_required { @@ -335,33 +360,110 @@ pub mod committee { } } - // finally re-order teh channels to make them visually apealing - let mut channel_names = channels_name.clone().into_keys().collect::>(); - channel_names.sort(); + let mut channel_names = vec![]; + for role in roles_db.values() { + // save these to db + db_role_set(db, role).await; - // get a list of all teh new positions - let mut new_positions = vec![]; - for (i, name) in channel_names.iter().enumerate() { - if let Some(channel) = channels_name.get_mut(name) { - let position_new = i as u64; - if position_new != channel.position as u64 { - new_positions.push((channel.id.to_owned(), position_new)); - } + // pull out teh channel names + if re_order { + channel_names.push((role.name_channel.to_owned(), role.id_channel)); } } - if !new_positions.is_empty() { - match server.reorder_channels(&ctx, new_positions).await { - Ok(_) => { - println!("Successfully re-orderd the committee category"); + if re_order { + channel_names.sort_by_key(|(name, _)| name.to_owned()); + + // get a list of all teh new positions + let mut new_positions = vec![]; + for (i, (_, id)) in channel_names.iter().enumerate() { + if let Some(channel) = channels.get_mut(id) { + let position_new = i as u64; + if position_new != channel.position as u64 { + new_positions.push((channel.id.to_owned(), position_new)); + } } - Err(e) => { - dbg!("Failed to re-order ", e); + } + + if !new_positions.is_empty() { + match server.reorder_channels(&ctx, new_positions).await { + Ok(_) => { + println!("Successfully re-orderd the committee category"); + } + Err(e) => { + dbg!("Failed to re-order ", e); + } } } } } + #[derive(Debug, Clone, Deserialize, Serialize)] + struct CommitteeRoles { + id_wolves: i64, + id_role: RoleId, + id_channel: ChannelId, + name_role: String, + name_channel: String, + count: i64, + } + + impl<'r> FromRow<'r, SqliteRow> for CommitteeRoles { + fn from_row(row: &'r SqliteRow) -> Result { + Ok(Self { + id_wolves: row.try_get("id_wolves")?, + id_role: get_role_from_row(row, "id_role"), + id_channel: get_channel_from_row(row, "id_channel"), + name_role: row.try_get("name_role")?, + name_channel: row.try_get("name_channel")?, + count: row.try_get("count")?, + }) + } + } + + async fn db_role_set(db: &Pool, role: &CommitteeRoles) { + // expiry + match sqlx::query_as::<_, CommitteeRoles>( + " + INSERT INTO committee_roles (id_wolves, id_role, id_channel, name_role, name_channel, count) + VALUES ($1, $2, $3, $4, $5, $6) + ON CONFLICT(id_wolves) DO UPDATE SET name_role = $4, name_channel = $4, count = $6 + ", + ) + .bind(role.id_wolves) + .bind(role.id_role.get() as i64) + .bind(role.id_channel.get() as i64) + .bind(&role.name_role) + .bind(&role.name_channel) + .bind(role.count) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + println!("Failure to insert into Wolves {:?}", role); + println!("{:?}", e); + } + } + } + + async fn db_roles_get(db: &Pool) -> Vec { + // expiry + sqlx::query_as::<_, CommitteeRoles>( + " + SELECT * + FROM committee_roles + ", + ) + .fetch_all(db) + .await + .unwrap_or_else(|e| { + println!("Failure to get Roles from committee_roles"); + println!("{:?}", e); + vec![] + }) + } + async fn get_committees(db: &Pool) -> Vec { sqlx::query_as::<_, Committees>( r#" From 0bedf96da5c1cfdde9c42ae98f009f37062fea1c Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 27 Feb 2025 01:24:13 +0000 Subject: [PATCH 086/170] fix: properly update teh channel name --- src/common/set_roles.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index da8d9d1..654e669 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -427,7 +427,7 @@ pub mod committee { " INSERT INTO committee_roles (id_wolves, id_role, id_channel, name_role, name_channel, count) VALUES ($1, $2, $3, $4, $5, $6) - ON CONFLICT(id_wolves) DO UPDATE SET name_role = $4, name_channel = $4, count = $6 + ON CONFLICT(id_wolves) DO UPDATE SET name_role = $4, name_channel = $5, count = $6 ", ) .bind(role.id_wolves) From 143483d3b3f8360631ea32fb9e66ddd8ee2ff736 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 27 Feb 2025 03:02:35 +0000 Subject: [PATCH 087/170] feat: new command to see how many of each club/soc are on teh server --- src/commands/committee.rs | 80 +++++++++++++++++++++++++++++++++++++++ src/commands/mod.rs | 1 + src/common/set_roles.rs | 20 +++++----- src/main.rs | 18 +++++++++ 4 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 src/commands/committee.rs diff --git a/src/commands/committee.rs b/src/commands/committee.rs new file mode 100644 index 0000000..4a4aa16 --- /dev/null +++ b/src/commands/committee.rs @@ -0,0 +1,80 @@ +pub mod count { + + // get the list of all the current clubs/socs members + + use serenity::all::{ + CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption, + }; + use skynet_discord_bot::common::database::DataBase; + use skynet_discord_bot::common::set_roles::committee::db_roles_get; + + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + let sub_options = if let Some(CommandDataOption { + value: CommandDataOptionValue::SubCommand(key), + .. + }) = command.data.options.first() + { + key + } else { + return "Please provide a wolves API key".to_string(); + }; + + let all = if let Some(x) = sub_options.first() { + match x.value { + CommandDataOptionValue::Boolean(y) => y, + _ => false, + } + } else { + false + }; + + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Databse in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let mut cs = vec![]; + // pull it from a DB + for committee in db_roles_get(&db).await { + if !all && committee.count == 0 { + continue; + } + cs.push((committee.count, committee.name_role.to_owned())); + } + + cs.sort_by_key(|(count, _)| *count); + cs.reverse(); + + // msg can be a max 2000 chars long + let mut limit = 2000; + + let mut response = vec![]; + for (count, name) in cs { + let leading = if count < 10 { " " } else { "" }; + + let line = format!("{}{} {}", leading, count, name); + + let length = line.len() + 1; + + if length < limit { + response.push(line); + limit -= length; + } else { + break; + } + } + + response.join("\n") + } + + pub fn register() -> CreateCommand { + CreateCommand::new("committee") + .description("Commands related to teh committees") + //.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommand, "list", "List out the Committee Roles Numbers") + .add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "all", "List out all the Committee Roles Numbers").required(false)), + ) + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 9e4170a..a11ffd5 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,4 +1,5 @@ pub mod add_server; +pub mod committee; pub mod link_email; pub mod minecraft; pub mod role_adder; diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 654e669..908e6d6 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -221,7 +221,7 @@ pub mod committee { x.name_role = committee.name_full.to_owned(); x.name_channel = committee.name_profile.to_owned(); } - + // handle new clubs/socs if let std::collections::hash_map::Entry::Vacant(e) = roles_db.entry(committee.id) { // create channel @@ -293,11 +293,13 @@ pub mod committee { // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) if let Some(x) = get_server_member_discord(db, id_wolves).await { if let Some(member_tmp) = x.discord { - let values = users_roles.entry(member_tmp).or_insert(vec![]); - values.push(r); + if server.member(ctx, &member_tmp).await.is_ok() { + let values = users_roles.entry(member_tmp).or_insert(vec![]); + values.push(r); - if let Some(x) = roles_db.get_mut(&committee.id) { - x.count += 1; + if let Some(x) = roles_db.get_mut(&committee.id) { + x.count += 1; + } } } } @@ -399,13 +401,13 @@ pub mod committee { } #[derive(Debug, Clone, Deserialize, Serialize)] - struct CommitteeRoles { + pub struct CommitteeRoles { id_wolves: i64, id_role: RoleId, id_channel: ChannelId, - name_role: String, + pub name_role: String, name_channel: String, - count: i64, + pub count: i64, } impl<'r> FromRow<'r, SqliteRow> for CommitteeRoles { @@ -447,7 +449,7 @@ pub mod committee { } } - async fn db_roles_get(db: &Pool) -> Vec { + pub async fn db_roles_get(db: &Pool) -> Vec { // expiry sqlx::query_as::<_, CommitteeRoles>( " diff --git a/src/main.rs b/src/main.rs index 5dab0f0..43ad82a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -130,6 +130,23 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use println!("{:?}", e) } } + + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config_global = config_lock.read().await; + + match &config_global + .committee_server + .set_commands(&ctx.http, vec![commands::committee::count::register()]) + .await + { + Ok(_) => {} + Err(e) => { + println!("{:?}", e) + } + } } async fn interaction_create(&self, ctx: Context, interaction: Interaction) { @@ -148,6 +165,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use "minecraft_add" => commands::minecraft::server::add::run(&command, &ctx).await, "minecraft_list" => commands::minecraft::server::list::run(&command, &ctx).await, "minecraft_delete" => commands::minecraft::server::delete::run(&command, &ctx).await, + "committee" => commands::committee::count::run(&command, &ctx).await, _ => "not implemented :(".to_string(), }; From 934842cbc9106d63525f08ea2f5caa3f724b2db2 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 27 Feb 2025 22:37:40 +0000 Subject: [PATCH 088/170] feat: will now properly re-order channels whenever a new club/soc is added --- src/common/set_roles.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 908e6d6..52836e1 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -363,28 +363,31 @@ pub mod committee { } let mut channel_names = vec![]; + let mut positions = vec![]; for role in roles_db.values() { // save these to db db_role_set(db, role).await; - // pull out teh channel names if re_order { - channel_names.push((role.name_channel.to_owned(), role.id_channel)); + let channel_id = role.id_channel.to_owned(); + if let Some(channel) = channels.get_mut(&channel_id) { + // record the position of each of teh C&S channels + positions.push(channel.position); + + // pull out teh channel names + channel_names.push((role.name_channel.to_owned(), channel_id)); + } } } if re_order { + // sort by the position and name + positions.sort(); channel_names.sort_by_key(|(name, _)| name.to_owned()); - // get a list of all teh new positions let mut new_positions = vec![]; for (i, (_, id)) in channel_names.iter().enumerate() { - if let Some(channel) = channels.get_mut(id) { - let position_new = i as u64; - if position_new != channel.position as u64 { - new_positions.push((channel.id.to_owned(), position_new)); - } - } + new_positions.push((id.to_owned(), positions[i] as u64)); } if !new_positions.is_empty() { From 7d7afcd00cd8f0993b26cef88c3009f0df44315d Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 27 Feb 2025 23:56:48 +0000 Subject: [PATCH 089/170] feat: new command to count the server info, available on the computer soc server --- src/commands/committee.rs | 80 ------------------- src/commands/count.rs | 156 ++++++++++++++++++++++++++++++++++++++ src/commands/mod.rs | 2 +- src/common/set_roles.rs | 2 +- src/main.rs | 26 ++++++- 5 files changed, 180 insertions(+), 86 deletions(-) delete mode 100644 src/commands/committee.rs create mode 100644 src/commands/count.rs diff --git a/src/commands/committee.rs b/src/commands/committee.rs deleted file mode 100644 index 4a4aa16..0000000 --- a/src/commands/committee.rs +++ /dev/null @@ -1,80 +0,0 @@ -pub mod count { - - // get the list of all the current clubs/socs members - - use serenity::all::{ - CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption, - }; - use skynet_discord_bot::common::database::DataBase; - use skynet_discord_bot::common::set_roles::committee::db_roles_get; - - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let sub_options = if let Some(CommandDataOption { - value: CommandDataOptionValue::SubCommand(key), - .. - }) = command.data.options.first() - { - key - } else { - return "Please provide a wolves API key".to_string(); - }; - - let all = if let Some(x) = sub_options.first() { - match x.value { - CommandDataOptionValue::Boolean(y) => y, - _ => false, - } - } else { - false - }; - - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Databse in TypeMap.").clone() - }; - let db = db_lock.read().await; - - let mut cs = vec![]; - // pull it from a DB - for committee in db_roles_get(&db).await { - if !all && committee.count == 0 { - continue; - } - cs.push((committee.count, committee.name_role.to_owned())); - } - - cs.sort_by_key(|(count, _)| *count); - cs.reverse(); - - // msg can be a max 2000 chars long - let mut limit = 2000; - - let mut response = vec![]; - for (count, name) in cs { - let leading = if count < 10 { " " } else { "" }; - - let line = format!("{}{} {}", leading, count, name); - - let length = line.len() + 1; - - if length < limit { - response.push(line); - limit -= length; - } else { - break; - } - } - - response.join("\n") - } - - pub fn register() -> CreateCommand { - CreateCommand::new("committee") - .description("Commands related to teh committees") - //.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) - .add_option( - CreateCommandOption::new(CommandOptionType::SubCommand, "list", "List out the Committee Roles Numbers") - .add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "all", "List out all the Committee Roles Numbers").required(false)), - ) - } -} diff --git a/src/commands/count.rs b/src/commands/count.rs new file mode 100644 index 0000000..bb64f68 --- /dev/null +++ b/src/commands/count.rs @@ -0,0 +1,156 @@ +pub mod committee { + + // get the list of all the current clubs/socs members + + use serenity::all::{ + CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption, + }; + use skynet_discord_bot::common::database::DataBase; + use skynet_discord_bot::common::set_roles::committee::db_roles_get; + + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + let sub_options = if let Some(CommandDataOption { + value: CommandDataOptionValue::SubCommand(key), + .. + }) = command.data.options.first() + { + key + } else { + return "Please provide a wolves API key".to_string(); + }; + + let all = if let Some(x) = sub_options.first() { + match x.value { + CommandDataOptionValue::Boolean(y) => y, + _ => false, + } + } else { + false + }; + + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Databse in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let mut cs = vec![]; + // pull it from a DB + for committee in db_roles_get(&db).await { + if !all && committee.count == 0 { + continue; + } + cs.push((committee.count, committee.name_role.to_owned())); + } + + cs.sort_by_key(|(count, _)| *count); + cs.reverse(); + + // msg can be a max 2000 chars long + let mut limit = 2000 - 3; + + let mut response = vec!["```".to_string()]; + for (count, name) in cs { + let leading = if count < 10 { " " } else { "" }; + + let line = format!("{}{} {}", leading, count, name); + + let length = line.len() + 1; + + if length < (limit + 3) { + response.push(line); + limit -= length; + } else { + break; + } + } + response.push("```".to_string()); + + response.join("\n") + } + + pub fn register() -> CreateCommand { + CreateCommand::new("count") + .description("Commands related to teh committees") + //.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommand, "committee", "List out the Committee Roles Numbers") + .add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "all", "List out all the Committee Roles Numbers").required(false)), + ) + } +} + +pub mod servers { + // get the list of all the current clubs/socs + use serenity::all::{CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption}; + use skynet_discord_bot::common::database::{get_server_config_bulk, DataBase}; + use skynet_discord_bot::common::set_roles::committee::get_committees; + use std::collections::HashMap; + + pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Databse in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let mut committees = HashMap::new(); + for committee in get_committees(&db).await { + committees.insert(committee.id, committee.to_owned()); + } + + let mut cs = vec![]; + // pull it from a DB + for server_config in get_server_config_bulk(&db).await { + if let Some(x) = committees.get(&server_config.wolves_id) { + cs.push((server_config.member_current, server_config.member_past, x.name_full.to_owned())); + } + } + + cs.sort_by_key(|(count, _, _)| *count); + cs.reverse(); + + // msg can be a max 2000 chars long + let mut limit = 2000 - 3; + + let mut response = vec!["```".to_string()]; + for (current, past, name) in cs { + let current_leading = if current < 10 { + " " + } else if current < 100 { + " " + } else { + "" + }; + let past_leading = if past < 10 { + " " + } else if past < 100 { + " " + } else { + "" + }; + + let line = format!("{}{} {}{} {}", current_leading, current, past_leading, past, name); + + let length = line.len() + 1; + + // +3 is to account for the closing fense + if length < (limit + 3) { + response.push(line); + limit -= length; + } else { + break; + } + } + response.push("```".to_string()); + + response.join("\n") + } + + pub fn register() -> CreateCommand { + CreateCommand::new("count") + .description("Commands related to all servers") + .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) + .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "servers", "List out all servers using the skynet bot")) + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index a11ffd5..76b43cb 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,5 @@ pub mod add_server; -pub mod committee; +pub mod count; pub mod link_email; pub mod minecraft; pub mod role_adder; diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 52836e1..323677f 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -469,7 +469,7 @@ pub mod committee { }) } - async fn get_committees(db: &Pool) -> Vec { + pub async fn get_committees(db: &Pool) -> Vec { sqlx::query_as::<_, Committees>( r#" SELECT * diff --git a/src/main.rs b/src/main.rs index 43ad82a..1efaed7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ pub mod commands; use crate::commands::role_adder::tools::on_role_change; -use serenity::all::{ActivityData, Command, CreateMessage, EditInteractionResponse, GuildMemberUpdateEvent, Interaction}; +use serenity::all::{ActivityData, Command, CreateMessage, EditInteractionResponse, GuildId, GuildMemberUpdateEvent, Interaction}; use serenity::model::guild::Member; use serenity::{ async_trait, @@ -139,7 +139,17 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use match &config_global .committee_server - .set_commands(&ctx.http, vec![commands::committee::count::register()]) + .set_commands(&ctx.http, vec![commands::count::committee::register()]) + .await + { + Ok(_) => {} + Err(e) => { + println!("{:?}", e) + } + } + + match GuildId::new(689189992417067052) + .set_commands(&ctx.http, vec![commands::count::servers::register()]) .await { Ok(_) => {} @@ -165,8 +175,16 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use "minecraft_add" => commands::minecraft::server::add::run(&command, &ctx).await, "minecraft_list" => commands::minecraft::server::list::run(&command, &ctx).await, "minecraft_delete" => commands::minecraft::server::delete::run(&command, &ctx).await, - "committee" => commands::committee::count::run(&command, &ctx).await, - _ => "not implemented :(".to_string(), + // sub command + "count" => match command.data.options.first() { + None => "Invalid Command".to_string(), + Some(x) => match x.name.as_str() { + "committee" => commands::count::committee::run(&command, &ctx).await, + "servers" => commands::count::servers::run(&command, &ctx).await, + &_ => format!("not implemented :( count {}", x.name.as_str()), + }, + }, + _ => format!("not implemented :( {}", command.data.name.as_str()), }; if let Err(why) = command.edit_response(&ctx.http, EditInteractionResponse::new().content(content)).await { From a11ba15f4a56a2c29247021ee56492d04d751284 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 27 Feb 2025 23:58:35 +0000 Subject: [PATCH 090/170] fix: limit the count committee to committee members and above --- src/commands/count.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/commands/count.rs b/src/commands/count.rs index bb64f68..c48a506 100644 --- a/src/commands/count.rs +++ b/src/commands/count.rs @@ -71,8 +71,9 @@ pub mod committee { pub fn register() -> CreateCommand { CreateCommand::new("count") - .description("Commands related to teh committees") - //.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) + .description("Count Committee Members") + // All committee members have teh Manage Role permission + .default_member_permissions(serenity::model::Permissions::MANAGE_ROLES) .add_option( CreateCommandOption::new(CommandOptionType::SubCommand, "committee", "List out the Committee Roles Numbers") .add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "all", "List out all the Committee Roles Numbers").required(false)), @@ -149,7 +150,7 @@ pub mod servers { pub fn register() -> CreateCommand { CreateCommand::new("count") - .description("Commands related to all servers") + .description("Count the servers") .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "servers", "List out all servers using the skynet bot")) } From b43f760fb1ff965e171bbde0440531ac17e46a9b Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Fri, 28 Feb 2025 00:05:57 +0000 Subject: [PATCH 091/170] fmt: feedback from clippy --- src/common/set_roles.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 323677f..b857e8a 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -218,8 +218,8 @@ pub mod committee { // if a club/soc ever changes their name if let Some(x) = roles_db.get_mut(&committee.id) { - x.name_role = committee.name_full.to_owned(); - x.name_channel = committee.name_profile.to_owned(); + committee.name_full.clone_into(&mut x.name_role); + committee.name_profile.clone_into(&mut x.name_channel); } // handle new clubs/socs From 058f8a7a7dc04bd9dcb26105b2a07e711fad9b93 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Fri, 28 Feb 2025 10:58:44 +0000 Subject: [PATCH 092/170] fmt: feedback from clippy --- src/commands/count.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/count.rs b/src/commands/count.rs index c48a506..d51669e 100644 --- a/src/commands/count.rs +++ b/src/commands/count.rs @@ -72,8 +72,8 @@ pub mod committee { pub fn register() -> CreateCommand { CreateCommand::new("count") .description("Count Committee Members") - // All committee members have teh Manage Role permission - .default_member_permissions(serenity::model::Permissions::MANAGE_ROLES) + // All Committee members are able to add reactions to posts + .default_member_permissions(serenity::model::Permissions::ADD_REACTIONS) .add_option( CreateCommandOption::new(CommandOptionType::SubCommand, "committee", "List out the Committee Roles Numbers") .add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "all", "List out all the Committee Roles Numbers").required(false)), From 3a39084f407392ea7f7c6a797bd0b4567d2c5f1d Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 6 Mar 2025 19:21:17 +0000 Subject: [PATCH 093/170] feat: unlink is now a subcommand of wolves --- src/commands/link_email.rs | 59 ++++++++++++++++++++++++++++++++++++++ src/main.rs | 10 +++++++ 2 files changed, 69 insertions(+) diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 71cfbb0..9120a6d 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -449,3 +449,62 @@ pub mod verify { .await } } + +pub mod wolves { + use serenity::all::{CommandOptionType, CreateCommand, CreateCommandOption}; + + pub mod unlink { + use serenity::all::{CommandInteraction, Context, UserId}; + use skynet_discord_bot::common::database::{DataBase, Wolves}; + use sqlx::{Pool, Sqlite}; + + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Databse in TypeMap.").clone() + }; + let db = db_lock.read().await; + + // dosent matter if there is one or not, it will be removed regardless + delete_link(&db, &command.user.id).await; + + "Discord link removed".to_string() + } + + async fn delete_link(db: &Pool, user: &UserId) { + match sqlx::query_as::<_, Wolves>( + " + UPDATE wolves + SET discord = NULL + WHERE discord = ?1; + ", + ) + .bind(user.get() as i64) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + dbg!(e); + } + } + } + } + + pub fn register() -> CreateCommand { + CreateCommand::new("wolves") + .description("Commands related to UL Wolves") + // All Committee members are able to add reactions to posts + .default_member_permissions(serenity::model::Permissions::ADD_REACTIONS) + .add_option(CreateCommandOption::new( + // link + + // verify + + // unlink + CommandOptionType::SubCommand, + "unlink", + "List out the Committee Roles Numbers", + )) + } +} diff --git a/src/main.rs b/src/main.rs index 1efaed7..cbfd311 100644 --- a/src/main.rs +++ b/src/main.rs @@ -117,6 +117,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use commands::role_adder::edit::register(), commands::link_email::link::register(), commands::link_email::verify::register(), + commands::link_email::wolves::register(), commands::minecraft::server::add::register(), commands::minecraft::server::list::register(), commands::minecraft::server::delete::register(), @@ -167,6 +168,15 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use let content = match command.data.name.as_str() { // user commands "link_wolves" => commands::link_email::link::run(&command, &ctx).await, + "wolves" => match command.data.options.first() { + None => "Invalid Command".to_string(), + Some(x) => match x.name.as_str() { + "unlink" => commands::link_email::wolves::unlink::run(&command, &ctx).await, + // "link" => commands::count::servers::run(&command, &ctx).await, + &_ => format!("not implemented :( wolves {}", x.name.as_str()), + }, + }, + "verify" => commands::link_email::verify::run(&command, &ctx).await, "link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await, // admin commands From aa58c97fcf2f2d7f227e3f620c3c5c18559a1e3f Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 6 Mar 2025 20:32:26 +0000 Subject: [PATCH 094/170] feat: made the other user wolves commands sub_commands --- src/commands/link_email.rs | 720 +++++++++++++++++++------------------ src/commands/minecraft.rs | 2 +- src/main.rs | 6 +- 3 files changed, 366 insertions(+), 362 deletions(-) diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs index 9120a6d..6ed5bc0 100644 --- a/src/commands/link_email.rs +++ b/src/commands/link_email.rs @@ -1,178 +1,184 @@ -use lettre::{ - message::{header, MultiPart, SinglePart}, - transport::smtp::{self, authentication::Credentials}, - Message, SmtpTransport, Transport, -}; -use maud::html; -use serenity::{builder::CreateCommand, client::Context, model::id::UserId}; -use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify}; -use skynet_discord_bot::{get_now_iso, random_string, Config}; -use sqlx::{Pool, Sqlite}; +pub mod wolves { + use lettre::{ + message::{header, MultiPart, SinglePart}, + transport::smtp::{self, authentication::Credentials}, + Message, SmtpTransport, Transport, + }; + use maud::html; + use serenity::all::CommandOptionType; + use serenity::builder::CreateCommandOption; + use serenity::{builder::CreateCommand, client::Context, model::id::UserId}; + use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify}; + use skynet_discord_bot::{get_now_iso, random_string, Config}; + use sqlx::{Pool, Sqlite}; -pub mod link { - use super::*; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}; + pub mod link { + use super::*; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Databse in TypeMap.").clone() - }; - let db = db_lock.read().await; + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Databse in TypeMap.").clone() + }; + let db = db_lock.read().await; - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config = config_lock.read().await; + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config = config_lock.read().await; - if get_server_member_discord(&db, &command.user.id).await.is_some() { - return "Already linked".to_string(); - } - - db_pending_clear_expired(&db).await; - - if get_verify_from_db(&db, &command.user.id).await.is_some() { - return "Linking already in process, please check email.".to_string(); - } - - let email = if let Some(CommandDataOption { - value: CommandDataOptionValue::String(email), - .. - }) = command.data.options.first() - { - email.trim() - } else { - return "Please provide a valid user".to_string(); - }; - - // check if email exists - let details = match get_server_member_email(&db, email).await { - None => { - let invalid_user = "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string(); - - let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); - - // see if the user actually exists - let id = match wolves.get_member(email).await { - None => { - return invalid_user; - } - Some(x) => x, - }; - - // save teh user id and email to teh db - match save_to_db_user(&db, id, email).await { - Ok(x) => x, - Err(x) => { - dbg!(x); - return "Error: unable to save user to teh database, contact Computer Society".to_string(); - } - }; - - // pull it back out (technically could do it in previous step but more explicit) - match get_server_member_email(&db, email).await { - None => { - return "Error: failed to read user from database.".to_string(); - } - Some(x) => x, - } + if get_server_member_discord(&db, &command.user.id).await.is_some() { + return "Already linked".to_string(); } - Some(x) => x, - }; - if details.discord.is_some() { - return "Email already verified".to_string(); - } + db_pending_clear_expired(&db).await; - // generate a auth key - let auth = random_string(20); - match send_mail(&config, &details.email, &auth, &command.user.name) { - Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await { - Ok(_) => {} + if get_verify_from_db(&db, &command.user.id).await.is_some() { + return "Linking already in process, please check email.".to_string(); + } + + let sub_options = if let Some(CommandDataOption { + value: CommandDataOptionValue::SubCommand(options), + .. + }) = command.data.options.first() + { + options + } else { + return "Please provide sub options".to_string(); + }; + + let email = if let Some(x) = sub_options.first() { + match &x.value { + CommandDataOptionValue::String(email) => email.trim(), + _ => return "Please provide a valid email".to_string(), + } + } else { + return "Please provide a valid email".to_string(); + }; + + // check if email exists + let details = match get_server_member_email(&db, email).await { + None => { + let invalid_user = "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string(); + + let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); + + // see if the user actually exists + let id = match wolves.get_member(email).await { + None => { + return invalid_user; + } + Some(x) => x, + }; + + // save teh user id and email to teh db + match save_to_db_user(&db, id, email).await { + Ok(x) => x, + Err(x) => { + dbg!(x); + return "Error: unable to save user to teh database, contact Computer Society".to_string(); + } + }; + + // pull it back out (technically could do it in previous step but more explicit) + match get_server_member_email(&db, email).await { + None => { + return "Error: failed to read user from database.".to_string(); + } + Some(x) => x, + } + } + Some(x) => x, + }; + + if details.discord.is_some() { + return "Email already verified".to_string(); + } + + // generate a auth key + let auth = random_string(20); + match send_mail(&config, &details.email, &auth, &command.user.name) { + Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await { + Ok(_) => {} + Err(e) => { + return format!("Unable to save to db {} {e:?}", &details.email); + } + }, Err(e) => { - return format!("Unable to save to db {} {e:?}", &details.email); + return format!("Unable to send mail to {} {e:?}", &details.email); } - }, - Err(e) => { - return format!("Unable to send mail to {} {e:?}", &details.email); } + + format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email) } - format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email) - } - - pub fn register() -> CreateCommand { - CreateCommand::new("link_wolves") - .description("Set Wolves Email") - .add_option(CreateCommandOption::new(CommandOptionType::String, "email", "UL Wolves Email").required(true)) - } - - pub async fn get_server_member_discord(db: &Pool, user: &UserId) -> Option { - sqlx::query_as::<_, Wolves>( - r#" + pub async fn get_server_member_discord(db: &Pool, user: &UserId) -> Option { + sqlx::query_as::<_, Wolves>( + r#" SELECT * FROM wolves WHERE discord = ? "#, - ) - .bind(user.get() as i64) - .fetch_one(db) - .await - .ok() - } + ) + .bind(user.get() as i64) + .fetch_one(db) + .await + .ok() + } - async fn get_server_member_email(db: &Pool, email: &str) -> Option { - sqlx::query_as::<_, Wolves>( - r#" + async fn get_server_member_email(db: &Pool, email: &str) -> Option { + sqlx::query_as::<_, Wolves>( + r#" SELECT * FROM wolves WHERE email = ? "#, - ) - .bind(email) - .fetch_one(db) - .await - .ok() - } + ) + .bind(email) + .fetch_one(db) + .await + .ok() + } - fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result { - let discord = "https://computer.discord.skynet.ie"; - let sender = format!("UL Computer Society <{}>", &config.mail_user); + fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result { + let discord = "https://computer.discord.skynet.ie"; + let sender = format!("UL Computer Society <{}>", &config.mail_user); - // Create the html we want to send. - let html = html! { - head { - title { "UL Wolves Discord Linker" } - style type="text/css" { - "h2, h4 { font-family: Arial, Helvetica, sans-serif; }" - } - } - div { - h2 { "UL Wolves Discord Linker" } + // Create the html we want to send. + let html = html! { + head { + title { "UL Wolves Discord Linker" } + style type="text/css" { + "h2, h4 { font-family: Arial, Helvetica, sans-serif; }" + } + } + div { + h2 { "UL Wolves Discord Linker" } - h3 { "Link your UL Wolves Account to Discord" } - // Substitute in the name of our recipient. - p { "Hi " (user) "," } - p { - "Please paste this line into Discord (and press enter) to verify your discord account:" - br; - pre { "/verify code: " (auth)} - } - hr; - h3 { "Help & Support" } - p { - "If you have issues please refer to our Computer Society Discord Server:" - br; - a href=(discord) { (discord) } - br; - "UL Computer Society" - } - } - }; + h3 { "Link your UL Wolves Account to Discord" } + // Substitute in the name of our recipient. + p { "Hi " (user) "," } + p { + "Please paste this line into Discord (and press enter) to verify your discord account:" + br; + pre { "/wolves verify code: " (auth)} + } + hr; + h3 { "Help & Support" } + p { + "If you have issues please refer to our Computer Society Discord Server:" + br; + a href=(discord) { (discord) } + br; + "UL Computer Society" + } + } + }; - let body_text = format!( - r#" + let body_text = format!( + r#" UL Wolves Discord Linker Link your UL Wolves Account to Discord @@ -181,7 +187,7 @@ pub mod link { Hi {user}, Please paste this line into Discord (and press enter) to verify your Discord account: - /verify code: {auth} + /wolves verify code: {auth} ------------------------------------------------------------------------- @@ -191,267 +197,267 @@ pub mod link { {discord} UL Computer Society "# - ); + ); - // Build the message. - let email = Message::builder() - .from(sender.parse().unwrap()) - .to(mail.parse().unwrap()) - .subject("Skynet: Link Discord to Wolves.") - .multipart( - // This is composed of two parts. - // also helps not trip spam settings (uneven number of url's - MultiPart::alternative() - .singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text)) - .singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())), - ) - .expect("failed to build email"); + // Build the message. + let email = Message::builder() + .from(sender.parse().unwrap()) + .to(mail.parse().unwrap()) + .subject("Skynet: Link Discord to Wolves.") + .multipart( + // This is composed of two parts. + // also helps not trip spam settings (uneven number of url's + MultiPart::alternative() + .singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text)) + .singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())), + ) + .expect("failed to build email"); - let creds = Credentials::new(config.mail_user.clone(), config.mail_pass.clone()); + let creds = Credentials::new(config.mail_user.clone(), config.mail_pass.clone()); - // Open a remote connection to gmail using STARTTLS - let mailer = SmtpTransport::starttls_relay(&config.mail_smtp)?.credentials(creds).build(); + // Open a remote connection to gmail using STARTTLS + let mailer = SmtpTransport::starttls_relay(&config.mail_smtp)?.credentials(creds).build(); - // Send the email - mailer.send(&email) - } + // Send the email + mailer.send(&email) + } - pub async fn db_pending_clear_expired(pool: &Pool) -> Option { - sqlx::query_as::<_, WolvesVerify>( - r#" + pub async fn db_pending_clear_expired(pool: &Pool) -> Option { + sqlx::query_as::<_, WolvesVerify>( + r#" DELETE FROM wolves_verify WHERE date_expiry < ? "#, - ) - .bind(get_now_iso(true)) - .fetch_one(pool) - .await - .ok() - } + ) + .bind(get_now_iso(true)) + .fetch_one(pool) + .await + .ok() + } - pub async fn get_verify_from_db(db: &Pool, user: &UserId) -> Option { - sqlx::query_as::<_, WolvesVerify>( - r#" + pub async fn get_verify_from_db(db: &Pool, user: &UserId) -> Option { + sqlx::query_as::<_, WolvesVerify>( + r#" SELECT * FROM wolves_verify WHERE discord = ? "#, - ) - .bind(user.get() as i64) - .fetch_one(db) - .await - .ok() - } + ) + .bind(user.get() as i64) + .fetch_one(db) + .await + .ok() + } - async fn save_to_db(db: &Pool, record: &Wolves, auth: &str, user: &UserId) -> Result, sqlx::Error> { - sqlx::query_as::<_, WolvesVerify>( - " + async fn save_to_db(db: &Pool, record: &Wolves, auth: &str, user: &UserId) -> Result, sqlx::Error> { + sqlx::query_as::<_, WolvesVerify>( + " INSERT INTO wolves_verify (email, discord, auth_code, date_expiry) VALUES (?1, ?2, ?3, ?4) ", - ) - .bind(record.email.to_owned()) - .bind(user.get() as i64) - .bind(auth.to_owned()) - .bind(get_now_iso(false)) - .fetch_optional(db) - .await - } + ) + .bind(record.email.to_owned()) + .bind(user.get() as i64) + .bind(auth.to_owned()) + .bind(get_now_iso(false)) + .fetch_optional(db) + .await + } - async fn save_to_db_user(db: &Pool, id_wolves: i64, email: &str) -> Result, sqlx::Error> { - sqlx::query_as::<_, Wolves>( - " + async fn save_to_db_user(db: &Pool, id_wolves: i64, email: &str) -> Result, sqlx::Error> { + sqlx::query_as::<_, Wolves>( + " INSERT INTO wolves (id_wolves, email) VALUES ($1, $2) ON CONFLICT(id_wolves) DO UPDATE SET email = $2 ", - ) - .bind(id_wolves) - .bind(email) - .fetch_optional(db) - .await - } -} - -pub mod verify { - use super::*; - use crate::commands::link_email::link::{db_pending_clear_expired, get_server_member_discord, get_verify_from_db}; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption, GuildId, RoleId}; - use serenity::model::user::User; - use skynet_discord_bot::common::database::get_server_config; - use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; - use skynet_discord_bot::common::wolves::committees::Committees; - use sqlx::Error; - - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Databse in TypeMap.").clone() - }; - let db = db_lock.read().await; - - // check if user has used /link_wolves - let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await { - x - } else { - return "Please use /link_wolves first".to_string(); - }; - - let code = if let Some(CommandDataOption { - value: CommandDataOptionValue::String(code), - .. - }) = command.data.options.first() - { - code - } else { - return "Please provide a verification code".to_string(); - }; - - db_pending_clear_expired(&db).await; - - if &details.auth_code != code { - return "Invalid verification code".to_string(); + ) + .bind(id_wolves) + .bind(email) + .fetch_optional(db) + .await } + } - match db_pending_clear_successful(&db, &command.user.id).await { - Ok(_) => { - return match set_discord(&db, &command.user.id, &details.email).await { - Ok(_) => { - // get teh right roles for the user - set_server_roles(&db, &command.user, ctx).await; + pub mod verify { + use super::*; + use crate::commands::link_email::wolves::link::{db_pending_clear_expired, get_server_member_discord, get_verify_from_db}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, GuildId, RoleId}; + use serenity::model::user::User; + use skynet_discord_bot::common::database::get_server_config; + use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; + use skynet_discord_bot::common::wolves::committees::Committees; + use sqlx::Error; - // check if they are a committee member, and on that server - set_server_roles_committee(&db, &command.user, ctx).await; + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Database in TypeMap.").clone() + }; + let db = db_lock.read().await; - "Discord username linked to Wolves".to_string() - } - Err(e) => { - println!("{:?}", e); - "Failed to save, please try /link_wolves again".to_string() - } - }; + // check if user has used /link_wolves + let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await { + x + } else { + return "Please use ''/wolves link'' first".to_string(); + }; + + let sub_options = if let Some(CommandDataOption { + value: CommandDataOptionValue::SubCommand(options), + .. + }) = command.data.options.first() + { + options + } else { + return "Please provide sub options".to_string(); + }; + + let code = if let Some(x) = sub_options.first() { + match &x.value { + CommandDataOptionValue::String(y) => y.trim(), + _ => return "Please provide a verification code".to_string(), + } + } else { + return "Please provide a verification code".to_string(); + }; + + db_pending_clear_expired(&db).await; + + if details.auth_code != code { + return "Invalid verification code".to_string(); } - Err(e) => println!("{:?}", e), + + match db_pending_clear_successful(&db, &command.user.id).await { + Ok(_) => { + return match set_discord(&db, &command.user.id, &details.email).await { + Ok(_) => { + // get teh right roles for the user + set_server_roles(&db, &command.user, ctx).await; + + // check if they are a committee member, and on that server + set_server_roles_committee(&db, &command.user, ctx).await; + + "Discord username linked to Wolves".to_string() + } + Err(e) => { + println!("{:?}", e); + "Failed to save, please try /link_wolves again".to_string() + } + }; + } + Err(e) => println!("{:?}", e), + } + + "Failed to verify".to_string() } - "Failed to verify".to_string() - } - - pub fn register() -> CreateCommand { - CreateCommand::new("verify") - .description("Verify Wolves Email") - .add_option(CreateCommandOption::new(CommandOptionType::String, "code", "Code from verification email").required(true)) - } - - async fn db_pending_clear_successful(pool: &Pool, user: &UserId) -> Result, Error> { - sqlx::query_as::<_, WolvesVerify>( - r#" + async fn db_pending_clear_successful(pool: &Pool, user: &UserId) -> Result, Error> { + sqlx::query_as::<_, WolvesVerify>( + r#" DELETE FROM wolves_verify WHERE discord = ? "#, - ) - .bind(user.get() as i64) - .fetch_optional(pool) - .await - } + ) + .bind(user.get() as i64) + .fetch_optional(pool) + .await + } - async fn set_discord(db: &Pool, discord: &UserId, email: &str) -> Result, Error> { - sqlx::query_as::<_, Wolves>( - " + async fn set_discord(db: &Pool, discord: &UserId, email: &str) -> Result, Error> { + sqlx::query_as::<_, Wolves>( + " UPDATE wolves SET discord = ? WHERE email = ? ", - ) - .bind(discord.get() as i64) - .bind(email) - .fetch_optional(db) - .await - } + ) + .bind(discord.get() as i64) + .bind(email) + .fetch_optional(db) + .await + } - async fn set_server_roles(db: &Pool, discord: &User, ctx: &Context) { - if let Ok(servers) = get_servers(db, &discord.id).await { - for server in servers { - if let Ok(member) = server.server.member(&ctx.http, &discord.id).await { - if let Some(config) = get_server_config(db, &server.server).await { - let Servers { - role_past, - role_current, - .. - } = config; + async fn set_server_roles(db: &Pool, discord: &User, ctx: &Context) { + if let Ok(servers) = get_servers(db, &discord.id).await { + for server in servers { + if let Ok(member) = server.server.member(&ctx.http, &discord.id).await { + if let Some(config) = get_server_config(db, &server.server).await { + let Servers { + role_past, + role_current, + .. + } = config; - let mut roles = vec![]; + let mut roles = vec![]; - if let Some(role) = &role_past { - if !member.roles.contains(role) { - roles.push(role.to_owned()); + if let Some(role) = &role_past { + if !member.roles.contains(role) { + roles.push(role.to_owned()); + } } - } - if !member.roles.contains(&role_current) { - roles.push(role_current.to_owned()); - } + if !member.roles.contains(&role_current) { + roles.push(role_current.to_owned()); + } - if let Err(e) = member.add_roles(&ctx, &roles).await { - println!("{:?}", e); + if let Err(e) = member.add_roles(&ctx, &roles).await { + println!("{:?}", e); + } } } } } } - } - async fn get_committees_id(db: &Pool, wolves_id: i64) -> Vec { - sqlx::query_as::<_, Committees>( - r#" + async fn get_committees_id(db: &Pool, wolves_id: i64) -> Vec { + sqlx::query_as::<_, Committees>( + r#" SELECT * FROM committees WHERE committee LIKE ?1 "#, - ) - .bind(format!("%{}%", wolves_id)) - .fetch_all(db) - .await - .unwrap_or_else(|e| { - dbg!(e); - vec![] - }) - } + ) + .bind(format!("%{}%", wolves_id)) + .fetch_all(db) + .await + .unwrap_or_else(|e| { + dbg!(e); + vec![] + }) + } - async fn set_server_roles_committee(db: &Pool, discord: &User, ctx: &Context) { - if let Some(x) = get_server_member_discord(db, &discord.id).await { - // if they are a member of one or more committees, and in teh committee server then give the teh general committee role - // they will get teh more specific vanity role later - if !get_committees_id(db, x.id_wolves).await.is_empty() { - let server = GuildId::new(1220150752656363520); - let committee_member = RoleId::new(1226602779968274573); + async fn set_server_roles_committee(db: &Pool, discord: &User, ctx: &Context) { + if let Some(x) = get_server_member_discord(db, &discord.id).await { + // if they are a member of one or more committees, and in teh committee server then give the teh general committee role + // they will get teh more specific vanity role later + if !get_committees_id(db, x.id_wolves).await.is_empty() { + let server = GuildId::new(1220150752656363520); + let committee_member = RoleId::new(1226602779968274573); - if let Ok(member) = server.member(ctx, &discord.id).await { - member.add_roles(&ctx, &[committee_member]).await.unwrap_or_default(); + if let Ok(member) = server.member(ctx, &discord.id).await { + member.add_roles(&ctx, &[committee_member]).await.unwrap_or_default(); + } } } } - } - async fn get_servers(db: &Pool, discord: &UserId) -> Result, Error> { - sqlx::query_as::<_, ServerMembersWolves>( - " + async fn get_servers(db: &Pool, discord: &UserId) -> Result, Error> { + sqlx::query_as::<_, ServerMembersWolves>( + " SELECT * FROM server_members JOIN wolves USING (id_wolves) WHERE discord = ? ", - ) - .bind(discord.get() as i64) - .fetch_all(db) - .await + ) + .bind(discord.get() as i64) + .fetch_all(db) + .await + } } -} - -pub mod wolves { - use serenity::all::{CommandOptionType, CreateCommand, CreateCommandOption}; pub mod unlink { use serenity::all::{CommandInteraction, Context, UserId}; @@ -494,17 +500,17 @@ pub mod wolves { pub fn register() -> CreateCommand { CreateCommand::new("wolves") .description("Commands related to UL Wolves") - // All Committee members are able to add reactions to posts - .default_member_permissions(serenity::model::Permissions::ADD_REACTIONS) - .add_option(CreateCommandOption::new( - // link - - // verify - - // unlink - CommandOptionType::SubCommand, - "unlink", - "List out the Committee Roles Numbers", - )) + // link + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommand, "link", "Link your Wolves account to your Discord") + .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "email", "UL Wolves Email").required(true)), + ) + // verify + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommand, "verify", "Verify Wolves Email") + .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "code", "Code from verification email").required(true)), + ) + // unlink + .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "unlink", "Unlink your Wolves account from your Discord")) } } diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 5f1321a..d476e88 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -7,7 +7,7 @@ pub(crate) mod user { use super::*; pub(crate) mod add { use super::*; - use crate::commands::link_email::link::get_server_member_discord; + use crate::commands::link_email::wolves::link::get_server_member_discord; use serde::{Deserialize, Serialize}; use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; use serenity::model::id::UserId; diff --git a/src/main.rs b/src/main.rs index cbfd311..d35dfe5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -115,8 +115,6 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use vec![ commands::add_server::register(), commands::role_adder::edit::register(), - commands::link_email::link::register(), - commands::link_email::verify::register(), commands::link_email::wolves::register(), commands::minecraft::server::add::register(), commands::minecraft::server::list::register(), @@ -167,17 +165,17 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use let content = match command.data.name.as_str() { // user commands - "link_wolves" => commands::link_email::link::run(&command, &ctx).await, "wolves" => match command.data.options.first() { None => "Invalid Command".to_string(), Some(x) => match x.name.as_str() { + "link" => commands::link_email::wolves::link::run(&command, &ctx).await, + "verify" => commands::link_email::wolves::verify::run(&command, &ctx).await, "unlink" => commands::link_email::wolves::unlink::run(&command, &ctx).await, // "link" => commands::count::servers::run(&command, &ctx).await, &_ => format!("not implemented :( wolves {}", x.name.as_str()), }, }, - "verify" => commands::link_email::verify::run(&command, &ctx).await, "link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await, // admin commands "add" => commands::add_server::run(&command, &ctx).await, From 052f6aecb2dac6676c9da592dc0c655e60b4585e Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 6 Mar 2025 20:32:39 +0000 Subject: [PATCH 095/170] doc: updated the docs for the suer sub-commands --- doc/User.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/User.md b/doc/User.md index fec6abe..f2f050e 100644 --- a/doc/User.md +++ b/doc/User.md @@ -8,13 +8,13 @@ This is to link your Discord account with your UL Wolves account. **You will only need to do this once**. ### Setup -1. In a Discord server with the Skynet Bot enter ``/link_wolves YOUR_WOLVES_CONTACT_EMAIL`` +1. In a Discord server with the Skynet Bot enter ``/wolves link YOUR_WOLVES_CONTACT_EMAIL`` link process start * Your ``YOUR_WOLVES_CONTACT_EMAIL`` is the email in the Contact Email here: * This is most likely your student mail 2. An email will be sent to you with a verification code. signup email -3. Verify the code using ``/verify CODE_FROM_EMAIL`` in Discord. +3. Verify the code using ``/wolves verify CODE_FROM_EMAIL`` in Discord. verify in discord 4. Once complete your Wolves and Discord accounts will be linked. From 0e6a5d3455f8a85ec31b8b54bbad30910279c1ee Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 6 Mar 2025 20:43:36 +0000 Subject: [PATCH 096/170] fmt: re-organised it o better reflect what teh commands in teh file are for --- src/commands/link_email.rs | 516 ------------------------------------- src/commands/minecraft.rs | 2 +- src/commands/mod.rs | 2 +- src/commands/wolves.rs | 515 ++++++++++++++++++++++++++++++++++++ src/main.rs | 8 +- 5 files changed, 521 insertions(+), 522 deletions(-) delete mode 100644 src/commands/link_email.rs create mode 100644 src/commands/wolves.rs diff --git a/src/commands/link_email.rs b/src/commands/link_email.rs deleted file mode 100644 index 6ed5bc0..0000000 --- a/src/commands/link_email.rs +++ /dev/null @@ -1,516 +0,0 @@ -pub mod wolves { - use lettre::{ - message::{header, MultiPart, SinglePart}, - transport::smtp::{self, authentication::Credentials}, - Message, SmtpTransport, Transport, - }; - use maud::html; - use serenity::all::CommandOptionType; - use serenity::builder::CreateCommandOption; - use serenity::{builder::CreateCommand, client::Context, model::id::UserId}; - use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify}; - use skynet_discord_bot::{get_now_iso, random_string, Config}; - use sqlx::{Pool, Sqlite}; - - pub mod link { - use super::*; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; - - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Databse in TypeMap.").clone() - }; - let db = db_lock.read().await; - - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config = config_lock.read().await; - - if get_server_member_discord(&db, &command.user.id).await.is_some() { - return "Already linked".to_string(); - } - - db_pending_clear_expired(&db).await; - - if get_verify_from_db(&db, &command.user.id).await.is_some() { - return "Linking already in process, please check email.".to_string(); - } - - let sub_options = if let Some(CommandDataOption { - value: CommandDataOptionValue::SubCommand(options), - .. - }) = command.data.options.first() - { - options - } else { - return "Please provide sub options".to_string(); - }; - - let email = if let Some(x) = sub_options.first() { - match &x.value { - CommandDataOptionValue::String(email) => email.trim(), - _ => return "Please provide a valid email".to_string(), - } - } else { - return "Please provide a valid email".to_string(); - }; - - // check if email exists - let details = match get_server_member_email(&db, email).await { - None => { - let invalid_user = "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string(); - - let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); - - // see if the user actually exists - let id = match wolves.get_member(email).await { - None => { - return invalid_user; - } - Some(x) => x, - }; - - // save teh user id and email to teh db - match save_to_db_user(&db, id, email).await { - Ok(x) => x, - Err(x) => { - dbg!(x); - return "Error: unable to save user to teh database, contact Computer Society".to_string(); - } - }; - - // pull it back out (technically could do it in previous step but more explicit) - match get_server_member_email(&db, email).await { - None => { - return "Error: failed to read user from database.".to_string(); - } - Some(x) => x, - } - } - Some(x) => x, - }; - - if details.discord.is_some() { - return "Email already verified".to_string(); - } - - // generate a auth key - let auth = random_string(20); - match send_mail(&config, &details.email, &auth, &command.user.name) { - Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await { - Ok(_) => {} - Err(e) => { - return format!("Unable to save to db {} {e:?}", &details.email); - } - }, - Err(e) => { - return format!("Unable to send mail to {} {e:?}", &details.email); - } - } - - format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email) - } - - pub async fn get_server_member_discord(db: &Pool, user: &UserId) -> Option { - sqlx::query_as::<_, Wolves>( - r#" - SELECT * - FROM wolves - WHERE discord = ? - "#, - ) - .bind(user.get() as i64) - .fetch_one(db) - .await - .ok() - } - - async fn get_server_member_email(db: &Pool, email: &str) -> Option { - sqlx::query_as::<_, Wolves>( - r#" - SELECT * - FROM wolves - WHERE email = ? - "#, - ) - .bind(email) - .fetch_one(db) - .await - .ok() - } - - fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result { - let discord = "https://computer.discord.skynet.ie"; - let sender = format!("UL Computer Society <{}>", &config.mail_user); - - // Create the html we want to send. - let html = html! { - head { - title { "UL Wolves Discord Linker" } - style type="text/css" { - "h2, h4 { font-family: Arial, Helvetica, sans-serif; }" - } - } - div { - h2 { "UL Wolves Discord Linker" } - - h3 { "Link your UL Wolves Account to Discord" } - // Substitute in the name of our recipient. - p { "Hi " (user) "," } - p { - "Please paste this line into Discord (and press enter) to verify your discord account:" - br; - pre { "/wolves verify code: " (auth)} - } - hr; - h3 { "Help & Support" } - p { - "If you have issues please refer to our Computer Society Discord Server:" - br; - a href=(discord) { (discord) } - br; - "UL Computer Society" - } - } - }; - - let body_text = format!( - r#" - UL Wolves Discord Linker - Link your UL Wolves Account to Discord - - Link your Account - - Hi {user}, - - Please paste this line into Discord (and press enter) to verify your Discord account: - /wolves verify code: {auth} - - ------------------------------------------------------------------------- - - Help & Support - - If you have issues please refer to our Computer Society Discord Server: - {discord} - UL Computer Society - "# - ); - - // Build the message. - let email = Message::builder() - .from(sender.parse().unwrap()) - .to(mail.parse().unwrap()) - .subject("Skynet: Link Discord to Wolves.") - .multipart( - // This is composed of two parts. - // also helps not trip spam settings (uneven number of url's - MultiPart::alternative() - .singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text)) - .singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())), - ) - .expect("failed to build email"); - - let creds = Credentials::new(config.mail_user.clone(), config.mail_pass.clone()); - - // Open a remote connection to gmail using STARTTLS - let mailer = SmtpTransport::starttls_relay(&config.mail_smtp)?.credentials(creds).build(); - - // Send the email - mailer.send(&email) - } - - pub async fn db_pending_clear_expired(pool: &Pool) -> Option { - sqlx::query_as::<_, WolvesVerify>( - r#" - DELETE - FROM wolves_verify - WHERE date_expiry < ? - "#, - ) - .bind(get_now_iso(true)) - .fetch_one(pool) - .await - .ok() - } - - pub async fn get_verify_from_db(db: &Pool, user: &UserId) -> Option { - sqlx::query_as::<_, WolvesVerify>( - r#" - SELECT * - FROM wolves_verify - WHERE discord = ? - "#, - ) - .bind(user.get() as i64) - .fetch_one(db) - .await - .ok() - } - - async fn save_to_db(db: &Pool, record: &Wolves, auth: &str, user: &UserId) -> Result, sqlx::Error> { - sqlx::query_as::<_, WolvesVerify>( - " - INSERT INTO wolves_verify (email, discord, auth_code, date_expiry) - VALUES (?1, ?2, ?3, ?4) - ", - ) - .bind(record.email.to_owned()) - .bind(user.get() as i64) - .bind(auth.to_owned()) - .bind(get_now_iso(false)) - .fetch_optional(db) - .await - } - - async fn save_to_db_user(db: &Pool, id_wolves: i64, email: &str) -> Result, sqlx::Error> { - sqlx::query_as::<_, Wolves>( - " - INSERT INTO wolves (id_wolves, email) - VALUES ($1, $2) - ON CONFLICT(id_wolves) DO UPDATE SET email = $2 - ", - ) - .bind(id_wolves) - .bind(email) - .fetch_optional(db) - .await - } - } - - pub mod verify { - use super::*; - use crate::commands::link_email::wolves::link::{db_pending_clear_expired, get_server_member_discord, get_verify_from_db}; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, GuildId, RoleId}; - use serenity::model::user::User; - use skynet_discord_bot::common::database::get_server_config; - use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; - use skynet_discord_bot::common::wolves::committees::Committees; - use sqlx::Error; - - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Database in TypeMap.").clone() - }; - let db = db_lock.read().await; - - // check if user has used /link_wolves - let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await { - x - } else { - return "Please use ''/wolves link'' first".to_string(); - }; - - let sub_options = if let Some(CommandDataOption { - value: CommandDataOptionValue::SubCommand(options), - .. - }) = command.data.options.first() - { - options - } else { - return "Please provide sub options".to_string(); - }; - - let code = if let Some(x) = sub_options.first() { - match &x.value { - CommandDataOptionValue::String(y) => y.trim(), - _ => return "Please provide a verification code".to_string(), - } - } else { - return "Please provide a verification code".to_string(); - }; - - db_pending_clear_expired(&db).await; - - if details.auth_code != code { - return "Invalid verification code".to_string(); - } - - match db_pending_clear_successful(&db, &command.user.id).await { - Ok(_) => { - return match set_discord(&db, &command.user.id, &details.email).await { - Ok(_) => { - // get teh right roles for the user - set_server_roles(&db, &command.user, ctx).await; - - // check if they are a committee member, and on that server - set_server_roles_committee(&db, &command.user, ctx).await; - - "Discord username linked to Wolves".to_string() - } - Err(e) => { - println!("{:?}", e); - "Failed to save, please try /link_wolves again".to_string() - } - }; - } - Err(e) => println!("{:?}", e), - } - - "Failed to verify".to_string() - } - - async fn db_pending_clear_successful(pool: &Pool, user: &UserId) -> Result, Error> { - sqlx::query_as::<_, WolvesVerify>( - r#" - DELETE - FROM wolves_verify - WHERE discord = ? - "#, - ) - .bind(user.get() as i64) - .fetch_optional(pool) - .await - } - - async fn set_discord(db: &Pool, discord: &UserId, email: &str) -> Result, Error> { - sqlx::query_as::<_, Wolves>( - " - UPDATE wolves - SET discord = ? - WHERE email = ? - ", - ) - .bind(discord.get() as i64) - .bind(email) - .fetch_optional(db) - .await - } - - async fn set_server_roles(db: &Pool, discord: &User, ctx: &Context) { - if let Ok(servers) = get_servers(db, &discord.id).await { - for server in servers { - if let Ok(member) = server.server.member(&ctx.http, &discord.id).await { - if let Some(config) = get_server_config(db, &server.server).await { - let Servers { - role_past, - role_current, - .. - } = config; - - let mut roles = vec![]; - - if let Some(role) = &role_past { - if !member.roles.contains(role) { - roles.push(role.to_owned()); - } - } - - if !member.roles.contains(&role_current) { - roles.push(role_current.to_owned()); - } - - if let Err(e) = member.add_roles(&ctx, &roles).await { - println!("{:?}", e); - } - } - } - } - } - } - - async fn get_committees_id(db: &Pool, wolves_id: i64) -> Vec { - sqlx::query_as::<_, Committees>( - r#" - SELECT * - FROM committees - WHERE committee LIKE ?1 - "#, - ) - .bind(format!("%{}%", wolves_id)) - .fetch_all(db) - .await - .unwrap_or_else(|e| { - dbg!(e); - vec![] - }) - } - - async fn set_server_roles_committee(db: &Pool, discord: &User, ctx: &Context) { - if let Some(x) = get_server_member_discord(db, &discord.id).await { - // if they are a member of one or more committees, and in teh committee server then give the teh general committee role - // they will get teh more specific vanity role later - if !get_committees_id(db, x.id_wolves).await.is_empty() { - let server = GuildId::new(1220150752656363520); - let committee_member = RoleId::new(1226602779968274573); - - if let Ok(member) = server.member(ctx, &discord.id).await { - member.add_roles(&ctx, &[committee_member]).await.unwrap_or_default(); - } - } - } - } - - async fn get_servers(db: &Pool, discord: &UserId) -> Result, Error> { - sqlx::query_as::<_, ServerMembersWolves>( - " - SELECT * - FROM server_members - JOIN wolves USING (id_wolves) - WHERE discord = ? - ", - ) - .bind(discord.get() as i64) - .fetch_all(db) - .await - } - } - - pub mod unlink { - use serenity::all::{CommandInteraction, Context, UserId}; - use skynet_discord_bot::common::database::{DataBase, Wolves}; - use sqlx::{Pool, Sqlite}; - - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Databse in TypeMap.").clone() - }; - let db = db_lock.read().await; - - // dosent matter if there is one or not, it will be removed regardless - delete_link(&db, &command.user.id).await; - - "Discord link removed".to_string() - } - - async fn delete_link(db: &Pool, user: &UserId) { - match sqlx::query_as::<_, Wolves>( - " - UPDATE wolves - SET discord = NULL - WHERE discord = ?1; - ", - ) - .bind(user.get() as i64) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - dbg!(e); - } - } - } - } - - pub fn register() -> CreateCommand { - CreateCommand::new("wolves") - .description("Commands related to UL Wolves") - // link - .add_option( - CreateCommandOption::new(CommandOptionType::SubCommand, "link", "Link your Wolves account to your Discord") - .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "email", "UL Wolves Email").required(true)), - ) - // verify - .add_option( - CreateCommandOption::new(CommandOptionType::SubCommand, "verify", "Verify Wolves Email") - .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "code", "Code from verification email").required(true)), - ) - // unlink - .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "unlink", "Unlink your Wolves account from your Discord")) - } -} diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index d476e88..124179c 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -7,7 +7,7 @@ pub(crate) mod user { use super::*; pub(crate) mod add { use super::*; - use crate::commands::link_email::wolves::link::get_server_member_discord; + use crate::commands::wolves::link::get_server_member_discord; use serde::{Deserialize, Serialize}; use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; use serenity::model::id::UserId; diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 76b43cb..9ab59ac 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,5 +1,5 @@ pub mod add_server; pub mod count; -pub mod link_email; pub mod minecraft; pub mod role_adder; +pub mod wolves; diff --git a/src/commands/wolves.rs b/src/commands/wolves.rs new file mode 100644 index 0000000..533e379 --- /dev/null +++ b/src/commands/wolves.rs @@ -0,0 +1,515 @@ + +use lettre::{ + message::{header, MultiPart, SinglePart}, + transport::smtp::{self, authentication::Credentials}, + Message, SmtpTransport, Transport, +}; +use maud::html; +use serenity::all::CommandOptionType; +use serenity::builder::CreateCommandOption; +use serenity::{builder::CreateCommand, client::Context, model::id::UserId}; +use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify}; +use skynet_discord_bot::{get_now_iso, random_string, Config}; +use sqlx::{Pool, Sqlite}; + +pub mod link { + use super::*; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; + + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Databse in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config = config_lock.read().await; + + if get_server_member_discord(&db, &command.user.id).await.is_some() { + return "Already linked".to_string(); + } + + db_pending_clear_expired(&db).await; + + if get_verify_from_db(&db, &command.user.id).await.is_some() { + return "Linking already in process, please check email.".to_string(); + } + + let sub_options = if let Some(CommandDataOption { + value: CommandDataOptionValue::SubCommand(options), + .. + }) = command.data.options.first() + { + options + } else { + return "Please provide sub options".to_string(); + }; + + let email = if let Some(x) = sub_options.first() { + match &x.value { + CommandDataOptionValue::String(email) => email.trim(), + _ => return "Please provide a valid email".to_string(), + } + } else { + return "Please provide a valid email".to_string(); + }; + + // check if email exists + let details = match get_server_member_email(&db, email).await { + None => { + let invalid_user = "Please check it matches (including case) your preferred contact on https://ulwolves.ie/memberships/profile and that you are fully paid up.".to_string(); + + let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api)); + + // see if the user actually exists + let id = match wolves.get_member(email).await { + None => { + return invalid_user; + } + Some(x) => x, + }; + + // save teh user id and email to teh db + match save_to_db_user(&db, id, email).await { + Ok(x) => x, + Err(x) => { + dbg!(x); + return "Error: unable to save user to teh database, contact Computer Society".to_string(); + } + }; + + // pull it back out (technically could do it in previous step but more explicit) + match get_server_member_email(&db, email).await { + None => { + return "Error: failed to read user from database.".to_string(); + } + Some(x) => x, + } + } + Some(x) => x, + }; + + if details.discord.is_some() { + return "Email already verified".to_string(); + } + + // generate a auth key + let auth = random_string(20); + match send_mail(&config, &details.email, &auth, &command.user.name) { + Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await { + Ok(_) => {} + Err(e) => { + return format!("Unable to save to db {} {e:?}", &details.email); + } + }, + Err(e) => { + return format!("Unable to send mail to {} {e:?}", &details.email); + } + } + + format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email) + } + + pub async fn get_server_member_discord(db: &Pool, user: &UserId) -> Option { + sqlx::query_as::<_, Wolves>( + r#" + SELECT * + FROM wolves + WHERE discord = ? + "#, + ) + .bind(user.get() as i64) + .fetch_one(db) + .await + .ok() + } + + async fn get_server_member_email(db: &Pool, email: &str) -> Option { + sqlx::query_as::<_, Wolves>( + r#" + SELECT * + FROM wolves + WHERE email = ? + "#, + ) + .bind(email) + .fetch_one(db) + .await + .ok() + } + + fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result { + let discord = "https://computer.discord.skynet.ie"; + let sender = format!("UL Computer Society <{}>", &config.mail_user); + + // Create the html we want to send. + let html = html! { + head { + title { "UL Wolves Discord Linker" } + style type="text/css" { + "h2, h4 { font-family: Arial, Helvetica, sans-serif; }" + } + } + div { + h2 { "UL Wolves Discord Linker" } + + h3 { "Link your UL Wolves Account to Discord" } + // Substitute in the name of our recipient. + p { "Hi " (user) "," } + p { + "Please paste this line into Discord (and press enter) to verify your discord account:" + br; + pre { "/wolves verify code: " (auth)} + } + hr; + h3 { "Help & Support" } + p { + "If you have issues please refer to our Computer Society Discord Server:" + br; + a href=(discord) { (discord) } + br; + "UL Computer Society" + } + } + }; + + let body_text = format!( + r#" + UL Wolves Discord Linker + Link your UL Wolves Account to Discord + + Link your Account + + Hi {user}, + + Please paste this line into Discord (and press enter) to verify your Discord account: + /wolves verify code: {auth} + + ------------------------------------------------------------------------- + + Help & Support + + If you have issues please refer to our Computer Society Discord Server: + {discord} + UL Computer Society + "# + ); + + // Build the message. + let email = Message::builder() + .from(sender.parse().unwrap()) + .to(mail.parse().unwrap()) + .subject("Skynet: Link Discord to Wolves.") + .multipart( + // This is composed of two parts. + // also helps not trip spam settings (uneven number of url's + MultiPart::alternative() + .singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text)) + .singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())), + ) + .expect("failed to build email"); + + let creds = Credentials::new(config.mail_user.clone(), config.mail_pass.clone()); + + // Open a remote connection to gmail using STARTTLS + let mailer = SmtpTransport::starttls_relay(&config.mail_smtp)?.credentials(creds).build(); + + // Send the email + mailer.send(&email) + } + + pub async fn db_pending_clear_expired(pool: &Pool) -> Option { + sqlx::query_as::<_, WolvesVerify>( + r#" + DELETE + FROM wolves_verify + WHERE date_expiry < ? + "#, + ) + .bind(get_now_iso(true)) + .fetch_one(pool) + .await + .ok() + } + + pub async fn get_verify_from_db(db: &Pool, user: &UserId) -> Option { + sqlx::query_as::<_, WolvesVerify>( + r#" + SELECT * + FROM wolves_verify + WHERE discord = ? + "#, + ) + .bind(user.get() as i64) + .fetch_one(db) + .await + .ok() + } + + async fn save_to_db(db: &Pool, record: &Wolves, auth: &str, user: &UserId) -> Result, sqlx::Error> { + sqlx::query_as::<_, WolvesVerify>( + " + INSERT INTO wolves_verify (email, discord, auth_code, date_expiry) + VALUES (?1, ?2, ?3, ?4) + ", + ) + .bind(record.email.to_owned()) + .bind(user.get() as i64) + .bind(auth.to_owned()) + .bind(get_now_iso(false)) + .fetch_optional(db) + .await + } + + async fn save_to_db_user(db: &Pool, id_wolves: i64, email: &str) -> Result, sqlx::Error> { + sqlx::query_as::<_, Wolves>( + " + INSERT INTO wolves (id_wolves, email) + VALUES ($1, $2) + ON CONFLICT(id_wolves) DO UPDATE SET email = $2 + ", + ) + .bind(id_wolves) + .bind(email) + .fetch_optional(db) + .await + } +} + +pub mod verify { + use super::*; + use crate::commands::wolves::link::{db_pending_clear_expired, get_server_member_discord, get_verify_from_db}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, GuildId, RoleId}; + use serenity::model::user::User; + use skynet_discord_bot::common::database::get_server_config; + use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; + use skynet_discord_bot::common::wolves::committees::Committees; + use sqlx::Error; + + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Database in TypeMap.").clone() + }; + let db = db_lock.read().await; + + // check if user has used /link_wolves + let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await { + x + } else { + return "Please use ''/wolves link'' first".to_string(); + }; + + let sub_options = if let Some(CommandDataOption { + value: CommandDataOptionValue::SubCommand(options), + .. + }) = command.data.options.first() + { + options + } else { + return "Please provide sub options".to_string(); + }; + + let code = if let Some(x) = sub_options.first() { + match &x.value { + CommandDataOptionValue::String(y) => y.trim(), + _ => return "Please provide a verification code".to_string(), + } + } else { + return "Please provide a verification code".to_string(); + }; + + db_pending_clear_expired(&db).await; + + if details.auth_code != code { + return "Invalid verification code".to_string(); + } + + match db_pending_clear_successful(&db, &command.user.id).await { + Ok(_) => { + return match set_discord(&db, &command.user.id, &details.email).await { + Ok(_) => { + // get teh right roles for the user + set_server_roles(&db, &command.user, ctx).await; + + // check if they are a committee member, and on that server + set_server_roles_committee(&db, &command.user, ctx).await; + + "Discord username linked to Wolves".to_string() + } + Err(e) => { + println!("{:?}", e); + "Failed to save, please try /link_wolves again".to_string() + } + }; + } + Err(e) => println!("{:?}", e), + } + + "Failed to verify".to_string() + } + + async fn db_pending_clear_successful(pool: &Pool, user: &UserId) -> Result, Error> { + sqlx::query_as::<_, WolvesVerify>( + r#" + DELETE + FROM wolves_verify + WHERE discord = ? + "#, + ) + .bind(user.get() as i64) + .fetch_optional(pool) + .await + } + + async fn set_discord(db: &Pool, discord: &UserId, email: &str) -> Result, Error> { + sqlx::query_as::<_, Wolves>( + " + UPDATE wolves + SET discord = ? + WHERE email = ? + ", + ) + .bind(discord.get() as i64) + .bind(email) + .fetch_optional(db) + .await + } + + async fn set_server_roles(db: &Pool, discord: &User, ctx: &Context) { + if let Ok(servers) = get_servers(db, &discord.id).await { + for server in servers { + if let Ok(member) = server.server.member(&ctx.http, &discord.id).await { + if let Some(config) = get_server_config(db, &server.server).await { + let Servers { + role_past, + role_current, + .. + } = config; + + let mut roles = vec![]; + + if let Some(role) = &role_past { + if !member.roles.contains(role) { + roles.push(role.to_owned()); + } + } + + if !member.roles.contains(&role_current) { + roles.push(role_current.to_owned()); + } + + if let Err(e) = member.add_roles(&ctx, &roles).await { + println!("{:?}", e); + } + } + } + } + } + } + + async fn get_committees_id(db: &Pool, wolves_id: i64) -> Vec { + sqlx::query_as::<_, Committees>( + r#" + SELECT * + FROM committees + WHERE committee LIKE ?1 + "#, + ) + .bind(format!("%{}%", wolves_id)) + .fetch_all(db) + .await + .unwrap_or_else(|e| { + dbg!(e); + vec![] + }) + } + + async fn set_server_roles_committee(db: &Pool, discord: &User, ctx: &Context) { + if let Some(x) = get_server_member_discord(db, &discord.id).await { + // if they are a member of one or more committees, and in teh committee server then give the teh general committee role + // they will get teh more specific vanity role later + if !get_committees_id(db, x.id_wolves).await.is_empty() { + let server = GuildId::new(1220150752656363520); + let committee_member = RoleId::new(1226602779968274573); + + if let Ok(member) = server.member(ctx, &discord.id).await { + member.add_roles(&ctx, &[committee_member]).await.unwrap_or_default(); + } + } + } + } + + async fn get_servers(db: &Pool, discord: &UserId) -> Result, Error> { + sqlx::query_as::<_, ServerMembersWolves>( + " + SELECT * + FROM server_members + JOIN wolves USING (id_wolves) + WHERE discord = ? + ", + ) + .bind(discord.get() as i64) + .fetch_all(db) + .await + } +} + +pub mod unlink { + use serenity::all::{CommandInteraction, Context, UserId}; + use skynet_discord_bot::common::database::{DataBase, Wolves}; + use sqlx::{Pool, Sqlite}; + + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Databse in TypeMap.").clone() + }; + let db = db_lock.read().await; + + // dosent matter if there is one or not, it will be removed regardless + delete_link(&db, &command.user.id).await; + + "Discord link removed".to_string() + } + + async fn delete_link(db: &Pool, user: &UserId) { + match sqlx::query_as::<_, Wolves>( + " + UPDATE wolves + SET discord = NULL + WHERE discord = ?1; + ", + ) + .bind(user.get() as i64) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + dbg!(e); + } + } + } +} + +pub fn register() -> CreateCommand { + CreateCommand::new("wolves") + .description("Commands related to UL Wolves") + // link + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommand, "link", "Link your Wolves account to your Discord") + .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "email", "UL Wolves Email").required(true)), + ) + // verify + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommand, "verify", "Verify Wolves Email") + .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "code", "Code from verification email").required(true)), + ) + // unlink + .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "unlink", "Unlink your Wolves account from your Discord")) +} diff --git a/src/main.rs b/src/main.rs index d35dfe5..c270b1b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -115,7 +115,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use vec![ commands::add_server::register(), commands::role_adder::edit::register(), - commands::link_email::wolves::register(), + commands::wolves::register(), commands::minecraft::server::add::register(), commands::minecraft::server::list::register(), commands::minecraft::server::delete::register(), @@ -168,9 +168,9 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use "wolves" => match command.data.options.first() { None => "Invalid Command".to_string(), Some(x) => match x.name.as_str() { - "link" => commands::link_email::wolves::link::run(&command, &ctx).await, - "verify" => commands::link_email::wolves::verify::run(&command, &ctx).await, - "unlink" => commands::link_email::wolves::unlink::run(&command, &ctx).await, + "link" => commands::wolves::link::run(&command, &ctx).await, + "verify" => commands::wolves::verify::run(&command, &ctx).await, + "unlink" => commands::wolves::unlink::run(&command, &ctx).await, // "link" => commands::count::servers::run(&command, &ctx).await, &_ => format!("not implemented :( wolves {}", x.name.as_str()), }, From df032f2d7be95ed1a4ea37d27dec5778bcf230b8 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 6 Mar 2025 21:35:17 +0000 Subject: [PATCH 097/170] feat: updated the core committee commands --- doc/Committee.md | 2 +- src/commands/add_server.rs | 62 +++++++++++++++++------------------ src/commands/committee.rs | 23 +++++++++++++ src/commands/mod.rs | 1 + src/commands/role_adder.rs | 66 ++++++++++++++++++-------------------- src/commands/wolves.rs | 1 - src/main.rs | 14 +++++--- 7 files changed, 95 insertions(+), 74 deletions(-) create mode 100644 src/commands/committee.rs diff --git a/doc/Committee.md b/doc/Committee.md index a8a12a7..a400536 100644 --- a/doc/Committee.md +++ b/doc/Committee.md @@ -40,7 +40,7 @@ This is where the bot is configured. You will need the ``api_key`` from the start of the process. You (personally) will need a role with ``Manage Server`` permission to be able to do this. -1. Use the command ``/add`` and a list of options will pop up. +1. Use the command ``/committee add`` and a list of options will pop up. 2. ``api_key`` is the key you got from Keith earlier. 3. ``role_current`` is the ``member-current`` that you created earlier. 4. ``role_past`` (optional) is the role for all current and past members. diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index 70ec3c8..387bd9e 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -1,47 +1,53 @@ -use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; -use serenity::{builder::CreateCommand, client::Context}; +use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; +use serenity::client::Context; use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers}; use skynet_discord_bot::common::set_roles::normal::update_server; use skynet_discord_bot::common::wolves::cns::get_wolves; use sqlx::{Error, Pool, Sqlite}; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let wolves_api = if let Some(CommandDataOption { - value: CommandDataOptionValue::String(key), + let sub_options = if let Some(CommandDataOption { + value: CommandDataOptionValue::SubCommand(options), .. }) = command.data.options.first() { - key.to_string() + options + } else { + return "Please provide sub options".to_string(); + }; + + let wolves_api = if let Some(x) = sub_options.first() { + match &x.value { + CommandDataOptionValue::String(key) => key.to_string(), + _ => return "Please provide a wolves API key".to_string(), + } } else { return "Please provide a wolves API key".to_string(); }; - let role_current = if let Some(CommandDataOption { - value: CommandDataOptionValue::Role(role), - .. - }) = command.data.options.get(1) - { - role.to_owned() + let role_current = if let Some(x) = sub_options.get(1) { + match &x.value { + CommandDataOptionValue::Role(role) => role.to_owned(), + _ => return "Please provide a valid role for ``Role Current``".to_string(), + } } else { return "Please provide a valid role for ``Role Current``".to_string(); }; - let role_past = if let Some(CommandDataOption { - value: CommandDataOptionValue::Role(role), - .. - }) = command.data.options.get(5) - { - Some(role.to_owned()) + let role_past = if let Some(x) = sub_options.get(5) { + match &x.value { + CommandDataOptionValue::Role(role) => Some(role.to_owned()), + _ => None, + } } else { None }; - let bot_channel_id = if let Some(CommandDataOption { - value: CommandDataOptionValue::Channel(channel), - .. - }) = command.data.options.get(2) - { - channel.to_owned() + let bot_channel_id = if let Some(x) = sub_options.get(2) { + match &x.value { + CommandDataOptionValue::Channel(channel) => channel.to_owned(), + _ => return "Please provide a valid channel for ``Bot Channel``".to_string(), + } } else { return "Please provide a valid channel for ``Bot Channel``".to_string(); }; @@ -74,16 +80,6 @@ pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { "Added/Updated server info".to_string() } -pub fn register() -> CreateCommand { - CreateCommand::new("add") - .description("Enable the bot for this discord") - .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) - .add_option(CreateCommandOption::new(CommandOptionType::String, "api_key", "UL Wolves API Key").required(true)) - .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_current", "Role for Current members").required(true)) - .add_option(CreateCommandOption::new(CommandOptionType::Channel, "bot_channel", "Safe space for folks to use the bot commands.").required(true)) - .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_past", "Role for Past members").required(false)) -} - async fn add_server(db: &Pool, ctx: &Context, server: &Servers) -> Result, Error> { let existing = get_server_config(db, &server.server).await; let role_past = server.role_past.map(|x| x.get() as i64); diff --git a/src/commands/committee.rs b/src/commands/committee.rs new file mode 100644 index 0000000..4813c6a --- /dev/null +++ b/src/commands/committee.rs @@ -0,0 +1,23 @@ +use serenity::all::{CommandOptionType, CreateCommand, CreateCommandOption}; + +pub fn register() -> CreateCommand { + CreateCommand::new("committee") + .description("Commands related to what committees can do") + .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommand, "add", "Enable the bot for this discord") + .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "api_key", "UL Wolves API Key").required(true)) + .add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_current", "Role for Current members").required(true)) + .add_sub_option( + CreateCommandOption::new(CommandOptionType::Channel, "bot_channel", "Safe space for folks to use the bot commands.").required(true), + ) + .add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_past", "Role for Past members").required(false)), + ) + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommand, "roles_adder", "Combine roles together to an new one") + .add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_a", "A role you want to add to Role B").required(true)) + .add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_b", "A role you want to add to Role A").required(true)) + .add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_c", "Sum of A and B").required(true)) + .add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "delete", "Delete this entry.").required(false)), + ) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 9ab59ac..b513fc2 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,4 +1,5 @@ pub mod add_server; +pub mod committee; pub mod count; pub mod minecraft; pub mod role_adder; diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index b0d28e2..f58803b 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -5,37 +5,44 @@ use sqlx::{Error, Pool, Sqlite}; pub mod edit { use super::*; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let role_a = if let Some(CommandDataOption { - value: CommandDataOptionValue::Role(role), + let sub_options = if let Some(CommandDataOption { + value: CommandDataOptionValue::SubCommand(options), .. }) = command.data.options.first() { - role.to_owned() + options } else { - return "Please provide a valid role for ``Role Current``".to_string(); + return "Please provide sub options".to_string(); }; - let role_b = if let Some(CommandDataOption { - value: CommandDataOptionValue::Role(role), - .. - }) = command.data.options.get(1) - { - role.to_owned() + let role_a = if let Some(x) = sub_options.first() { + match &x.value { + CommandDataOptionValue::Role(role) => role.to_owned(), + _ => return "Please provide a valid role for ``Role A``".to_string(), + } } else { - return "Please provide a valid role for ``Role Current``".to_string(); + return "Please provide a valid role for ``Role A``".to_string(); }; - let role_c = if let Some(CommandDataOption { - value: CommandDataOptionValue::Role(role), - .. - }) = command.data.options.get(2) - { - role.to_owned() + let role_b = if let Some(x) = sub_options.get(1) { + match &x.value { + CommandDataOptionValue::Role(role) => role.to_owned(), + _ => return "Please provide a valid role for ``Role B``".to_string(), + } } else { - return "Please provide a valid role for ``Role Current``".to_string(); + return "Please provide a valid role for ``Role B``".to_string(); + }; + + let role_c = if let Some(x) = sub_options.get(2) { + match &x.value { + CommandDataOptionValue::Role(role) => role.to_owned(), + _ => return "Please provide a valid role for ``Role C``".to_string(), + } + } else { + return "Please provide a valid role for ``Role C``".to_string(); }; if role_a == role_b { @@ -46,12 +53,11 @@ pub mod edit { return "Role C cannot be same as A or B".to_string(); } - let delete = if let Some(CommandDataOption { - value: CommandDataOptionValue::Boolean(z), - .. - }) = command.data.options.get(3) - { - *z + let delete = if let Some(x) = sub_options.get(3) { + match &x.value { + CommandDataOptionValue::Boolean(z) => *z, + _ => false, + } } else { false }; @@ -101,16 +107,6 @@ pub mod edit { } } - pub fn register() -> CreateCommand { - CreateCommand::new("roles_adder") - .description("Combine roles together to an new one") - .default_member_permissions(serenity::model::Permissions::MANAGE_GUILD) - .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_a", "A role you want to add to Role B").required(true)) - .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_b", "A role you want to add to Role A").required(true)) - .add_option(CreateCommandOption::new(CommandOptionType::Role, "role_c", "Sum of A and B").required(true)) - .add_option(CreateCommandOption::new(CommandOptionType::Boolean, "delete", "Delete this entry.").required(false)) - } - async fn add_server(db: &Pool, server: &RoleAdder, delete: bool) -> Result, Error> { if delete { sqlx::query_as::<_, RoleAdder>( diff --git a/src/commands/wolves.rs b/src/commands/wolves.rs index 533e379..3d8b120 100644 --- a/src/commands/wolves.rs +++ b/src/commands/wolves.rs @@ -1,4 +1,3 @@ - use lettre::{ message::{header, MultiPart, SinglePart}, transport::smtp::{self, authentication::Credentials}, diff --git a/src/main.rs b/src/main.rs index c270b1b..7a47e87 100644 --- a/src/main.rs +++ b/src/main.rs @@ -113,9 +113,8 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use match Command::set_global_commands( &ctx.http, vec![ - commands::add_server::register(), - commands::role_adder::edit::register(), commands::wolves::register(), + commands::committee::register(), commands::minecraft::server::add::register(), commands::minecraft::server::list::register(), commands::minecraft::server::delete::register(), @@ -178,8 +177,15 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use "link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await, // admin commands - "add" => commands::add_server::run(&command, &ctx).await, - "roles_adder" => commands::role_adder::edit::run(&command, &ctx).await, + "committee" => match command.data.options.first() { + None => "Invalid Command".to_string(), + Some(x) => match x.name.as_str() { + "add" => commands::add_server::run(&command, &ctx).await, + "roles_adder" => commands::role_adder::edit::run(&command, &ctx).await, + // "link" => commands::count::servers::run(&command, &ctx).await, + &_ => format!("not implemented :( committee {}", x.name.as_str()), + }, + }, "minecraft_add" => commands::minecraft::server::add::run(&command, &ctx).await, "minecraft_list" => commands::minecraft::server::list::run(&command, &ctx).await, "minecraft_delete" => commands::minecraft::server::delete::run(&command, &ctx).await, From f307fcea43fe0d002c96921fb4b819c8e78cca84 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 6 Mar 2025 21:42:27 +0000 Subject: [PATCH 098/170] feat: made the minecraft command fall under Wolves --- doc/User.md | 2 +- src/commands/minecraft.rs | 38 +++++++++++++++++++------------------- src/commands/wolves.rs | 5 +++++ src/main.rs | 3 +-- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/doc/User.md b/doc/User.md index f2f050e..570877c 100644 --- a/doc/User.md +++ b/doc/User.md @@ -23,4 +23,4 @@ You will get member roles on any Discord that is using the bot that you are a me ### Minecraft You can link your Minecraft username to grant you access to any Minecraft server run by UL Computer Society. -``/link_minecraft MINECRAFT_USERNAME`` +``/wolves link_minecraft MINECRAFT_USERNAME`` diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 124179c..3b71632 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -1,4 +1,4 @@ -use serenity::{builder::CreateCommand, client::Context}; +use serenity::client::Context; use skynet_discord_bot::common::database::DataBase; use sqlx::{Pool, Sqlite}; @@ -9,20 +9,13 @@ pub(crate) mod user { use super::*; use crate::commands::wolves::link::get_server_member_discord; use serde::{Deserialize, Serialize}; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; use serenity::model::id::UserId; use skynet_discord_bot::common::database::Wolves; use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft}; use skynet_discord_bot::Config; use sqlx::Error; - pub fn register() -> CreateCommand { - CreateCommand::new("link_minecraft") - .description("Link your minecraft account") - .add_option(CreateCommandOption::new(CommandOptionType::String, "minecraft_username", "Your Minecraft username").required(true)) - .add_option(CreateCommandOption::new(CommandOptionType::Boolean, "bedrock_account", "Is this a Bedrock account?").required(false)) - } - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { let db_lock = { let data_read = ctx.data.read().await; @@ -41,23 +34,30 @@ pub(crate) mod user { return "Not linked with wolves, please use ``/link_wolves`` with your wolves email.".to_string(); } - let username = if let Some(CommandDataOption { - value: CommandDataOptionValue::String(username), + let sub_options = if let Some(CommandDataOption { + value: CommandDataOptionValue::SubCommand(options), .. }) = command.data.options.first() { - username.trim() + options + } else { + return "Please provide sub options".to_string(); + }; + + let username = if let Some(x) = sub_options.first() { + match &x.value { + CommandDataOptionValue::String(username) => username.trim(), + _ => return "Please provide a valid username".to_string(), + } } else { return "Please provide a valid username".to_string(); }; - // this is always true unless they state its not - let java = if let Some(CommandDataOption { - value: CommandDataOptionValue::Boolean(z), - .. - }) = command.data.options.get(1) - { - !z + let java = if let Some(x) = sub_options.get(1) { + match &x.value { + CommandDataOptionValue::Boolean(z) => !z, + _ => true, + } } else { true }; diff --git a/src/commands/wolves.rs b/src/commands/wolves.rs index 3d8b120..92c6ec0 100644 --- a/src/commands/wolves.rs +++ b/src/commands/wolves.rs @@ -511,4 +511,9 @@ pub fn register() -> CreateCommand { ) // unlink .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "unlink", "Unlink your Wolves account from your Discord")) + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommand, "link_minecraft", "Link your minecraft account") + .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "minecraft_username", "Your Minecraft username").required(true)) + .add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "bedrock_account", "Is this a Bedrock account?").required(false)), + ) } diff --git a/src/main.rs b/src/main.rs index 7a47e87..dc15840 100644 --- a/src/main.rs +++ b/src/main.rs @@ -118,7 +118,6 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use commands::minecraft::server::add::register(), commands::minecraft::server::list::register(), commands::minecraft::server::delete::register(), - commands::minecraft::user::add::register(), ], ) .await @@ -170,12 +169,12 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use "link" => commands::wolves::link::run(&command, &ctx).await, "verify" => commands::wolves::verify::run(&command, &ctx).await, "unlink" => commands::wolves::unlink::run(&command, &ctx).await, + "link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await, // "link" => commands::count::servers::run(&command, &ctx).await, &_ => format!("not implemented :( wolves {}", x.name.as_str()), }, }, - "link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await, // admin commands "committee" => match command.data.options.first() { None => "Invalid Command".to_string(), From 76cddde36df8a6a05caa43d2983e27dd94b9c131 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 6 Mar 2025 22:54:57 +0000 Subject: [PATCH 099/170] doc: updated images for documentation --- media/setup_user_01.png | 4 ++-- media/setup_user_03.png | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/media/setup_user_01.png b/media/setup_user_01.png index b5e207b..c8ac1a8 100644 --- a/media/setup_user_01.png +++ b/media/setup_user_01.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f6440b2d302c5a7e46493687bb0bbf941c29c5552c71869303397da3c4f0a52b -size 72163 +oid sha256:684bcaa532d75d90a63512c648c44d9cd12a6e34fce6c2a55bf1d5d4f7446371 +size 42969 diff --git a/media/setup_user_03.png b/media/setup_user_03.png index fda3bda..f9f4e3a 100644 --- a/media/setup_user_03.png +++ b/media/setup_user_03.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f19d24686bb9da8857263d1f146e29413fd6bd316981281c18ef2b4ec3621596 -size 51740 +oid sha256:3952b8c2a55604b88a0034f68dd29abd3a72b3e1ced8074bacb358b96f14c7b1 +size 48708 From 421d425f5d1300f67b1be8542cde0dc3d501bbf6 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 10 Mar 2025 20:17:55 +0000 Subject: [PATCH 100/170] feat: lock down to using the specific rust version --- flake.nix | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 92eac43..0a37869 100644 --- a/flake.nix +++ b/flake.nix @@ -20,6 +20,7 @@ }: utils.lib.eachDefaultSystem ( system: let + overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml)); pkgs = (import nixpkgs) {inherit system;}; naersk' = pkgs.callPackage naersk {}; package_name = "skynet_discord_bot"; @@ -62,7 +63,15 @@ # `nix develop` devShell = pkgs.mkShell { - nativeBuildInputs = with pkgs; [rustc cargo pkg-config openssl rustfmt]; + nativeBuildInputs = with pkgs; [rustup rustPlatform.bindgenHook pkg-config openssl]; + # libraries here + buildInputs = [ ]; + RUSTC_VERSION = overrides.toolchain.channel; + # https://github.com/rust-lang/rust-bindgen#environment-variables + shellHook = '' + export PATH="''${CARGO_HOME:-~/.cargo}/bin":"$PATH" + export PATH="''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-${pkgs.stdenv.hostPlatform.rust.rustcTarget}/bin":"$PATH" + ''; }; nixosModule = { From b44518c467b61650eab420d979f46166680c8a8a Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Fri, 14 Mar 2025 04:03:20 +0000 Subject: [PATCH 101/170] feat: improved the count command --- src/commands/count.rs | 81 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/src/commands/count.rs b/src/commands/count.rs index d51669e..b5713ea 100644 --- a/src/commands/count.rs +++ b/src/commands/count.rs @@ -83,9 +83,12 @@ pub mod committee { pub mod servers { // get the list of all the current clubs/socs + use serde::{Deserialize, Serialize}; use serenity::all::{CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption}; use skynet_discord_bot::common::database::{get_server_config_bulk, DataBase}; use skynet_discord_bot::common::set_roles::committee::get_committees; + use skynet_discord_bot::get_now_iso; + use sqlx::{Pool, Sqlite}; use std::collections::HashMap; pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String { @@ -108,7 +111,15 @@ pub mod servers { } } - cs.sort_by_key(|(count, _, _)| *count); + // get all members + let (wolves_current, wolves_past) = get_wolves_total(&db).await; + cs.push((wolves_current, wolves_past, String::from("Skynet Network"))); + + // treat teh committee server as its own thing + let committee_current = get_wolves_committee(&db).await; + cs.push((committee_current, 0, String::from("Committee"))); + + cs.sort_by_key(|(current, _, _)| *current); cs.reverse(); // msg can be a max 2000 chars long @@ -148,6 +159,74 @@ pub mod servers { response.join("\n") } + #[derive(Debug, Clone, Deserialize, Serialize, sqlx::FromRow)] + pub struct Count { + pub count: i64, + } + + async fn get_wolves_total(db: &Pool) -> (i64, i64) { + let total = match sqlx::query_as::<_, Count>( + r#" + SELECT COUNT(DISTINCT id_wolves) as count + FROM wolves + WHERE discord IS NOT NULL + "#, + ) + .fetch_one(db) + .await + { + Ok(res) => res.count, + Err(e) => { + dbg!(e); + 0 + } + }; + + let current = match sqlx::query_as::<_, Count>( + r#" + SELECT COUNT(DISTINCT id_wolves) as count + FROM server_members + JOIN wolves USING (id_wolves) + WHERE ( + wolves.discord IS NOT NULL + AND server_members.expiry > ? + ) + "#, + ) + .bind(get_now_iso(true)) + .fetch_one(db) + .await + { + Ok(res) => res.count, + Err(e) => { + dbg!(e); + 0 + } + }; + + (current, total) + } + + async fn get_wolves_committee(db: &Pool) -> i64 { + // expiry + match sqlx::query_as::<_, Count>( + " + SELECT count + FROM committee_roles + WHERE id_wolves = '0' + ", + ) + .fetch_one(db) + .await + { + Ok(res) => res.count, + Err(e) => { + dbg!(e); + 0 + } + } + } + pub fn register() -> CreateCommand { CreateCommand::new("count") .description("Count the servers") From a90724398683c245be2a5647fbeca78b2e7f5b5c Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Fri, 14 Mar 2025 04:44:32 +0000 Subject: [PATCH 102/170] fix: further improvements to teh count command --- src/commands/count.rs | 64 ++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 22 deletions(-) diff --git a/src/commands/count.rs b/src/commands/count.rs index b5713ea..0289c59 100644 --- a/src/commands/count.rs +++ b/src/commands/count.rs @@ -112,12 +112,13 @@ pub mod servers { } // get all members - let (wolves_current, wolves_past) = get_wolves_total(&db).await; - cs.push((wolves_current, wolves_past, String::from("Skynet Network"))); + let (wolves_current, wolves_past, total) = get_wolves_total(&db).await; + cs.push((total, total, String::from("Skynet Network"))); + cs.push((wolves_current, wolves_past, String::from("Clubs/Socs Servers"))); // treat teh committee server as its own thing let committee_current = get_wolves_committee(&db).await; - cs.push((committee_current, 0, String::from("Committee"))); + cs.push((committee_current, 0, String::from("Committee Server"))); cs.sort_by_key(|(current, _, _)| *current); cs.reverse(); @@ -164,24 +165,7 @@ pub mod servers { pub count: i64, } - async fn get_wolves_total(db: &Pool) -> (i64, i64) { - let total = match sqlx::query_as::<_, Count>( - r#" - SELECT COUNT(DISTINCT id_wolves) as count - FROM wolves - WHERE discord IS NOT NULL - "#, - ) - .fetch_one(db) - .await - { - Ok(res) => res.count, - Err(e) => { - dbg!(e); - 0 - } - }; - + async fn get_wolves_total(db: &Pool) -> (i64, i64, i64) { let current = match sqlx::query_as::<_, Count>( r#" SELECT COUNT(DISTINCT id_wolves) as count @@ -204,7 +188,43 @@ pub mod servers { } }; - (current, total) + let cns = match sqlx::query_as::<_, Count>( + r#" + SELECT COUNT(DISTINCT id_wolves) as count + FROM server_members + JOIN wolves USING (id_wolves) + WHERE wolves.discord IS NOT NULL + "#, + ) + .bind(get_now_iso(true)) + .fetch_one(db) + .await + { + Ok(res) => res.count, + Err(e) => { + dbg!(e); + 0 + } + }; + + let total = match sqlx::query_as::<_, Count>( + r#" + SELECT COUNT(DISTINCT id_wolves) as count + FROM wolves + WHERE discord IS NOT NULL + "#, + ) + .fetch_one(db) + .await + { + Ok(res) => res.count, + Err(e) => { + dbg!(e); + 0 + } + }; + + (current, cns, total) } async fn get_wolves_committee(db: &Pool) -> i64 { From 7f7e7ac598f4a9e03c5bd205f1507cd7a76394fe Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Fri, 14 Mar 2025 04:59:31 +0000 Subject: [PATCH 103/170] fix: output looks strange when teh committee value is 0 --- src/commands/count.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/count.rs b/src/commands/count.rs index 0289c59..678c9e0 100644 --- a/src/commands/count.rs +++ b/src/commands/count.rs @@ -118,7 +118,7 @@ pub mod servers { // treat teh committee server as its own thing let committee_current = get_wolves_committee(&db).await; - cs.push((committee_current, 0, String::from("Committee Server"))); + cs.push((committee_current, committee_current, String::from("Committee Server"))); cs.sort_by_key(|(current, _, _)| *current); cs.reverse(); From e7425588a6aa93af44b7f9f33d3b318992306bdb Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 9 Apr 2025 00:06:20 +0100 Subject: [PATCH 104/170] fix: hardcode teh inter-committee server --- src/common/set_roles.rs | 8 ++++---- src/main.rs | 12 +++--------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index b857e8a..0dc7cc5 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -135,7 +135,7 @@ pub mod committee { use crate::common::wolves::committees::Committees; use crate::Config; use serde::{Deserialize, Serialize}; - use serenity::all::EditRole; + use serenity::all::{EditRole, GuildId}; use serenity::builder::CreateChannel; use serenity::client::Context; use serenity::model::channel::ChannelType; @@ -161,7 +161,7 @@ pub mod committee { }; let config_global = config_lock.read().await; - let server = config_global.committee_server; + let server = GuildId::new(1220150752656363520); // because to use it to update a single user we need to pre-get the members of teh server let mut members = server.members(&ctx, None, None).await.unwrap_or_default(); @@ -172,8 +172,8 @@ pub mod committee { /** This function can take a vec of members (or just one) and gives tehm the appropiate roles on teh committee server */ - pub async fn update_committees(db: &Pool, ctx: &Context, config: &Config, members: &mut Vec) { - let server = config.committee_server; + pub async fn update_committees(db: &Pool, ctx: &Context, _config: &Config, members: &mut Vec) { + let server = GuildId::new(1220150752656363520); let committee_member = RoleId::new(1226602779968274573); let committees = get_committees(db).await; let categories = [ diff --git a/src/main.rs b/src/main.rs index dc15840..be31da8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,7 +40,8 @@ impl EventHandler for Handler { let config_global = config_lock.read().await; // committee server takes priority - if new_member.guild_id.eq(&config_global.committee_server) { + let committee_server = GuildId::new(1220150752656363520); + if new_member.guild_id.get() == committee_server.get() { let mut member = vec![new_member.clone()]; update_committees(&db, &ctx, &config_global, &mut member).await; return; @@ -128,14 +129,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } } - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config_global = config_lock.read().await; - - match &config_global - .committee_server + match GuildId::new(1220150752656363520) .set_commands(&ctx.http, vec![commands::count::committee::register()]) .await { From 22ff91b15201327823e1612b1115112cb19a6bc6 Mon Sep 17 00:00:00 2001 From: esy Date: Wed, 23 Apr 2025 11:30:22 +0000 Subject: [PATCH 105/170] correct command for linking wolves acc --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index be31da8..6d5e849 100644 --- a/src/main.rs +++ b/src/main.rs @@ -75,7 +75,7 @@ impl EventHandler for Handler { let msg = format!( r#" Welcome {} to the {} server! -Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use ``/link_wolves`` to get full access. +Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use ``/wolves link email_here`` with the email associated with your wolves account, to get full access. "#, new_member.display_name(), committee.name_full, From d28a56f25599f4715d7e64e7931372b9de4b5e22 Mon Sep 17 00:00:00 2001 From: esy Date: Wed, 23 Apr 2025 12:39:49 +0100 Subject: [PATCH 106/170] fix: out of date command --- src/commands/minecraft.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 3b71632..ca395c6 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -31,7 +31,7 @@ pub(crate) mod user { // user has to have previously linked with wolves if get_server_member_discord(&db, &command.user.id).await.is_none() { - return "Not linked with wolves, please use ``/link_wolves`` with your wolves email.".to_string(); + return "Not linked with wolves, please use ``/wolves link`` with your wolves email.".to_string(); } let sub_options = if let Some(CommandDataOption { From f1dbbec32df58ded8d840e02f54ffcc9c64e821a Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Fri, 6 Jun 2025 18:50:10 +0100 Subject: [PATCH 107/170] feat: update teh base rust version --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 8cca5be..0837c1f 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.80" +channel = "1.87" From e449204863632a2b6668ebfcd0638a5cf9cc544c Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Fri, 6 Jun 2025 23:59:00 +0100 Subject: [PATCH 108/170] feat: need some new inputs to get this to build --- flake.nix | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 0a37869..2f8ba95 100644 --- a/flake.nix +++ b/flake.nix @@ -27,6 +27,8 @@ desc = "Skynet Discord Bot"; buildInputs = with pkgs; [ openssl + glib + gdk-pixbuf pkg-config rustfmt ]; @@ -37,6 +39,10 @@ pname = "${package_name}"; src = ./.; buildInputs = buildInputs; + postInstall = '' + mkdir $out/config + cp .server-icons.toml $out/config + ''; }; # Run `nix build .#fmt` to run tests fmt = naersk'.buildPackage { @@ -63,9 +69,9 @@ # `nix develop` devShell = pkgs.mkShell { - nativeBuildInputs = with pkgs; [rustup rustPlatform.bindgenHook pkg-config openssl]; + nativeBuildInputs = with pkgs; [rustup rustPlatform.bindgenHook]; # libraries here - buildInputs = [ ]; + buildInputs = buildInputs; RUSTC_VERSION = overrides.toolchain.channel; # https://github.com/rust-lang/rust-bindgen#environment-variables shellHook = '' From fcfcfb8409ca17158db8caf93b04939b4de53752 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 7 Jun 2025 00:09:22 +0100 Subject: [PATCH 109/170] feat: added libraries needed to run the new feature --- Cargo.lock | 599 ++++++++++++++++++++++++++++++++++++++++++++++------- Cargo.toml | 6 +- 2 files changed, 528 insertions(+), 77 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 47a42f9..f21a0da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -17,6 +17,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + [[package]] name = "aead" version = "0.3.2" @@ -110,6 +116,15 @@ version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" +[[package]] +name = "arrayvec" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" +dependencies = [ + "nodrop", +] + [[package]] name = "arrayvec" version = "0.7.6" @@ -243,8 +258,8 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -260,8 +275,8 @@ version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -307,6 +322,15 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +dependencies = [ + "byteorder", +] + [[package]] name = "base64" version = "0.13.1" @@ -383,6 +407,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytemuck" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" + [[package]] name = "byteorder" version = "1.5.0" @@ -410,6 +440,16 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfg-expr" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a2b34126159980f92da2a08bdec0694fd80fb5eb9e48aff25d20a0d8dfa710d" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -691,8 +731,8 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -763,6 +803,27 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "euclid" +version = "0.19.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "596b99621b9477e7a5f94d2d8dd13a9c5c302ac358b822c67a42b6f1054450e1" +dependencies = [ + "euclid_macros", + "num-traits", +] + +[[package]] +name = "euclid_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdcb84c18ea5037a1c5a23039b4ff29403abce2e0d6b1daa11cf0bde2b30be15" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "syn 0.15.44", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -815,6 +876,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float-cmp" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e" + [[package]] name = "flume" version = "0.9.2" @@ -965,8 +1032,8 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -1009,6 +1076,31 @@ dependencies = [ "byteorder", ] +[[package]] +name = "gdk-pixbuf" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd242894c084f4beed508a56952750bce3e96e85eb68fdc153637daa163e10c" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b34f3b580c988bd217e9543a2de59823fafae369d1a055555e5f95a8b130b96" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1069,6 +1161,80 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +[[package]] +name = "gio" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2a5c3829f5794cb15120db87707b2ec03720edff7ad09eb7b711b532e3fe747" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "pin-project-lite", + "smallvec", +] + +[[package]] +name = "gio-sys" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "windows-sys 0.59.0", +] + +[[package]] +name = "glib" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c501c495842c2b23cdacead803a5a343ca2a5d7a7ddaff14cc5f6cf22cfb92c2" +dependencies = [ + "bitflags 2.6.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "smallvec", +] + +[[package]] +name = "glib-macros" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe6dc9ce29887c4b3b74d78d5ba473db160a258ae7ed883d23632ac7fed7bc9" +dependencies = [ + "heck", + "proc-macro-crate", + "proc-macro2 1.0.92", + "quote 1.0.37", + "syn 2.0.89", +] + +[[package]] +name = "glib-sys" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215" +dependencies = [ + "libc", + "system-deps", +] + [[package]] name = "gloo-timers" version = "0.3.0" @@ -1081,6 +1247,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "gobject-sys" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "h2" version = "0.3.26" @@ -1581,21 +1758,11 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - [[package]] name = "idna" version = "1.0.3" @@ -1718,7 +1885,7 @@ dependencies = [ "futures-util", "hostname", "httpdate", - "idna 1.0.3", + "idna", "mime", "native-tls", "nom", @@ -1735,6 +1902,18 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +[[package]] +name = "libflate" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9135df43b1f5d0e333385cb6e7897ecd1a43d7d11b91ac003f4d2c2d2401fdd" +dependencies = [ + "adler32", + "crc32fast", + "rle-decode-fast", + "take_mut", +] + [[package]] name = "libm" version = "0.2.8" @@ -1805,6 +1984,17 @@ dependencies = [ "value-bag", ] +[[package]] +name = "lyon_geom" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb9bf1f1d43be9a9cc2343a7a096dc113cc25337a13e8f99721b01d1d548b60" +dependencies = [ + "arrayvec 0.4.12", + "euclid", + "num-traits", +] + [[package]] name = "maud" version = "0.27.0" @@ -1821,9 +2011,9 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7261b00f3952f617899bc012e3dbd56e4f0110a038175929fa5d18e5a19913ca" dependencies = [ - "proc-macro2", + "proc-macro2 1.0.92", "proc-macro2-diagnostics", - "quote", + "quote 1.0.37", "syn 2.0.89", ] @@ -1903,6 +2093,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "nom" version = "7.1.3" @@ -2008,8 +2204,8 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -2075,6 +2271,24 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "phf" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_shared" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.7" @@ -2090,8 +2304,8 @@ version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -2186,12 +2400,30 @@ dependencies = [ "zerocopy 0.7.35", ] +[[package]] +name = "proc-macro-crate" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" +dependencies = [ + "toml_edit", +] + [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid", +] + [[package]] name = "proc-macro2" version = "1.0.92" @@ -2207,8 +2439,8 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", "version_check", ] @@ -2222,13 +2454,22 @@ dependencies = [ "cc", ] +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "proc-macro2", + "proc-macro2 1.0.92", ] [[package]] @@ -2339,6 +2580,12 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "rctree" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0b3901505c2faa2390e27188078852eb3ed0dd9176e2153f403cdcdd18e0e7" + [[package]] name = "redox_syscall" version = "0.5.4" @@ -2435,6 +2682,26 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "resvg" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef03b3fc408e1d4107b554ef7717a5933aa9f423e7154af626dd3ae557318d4" +dependencies = [ + "log", + "rgb", + "usvg", +] + +[[package]] +name = "rgb" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +dependencies = [ + "bytemuck", +] + [[package]] name = "ring" version = "0.17.8" @@ -2450,6 +2717,21 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rle-decode-fast" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" + +[[package]] +name = "roxmltree" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330d8f80a274bc3cb608908ee345970e7e24b96907f1ad69615a498bec57871c" +dependencies = [ + "xmlparser", +] + [[package]] name = "rsa" version = "0.9.6" @@ -2685,8 +2967,8 @@ version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -2713,6 +2995,15 @@ dependencies = [ "thiserror 1.0.63", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2731,7 +3022,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d72ec4323681bf9a3cabe40fd080abc2435859b502a1b5aa9bf693f125bfa76" dependencies = [ - "arrayvec", + "arrayvec 0.7.6", "async-trait", "base64 0.22.1", "bitflags 2.6.0", @@ -2831,21 +3122,36 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simplecss" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135685097a85a64067df36e28a243e94a94f76d829087ce0be34eeb014260c0e" + +[[package]] +name = "siphasher" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" + [[package]] name = "skynet_discord_bot" version = "0.1.0" dependencies = [ "chrono", "dotenvy", + "gdk-pixbuf", "lettre", "maud", "rand 0.9.0", + "resvg", "serde", "serde_json", "serenity", "sqlx", "surf", "tokio", + "toml", "wolves_oxidised", ] @@ -2968,8 +3274,8 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "sqlx-core", "sqlx-macros-core", "syn 2.0.89", @@ -2986,8 +3292,8 @@ dependencies = [ "heck", "hex", "once_cell", - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "serde", "serde_json", "sha2 0.10.8", @@ -3151,8 +3457,8 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "serde", "serde_derive", "syn 1.0.109", @@ -3165,8 +3471,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" dependencies = [ "base-x", - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "serde", "serde_derive", "serde_json", @@ -3220,14 +3526,48 @@ dependencies = [ "web-sys", ] +[[package]] +name = "svgdom" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffe3c59d84b307fc361bbf0baff2aa6f6d7c946fdd4f1fbbcbb7efcd04b0334" +dependencies = [ + "log", + "roxmltree", + "simplecss", + "slab", + "svgtypes", +] + +[[package]] +name = "svgtypes" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "444c882c28925ae0585df228a90f9951569588646ceca4753560de93cdd02258" +dependencies = [ + "float-cmp", + "phf", +] + +[[package]] +name = "syn" +version = "0.15.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid", +] + [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "unicode-ident", ] @@ -3237,8 +3577,8 @@ version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "unicode-ident", ] @@ -3263,8 +3603,8 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -3310,6 +3650,31 @@ dependencies = [ "libc", ] +[[package]] +name = "system-deps" +version = "7.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml", + "version-compare", +] + +[[package]] +name = "take_mut" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" + +[[package]] +name = "target-lexicon" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" + [[package]] name = "tempfile" version = "3.12.0" @@ -3347,8 +3712,8 @@ version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -3358,8 +3723,8 @@ version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -3426,8 +3791,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" dependencies = [ "proc-macro-hack", - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "standback", "syn 1.0.109", ] @@ -3481,8 +3846,8 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -3581,6 +3946,47 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tower-service" version = "0.3.3" @@ -3605,8 +4011,8 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -3704,6 +4110,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + [[package]] name = "universal-hash" version = "0.4.0" @@ -3722,16 +4134,30 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna", "percent-encoding", "serde", ] +[[package]] +name = "usvg" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0286ce4fda429767b5fc6968c8b541a5fc21f0afd67bbe94d4b7e7177d1f77cb" +dependencies = [ + "base64 0.10.1", + "libflate", + "log", + "lyon_geom", + "rctree", + "svgdom", +] + [[package]] name = "utf-8" version = "0.7.6" @@ -3762,6 +4188,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + [[package]] name = "version_check" version = "0.9.5" @@ -3830,8 +4262,8 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", "wasm-bindgen-shared", ] @@ -3854,7 +4286,7 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ - "quote", + "quote 1.0.37", "wasm-bindgen-macro-support", ] @@ -3864,8 +4296,8 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", @@ -4144,6 +4576,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.50.0" @@ -4186,6 +4627,12 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +[[package]] +name = "xmlparser" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecec95f00fb0ff019153e64ea520f87d1409769db3e8f4db3ea588638a3e1cee" + [[package]] name = "yoke" version = "0.7.5" @@ -4204,8 +4651,8 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", "synstructure", ] @@ -4235,8 +4682,8 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -4246,8 +4693,8 @@ version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76331675d372f91bf8d17e13afbd5fe639200b73d01f0fc748bb059f9cca2db7" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] @@ -4266,8 +4713,8 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", "synstructure", ] @@ -4295,7 +4742,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.92", + "quote 1.0.37", "syn 2.0.89", ] diff --git a/Cargo.toml b/Cargo.toml index d999267..968e6bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,4 +45,8 @@ chrono = "0.4" lettre = "0.11" maud = "0.27" -serde = "1.0" \ No newline at end of file +toml = "0.8.23" +serde = "1.0" + +gdk-pixbuf = "0.20.10" +resvg = "=0.6.1" \ No newline at end of file From 725bfa41ccc6d827e9f6ea3d0f9f5dd451a91a26 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 7 Jun 2025 00:10:52 +0100 Subject: [PATCH 110/170] feat: initial tests of new function to handle changing the logo in discord server --- .server-icons.toml | 23 ++++ src/bin/update_server-icon.rs | 193 ++++++++++++++++++++++++++++++++++ 2 files changed, 216 insertions(+) create mode 100644 .server-icons.toml create mode 100644 src/bin/update_server-icon.rs diff --git a/.server-icons.toml b/.server-icons.toml new file mode 100644 index 0000000..587f63b --- /dev/null +++ b/.server-icons.toml @@ -0,0 +1,23 @@ +# this file controls the + +[source] +repo = "https://forgejo.skynet.ie/Computer_Society/open-goverance" +directory = "Resources/Logo_Variants" + +[[festivals]] +name = "pride" +all_year = true +start = { day = 1, month = 6, year = 0} +end = { day = 30, month = 6, year = 0} + +[[festivals]] +name = "christmas" +all_year = false +start = { day = 1, month = 12, year = 0} +end = { day = 31, month = 12, year = 0} + +[[festivals]] +name = "halloween" +all_year = false +start = { day = 1, month = 12, year = 0} +end = { day = 31, month = 12, year = 0} \ No newline at end of file diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs new file mode 100644 index 0000000..229bdbf --- /dev/null +++ b/src/bin/update_server-icon.rs @@ -0,0 +1,193 @@ +use serenity::{ + async_trait, + client::{Context, EventHandler}, + model::gateway::{GatewayIntents, Ready}, + Client, +}; +use skynet_discord_bot::common::database::{db_init, DataBase}; +use skynet_discord_bot::{get_config, Config}; +use std::{fs, process, sync::Arc}; +use std::fs::File; +use std::io::Read; +use std::process::Command; +use chrono::{Datelike, Utc}; +use gdk_pixbuf::PixbufLoader; +use gdk_pixbuf::prelude::PixbufLoaderExt; +use resvg::usvg; +use serde::Deserialize; +use serenity::all::GuildId; +use tokio::sync::RwLock; + +#[tokio::main] +async fn main() { + let config = get_config(); + let db = match db_init(&config).await { + Ok(x) => x, + Err(_) => return, + }; + + // Intents are a bitflag, bitwise operations can be used to dictate which intents to use + let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; + // Build our client. + let mut client = Client::builder(&config.discord_token, intents) + .event_handler(Handler {}) + .await + .expect("Error creating client"); + + { + let mut data = client.data.write().await; + + data.insert::(Arc::new(RwLock::new(config))); + data.insert::(Arc::new(RwLock::new(db))); + } + + if let Err(why) = client.start().await { + println!("Client error: {:?}", why); + } +} + +struct Handler; +#[async_trait] +impl EventHandler for Handler { + async fn ready(&self, ctx: Context, ready: Ready) { + let ctx = Arc::new(ctx); + println!("{} is connected!", ready.user.name); + + + // pull in the open governance repo + // u[date committee server + update_icon_main(Arc::clone(&ctx)).await; + + // finish up + process::exit(0); + } +} + +async fn update_icon_main(ctx: Arc) { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + + + let config_global = config_lock.read().await; + let config_toml = get_config_icons(); + let server = GuildId::new(689189992417067052); + + + // clone repo into local folder + clone_repo(&config_global, &config_toml); + + // see if there is a current festival + let festival_data = get_festival(&config_toml); + + // get a list of all the graphics files + get_logos(&config_global, &config_toml); +} + +fn get_festival(config_toml: &ConfigToml)-> (Option, Vec){ + let today = Utc::now(); + let day = today.day(); + let month = today.month(); + let year = today.year(); + + let mut festival_current = None; + let mut festival_not = vec![]; + + for festival in &config_toml.festivals { + if (day >= festival.start.day && day <= festival.end.day) + && (month >= festival.start.month && month <= festival.end.month ) + && (year >= festival.start.year && year <= festival.end.year) { + festival_current = Some(festival.name.to_owned()); + } else if !festival.all_year { + festival_not.push(festival.name.to_owned()); + } + } + + (festival_current, festival_not) +} + +#[derive(Deserialize)] +struct ConfigToml { + source: ConfigTomlSource, + festivals: Vec, +} +#[derive(Deserialize)] +struct ConfigTomlSource { + repo: String, + directory: String, +} + +#[derive(Deserialize)] +struct ConfigTomlFestivals { + name: String, + all_year: bool, + start: ConfigTomlFestivalsTime, + end: ConfigTomlFestivalsTime, +} +#[derive(Deserialize)] +struct ConfigTomlFestivalsTime { + day: u32, + month: u32, + year:i32 +} + +fn get_config_icons() -> ConfigToml { + let toml_raw = include_str!("../../.server-icons.toml"); + let config: ConfigToml = toml::from_str(toml_raw).unwrap(); + config +} +fn clone_repo(config: &Config, config_toml: &ConfigToml){ + let url = &config_toml.source.repo; + let folder = format!("{}/open-governance", &config.home); + + Command::new("git") + .arg("clone") + .arg(url) + .arg(&folder) + .output() + .expect("failed to execute process"); + + Command::new("git") + .arg("pull") + .arg("origin") + .arg("main") + .current_dir(&folder) + .output() + .expect("failed to execute process"); +} + +fn convert_svg_to_png(original: &str, out: &str){ + let mut f = File::open(original).unwrap(); + let mut buffer = Vec::new(); + // read the whole file + f.read_to_end(&mut buffer).unwrap(); + let loader = PixbufLoader::with_mime_type("image/svg+xml") + .expect("error loader"); + loader.write(&buffer).expect("TODO: panic message"); + loader.close().expect("TODO: panic message"); + let pixbuf = loader.pixbuf().expect("no pixbuf"); + + let (width, height) = (pixbuf.width(), pixbuf.height()); + println!("size: {}x{}", width, height); + let bytes: Vec = + pixbuf.save_to_bufferv("png", &[]).expect("must not error"); + fs::write(out, &bytes).expect("TODO: panic message"); +} + +fn get_logos(config: &Config, config_toml: &ConfigToml){ + let folder = format!("{}/open-governance/{}", &config.home, &config_toml.source.directory); + + let paths = fs::read_dir(folder).unwrap(); + for path in paths { + let tmp = path.unwrap(); + + println!("Name: {}", &tmp.path().display()); + } +} \ No newline at end of file From 0034bd34d6d8a053544e64f35dcca7957c4ea19b Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 7 Jun 2025 01:05:08 +0100 Subject: [PATCH 111/170] feat: code borrowed from https://github.com/MCorange99/svg2colored-png/tree/main in order to convert from svg to png --- Cargo.lock | 823 +++++++++++++++++++++++++--------- Cargo.toml | 11 +- src/bin/update_server-icon.rs | 39 +- src/common/mod.rs | 2 + src/common/renderer.rs | 213 +++++++++ 5 files changed, 867 insertions(+), 221 deletions(-) create mode 100644 src/common/renderer.rs diff --git a/Cargo.lock b/Cargo.lock index f21a0da..1f38aa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,12 +17,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - [[package]] name = "aead" version = "0.3.2" @@ -110,6 +104,56 @@ dependencies = [ "libc", ] +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.59.0", +] + [[package]] name = "anyhow" version = "1.0.93" @@ -117,13 +161,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" [[package]] -name = "arrayvec" -version = "0.4.12" +name = "arrayref" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9" -dependencies = [ - "nodrop", -] +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" @@ -258,8 +299,8 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -275,8 +316,8 @@ version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -322,15 +363,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" -[[package]] -name = "base64" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" -dependencies = [ - "byteorder", -] - [[package]] name = "base64" version = "0.13.1" @@ -489,6 +521,95 @@ dependencies = [ "generic-array", ] +[[package]] +name = "clap" +version = "4.5.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.89", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "color-eyre" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5920befb47832a6d61ee3a3a846565cfa39b331331e68a3b1d1116630f2f26d" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b88ea9df13354b55bc7234ebcce36e6ef896aca2e42a15de9e10edce01b427" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "colored" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" +dependencies = [ + "lazy_static", + "windows-sys 0.59.0", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -677,6 +798,12 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" +[[package]] +name = "data-url" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d7439c3735f405729d52c3fbbe4de140eaf938a1fe47d227c27f8254d4302a5" + [[package]] name = "der" version = "0.7.9" @@ -731,8 +858,8 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -803,27 +930,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "euclid" -version = "0.19.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "596b99621b9477e7a5f94d2d8dd13a9c5c302ac358b822c67a42b6f1054450e1" -dependencies = [ - "euclid_macros", - "num-traits", -] - -[[package]] -name = "euclid_macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdcb84c18ea5037a1c5a23039b4ff29403abce2e0d6b1daa11cf0bde2b30be15" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "syn 0.15.44", -] - [[package]] name = "event-listener" version = "2.5.3" @@ -851,6 +957,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -866,6 +982,15 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + [[package]] name = "flate2" version = "1.0.33" @@ -878,9 +1003,9 @@ dependencies = [ [[package]] name = "float-cmp" -version = "0.5.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75224bec9bfe1a65e2d34132933f2de7fe79900c96a0174307554244ece8150e" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" [[package]] name = "flume" @@ -916,6 +1041,27 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f" +[[package]] +name = "fontconfig-parser" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbc773e24e02d4ddd8395fd30dc147524273a83e54e0f312d986ea30de5f5646" +dependencies = [ + "roxmltree 0.20.0", +] + +[[package]] +name = "fontdb" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff20bef7942a72af07104346154a70a70b089c572e454b41bef6eb6cb10e9c06" +dependencies = [ + "fontconfig-parser", + "log", + "memmap2", + "ttf-parser", +] + [[package]] name = "foreign-types" version = "0.3.2" @@ -1032,8 +1178,8 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -1155,6 +1301,16 @@ dependencies = [ "polyval", ] +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gimli" version = "0.31.0" @@ -1220,8 +1376,8 @@ checksum = "ebe6dc9ce29887c4b3b74d78d5ba473db160a258ae7ed883d23632ac7fed7bc9" dependencies = [ "heck", "proc-macro-crate", - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -1758,8 +1914,8 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -1784,6 +1940,18 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "imagesize" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72ad49b554c1728b1e83254a1b1565aea4161e28dabbfa171fc15fe62299caf" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "2.5.0" @@ -1815,6 +1983,12 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + [[package]] name = "isahc" version = "0.9.14" @@ -1844,6 +2018,12 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "jpeg-decoder" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" + [[package]] name = "js-sys" version = "0.3.70" @@ -1853,6 +2033,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kurbo" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a53776d271cfb873b17c618af0298445c88afc52837f3e948fa3fafd131f449" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "kurbo" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd85a5776cd9500c2e2059c8c76c3b01528566b7fcbaf8098b55a33fc298849b" +dependencies = [ + "arrayvec", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -1902,18 +2100,6 @@ version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" -[[package]] -name = "libflate" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9135df43b1f5d0e333385cb6e7897ecd1a43d7d11b91ac003f4d2c2d2401fdd" -dependencies = [ - "adler32", - "crc32fast", - "rle-decode-fast", - "take_mut", -] - [[package]] name = "libm" version = "0.2.8" @@ -1984,17 +2170,6 @@ dependencies = [ "value-bag", ] -[[package]] -name = "lyon_geom" -version = "0.12.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb9bf1f1d43be9a9cc2343a7a096dc113cc25337a13e8f99721b01d1d548b60" -dependencies = [ - "arrayvec 0.4.12", - "euclid", - "num-traits", -] - [[package]] name = "maud" version = "0.27.0" @@ -2011,9 +2186,9 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7261b00f3952f617899bc012e3dbd56e4f0110a038175929fa5d18e5a19913ca" dependencies = [ - "proc-macro2 1.0.92", + "proc-macro2", "proc-macro2-diagnostics", - "quote 1.0.37", + "quote", "syn 2.0.89", ] @@ -2033,6 +2208,15 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + [[package]] name = "mime" version = "0.3.17" @@ -2062,6 +2246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ "adler2", + "simd-adler32", ] [[package]] @@ -2093,12 +2278,6 @@ dependencies = [ "tempfile", ] -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - [[package]] name = "nom" version = "7.1.3" @@ -2162,6 +2341,15 @@ dependencies = [ "libm", ] +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + [[package]] name = "object" version = "0.36.4" @@ -2177,6 +2365,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "opaque-debug" version = "0.3.1" @@ -2204,8 +2398,8 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -2227,6 +2421,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "owo-colors" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26995317201fa17f3656c36716aed4a7c81743a9634ac4c99c0eeda495db0cec" + [[package]] name = "parking" version = "2.2.1" @@ -2272,22 +2472,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "phf" -version = "0.7.24" +name = "pico-args" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_shared" -version = "0.7.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" -dependencies = [ - "siphasher", -] +checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" [[package]] name = "pin-project" @@ -2304,8 +2492,8 @@ version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -2359,6 +2547,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "polling" version = "3.7.4" @@ -2415,15 +2616,6 @@ version = "0.5.20+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" -[[package]] -name = "proc-macro2" -version = "0.4.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -dependencies = [ - "unicode-xid", -] - [[package]] name = "proc-macro2" version = "1.0.92" @@ -2439,8 +2631,8 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", "version_check", ] @@ -2454,22 +2646,13 @@ dependencies = [ "cc", ] -[[package]] -name = "quote" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -dependencies = [ - "proc-macro2 0.4.30", -] - [[package]] name = "quote" version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "proc-macro2 1.0.92", + "proc-macro2", ] [[package]] @@ -2582,9 +2765,9 @@ dependencies = [ [[package]] name = "rctree" -version = "0.2.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0b3901505c2faa2390e27188078852eb3ed0dd9176e2153f403cdcdd18e0e7" +checksum = "3b42e27ef78c35d3998403c1d26f3efd9e135d3e5121b0a4845cc5cc27547f4f" [[package]] name = "redox_syscall" @@ -2684,13 +2867,21 @@ dependencies = [ [[package]] name = "resvg" -version = "0.6.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef03b3fc408e1d4107b554ef7717a5933aa9f423e7154af626dd3ae557318d4" +checksum = "76888219c0881e22b0ceab06fddcfe83163cd81642bd60c7842387f9c968a72e" dependencies = [ + "gif", + "jpeg-decoder", "log", + "pico-args", + "png", "rgb", + "svgfilters", + "svgtypes 0.10.0", + "tiny-skia", "usvg", + "usvg-text-layout", ] [[package]] @@ -2718,20 +2909,33 @@ dependencies = [ ] [[package]] -name = "rle-decode-fast" -version = "1.0.3" +name = "rosvgtree" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3582f63211428f83597b51b2ddb88e2a91a9d52d12831f9d08f5e624e8977422" +checksum = "bdc23d1ace03d6b8153c7d16f0708cd80b61ee8e80304954803354e67e40d150" +dependencies = [ + "log", + "roxmltree 0.18.1", + "simplecss", + "siphasher", + "svgtypes 0.9.0", +] [[package]] name = "roxmltree" -version = "0.6.1" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330d8f80a274bc3cb608908ee345970e7e24b96907f1ad69615a498bec57871c" +checksum = "862340e351ce1b271a378ec53f304a5558f7db87f3769dc655a8f6ecbb68b302" dependencies = [ "xmlparser", ] +[[package]] +name = "roxmltree" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c20b6793b5c2fa6553b250154b78d6d0db37e72700ae35fad9387a46f487c97" + [[package]] name = "rsa" version = "0.9.6" @@ -2864,6 +3068,22 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustybuzz" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162bdf42e261bee271b3957691018634488084ef577dddeb6420a9684cab2a6a" +dependencies = [ + "bitflags 1.3.2", + "bytemuck", + "smallvec", + "ttf-parser", + "unicode-bidi-mirroring", + "unicode-ccc", + "unicode-general-category", + "unicode-script", +] + [[package]] name = "ryu" version = "1.0.18" @@ -2967,8 +3187,8 @@ version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -3022,7 +3242,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d72ec4323681bf9a3cabe40fd080abc2435859b502a1b5aa9bf693f125bfa76" dependencies = [ - "arrayvec 0.7.6", + "arrayvec", "async-trait", "base64 0.22.1", "bitflags 2.6.0", @@ -3097,6 +3317,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3123,35 +3352,64 @@ dependencies = [ ] [[package]] -name = "simplecss" -version = "0.1.0" +name = "simd-adler32" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "135685097a85a64067df36e28a243e94a94f76d829087ce0be34eeb014260c0e" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "simple_logger" +version = "4.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7e46c8c90251d47d08b28b8a419ffb4aede0f87c2eea95e17d1d5bacbf3ef1" +dependencies = [ + "colored", + "log", + "time 0.3.36", + "windows-sys 0.48.0", +] + +[[package]] +name = "simplecss" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a9c6883ca9c3c7c90e888de77b7a5c849c779d25d74a1269b0218b14e8b136c" +dependencies = [ + "log", +] [[package]] name = "siphasher" -version = "0.2.3" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "skynet_discord_bot" version = "0.1.0" dependencies = [ "chrono", + "clap", + "color-eyre", "dotenvy", + "eyre", "gdk-pixbuf", "lettre", + "log", "maud", "rand 0.9.0", "resvg", "serde", "serde_json", "serenity", + "simple_logger", "sqlx", "surf", + "tiny-skia", "tokio", "toml", + "usvg", + "usvg-text-layout", "wolves_oxidised", ] @@ -3274,8 +3532,8 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3112e2ad78643fef903618d78cf0aec1cb3134b019730edb039b69eaf531f310" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "sqlx-core", "sqlx-macros-core", "syn 2.0.89", @@ -3292,8 +3550,8 @@ dependencies = [ "heck", "hex", "once_cell", - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "serde", "serde_json", "sha2 0.10.8", @@ -3457,8 +3715,8 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "serde", "serde_derive", "syn 1.0.109", @@ -3471,8 +3729,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11" dependencies = [ "base-x", - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "serde", "serde_derive", "serde_json", @@ -3486,6 +3744,15 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +[[package]] +name = "strict-num" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6637bab7722d379c8b41ba849228d680cc12d0a45ba1fa2b48f2a30577a06731" +dependencies = [ + "float-cmp", +] + [[package]] name = "stringprep" version = "0.1.5" @@ -3497,6 +3764,12 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "subtle" version = "2.6.1" @@ -3527,37 +3800,33 @@ dependencies = [ ] [[package]] -name = "svgdom" -version = "0.16.1" +name = "svgfilters" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ffe3c59d84b307fc361bbf0baff2aa6f6d7c946fdd4f1fbbcbb7efcd04b0334" +checksum = "639abcebc15fdc2df179f37d6f5463d660c1c79cd552c12343a4600827a04bce" dependencies = [ - "log", - "roxmltree", - "simplecss", - "slab", - "svgtypes", + "float-cmp", + "rgb", ] [[package]] name = "svgtypes" -version = "0.4.4" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "444c882c28925ae0585df228a90f9951569588646ceca4753560de93cdd02258" +checksum = "c9ee29c1407a5b18ccfe5f6ac82ac11bab3b14407e09c209a6c1a32098b19734" dependencies = [ - "float-cmp", - "phf", + "kurbo 0.8.3", + "siphasher", ] [[package]] -name = "syn" -version = "0.15.44" +name = "svgtypes" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +checksum = "98ffacedcdcf1da6579c907279b4f3c5492fbce99fbbf227f5ed270a589c2765" dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "unicode-xid", + "kurbo 0.9.5", + "siphasher", ] [[package]] @@ -3566,8 +3835,8 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "unicode-ident", ] @@ -3577,8 +3846,8 @@ version = "2.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "unicode-ident", ] @@ -3603,8 +3872,8 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -3663,12 +3932,6 @@ dependencies = [ "version-compare", ] -[[package]] -name = "take_mut" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" - [[package]] name = "target-lexicon" version = "0.13.2" @@ -3712,8 +3975,8 @@ version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -3723,11 +3986,21 @@ version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.2.27" @@ -3751,7 +4024,9 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", + "libc", "num-conv", + "num_threads", "powerfmt", "serde", "time-core", @@ -3791,12 +4066,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "standback", "syn 1.0.109", ] +[[package]] +name = "tiny-skia" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8493a203431061e901613751931f047d1971337153f96d0e5e363d6dbf6a67" +dependencies = [ + "arrayref", + "arrayvec", + "bytemuck", + "cfg-if", + "png", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adbfb5d3f3dd57a0e11d12f4f13d4ebbbc1b5c15b7ab0a156d030b21da5f677c" +dependencies = [ + "arrayref", + "bytemuck", + "strict-num", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -3846,8 +4146,8 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -4011,8 +4311,8 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -4023,6 +4323,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" +dependencies = [ + "tracing", + "tracing-subscriber", ] [[package]] @@ -4035,12 +4346,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "ttf-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633" + [[package]] name = "tungstenite" version = "0.21.0" @@ -4089,6 +4417,24 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +[[package]] +name = "unicode-bidi-mirroring" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d12260fb92d52f9008be7e4bca09f584780eb2266dc8fecc6a192bec561694" + +[[package]] +name = "unicode-ccc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2520efa644f8268dce4dcd3050eaa7fc044fca03961e9998ac7e2e92b77cf1" + +[[package]] +name = "unicode-general-category" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2281c8c1d221438e373249e065ca4989c4c36952c211ff21a0ee91c44a3869e7" + [[package]] name = "unicode-ident" version = "1.0.13" @@ -4111,10 +4457,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" [[package]] -name = "unicode-xid" +name = "unicode-script" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f" + +[[package]] +name = "unicode-vo" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" +checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94" [[package]] name = "universal-hash" @@ -4146,16 +4498,35 @@ dependencies = [ [[package]] name = "usvg" -version = "0.6.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0286ce4fda429767b5fc6968c8b541a5fc21f0afd67bbe94d4b7e7177d1f77cb" +checksum = "63b6bb4e62619d9f68aa2d8a823fea2bff302340a1f2d45c264d5b0be170832e" dependencies = [ - "base64 0.10.1", - "libflate", + "base64 0.21.7", + "data-url", + "flate2", + "imagesize", + "kurbo 0.9.5", "log", - "lyon_geom", "rctree", - "svgdom", + "rosvgtree", + "strict-num", +] + +[[package]] +name = "usvg-text-layout" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "195386e01bc35f860db024de275a76e7a31afdf975d18beb6d0e44764118b4db" +dependencies = [ + "fontdb", + "kurbo 0.9.5", + "log", + "rustybuzz", + "unicode-bidi", + "unicode-script", + "unicode-vo", + "usvg", ] [[package]] @@ -4176,6 +4547,18 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "value-bag" version = "1.10.0" @@ -4262,8 +4645,8 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", "wasm-bindgen-shared", ] @@ -4286,7 +4669,7 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" dependencies = [ - "quote 1.0.37", + "quote", "wasm-bindgen-macro-support", ] @@ -4296,8 +4679,8 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", "wasm-bindgen-backend", "wasm-bindgen-shared", @@ -4347,6 +4730,12 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "weezl" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + [[package]] name = "whoami" version = "1.5.2" @@ -4629,9 +5018,9 @@ checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[package]] name = "xmlparser" -version = "0.9.0" +version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecec95f00fb0ff019153e64ea520f87d1409769db3e8f4db3ea588638a3e1cee" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" [[package]] name = "yoke" @@ -4651,8 +5040,8 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", "synstructure", ] @@ -4682,8 +5071,8 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -4693,8 +5082,8 @@ version = "0.8.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76331675d372f91bf8d17e13afbd5fe639200b73d01f0fc748bb059f9cca2db7" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] @@ -4713,8 +5102,8 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", "synstructure", ] @@ -4742,7 +5131,7 @@ version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ - "proc-macro2 1.0.92", - "quote 1.0.37", + "proc-macro2", + "quote", "syn 2.0.89", ] diff --git a/Cargo.toml b/Cargo.toml index 968e6bb..deeeca5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,4 +49,13 @@ toml = "0.8.23" serde = "1.0" gdk-pixbuf = "0.20.10" -resvg = "=0.6.1" \ No newline at end of file +clap = { version = "4.1.4", features = ["derive"] } + +eyre = "0.6.8" +color-eyre = "0.6.2" +usvg-text-layout = "0.29.0" +usvg = "0.29.0" +resvg = "0.29.0" +tiny-skia = "0.8.3" +log = "0.4.20" +simple_logger = "4.2.0" \ No newline at end of file diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index 229bdbf..c262ef2 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -7,16 +7,19 @@ use serenity::{ use skynet_discord_bot::common::database::{db_init, DataBase}; use skynet_discord_bot::{get_config, Config}; use std::{fs, process, sync::Arc}; +use std::ffi::OsStr; use std::fs::File; use std::io::Read; +use std::path::PathBuf; use std::process::Command; use chrono::{Datelike, Utc}; -use gdk_pixbuf::PixbufLoader; +use gdk_pixbuf::{Pixbuf, PixbufFormat, PixbufLoader}; use gdk_pixbuf::prelude::PixbufLoaderExt; use resvg::usvg; use serde::Deserialize; use serenity::all::GuildId; use tokio::sync::RwLock; +use skynet_discord_bot::common::renderer::{Args, Renderer}; #[tokio::main] async fn main() { @@ -163,7 +166,7 @@ fn clone_repo(config: &Config, config_toml: &ConfigToml){ .expect("failed to execute process"); } -fn convert_svg_to_png(original: &str, out: &str){ +fn convert_svg_to_png(original: &PathBuf, out: &PathBuf){ let mut f = File::open(original).unwrap(); let mut buffer = Vec::new(); // read the whole file @@ -183,11 +186,41 @@ fn convert_svg_to_png(original: &str, out: &str){ fn get_logos(config: &Config, config_toml: &ConfigToml){ let folder = format!("{}/open-governance/{}", &config.home, &config_toml.source.directory); - + let folder_path = PathBuf::from(&folder); let paths = fs::read_dir(folder).unwrap(); + + let args = Args{ + input: folder_path.clone(), + output: folder_path.clone(), + colors: String::from(""), + width: 1024, + height: 1024, + }; + let mut r = Renderer::new(&args).unwrap(); + for path in paths { let tmp = path.unwrap(); + let mut path_local = tmp.path().to_owned(); + match tmp.path().extension() { + None => {} + Some(ext) => { + if ext == "svg" { + let mut path_new = path_local.clone(); + path_new.set_extension("png"); + + // check if exists + match r.render(&path_local, &args) { + Ok(_) => log::info!("Successfully rendered all colors of {path_local:?}"), + Err(e) => { + log::error!("Failed to render {path_local:?}: {}", e) + } + } + path_local = path_new; + } + } + }; + println!("Name: {}", &tmp.path().display()); } } \ No newline at end of file diff --git a/src/common/mod.rs b/src/common/mod.rs index 38f457a..7b93f40 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -2,3 +2,5 @@ pub mod database; pub mod minecraft; pub mod set_roles; pub mod wolves; + +pub mod renderer; \ No newline at end of file diff --git a/src/common/renderer.rs b/src/common/renderer.rs new file mode 100644 index 0000000..61d45ea --- /dev/null +++ b/src/common/renderer.rs @@ -0,0 +1,213 @@ +use std::path::{Path, PathBuf}; + +use clap::builder::OsStr; +use clap::Parser; +use color_eyre::{Result, eyre::bail}; +use usvg_text_layout::TreeTextToPath; + +#[derive(Parser, Debug, Clone)] +#[command(name = "svg2colored-png")] +#[command(author = "MCorange ")] +#[command(version = env!("CARGO_PKG_VERSION"))] +#[command(about = "Converts svgs to multiple png's that differ in color", long_about = "Made by MCorange ")] +pub struct Args { + /// Input folder with the SVG's + #[arg(long, short)] + pub input: PathBuf, + + /// Output folder where the PNG's will be placed + #[arg[long, short]] + pub output: PathBuf, + + /// Comma seperated colors that will be used in HEX Eg. 000000,ffffff + /// Can be like an object: black:000000,white:ffffff + #[arg[long, short, default_value_t = String::from("0d6efd,6c757d,198754,0dcaf0,ffc107,dc3545,f8f9fa,212529,ffffff,000000")]] + pub colors: String, + + /// Width of the generated PNG's + #[arg(long, default_value_t = 1024)] + pub width: u32, + + /// Height of the generated PNG's + #[arg(long, default_value_t = 1024)] + pub height: u32 + +} + +#[derive(Debug, Clone)] +enum ColorType { + Array(Vec), + Object(Vec<(String, String)>), + None +} + + +#[derive(Debug, Clone)] +pub struct Renderer { + fontdb: usvg_text_layout::fontdb::Database, + colors: ColorType, + size: (u32, u32), + pub count: u64, +} + +impl Renderer { + pub fn new(args: &Args) -> Result { + let mut db = usvg_text_layout::fontdb::Database::new(); + db.load_system_fonts(); + + let mut this = Self { + fontdb: db, + colors: ColorType::None, + size: (args.width, args.height), + count: 0, + }; + + let colors = if args.colors.contains(':') { + //? object + let obj = args.colors.split(',').map(|s| { + let s = s.split(':').collect::>(); + + if s.len() < 2 { + log::error!("Invalid color object, try checking help"); + return None; + } + + Some((s[0].to_string(), s[1].to_string())) + }).collect::>>(); + + let mut colors = Vec::new(); + + for c in obj { + if let Some(c) = c { + std::fs::create_dir_all(args.output.join(&c.0))?; + + colors.push(c); + } + } + + ColorType::Object(colors) + + } else { + //? list + // let colors = args.colors.split(",").map(|s| { + // s.to_string() + // }) + // .collect::>(); + + let mut colors = Vec::new(); + + for color in args.colors.split(',') { + std::fs::create_dir_all(args.output.join(&color))?; + colors.push(color.to_string()) + } + + + ColorType::Array(colors) + }; + + this.colors = colors; + Ok(this) + } + + + pub fn render(&mut self, fi: &Path, args: &Args) -> Result<()> { + match fi.extension() { + Some(e) if e.to_str() == Some("svg") => {}, + Some(_) | + None => { + log::warn!("Filer {:?} is not of type SVG", fi); + // util::logger::warning(format!("File '{}' is not of SVG type", fi.clone().to_str().unwrap())); + bail!("Failed to render"); + } + }; + + match self.colors.clone() { + ColorType::Array(c) => { + for color in c { + log::info!("Rendering the color {color:?}"); + let fo = self.get_out_file(fi, &color, &args); + self.render_one(fi, &fo, &color)?; + + } + }, + ColorType::Object(c) => { + for o in c { + log::info!("Rendering the color {:?}", o); + let fo = self.get_out_file(fi, &o.0, &args); + self.render_one(fi, &fo, &o.1)?; + + } + }, + ColorType::None => unreachable!(), + } + + Ok(()) + } + + fn render_one(&mut self, fi: &Path, fo: &Path, color: &String) -> Result<()>{ + + if fo.exists() { + log::warn!("File {fo:?} exists, skipping"); + return Ok(()); + } + + let svg = self.set_color(&self.get_svg_data(fi)?, &color); + + let mut opt = usvg::Options::default(); + // Get file's absolute directory. + opt.resources_dir = std::fs::canonicalize(fi.clone()) + .ok() + .and_then(|p| p.parent().map(|p| p.to_path_buf())); + + let mut tree = match usvg::Tree::from_data(svg.as_bytes(), &opt) { + Ok(v) => v, + Err(_) => { + log::error!("Failed to parse {fi:?}"); + bail!(""); + } + }; + + tree.convert_text(&self.fontdb); + + let mut pixmap = tiny_skia::Pixmap::new(self.size.0, self.size.1).unwrap(); + + log::info!("Rendering {fo:?}"); + + //? maybe handle this and possibly throw error if its none + let _ = resvg::render( + &tree, + usvg::FitTo::Size(self.size.0, self.size.1), + tiny_skia::Transform::default(), + pixmap.as_mut(), + ); + + + pixmap.save_png(fo)?; + self.count += 1; + Ok(()) + } + + + #[inline] + fn get_out_file(&mut self, fi: &Path, sub_folder: &String, args: &Args) -> PathBuf { + let mut fo: std::path::PathBuf = args.output.clone(); + // fo.push(sub_folder); + fo.push(fi.file_name().unwrap_or(&OsStr::from("default")).to_str().unwrap_or("default").replace(".svg", "")); + fo.set_extension("png"); + fo + } + + fn set_color(&self, svg: &String, color: &String) -> String { + svg.replace("fill=\"currentColor\"", &format!("fill=\"#{}\"", color)) + } + + fn get_svg_data(&self, fi: &Path) -> Result{ + match std::fs::read_to_string(fi) { + Ok(d) => Ok(d), + Err(_) => { + log::error!("File {fi:?} does not exist"); + bail!("File {fi:?} does not exist"); + } + } + } +} \ No newline at end of file From b4f68357046edda17649133dc135e60dcb9b32d5 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 7 Jun 2025 19:55:36 +0100 Subject: [PATCH 112/170] feat: got the logos, and converted them if needs be --- src/bin/update_server-icon.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index c262ef2..65875bc 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -7,7 +7,7 @@ use serenity::{ use skynet_discord_bot::common::database::{db_init, DataBase}; use skynet_discord_bot::{get_config, Config}; use std::{fs, process, sync::Arc}; -use std::ffi::OsStr; +use std::ffi::{OsStr, OsString}; use std::fs::File; use std::io::Read; use std::path::PathBuf; @@ -91,7 +91,7 @@ async fn update_icon_main(ctx: Arc) { let festival_data = get_festival(&config_toml); // get a list of all the graphics files - get_logos(&config_global, &config_toml); + let logos = get_logos(&config_global, &config_toml); } fn get_festival(config_toml: &ConfigToml)-> (Option, Vec){ @@ -184,7 +184,7 @@ fn convert_svg_to_png(original: &PathBuf, out: &PathBuf){ fs::write(out, &bytes).expect("TODO: panic message"); } -fn get_logos(config: &Config, config_toml: &ConfigToml){ +fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec<(OsString, PathBuf)> { let folder = format!("{}/open-governance/{}", &config.home, &config_toml.source.directory); let folder_path = PathBuf::from(&folder); let paths = fs::read_dir(folder).unwrap(); @@ -198,10 +198,14 @@ fn get_logos(config: &Config, config_toml: &ConfigToml){ }; let mut r = Renderer::new(&args).unwrap(); - for path in paths { - let tmp = path.unwrap(); - - let mut path_local = tmp.path().to_owned(); + let mut logos = vec![]; + + for tmp in paths.flatten() { + let path_local = tmp.path().to_owned(); + let path_local2 = tmp.path().to_owned(); + let temp2 = path_local2.file_name().unwrap(); + let mut file_path = tmp.path(); + match tmp.path().extension() { None => {} Some(ext) => { @@ -216,11 +220,15 @@ fn get_logos(config: &Config, config_toml: &ConfigToml){ log::error!("Failed to render {path_local:?}: {}", e) } } - path_local = path_new; + file_path = path_new; } } }; + logos.push((temp2.to_owned(), file_path.to_owned())); + println!("Name: {}", &tmp.path().display()); } + + logos } \ No newline at end of file From a7423959dcb0e7a53b21a8c4744372153a3fe81c Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 7 Jun 2025 21:24:59 +0100 Subject: [PATCH 113/170] fix: use a struct for clarity --- src/bin/update_server-icon.rs | 36 ++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index 65875bc..ec09845 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -94,26 +94,33 @@ async fn update_icon_main(ctx: Arc) { let logos = get_logos(&config_global, &config_toml); } -fn get_festival(config_toml: &ConfigToml)-> (Option, Vec){ +struct FestivalData{ + current: Option, + exclusions: Vec, +} + +fn get_festival(config_toml: &ConfigToml)-> FestivalData { let today = Utc::now(); let day = today.day(); let month = today.month(); let year = today.year(); - let mut festival_current = None; - let mut festival_not = vec![]; + let mut result = FestivalData { + current: None, + exclusions: vec![], + }; for festival in &config_toml.festivals { if (day >= festival.start.day && day <= festival.end.day) && (month >= festival.start.month && month <= festival.end.month ) && (year >= festival.start.year && year <= festival.end.year) { - festival_current = Some(festival.name.to_owned()); + result.current = Some(festival.name.to_owned()); } else if !festival.all_year { - festival_not.push(festival.name.to_owned()); + result.exclusions.push(festival.name.to_owned()); } } - (festival_current, festival_not) + result } #[derive(Deserialize)] @@ -184,7 +191,11 @@ fn convert_svg_to_png(original: &PathBuf, out: &PathBuf){ fs::write(out, &bytes).expect("TODO: panic message"); } -fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec<(OsString, PathBuf)> { +struct LogoData { + name: OsString, + path: PathBuf, +} +fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { let folder = format!("{}/open-governance/{}", &config.home, &config_toml.source.directory); let folder_path = PathBuf::from(&folder); let paths = fs::read_dir(folder).unwrap(); @@ -203,8 +214,8 @@ fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec<(OsString, PathB for tmp in paths.flatten() { let path_local = tmp.path().to_owned(); let path_local2 = tmp.path().to_owned(); - let temp2 = path_local2.file_name().unwrap(); - let mut file_path = tmp.path(); + let name = path_local2.file_name().unwrap().to_owned(); + let mut path = tmp.path(); match tmp.path().extension() { None => {} @@ -220,12 +231,15 @@ fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec<(OsString, PathB log::error!("Failed to render {path_local:?}: {}", e) } } - file_path = path_new; + path = path_new; } } }; - logos.push((temp2.to_owned(), file_path.to_owned())); + logos.push(LogoData{ + name, + path, + }); println!("Name: {}", &tmp.path().display()); } From ffd6e40d0bc74785c79fb6b48109ea7d32de9540 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 7 Jun 2025 22:29:00 +0100 Subject: [PATCH 114/170] fix: was being too strict in matching the year --- src/bin/update_server-icon.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index ec09845..c2a14b2 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -111,10 +111,13 @@ fn get_festival(config_toml: &ConfigToml)-> FestivalData { }; for festival in &config_toml.festivals { - if (day >= festival.start.day && day <= festival.end.day) - && (month >= festival.start.month && month <= festival.end.month ) - && (year >= festival.start.year && year <= festival.end.year) { - result.current = Some(festival.name.to_owned()); + if (day >= festival.start.day && day <= festival.end.day) && + (month >= festival.start.month && month <= festival.end.month ) { + if festival.start.year == 0 || festival.end.year == 0 { + result.current = Some(festival.name.to_owned()); + } else if (year >= festival.start.year && year <= festival.end.year) { + result.current = Some(festival.name.to_owned()); + } } else if !festival.all_year { result.exclusions.push(festival.name.to_owned()); } From 537fdfd40c0a54f740a0f22e1922a247a57b0074 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 7 Jun 2025 22:44:47 +0100 Subject: [PATCH 115/170] feat: put the converted files into a subfolder --- src/bin/update_server-icon.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index c2a14b2..ac13ec7 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -201,11 +201,13 @@ struct LogoData { fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { let folder = format!("{}/open-governance/{}", &config.home, &config_toml.source.directory); let folder_path = PathBuf::from(&folder); + let mut folder_output = folder_path.clone(); + folder_output.push("converted"); let paths = fs::read_dir(folder).unwrap(); let args = Args{ input: folder_path.clone(), - output: folder_path.clone(), + output: folder_output, colors: String::from(""), width: 1024, height: 1024, @@ -219,6 +221,10 @@ fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { let path_local2 = tmp.path().to_owned(); let name = path_local2.file_name().unwrap().to_owned(); let mut path = tmp.path(); + + if path.is_dir() { + continue; + } match tmp.path().extension() { None => {} @@ -226,6 +232,11 @@ fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { if ext == "svg" { let mut path_new = path_local.clone(); path_new.set_extension("png"); + let filename_tmp = path_new.clone(); + let filename = filename_tmp.file_name().unwrap_or_default(); + path_new.pop(); + path_new.push("converted"); + path_new.push(filename); // check if exists match r.render(&path_local, &args) { From acdfe4b423438105baa4cc0d83dfab61680e7221 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 7 Jun 2025 23:02:16 +0100 Subject: [PATCH 116/170] fix: only convert if it hasnt already been converted --- src/bin/update_server-icon.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index ac13ec7..e3440b9 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -239,10 +239,13 @@ fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { path_new.push(filename); // check if exists - match r.render(&path_local, &args) { - Ok(_) => log::info!("Successfully rendered all colors of {path_local:?}"), - Err(e) => { - log::error!("Failed to render {path_local:?}: {}", e) + if !path_new.exists() { + // convert if it hasnt been converted already + match r.render(&path_local, &args) { + Ok(_) => log::info!("Successfully rendered all colors of {path_local:?}"), + Err(e) => { + log::error!("Failed to render {path_local:?}: {}", e) + } } } path = path_new; From 1ff993d236654ef806f0a799b76a999a18982869 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 7 Jun 2025 23:04:57 +0100 Subject: [PATCH 117/170] feat: only need to keep whatever ones are in teh current season (if at all) --- src/bin/update_server-icon.rs | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index e3440b9..cce07f9 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -92,6 +92,9 @@ async fn update_icon_main(ctx: Arc) { // get a list of all the graphics files let logos = get_logos(&config_global, &config_toml); + + // filter them so only the current season (if any) are active + let logos_filtered = logos_filter(&festival_data, logos); } struct FestivalData{ @@ -262,4 +265,34 @@ fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { } logos +} + +fn logos_filter(festival_data: &FestivalData, existing: Vec) -> Vec{ + let mut filtered: Vec = vec![]; + + for logo in existing { + let name_lowercase0 = logo.name.to_ascii_lowercase(); + let name_lowercase = name_lowercase0.to_str().unwrap_or_default(); + + // if its a current festival filter based on it + if let Some(x) = &festival_data.current { + if name_lowercase.contains(x) { + filtered.push(logo); + } + } else { + // else filter using the excluded ones + let mut excluded = false; + for festival in &festival_data.exclusions { + if name_lowercase.contains(festival) { + excluded = true; + } + } + + if !excluded { + filtered.push(logo); + } + } + } + + filtered } \ No newline at end of file From 4f96c9087f9ad812fae5d02b739d6d2ad06403d6 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 7 Jun 2025 23:53:24 +0100 Subject: [PATCH 118/170] feat: get a random image --- src/bin/update_server-icon.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index cce07f9..bd2f295 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -15,6 +15,7 @@ use std::process::Command; use chrono::{Datelike, Utc}; use gdk_pixbuf::{Pixbuf, PixbufFormat, PixbufLoader}; use gdk_pixbuf::prelude::PixbufLoaderExt; +use rand::seq::IndexedRandom; use resvg::usvg; use serde::Deserialize; use serenity::all::GuildId; @@ -95,6 +96,9 @@ async fn update_icon_main(ctx: Arc) { // filter them so only the current season (if any) are active let logos_filtered = logos_filter(&festival_data, logos); + + let mut rng = rand::rng(); + let logo_selected = logos_filtered.choose(&mut rng).unwrap(); } struct FestivalData{ From 7bcf30fb3a1c81472e2f64cc473d4bfb61482e75 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 8 Jun 2025 00:18:30 +0100 Subject: [PATCH 119/170] feat: can now set the server icon programmatically --- src/bin/update_server-icon.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index bd2f295..56a6f0b 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -15,10 +15,13 @@ use std::process::Command; use chrono::{Datelike, Utc}; use gdk_pixbuf::{Pixbuf, PixbufFormat, PixbufLoader}; use gdk_pixbuf::prelude::PixbufLoaderExt; +use rand::rngs::SmallRng; +use rand::SeedableRng; use rand::seq::IndexedRandom; use resvg::usvg; use serde::Deserialize; use serenity::all::GuildId; +use serenity::builder::{CreateAttachment, EditGuild}; use tokio::sync::RwLock; use skynet_discord_bot::common::renderer::{Args, Renderer}; @@ -97,8 +100,10 @@ async fn update_icon_main(ctx: Arc) { // filter them so only the current season (if any) are active let logos_filtered = logos_filter(&festival_data, logos); - let mut rng = rand::rng(); + let mut rng = SmallRng::from_os_rng(); let logo_selected = logos_filtered.choose(&mut rng).unwrap(); + + logo_set(&ctx, &server, logo_selected).await; } struct FestivalData{ @@ -299,4 +304,12 @@ fn logos_filter(festival_data: &FestivalData, existing: Vec) -> Vec, server: &GuildId, logo_selected: &LogoData){ + let icon = CreateAttachment::path(logo_selected.path.to_str().unwrap_or_default()).await.unwrap(); + + // assuming a `guild` has already been bound + let builder = EditGuild::new().icon(Some(&icon)); + server.edit(ctx, builder).await.unwrap(); } \ No newline at end of file From 1555a94656b7ca6aa8519b556b880c50d781808b Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 8 Jun 2025 00:20:38 +0100 Subject: [PATCH 120/170] fix: give a reference where it needed to be --- src/common/renderer.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/common/renderer.rs b/src/common/renderer.rs index 61d45ea..65af0a4 100644 --- a/src/common/renderer.rs +++ b/src/common/renderer.rs @@ -1,3 +1,6 @@ +// this code is taken from https://github.com/MCorange99/svg2colored-png/tree/main +// I was unable to figure out how to use usvg myself so younked it from here. + use std::path::{Path, PathBuf}; use clap::builder::OsStr; From 51d5904ffdfc71e087e96624b49df0135db68932 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 10 Jun 2025 16:54:13 +0100 Subject: [PATCH 121/170] feat: allow for overlapping festivals --- src/bin/update_server-icon.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index 56a6f0b..d161352 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -107,7 +107,7 @@ async fn update_icon_main(ctx: Arc) { } struct FestivalData{ - current: Option, + current: Vec, exclusions: Vec, } @@ -118,7 +118,7 @@ fn get_festival(config_toml: &ConfigToml)-> FestivalData { let year = today.year(); let mut result = FestivalData { - current: None, + current: vec![], exclusions: vec![], }; @@ -126,9 +126,9 @@ fn get_festival(config_toml: &ConfigToml)-> FestivalData { if (day >= festival.start.day && day <= festival.end.day) && (month >= festival.start.month && month <= festival.end.month ) { if festival.start.year == 0 || festival.end.year == 0 { - result.current = Some(festival.name.to_owned()); + result.current.push(festival.name.to_owned()); } else if (year >= festival.start.year && year <= festival.end.year) { - result.current = Some(festival.name.to_owned()); + result.current.push(festival.name.to_owned()); } } else if !festival.all_year { result.exclusions.push(festival.name.to_owned()); @@ -279,27 +279,27 @@ fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { fn logos_filter(festival_data: &FestivalData, existing: Vec) -> Vec{ let mut filtered: Vec = vec![]; - for logo in existing { + 'outer: for logo in existing { let name_lowercase0 = logo.name.to_ascii_lowercase(); let name_lowercase = name_lowercase0.to_str().unwrap_or_default(); // if its a current festival filter based on it - if let Some(x) = &festival_data.current { - if name_lowercase.contains(x) { + for festival in &festival_data.current { + if name_lowercase.contains(festival) { filtered.push(logo); + continue 'outer; } - } else { - // else filter using the excluded ones - let mut excluded = false; - for festival in &festival_data.exclusions { - if name_lowercase.contains(festival) { - excluded = true; - } + } + // else filter using the excluded ones + let mut excluded = false; + for festival in &festival_data.exclusions { + if name_lowercase.contains(festival) { + excluded = true; } + } - if !excluded { - filtered.push(logo); - } + if !excluded { + filtered.push(logo); } } From 3523dac46e5deed3fb6e680dfd5f0f6113cff58c Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 14 Jun 2025 15:54:07 +0100 Subject: [PATCH 122/170] fix: properly filter icon based on the festival --- src/bin/update_server-icon.rs | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index d161352..a9b7c62 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -283,23 +283,26 @@ fn logos_filter(festival_data: &FestivalData, existing: Vec) -> Vec Date: Sat, 14 Jun 2025 16:46:04 +0100 Subject: [PATCH 123/170] feat: save the selected image to teh library --- db/migrations/11_server-icons.sql | 8 ++++++ src/bin/update_server-icon.rs | 41 +++++++++++++++++++++++++++---- 2 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 db/migrations/11_server-icons.sql diff --git a/db/migrations/11_server-icons.sql b/db/migrations/11_server-icons.sql new file mode 100644 index 0000000..37d8b5b --- /dev/null +++ b/db/migrations/11_server-icons.sql @@ -0,0 +1,8 @@ + +CREATE TABLE IF NOT EXISTS server_icons ( + id INTEGER PRIMARY KEY, + name TEXT NOT NULL, + date TEXT NOT NULL +); + +CREATE INDEX IF NOT EXISTS index_name ON server_icons (name); \ No newline at end of file diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index a9b7c62..15db9bd 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -4,8 +4,8 @@ use serenity::{ model::gateway::{GatewayIntents, Ready}, Client, }; -use skynet_discord_bot::common::database::{db_init, DataBase}; -use skynet_discord_bot::{get_config, Config}; +use skynet_discord_bot::common::database::{db_init, DataBase, RoleAdder}; +use skynet_discord_bot::{get_config, get_now_iso, Config}; use std::{fs, process, sync::Arc}; use std::ffi::{OsStr, OsString}; use std::fs::File; @@ -22,6 +22,7 @@ use resvg::usvg; use serde::Deserialize; use serenity::all::GuildId; use serenity::builder::{CreateAttachment, EditGuild}; +use sqlx::{Pool, Sqlite}; use tokio::sync::RwLock; use skynet_discord_bot::common::renderer::{Args, Renderer}; @@ -103,7 +104,7 @@ async fn update_icon_main(ctx: Arc) { let mut rng = SmallRng::from_os_rng(); let logo_selected = logos_filtered.choose(&mut rng).unwrap(); - logo_set(&ctx, &server, logo_selected).await; + logo_set(&ctx, &db,&server, logo_selected).await; } struct FestivalData{ @@ -309,10 +310,40 @@ fn logos_filter(festival_data: &FestivalData, existing: Vec) -> Vec, server: &GuildId, logo_selected: &LogoData){ + +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct ServerIcons { + pub id: i64, + pub name: String, + pub date: String, +} + +async fn logo_set(ctx: &Arc, db: &Pool, server: &GuildId, logo_selected: &LogoData){ + // add to teh database + logo_set_db(db ,logo_selected).await; + let icon = CreateAttachment::path(logo_selected.path.to_str().unwrap_or_default()).await.unwrap(); // assuming a `guild` has already been bound let builder = EditGuild::new().icon(Some(&icon)); server.edit(ctx, builder).await.unwrap(); -} \ No newline at end of file +} + +async fn logo_set_db(db: &Pool, logo_selected: &LogoData){ + let name = logo_selected.name.to_str().unwrap_or_default(); + + sqlx::query_as::<_, ServerIcons>( + " + INSERT OR REPLACE INTO server_icons (name, date) + VALUES (?1, ?2, ?3) + ", + ) + .bind(name) + .bind(get_now_iso(false)) + .fetch_optional(db) + .await; +} + +// fn command(config_toml: &ConfigToml, logo_selected: &LogoData){ +// let web_url = format!("{}/src/branch/main/{}/{}", &config_toml.source.repo, &config_toml.source.directory, &logo_selected.name.to_str().unwrap_or_default()); +// } \ No newline at end of file From 9d50efb75768d83c913ffa9c16952d6e0c083759 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 14 Jun 2025 18:00:40 +0100 Subject: [PATCH 124/170] fmt: cargo+clippy --- Cargo.lock | 311 --------------------------------- Cargo.toml | 8 +- src/bin/update_server-icon.rs | 168 ++++++++---------- src/common/mod.rs | 2 +- src/common/renderer.rs | 316 ++++++++++++++++------------------ 5 files changed, 222 insertions(+), 583 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1f38aa1..66e67f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,56 +104,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anstream" -version = "0.6.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" - -[[package]] -name = "anstyle-parse" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" -dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.59.0", -] - [[package]] name = "anyhow" version = "1.0.93" @@ -472,16 +422,6 @@ dependencies = [ "shlex", ] -[[package]] -name = "cfg-expr" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a2b34126159980f92da2a08bdec0694fd80fb5eb9e48aff25d20a0d8dfa710d" -dependencies = [ - "smallvec", - "target-lexicon", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -521,46 +461,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "clap" -version = "4.5.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd60e63e9be68e5fb56422e397cf9baddded06dae1d2e523401542383bc72a9f" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.5.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89cc6392a1f72bbeb820d71f32108f61fdaf18bc526e1d23954168a67759ef51" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.5.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.89", -] - -[[package]] -name = "clap_lex" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" - [[package]] name = "color-eyre" version = "0.6.5" @@ -594,22 +494,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" -[[package]] -name = "colorchoice" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" - -[[package]] -name = "colored" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" -dependencies = [ - "lazy_static", - "windows-sys 0.59.0", -] - [[package]] name = "concurrent-queue" version = "2.5.0" @@ -1222,31 +1106,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "gdk-pixbuf" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd242894c084f4beed508a56952750bce3e96e85eb68fdc153637daa163e10c" -dependencies = [ - "gdk-pixbuf-sys", - "gio", - "glib", - "libc", -] - -[[package]] -name = "gdk-pixbuf-sys" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b34f3b580c988bd217e9543a2de59823fafae369d1a055555e5f95a8b130b96" -dependencies = [ - "gio-sys", - "glib-sys", - "gobject-sys", - "libc", - "system-deps", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1317,80 +1176,6 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" -[[package]] -name = "gio" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2a5c3829f5794cb15120db87707b2ec03720edff7ad09eb7b711b532e3fe747" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-util", - "gio-sys", - "glib", - "libc", - "pin-project-lite", - "smallvec", -] - -[[package]] -name = "gio-sys" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521e93a7e56fc89e84aea9a52cfc9436816a4b363b030260b699950ff1336c83" -dependencies = [ - "glib-sys", - "gobject-sys", - "libc", - "system-deps", - "windows-sys 0.59.0", -] - -[[package]] -name = "glib" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c501c495842c2b23cdacead803a5a343ca2a5d7a7ddaff14cc5f6cf22cfb92c2" -dependencies = [ - "bitflags 2.6.0", - "futures-channel", - "futures-core", - "futures-executor", - "futures-task", - "futures-util", - "gio-sys", - "glib-macros", - "glib-sys", - "gobject-sys", - "libc", - "memchr", - "smallvec", -] - -[[package]] -name = "glib-macros" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe6dc9ce29887c4b3b74d78d5ba473db160a258ae7ed883d23632ac7fed7bc9" -dependencies = [ - "heck", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 2.0.89", -] - -[[package]] -name = "glib-sys" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ab79e1ed126803a8fb827e3de0e2ff95191912b8db65cee467edb56fc4cc215" -dependencies = [ - "libc", - "system-deps", -] - [[package]] name = "gloo-timers" version = "0.3.0" @@ -1403,17 +1188,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gobject-sys" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec9aca94bb73989e3cfdbf8f2e0f1f6da04db4d291c431f444838925c4c63eda" -dependencies = [ - "glib-sys", - "libc", - "system-deps", -] - [[package]] name = "h2" version = "0.3.26" @@ -1983,12 +1757,6 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" -[[package]] -name = "is_terminal_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" - [[package]] name = "isahc" version = "0.9.14" @@ -2341,15 +2109,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - [[package]] name = "object" version = "0.36.4" @@ -2365,12 +2124,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "once_cell_polyfill" -version = "1.70.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" - [[package]] name = "opaque-debug" version = "0.3.1" @@ -2601,15 +2354,6 @@ dependencies = [ "zerocopy 0.7.35", ] -[[package]] -name = "proc-macro-crate" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" -dependencies = [ - "toml_edit", -] - [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -3357,18 +3101,6 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" -[[package]] -name = "simple_logger" -version = "4.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e7e46c8c90251d47d08b28b8a419ffb4aede0f87c2eea95e17d1d5bacbf3ef1" -dependencies = [ - "colored", - "log", - "time 0.3.36", - "windows-sys 0.48.0", -] - [[package]] name = "simplecss" version = "0.2.2" @@ -3389,20 +3121,16 @@ name = "skynet_discord_bot" version = "0.1.0" dependencies = [ "chrono", - "clap", "color-eyre", "dotenvy", "eyre", - "gdk-pixbuf", "lettre", - "log", "maud", "rand 0.9.0", "resvg", "serde", "serde_json", "serenity", - "simple_logger", "sqlx", "surf", "tiny-skia", @@ -3764,12 +3492,6 @@ dependencies = [ "unicode-properties", ] -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "subtle" version = "2.6.1" @@ -3919,25 +3641,6 @@ dependencies = [ "libc", ] -[[package]] -name = "system-deps" -version = "7.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4be53aa0cba896d2dc615bd42bbc130acdcffa239e0a2d965ea5b3b2a86ffdb" -dependencies = [ - "cfg-expr", - "heck", - "pkg-config", - "toml", - "version-compare", -] - -[[package]] -name = "target-lexicon" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e502f78cdbb8ba4718f566c418c52bc729126ffd16baee5baa718cf25dd5a69a" - [[package]] name = "tempfile" version = "3.12.0" @@ -4024,9 +3727,7 @@ checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", - "libc", "num-conv", - "num_threads", "powerfmt", "serde", "time-core", @@ -4547,12 +4248,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - [[package]] name = "valuable" version = "0.1.1" @@ -4571,12 +4266,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "version-compare" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" - [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index deeeca5..3dffd41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,14 +48,10 @@ maud = "0.27" toml = "0.8.23" serde = "1.0" -gdk-pixbuf = "0.20.10" -clap = { version = "4.1.4", features = ["derive"] } - +# for image conversion eyre = "0.6.8" color-eyre = "0.6.2" usvg-text-layout = "0.29.0" usvg = "0.29.0" resvg = "0.29.0" -tiny-skia = "0.8.3" -log = "0.4.20" -simple_logger = "4.2.0" \ No newline at end of file +tiny-skia = "0.8.3" \ No newline at end of file diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index 15db9bd..5b42ac1 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -1,30 +1,30 @@ +use chrono::{Datelike, Utc}; +use rand::{rngs::SmallRng, seq::IndexedRandom, SeedableRng}; +use serde::Deserialize; use serenity::{ + all::GuildId, async_trait, + builder::{CreateAttachment, EditGuild}, client::{Context, EventHandler}, model::gateway::{GatewayIntents, Ready}, Client, }; -use skynet_discord_bot::common::database::{db_init, DataBase, RoleAdder}; -use skynet_discord_bot::{get_config, get_now_iso, Config}; -use std::{fs, process, sync::Arc}; -use std::ffi::{OsStr, OsString}; -use std::fs::File; -use std::io::Read; -use std::path::PathBuf; -use std::process::Command; -use chrono::{Datelike, Utc}; -use gdk_pixbuf::{Pixbuf, PixbufFormat, PixbufLoader}; -use gdk_pixbuf::prelude::PixbufLoaderExt; -use rand::rngs::SmallRng; -use rand::SeedableRng; -use rand::seq::IndexedRandom; -use resvg::usvg; -use serde::Deserialize; -use serenity::all::GuildId; -use serenity::builder::{CreateAttachment, EditGuild}; +use skynet_discord_bot::{ + common::{ + database::{db_init, DataBase}, + renderer::{Args, Renderer}, + }, + get_config, get_now_iso, Config, +}; use sqlx::{Pool, Sqlite}; +use std::{ + ffi::OsString, + fs, + path::PathBuf, + process::{self, Command}, + sync::Arc, +}; use tokio::sync::RwLock; -use skynet_discord_bot::common::renderer::{Args, Renderer}; #[tokio::main] async fn main() { @@ -61,7 +61,6 @@ impl EventHandler for Handler { let ctx = Arc::new(ctx); println!("{} is connected!", ready.user.name); - // pull in the open governance repo // u[date committee server update_icon_main(Arc::clone(&ctx)).await; @@ -82,19 +81,17 @@ async fn update_icon_main(ctx: Arc) { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() }; - - + let config_global = config_lock.read().await; let config_toml = get_config_icons(); let server = GuildId::new(689189992417067052); - - + // clone repo into local folder clone_repo(&config_global, &config_toml); - + // see if there is a current festival let festival_data = get_festival(&config_toml); - + // get a list of all the graphics files let logos = get_logos(&config_global, &config_toml); @@ -104,31 +101,29 @@ async fn update_icon_main(ctx: Arc) { let mut rng = SmallRng::from_os_rng(); let logo_selected = logos_filtered.choose(&mut rng).unwrap(); - logo_set(&ctx, &db,&server, logo_selected).await; + logo_set(&ctx, &db, &server, logo_selected).await; } -struct FestivalData{ +#[derive(Debug)] +struct FestivalData { current: Vec, exclusions: Vec, } -fn get_festival(config_toml: &ConfigToml)-> FestivalData { +fn get_festival(config_toml: &ConfigToml) -> FestivalData { let today = Utc::now(); let day = today.day(); let month = today.month(); let year = today.year(); - + let mut result = FestivalData { current: vec![], exclusions: vec![], }; - + for festival in &config_toml.festivals { - if (day >= festival.start.day && day <= festival.end.day) && - (month >= festival.start.month && month <= festival.end.month ) { - if festival.start.year == 0 || festival.end.year == 0 { - result.current.push(festival.name.to_owned()); - } else if (year >= festival.start.year && year <= festival.end.year) { + if (day >= festival.start.day && day <= festival.end.day) && (month >= festival.start.month && month <= festival.end.month) { + if festival.start.year == 0 || festival.end.year == 0 || (year >= festival.start.year && year <= festival.end.year) { result.current.push(festival.name.to_owned()); } } else if !festival.all_year { @@ -144,24 +139,24 @@ struct ConfigToml { source: ConfigTomlSource, festivals: Vec, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] struct ConfigTomlSource { repo: String, directory: String, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] struct ConfigTomlFestivals { name: String, all_year: bool, start: ConfigTomlFestivalsTime, end: ConfigTomlFestivalsTime, } -#[derive(Deserialize)] +#[derive(Deserialize, Debug)] struct ConfigTomlFestivalsTime { day: u32, month: u32, - year:i32 + year: i32, } fn get_config_icons() -> ConfigToml { @@ -169,56 +164,34 @@ fn get_config_icons() -> ConfigToml { let config: ConfigToml = toml::from_str(toml_raw).unwrap(); config } -fn clone_repo(config: &Config, config_toml: &ConfigToml){ +fn clone_repo(config: &Config, config_toml: &ConfigToml) { let url = &config_toml.source.repo; let folder = format!("{}/open-governance", &config.home); - Command::new("git") - .arg("clone") - .arg(url) - .arg(&folder) - .output() - .expect("failed to execute process"); + Command::new("git").arg("clone").arg(url).arg(&folder).output().expect("failed to execute process"); Command::new("git") - .arg("pull") - .arg("origin") - .arg("main") - .current_dir(&folder) - .output() - .expect("failed to execute process"); -} - -fn convert_svg_to_png(original: &PathBuf, out: &PathBuf){ - let mut f = File::open(original).unwrap(); - let mut buffer = Vec::new(); - // read the whole file - f.read_to_end(&mut buffer).unwrap(); - let loader = PixbufLoader::with_mime_type("image/svg+xml") - .expect("error loader"); - loader.write(&buffer).expect("TODO: panic message"); - loader.close().expect("TODO: panic message"); - let pixbuf = loader.pixbuf().expect("no pixbuf"); - - let (width, height) = (pixbuf.width(), pixbuf.height()); - println!("size: {}x{}", width, height); - let bytes: Vec = - pixbuf.save_to_bufferv("png", &[]).expect("must not error"); - fs::write(out, &bytes).expect("TODO: panic message"); + .arg("pull") + .arg("origin") + .arg("main") + .current_dir(&folder) + .output() + .expect("failed to execute process"); } +#[derive(Debug)] struct LogoData { name: OsString, path: PathBuf, } -fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { +fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { let folder = format!("{}/open-governance/{}", &config.home, &config_toml.source.directory); let folder_path = PathBuf::from(&folder); let mut folder_output = folder_path.clone(); folder_output.push("converted"); let paths = fs::read_dir(folder).unwrap(); - let args = Args{ + let args = Args { input: folder_path.clone(), output: folder_output, colors: String::from(""), @@ -226,15 +199,15 @@ fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { height: 1024, }; let mut r = Renderer::new(&args).unwrap(); - + let mut logos = vec![]; - + for tmp in paths.flatten() { let path_local = tmp.path().to_owned(); let path_local2 = tmp.path().to_owned(); let name = path_local2.file_name().unwrap().to_owned(); let mut path = tmp.path(); - + if path.is_dir() { continue; } @@ -250,14 +223,14 @@ fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { path_new.pop(); path_new.push("converted"); path_new.push(filename); - + // check if exists if !path_new.exists() { // convert if it hasnt been converted already match r.render(&path_local, &args) { - Ok(_) => log::info!("Successfully rendered all colors of {path_local:?}"), - Err(e) => { - log::error!("Failed to render {path_local:?}: {}", e) + Ok(_) => {} + Err(_e) => { + dbg!("Failed to render {path_local:?}: {}"); } } } @@ -266,18 +239,18 @@ fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { } }; - logos.push(LogoData{ + logos.push(LogoData { name, path, }); - println!("Name: {}", &tmp.path().display()); + // println!("Name: {}", &tmp.path().display()); } - + logos } -fn logos_filter(festival_data: &FestivalData, existing: Vec) -> Vec{ +fn logos_filter(festival_data: &FestivalData, existing: Vec) -> Vec { let mut filtered: Vec = vec![]; 'outer: for logo in existing { @@ -310,7 +283,6 @@ fn logos_filter(festival_data: &FestivalData, existing: Vec) -> Vec, db: &Pool, server: &GuildId, logo_selected: &LogoData){ +async fn logo_set(ctx: &Arc, db: &Pool, server: &GuildId, logo_selected: &LogoData) { // add to teh database - logo_set_db(db ,logo_selected).await; + logo_set_db(db, logo_selected).await; let icon = CreateAttachment::path(logo_selected.path.to_str().unwrap_or_default()).await.unwrap(); @@ -329,21 +301,27 @@ async fn logo_set(ctx: &Arc, db: &Pool, server: &GuildId, logo server.edit(ctx, builder).await.unwrap(); } -async fn logo_set_db(db: &Pool, logo_selected: &LogoData){ +async fn logo_set_db(db: &Pool, logo_selected: &LogoData) { let name = logo_selected.name.to_str().unwrap_or_default(); - sqlx::query_as::<_, ServerIcons>( + match sqlx::query_as::<_, ServerIcons>( " INSERT OR REPLACE INTO server_icons (name, date) - VALUES (?1, ?2, ?3) + VALUES (?1, ?2) ", ) - .bind(name) - .bind(get_now_iso(false)) - .fetch_optional(db) - .await; + .bind(name) + .bind(get_now_iso(false)) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + dbg!(e); + } + } } // fn command(config_toml: &ConfigToml, logo_selected: &LogoData){ // let web_url = format!("{}/src/branch/main/{}/{}", &config_toml.source.repo, &config_toml.source.directory, &logo_selected.name.to_str().unwrap_or_default()); -// } \ No newline at end of file +// } diff --git a/src/common/mod.rs b/src/common/mod.rs index 7b93f40..b82b7bb 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -3,4 +3,4 @@ pub mod minecraft; pub mod set_roles; pub mod wolves; -pub mod renderer; \ No newline at end of file +pub mod renderer; diff --git a/src/common/renderer.rs b/src/common/renderer.rs index 65af0a4..f99d891 100644 --- a/src/common/renderer.rs +++ b/src/common/renderer.rs @@ -1,216 +1,192 @@ // this code is taken from https://github.com/MCorange99/svg2colored-png/tree/main // I was unable to figure out how to use usvg myself so younked it from here. +use std::ffi::OsStr; use std::path::{Path, PathBuf}; -use clap::builder::OsStr; -use clap::Parser; -use color_eyre::{Result, eyre::bail}; +// use clap::builder::OsStr; +use color_eyre::{eyre::bail, Result}; use usvg_text_layout::TreeTextToPath; -#[derive(Parser, Debug, Clone)] -#[command(name = "svg2colored-png")] -#[command(author = "MCorange ")] -#[command(version = env!("CARGO_PKG_VERSION"))] -#[command(about = "Converts svgs to multiple png's that differ in color", long_about = "Made by MCorange ")] +#[derive(Debug, Clone)] pub struct Args { - /// Input folder with the SVG's - #[arg(long, short)] - pub input: PathBuf, + pub input: PathBuf, - /// Output folder where the PNG's will be placed - #[arg[long, short]] - pub output: PathBuf, + /// Output folder where the PNG's will be placed + pub output: PathBuf, - /// Comma seperated colors that will be used in HEX Eg. 000000,ffffff - /// Can be like an object: black:000000,white:ffffff - #[arg[long, short, default_value_t = String::from("0d6efd,6c757d,198754,0dcaf0,ffc107,dc3545,f8f9fa,212529,ffffff,000000")]] - pub colors: String, + /// Comma seperated colors that will be used in HEX Eg. 000000,ffffff + /// Can be like an object: black:000000,white:ffffff + pub colors: String, - /// Width of the generated PNG's - #[arg(long, default_value_t = 1024)] - pub width: u32, - - /// Height of the generated PNG's - #[arg(long, default_value_t = 1024)] - pub height: u32 + /// Width of the generated PNG's + pub width: u32, + /// Height of the generated PNG's + pub height: u32, } #[derive(Debug, Clone)] enum ColorType { - Array(Vec), - Object(Vec<(String, String)>), - None + Array(Vec), + Object(Vec<(String, String)>), + None, } - #[derive(Debug, Clone)] pub struct Renderer { - fontdb: usvg_text_layout::fontdb::Database, - colors: ColorType, - size: (u32, u32), - pub count: u64, + fontdb: usvg_text_layout::fontdb::Database, + colors: ColorType, + size: (u32, u32), + pub count: u64, } impl Renderer { - pub fn new(args: &Args) -> Result { - let mut db = usvg_text_layout::fontdb::Database::new(); - db.load_system_fonts(); + pub fn new(args: &Args) -> Result { + let mut db = usvg_text_layout::fontdb::Database::new(); + db.load_system_fonts(); - let mut this = Self { - fontdb: db, - colors: ColorType::None, - size: (args.width, args.height), - count: 0, - }; + let mut this = Self { + fontdb: db, + colors: ColorType::None, + size: (args.width, args.height), + count: 0, + }; - let colors = if args.colors.contains(':') { - //? object - let obj = args.colors.split(',').map(|s| { - let s = s.split(':').collect::>(); + let colors = if args.colors.contains(':') { + //? object + let obj = args + .colors + .split(',') + .map(|s| { + let s = s.split(':').collect::>(); - if s.len() < 2 { - log::error!("Invalid color object, try checking help"); - return None; - } + if s.len() < 2 { + dbg!("Invalid color object, try checking help"); + return None; + } - Some((s[0].to_string(), s[1].to_string())) - }).collect::>>(); + Some((s[0].to_string(), s[1].to_string())) + }) + .collect::>>(); - let mut colors = Vec::new(); + let mut colors = Vec::new(); - for c in obj { - if let Some(c) = c { - std::fs::create_dir_all(args.output.join(&c.0))?; + for c in obj.into_iter().flatten() { + std::fs::create_dir_all(args.output.join(&c.0))?; - colors.push(c); - } - } + colors.push(c); + } - ColorType::Object(colors) + ColorType::Object(colors) + } else { + //? list + // let colors = args.colors.split(",").map(|s| { + // s.to_string() + // }) + // .collect::>(); - } else { - //? list - // let colors = args.colors.split(",").map(|s| { - // s.to_string() - // }) - // .collect::>(); + let mut colors = Vec::new(); - let mut colors = Vec::new(); + for color in args.colors.split(',') { + std::fs::create_dir_all(args.output.join(color))?; + colors.push(color.to_string()) + } - for color in args.colors.split(',') { - std::fs::create_dir_all(args.output.join(&color))?; - colors.push(color.to_string()) - } + ColorType::Array(colors) + }; + this.colors = colors; + Ok(this) + } - ColorType::Array(colors) - }; + pub fn render(&mut self, fi: &Path, args: &Args) -> Result<()> { + match fi.extension() { + Some(e) if e.to_str() == Some("svg") => {} + Some(_) | None => { + dbg!("Filer {:?} is not of type SVG", fi); + // util::logger::warning(format!("File '{}' is not of SVG type", fi.clone().to_str().unwrap())); + bail!("Failed to render"); + } + }; - this.colors = colors; - Ok(this) - } - - - pub fn render(&mut self, fi: &Path, args: &Args) -> Result<()> { - match fi.extension() { - Some(e) if e.to_str() == Some("svg") => {}, - Some(_) | - None => { - log::warn!("Filer {:?} is not of type SVG", fi); - // util::logger::warning(format!("File '{}' is not of SVG type", fi.clone().to_str().unwrap())); - bail!("Failed to render"); - } - }; - - match self.colors.clone() { - ColorType::Array(c) => { - for color in c { - log::info!("Rendering the color {color:?}"); - let fo = self.get_out_file(fi, &color, &args); - self.render_one(fi, &fo, &color)?; - - } - }, - ColorType::Object(c) => { - for o in c { - log::info!("Rendering the color {:?}", o); - let fo = self.get_out_file(fi, &o.0, &args); - self.render_one(fi, &fo, &o.1)?; - - } - }, - ColorType::None => unreachable!(), + match self.colors.clone() { + ColorType::Array(c) => { + for color in c { + // log::info!("Rendering the color {color:?}"); + let fo = self.get_out_file(fi, &color, args); + self.render_one(fi, &fo, &color)?; } - - Ok(()) - } - - fn render_one(&mut self, fi: &Path, fo: &Path, color: &String) -> Result<()>{ - - if fo.exists() { - log::warn!("File {fo:?} exists, skipping"); - return Ok(()); + } + ColorType::Object(c) => { + for o in c { + // log::info!("Rendering the color {:?}", o); + let fo = self.get_out_file(fi, &o.0, args); + self.render_one(fi, &fo, &o.1)?; } - - let svg = self.set_color(&self.get_svg_data(fi)?, &color); - - let mut opt = usvg::Options::default(); - // Get file's absolute directory. - opt.resources_dir = std::fs::canonicalize(fi.clone()) - .ok() - .and_then(|p| p.parent().map(|p| p.to_path_buf())); - - let mut tree = match usvg::Tree::from_data(svg.as_bytes(), &opt) { - Ok(v) => v, - Err(_) => { - log::error!("Failed to parse {fi:?}"); - bail!(""); - } - }; - - tree.convert_text(&self.fontdb); - - let mut pixmap = tiny_skia::Pixmap::new(self.size.0, self.size.1).unwrap(); - - log::info!("Rendering {fo:?}"); - - //? maybe handle this and possibly throw error if its none - let _ = resvg::render( - &tree, - usvg::FitTo::Size(self.size.0, self.size.1), - tiny_skia::Transform::default(), - pixmap.as_mut(), - ); - - - pixmap.save_png(fo)?; - self.count += 1; - Ok(()) + } + ColorType::None => unreachable!(), } + Ok(()) + } - #[inline] - fn get_out_file(&mut self, fi: &Path, sub_folder: &String, args: &Args) -> PathBuf { - let mut fo: std::path::PathBuf = args.output.clone(); - // fo.push(sub_folder); - fo.push(fi.file_name().unwrap_or(&OsStr::from("default")).to_str().unwrap_or("default").replace(".svg", "")); - fo.set_extension("png"); - fo + fn render_one(&mut self, fi: &Path, fo: &Path, color: &String) -> Result<()> { + if fo.exists() { + dbg!("File {fo:?} exists, skipping"); + return Ok(()); } - fn set_color(&self, svg: &String, color: &String) -> String { - svg.replace("fill=\"currentColor\"", &format!("fill=\"#{}\"", color)) - } + let svg = self.set_color(&self.get_svg_data(fi)?, color); - fn get_svg_data(&self, fi: &Path) -> Result{ - match std::fs::read_to_string(fi) { - Ok(d) => Ok(d), - Err(_) => { - log::error!("File {fi:?} does not exist"); - bail!("File {fi:?} does not exist"); - } - } + let opt = usvg::Options { + // Get file's absolute directory. + resources_dir: std::fs::canonicalize(fi).ok().and_then(|p| p.parent().map(|p| p.to_path_buf())), + ..Default::default() + }; + + let mut tree = match usvg::Tree::from_data(svg.as_bytes(), &opt) { + Ok(v) => v, + Err(_) => { + dbg!("Failed to parse {fi:?}"); + bail!(""); + } + }; + + tree.convert_text(&self.fontdb); + + let mut pixmap = tiny_skia::Pixmap::new(self.size.0, self.size.1).unwrap(); + + // log::info!("Rendering {fo:?}"); + + //? maybe handle this and possibly throw error if its none + let _ = resvg::render(&tree, usvg::FitTo::Size(self.size.0, self.size.1), tiny_skia::Transform::default(), pixmap.as_mut()); + + pixmap.save_png(fo)?; + self.count += 1; + Ok(()) + } + + #[inline] + fn get_out_file(&mut self, fi: &Path, _sub_folder: &str, args: &Args) -> PathBuf { + let mut fo: std::path::PathBuf = args.output.clone(); + // fo.push(sub_folder); + fo.push(fi.file_name().unwrap_or(OsStr::new("default")).to_str().unwrap_or("default").replace(".svg", "")); + fo.set_extension("png"); + fo + } + + fn set_color(&self, svg: &str, color: &String) -> String { + svg.replace("fill=\"currentColor\"", &format!("fill=\"#{}\"", color)) + } + + fn get_svg_data(&self, fi: &Path) -> Result { + match std::fs::read_to_string(fi) { + Ok(d) => Ok(d), + Err(_) => { + dbg!("File {fi:?} does not exist"); + bail!("File {fi:?} does not exist"); + } } -} \ No newline at end of file + } +} From 6d5ad8e418ceef1e1f87c6829ef13cd57990f343 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 15 Jun 2025 22:07:52 +0100 Subject: [PATCH 125/170] feat: split out the functions so they can be shared with commands --- src/bin/update_server-icon.rs | 293 +++------------------------------- src/common/mod.rs | 1 + src/common/server_icon.rs | 263 ++++++++++++++++++++++++++++++ 3 files changed, 283 insertions(+), 274 deletions(-) create mode 100644 src/common/server_icon.rs diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index 5b42ac1..4d49490 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -1,27 +1,16 @@ -use chrono::{Datelike, Utc}; -use rand::{rngs::SmallRng, seq::IndexedRandom, SeedableRng}; -use serde::Deserialize; use serenity::{ - all::GuildId, async_trait, - builder::{CreateAttachment, EditGuild}, client::{Context, EventHandler}, model::gateway::{GatewayIntents, Ready}, Client, }; +use skynet_discord_bot::common::server_icon; use skynet_discord_bot::{ - common::{ - database::{db_init, DataBase}, - renderer::{Args, Renderer}, - }, - get_config, get_now_iso, Config, + common::database::{db_init, DataBase}, + get_config, Config, }; -use sqlx::{Pool, Sqlite}; use std::{ - ffi::OsString, - fs, - path::PathBuf, - process::{self, Command}, + process::self, sync::Arc, }; use tokio::sync::RwLock; @@ -61,267 +50,23 @@ impl EventHandler for Handler { let ctx = Arc::new(ctx); println!("{} is connected!", ready.user.name); - // pull in the open governance repo - // u[date committee server - update_icon_main(Arc::clone(&ctx)).await; + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + + let config_global = config_lock.read().await; + let config_toml = server_icon::get_config_icons(); + + server_icon::update_icon::update_icon_main(&ctx, &db, &config_global, &config_toml).await; // finish up process::exit(0); } } - -async fn update_icon_main(ctx: Arc) { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let db = db_lock.read().await; - - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - - let config_global = config_lock.read().await; - let config_toml = get_config_icons(); - let server = GuildId::new(689189992417067052); - - // clone repo into local folder - clone_repo(&config_global, &config_toml); - - // see if there is a current festival - let festival_data = get_festival(&config_toml); - - // get a list of all the graphics files - let logos = get_logos(&config_global, &config_toml); - - // filter them so only the current season (if any) are active - let logos_filtered = logos_filter(&festival_data, logos); - - let mut rng = SmallRng::from_os_rng(); - let logo_selected = logos_filtered.choose(&mut rng).unwrap(); - - logo_set(&ctx, &db, &server, logo_selected).await; -} - -#[derive(Debug)] -struct FestivalData { - current: Vec, - exclusions: Vec, -} - -fn get_festival(config_toml: &ConfigToml) -> FestivalData { - let today = Utc::now(); - let day = today.day(); - let month = today.month(); - let year = today.year(); - - let mut result = FestivalData { - current: vec![], - exclusions: vec![], - }; - - for festival in &config_toml.festivals { - if (day >= festival.start.day && day <= festival.end.day) && (month >= festival.start.month && month <= festival.end.month) { - if festival.start.year == 0 || festival.end.year == 0 || (year >= festival.start.year && year <= festival.end.year) { - result.current.push(festival.name.to_owned()); - } - } else if !festival.all_year { - result.exclusions.push(festival.name.to_owned()); - } - } - - result -} - -#[derive(Deserialize)] -struct ConfigToml { - source: ConfigTomlSource, - festivals: Vec, -} -#[derive(Deserialize, Debug)] -struct ConfigTomlSource { - repo: String, - directory: String, -} - -#[derive(Deserialize, Debug)] -struct ConfigTomlFestivals { - name: String, - all_year: bool, - start: ConfigTomlFestivalsTime, - end: ConfigTomlFestivalsTime, -} -#[derive(Deserialize, Debug)] -struct ConfigTomlFestivalsTime { - day: u32, - month: u32, - year: i32, -} - -fn get_config_icons() -> ConfigToml { - let toml_raw = include_str!("../../.server-icons.toml"); - let config: ConfigToml = toml::from_str(toml_raw).unwrap(); - config -} -fn clone_repo(config: &Config, config_toml: &ConfigToml) { - let url = &config_toml.source.repo; - let folder = format!("{}/open-governance", &config.home); - - Command::new("git").arg("clone").arg(url).arg(&folder).output().expect("failed to execute process"); - - Command::new("git") - .arg("pull") - .arg("origin") - .arg("main") - .current_dir(&folder) - .output() - .expect("failed to execute process"); -} - -#[derive(Debug)] -struct LogoData { - name: OsString, - path: PathBuf, -} -fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { - let folder = format!("{}/open-governance/{}", &config.home, &config_toml.source.directory); - let folder_path = PathBuf::from(&folder); - let mut folder_output = folder_path.clone(); - folder_output.push("converted"); - let paths = fs::read_dir(folder).unwrap(); - - let args = Args { - input: folder_path.clone(), - output: folder_output, - colors: String::from(""), - width: 1024, - height: 1024, - }; - let mut r = Renderer::new(&args).unwrap(); - - let mut logos = vec![]; - - for tmp in paths.flatten() { - let path_local = tmp.path().to_owned(); - let path_local2 = tmp.path().to_owned(); - let name = path_local2.file_name().unwrap().to_owned(); - let mut path = tmp.path(); - - if path.is_dir() { - continue; - } - - match tmp.path().extension() { - None => {} - Some(ext) => { - if ext == "svg" { - let mut path_new = path_local.clone(); - path_new.set_extension("png"); - let filename_tmp = path_new.clone(); - let filename = filename_tmp.file_name().unwrap_or_default(); - path_new.pop(); - path_new.push("converted"); - path_new.push(filename); - - // check if exists - if !path_new.exists() { - // convert if it hasnt been converted already - match r.render(&path_local, &args) { - Ok(_) => {} - Err(_e) => { - dbg!("Failed to render {path_local:?}: {}"); - } - } - } - path = path_new; - } - } - }; - - logos.push(LogoData { - name, - path, - }); - - // println!("Name: {}", &tmp.path().display()); - } - - logos -} - -fn logos_filter(festival_data: &FestivalData, existing: Vec) -> Vec { - let mut filtered: Vec = vec![]; - - 'outer: for logo in existing { - let name_lowercase0 = logo.name.to_ascii_lowercase(); - let name_lowercase = name_lowercase0.to_str().unwrap_or_default(); - - if !festival_data.current.is_empty() { - // if its a current festival filter based on it - for festival in &festival_data.current { - if name_lowercase.contains(festival) { - filtered.push(logo); - continue 'outer; - } - } - } else { - // else filter using the excluded ones - let mut excluded = false; - for festival in &festival_data.exclusions { - if name_lowercase.contains(festival) { - excluded = true; - } - } - - if !excluded { - filtered.push(logo); - } - } - } - - filtered -} - -#[derive(Debug, Clone, sqlx::FromRow)] -pub struct ServerIcons { - pub id: i64, - pub name: String, - pub date: String, -} - -async fn logo_set(ctx: &Arc, db: &Pool, server: &GuildId, logo_selected: &LogoData) { - // add to teh database - logo_set_db(db, logo_selected).await; - - let icon = CreateAttachment::path(logo_selected.path.to_str().unwrap_or_default()).await.unwrap(); - - // assuming a `guild` has already been bound - let builder = EditGuild::new().icon(Some(&icon)); - server.edit(ctx, builder).await.unwrap(); -} - -async fn logo_set_db(db: &Pool, logo_selected: &LogoData) { - let name = logo_selected.name.to_str().unwrap_or_default(); - - match sqlx::query_as::<_, ServerIcons>( - " - INSERT OR REPLACE INTO server_icons (name, date) - VALUES (?1, ?2) - ", - ) - .bind(name) - .bind(get_now_iso(false)) - .fetch_optional(db) - .await - { - Ok(_) => {} - Err(e) => { - dbg!(e); - } - } -} - -// fn command(config_toml: &ConfigToml, logo_selected: &LogoData){ -// let web_url = format!("{}/src/branch/main/{}/{}", &config_toml.source.repo, &config_toml.source.directory, &logo_selected.name.to_str().unwrap_or_default()); -// } diff --git a/src/common/mod.rs b/src/common/mod.rs index b82b7bb..9e1745e 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -4,3 +4,4 @@ pub mod set_roles; pub mod wolves; pub mod renderer; +pub mod server_icon; diff --git a/src/common/server_icon.rs b/src/common/server_icon.rs new file mode 100644 index 0000000..4a489be --- /dev/null +++ b/src/common/server_icon.rs @@ -0,0 +1,263 @@ +use serde::Deserialize; +use std::{ffi::OsString, path::PathBuf}; + +#[derive(Deserialize)] +pub struct ConfigToml { + pub source: ConfigTomlSource, + pub festivals: Vec, +} + +#[derive(Deserialize, Debug)] +pub struct ConfigTomlSource { + pub repo: String, + pub directory: String, +} + +#[derive(Deserialize, Debug)] +pub struct ConfigTomlFestivals { + pub name: String, + pub all_year: bool, + pub start: ConfigTomlFestivalsTime, + pub end: ConfigTomlFestivalsTime, +} + +#[derive(Deserialize, Debug)] +pub struct ConfigTomlFestivalsTime { + pub day: u32, + pub month: u32, + pub year: i32, +} + +pub fn get_config_icons() -> ConfigToml { + let toml_raw = include_str!("../../.server-icons.toml"); + let config: ConfigToml = toml::from_str(toml_raw).unwrap(); + config +} + +#[derive(Debug)] +pub struct LogoData { + pub name: OsString, + pub path: PathBuf, +} + +#[derive(Debug, Clone, sqlx::FromRow)] +pub struct ServerIcons { + pub id: i64, + pub name: String, + pub date: String, +} + +pub mod update_icon { + use super::*; + use crate::{ + common::renderer::{Args, Renderer}, + get_now_iso, Config, + }; + use chrono::{Datelike, Utc}; + use rand::{rngs::SmallRng, seq::IndexedRandom, SeedableRng}; + use serenity::{ + all::GuildId, + builder::{CreateAttachment, EditGuild}, + client::Context, + }; + use sqlx::{Pool, Sqlite}; + use std::{fs, process::Command}; + + /// Update the server icon, pulling from open governance. + pub async fn update_icon_main(ctx: &Context, db: &Pool, config_global: &Config, config_toml: &ConfigToml) { + let server = GuildId::new(689189992417067052); + + // clone repo into local folder + clone_repo(config_global, config_toml); + + // see if there is a current festival + let festival_data = get_festival(config_toml); + + // get a list of all the graphics files + let logos = get_logos(config_global, config_toml); + + // filter them so only the current season (if any) are active + let logos_filtered = logos_filter(&festival_data, logos); + + let mut rng = SmallRng::from_os_rng(); + let logo_selected = logos_filtered.choose(&mut rng).unwrap(); + + logo_set(ctx, db, &server, logo_selected).await; + } + + #[derive(Debug)] + struct FestivalData { + current: Vec, + exclusions: Vec, + } + + fn get_festival(config_toml: &ConfigToml) -> FestivalData { + let today = Utc::now(); + let day = today.day(); + let month = today.month(); + let year = today.year(); + + let mut result = FestivalData { + current: vec![], + exclusions: vec![], + }; + + for festival in &config_toml.festivals { + if (day >= festival.start.day && day <= festival.end.day) && (month >= festival.start.month && month <= festival.end.month) { + if festival.start.year == 0 || festival.end.year == 0 || (year >= festival.start.year && year <= festival.end.year) { + result.current.push(festival.name.to_owned()); + } + } else if !festival.all_year { + result.exclusions.push(festival.name.to_owned()); + } + } + + result + } + + fn clone_repo(config: &Config, config_toml: &ConfigToml) { + let url = &config_toml.source.repo; + let folder = format!("{}/open-governance", &config.home); + + Command::new("git").arg("clone").arg(url).arg(&folder).output().expect("failed to execute process"); + + Command::new("git") + .arg("pull") + .arg("origin") + .arg("main") + .current_dir(&folder) + .output() + .expect("failed to execute process"); + } + + fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { + let folder = format!("{}/open-governance/{}", &config.home, &config_toml.source.directory); + let folder_path = PathBuf::from(&folder); + let mut folder_output = folder_path.clone(); + folder_output.push("converted"); + let paths = fs::read_dir(folder).unwrap(); + + let args = Args { + input: folder_path.clone(), + output: folder_output, + colors: String::from(""), + width: 1024, + height: 1024, + }; + let mut r = Renderer::new(&args).unwrap(); + + let mut logos = vec![]; + + for tmp in paths.flatten() { + let path_local = tmp.path().to_owned(); + let path_local2 = tmp.path().to_owned(); + let name = path_local2.file_name().unwrap().to_owned(); + let mut path = tmp.path(); + + if path.is_dir() { + continue; + } + + match tmp.path().extension() { + None => {} + Some(ext) => { + if ext == "svg" { + let mut path_new = path_local.clone(); + path_new.set_extension("png"); + let filename_tmp = path_new.clone(); + let filename = filename_tmp.file_name().unwrap_or_default(); + path_new.pop(); + path_new.push("converted"); + path_new.push(filename); + + // check if exists + if !path_new.exists() { + // convert if it hasnt been converted already + match r.render(&path_local, &args) { + Ok(_) => {} + Err(_e) => { + dbg!("Failed to render {path_local:?}: {}"); + } + } + } + path = path_new; + } + } + }; + + logos.push(LogoData { + name, + path, + }); + + // println!("Name: {}", &tmp.path().display()); + } + + logos + } + + fn logos_filter(festival_data: &FestivalData, existing: Vec) -> Vec { + let mut filtered: Vec = vec![]; + + 'outer: for logo in existing { + let name_lowercase0 = logo.name.to_ascii_lowercase(); + let name_lowercase = name_lowercase0.to_str().unwrap_or_default(); + + if !festival_data.current.is_empty() { + // if its a current festival filter based on it + for festival in &festival_data.current { + if name_lowercase.contains(festival) { + filtered.push(logo); + continue 'outer; + } + } + } else { + // else filter using the excluded ones + let mut excluded = false; + for festival in &festival_data.exclusions { + if name_lowercase.contains(festival) { + excluded = true; + } + } + + if !excluded { + filtered.push(logo); + } + } + } + + filtered + } + + async fn logo_set(ctx: &Context, db: &Pool, server: &GuildId, logo_selected: &LogoData) { + // add to teh database + logo_set_db(db, logo_selected).await; + + let icon = CreateAttachment::path(logo_selected.path.to_str().unwrap_or_default()).await.unwrap(); + + // assuming a `guild` has already been bound + let builder = EditGuild::new().icon(Some(&icon)); + server.edit(ctx, builder).await.unwrap(); + } + + async fn logo_set_db(db: &Pool, logo_selected: &LogoData) { + let name = logo_selected.name.to_str().unwrap_or_default(); + + match sqlx::query_as::<_, ServerIcons>( + " + INSERT OR REPLACE INTO server_icons (name, date) + VALUES (?1, ?2) + ", + ) + .bind(name) + .bind(get_now_iso(false)) + .fetch_optional(db) + .await + { + Ok(_) => {} + Err(e) => { + dbg!(e); + } + } + } +} From 86a3af2a65fc76d899195753cae61858dfbcd3a2 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Jun 2025 00:21:49 +0100 Subject: [PATCH 126/170] feat: pull the config for the festivals locally, using teh imported repo --- .server-icons.toml | 19 +------ src/bin/update_server-icon.rs | 11 ++-- src/common/server_icon.rs | 104 ++++++++++++++++++++++------------ 3 files changed, 74 insertions(+), 60 deletions(-) diff --git a/.server-icons.toml b/.server-icons.toml index 587f63b..eeb302c 100644 --- a/.server-icons.toml +++ b/.server-icons.toml @@ -3,21 +3,4 @@ [source] repo = "https://forgejo.skynet.ie/Computer_Society/open-goverance" directory = "Resources/Logo_Variants" - -[[festivals]] -name = "pride" -all_year = true -start = { day = 1, month = 6, year = 0} -end = { day = 30, month = 6, year = 0} - -[[festivals]] -name = "christmas" -all_year = false -start = { day = 1, month = 12, year = 0} -end = { day = 31, month = 12, year = 0} - -[[festivals]] -name = "halloween" -all_year = false -start = { day = 1, month = 12, year = 0} -end = { day = 31, month = 12, year = 0} \ No newline at end of file +file = "_festivals.toml" \ No newline at end of file diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index 4d49490..ff4cef9 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -4,15 +4,12 @@ use serenity::{ model::gateway::{GatewayIntents, Ready}, Client, }; -use skynet_discord_bot::common::server_icon; +use skynet_discord_bot::common::server_icon::{get_config_icons, update_icon}; use skynet_discord_bot::{ common::database::{db_init, DataBase}, get_config, Config, }; -use std::{ - process::self, - sync::Arc, -}; +use std::{process, sync::Arc}; use tokio::sync::RwLock; #[tokio::main] @@ -62,9 +59,9 @@ impl EventHandler for Handler { }; let config_global = config_lock.read().await; - let config_toml = server_icon::get_config_icons(); + let config_toml = get_config_icons::minimal(); - server_icon::update_icon::update_icon_main(&ctx, &db, &config_global, &config_toml).await; + update_icon::update_icon_main(&ctx, &db, &config_global, &config_toml).await; // finish up process::exit(0); diff --git a/src/common/server_icon.rs b/src/common/server_icon.rs index 4a489be..3f88d34 100644 --- a/src/common/server_icon.rs +++ b/src/common/server_icon.rs @@ -1,37 +1,65 @@ use serde::Deserialize; -use std::{ffi::OsString, path::PathBuf}; +use std::{ffi::OsString, fs, path::PathBuf}; -#[derive(Deserialize)] -pub struct ConfigToml { - pub source: ConfigTomlSource, - pub festivals: Vec, -} +pub mod get_config_icons { + use super::*; + use crate::Config; -#[derive(Deserialize, Debug)] -pub struct ConfigTomlSource { - pub repo: String, - pub directory: String, -} + #[derive(Deserialize)] + pub struct ConfigToml { + pub source: ConfigTomlSource, + pub festivals: Vec, + } -#[derive(Deserialize, Debug)] -pub struct ConfigTomlFestivals { - pub name: String, - pub all_year: bool, - pub start: ConfigTomlFestivalsTime, - pub end: ConfigTomlFestivalsTime, -} + #[derive(Deserialize)] + pub struct ConfigTomlLocal { + pub source: ConfigTomlSource, + } + #[derive(Deserialize)] + pub struct ConfigTomlRemote { + pub festivals: Vec, + } -#[derive(Deserialize, Debug)] -pub struct ConfigTomlFestivalsTime { - pub day: u32, - pub month: u32, - pub year: i32, -} + #[derive(Deserialize, Debug)] + pub struct ConfigTomlSource { + pub repo: String, + pub directory: String, + pub file: String, + } -pub fn get_config_icons() -> ConfigToml { - let toml_raw = include_str!("../../.server-icons.toml"); - let config: ConfigToml = toml::from_str(toml_raw).unwrap(); - config + #[derive(Deserialize, Debug)] + pub struct ConfigTomlFestivals { + pub name: String, + pub all_year: bool, + pub start: ConfigTomlFestivalsTime, + pub end: ConfigTomlFestivalsTime, + } + + #[derive(Deserialize, Debug)] + pub struct ConfigTomlFestivalsTime { + pub day: u32, + pub month: u32, + pub year: i32, + } + pub fn minimal() -> ConfigTomlLocal { + let toml_raw_min = include_str!("../../.server-icons.toml"); + let config_min: ConfigTomlLocal = toml::from_str(toml_raw_min).unwrap(); + config_min + } + + // since a copy of the festival file is in the repo we just need to get to it + pub fn full(config: &Config) -> ConfigToml { + let config_source = minimal(); + + let file_path = format!("{}/open-governance/{}/{}", &config.home, &config_source.source.directory, &config_source.source.file); + let contents = fs::read_to_string(file_path).expect("Should have been able to read the file"); + let config_festivals: ConfigTomlRemote = toml::from_str(&contents).unwrap(); + + ConfigToml { + source: config_source.source, + festivals: config_festivals.festivals, + } + } } #[derive(Debug)] @@ -50,7 +78,10 @@ pub struct ServerIcons { pub mod update_icon { use super::*; use crate::{ - common::renderer::{Args, Renderer}, + common::{ + renderer::{Args, Renderer}, + server_icon::get_config_icons::{self, ConfigToml, ConfigTomlLocal}, + }, get_now_iso, Config, }; use chrono::{Datelike, Utc}; @@ -61,20 +92,23 @@ pub mod update_icon { client::Context, }; use sqlx::{Pool, Sqlite}; - use std::{fs, process::Command}; + use std::process::Command; /// Update the server icon, pulling from open governance. - pub async fn update_icon_main(ctx: &Context, db: &Pool, config_global: &Config, config_toml: &ConfigToml) { + pub async fn update_icon_main(ctx: &Context, db: &Pool, config_global: &Config, config_toml_local: &ConfigTomlLocal) { let server = GuildId::new(689189992417067052); // clone repo into local folder - clone_repo(config_global, config_toml); + clone_repo(config_global, config_toml_local); + + // now the repo has been downloaded/updated we can now access the festivals + let config_toml = get_config_icons::full(config_global); // see if there is a current festival - let festival_data = get_festival(config_toml); + let festival_data = get_festival(&config_toml); // get a list of all the graphics files - let logos = get_logos(config_global, config_toml); + let logos = get_logos(config_global, &config_toml); // filter them so only the current season (if any) are active let logos_filtered = logos_filter(&festival_data, logos); @@ -115,7 +149,7 @@ pub mod update_icon { result } - fn clone_repo(config: &Config, config_toml: &ConfigToml) { + fn clone_repo(config: &Config, config_toml: &ConfigTomlLocal) { let url = &config_toml.source.repo; let folder = format!("{}/open-governance", &config.home); From 86f54aec6dbf387b685ebc5f6b327bf7bf328139 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Jun 2025 04:31:21 +0100 Subject: [PATCH 127/170] feat: got the commands mostly working, will need some further fine tuning --- src/commands/committee.rs | 4 + src/commands/mod.rs | 1 + src/commands/server_icon.rs | 208 ++++++++++++++++++++++++++++++++++++ src/main.rs | 51 ++++++++- 4 files changed, 262 insertions(+), 2 deletions(-) create mode 100644 src/commands/server_icon.rs diff --git a/src/commands/committee.rs b/src/commands/committee.rs index 4813c6a..944fbc0 100644 --- a/src/commands/committee.rs +++ b/src/commands/committee.rs @@ -20,4 +20,8 @@ pub fn register() -> CreateCommand { .add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_c", "Sum of A and B").required(true)) .add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "delete", "Delete this entry.").required(false)), ) + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommandGroup, "icon", "Committee commands for the server icon") + .add_sub_option(CreateCommandOption::new(CommandOptionType::SubCommand, "change", "Change the server icon.")), + ) } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index b513fc2..5815541 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -3,4 +3,5 @@ pub mod committee; pub mod count; pub mod minecraft; pub mod role_adder; +pub mod server_icon; pub mod wolves; diff --git a/src/commands/server_icon.rs b/src/commands/server_icon.rs new file mode 100644 index 0000000..a6114c5 --- /dev/null +++ b/src/commands/server_icon.rs @@ -0,0 +1,208 @@ +use serenity::all::{CommandInteraction, Context}; +use skynet_discord_bot::{ + common::{ + database::DataBase, + server_icon::{get_config_icons, update_icon::update_icon_main, ServerIcons}, + }, + Config, +}; + +use serenity::all::{CommandOptionType, CreateCommand, CreateCommandOption}; + +// commands that server mods are able to use +pub(crate) mod admin { + use super::*; + + // Moderators can force a icon change + pub(crate) mod change { + use super::*; + + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Databse in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config_global = config_lock.read().await; + + let config_toml = get_config_icons::minimal(); + update_icon_main(ctx, &db, &config_global, &config_toml).await; + + "Changed server Icon".to_string() + } + } +} + +// commands for general users +pub(crate) mod user { + use super::*; + use skynet_discord_bot::common::server_icon::get_config_icons::ConfigTomlLocal; + + pub fn register() -> CreateCommand { + CreateCommand::new("icon") + .description("Commands related to the Server Icon") + .add_option( + CreateCommandOption::new(CommandOptionType::SubCommandGroup, "current", "Information on current items.") + .add_sub_option(CreateCommandOption::new(CommandOptionType::SubCommand, "icon", "Information on current icon.")) + .add_sub_option(CreateCommandOption::new(CommandOptionType::SubCommand, "festival", "Information on current festival.")), + ) + .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "stats", "Some Stats.")) + } + + fn get_logo_url(config_toml: &ConfigTomlLocal, logo_name: &str) -> String { + format!("{}/src/branch/main/{}/{}", &config_toml.source.repo, &config_toml.source.directory, logo_name) + } + + /// Regular users can get teh link to teh current icon + pub(crate) mod current { + use super::*; + + pub(crate) mod icon { + use super::*; + + use sqlx::{Pool, Sqlite}; + + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Databse in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let config_toml = get_config_icons::minimal(); + + if let Some(logo) = get_current_icon(&db).await { + get_logo_url(&config_toml, &logo.name) + } else { + "Could not find current icon".to_string() + } + } + + async fn get_current_icon(db: &Pool) -> Option { + match sqlx::query_as::<_, ServerIcons>( + " + SELECT * from server_icons ORDER BY id DESC LIMIT 1 + ", + ) + .fetch_one(db) + .await + { + Ok(res) => Some(res), + Err(e) => { + dbg!(e); + None + } + } + } + } + + pub(crate) mod festival { + use serenity::all::{CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption}; + use skynet_discord_bot::common::server_icon::get_config_icons; + use skynet_discord_bot::Config; + + // use this to return what current festivals are active? + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config = config_lock.read().await; + + let config_toml = get_config_icons::full(&config); + + let mut response = vec![]; + + for festival in &config_toml.festivals { + response.push(festival.name.to_owned()); + } + + format!("Festivals active: {}", response.join(", ")) + } + } + } + + /// Get the statistics of the icons + pub(crate) mod stats { + use super::*; + use sqlx::{Pool, Sqlite}; + + pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Databse in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let config_toml = get_config_icons::minimal(); + + let totals = get_totals(&db).await; + + fmt_msg(&config_toml, &totals) + } + + #[derive(Debug, Clone, sqlx::FromRow)] + pub struct CountResult { + pub name: String, + pub times: i64, + } + + async fn get_totals(db: &Pool) -> Vec { + sqlx::query_as::<_, CountResult>( + " + SELECT + DISTINCT name, + COUNT(*) OVER(PARTITION BY name) AS times + FROM server_icons + ", + ) + .fetch_all(db) + .await + .unwrap_or_else(|e| { + dbg!(e); + vec![] + }) + } + + fn fmt_msg(config_toml: &ConfigTomlLocal, totals: &Vec) -> String { + // msg can be a max 2000 chars long + let mut limit = 2000 - 3; + + let mut response = vec![]; + for CountResult { + name, + times, + } in totals + { + let current_leading = if times < &10 { + "00" + } else if times < &100 { + "0" + } else { + "" + }; + + let url = get_logo_url(config_toml, name); + + let line = format!("{}{} <{}>", current_leading, times, url); + + let length = line.len() + 1; + + // +3 is to account for the closing fense + if length < (limit + 3) { + response.push(line); + limit -= length; + } else { + break; + } + } + + response.join("\n") + } + } +} diff --git a/src/main.rs b/src/main.rs index 6d5e849..3b57470 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,10 @@ pub mod commands; use crate::commands::role_adder::tools::on_role_change; -use serenity::all::{ActivityData, Command, CreateMessage, EditInteractionResponse, GuildId, GuildMemberUpdateEvent, Interaction}; +use serenity::all::{ + ActivityData, Command, CommandDataOption, CommandDataOptionValue, CommandOptionType, CreateMessage, EditInteractionResponse, GuildId, + GuildMemberUpdateEvent, Interaction, +}; use serenity::model::guild::Member; use serenity::{ async_trait, @@ -129,6 +132,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } } + // Inter-Committee server match GuildId::new(1220150752656363520) .set_commands(&ctx.http, vec![commands::count::committee::register()]) .await @@ -139,8 +143,16 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } } + // compsoc Server match GuildId::new(689189992417067052) - .set_commands(&ctx.http, vec![commands::count::servers::register()]) + .set_commands( + &ctx.http, + vec![ + // commands just for the compsoc server + commands::count::servers::register(), + commands::server_icon::user::register(), + ], + ) .await { Ok(_) => {} @@ -175,6 +187,18 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use Some(x) => match x.name.as_str() { "add" => commands::add_server::run(&command, &ctx).await, "roles_adder" => commands::role_adder::edit::run(&command, &ctx).await, + "icon" => match &x.value { + CommandDataOptionValue::SubCommandGroup(y) => match y.first() { + None => "error".to_string(), + Some(z) => match z.name.as_str() { + "change" => commands::server_icon::admin::change::run(&command, &ctx).await, + &_ => format!("not implemented :( count {}", x.name.as_str()), + }, + }, + _ => { + format!("not implemented :( committee {}", x.name.as_str()) + } + }, // "link" => commands::count::servers::run(&command, &ctx).await, &_ => format!("not implemented :( committee {}", x.name.as_str()), }, @@ -191,6 +215,29 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use &_ => format!("not implemented :( count {}", x.name.as_str()), }, }, + + "icon" => match command.data.options.first() { + None => "Invalid Command".to_string(), + Some(x) => match x.name.as_str() { + "current" => { + let result = match &x.value { + CommandDataOptionValue::SubCommandGroup(y) => match y.first() { + None => "error".to_string(), + Some(z) => match z.name.as_str() { + "icon" => commands::server_icon::user::current::icon::run(&command, &ctx).await, + "festival" => commands::server_icon::user::current::festival::run(&command, &ctx).await, + &_ => format!("not implemented :( count {}", x.name.as_str()), + }, + }, + &_ => format!("not implemented :( {}", command.data.name.as_str()), + }; + + result + } + "stats" => commands::server_icon::user::stats::run(&command, &ctx).await, + &_ => format!("not implemented :( count {}", x.name.as_str()), + }, + }, _ => format!("not implemented :( {}", command.data.name.as_str()), }; From 0f4524ea637ef29aba7f2dab964015274d0ff0c9 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Jun 2025 05:19:58 +0100 Subject: [PATCH 128/170] feat: tidied up the command outouts --- db/migrations/11_server-icons.sql | 1 + src/commands/server_icon.rs | 37 ++++++++++++++++++++++--------- src/common/server_icon.rs | 13 ++++++----- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/db/migrations/11_server-icons.sql b/db/migrations/11_server-icons.sql index 37d8b5b..20fb472 100644 --- a/db/migrations/11_server-icons.sql +++ b/db/migrations/11_server-icons.sql @@ -2,6 +2,7 @@ CREATE TABLE IF NOT EXISTS server_icons ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, + path TEXT NOT NULL, date TEXT NOT NULL ); diff --git a/src/commands/server_icon.rs b/src/commands/server_icon.rs index a6114c5..4ce10ac 100644 --- a/src/commands/server_icon.rs +++ b/src/commands/server_icon.rs @@ -51,7 +51,7 @@ pub(crate) mod user { .add_sub_option(CreateCommandOption::new(CommandOptionType::SubCommand, "icon", "Information on current icon.")) .add_sub_option(CreateCommandOption::new(CommandOptionType::SubCommand, "festival", "Information on current festival.")), ) - .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "stats", "Some Stats.")) + .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "stats", "How many times the particular icon has been used")) } fn get_logo_url(config_toml: &ConfigTomlLocal, logo_name: &str) -> String { @@ -64,6 +64,7 @@ pub(crate) mod user { pub(crate) mod icon { use super::*; + use serenity::all::{CreateAttachment, EditInteractionResponse}; use sqlx::{Pool, Sqlite}; @@ -77,7 +78,15 @@ pub(crate) mod user { let config_toml = get_config_icons::minimal(); if let Some(logo) = get_current_icon(&db).await { - get_logo_url(&config_toml, &logo.name) + let attachment = CreateAttachment::path(&logo.path).await.unwrap(); + match command.edit_response(&ctx.http, EditInteractionResponse::new().new_attachment(attachment)).await { + Ok(_) => {} + Err(e) => { + dbg!(e); + } + } + + format!("[{}]({})", &logo.name, get_logo_url(&config_toml, &logo.name)) } else { "Could not find current icon".to_string() } @@ -102,8 +111,8 @@ pub(crate) mod user { } pub(crate) mod festival { - use serenity::all::{CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption}; - use skynet_discord_bot::common::server_icon::get_config_icons; + use serenity::all::{CommandInteraction, Context}; + use skynet_discord_bot::common::server_icon::{get_config_icons, update_icon::get_festival}; use skynet_discord_bot::Config; // use this to return what current festivals are active? @@ -116,13 +125,13 @@ pub(crate) mod user { let config_toml = get_config_icons::full(&config); - let mut response = vec![]; + let response = get_festival(&config_toml).current; - for festival in &config_toml.festivals { - response.push(festival.name.to_owned()); + if response.is_empty() { + "No festival currently active".to_string() + } else { + format!("Festivals active: {}", response.join(", ")) } - - format!("Festivals active: {}", response.join(", ")) } } } @@ -170,6 +179,10 @@ pub(crate) mod user { } fn fmt_msg(config_toml: &ConfigTomlLocal, totals: &Vec) -> String { + let mut totals_local = totals.clone(); + totals_local.sort_by_key(|x| x.times); + totals_local.reverse(); + // msg can be a max 2000 chars long let mut limit = 2000 - 3; @@ -177,7 +190,7 @@ pub(crate) mod user { for CountResult { name, times, - } in totals + } in &totals_local { let current_leading = if times < &10 { "00" @@ -189,7 +202,9 @@ pub(crate) mod user { let url = get_logo_url(config_toml, name); - let line = format!("{}{} <{}>", current_leading, times, url); + // the `` is so that the numbers will be rendered in monospaced font + // the <> is to suppress the URL embed + let line = format!("``{}{}`` [{}](<{}>)", current_leading, times, name, url); let length = line.len() + 1; diff --git a/src/common/server_icon.rs b/src/common/server_icon.rs index 3f88d34..ab4b991 100644 --- a/src/common/server_icon.rs +++ b/src/common/server_icon.rs @@ -72,6 +72,7 @@ pub struct LogoData { pub struct ServerIcons { pub id: i64, pub name: String, + pub path: String, pub date: String, } @@ -120,12 +121,12 @@ pub mod update_icon { } #[derive(Debug)] - struct FestivalData { - current: Vec, + pub struct FestivalData { + pub current: Vec, exclusions: Vec, } - fn get_festival(config_toml: &ConfigToml) -> FestivalData { + pub fn get_festival(config_toml: &ConfigToml) -> FestivalData { let today = Utc::now(); let day = today.day(); let month = today.month(); @@ -276,15 +277,17 @@ pub mod update_icon { async fn logo_set_db(db: &Pool, logo_selected: &LogoData) { let name = logo_selected.name.to_str().unwrap_or_default(); + let path = logo_selected.path.to_str().unwrap_or_default(); match sqlx::query_as::<_, ServerIcons>( " - INSERT OR REPLACE INTO server_icons (name, date) - VALUES (?1, ?2) + INSERT OR REPLACE INTO server_icons (name, date, path) + VALUES (?1, ?2, ?3) ", ) .bind(name) .bind(get_now_iso(false)) + .bind(path) .fetch_optional(db) .await { From 721c8246acc3a2285e0dbedcef79939a9a349700 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Jun 2025 05:21:05 +0100 Subject: [PATCH 129/170] todo: add a todo where teh mc commands get moved in under committee --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index 3b57470..4d58bdd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -199,6 +199,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use format!("not implemented :( committee {}", x.name.as_str()) } }, + // TODO: move teh minecraft commands in here as a subgroup // "link" => commands::count::servers::run(&command, &ctx).await, &_ => format!("not implemented :( committee {}", x.name.as_str()), }, From b4cadffdb55bed8c0ab979588f64c0eba7b5d6be Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Jun 2025 05:22:30 +0100 Subject: [PATCH 130/170] fmt: for whenever it gets stabised (or we use nightly) this would be really good for cleaning up imports --- .rustfmt.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index b8ae8dd..6aeb30c 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -6,4 +6,5 @@ fn_params_layout = "Compressed" #brace_style = "PreferSameLine" struct_lit_width = 0 tab_spaces = 2 -use_small_heuristics = "Max" \ No newline at end of file +use_small_heuristics = "Max" +#imports_granularity="Crate" \ No newline at end of file From cae383a18663f6232378aa95ac2ef8ff58ee2a12 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Jun 2025 05:26:00 +0100 Subject: [PATCH 131/170] feat: set up the systemd timer for teh binary --- Cargo.toml | 3 +++ flake.nix | 2 ++ 2 files changed, 5 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 3dffd41..b179901 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,9 @@ name = "update_committee" [[bin]] name = "update_minecraft" +[[bin]] +name = "update_server-icon" + [dependencies] # discord library serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } diff --git a/flake.nix b/flake.nix index 2f8ba95..b707b60 100644 --- a/flake.nix +++ b/flake.nix @@ -141,6 +141,8 @@ "update_committee" = "*:15:00"; # minecraft stuff is updated at 5am "update_minecraft" = "5:10:00"; + # server icon gets updated daily at midnight + "update_server-icon" = "0:01:00"; }; in { options.services."${package_name}" = { From 652dd6ff1c3f5dcda95e0cb031422544f5818dae Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Jun 2025 05:27:11 +0100 Subject: [PATCH 132/170] ci: add workflow to check for lfs status --- .forgejo/workflows/check_lfs.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .forgejo/workflows/check_lfs.yaml diff --git a/.forgejo/workflows/check_lfs.yaml b/.forgejo/workflows/check_lfs.yaml new file mode 100644 index 0000000..dca575c --- /dev/null +++ b/.forgejo/workflows/check_lfs.yaml @@ -0,0 +1,12 @@ +on: + - pull_request + - push + - workflow_dispatch + + +jobs: + check_lfs: + # nix/docker + runs-on: nix + steps: + - uses: https://github.com/MPLew-is/lfs-check-action@1 \ No newline at end of file From 9134feee4ea57acbc6146d2ecd6c603e77d9d400 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Jun 2025 06:14:53 +0100 Subject: [PATCH 133/170] feat: cleaned up remaining unwraps, and then clippy+fmt --- src/commands/server_icon.rs | 21 +++---- src/common/server_icon.rs | 114 +++++++++++++++++++++++++++++------- src/main.rs | 3 +- 3 files changed, 105 insertions(+), 33 deletions(-) diff --git a/src/commands/server_icon.rs b/src/commands/server_icon.rs index 4ce10ac..9c970fc 100644 --- a/src/commands/server_icon.rs +++ b/src/commands/server_icon.rs @@ -17,7 +17,7 @@ pub(crate) mod admin { pub(crate) mod change { use super::*; - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() @@ -78,11 +78,12 @@ pub(crate) mod user { let config_toml = get_config_icons::minimal(); if let Some(logo) = get_current_icon(&db).await { - let attachment = CreateAttachment::path(&logo.path).await.unwrap(); - match command.edit_response(&ctx.http, EditInteractionResponse::new().new_attachment(attachment)).await { - Ok(_) => {} - Err(e) => { - dbg!(e); + if let Ok(attachment) = CreateAttachment::path(&logo.path).await { + match command.edit_response(&ctx.http, EditInteractionResponse::new().new_attachment(attachment)).await { + Ok(_) => {} + Err(e) => { + dbg!(e); + } } } @@ -116,7 +117,7 @@ pub(crate) mod user { use skynet_discord_bot::Config; // use this to return what current festivals are active? - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String { let config_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() @@ -141,7 +142,7 @@ pub(crate) mod user { use super::*; use sqlx::{Pool, Sqlite}; - pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { + pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() @@ -178,8 +179,8 @@ pub(crate) mod user { }) } - fn fmt_msg(config_toml: &ConfigTomlLocal, totals: &Vec) -> String { - let mut totals_local = totals.clone(); + fn fmt_msg(config_toml: &ConfigTomlLocal, totals: &[CountResult]) -> String { + let mut totals_local = totals.to_owned(); totals_local.sort_by_key(|x| x.times); totals_local.reverse(); diff --git a/src/common/server_icon.rs b/src/common/server_icon.rs index ab4b991..167a105 100644 --- a/src/common/server_icon.rs +++ b/src/common/server_icon.rs @@ -43,8 +43,16 @@ pub mod get_config_icons { } pub fn minimal() -> ConfigTomlLocal { let toml_raw_min = include_str!("../../.server-icons.toml"); - let config_min: ConfigTomlLocal = toml::from_str(toml_raw_min).unwrap(); - config_min + toml::from_str::(toml_raw_min).unwrap_or_else(|e| { + dbg!(e); + ConfigTomlLocal { + source: ConfigTomlSource { + repo: "".to_string(), + directory: "".to_string(), + file: "".to_string(), + }, + } + }) } // since a copy of the festival file is in the repo we just need to get to it @@ -52,12 +60,21 @@ pub mod get_config_icons { let config_source = minimal(); let file_path = format!("{}/open-governance/{}/{}", &config.home, &config_source.source.directory, &config_source.source.file); - let contents = fs::read_to_string(file_path).expect("Should have been able to read the file"); - let config_festivals: ConfigTomlRemote = toml::from_str(&contents).unwrap(); + let contents = fs::read_to_string(file_path).unwrap_or_else(|e| { + dbg!(e); + "".to_string() + }); + let festivals = match toml::from_str::(&contents) { + Ok(config_festivals) => config_festivals.festivals, + Err(e) => { + dbg!(e); + vec![] + } + }; ConfigToml { source: config_source.source, - festivals: config_festivals.festivals, + festivals, } } } @@ -154,15 +171,26 @@ pub mod update_icon { let url = &config_toml.source.repo; let folder = format!("{}/open-governance", &config.home); - Command::new("git").arg("clone").arg(url).arg(&folder).output().expect("failed to execute process"); + if let Err(e) = Command::new("git") + // clone the repo, gracefully "fails" + .arg("clone") + .arg(url) + .arg(&folder) + .output() + { + dbg!(e); + } - Command::new("git") + if let Err(e) = Command::new("git") + // Update the repo .arg("pull") .arg("origin") .arg("main") .current_dir(&folder) .output() - .expect("failed to execute process"); + { + dbg!(e); + } } fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { @@ -170,7 +198,13 @@ pub mod update_icon { let folder_path = PathBuf::from(&folder); let mut folder_output = folder_path.clone(); folder_output.push("converted"); - let paths = fs::read_dir(folder).unwrap(); + let paths = match fs::read_dir(folder) { + Ok(x) => x, + Err(e) => { + dbg!(e); + return vec![]; + } + }; let args = Args { input: folder_path.clone(), @@ -179,14 +213,26 @@ pub mod update_icon { width: 1024, height: 1024, }; - let mut r = Renderer::new(&args).unwrap(); + let mut r = match Renderer::new(&args) { + Ok(x) => x, + Err(e) => { + let _ = dbg!(e); + return vec![]; + } + }; let mut logos = vec![]; for tmp in paths.flatten() { let path_local = tmp.path().to_owned(); let path_local2 = tmp.path().to_owned(); - let name = path_local2.file_name().unwrap().to_owned(); + let name = match path_local2.file_name() { + None => { + dbg!(path_local2); + continue; + } + Some(x) => x.to_owned(), + }; let mut path = tmp.path(); if path.is_dir() { @@ -200,7 +246,13 @@ pub mod update_icon { let mut path_new = path_local.clone(); path_new.set_extension("png"); let filename_tmp = path_new.clone(); - let filename = filename_tmp.file_name().unwrap_or_default(); + let filename = match filename_tmp.file_name() { + None => { + dbg!(filename_tmp); + continue; + } + Some(x) => x, + }; path_new.pop(); path_new.push("converted"); path_new.push(filename); @@ -266,18 +318,36 @@ pub mod update_icon { async fn logo_set(ctx: &Context, db: &Pool, server: &GuildId, logo_selected: &LogoData) { // add to teh database - logo_set_db(db, logo_selected).await; + if !logo_set_db(db, logo_selected).await { + // something went wrong + return; + } - let icon = CreateAttachment::path(logo_selected.path.to_str().unwrap_or_default()).await.unwrap(); - - // assuming a `guild` has already been bound - let builder = EditGuild::new().icon(Some(&icon)); - server.edit(ctx, builder).await.unwrap(); + if let Some(logo_path) = logo_selected.path.to_str() { + match CreateAttachment::path(logo_path).await { + Ok(icon) => { + // assuming a `guild` has already been bound + let builder = EditGuild::new().icon(Some(&icon)); + if let Err(e) = server.edit(ctx, builder).await { + dbg!(e); + } + } + Err(e) => { + dbg!(e); + } + } + } } - async fn logo_set_db(db: &Pool, logo_selected: &LogoData) { - let name = logo_selected.name.to_str().unwrap_or_default(); - let path = logo_selected.path.to_str().unwrap_or_default(); + async fn logo_set_db(db: &Pool, logo_selected: &LogoData) -> bool { + let name = match logo_selected.name.to_str() { + None => return false, + Some(x) => x, + }; + let path = match logo_selected.path.to_str() { + None => return false, + Some(x) => x, + }; match sqlx::query_as::<_, ServerIcons>( " @@ -294,7 +364,9 @@ pub mod update_icon { Ok(_) => {} Err(e) => { dbg!(e); + return false; } } + true } } diff --git a/src/main.rs b/src/main.rs index 4d58bdd..ebad7a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,8 +2,7 @@ pub mod commands; use crate::commands::role_adder::tools::on_role_change; use serenity::all::{ - ActivityData, Command, CommandDataOption, CommandDataOptionValue, CommandOptionType, CreateMessage, EditInteractionResponse, GuildId, - GuildMemberUpdateEvent, Interaction, + ActivityData, Command, CommandDataOptionValue, CreateMessage, EditInteractionResponse, GuildId, GuildMemberUpdateEvent, Interaction, }; use serenity::model::guild::Member; use serenity::{ From f841039c530160dca8ffd916f0af203f268ee4b2 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Jun 2025 20:16:25 +0100 Subject: [PATCH 134/170] fix: was pulling in the wrong env var --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 762dc69..c682364 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,7 +99,7 @@ pub fn get_config() -> Config { config.committee_server = GuildId::new(x); } } - if let Ok(x) = env::var("COMMITTEE_DISCORD") { + if let Ok(x) = env::var("COMMITTEE_ROLE") { if let Ok(x) = x.trim().parse::() { config.committee_role = RoleId::new(x); } From 72226cc59bb4d97a5dab8080050acbcf52a0603d Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Jun 2025 20:20:21 +0100 Subject: [PATCH 135/170] feat: add support for passing teh compsoc server id via env --- src/lib.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index c682364..8a7273d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,6 +33,9 @@ pub struct Config { pub committee_server: GuildId, pub committee_role: RoleId, pub committee_category: ChannelId, + + // items pertaining to compsoc only + pub compsoc_server: GuildId, } impl TypeMapKey for Config { type Value = Arc>; @@ -58,6 +61,7 @@ pub fn get_config() -> Config { committee_server: GuildId::new(1), committee_role: RoleId::new(1), committee_category: ChannelId::new(1), + compsoc_server: GuildId::new(1), }; if let Ok(x) = env::var("DATABASE_HOME") { @@ -110,6 +114,12 @@ pub fn get_config() -> Config { } } + if let Ok(x) = env::var("COMPSOC_DISCORD") { + if let Ok(x) = x.trim().parse::() { + config.compsoc_server = GuildId::new(x); + } + } + config } From a6eff75e397a34b6b0b904b3aba6b0324d7c36ef Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 16 Jun 2025 21:50:26 +0100 Subject: [PATCH 136/170] feat: use values from teh env file to dictate the servers --- src/commands/wolves.rs | 12 +++++++++--- src/common/server_icon.rs | 2 +- src/common/set_roles.rs | 18 ++++++------------ src/lib.rs | 10 ++++++---- src/main.rs | 20 +++++++++++--------- 5 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/commands/wolves.rs b/src/commands/wolves.rs index 92c6ec0..d790f9f 100644 --- a/src/commands/wolves.rs +++ b/src/commands/wolves.rs @@ -282,7 +282,7 @@ pub mod link { pub mod verify { use super::*; use crate::commands::wolves::link::{db_pending_clear_expired, get_server_member_discord, get_verify_from_db}; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, GuildId, RoleId}; + use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; use serenity::model::user::User; use skynet_discord_bot::common::database::get_server_config; use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; @@ -429,12 +429,18 @@ pub mod verify { } async fn set_server_roles_committee(db: &Pool, discord: &User, ctx: &Context) { + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config = config_lock.read().await; + if let Some(x) = get_server_member_discord(db, &discord.id).await { // if they are a member of one or more committees, and in teh committee server then give the teh general committee role // they will get teh more specific vanity role later if !get_committees_id(db, x.id_wolves).await.is_empty() { - let server = GuildId::new(1220150752656363520); - let committee_member = RoleId::new(1226602779968274573); + let server = config.committee_server; + let committee_member = config.committee_role; if let Ok(member) = server.member(ctx, &discord.id).await { member.add_roles(&ctx, &[committee_member]).await.unwrap_or_default(); diff --git a/src/common/server_icon.rs b/src/common/server_icon.rs index 167a105..8fcbcee 100644 --- a/src/common/server_icon.rs +++ b/src/common/server_icon.rs @@ -114,7 +114,7 @@ pub mod update_icon { /// Update the server icon, pulling from open governance. pub async fn update_icon_main(ctx: &Context, db: &Pool, config_global: &Config, config_toml_local: &ConfigTomlLocal) { - let server = GuildId::new(689189992417067052); + let server = config_global.compsoc_server; // clone repo into local folder clone_repo(config_global, config_toml_local); diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 0dc7cc5..4e24b1a 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -135,7 +135,7 @@ pub mod committee { use crate::common::wolves::committees::Committees; use crate::Config; use serde::{Deserialize, Serialize}; - use serenity::all::{EditRole, GuildId}; + use serenity::all::EditRole; use serenity::builder::CreateChannel; use serenity::client::Context; use serenity::model::channel::ChannelType; @@ -161,7 +161,7 @@ pub mod committee { }; let config_global = config_lock.read().await; - let server = GuildId::new(1220150752656363520); + let server = config_global.committee_server; // because to use it to update a single user we need to pre-get the members of teh server let mut members = server.members(&ctx, None, None).await.unwrap_or_default(); @@ -172,17 +172,11 @@ pub mod committee { /** This function can take a vec of members (or just one) and gives tehm the appropiate roles on teh committee server */ - pub async fn update_committees(db: &Pool, ctx: &Context, _config: &Config, members: &mut Vec) { - let server = GuildId::new(1220150752656363520); - let committee_member = RoleId::new(1226602779968274573); + pub async fn update_committees(db: &Pool, ctx: &Context, config: &Config, members: &mut Vec) { + let server = config.committee_server; + let committee_member = config.committee_role; let committees = get_committees(db).await; - let categories = [ - ChannelId::new(1226606560973815839), - // C&S Chats 2 - ChannelId::new(1341457244973305927), - // C&S Chats 3 - ChannelId::new(1341457509717639279), - ]; + let categories = config.committee_category.clone(); // information about the server let mut roles_db = HashMap::new(); diff --git a/src/lib.rs b/src/lib.rs index 8a7273d..abf8a2d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,7 +32,7 @@ pub struct Config { // discord server for committee pub committee_server: GuildId, pub committee_role: RoleId, - pub committee_category: ChannelId, + pub committee_category: Vec, // items pertaining to compsoc only pub compsoc_server: GuildId, @@ -60,7 +60,7 @@ pub fn get_config() -> Config { wolves_api: "".to_string(), committee_server: GuildId::new(1), committee_role: RoleId::new(1), - committee_category: ChannelId::new(1), + committee_category: vec![], compsoc_server: GuildId::new(1), }; @@ -109,8 +109,10 @@ pub fn get_config() -> Config { } } if let Ok(x) = env::var("COMMITTEE_CATEGORY") { - if let Ok(x) = x.trim().parse::() { - config.committee_category = ChannelId::new(x); + for part in x.split(",") { + if let Ok(x) = part.trim().parse::() { + config.committee_category.push(ChannelId::new(x)); + } } } diff --git a/src/main.rs b/src/main.rs index ebad7a0..9de027e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,7 @@ pub mod commands; use crate::commands::role_adder::tools::on_role_change; -use serenity::all::{ - ActivityData, Command, CommandDataOptionValue, CreateMessage, EditInteractionResponse, GuildId, GuildMemberUpdateEvent, Interaction, -}; +use serenity::all::{ActivityData, Command, CommandDataOptionValue, CreateMessage, EditInteractionResponse, GuildMemberUpdateEvent, Interaction}; use serenity::model::guild::Member; use serenity::{ async_trait, @@ -42,7 +40,7 @@ impl EventHandler for Handler { let config_global = config_lock.read().await; // committee server takes priority - let committee_server = GuildId::new(1220150752656363520); + let committee_server = config_global.committee_server; if new_member.guild_id.get() == committee_server.get() { let mut member = vec![new_member.clone()]; update_committees(&db, &ctx, &config_global, &mut member).await; @@ -113,6 +111,12 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use println!("[Main] {} is connected!", ready.user.name); ctx.set_presence(Some(ActivityData::playing("with humanity's fate")), OnlineStatus::Online); + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config = config_lock.read().await; + match Command::set_global_commands( &ctx.http, vec![ @@ -132,10 +136,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } // Inter-Committee server - match GuildId::new(1220150752656363520) - .set_commands(&ctx.http, vec![commands::count::committee::register()]) - .await - { + match config.committee_server.set_commands(&ctx.http, vec![commands::count::committee::register()]).await { Ok(_) => {} Err(e) => { println!("{:?}", e) @@ -143,7 +144,8 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } // compsoc Server - match GuildId::new(689189992417067052) + match config + .compsoc_server .set_commands( &ctx.http, vec![ From 327ff99b693c838476e98cbf599c6ed2ee50f2d9 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 17 Jun 2025 16:21:15 +0100 Subject: [PATCH 137/170] fix: the pipeline got caught on a lint This isnt in the 1.87 rustfmt but its stilla good catch --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index abf8a2d..734fa2c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,7 +109,7 @@ pub fn get_config() -> Config { } } if let Ok(x) = env::var("COMMITTEE_CATEGORY") { - for part in x.split(",") { + for part in x.split(',') { if let Ok(x) = part.trim().parse::() { config.committee_category.push(ChannelId::new(x)); } From 3a56d7bba5fa2ca562ab5288f8799339cdc947e3 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 17 Jun 2025 16:21:57 +0100 Subject: [PATCH 138/170] feat: lock the ``nix build`` to using the repo rust version --- flake.lock | 17 +++++++++++++++++ flake.nix | 23 +++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/flake.lock b/flake.lock index 9f7289a..2c4a9bc 100644 --- a/flake.lock +++ b/flake.lock @@ -32,6 +32,22 @@ "type": "indirect" } }, + "nixpkgs-mozilla": { + "flake": false, + "locked": { + "lastModified": 1744624473, + "narHash": "sha256-S6zT/w5SyAkJ//dYdjbrXgm+6Vkd/k7qqUl4WgZ6jjk=", + "owner": "mozilla", + "repo": "nixpkgs-mozilla", + "rev": "2292d4b35aa854e312ad2e95c4bb5c293656f21a", + "type": "github" + }, + "original": { + "owner": "mozilla", + "repo": "nixpkgs-mozilla", + "type": "github" + } + }, "nixpkgs_2": { "locked": { "lastModified": 1722995383, @@ -51,6 +67,7 @@ "inputs": { "naersk": "naersk", "nixpkgs": "nixpkgs_2", + "nixpkgs-mozilla": "nixpkgs-mozilla", "utils": "utils" } }, diff --git a/flake.nix b/flake.nix index b707b60..f7fee94 100644 --- a/flake.nix +++ b/flake.nix @@ -4,6 +4,10 @@ inputs = { nixpkgs.url = "nixpkgs/nixos-unstable"; naersk.url = "github:nix-community/naersk"; + nixpkgs-mozilla = { + url = "github:mozilla/nixpkgs-mozilla"; + flake = false; + }; utils.url = "github:numtide/flake-utils"; }; @@ -17,12 +21,27 @@ nixpkgs, utils, naersk, + nixpkgs-mozilla, }: utils.lib.eachDefaultSystem ( system: let overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml)); - pkgs = (import nixpkgs) {inherit system;}; - naersk' = pkgs.callPackage naersk {}; + pkgs = (import nixpkgs) { + inherit system; + + overlays = [ + (import nixpkgs-mozilla) + ]; + }; + toolchain = (pkgs.rustChannelOf { + rustToolchain = ./rust-toolchain.toml; + sha256 = "sha256-KUm16pHj+cRedf8vxs/Hd2YWxpOrWZ7UOrwhILdSJBU="; + }).rust; + + naersk' = pkgs.callPackage naersk { + cargo = toolchain; + rustc = toolchain; + }; package_name = "skynet_discord_bot"; desc = "Skynet Discord Bot"; buildInputs = with pkgs; [ From 1dc5c105df7ba80643b596d3de025a28871aba2c Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 18 Jun 2025 03:57:04 +0100 Subject: [PATCH 139/170] fix: needed to add git and git lfs to teh path of the service --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index f7fee94..1b39fbb 100644 --- a/flake.nix +++ b/flake.nix @@ -122,7 +122,7 @@ wantedBy = []; after = ["network-online.target"]; environment = environment_config; - + path = with pkgs; [ git git-lfs ]; serviceConfig = { Type = "oneshot"; User = "${cfg.user}"; From 7403f531eb38a8c1ff0621d0c7f2fb30ecc919fd Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Tue, 24 Jun 2025 00:00:56 +0100 Subject: [PATCH 140/170] feat: the backend is pretty simple, just pull the rep link from teh config_toml and add on the path to the docs. Then its just linking it all up. Closes #37 --- src/commands/wolves.rs | 17 +++++++++++++++++ src/main.rs | 1 + 2 files changed, 18 insertions(+) diff --git a/src/commands/wolves.rs b/src/commands/wolves.rs index d790f9f..d78454b 100644 --- a/src/commands/wolves.rs +++ b/src/commands/wolves.rs @@ -279,6 +279,22 @@ pub mod link { } } +pub mod link_docs { + use super::*; + pub mod users { + use super::*; + use serenity::all::CommandInteraction; + + pub async fn run(_command: &CommandInteraction, _ctx: &Context) -> String { + "https://forgejo.skynet.ie/Skynet/discord-bot/src/branch/main/doc/User.md".to_string() + } + } + + // pub mod committee { + // + // } +} + pub mod verify { use super::*; use crate::commands::wolves::link::{db_pending_clear_expired, get_server_member_discord, get_verify_from_db}; @@ -522,4 +538,5 @@ pub fn register() -> CreateCommand { .add_sub_option(CreateCommandOption::new(CommandOptionType::String, "minecraft_username", "Your Minecraft username").required(true)) .add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "bedrock_account", "Is this a Bedrock account?").required(false)), ) + .add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "docs", "Link to where the documentation can be found.")) } diff --git a/src/main.rs b/src/main.rs index 9de027e..238777d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -177,6 +177,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use "verify" => commands::wolves::verify::run(&command, &ctx).await, "unlink" => commands::wolves::unlink::run(&command, &ctx).await, "link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await, + "docs" => commands::wolves::link_docs::users::run(&command, &ctx).await, // "link" => commands::count::servers::run(&command, &ctx).await, &_ => format!("not implemented :( wolves {}", x.name.as_str()), }, From c4da3e91096741dc827f3a5ac3c184160b8f1e8c Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sat, 5 Jul 2025 15:31:53 +0100 Subject: [PATCH 141/170] fix: only allow image files to be chosen --- src/common/server_icon.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/common/server_icon.rs b/src/common/server_icon.rs index 8fcbcee..abd0972 100644 --- a/src/common/server_icon.rs +++ b/src/common/server_icon.rs @@ -286,9 +286,19 @@ pub mod update_icon { fn logos_filter(festival_data: &FestivalData, existing: Vec) -> Vec { let mut filtered: Vec = vec![]; + let allowed_files = vec![".png", ".jpeg", ".gif", ".svg"]; 'outer: for logo in existing { let name_lowercase0 = logo.name.to_ascii_lowercase(); let name_lowercase = name_lowercase0.to_str().unwrap_or_default(); + let mut allowed = false; + for allowed_type in &allowed_files { + if name_lowercase.ends_with(allowed_type) { + allowed = true; + } + } + if !allowed { + continue; + } if !festival_data.current.is_empty() { // if its a current festival filter based on it From 76f8aa2712dcfeb701b3dce16c7d93323afbeb66 Mon Sep 17 00:00:00 2001 From: esy Date: Sun, 6 Jul 2025 18:12:04 +0000 Subject: [PATCH 142/170] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 01543e6..1f49dc1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Skynet Discord Bot The Skynet bot is designed to manage users on Discord. It allows users to link their UL Wolves account with Wolves in a GDPR compliant manner. -Skynet (bot) is hosted is hosted by the Computer Society on Skynet (computer cluster). +Skynet (bot) is hosted by the Computer Society on Skynet (computer cluster). ## Documentation We have split up the documentation into different segments depending on who the user is. From 764e8cd620d61ed6ca6d168cd06489e94d40b615 Mon Sep 17 00:00:00 2001 From: Daragh Date: Sun, 6 Jul 2025 23:28:51 +0000 Subject: [PATCH 143/170] Fix LFS on discord bot (#39) Reviewed-on: https://forgejo.skynet.ie/Skynet/discord-bot/pulls/39 Thanks @esy Co-authored-by: Daragh Co-committed-by: Daragh --- flake.nix | 1 + src/common/server_icon.rs | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/flake.nix b/flake.nix index 1b39fbb..4494ace 100644 --- a/flake.nix +++ b/flake.nix @@ -221,6 +221,7 @@ after = ["network-online.target"]; wants = []; environment = environment_config; + path = with pkgs; [ git git-lfs ]; serviceConfig = { User = "${cfg.user}"; diff --git a/src/common/server_icon.rs b/src/common/server_icon.rs index abd0972..d99f265 100644 --- a/src/common/server_icon.rs +++ b/src/common/server_icon.rs @@ -191,6 +191,28 @@ pub mod update_icon { { dbg!(e); } + + if let Err(e) = Command::new("git") + // Install LFS for the repo + .arg("lfs") + .arg("install") + .current_dir(&folder) + .output() + { + dbg!(e); + } + + if let Err(e) = Command::new("git") + // clone the repo, gracefully "fails" + .arg("lfs") + .arg("pull") + .arg("origin") + .arg("main") + .current_dir(&folder) + .output() + { + dbg!(e); + } } fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec { From b8ffd421844414b44cc66d7ef671c206072027da Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 7 Jul 2025 21:29:37 +0100 Subject: [PATCH 144/170] feat: the bot wasnt using any caching, this should make many operations far faster now --- src/main.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 238777d..2770db7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,16 @@ pub mod commands; use crate::commands::role_adder::tools::on_role_change; -use serenity::all::{ActivityData, Command, CommandDataOptionValue, CreateMessage, EditInteractionResponse, GuildMemberUpdateEvent, Interaction}; -use serenity::model::guild::Member; +use serenity::all::{ + ActivityData, Command, CommandDataOptionValue, CreateMessage, EditInteractionResponse, GuildId, GuildMemberUpdateEvent, Interaction, +}; use serenity::{ async_trait, client::{Context, EventHandler}, + gateway::ChunkGuildFilter, model::{ gateway::{GatewayIntents, Ready}, + guild::Member, user::OnlineStatus, }, Client, @@ -24,6 +27,14 @@ struct Handler; #[async_trait] impl EventHandler for Handler { + // this caches members of all servers teh bot is in + async fn cache_ready(&self, ctx: Context, guilds: Vec) { + for guild in guilds { + ctx.shard.chunk_guild(guild, Some(500), false, ChunkGuildFilter::None, None); + } + println!("Cache built successfully!"); + } + // handles previously linked accounts joining the server async fn guild_member_addition(&self, ctx: Context, new_member: Member) { let db_lock = { @@ -281,6 +292,7 @@ async fn main() { // Build our client. let mut client = Client::builder(&config.discord_token, intents) .event_handler(Handler {}) + .cache_settings(serenity::cache::Settings::default()) .await .expect("Error creating client"); From 3abbb8d4854a41754bda907271ef1504fbbfe53b Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 7 Jul 2025 22:18:04 +0100 Subject: [PATCH 145/170] feat: added script to clean up the committee server if it got flooded with extra channels --- Cargo.toml | 3 + src/bin/cleanup_committee.rs | 129 +++++++++++++++++++++++++++++++++++ src/common/set_roles.rs | 6 +- 3 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 src/bin/cleanup_committee.rs diff --git a/Cargo.toml b/Cargo.toml index b179901..bca450c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,9 @@ name = "update_minecraft" [[bin]] name = "update_server-icon" +[[bin]] +name = "cleanup_committee" + [dependencies] # discord library serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] } diff --git a/src/bin/cleanup_committee.rs b/src/bin/cleanup_committee.rs new file mode 100644 index 0000000..7d2ea5a --- /dev/null +++ b/src/bin/cleanup_committee.rs @@ -0,0 +1,129 @@ +use serenity::all::{ChunkGuildFilter, GuildId}; +use serenity::{ + async_trait, + client::{Context, EventHandler}, + model::gateway::{GatewayIntents, Ready}, + Client, +}; +use skynet_discord_bot::common::database::{db_init, DataBase}; + +use skynet_discord_bot::common::set_roles::committee::{db_roles_get}; +use skynet_discord_bot::{get_config, Config}; +use sqlx::{Pool, Sqlite}; +use std::{process, sync::Arc}; +use tokio::sync::RwLock; + +/// Cleanup teh Committee server +/// +/// This removes any invalid roles/channels which ay have been set up accidentally +/// DO NOT run this locally unless you have a fresh copy of teh live database handy. +#[tokio::main] +async fn main() { + let config = get_config(); + let db = match db_init(&config).await { + Ok(x) => x, + Err(_) => return, + }; + + // Intents are a bitflag, bitwise operations can be used to dictate which intents to use + let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; + // Build our client. + let mut client = Client::builder(&config.discord_token, intents) + .event_handler(Handler {}) + .cache_settings(serenity::cache::Settings::default()) + .await + .expect("Error creating client"); + + { + let mut data = client.data.write().await; + + data.insert::(Arc::new(RwLock::new(config))); + data.insert::(Arc::new(RwLock::new(db))); + } + + if let Err(why) = client.start().await { + println!("Client error: {:?}", why); + } +} + +struct Handler; +#[async_trait] +impl EventHandler for Handler { + async fn cache_ready(&self, ctx: Context, guilds: Vec) { + for guild in guilds { + ctx.shard.chunk_guild(guild, Some(500), false, ChunkGuildFilter::None, None); + } + println!("Cache built successfully!"); + } + + async fn ready(&self, ctx: Context, ready: Ready) { + let ctx = Arc::new(ctx); + println!("{} is connected!", ready.user.name); + + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + + let db = db_lock.read().await; + + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config = config_lock.read().await; + + cleanup(&db, &ctx, &config).await; + // finish up + process::exit(0); + } +} + +async fn cleanup(db: &Pool, ctx: &Context, config: &Config) { + let server = config.committee_server; + let committees = db_roles_get(db).await; + + if let Ok(channels) = server.channels(ctx).await { + for (id, channel) in &channels { + let name = &channel.name; + let committee_tmp = committees.iter().filter(|x| &x.name_channel == name).collect::>(); + let committee = match committee_tmp.first() { + // if there are no committees which match then this is not a channelw e care about + None => { + continue; + } + Some(x) => x, + }; + + // if the id of the channel does not match then remove it + if id != &committee.id_channel { + println!("Deleting Channel - ID: {} Name: {}", id, &channel.name); + if let Err(e) = channel.delete(ctx).await { + dbg!(e); + } + } + } + } + + if let Ok(mut roles) = server.roles(ctx).await { + for (id, role) in &mut roles { + let name = &role.name; + let committee_tmp = committees.iter().filter(|x| &x.name_role == name).collect::>(); + let committee = match committee_tmp.first() { + // if there are no committees which match then this is not a channelw e care about + None => { + continue; + } + Some(x) => x, + }; + + // if the id of the role does not match then remove it + if id != &committee.id_role { + println!("Deleting Role - ID: {} Name: {}", id, &role.name); + if let Err(e) = role.delete(ctx).await { + dbg!(e); + } + } + } + } +} \ No newline at end of file diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 4e24b1a..0012539 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -400,10 +400,10 @@ pub mod committee { #[derive(Debug, Clone, Deserialize, Serialize)] pub struct CommitteeRoles { id_wolves: i64, - id_role: RoleId, - id_channel: ChannelId, + pub id_role: RoleId, + pub id_channel: ChannelId, pub name_role: String, - name_channel: String, + pub name_channel: String, pub count: i64, } From e901f3ed74120cb533b6eff7a873a8cd07776efd Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 7 Jul 2025 22:22:25 +0100 Subject: [PATCH 146/170] feat: add caching to everything, should make all member interacts faster --- src/bin/cleanup_committee.rs | 6 +++--- src/bin/update_committee.rs | 9 +++++++++ src/bin/update_data.rs | 9 +++++++++ src/bin/update_server-icon.rs | 9 +++++++++ src/bin/update_users.rs | 9 +++++++++ src/main.rs | 2 +- 6 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/bin/cleanup_committee.rs b/src/bin/cleanup_committee.rs index 7d2ea5a..a2c3772 100644 --- a/src/bin/cleanup_committee.rs +++ b/src/bin/cleanup_committee.rs @@ -7,7 +7,7 @@ use serenity::{ }; use skynet_discord_bot::common::database::{db_init, DataBase}; -use skynet_discord_bot::common::set_roles::committee::{db_roles_get}; +use skynet_discord_bot::common::set_roles::committee::db_roles_get; use skynet_discord_bot::{get_config, Config}; use sqlx::{Pool, Sqlite}; use std::{process, sync::Arc}; @@ -51,7 +51,7 @@ struct Handler; impl EventHandler for Handler { async fn cache_ready(&self, ctx: Context, guilds: Vec) { for guild in guilds { - ctx.shard.chunk_guild(guild, Some(500), false, ChunkGuildFilter::None, None); + ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); } println!("Cache built successfully!"); } @@ -126,4 +126,4 @@ async fn cleanup(db: &Pool, ctx: &Context, config: &Config) { } } } -} \ No newline at end of file +} diff --git a/src/bin/update_committee.rs b/src/bin/update_committee.rs index d2026c0..2eef977 100644 --- a/src/bin/update_committee.rs +++ b/src/bin/update_committee.rs @@ -1,3 +1,4 @@ +use serenity::all::{ChunkGuildFilter, GuildId}; use serenity::{ async_trait, client::{Context, EventHandler}, @@ -23,6 +24,7 @@ async fn main() { // Build our client. let mut client = Client::builder(&config.discord_token, intents) .event_handler(Handler {}) + .cache_settings(serenity::cache::Settings::default()) .await .expect("Error creating client"); @@ -41,6 +43,13 @@ async fn main() { struct Handler; #[async_trait] impl EventHandler for Handler { + async fn cache_ready(&self, ctx: Context, guilds: Vec) { + for guild in guilds { + ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); + } + println!("Cache built successfully!"); + } + async fn ready(&self, ctx: Context, ready: Ready) { let ctx = Arc::new(ctx); println!("{} is connected!", ready.user.name); diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index 2d36892..d184ccd 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -1,3 +1,4 @@ +use serenity::all::{ChunkGuildFilter, GuildId}; use serenity::{ async_trait, client::{Context, EventHandler}, @@ -27,6 +28,7 @@ async fn main() { // Build our client. let mut client = Client::builder(&config.discord_token, intents) .event_handler(Handler {}) + .cache_settings(serenity::cache::Settings::default()) .await .expect("Error creating client"); @@ -45,6 +47,13 @@ async fn main() { struct Handler; #[async_trait] impl EventHandler for Handler { + async fn cache_ready(&self, ctx: Context, guilds: Vec) { + for guild in guilds { + ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); + } + println!("Cache built successfully!"); + } + async fn ready(&self, ctx: Context, ready: Ready) { let ctx = Arc::new(ctx); println!("{} is connected!", ready.user.name); diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index ff4cef9..2c093f9 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -1,3 +1,4 @@ +use serenity::all::{ChunkGuildFilter, GuildId}; use serenity::{ async_trait, client::{Context, EventHandler}, @@ -25,6 +26,7 @@ async fn main() { // Build our client. let mut client = Client::builder(&config.discord_token, intents) .event_handler(Handler {}) + .cache_settings(serenity::cache::Settings::default()) .await .expect("Error creating client"); @@ -43,6 +45,13 @@ async fn main() { struct Handler; #[async_trait] impl EventHandler for Handler { + async fn cache_ready(&self, ctx: Context, guilds: Vec) { + for guild in guilds { + ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); + } + println!("Cache built successfully!"); + } + async fn ready(&self, ctx: Context, ready: Ready) { let ctx = Arc::new(ctx); println!("{} is connected!", ready.user.name); diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index 3617d14..27dc1d6 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -1,3 +1,4 @@ +use serenity::all::{ChunkGuildFilter, GuildId}; use serenity::{ async_trait, client::{Context, EventHandler}, @@ -23,6 +24,7 @@ async fn main() { // Build our client. let mut client = Client::builder(&config.discord_token, intents) .event_handler(Handler {}) + .cache_settings(serenity::cache::Settings::default()) .await .expect("Error creating client"); @@ -41,6 +43,13 @@ async fn main() { struct Handler; #[async_trait] impl EventHandler for Handler { + async fn cache_ready(&self, ctx: Context, guilds: Vec) { + for guild in guilds { + ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); + } + println!("Cache built successfully!"); + } + async fn ready(&self, ctx: Context, ready: Ready) { let ctx = Arc::new(ctx); println!("{} is connected!", ready.user.name); diff --git a/src/main.rs b/src/main.rs index 2770db7..b09fb7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,7 +30,7 @@ impl EventHandler for Handler { // this caches members of all servers teh bot is in async fn cache_ready(&self, ctx: Context, guilds: Vec) { for guild in guilds { - ctx.shard.chunk_guild(guild, Some(500), false, ChunkGuildFilter::None, None); + ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); } println!("Cache built successfully!"); } From 2b2dfc253147a0ed5f4456db5c20b421deae71c9 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 20:32:43 +0100 Subject: [PATCH 147/170] feat: cleaned up array that was used to count members/changes to a struct --- src/common/set_roles.rs | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 0012539..5c7e29c 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -5,6 +5,13 @@ pub mod normal { use serenity::model::id::{GuildId, RoleId, UserId}; use sqlx::{Pool, Sqlite}; + struct RolesChange { + total: i32, + new: i32, + current_add: i32, + current_rem: i32, + } + pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option], members_changed: &[UserId]) { let db_lock = { let data_read = ctx.data.read().await; @@ -20,7 +27,12 @@ pub mod normal { .. } = server; - let mut roles_set = [0, 0, 0]; + let mut roles_set = RolesChange { + total: 0, + new: 0, + current_add: 0, + current_rem: 0, + }; let mut members = vec![]; for member in get_server_member_bulk(&db, server).await { @@ -38,17 +50,19 @@ pub mod normal { } if members.contains(&member.user.id) { + roles_set.total += 1; + let mut roles = vec![]; if let Some(role) = &role_past { if !member.roles.contains(role) { - roles_set[0] += 1; + roles_set.new += 1; roles.push(role.to_owned()); } } if !member.roles.contains(role_current) { - roles_set[1] += 1; + roles_set.current_add += 1; roles.push(role_current.to_owned()); } @@ -65,7 +79,7 @@ pub mod normal { } if member.roles.contains(role_current) { - roles_set[2] += 1; + roles_set.current_rem += 1; // if theya re not a current member and have the role then remove it if let Err(e) = member.remove_role(ctx, role_current).await { println!("{:?}", e); @@ -83,7 +97,14 @@ pub mod normal { set_server_numbers(&db, server, members_all as i64, members.len() as i64).await; // small bit of logging to note changes over time - println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.get(), roles_set[0], roles_set[1], roles_set[2]); + println!( + "{:?} Total: {} Changes: New: +{}, Current: +{}/-{}", + server.get(), + roles_set.total, + roles_set.new, + roles_set.current_add, + roles_set.current_rem + ); } pub async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec { From 04aa0e63d4fb0422c2762799eab3d5e3c145abc4 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 20:37:28 +0100 Subject: [PATCH 148/170] feat: setup update users to be every 5 min while using the cache --- src/main.rs | 51 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index b09fb7a..5dab67f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,8 @@ pub mod commands; use crate::commands::role_adder::tools::on_role_change; use serenity::all::{ - ActivityData, Command, CommandDataOptionValue, CreateMessage, EditInteractionResponse, GuildId, GuildMemberUpdateEvent, Interaction, + ActivityData, Command, CommandDataOptionValue, CreateMessage, EditInteractionResponse, GuildId, GuildMemberUpdateEvent, GuildMembersChunkEvent, + Interaction, }; use serenity::{ async_trait, @@ -15,15 +16,20 @@ use serenity::{ }, Client, }; -use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_member, DataBase}; +use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_config_bulk, get_server_member, DataBase}; use skynet_discord_bot::common::set_roles::committee::update_committees; +use skynet_discord_bot::common::set_roles::normal; use skynet_discord_bot::common::wolves::committees::Committees; use skynet_discord_bot::{get_config, Config}; use sqlx::{Pool, Sqlite}; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; +use std::time::Duration; use tokio::sync::RwLock; -struct Handler; +struct Handler { + is_loop_running: AtomicBool, +} #[async_trait] impl EventHandler for Handler { @@ -35,6 +41,41 @@ impl EventHandler for Handler { println!("Cache built successfully!"); } + async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) { + if (chunk.chunk_index + 1) == chunk.chunk_count { + // from https://github.com/serenity-rs/serenity/blob/18349f7bba43acad4261103eb38fe01d93f382df/examples/e13_parallel_loops/src/main.rs#L48 + let ctx = Arc::new(ctx); + + if !self.is_loop_running.load(Ordering::Relaxed) { + // We have to clone the Arc, as it gets moved into the new thread. + + { + // this is to update member roles every 5 min + let ctx1 = Arc::clone(&ctx); + tokio::spawn(async move { + let db_lock = { + let data_read = ctx1.data.read().await; + data_read.get::().expect("Expected Database in TypeMap.").clone() + }; + let db = db_lock.read().await; + + loop { + println!("User update - Start"); + for server_config in get_server_config_bulk(&db).await { + normal::update_server(&ctx, &server_config, &[], &[]).await; + } + println!("User update - End"); + tokio::time::sleep(Duration::from_secs(60 * 5)).await; + } + }); + } + + // Now that the loop is running, we set the bool to true + self.is_loop_running.swap(true, Ordering::Relaxed); + } + } + } + // handles previously linked accounts joining the server async fn guild_member_addition(&self, ctx: Context, new_member: Member) { let db_lock = { @@ -291,7 +332,9 @@ async fn main() { let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; // Build our client. let mut client = Client::builder(&config.discord_token, intents) - .event_handler(Handler {}) + .event_handler(Handler { + is_loop_running: AtomicBool::new(false), + }) .cache_settings(serenity::cache::Settings::default()) .await .expect("Error creating client"); From 3dd81a5c5412dda8090f3d3651a236628ee5a964 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 20:40:15 +0100 Subject: [PATCH 149/170] feat: remove the update_users service --- Cargo.toml | 5 +-- flake.nix | 2 -- src/bin/update_users.rs | 76 ----------------------------------------- 3 files changed, 1 insertion(+), 82 deletions(-) delete mode 100644 src/bin/update_users.rs diff --git a/Cargo.toml b/Cargo.toml index bca450c..f2bc696 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,6 @@ edition = "2021" [[bin]] name = "update_data" -[[bin]] -name = "update_users" - [[bin]] name = "update_committee" @@ -38,7 +35,7 @@ surf = "2.3" dotenvy = "0.15" # For sqlite -sqlx = { version = "0.8", features = [ "runtime-tokio", "sqlite", "migrate" ] } +sqlx = { version = "0.8", features = ["runtime-tokio", "sqlite", "migrate"] } serde_json = { version = "1.0", features = ["raw_value"] } # create random strings diff --git a/flake.nix b/flake.nix index 4494ace..04e062b 100644 --- a/flake.nix +++ b/flake.nix @@ -154,8 +154,6 @@ scripts = { # every 20 min "update_data" = "*:0,10,20,30,40,50"; - # groups are updated every hour, offset from teh ldap - "update_users" = "*:05:00"; # Committee server has its own timer "update_committee" = "*:15:00"; # minecraft stuff is updated at 5am diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs deleted file mode 100644 index 27dc1d6..0000000 --- a/src/bin/update_users.rs +++ /dev/null @@ -1,76 +0,0 @@ -use serenity::all::{ChunkGuildFilter, GuildId}; -use serenity::{ - async_trait, - client::{Context, EventHandler}, - model::gateway::{GatewayIntents, Ready}, - Client, -}; -use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase}; -use skynet_discord_bot::common::set_roles::normal; -use skynet_discord_bot::{get_config, Config}; -use std::{process, sync::Arc}; -use tokio::sync::RwLock; - -#[tokio::main] -async fn main() { - let config = get_config(); - let db = match db_init(&config).await { - Ok(x) => x, - Err(_) => return, - }; - - // Intents are a bitflag, bitwise operations can be used to dictate which intents to use - let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; - // Build our client. - let mut client = Client::builder(&config.discord_token, intents) - .event_handler(Handler {}) - .cache_settings(serenity::cache::Settings::default()) - .await - .expect("Error creating client"); - - { - let mut data = client.data.write().await; - - data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(RwLock::new(db))); - } - - if let Err(why) = client.start().await { - println!("Client error: {:?}", why); - } -} - -struct Handler; -#[async_trait] -impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, guilds: Vec) { - for guild in guilds { - ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); - } - println!("Cache built successfully!"); - } - - async fn ready(&self, ctx: Context, ready: Ready) { - let ctx = Arc::new(ctx); - println!("{} is connected!", ready.user.name); - - // this goes into each server and sets roles for each wolves member - check_bulk(Arc::clone(&ctx)).await; - - // finish up - process::exit(0); - } -} - -async fn check_bulk(ctx: Arc) { - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - - let db = db_lock.read().await; - - for server_config in get_server_config_bulk(&db).await { - normal::update_server(&ctx, &server_config, &[], &[]).await; - } -} From a8bed0bacc72295eb193f1351bb6cb33a730ccf6 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 20:42:32 +0100 Subject: [PATCH 150/170] fix: was calling the wrong ctx --- src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5dab67f..08699e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,10 +51,10 @@ impl EventHandler for Handler { { // this is to update member roles every 5 min - let ctx1 = Arc::clone(&ctx); + let ctx_task = Arc::clone(&ctx); tokio::spawn(async move { let db_lock = { - let data_read = ctx1.data.read().await; + let data_read = ctx_task.data.read().await; data_read.get::().expect("Expected Database in TypeMap.").clone() }; let db = db_lock.read().await; @@ -62,7 +62,7 @@ impl EventHandler for Handler { loop { println!("User update - Start"); for server_config in get_server_config_bulk(&db).await { - normal::update_server(&ctx, &server_config, &[], &[]).await; + normal::update_server(&ctx_task, &server_config, &[], &[]).await; } println!("User update - End"); tokio::time::sleep(Duration::from_secs(60 * 5)).await; From 43ef787d59f1315a8537e318b6ae2c88ca3d3f29 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 22:25:31 +0100 Subject: [PATCH 151/170] fix: no need to pass in teh full object, a reference will do --- src/common/set_roles.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 5c7e29c..c51c52e 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -168,7 +168,7 @@ pub mod committee { use std::collections::HashMap; use std::sync::Arc; - pub async fn check_committee(ctx: Arc) { + pub async fn check_committee(ctx: &Context) { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() From 1729ec0a54d1656139f403b7a9c50131dedd5d6e Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 22:26:51 +0100 Subject: [PATCH 152/170] feat: drastically speed up the committee server script, it no longer tries to remove the committee role from those who dont have it. --- src/common/set_roles.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index c51c52e..fbbc828 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -329,15 +329,11 @@ pub mod committee { None => { vec![] } - Some(x) => { - let mut tmp = x.to_owned(); - if !tmp.is_empty() { - tmp.push(committee_member); - } - tmp - } + Some(x) => x.to_owned(), }; + let on_committee = !roles_required.is_empty(); + let mut roles_rem = vec![]; let mut roles_add = vec![]; // get a list of all the roles to remove from someone @@ -346,14 +342,25 @@ pub mod committee { for role in &roles_current { roles_current_id.push(role.id.to_owned()); if !roles_required.contains(&role.id) { - roles_rem.push(role.id.to_owned()); + if role.id == committee_member { + if !on_committee { + roles_rem.push(role.id.to_owned()); + } + } } } - if !roles_required.is_empty() { + let has_committee_role = roles_current_id.contains(&committee_member); + + if on_committee && !has_committee_role { // if there are committee roles then give the general purporse role roles_add.push(committee_member); + } + if !on_committee && has_committee_role { + roles_rem.push(committee_member); + } + if !roles_required.is_empty() { if let Some(x) = roles_db.get_mut(&0) { x.count += 1; } @@ -372,8 +379,6 @@ pub mod committee { if !roles_add.is_empty() { // these roles are flavor roles, only there to make folks mentionable member.add_roles(&ctx, &roles_add).await.unwrap_or_default(); - } else { - member.remove_roles(&ctx, &[committee_member]).await.unwrap_or_default(); } } From 5815cde38b1216f4b286e19617ae324fe15ebadd Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 22:28:59 +0100 Subject: [PATCH 153/170] fmt: better wording for logs --- src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 08699e4..76ef059 100644 --- a/src/main.rs +++ b/src/main.rs @@ -60,11 +60,11 @@ impl EventHandler for Handler { let db = db_lock.read().await; loop { - println!("User update - Start"); + println!("Update - Users - Start"); for server_config in get_server_config_bulk(&db).await { normal::update_server(&ctx_task, &server_config, &[], &[]).await; } - println!("User update - End"); + println!("Update - Users - End"); tokio::time::sleep(Duration::from_secs(60 * 5)).await; } }); From 96eb81293b498b71f55824d47e8f7f72314cb03b Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 22:32:55 +0100 Subject: [PATCH 154/170] feat: got the committee update running smoothly (using cache and far far faster due to fixing some logic issues) --- src/main.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 76ef059..ffbd10d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,7 @@ use serenity::{ }; use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_config_bulk, get_server_member, DataBase}; use skynet_discord_bot::common::set_roles::committee::update_committees; -use skynet_discord_bot::common::set_roles::normal; +use skynet_discord_bot::common::set_roles::{committee, normal}; use skynet_discord_bot::common::wolves::committees::Committees; use skynet_discord_bot::{get_config, Config}; use sqlx::{Pool, Sqlite}; @@ -70,6 +70,19 @@ impl EventHandler for Handler { }); } + { + // this is to update committee roles every 5 min + let ctx_task = Arc::clone(&ctx); + tokio::spawn(async move { + loop { + println!("Update - Committee - Start"); + committee::check_committee(&ctx_task).await; + println!("Update - Committee - End"); + tokio::time::sleep(Duration::from_secs(60 * 5)).await; + } + }); + } + // Now that the loop is running, we set the bool to true self.is_loop_running.swap(true, Ordering::Relaxed); } From eb8821674080a465f275f9e8fcf03fa18a40f776 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 22:33:56 +0100 Subject: [PATCH 155/170] feat: removed the cronjob for updating the committee server --- Cargo.toml | 3 -- flake.nix | 2 -- src/bin/update_committee.rs | 63 ------------------------------------- 3 files changed, 68 deletions(-) delete mode 100644 src/bin/update_committee.rs diff --git a/Cargo.toml b/Cargo.toml index f2bc696..deb6676 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,9 +8,6 @@ edition = "2021" [[bin]] name = "update_data" -[[bin]] -name = "update_committee" - [[bin]] name = "update_minecraft" diff --git a/flake.nix b/flake.nix index 04e062b..ea52e09 100644 --- a/flake.nix +++ b/flake.nix @@ -154,8 +154,6 @@ scripts = { # every 20 min "update_data" = "*:0,10,20,30,40,50"; - # Committee server has its own timer - "update_committee" = "*:15:00"; # minecraft stuff is updated at 5am "update_minecraft" = "5:10:00"; # server icon gets updated daily at midnight diff --git a/src/bin/update_committee.rs b/src/bin/update_committee.rs deleted file mode 100644 index 2eef977..0000000 --- a/src/bin/update_committee.rs +++ /dev/null @@ -1,63 +0,0 @@ -use serenity::all::{ChunkGuildFilter, GuildId}; -use serenity::{ - async_trait, - client::{Context, EventHandler}, - model::gateway::{GatewayIntents, Ready}, - Client, -}; -use skynet_discord_bot::common::database::{db_init, DataBase}; -use skynet_discord_bot::common::set_roles::committee; -use skynet_discord_bot::{get_config, Config}; -use std::{process, sync::Arc}; -use tokio::sync::RwLock; - -#[tokio::main] -async fn main() { - let config = get_config(); - let db = match db_init(&config).await { - Ok(x) => x, - Err(_) => return, - }; - - // Intents are a bitflag, bitwise operations can be used to dictate which intents to use - let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; - // Build our client. - let mut client = Client::builder(&config.discord_token, intents) - .event_handler(Handler {}) - .cache_settings(serenity::cache::Settings::default()) - .await - .expect("Error creating client"); - - { - let mut data = client.data.write().await; - - data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(RwLock::new(db))); - } - - if let Err(why) = client.start().await { - println!("Client error: {:?}", why); - } -} - -struct Handler; -#[async_trait] -impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, guilds: Vec) { - for guild in guilds { - ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); - } - println!("Cache built successfully!"); - } - - async fn ready(&self, ctx: Context, ready: Ready) { - let ctx = Arc::new(ctx); - println!("{} is connected!", ready.user.name); - - // u[date committee server - committee::check_committee(Arc::clone(&ctx)).await; - - // finish up - process::exit(0); - } -} From 13eb2307543c7d117661a8affedae04f175084f4 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 22:48:04 +0100 Subject: [PATCH 156/170] fix: updating the wolves (user data) should not trigger a server update (directly). That should always be triggered separately. This was a holdover from a time when updating teh users was expensive (timewise) --- src/common/wolves.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 6f73842..e199ad2 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -49,7 +49,6 @@ async fn add_users_wolves(db: &Pool, user: &WolvesResultUserMin) { */ pub mod cns { use crate::common::database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers}; - use crate::common::set_roles::normal::update_server; use crate::common::wolves::{add_users_wolves, WolvesResultUserMin}; use crate::Config; use serenity::client::Context; @@ -96,7 +95,6 @@ pub mod cns { let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::>(); // list of users that need to be updated for this server - let mut user_to_update = vec![]; let mut server_name_tmp = None; for user in wolves.get_members(wolves_api).await { // dbg!(&user.committee); @@ -115,10 +113,6 @@ pub mod cns { add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await; if old.expiry != user.expiry { add_users_server_members(&db, server, &user).await; - - if let Some(discord_id) = old.discord { - user_to_update.push(discord_id); - } } } } @@ -129,9 +123,6 @@ pub mod cns { set_server_member(&db, server, cs_id).await; } } - if !user_to_update.is_empty() { - update_server(ctx, &server_config, &[], &user_to_update).await; - } } } From 227db8a74130d104aff9e767fb155f72913e06a8 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 22:54:34 +0100 Subject: [PATCH 157/170] feat: moved the update data to the main thread --- Cargo.toml | 4 --- flake.nix | 2 -- src/bin/update_data.rs | 70 ------------------------------------------ src/main.rs | 22 ++++++++++++- 4 files changed, 21 insertions(+), 77 deletions(-) delete mode 100644 src/bin/update_data.rs diff --git a/Cargo.toml b/Cargo.toml index deb6676..ef48675 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,10 +4,6 @@ version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[[bin]] -name = "update_data" - [[bin]] name = "update_minecraft" diff --git a/flake.nix b/flake.nix index ea52e09..31d6434 100644 --- a/flake.nix +++ b/flake.nix @@ -152,8 +152,6 @@ # modify these scripts = { - # every 20 min - "update_data" = "*:0,10,20,30,40,50"; # minecraft stuff is updated at 5am "update_minecraft" = "5:10:00"; # server icon gets updated daily at midnight diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs deleted file mode 100644 index d184ccd..0000000 --- a/src/bin/update_data.rs +++ /dev/null @@ -1,70 +0,0 @@ -use serenity::all::{ChunkGuildFilter, GuildId}; -use serenity::{ - async_trait, - client::{Context, EventHandler}, - model::gateway::{GatewayIntents, Ready}, - Client, -}; -use skynet_discord_bot::common::database::{db_init, DataBase}; -use skynet_discord_bot::common::wolves::cns::get_wolves; -use skynet_discord_bot::common::wolves::committees::get_cns; -use skynet_discord_bot::{get_config, Config}; -use std::{process, sync::Arc}; -use tokio::sync::RwLock; - -#[tokio::main] -async fn main() { - let config = get_config(); - let db = match db_init(&config).await { - Ok(x) => x, - Err(e) => { - dbg!(e); - return; - } - }; - - // Intents are a bitflag, bitwise operations can be used to dictate which intents to use - let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; - // Build our client. - let mut client = Client::builder(&config.discord_token, intents) - .event_handler(Handler {}) - .cache_settings(serenity::cache::Settings::default()) - .await - .expect("Error creating client"); - - { - let mut data = client.data.write().await; - - data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(RwLock::new(db))); - } - - if let Err(why) = client.start().await { - println!("Client error: {:?}", why); - } -} - -struct Handler; -#[async_trait] -impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, guilds: Vec) { - for guild in guilds { - ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); - } - println!("Cache built successfully!"); - } - - async fn ready(&self, ctx: Context, ready: Ready) { - let ctx = Arc::new(ctx); - println!("{} is connected!", ready.user.name); - - // get the data for each individual club/soc - get_wolves(&ctx).await; - - // get teh data for the clubs/socs committees - get_cns(&ctx).await; - - // finish up - process::exit(0); - } -} diff --git a/src/main.rs b/src/main.rs index ffbd10d..df08f6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,7 +19,8 @@ use serenity::{ use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_config_bulk, get_server_member, DataBase}; use skynet_discord_bot::common::set_roles::committee::update_committees; use skynet_discord_bot::common::set_roles::{committee, normal}; -use skynet_discord_bot::common::wolves::committees::Committees; +use skynet_discord_bot::common::wolves::cns::get_wolves; +use skynet_discord_bot::common::wolves::committees::{get_cns, Committees}; use skynet_discord_bot::{get_config, Config}; use sqlx::{Pool, Sqlite}; use std::sync::atomic::{AtomicBool, Ordering}; @@ -49,6 +50,25 @@ impl EventHandler for Handler { if !self.is_loop_running.load(Ordering::Relaxed) { // We have to clone the Arc, as it gets moved into the new thread. + { + // This updates all the data, wolves user data and the committees + let ctx_task = Arc::clone(&ctx); + tokio::spawn(async move { + loop { + println!("Update - Data - Start"); + + // get the data for each individual club/soc + get_wolves(&ctx_task).await; + + // get teh data for the clubs/socs committees + get_cns(&ctx_task).await; + + println!("Update - Data - End"); + tokio::time::sleep(Duration::from_secs(60 * 5)).await; + } + }); + } + { // this is to update member roles every 5 min let ctx_task = Arc::clone(&ctx); From feff293043b4f2f587e9aef6475decdc5d3e87cb Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 23:12:02 +0100 Subject: [PATCH 158/170] feat: moved the update server icon to the main thread --- Cargo.toml | 3 -- flake.nix | 3 +- src/bin/update_server-icon.rs | 78 ----------------------------------- src/commands/server_icon.rs | 2 +- src/main.rs | 40 +++++++++++++++++- 5 files changed, 40 insertions(+), 86 deletions(-) delete mode 100644 src/bin/update_server-icon.rs diff --git a/Cargo.toml b/Cargo.toml index ef48675..79ad1d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,6 @@ edition = "2021" [[bin]] name = "update_minecraft" -[[bin]] -name = "update_server-icon" - [[bin]] name = "cleanup_committee" diff --git a/flake.nix b/flake.nix index 31d6434..2dee869 100644 --- a/flake.nix +++ b/flake.nix @@ -153,9 +153,8 @@ # modify these scripts = { # minecraft stuff is updated at 5am + # this service does not depend on teh discord cache "update_minecraft" = "5:10:00"; - # server icon gets updated daily at midnight - "update_server-icon" = "0:01:00"; }; in { options.services."${package_name}" = { diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs deleted file mode 100644 index 2c093f9..0000000 --- a/src/bin/update_server-icon.rs +++ /dev/null @@ -1,78 +0,0 @@ -use serenity::all::{ChunkGuildFilter, GuildId}; -use serenity::{ - async_trait, - client::{Context, EventHandler}, - model::gateway::{GatewayIntents, Ready}, - Client, -}; -use skynet_discord_bot::common::server_icon::{get_config_icons, update_icon}; -use skynet_discord_bot::{ - common::database::{db_init, DataBase}, - get_config, Config, -}; -use std::{process, sync::Arc}; -use tokio::sync::RwLock; - -#[tokio::main] -async fn main() { - let config = get_config(); - let db = match db_init(&config).await { - Ok(x) => x, - Err(_) => return, - }; - - // Intents are a bitflag, bitwise operations can be used to dictate which intents to use - let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; - // Build our client. - let mut client = Client::builder(&config.discord_token, intents) - .event_handler(Handler {}) - .cache_settings(serenity::cache::Settings::default()) - .await - .expect("Error creating client"); - - { - let mut data = client.data.write().await; - - data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(RwLock::new(db))); - } - - if let Err(why) = client.start().await { - println!("Client error: {:?}", why); - } -} - -struct Handler; -#[async_trait] -impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, guilds: Vec) { - for guild in guilds { - ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); - } - println!("Cache built successfully!"); - } - - async fn ready(&self, ctx: Context, ready: Ready) { - let ctx = Arc::new(ctx); - println!("{} is connected!", ready.user.name); - - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let db = db_lock.read().await; - - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - - let config_global = config_lock.read().await; - let config_toml = get_config_icons::minimal(); - - update_icon::update_icon_main(&ctx, &db, &config_global, &config_toml).await; - - // finish up - process::exit(0); - } -} diff --git a/src/commands/server_icon.rs b/src/commands/server_icon.rs index 9c970fc..59d755f 100644 --- a/src/commands/server_icon.rs +++ b/src/commands/server_icon.rs @@ -93,7 +93,7 @@ pub(crate) mod user { } } - async fn get_current_icon(db: &Pool) -> Option { + pub async fn get_current_icon(db: &Pool) -> Option { match sqlx::query_as::<_, ServerIcons>( " SELECT * from server_icons ORDER BY id DESC LIMIT 1 diff --git a/src/main.rs b/src/main.rs index df08f6b..6e6ad8a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,11 @@ pub mod commands; use crate::commands::role_adder::tools::on_role_change; +use crate::commands::server_icon::user::current::icon::get_current_icon; +use chrono::{Days, SecondsFormat, TimeDelta, Utc}; use serenity::all::{ - ActivityData, Command, CommandDataOptionValue, CreateMessage, EditInteractionResponse, GuildId, GuildMemberUpdateEvent, GuildMembersChunkEvent, - Interaction, + ActivityData, Command, CommandDataOptionValue, CreateAttachment, CreateMessage, EditInteractionResponse, GuildId, GuildMemberUpdateEvent, + GuildMembersChunkEvent, Interaction, }; use serenity::{ async_trait, @@ -17,6 +19,7 @@ use serenity::{ Client, }; use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_config_bulk, get_server_member, DataBase}; +use skynet_discord_bot::common::server_icon::{get_config_icons, update_icon}; use skynet_discord_bot::common::set_roles::committee::update_committees; use skynet_discord_bot::common::set_roles::{committee, normal}; use skynet_discord_bot::common::wolves::cns::get_wolves; @@ -103,6 +106,39 @@ impl EventHandler for Handler { }); } + { + // this updates teh server icon once a day + let ctx_task = Arc::clone(&ctx); + tokio::spawn(async move { + let db_lock = { + let data_read = ctx_task.data.read().await; + data_read.get::().expect("Expected Database in TypeMap.").clone() + }; + let db = db_lock.read().await; + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + + let config_global = config_lock.read().await; + let config_toml = get_config_icons::minimal(); + + loop { + println!("Update - Logo - Start"); + // even though this task will run every 5 min it will only actually change the icon when its a day old + if let Some(logo) = get_current_icon(&db).await { + let now = Utc::now(); + let yesterday = now.checked_sub_days(Days::new(1)).unwrap_or_default(); + if logo.date < yesterday.to_rfc3339_opts(SecondsFormat::Millis, true) { + update_icon::update_icon_main(&ctx, &db, &config_global, &config_toml).await; + } + } + println!("Update - Logo - End"); + tokio::time::sleep(Duration::from_secs(60 * 5)).await; + } + }); + } + // Now that the loop is running, we set the bool to true self.is_loop_running.swap(true, Ordering::Relaxed); } From 1af7f28a453bda331c203e862a71258690860490 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 23:40:34 +0100 Subject: [PATCH 159/170] feat: restore teh original services, giving up the logging and control was too much --- Cargo.toml | 9 ++++ flake.nix | 8 ++++ src/bin/update_committee.rs | 63 ++++++++++++++++++++++++++++ src/bin/update_data.rs | 70 +++++++++++++++++++++++++++++++ src/bin/update_server-icon.rs | 78 +++++++++++++++++++++++++++++++++++ src/bin/update_users.rs | 76 ++++++++++++++++++++++++++++++++++ 6 files changed, 304 insertions(+) create mode 100644 src/bin/update_committee.rs create mode 100644 src/bin/update_data.rs create mode 100644 src/bin/update_server-icon.rs create mode 100644 src/bin/update_users.rs diff --git a/Cargo.toml b/Cargo.toml index 79ad1d0..832eb09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,9 +4,18 @@ version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "update_data" + +[[bin]] +name = "update_committee" + [[bin]] name = "update_minecraft" +[[bin]] +name = "update_server-icon" + [[bin]] name = "cleanup_committee" diff --git a/flake.nix b/flake.nix index 2dee869..81f4490 100644 --- a/flake.nix +++ b/flake.nix @@ -152,9 +152,17 @@ # modify these scripts = { + # every 10 min + "update_data" = "*:0,10,20,30,40,50"; + # groups are updated every hour, offset from teh ldap + "update_users" = "*:5,15,25,35,45,55"; + # Committee server has its own timer + "update_committee" = "*:5,15,25,35,45,55"; # minecraft stuff is updated at 5am # this service does not depend on teh discord cache "update_minecraft" = "5:10:00"; + # server icon gets updated daily at midnight + "update_server-icon" = "0:01:00"; }; in { options.services."${package_name}" = { diff --git a/src/bin/update_committee.rs b/src/bin/update_committee.rs new file mode 100644 index 0000000..b348d18 --- /dev/null +++ b/src/bin/update_committee.rs @@ -0,0 +1,63 @@ +use serenity::all::{ChunkGuildFilter, GuildId}; +use serenity::{ + async_trait, + client::{Context, EventHandler}, + model::gateway::{GatewayIntents, Ready}, + Client, +}; +use skynet_discord_bot::common::database::{db_init, DataBase}; +use skynet_discord_bot::common::set_roles::committee; +use skynet_discord_bot::{get_config, Config}; +use std::{process, sync::Arc}; +use tokio::sync::RwLock; + +#[tokio::main] +async fn main() { + let config = get_config(); + let db = match db_init(&config).await { + Ok(x) => x, + Err(_) => return, + }; + + // Intents are a bitflag, bitwise operations can be used to dictate which intents to use + let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; + // Build our client. + let mut client = Client::builder(&config.discord_token, intents) + .event_handler(Handler {}) + .cache_settings(serenity::cache::Settings::default()) + .await + .expect("Error creating client"); + + { + let mut data = client.data.write().await; + + data.insert::(Arc::new(RwLock::new(config))); + data.insert::(Arc::new(RwLock::new(db))); + } + + if let Err(why) = client.start().await { + println!("Client error: {:?}", why); + } +} + +struct Handler; +#[async_trait] +impl EventHandler for Handler { + async fn cache_ready(&self, ctx: Context, guilds: Vec) { + for guild in guilds { + ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); + } + println!("Cache built successfully!"); + } + + async fn ready(&self, ctx: Context, ready: Ready) { + let ctx = Arc::new(ctx); + println!("{} is connected!", ready.user.name); + + // u[date committee server + committee::check_committee(&ctx).await; + + // finish up + process::exit(0); + } +} diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs new file mode 100644 index 0000000..d184ccd --- /dev/null +++ b/src/bin/update_data.rs @@ -0,0 +1,70 @@ +use serenity::all::{ChunkGuildFilter, GuildId}; +use serenity::{ + async_trait, + client::{Context, EventHandler}, + model::gateway::{GatewayIntents, Ready}, + Client, +}; +use skynet_discord_bot::common::database::{db_init, DataBase}; +use skynet_discord_bot::common::wolves::cns::get_wolves; +use skynet_discord_bot::common::wolves::committees::get_cns; +use skynet_discord_bot::{get_config, Config}; +use std::{process, sync::Arc}; +use tokio::sync::RwLock; + +#[tokio::main] +async fn main() { + let config = get_config(); + let db = match db_init(&config).await { + Ok(x) => x, + Err(e) => { + dbg!(e); + return; + } + }; + + // Intents are a bitflag, bitwise operations can be used to dictate which intents to use + let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; + // Build our client. + let mut client = Client::builder(&config.discord_token, intents) + .event_handler(Handler {}) + .cache_settings(serenity::cache::Settings::default()) + .await + .expect("Error creating client"); + + { + let mut data = client.data.write().await; + + data.insert::(Arc::new(RwLock::new(config))); + data.insert::(Arc::new(RwLock::new(db))); + } + + if let Err(why) = client.start().await { + println!("Client error: {:?}", why); + } +} + +struct Handler; +#[async_trait] +impl EventHandler for Handler { + async fn cache_ready(&self, ctx: Context, guilds: Vec) { + for guild in guilds { + ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); + } + println!("Cache built successfully!"); + } + + async fn ready(&self, ctx: Context, ready: Ready) { + let ctx = Arc::new(ctx); + println!("{} is connected!", ready.user.name); + + // get the data for each individual club/soc + get_wolves(&ctx).await; + + // get teh data for the clubs/socs committees + get_cns(&ctx).await; + + // finish up + process::exit(0); + } +} diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs new file mode 100644 index 0000000..2c093f9 --- /dev/null +++ b/src/bin/update_server-icon.rs @@ -0,0 +1,78 @@ +use serenity::all::{ChunkGuildFilter, GuildId}; +use serenity::{ + async_trait, + client::{Context, EventHandler}, + model::gateway::{GatewayIntents, Ready}, + Client, +}; +use skynet_discord_bot::common::server_icon::{get_config_icons, update_icon}; +use skynet_discord_bot::{ + common::database::{db_init, DataBase}, + get_config, Config, +}; +use std::{process, sync::Arc}; +use tokio::sync::RwLock; + +#[tokio::main] +async fn main() { + let config = get_config(); + let db = match db_init(&config).await { + Ok(x) => x, + Err(_) => return, + }; + + // Intents are a bitflag, bitwise operations can be used to dictate which intents to use + let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; + // Build our client. + let mut client = Client::builder(&config.discord_token, intents) + .event_handler(Handler {}) + .cache_settings(serenity::cache::Settings::default()) + .await + .expect("Error creating client"); + + { + let mut data = client.data.write().await; + + data.insert::(Arc::new(RwLock::new(config))); + data.insert::(Arc::new(RwLock::new(db))); + } + + if let Err(why) = client.start().await { + println!("Client error: {:?}", why); + } +} + +struct Handler; +#[async_trait] +impl EventHandler for Handler { + async fn cache_ready(&self, ctx: Context, guilds: Vec) { + for guild in guilds { + ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); + } + println!("Cache built successfully!"); + } + + async fn ready(&self, ctx: Context, ready: Ready) { + let ctx = Arc::new(ctx); + println!("{} is connected!", ready.user.name); + + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let db = db_lock.read().await; + + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + + let config_global = config_lock.read().await; + let config_toml = get_config_icons::minimal(); + + update_icon::update_icon_main(&ctx, &db, &config_global, &config_toml).await; + + // finish up + process::exit(0); + } +} diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs new file mode 100644 index 0000000..27dc1d6 --- /dev/null +++ b/src/bin/update_users.rs @@ -0,0 +1,76 @@ +use serenity::all::{ChunkGuildFilter, GuildId}; +use serenity::{ + async_trait, + client::{Context, EventHandler}, + model::gateway::{GatewayIntents, Ready}, + Client, +}; +use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase}; +use skynet_discord_bot::common::set_roles::normal; +use skynet_discord_bot::{get_config, Config}; +use std::{process, sync::Arc}; +use tokio::sync::RwLock; + +#[tokio::main] +async fn main() { + let config = get_config(); + let db = match db_init(&config).await { + Ok(x) => x, + Err(_) => return, + }; + + // Intents are a bitflag, bitwise operations can be used to dictate which intents to use + let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; + // Build our client. + let mut client = Client::builder(&config.discord_token, intents) + .event_handler(Handler {}) + .cache_settings(serenity::cache::Settings::default()) + .await + .expect("Error creating client"); + + { + let mut data = client.data.write().await; + + data.insert::(Arc::new(RwLock::new(config))); + data.insert::(Arc::new(RwLock::new(db))); + } + + if let Err(why) = client.start().await { + println!("Client error: {:?}", why); + } +} + +struct Handler; +#[async_trait] +impl EventHandler for Handler { + async fn cache_ready(&self, ctx: Context, guilds: Vec) { + for guild in guilds { + ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); + } + println!("Cache built successfully!"); + } + + async fn ready(&self, ctx: Context, ready: Ready) { + let ctx = Arc::new(ctx); + println!("{} is connected!", ready.user.name); + + // this goes into each server and sets roles for each wolves member + check_bulk(Arc::clone(&ctx)).await; + + // finish up + process::exit(0); + } +} + +async fn check_bulk(ctx: Arc) { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + + let db = db_lock.read().await; + + for server_config in get_server_config_bulk(&db).await { + normal::update_server(&ctx, &server_config, &[], &[]).await; + } +} From bd9d0cd43f12ce062ceab1811848ede632d4e4c3 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 23:44:13 +0100 Subject: [PATCH 160/170] fix: these do not need to use teh cache --- src/bin/update_data.rs | 7 ------- src/bin/update_server-icon.rs | 7 ------- 2 files changed, 14 deletions(-) diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index d184ccd..e67720e 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -47,13 +47,6 @@ async fn main() { struct Handler; #[async_trait] impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, guilds: Vec) { - for guild in guilds { - ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); - } - println!("Cache built successfully!"); - } - async fn ready(&self, ctx: Context, ready: Ready) { let ctx = Arc::new(ctx); println!("{} is connected!", ready.user.name); diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index 2c093f9..c59634e 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -45,13 +45,6 @@ async fn main() { struct Handler; #[async_trait] impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, guilds: Vec) { - for guild in guilds { - ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); - } - println!("Cache built successfully!"); - } - async fn ready(&self, ctx: Context, ready: Ready) { let ctx = Arc::new(ctx); println!("{} is connected!", ready.user.name); From 6d08312f48dca12d4b06d104499ef3633fa21924 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 23:46:30 +0100 Subject: [PATCH 161/170] fix: these were not using teh cache to access teh member/role data *was executing before teh cache was built (``cache_ready`` is only when teh cache has been init, not when it is populated) --- src/bin/cleanup_committee.rs | 35 +++++++++++++++++------------------ src/bin/update_committee.rs | 17 ++++++++--------- src/bin/update_users.rs | 19 +++++++++---------- 3 files changed, 34 insertions(+), 37 deletions(-) diff --git a/src/bin/cleanup_committee.rs b/src/bin/cleanup_committee.rs index a2c3772..cc68536 100644 --- a/src/bin/cleanup_committee.rs +++ b/src/bin/cleanup_committee.rs @@ -1,4 +1,4 @@ -use serenity::all::{ChunkGuildFilter, GuildId}; +use serenity::all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent}; use serenity::{ async_trait, client::{Context, EventHandler}, @@ -56,26 +56,25 @@ impl EventHandler for Handler { println!("Cache built successfully!"); } - async fn ready(&self, ctx: Context, ready: Ready) { - let ctx = Arc::new(ctx); - println!("{} is connected!", ready.user.name); + async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) { + if (chunk.chunk_index + 1) == chunk.chunk_count { + let db_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; + let db = db_lock.read().await; - let db = db_lock.read().await; + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config = config_lock.read().await; - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - let config = config_lock.read().await; - - cleanup(&db, &ctx, &config).await; - // finish up - process::exit(0); + cleanup(&db, &ctx, &config).await; + // finish up + process::exit(0); + } } } diff --git a/src/bin/update_committee.rs b/src/bin/update_committee.rs index b348d18..e3e7277 100644 --- a/src/bin/update_committee.rs +++ b/src/bin/update_committee.rs @@ -1,4 +1,4 @@ -use serenity::all::{ChunkGuildFilter, GuildId}; +use serenity::all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent}; use serenity::{ async_trait, client::{Context, EventHandler}, @@ -50,14 +50,13 @@ impl EventHandler for Handler { println!("Cache built successfully!"); } - async fn ready(&self, ctx: Context, ready: Ready) { - let ctx = Arc::new(ctx); - println!("{} is connected!", ready.user.name); + async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) { + if (chunk.chunk_index + 1) == chunk.chunk_count { + // u[date committee server + committee::check_committee(&ctx).await; - // u[date committee server - committee::check_committee(&ctx).await; - - // finish up - process::exit(0); + // finish up + process::exit(0); + } } } diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index 27dc1d6..6ba99b0 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -1,4 +1,4 @@ -use serenity::all::{ChunkGuildFilter, GuildId}; +use serenity::all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent}; use serenity::{ async_trait, client::{Context, EventHandler}, @@ -50,19 +50,18 @@ impl EventHandler for Handler { println!("Cache built successfully!"); } - async fn ready(&self, ctx: Context, ready: Ready) { - let ctx = Arc::new(ctx); - println!("{} is connected!", ready.user.name); + async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) { + if (chunk.chunk_index + 1) == chunk.chunk_count { + // this goes into each server and sets roles for each wolves member + check_bulk(&ctx).await; - // this goes into each server and sets roles for each wolves member - check_bulk(Arc::clone(&ctx)).await; - - // finish up - process::exit(0); + // finish up + process::exit(0); + } } } -async fn check_bulk(ctx: Arc) { +async fn check_bulk(ctx: &Context) { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() From 57d4947edfdcb005faaa98d45d7973dbe1870557 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Sun, 20 Jul 2025 23:48:05 +0100 Subject: [PATCH 162/170] fix: no longer needint to wait until the cache in teh main program is filled --- src/main.rs | 100 ---------------------------------------------------- 1 file changed, 100 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6e6ad8a..e76bd34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -45,106 +45,6 @@ impl EventHandler for Handler { println!("Cache built successfully!"); } - async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) { - if (chunk.chunk_index + 1) == chunk.chunk_count { - // from https://github.com/serenity-rs/serenity/blob/18349f7bba43acad4261103eb38fe01d93f382df/examples/e13_parallel_loops/src/main.rs#L48 - let ctx = Arc::new(ctx); - - if !self.is_loop_running.load(Ordering::Relaxed) { - // We have to clone the Arc, as it gets moved into the new thread. - - { - // This updates all the data, wolves user data and the committees - let ctx_task = Arc::clone(&ctx); - tokio::spawn(async move { - loop { - println!("Update - Data - Start"); - - // get the data for each individual club/soc - get_wolves(&ctx_task).await; - - // get teh data for the clubs/socs committees - get_cns(&ctx_task).await; - - println!("Update - Data - End"); - tokio::time::sleep(Duration::from_secs(60 * 5)).await; - } - }); - } - - { - // this is to update member roles every 5 min - let ctx_task = Arc::clone(&ctx); - tokio::spawn(async move { - let db_lock = { - let data_read = ctx_task.data.read().await; - data_read.get::().expect("Expected Database in TypeMap.").clone() - }; - let db = db_lock.read().await; - - loop { - println!("Update - Users - Start"); - for server_config in get_server_config_bulk(&db).await { - normal::update_server(&ctx_task, &server_config, &[], &[]).await; - } - println!("Update - Users - End"); - tokio::time::sleep(Duration::from_secs(60 * 5)).await; - } - }); - } - - { - // this is to update committee roles every 5 min - let ctx_task = Arc::clone(&ctx); - tokio::spawn(async move { - loop { - println!("Update - Committee - Start"); - committee::check_committee(&ctx_task).await; - println!("Update - Committee - End"); - tokio::time::sleep(Duration::from_secs(60 * 5)).await; - } - }); - } - - { - // this updates teh server icon once a day - let ctx_task = Arc::clone(&ctx); - tokio::spawn(async move { - let db_lock = { - let data_read = ctx_task.data.read().await; - data_read.get::().expect("Expected Database in TypeMap.").clone() - }; - let db = db_lock.read().await; - let config_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Config in TypeMap.").clone() - }; - - let config_global = config_lock.read().await; - let config_toml = get_config_icons::minimal(); - - loop { - println!("Update - Logo - Start"); - // even though this task will run every 5 min it will only actually change the icon when its a day old - if let Some(logo) = get_current_icon(&db).await { - let now = Utc::now(); - let yesterday = now.checked_sub_days(Days::new(1)).unwrap_or_default(); - if logo.date < yesterday.to_rfc3339_opts(SecondsFormat::Millis, true) { - update_icon::update_icon_main(&ctx, &db, &config_global, &config_toml).await; - } - } - println!("Update - Logo - End"); - tokio::time::sleep(Duration::from_secs(60 * 5)).await; - } - }); - } - - // Now that the loop is running, we set the bool to true - self.is_loop_running.swap(true, Ordering::Relaxed); - } - } - } - // handles previously linked accounts joining the server async fn guild_member_addition(&self, ctx: Context, new_member: Member) { let db_lock = { From 9d409e3692c8be39d7d98cccc18cd60f8bf8fa51 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 21 Jul 2025 00:38:59 +0100 Subject: [PATCH 163/170] fmt: clippy and nightly fmt --- .rustfmt.toml | 2 +- src/bin/cleanup_committee.rs | 15 ++++++---- src/bin/update_committee.rs | 14 +++++---- src/bin/update_data.rs | 12 ++++---- src/bin/update_minecraft.rs | 10 +++++-- src/bin/update_server-icon.rs | 7 +++-- src/bin/update_users.rs | 16 ++++++---- src/commands/add_server.rs | 14 +++++---- src/commands/count.rs | 13 ++++---- src/commands/minecraft.rs | 56 +++++++++++++++++++++-------------- src/commands/role_adder.rs | 3 +- src/commands/server_icon.rs | 6 ++-- src/commands/wolves.rs | 28 +++++++++++------- src/common/database.rs | 19 +++++++----- src/common/minecraft.rs | 9 ++---- src/common/renderer.rs | 6 ++-- src/common/set_roles.rs | 49 ++++++++++++++++-------------- src/common/wolves.rs | 16 +++++----- src/lib.rs | 6 ++-- src/main.rs | 36 +++++++++------------- 20 files changed, 194 insertions(+), 143 deletions(-) diff --git a/.rustfmt.toml b/.rustfmt.toml index 6aeb30c..2b0831a 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -7,4 +7,4 @@ fn_params_layout = "Compressed" struct_lit_width = 0 tab_spaces = 2 use_small_heuristics = "Max" -#imports_granularity="Crate" \ No newline at end of file +imports_granularity = "Crate" \ No newline at end of file diff --git a/src/bin/cleanup_committee.rs b/src/bin/cleanup_committee.rs index cc68536..76f0526 100644 --- a/src/bin/cleanup_committee.rs +++ b/src/bin/cleanup_committee.rs @@ -1,14 +1,17 @@ -use serenity::all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent}; use serenity::{ + all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent}, async_trait, client::{Context, EventHandler}, - model::gateway::{GatewayIntents, Ready}, + model::gateway::GatewayIntents, Client, }; -use skynet_discord_bot::common::database::{db_init, DataBase}; - -use skynet_discord_bot::common::set_roles::committee::db_roles_get; -use skynet_discord_bot::{get_config, Config}; +use skynet_discord_bot::{ + common::{ + database::{db_init, DataBase}, + set_roles::committee::db_roles_get, + }, + get_config, Config, +}; use sqlx::{Pool, Sqlite}; use std::{process, sync::Arc}; use tokio::sync::RwLock; diff --git a/src/bin/update_committee.rs b/src/bin/update_committee.rs index e3e7277..ff263f6 100644 --- a/src/bin/update_committee.rs +++ b/src/bin/update_committee.rs @@ -1,13 +1,17 @@ -use serenity::all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent}; use serenity::{ + all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent}, async_trait, client::{Context, EventHandler}, - model::gateway::{GatewayIntents, Ready}, + model::gateway::GatewayIntents, Client, }; -use skynet_discord_bot::common::database::{db_init, DataBase}; -use skynet_discord_bot::common::set_roles::committee; -use skynet_discord_bot::{get_config, Config}; +use skynet_discord_bot::{ + common::{ + database::{db_init, DataBase}, + set_roles::committee, + }, + get_config, Config, +}; use std::{process, sync::Arc}; use tokio::sync::RwLock; diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index e67720e..902920e 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -1,14 +1,16 @@ -use serenity::all::{ChunkGuildFilter, GuildId}; use serenity::{ async_trait, client::{Context, EventHandler}, model::gateway::{GatewayIntents, Ready}, Client, }; -use skynet_discord_bot::common::database::{db_init, DataBase}; -use skynet_discord_bot::common::wolves::cns::get_wolves; -use skynet_discord_bot::common::wolves::committees::get_cns; -use skynet_discord_bot::{get_config, Config}; +use skynet_discord_bot::{ + common::{ + database::{db_init, DataBase}, + wolves::{cns::get_wolves, committees::get_cns}, + }, + get_config, Config, +}; use std::{process, sync::Arc}; use tokio::sync::RwLock; diff --git a/src/bin/update_minecraft.rs b/src/bin/update_minecraft.rs index f5d3634..f7c24e0 100644 --- a/src/bin/update_minecraft.rs +++ b/src/bin/update_minecraft.rs @@ -1,6 +1,10 @@ -use skynet_discord_bot::common::database::db_init; -use skynet_discord_bot::common::minecraft::{get_minecraft_config, update_server, whitelist_wipe}; -use skynet_discord_bot::get_config; +use skynet_discord_bot::{ + common::{ + database::db_init, + minecraft::{get_minecraft_config, update_server, whitelist_wipe}, + }, + get_config, +}; use std::collections::HashSet; #[tokio::main] diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index c59634e..ba9de80 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -1,13 +1,14 @@ -use serenity::all::{ChunkGuildFilter, GuildId}; use serenity::{ async_trait, client::{Context, EventHandler}, model::gateway::{GatewayIntents, Ready}, Client, }; -use skynet_discord_bot::common::server_icon::{get_config_icons, update_icon}; use skynet_discord_bot::{ - common::database::{db_init, DataBase}, + common::{ + database::{db_init, DataBase}, + server_icon::{get_config_icons, update_icon}, + }, get_config, Config, }; use std::{process, sync::Arc}; diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index 6ba99b0..c146a25 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -1,13 +1,17 @@ -use serenity::all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent}; use serenity::{ + all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent}, async_trait, client::{Context, EventHandler}, - model::gateway::{GatewayIntents, Ready}, + model::gateway::GatewayIntents, Client, }; -use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase}; -use skynet_discord_bot::common::set_roles::normal; -use skynet_discord_bot::{get_config, Config}; +use skynet_discord_bot::{ + common::{ + database::{db_init, get_server_config_bulk, DataBase}, + set_roles::normal, + }, + get_config, Config, +}; use std::{process, sync::Arc}; use tokio::sync::RwLock; @@ -70,6 +74,6 @@ async fn check_bulk(ctx: &Context) { let db = db_lock.read().await; for server_config in get_server_config_bulk(&db).await { - normal::update_server(&ctx, &server_config, &[], &[]).await; + normal::update_server(ctx, &server_config, &[], &[]).await; } } diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index 387bd9e..63f3d49 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -1,8 +1,12 @@ -use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; -use serenity::client::Context; -use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers}; -use skynet_discord_bot::common::set_roles::normal::update_server; -use skynet_discord_bot::common::wolves::cns::get_wolves; +use serenity::{ + all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}, + client::Context, +}; +use skynet_discord_bot::common::{ + database::{get_server_config, DataBase, Servers}, + set_roles::normal::update_server, + wolves::cns::get_wolves, +}; use sqlx::{Error, Pool, Sqlite}; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { diff --git a/src/commands/count.rs b/src/commands/count.rs index 678c9e0..2bed533 100644 --- a/src/commands/count.rs +++ b/src/commands/count.rs @@ -5,8 +5,7 @@ pub mod committee { use serenity::all::{ CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption, }; - use skynet_discord_bot::common::database::DataBase; - use skynet_discord_bot::common::set_roles::committee::db_roles_get; + use skynet_discord_bot::common::{database::DataBase, set_roles::committee::db_roles_get}; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { let sub_options = if let Some(CommandDataOption { @@ -85,9 +84,13 @@ pub mod servers { // get the list of all the current clubs/socs use serde::{Deserialize, Serialize}; use serenity::all::{CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption}; - use skynet_discord_bot::common::database::{get_server_config_bulk, DataBase}; - use skynet_discord_bot::common::set_roles::committee::get_committees; - use skynet_discord_bot::get_now_iso; + use skynet_discord_bot::{ + common::{ + database::{get_server_config_bulk, DataBase}, + set_roles::committee::get_committees, + }, + get_now_iso, + }; use sqlx::{Pool, Sqlite}; use std::collections::HashMap; diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index ca395c6..f3a050d 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -9,11 +9,17 @@ pub(crate) mod user { use super::*; use crate::commands::wolves::link::get_server_member_discord; use serde::{Deserialize, Serialize}; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; - use serenity::model::id::UserId; - use skynet_discord_bot::common::database::Wolves; - use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft}; - use skynet_discord_bot::Config; + use serenity::{ + all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}, + model::id::UserId, + }; + use skynet_discord_bot::{ + common::{ + database::Wolves, + minecraft::{whitelist_update, Minecraft}, + }, + Config, + }; use sqlx::Error; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { @@ -185,14 +191,17 @@ pub(crate) mod server { use super::*; pub(crate) mod add { - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}; - use serenity::model::id::GuildId; + use serenity::{ + all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption}, + model::id::GuildId, + }; use sqlx::Error; // this is to managfe the server side of commands related to minecraft use super::*; - use skynet_discord_bot::common::minecraft::update_server; - use skynet_discord_bot::common::minecraft::Minecraft; - use skynet_discord_bot::Config; + use skynet_discord_bot::{ + common::minecraft::{update_server, Minecraft}, + Config, + }; pub fn register() -> CreateCommand { CreateCommand::new("minecraft_add") @@ -260,12 +269,14 @@ pub(crate) mod server { } pub(crate) mod list { - use serenity::all::CommandInteraction; - use serenity::builder::CreateCommand; - use serenity::client::Context; - use skynet_discord_bot::common::database::DataBase; - use skynet_discord_bot::common::minecraft::{get_minecraft_config_server, server_information}; - use skynet_discord_bot::Config; + use serenity::{all::CommandInteraction, builder::CreateCommand, client::Context}; + use skynet_discord_bot::{ + common::{ + database::DataBase, + minecraft::{get_minecraft_config_server, server_information}, + }, + Config, + }; pub fn register() -> CreateCommand { CreateCommand::new("minecraft_list") @@ -320,12 +331,13 @@ pub(crate) mod server { } pub(crate) mod delete { - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}; - use serenity::builder::CreateCommand; - use serenity::client::Context; - use serenity::model::id::GuildId; - use skynet_discord_bot::common::database::DataBase; - use skynet_discord_bot::common::minecraft::Minecraft; + use serenity::{ + all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption}, + builder::CreateCommand, + client::Context, + model::id::GuildId, + }; + use skynet_discord_bot::common::{database::DataBase, minecraft::Minecraft}; use sqlx::{Error, Pool, Sqlite}; pub fn register() -> CreateCommand { diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index f58803b..20928ba 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -142,8 +142,7 @@ pub mod edit { pub mod list {} pub mod tools { - use serenity::client::Context; - use serenity::model::guild::Member; + use serenity::{client::Context, model::guild::Member}; use skynet_discord_bot::common::database::RoleAdder; use sqlx::{Pool, Sqlite}; diff --git a/src/commands/server_icon.rs b/src/commands/server_icon.rs index 59d755f..2989c42 100644 --- a/src/commands/server_icon.rs +++ b/src/commands/server_icon.rs @@ -113,8 +113,10 @@ pub(crate) mod user { pub(crate) mod festival { use serenity::all::{CommandInteraction, Context}; - use skynet_discord_bot::common::server_icon::{get_config_icons, update_icon::get_festival}; - use skynet_discord_bot::Config; + use skynet_discord_bot::{ + common::server_icon::{get_config_icons, update_icon::get_festival}, + Config, + }; // use this to return what current festivals are active? pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String { diff --git a/src/commands/wolves.rs b/src/commands/wolves.rs index d78454b..f137622 100644 --- a/src/commands/wolves.rs +++ b/src/commands/wolves.rs @@ -4,11 +4,16 @@ use lettre::{ Message, SmtpTransport, Transport, }; use maud::html; -use serenity::all::CommandOptionType; -use serenity::builder::CreateCommandOption; -use serenity::{builder::CreateCommand, client::Context, model::id::UserId}; -use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify}; -use skynet_discord_bot::{get_now_iso, random_string, Config}; +use serenity::{ + all::CommandOptionType, + builder::{CreateCommand, CreateCommandOption}, + client::Context, + model::id::UserId, +}; +use skynet_discord_bot::{ + common::database::{DataBase, Wolves, WolvesVerify}, + get_now_iso, random_string, Config, +}; use sqlx::{Pool, Sqlite}; pub mod link { @@ -298,11 +303,14 @@ pub mod link_docs { pub mod verify { use super::*; use crate::commands::wolves::link::{db_pending_clear_expired, get_server_member_discord, get_verify_from_db}; - use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; - use serenity::model::user::User; - use skynet_discord_bot::common::database::get_server_config; - use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; - use skynet_discord_bot::common::wolves::committees::Committees; + use serenity::{ + all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}, + model::user::User, + }; + use skynet_discord_bot::common::{ + database::{get_server_config, ServerMembersWolves, Servers}, + wolves::committees::Committees, + }; use sqlx::Error; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { diff --git a/src/common/database.rs b/src/common/database.rs index 0663799..190baf2 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -1,12 +1,17 @@ use crate::Config; use serde::{Deserialize, Serialize}; -use serenity::model::guild; -use serenity::model::id::{ChannelId, GuildId, RoleId, UserId}; -use serenity::prelude::TypeMapKey; -use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow}; -use sqlx::{Error, FromRow, Pool, Row, Sqlite}; -use std::str::FromStr; -use std::sync::Arc; +use serenity::{ + model::{ + guild, + id::{ChannelId, GuildId, RoleId, UserId}, + }, + prelude::TypeMapKey, +}; +use sqlx::{ + sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow}, + Error, FromRow, Pool, Row, Sqlite, +}; +use std::{str::FromStr, sync::Arc}; use tokio::sync::RwLock; pub struct DataBase; diff --git a/src/common/minecraft.rs b/src/common/minecraft.rs index 4faa8ed..b7362e6 100644 --- a/src/common/minecraft.rs +++ b/src/common/minecraft.rs @@ -1,10 +1,7 @@ -use crate::common::set_roles::normal::get_server_member_bulk; -use crate::Config; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use crate::{common::set_roles::normal::get_server_member_bulk, Config}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serenity::model::id::GuildId; -use sqlx::sqlite::SqliteRow; -use sqlx::{Error, FromRow, Pool, Row, Sqlite}; +use sqlx::{sqlite::SqliteRow, Error, FromRow, Pool, Row, Sqlite}; #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Minecraft { diff --git a/src/common/renderer.rs b/src/common/renderer.rs index f99d891..e105c16 100644 --- a/src/common/renderer.rs +++ b/src/common/renderer.rs @@ -1,8 +1,10 @@ // this code is taken from https://github.com/MCorange99/svg2colored-png/tree/main // I was unable to figure out how to use usvg myself so younked it from here. -use std::ffi::OsStr; -use std::path::{Path, PathBuf}; +use std::{ + ffi::OsStr, + path::{Path, PathBuf}, +}; // use clap::builder::OsStr; use color_eyre::{eyre::bail, Result}; diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index fbbc828..3a894ee 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -1,8 +1,12 @@ pub mod normal { - use crate::common::database::{DataBase, ServerMembersWolves, Servers, Wolves}; - use crate::get_now_iso; - use serenity::client::Context; - use serenity::model::id::{GuildId, RoleId, UserId}; + use crate::{ + common::database::{DataBase, ServerMembersWolves, Servers, Wolves}, + get_now_iso, + }; + use serenity::{ + client::Context, + model::id::{GuildId, RoleId, UserId}, + }; use sqlx::{Pool, Sqlite}; struct RolesChange { @@ -152,21 +156,22 @@ pub mod normal { // for updating committee members pub mod committee { - use crate::common::database::{get_channel_from_row, get_role_from_row, DataBase, Wolves}; - use crate::common::wolves::committees::Committees; - use crate::Config; + use crate::{ + common::{ + database::{get_channel_from_row, get_role_from_row, DataBase, Wolves}, + wolves::committees::Committees, + }, + Config, + }; use serde::{Deserialize, Serialize}; - use serenity::all::EditRole; - use serenity::builder::CreateChannel; - use serenity::client::Context; - use serenity::model::channel::ChannelType; - use serenity::model::guild::Member; - use serenity::model::id::ChannelId; - use serenity::model::prelude::RoleId; - use sqlx::sqlite::SqliteRow; - use sqlx::{Error, FromRow, Pool, Row, Sqlite}; + use serenity::{ + all::EditRole, + builder::CreateChannel, + client::Context, + model::{channel::ChannelType, guild::Member, id::ChannelId, prelude::RoleId}, + }; + use sqlx::{sqlite::SqliteRow, Error, FromRow, Pool, Row, Sqlite}; use std::collections::HashMap; - use std::sync::Arc; pub async fn check_committee(ctx: &Context) { let db_lock = { @@ -187,7 +192,7 @@ pub mod committee { // because to use it to update a single user we need to pre-get the members of teh server let mut members = server.members(&ctx, None, None).await.unwrap_or_default(); - update_committees(&db, &ctx, &config_global, &mut members).await; + update_committees(&db, ctx, &config_global, &mut members).await; } /** @@ -342,11 +347,11 @@ pub mod committee { for role in &roles_current { roles_current_id.push(role.id.to_owned()); if !roles_required.contains(&role.id) { - if role.id == committee_member { - if !on_committee { - roles_rem.push(role.id.to_owned()); - } + if role.id == committee_member && on_committee { + continue; } + + roles_rem.push(role.id.to_owned()); } } diff --git a/src/common/wolves.rs b/src/common/wolves.rs index e199ad2..7ee71df 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -48,11 +48,14 @@ async fn add_users_wolves(db: &Pool, user: &WolvesResultUserMin) { This is getting data for Clubs and Socs */ pub mod cns { - use crate::common::database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers}; - use crate::common::wolves::{add_users_wolves, WolvesResultUserMin}; - use crate::Config; - use serenity::client::Context; - use serenity::model::id::GuildId; + use crate::{ + common::{ + database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers}, + wolves::{add_users_wolves, WolvesResultUserMin}, + }, + Config, + }; + use serenity::{client::Context, model::id::GuildId}; use sqlx::{Pool, Sqlite}; use std::collections::BTreeMap; @@ -191,8 +194,7 @@ pub mod cns { Get and store the data on C&S committees */ pub mod committees { - use crate::common::database::DataBase; - use crate::Config; + use crate::{common::database::DataBase, Config}; use serenity::client::Context; use sqlx::{Pool, Sqlite}; diff --git a/src/lib.rs b/src/lib.rs index 734fa2c..75c9d78 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,8 +3,10 @@ pub mod common; use chrono::{Datelike, SecondsFormat, Utc}; use dotenvy::dotenv; use rand::{distr::Alphanumeric, rng, Rng}; -use serenity::model::id::{ChannelId, GuildId, RoleId}; -use serenity::prelude::TypeMapKey; +use serenity::{ + model::id::{ChannelId, GuildId, RoleId}, + prelude::TypeMapKey, +}; use std::{env, sync::Arc}; use tokio::sync::RwLock; diff --git a/src/main.rs b/src/main.rs index e76bd34..c0bddb3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,39 +1,33 @@ pub mod commands; use crate::commands::role_adder::tools::on_role_change; -use crate::commands::server_icon::user::current::icon::get_current_icon; -use chrono::{Days, SecondsFormat, TimeDelta, Utc}; -use serenity::all::{ - ActivityData, Command, CommandDataOptionValue, CreateAttachment, CreateMessage, EditInteractionResponse, GuildId, GuildMemberUpdateEvent, - GuildMembersChunkEvent, Interaction, -}; use serenity::{ + all::{Command, CommandDataOptionValue, CreateMessage, EditInteractionResponse, Interaction}, async_trait, client::{Context, EventHandler}, - gateway::ChunkGuildFilter, + gateway::{ActivityData, ChunkGuildFilter}, model::{ + event::GuildMemberUpdateEvent, gateway::{GatewayIntents, Ready}, guild::Member, + id::GuildId, user::OnlineStatus, }, Client, }; -use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_config_bulk, get_server_member, DataBase}; -use skynet_discord_bot::common::server_icon::{get_config_icons, update_icon}; -use skynet_discord_bot::common::set_roles::committee::update_committees; -use skynet_discord_bot::common::set_roles::{committee, normal}; -use skynet_discord_bot::common::wolves::cns::get_wolves; -use skynet_discord_bot::common::wolves::committees::{get_cns, Committees}; -use skynet_discord_bot::{get_config, Config}; +use skynet_discord_bot::{ + common::{ + database::{db_init, get_server_config, get_server_member, DataBase}, + set_roles::committee::update_committees, + wolves::committees::Committees, + }, + get_config, Config, +}; use sqlx::{Pool, Sqlite}; -use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use std::time::Duration; use tokio::sync::RwLock; -struct Handler { - is_loop_running: AtomicBool, -} +struct Handler; #[async_trait] impl EventHandler for Handler { @@ -301,9 +295,7 @@ async fn main() { let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; // Build our client. let mut client = Client::builder(&config.discord_token, intents) - .event_handler(Handler { - is_loop_running: AtomicBool::new(false), - }) + .event_handler(Handler) .cache_settings(serenity::cache::Settings::default()) .await .expect("Error creating client"); From d0726169ee106d2da49f3960a82c86ebb481290b Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 21 Jul 2025 00:50:20 +0100 Subject: [PATCH 164/170] clippy: changes from nightly clippy all of this is embeding teh var into teh format macro --- src/bin/cleanup_committee.rs | 2 +- src/bin/update_committee.rs | 2 +- src/bin/update_data.rs | 2 +- src/bin/update_server-icon.rs | 2 +- src/bin/update_users.rs | 2 +- src/commands/add_server.rs | 4 ++-- src/commands/count.rs | 4 ++-- src/commands/minecraft.rs | 8 ++++---- src/commands/role_adder.rs | 12 ++++++------ src/commands/server_icon.rs | 2 +- src/commands/wolves.rs | 10 +++++----- src/common/database.rs | 2 +- src/common/minecraft.rs | 2 +- src/common/renderer.rs | 2 +- src/common/set_roles.rs | 14 +++++++------- src/common/wolves.rs | 12 ++++++------ src/main.rs | 12 ++++++------ 17 files changed, 47 insertions(+), 47 deletions(-) diff --git a/src/bin/cleanup_committee.rs b/src/bin/cleanup_committee.rs index 76f0526..c6485a7 100644 --- a/src/bin/cleanup_committee.rs +++ b/src/bin/cleanup_committee.rs @@ -45,7 +45,7 @@ async fn main() { } if let Err(why) = client.start().await { - println!("Client error: {:?}", why); + println!("Client error: {why:?}"); } } diff --git a/src/bin/update_committee.rs b/src/bin/update_committee.rs index ff263f6..b2792e0 100644 --- a/src/bin/update_committee.rs +++ b/src/bin/update_committee.rs @@ -40,7 +40,7 @@ async fn main() { } if let Err(why) = client.start().await { - println!("Client error: {:?}", why); + println!("Client error: {why:?}"); } } diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index 902920e..8f73ce9 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -42,7 +42,7 @@ async fn main() { } if let Err(why) = client.start().await { - println!("Client error: {:?}", why); + println!("Client error: {why:?}"); } } diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index ba9de80..56957da 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -39,7 +39,7 @@ async fn main() { } if let Err(why) = client.start().await { - println!("Client error: {:?}", why); + println!("Client error: {why:?}"); } } diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index c146a25..6ac9e00 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -40,7 +40,7 @@ async fn main() { } if let Err(why) = client.start().await { - println!("Client error: {:?}", why); + println!("Client error: {why:?}"); } } diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index 63f3d49..0c7dd2f 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -76,8 +76,8 @@ pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { match add_server(&db, ctx, &server_data).await { Ok(_) => {} Err(e) => { - println!("{:?}", e); - return format!("Failure to insert into Servers {:?}", server_data); + println!("{e:?}"); + return format!("Failure to insert into Servers {server_data:?}"); } } diff --git a/src/commands/count.rs b/src/commands/count.rs index 2bed533..8a64ee7 100644 --- a/src/commands/count.rs +++ b/src/commands/count.rs @@ -52,7 +52,7 @@ pub mod committee { for (count, name) in cs { let leading = if count < 10 { " " } else { "" }; - let line = format!("{}{} {}", leading, count, name); + let line = format!("{leading}{count} {name}"); let length = line.len() + 1; @@ -146,7 +146,7 @@ pub mod servers { "" }; - let line = format!("{}{} {}{} {}", current_leading, current, past_leading, past, name); + let line = format!("{current_leading}{current} {past_leading}{past} {name}"); let length = line.len() + 1; diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index f3a050d..a4d1b1b 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -75,14 +75,14 @@ pub(crate) mod user { Ok(_) => {} Err(e) => { dbg!("{:?}", e); - return format!("Failure to minecraft username {:?}", username); + return format!("Failure to minecraft username {username:?}"); } } username_mc = username.to_string(); } else { match get_minecraft_bedrock(username, &config.minecraft_mcprofile).await { None => { - return format!("No UID found for {:?}", username); + return format!("No UID found for {username:?}"); } Some(x) => { match add_minecraft_bedrock(&db, &command.user.id, &x.floodgateuid).await { @@ -238,7 +238,7 @@ pub(crate) mod server { match add_server(&db, &g_id, &server_minecraft).await { Ok(_) => {} Err(e) => { - println!("{:?}", e); + println!("{e:?}"); return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft); } } @@ -375,7 +375,7 @@ pub(crate) mod server { match server_remove(&db, &g_id, &server_minecraft).await { Ok(_) => {} Err(e) => { - println!("{:?}", e); + println!("{e:?}"); return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft); } } diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index 20928ba..e60ac83 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -79,8 +79,8 @@ pub mod edit { match add_server(&db, &server_data, delete).await { Ok(_) => {} Err(e) => { - println!("{:?}", e); - return format!("Failure to insert into Servers {:?}", server_data); + println!("{e:?}"); + return format!("Failure to insert into Servers {server_data:?}"); } } @@ -101,9 +101,9 @@ pub mod edit { } if delete { - format!("Removed {} + {} = {}", role_a_name, role_b_name, role_c_name) + format!("Removed {role_a_name} + {role_b_name} = {role_c_name}") } else { - format!("Added {} + {} = {}", role_a_name, role_b_name, role_c_name) + format!("Added {role_a_name} + {role_b_name} = {role_c_name}") } } @@ -179,13 +179,13 @@ pub mod tools { if !roles_add.is_empty() { if let Err(e) = new_data.add_roles(&ctx, &roles_add).await { - println!("{:?}", e); + println!("{e:?}"); } } if !roles_remove.is_empty() { if let Err(e) = new_data.remove_roles(&ctx, &roles_remove).await { - println!("{:?}", e); + println!("{e:?}"); } } } diff --git a/src/commands/server_icon.rs b/src/commands/server_icon.rs index 2989c42..0cba1b1 100644 --- a/src/commands/server_icon.rs +++ b/src/commands/server_icon.rs @@ -207,7 +207,7 @@ pub(crate) mod user { // the `` is so that the numbers will be rendered in monospaced font // the <> is to suppress the URL embed - let line = format!("``{}{}`` [{}](<{}>)", current_leading, times, name, url); + let line = format!("``{current_leading}{times}`` [{name}](<{url}>)"); let length = line.len() + 1; diff --git a/src/commands/wolves.rs b/src/commands/wolves.rs index f137622..dc40ad0 100644 --- a/src/commands/wolves.rs +++ b/src/commands/wolves.rs @@ -115,7 +115,7 @@ pub mod link { } } - format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email) + format!("Verification email sent to {email}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.") } pub async fn get_server_member_discord(db: &Pool, user: &UserId) -> Option { @@ -365,12 +365,12 @@ pub mod verify { "Discord username linked to Wolves".to_string() } Err(e) => { - println!("{:?}", e); + println!("{e:?}"); "Failed to save, please try /link_wolves again".to_string() } }; } - Err(e) => println!("{:?}", e), + Err(e) => println!("{e:?}"), } "Failed to verify".to_string() @@ -427,7 +427,7 @@ pub mod verify { } if let Err(e) = member.add_roles(&ctx, &roles).await { - println!("{:?}", e); + println!("{e:?}"); } } } @@ -443,7 +443,7 @@ pub mod verify { WHERE committee LIKE ?1 "#, ) - .bind(format!("%{}%", wolves_id)) + .bind(format!("%{wolves_id}%")) .fetch_all(db) .await .unwrap_or_else(|e| { diff --git a/src/common/database.rs b/src/common/database.rs index 190baf2..0c18668 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -225,7 +225,7 @@ pub async fn db_init(config: &Config) -> Result, Error> { let pool = SqlitePoolOptions::new() .max_connections(5) .connect_with( - SqliteConnectOptions::from_str(&format!("sqlite://{}", database))? + SqliteConnectOptions::from_str(&format!("sqlite://{database}"))? .foreign_keys(true) .create_if_missing(true), ) diff --git a/src/common/minecraft.rs b/src/common/minecraft.rs index b7362e6..60f7820 100644 --- a/src/common/minecraft.rs +++ b/src/common/minecraft.rs @@ -152,7 +152,7 @@ pub async fn get_minecraft_config_server(db: &Pool, g_id: GuildId) -> Ve } pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &str) { - println!("Update whitelist for {}", server); + println!("Update whitelist for {server}"); let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}"); let bearer = format!("Bearer {token}"); diff --git a/src/common/renderer.rs b/src/common/renderer.rs index e105c16..97e73a0 100644 --- a/src/common/renderer.rs +++ b/src/common/renderer.rs @@ -179,7 +179,7 @@ impl Renderer { } fn set_color(&self, svg: &str, color: &String) -> String { - svg.replace("fill=\"currentColor\"", &format!("fill=\"#{}\"", color)) + svg.replace("fill=\"currentColor\"", &format!("fill=\"#{color}\"")) } fn get_svg_data(&self, fi: &Path) -> Result { diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 3a894ee..6352c24 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -71,7 +71,7 @@ pub mod normal { } if let Err(e) = member.add_roles(ctx, &roles).await { - println!("{:?}", e); + println!("{e:?}"); } } else { // old and never @@ -86,13 +86,13 @@ pub mod normal { roles_set.current_rem += 1; // if theya re not a current member and have the role then remove it if let Err(e) = member.remove_role(ctx, role_current).await { - println!("{:?}", e); + println!("{e:?}"); } } } for role in remove_roles.iter().flatten() { if let Err(e) = member.remove_role(ctx, role).await { - println!("{:?}", e); + println!("{e:?}"); } } } @@ -148,7 +148,7 @@ pub mod normal { Ok(_) => {} Err(e) => { println!("Failure to insert into {}", server.get()); - println!("{:?}", e); + println!("{e:?}"); } } } @@ -471,8 +471,8 @@ pub mod committee { { Ok(_) => {} Err(e) => { - println!("Failure to insert into Wolves {:?}", role); - println!("{:?}", e); + println!("Failure to insert into Wolves {role:?}"); + println!("{e:?}"); } } } @@ -489,7 +489,7 @@ pub mod committee { .await .unwrap_or_else(|e| { println!("Failure to get Roles from committee_roles"); - println!("{:?}", e); + println!("{e:?}"); vec![] }) } diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 7ee71df..17305f6 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -38,8 +38,8 @@ async fn add_users_wolves(db: &Pool, user: &WolvesResultUserMin) { { Ok(_) => {} Err(e) => { - println!("Failure to insert into Wolves {:?}", user); - println!("{:?}", e); + println!("Failure to insert into Wolves {user:?}"); + println!("{e:?}"); } } } @@ -145,7 +145,7 @@ pub mod cns { Ok(_) => {} Err(e) => { println!("Failure to set server name {}", server.get()); - println!("{:?}", e); + println!("{e:?}"); } } } @@ -184,7 +184,7 @@ pub mod cns { Ok(_) => {} Err(e) => { println!("Failure to insert into ServerMembers {} {:?}", server.get(), user); - println!("{:?}", e); + println!("{e:?}"); } } } @@ -263,8 +263,8 @@ pub mod committees { { Ok(_) => {} Err(e) => { - println!("Failure to insert into Committees {:?}", committee); - println!("{:?}", e); + println!("Failure to insert into Committees {committee:?}"); + println!("{e:?}"); } } } diff --git a/src/main.rs b/src/main.rs index c0bddb3..6a87f84 100644 --- a/src/main.rs +++ b/src/main.rs @@ -81,7 +81,7 @@ impl EventHandler for Handler { } if let Err(e) = new_member.add_roles(&ctx, &roles).await { - println!("{:?}", e); + println!("{e:?}"); } } else { let tmp = get_committee(&db, config_server.wolves_id).await; @@ -146,7 +146,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use { Ok(_) => {} Err(e) => { - println!("{:?}", e) + println!("{e:?}") } } @@ -154,7 +154,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use match config.committee_server.set_commands(&ctx.http, vec![commands::count::committee::register()]).await { Ok(_) => {} Err(e) => { - println!("{:?}", e) + println!("{e:?}") } } @@ -173,7 +173,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use { Ok(_) => {} Err(e) => { - println!("{:?}", e) + println!("{e:?}") } } } @@ -260,7 +260,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use }; if let Err(why) = command.edit_response(&ctx.http, EditInteractionResponse::new().content(content)).await { - println!("Cannot respond to slash command: {}", why); + println!("Cannot respond to slash command: {why}"); } } } @@ -312,6 +312,6 @@ async fn main() { // Shards will automatically attempt to reconnect, and will perform // exponential backoff until it reconnects. if let Err(why) = client.start().await { - println!("Client error: {:?}", why); + println!("Client error: {why:?}"); } } From 854e946a8fd692e1ed1eaca15b1805d0a90e5d35 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 21 Jul 2025 00:59:48 +0100 Subject: [PATCH 165/170] ci: improve the pipeline to test for the full suite ci: dont pull in the lfs for teh PR pipeline build ci: checkout without LFS ci: see if using the ref directly will help ci: test a slight modification ci: see if new get ldfs script works ci: test using teh new v8 --- .forgejo/workflows/check_lfs.yaml | 2 -- .forgejo/workflows/on_pr.yaml | 51 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 .forgejo/workflows/on_pr.yaml diff --git a/.forgejo/workflows/check_lfs.yaml b/.forgejo/workflows/check_lfs.yaml index dca575c..aaa2117 100644 --- a/.forgejo/workflows/check_lfs.yaml +++ b/.forgejo/workflows/check_lfs.yaml @@ -1,9 +1,7 @@ on: - - pull_request - push - workflow_dispatch - jobs: check_lfs: # nix/docker diff --git a/.forgejo/workflows/on_pr.yaml b/.forgejo/workflows/on_pr.yaml new file mode 100644 index 0000000..d17dc47 --- /dev/null +++ b/.forgejo/workflows/on_pr.yaml @@ -0,0 +1,51 @@ +on: + - pull_request + +jobs: + check_lfs: + # nix/docker + runs-on: nix + steps: + - uses: https://github.com/MPLew-is/lfs-check-action@1 + + # rust code must be formatted for standardisation + lint_fmt: + # build it using teh base nixos system, helps with caching + runs-on: nix + steps: + # get the repo first + - uses: https://code.forgejo.org/actions/checkout@v4 + - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs/nix@v8 + with: + server_url: ${{ gitea.server_url }} + repository: ${{ gitea.repository }} + - run: nix build .#fmt --verbose + + # clippy is incredibly useful for making yer code better + lint_clippy: + # build it using teh base nixos system, helps with caching + runs-on: nix + permissions: + checks: write + steps: + # get the repo first + - uses: https://code.forgejo.org/actions/checkout@v4 + - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs/nix@v8 + with: + server_url: ${{ gitea.server_url }} + repository: ${{ gitea.repository }} + - run: nix build .#clippy --verbose + + build: + # build it using teh base nixos system, helps with caching + runs-on: nix + needs: [ lint_fmt, lint_clippy ] + steps: + # get the repo first + - uses: https://code.forgejo.org/actions/checkout@v4 + - uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs/nix@v8 + with: + server_url: ${{ gitea.server_url }} + repository: ${{ gitea.repository }} + - name: "Build it locally" + run: nix build --verbose From 095ff6f2cea5b3d35880d4a55b60930b60e3efcb Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 21 Jul 2025 02:51:33 +0100 Subject: [PATCH 166/170] fix: better handling of returning teh committees --- src/commands/count.rs | 6 ++++-- src/common/set_roles.rs | 26 +++++++++++++++++++------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/commands/count.rs b/src/commands/count.rs index 8a64ee7..2368bca 100644 --- a/src/commands/count.rs +++ b/src/commands/count.rs @@ -102,8 +102,10 @@ pub mod servers { let db = db_lock.read().await; let mut committees = HashMap::new(); - for committee in get_committees(&db).await { - committees.insert(committee.id, committee.to_owned()); + if let Some(x) = get_committees(&db).await { + for committee in x { + committees.insert(committee.id, committee.to_owned()); + } } let mut cs = vec![]; diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 6352c24..6e71992 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -201,7 +201,12 @@ pub mod committee { pub async fn update_committees(db: &Pool, ctx: &Context, config: &Config, members: &mut Vec) { let server = config.committee_server; let committee_member = config.committee_role; - let committees = get_committees(db).await; + let committees = match get_committees(db).await { + None => { + return; + } + Some(x) => x, + }; let categories = config.committee_category.clone(); // information about the server @@ -328,6 +333,10 @@ pub mod committee { // now we have a map of all users that should get roles time to go through all the folks on teh server for member in members { + // if member.user.id != 136522490632601600 { + // continue; + // } + // let roles_current = member.roles(ctx).unwrap_or_default(); let roles_required = match users_roles.get(&member.user.id) { @@ -494,8 +503,8 @@ pub mod committee { }) } - pub async fn get_committees(db: &Pool) -> Vec { - sqlx::query_as::<_, Committees>( + pub async fn get_committees(db: &Pool) -> Option> { + match sqlx::query_as::<_, Committees>( r#" SELECT * FROM committees @@ -503,10 +512,13 @@ pub mod committee { ) .fetch_all(db) .await - .unwrap_or_else(|e| { - dbg!(e); - vec![] - }) + { + Ok(x) => Some(x), + Err(e) => { + dbg!(e); + None + } + } } async fn get_server_member_discord(db: &Pool, user: &i64) -> Option { From a225c14b4fdaafe390dc8840065946d8c2a2e4b8 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Mon, 21 Jul 2025 04:29:03 +0100 Subject: [PATCH 167/170] fix: was ddossing the poor database guild_members_chunk is triggered for each chunk for each server it is on, and the bot is currently in 10 servers so it was runnign teh same thigns 10 times, clogging up conenctions --- src/bin/cleanup_committee.rs | 18 +++++++++++++----- src/bin/update_committee.rs | 18 +++++++++++++----- src/bin/update_users.rs | 27 ++++++++++++++++++++------- 3 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/bin/cleanup_committee.rs b/src/bin/cleanup_committee.rs index c6485a7..188cc5b 100644 --- a/src/bin/cleanup_committee.rs +++ b/src/bin/cleanup_committee.rs @@ -52,15 +52,23 @@ async fn main() { struct Handler; #[async_trait] impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, guilds: Vec) { - for guild in guilds { - ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); - } - println!("Cache built successfully!"); + async fn cache_ready(&self, ctx: Context, _guilds: Vec) { + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config_global = config_lock.read().await; + + let server = config_global.committee_server; + + ctx.shard.chunk_guild(server, Some(2000), false, ChunkGuildFilter::None, None); + + println!("Cache loaded"); } async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) { if (chunk.chunk_index + 1) == chunk.chunk_count { + println!("Cache built successfully!"); let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() diff --git a/src/bin/update_committee.rs b/src/bin/update_committee.rs index b2792e0..b8cc6c2 100644 --- a/src/bin/update_committee.rs +++ b/src/bin/update_committee.rs @@ -47,15 +47,23 @@ async fn main() { struct Handler; #[async_trait] impl EventHandler for Handler { - async fn cache_ready(&self, ctx: Context, guilds: Vec) { - for guild in guilds { - ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); - } - println!("Cache built successfully!"); + async fn cache_ready(&self, ctx: Context, _guilds: Vec) { + let config_lock = { + let data_read = ctx.data.read().await; + data_read.get::().expect("Expected Config in TypeMap.").clone() + }; + let config_global = config_lock.read().await; + + let server = config_global.committee_server; + + ctx.shard.chunk_guild(server, Some(2000), false, ChunkGuildFilter::None, None); + + println!("Cache loaded"); } async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) { if (chunk.chunk_index + 1) == chunk.chunk_count { + println!("Cache built successfully!"); // u[date committee server committee::check_committee(&ctx).await; diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index 6ac9e00..0cb11ac 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -12,6 +12,7 @@ use skynet_discord_bot::{ }, get_config, Config, }; +use std::sync::atomic::{AtomicUsize, Ordering}; use std::{process, sync::Arc}; use tokio::sync::RwLock; @@ -27,7 +28,10 @@ async fn main() { let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS; // Build our client. let mut client = Client::builder(&config.discord_token, intents) - .event_handler(Handler {}) + .event_handler(Handler { + server_count: Default::default(), + server_cached: Default::default(), + }) .cache_settings(serenity::cache::Settings::default()) .await .expect("Error creating client"); @@ -44,23 +48,32 @@ async fn main() { } } -struct Handler; +struct Handler { + server_count: AtomicUsize, + server_cached: AtomicUsize, +} #[async_trait] impl EventHandler for Handler { async fn cache_ready(&self, ctx: Context, guilds: Vec) { + self.server_count.swap(guilds.len(), Ordering::SeqCst); for guild in guilds { ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None); } - println!("Cache built successfully!"); + println!("Cache loaded {}", &self.server_count.load(Ordering::SeqCst)); } async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) { if (chunk.chunk_index + 1) == chunk.chunk_count { - // this goes into each server and sets roles for each wolves member - check_bulk(&ctx).await; + self.server_cached.fetch_add(1, Ordering::SeqCst); + if (self.server_cached.load(Ordering::SeqCst) + 1) == self.server_count.load(Ordering::SeqCst) { + println!("Cache built successfully!"); - // finish up - process::exit(0); + // this goes into each server and sets roles for each wolves member + check_bulk(&ctx).await; + + // finish up + process::exit(0); + } } } } From 061b73378a9484478226559f4a70cdb6721e7e13 Mon Sep 17 00:00:00 2001 From: silver Date: Sun, 31 Aug 2025 11:51:57 +0000 Subject: [PATCH 168/170] Bump the bot --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index 6a87f84..aecabec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ use sqlx::{Pool, Sqlite}; use std::sync::Arc; use tokio::sync::RwLock; +// Need To Define The Stuct (Committed To Bump The Bot) struct Handler; #[async_trait] From 3149a5f99fc927397fec18840ceb9cbe54d65ce0 Mon Sep 17 00:00:00 2001 From: silver Date: Sun, 31 Aug 2025 11:55:35 +0000 Subject: [PATCH 169/170] Bump 2 --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index aecabec..bbf389f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ use sqlx::{Pool, Sqlite}; use std::sync::Arc; use tokio::sync::RwLock; -// Need To Define The Stuct (Committed To Bump The Bot) +// Need To Define The Stuct (Committed To Bump The Bot) struct Handler; #[async_trait] From 7526a82bb7c384c16a09992458868b2f669ed279 Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Thu, 4 Sep 2025 23:11:24 +0100 Subject: [PATCH 170/170] feat: kill the service if it persists longer than 9 min --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index 81f4490..3a6032f 100644 --- a/flake.nix +++ b/flake.nix @@ -128,6 +128,8 @@ User = "${cfg.user}"; Group = "${cfg.user}"; ExecStart = "${self.defaultPackage."${system}"}/bin/${script}"; + # kill each service if its ran for 9 min + TimeoutStartSec=540; EnvironmentFile = [ "${cfg.env.discord}" "${cfg.env.mail}"