From 0eba54b6f247e472d188837a2daaa0b44d90637e Mon Sep 17 00:00:00 2001 From: Brendan Golden Date: Wed, 19 Feb 2025 12:29:53 +0000 Subject: [PATCH 001/100] 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 002/100] 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 003/100] 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 004/100] 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 005/100] 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 006/100] 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 007/100] 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 008/100] 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 009/100] 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 010/100] 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 011/100] 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 012/100] 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 013/100] 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 014/100] 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 015/100] 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 016/100] 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 017/100] 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 018/100] 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 019/100] 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 020/100] 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 021/100] 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 022/100] 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 023/100] 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 024/100] 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 025/100] 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 026/100] 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 027/100] 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 028/100] 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 029/100] 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 030/100] 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 031/100] 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 032/100] 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 033/100] 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 034/100] 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 035/100] 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 036/100] 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 037/100] 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 038/100] 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 039/100] 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 040/100] 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 041/100] 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 042/100] 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 043/100] 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 044/100] 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 045/100] 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 046/100] 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 047/100] 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 048/100] 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 049/100] 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 050/100] 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 051/100] 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 052/100] 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 053/100] 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 054/100] 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 055/100] 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 056/100] 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 057/100] 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 058/100] 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 059/100] 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 060/100] 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 061/100] 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 062/100] 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 063/100] 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 064/100] 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 065/100] 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 066/100] 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 067/100] 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 068/100] 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 069/100] 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 070/100] 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 071/100] 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 072/100] 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 073/100] 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 074/100] 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 075/100] 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 076/100] 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 077/100] 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 078/100] 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 079/100] 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 080/100] 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 081/100] 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 082/100] 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 083/100] 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 084/100] 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 085/100] 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 086/100] 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 087/100] 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 088/100] 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 089/100] 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 090/100] 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 091/100] 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 092/100] 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 093/100] 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 094/100] 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 095/100] 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 096/100] 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 097/100] 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}" From 7e90f451965b0edbd331765ad295a02f31d2bf24 Mon Sep 17 00:00:00 2001 From: Roman Moisieiev Date: Thu, 11 Sep 2025 12:35:50 +0100 Subject: [PATCH 098/100] Fix typos --- .taplo.toml | 2 ++ src/bin/cleanup_committee.rs | 8 ++++---- src/commands/add_server.rs | 2 +- src/commands/count.rs | 2 +- src/commands/minecraft.rs | 2 +- src/commands/role_adder.rs | 4 ++-- src/commands/server_icon.rs | 2 +- src/commands/wolves.rs | 8 ++++---- src/common/minecraft.rs | 4 ++-- src/common/renderer.rs | 5 ++--- src/common/server_icon.rs | 2 +- src/common/set_roles.rs | 12 ++++++------ src/lib.rs | 2 +- src/main.rs | 9 ++++----- 14 files changed, 32 insertions(+), 32 deletions(-) create mode 100644 .taplo.toml diff --git a/.taplo.toml b/.taplo.toml new file mode 100644 index 0000000..3b409e9 --- /dev/null +++ b/.taplo.toml @@ -0,0 +1,2 @@ +[formatting] +column_width = 120 diff --git a/src/bin/cleanup_committee.rs b/src/bin/cleanup_committee.rs index 188cc5b..9a01f4c 100644 --- a/src/bin/cleanup_committee.rs +++ b/src/bin/cleanup_committee.rs @@ -18,8 +18,8 @@ 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. +/// This removes any invalid roles/channels which have been set up accidentally +/// DO NOT run this locally unless you have a fresh copy of the live database handy. #[tokio::main] async fn main() { let config = get_config(); @@ -98,7 +98,7 @@ async fn cleanup(db: &Pool, ctx: &Context, config: &Config) { 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 + // if there are no committees which match then this is not a channel we care about None => { continue; } @@ -120,7 +120,7 @@ async fn cleanup(db: &Pool, ctx: &Context, config: &Config) { 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 + // if there are no committees which match then this is not a channel we care about None => { continue; } diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index 0c7dd2f..4511c1b 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -102,7 +102,7 @@ async fn add_server(db: &Pool, ctx: &Context, server: &Servers) -> Resul .fetch_optional(db) .await; - // if the entry does not exist already tehn do a user update + // if the entry does not exist already then do a user update let (update, current_remove, current_role, past_remove, past_role) = match &existing { None => (true, false, None, false, None), Some(x) => { diff --git a/src/commands/count.rs b/src/commands/count.rs index 2368bca..d354db0 100644 --- a/src/commands/count.rs +++ b/src/commands/count.rs @@ -152,7 +152,7 @@ pub mod servers { let length = line.len() + 1; - // +3 is to account for the closing fense + // +3 is to account for the closing fence if length < (limit + 3) { response.push(line); limit -= length; diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index a4d1b1b..61f4280 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -196,7 +196,7 @@ pub(crate) mod server { model::id::GuildId, }; use sqlx::Error; - // this is to managfe the server side of commands related to minecraft + // this is to manage the server side of commands related to minecraft use super::*; use skynet_discord_bot::{ common::minecraft::{update_server, Minecraft}, diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index e60ac83..7f59c08 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -147,7 +147,7 @@ pub mod tools { use sqlx::{Pool, Sqlite}; 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 + // check if the role changed is part of the ones for this server if let Ok(role_adders) = sqlx::query_as::<_, RoleAdder>( r#" SELECT * @@ -163,7 +163,7 @@ pub mod tools { let mut roles_remove = vec![]; for role_adder in role_adders { - // if the user has both A dnd B give them C + // if the user has both A and 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); diff --git a/src/commands/server_icon.rs b/src/commands/server_icon.rs index 0cba1b1..2ab087e 100644 --- a/src/commands/server_icon.rs +++ b/src/commands/server_icon.rs @@ -211,7 +211,7 @@ pub(crate) mod user { let length = line.len() + 1; - // +3 is to account for the closing fense + // +3 is to account for the closing fence if length < (limit + 3) { response.push(line); limit -= length; diff --git a/src/commands/wolves.rs b/src/commands/wolves.rs index dc40ad0..0c8ddfc 100644 --- a/src/commands/wolves.rs +++ b/src/commands/wolves.rs @@ -101,7 +101,7 @@ pub mod link { return "Email already verified".to_string(); } - // generate a auth key + // generate an 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 { @@ -210,7 +210,7 @@ pub mod link { .subject("Skynet: Link Discord to Wolves.") .multipart( // This is composed of two parts. - // also helps not trip spam settings (uneven number of url's + // also helps not trip spam settings (uneven number of urls) 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())), @@ -460,7 +460,7 @@ pub mod verify { 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 + // if they are a member of one or more committees, and in teh committee server then give them the 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 = config.committee_server; @@ -500,7 +500,7 @@ pub mod unlink { }; let db = db_lock.read().await; - // dosent matter if there is one or not, it will be removed regardless + // doesn't matter if there is one or not, it will be removed regardless delete_link(&db, &command.user.id).await; "Discord link removed".to_string() diff --git a/src/common/minecraft.rs b/src/common/minecraft.rs index 60f7820..77cd754 100644 --- a/src/common/minecraft.rs +++ b/src/common/minecraft.rs @@ -24,7 +24,7 @@ impl<'r> FromRow<'r, SqliteRow> for Minecraft { /** loop through all members of server get a list of folks with mc accounts that are members -and a list that arent members +and a list that aren't members */ pub async fn update_server(server_id: &str, db: &Pool, g_id: &GuildId, config: &Config) { let mut usernames = vec![]; @@ -109,7 +109,7 @@ pub async fn whitelist_wipe(server: &str, token: &str) { }; 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 + // recreate the 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 diff --git a/src/common/renderer.rs b/src/common/renderer.rs index 97e73a0..f2c50cc 100644 --- a/src/common/renderer.rs +++ b/src/common/renderer.rs @@ -1,12 +1,11 @@ // 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. +// I was unable to figure out how to use usvg myself so yoinked it from here. use std::{ ffi::OsStr, path::{Path, PathBuf}, }; -// use clap::builder::OsStr; use color_eyre::{eyre::bail, Result}; use usvg_text_layout::TreeTextToPath; @@ -17,7 +16,7 @@ pub struct Args { /// Output folder where the PNG's will be placed pub output: PathBuf, - /// Comma seperated colors that will be used in HEX Eg. 000000,ffffff + /// Comma separated colors that will be used in HEX Eg. 000000,ffffff /// Can be like an object: black:000000,white:ffffff pub colors: String, diff --git a/src/common/server_icon.rs b/src/common/server_icon.rs index d99f265..1a7c2ef 100644 --- a/src/common/server_icon.rs +++ b/src/common/server_icon.rs @@ -281,7 +281,7 @@ pub mod update_icon { // check if exists if !path_new.exists() { - // convert if it hasnt been converted already + // convert if it hasn't been converted already match r.render(&path_local, &args) { Ok(_) => {} Err(_e) => { diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 6e71992..9115986 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -48,7 +48,7 @@ pub mod normal { if let Ok(x) = server.members(ctx, None, None).await { for member in x { - // members_changed acts as an override to only deal with teh users in it + // members_changed acts as an override to only deal with the users in it if !members_changed.is_empty() && !members_changed.contains(&member.user.id) { continue; } @@ -84,7 +84,7 @@ pub mod normal { if member.roles.contains(role_current) { roles_set.current_rem += 1; - // if theya re not a current member and have the role then remove it + // if they'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:?}"); } @@ -196,7 +196,7 @@ pub mod committee { } /** - This function can take a vec of members (or just one) and gives tehm the appropiate roles on teh committee server + This function can take a Vec of members (or just one) and gives them the appropriate roles on teh committee server */ pub async fn update_committees(db: &Pool, ctx: &Context, config: &Config, members: &mut Vec) { let server = config.committee_server; @@ -228,11 +228,11 @@ pub mod committee { let mut channels = server.channels(&ctx).await.unwrap_or_default(); - // a map of users and the roles they are goign to be getting + // a map of users and the roles they are going to be getting let mut users_roles = HashMap::new(); let mut re_order = false; - // we need to create roles and channels if tehy dont already exist + // we need to create roles and channels if they don't already exist let mut category_index = 0; let mut i = 0; loop { @@ -367,7 +367,7 @@ pub mod committee { 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 + // if there are committee roles then give the general purpose role roles_add.push(committee_member); } if !on_committee && has_committee_role { diff --git a/src/lib.rs b/src/lib.rs index 75c9d78..1a6afb1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,7 @@ pub struct Config { pub committee_role: RoleId, pub committee_category: Vec, - // items pertaining to compsoc only + // items pertaining to CompSoc only pub compsoc_server: GuildId, } impl TypeMapKey for Config { diff --git a/src/main.rs b/src/main.rs index bbf389f..aeebac7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,6 @@ 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] @@ -117,7 +116,7 @@ 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 + // check if the role changed is part of the ones for this server if let Some(x) = new_data { on_role_change(&db, &ctx, x).await; } @@ -159,13 +158,13 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use } } - // compsoc Server + // CompSoc Server match config .compsoc_server .set_commands( &ctx.http, vec![ - // commands just for the compsoc server + // commands just for the CompSoc server commands::count::servers::register(), commands::server_icon::user::register(), ], @@ -182,7 +181,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::Command(command) = interaction { let _ = command.defer_ephemeral(&ctx.http).await; - //println!("Received command interaction: {:#?}", command); + // println!("Received command interaction: {:#?}", command); let content = match command.data.name.as_str() { // user commands From d70a037057c6209b2340b22a749359a16f7d164a Mon Sep 17 00:00:00 2001 From: Roman Moisieiev Date: Thu, 11 Sep 2025 12:36:48 +0100 Subject: [PATCH 099/100] Blame: ignore Fix Typos commit --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .git-blame-ignore-revs diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 0000000..a338a00 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Fix typos +7e90f451965b0edbd331765ad295a02f31d2bf24 From 062f826d28b4d385423656b10cca84b3644c8e60 Mon Sep 17 00:00:00 2001 From: Roman Moisieiev Date: Thu, 11 Sep 2025 12:54:54 +0100 Subject: [PATCH 100/100] Remove RwLock for database --- src/bin/cleanup_committee.rs | 6 ++---- src/bin/update_committee.rs | 2 +- src/bin/update_data.rs | 2 +- src/bin/update_server-icon.rs | 5 ++--- src/bin/update_users.rs | 15 +++++++++------ src/commands/add_server.rs | 3 +-- src/commands/count.rs | 6 ++---- src/commands/minecraft.rs | 16 ++++++---------- src/commands/role_adder.rs | 3 +-- src/commands/server_icon.rs | 9 +++------ src/commands/wolves.rs | 9 +++------ src/common/database.rs | 3 +-- src/common/set_roles.rs | 8 ++------ src/common/wolves.rs | 6 ++---- src/main.rs | 10 +++------- 15 files changed, 39 insertions(+), 64 deletions(-) diff --git a/src/bin/cleanup_committee.rs b/src/bin/cleanup_committee.rs index 9a01f4c..141af89 100644 --- a/src/bin/cleanup_committee.rs +++ b/src/bin/cleanup_committee.rs @@ -41,7 +41,7 @@ async fn main() { let mut data = client.data.write().await; data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(RwLock::new(db))); + data.insert::(Arc::new(db)); } if let Err(why) = client.start().await { @@ -69,13 +69,11 @@ impl EventHandler for Handler { 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 db = { 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() diff --git a/src/bin/update_committee.rs b/src/bin/update_committee.rs index b8cc6c2..d90cbac 100644 --- a/src/bin/update_committee.rs +++ b/src/bin/update_committee.rs @@ -36,7 +36,7 @@ async fn main() { let mut data = client.data.write().await; data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(RwLock::new(db))); + data.insert::(Arc::new(db)); } if let Err(why) = client.start().await { diff --git a/src/bin/update_data.rs b/src/bin/update_data.rs index 8f73ce9..fe4138f 100644 --- a/src/bin/update_data.rs +++ b/src/bin/update_data.rs @@ -38,7 +38,7 @@ async fn main() { let mut data = client.data.write().await; data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(RwLock::new(db))); + data.insert::(Arc::new(db)); } if let Err(why) = client.start().await { diff --git a/src/bin/update_server-icon.rs b/src/bin/update_server-icon.rs index 56957da..c4f9eca 100644 --- a/src/bin/update_server-icon.rs +++ b/src/bin/update_server-icon.rs @@ -35,7 +35,7 @@ async fn main() { let mut data = client.data.write().await; data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(RwLock::new(db))); + data.insert::(Arc::new(db)); } if let Err(why) = client.start().await { @@ -50,11 +50,10 @@ impl EventHandler for Handler { let ctx = Arc::new(ctx); println!("{} is connected!", ready.user.name); - let db_lock = { + let db = { 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; diff --git a/src/bin/update_users.rs b/src/bin/update_users.rs index 0cb11ac..3150bcf 100644 --- a/src/bin/update_users.rs +++ b/src/bin/update_users.rs @@ -12,8 +12,13 @@ use skynet_discord_bot::{ }, get_config, Config, }; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::{process, sync::Arc}; +use std::{ + process, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, +}; use tokio::sync::RwLock; #[tokio::main] @@ -40,7 +45,7 @@ async fn main() { let mut data = client.data.write().await; data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(RwLock::new(db))); + data.insert::(Arc::new(db)); } if let Err(why) = client.start().await { @@ -79,13 +84,11 @@ impl EventHandler for Handler { } async fn check_bulk(ctx: &Context) { - let db_lock = { + let db = { 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; } diff --git a/src/commands/add_server.rs b/src/commands/add_server.rs index 4511c1b..eaf0971 100644 --- a/src/commands/add_server.rs +++ b/src/commands/add_server.rs @@ -56,11 +56,10 @@ pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { return "Please provide a valid channel for ``Bot Channel``".to_string(); }; - let db_lock = { + let db = { 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 = Servers { server: command.guild_id.unwrap_or_default(), diff --git a/src/commands/count.rs b/src/commands/count.rs index d354db0..41b9d81 100644 --- a/src/commands/count.rs +++ b/src/commands/count.rs @@ -27,11 +27,10 @@ pub mod committee { false }; - let db_lock = { + let db = { 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 @@ -95,11 +94,10 @@ pub mod servers { use std::collections::HashMap; pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String { - let db_lock = { + let db = { 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(); if let Some(x) = get_committees(&db).await { diff --git a/src/commands/minecraft.rs b/src/commands/minecraft.rs index 61f4280..5098f11 100644 --- a/src/commands/minecraft.rs +++ b/src/commands/minecraft.rs @@ -23,11 +23,10 @@ pub(crate) mod user { use sqlx::Error; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db_lock = { + let db = { 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; @@ -229,11 +228,10 @@ pub(crate) mod server { return String::from("Expected Server ID"); }; - let db_lock = { + let db = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; - let db = db_lock.read().await; match add_server(&db, &g_id, &server_minecraft).await { Ok(_) => {} @@ -290,11 +288,10 @@ pub(crate) mod server { Some(x) => x, }; - let db_lock = { + let db = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; - let db = db_lock.read().await; let servers = get_minecraft_config_server(&db, g_id).await; @@ -366,11 +363,10 @@ pub(crate) mod server { return String::from("Expected Server ID"); }; - let db_lock = { - let data_read = ctx.data.read().await; - data_read.get::().expect("Expected Databse in TypeMap.").clone() + let db = { + let data = ctx.data.read().await; + data.get::().expect("Expected Databse in TypeMap.").clone() }; - let db = db_lock.read().await; match server_remove(&db, &g_id, &server_minecraft).await { Ok(_) => {} diff --git a/src/commands/role_adder.rs b/src/commands/role_adder.rs index 7f59c08..0573b37 100644 --- a/src/commands/role_adder.rs +++ b/src/commands/role_adder.rs @@ -62,11 +62,10 @@ pub mod edit { false }; - let db_lock = { + let db = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; - let db = db_lock.read().await; let server = command.guild_id.unwrap_or_default(); let server_data = RoleAdder { diff --git a/src/commands/server_icon.rs b/src/commands/server_icon.rs index 2ab087e..d4a78d5 100644 --- a/src/commands/server_icon.rs +++ b/src/commands/server_icon.rs @@ -18,11 +18,10 @@ pub(crate) mod admin { use super::*; pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String { - let db_lock = { + let db = { 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; @@ -69,11 +68,10 @@ pub(crate) mod user { use sqlx::{Pool, Sqlite}; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db_lock = { + let db = { 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(); @@ -145,11 +143,10 @@ pub(crate) mod user { use sqlx::{Pool, Sqlite}; pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String { - let db_lock = { + let db = { 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(); diff --git a/src/commands/wolves.rs b/src/commands/wolves.rs index 0c8ddfc..843ca70 100644 --- a/src/commands/wolves.rs +++ b/src/commands/wolves.rs @@ -21,11 +21,10 @@ pub mod link { use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction}; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db_lock = { + let db = { 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; @@ -314,11 +313,10 @@ pub mod verify { use sqlx::Error; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db_lock = { + let db = { 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 { @@ -494,11 +492,10 @@ pub mod unlink { use sqlx::{Pool, Sqlite}; pub async fn run(command: &CommandInteraction, ctx: &Context) -> String { - let db_lock = { + let db = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Databse in TypeMap.").clone() }; - let db = db_lock.read().await; // doesn't matter if there is one or not, it will be removed regardless delete_link(&db, &command.user.id).await; diff --git a/src/common/database.rs b/src/common/database.rs index 0c18668..2eaf5df 100644 --- a/src/common/database.rs +++ b/src/common/database.rs @@ -12,11 +12,10 @@ use sqlx::{ Error, FromRow, Pool, Row, Sqlite, }; use std::{str::FromStr, sync::Arc}; -use tokio::sync::RwLock; pub struct DataBase; impl TypeMapKey for DataBase { - type Value = Arc>>; + type Value = Arc>; } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/src/common/set_roles.rs b/src/common/set_roles.rs index 9115986..e11c637 100644 --- a/src/common/set_roles.rs +++ b/src/common/set_roles.rs @@ -17,13 +17,11 @@ pub mod normal { } pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option], members_changed: &[UserId]) { - let db_lock = { + let db = { 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, @@ -174,13 +172,11 @@ pub mod committee { use std::collections::HashMap; pub async fn check_committee(ctx: &Context) { - let db_lock = { + let db = { 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() diff --git a/src/common/wolves.rs b/src/common/wolves.rs index 17305f6..3744558 100644 --- a/src/common/wolves.rs +++ b/src/common/wolves.rs @@ -69,11 +69,10 @@ pub mod cns { } pub async fn get_wolves(ctx: &Context) { - let db_lock = { + let db = { 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; @@ -224,11 +223,10 @@ pub mod committees { } pub async fn get_cns(ctx: &Context) { - let db_lock = { + let db = { 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; diff --git a/src/main.rs b/src/main.rs index aeebac7..8dff675 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,13 +41,11 @@ impl EventHandler for Handler { // handles previously linked accounts joining the server async fn guild_member_addition(&self, ctx: Context, new_member: Member) { - let db_lock = { + let db = { 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() @@ -109,13 +107,11 @@ 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: Option, _: GuildMemberUpdateEvent) { // get config/db - let db_lock = { + let db = { 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 ones for this server if let Some(x) = new_data { on_role_change(&db, &ctx, x).await; @@ -304,7 +300,7 @@ async fn main() { let mut data = client.data.write().await; data.insert::(Arc::new(RwLock::new(config))); - data.insert::(Arc::new(RwLock::new(db))); + data.insert::(Arc::new(db)); } // Finally, start a single shard, and start listening to events.