pub mod common; use dotenvy::dotenv; use serde::{Deserialize, Serialize}; use serenity::{ model::id::{GuildId, RoleId}, prelude::TypeMapKey, }; use crate::set_roles::get_server_member_bulk; use chrono::{Datelike, SecondsFormat, Utc}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use serde::de::DeserializeOwned; use serenity::client::Context; use serenity::model::id::UserId; use serenity::model::prelude::application_command::ApplicationCommandInteraction; use sqlx::{ Pool, Sqlite, }; use std::{env, sync::Arc}; use tokio::sync::RwLock; use common::database::{ServerMembersWolves, Servers}; pub struct Config { // manages where teh database is stored pub home: String, pub database: String, // tokens for discord and other API's pub discord_token: String, pub discord_token_minecraft: String, // email settings pub mail_smtp: String, pub mail_user: String, pub mail_pass: String, // wolves API base for clubs/socs pub wolves_url: String, } impl TypeMapKey for Config { type Value = Arc>; } pub fn get_config() -> Config { dotenv().ok(); // reasonable defaults let mut config = Config { discord_token: "".to_string(), discord_token_minecraft: "".to_string(), home: ".".to_string(), database: "database.db".to_string(), mail_smtp: "".to_string(), mail_user: "".to_string(), mail_pass: "".to_string(), wolves_url: "".to_string(), }; if let Ok(x) = env::var("DATABASE_HOME") { config.home = x.trim().to_string(); } if let Ok(x) = env::var("DATABASE") { config.database = x.trim().to_string(); } if let Ok(x) = env::var("DISCORD_TOKEN") { config.discord_token = x.trim().to_string(); } if let Ok(x) = env::var("DISCORD_TOKEN_MINECRAFT") { config.discord_token_minecraft = x.trim().to_string(); } if let Ok(x) = env::var("EMAIL_SMTP") { config.mail_smtp = x.trim().to_string(); } if let Ok(x) = env::var("EMAIL_USER") { config.mail_user = x.trim().to_string(); } if let Ok(x) = env::var("EMAIL_PASS") { config.mail_pass = x.trim().to_string(); } if let Ok(x) = env::var("WOLVES_URL") { config.wolves_url = x.trim().to_string(); } config } pub fn get_now_iso(short: bool) -> String { let now = Utc::now(); if short { format!("{}-{:02}-{:02}", now.year(), now.month(), now.day()) } else { now.to_rfc3339_opts(SecondsFormat::Millis, true) } } pub fn random_string(len: usize) -> String { thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect() } pub mod set_roles { use crate::common::database::{DataBase, Wolves}; use super::*; pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option], members_changed: &[UserId]) { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Database in TypeMap.").clone() }; let db = db_lock.read().await; let Servers { server, role_past, role_current, .. } = server; let mut roles_set = [0, 0, 0]; let mut members = vec![]; for member in get_server_member_bulk(&db, server).await { if let Some(x) = member.discord { members.push(x); } } let mut members_all = members.len(); if let Ok(x) = server.members(ctx, None, None).await { for mut member in x { // members_changed acts as an override to only deal with teh users in it if !members_changed.is_empty() && !members_changed.contains(&member.user.id) { continue; } if members.contains(&member.user.id) { let mut roles = vec![]; if let Some(role) = &role_past { if !member.roles.contains(role) { roles_set[0] += 1; roles.push(role.to_owned()); } } if !member.roles.contains(role_current) { roles_set[1] += 1; roles.push(role_current.to_owned()); } if let Err(e) = member.add_roles(ctx, &roles).await { println!("{:?}", e); } } else { // old and never if let Some(role) = &role_past { if member.roles.contains(role) { members_all += 1; } } if member.roles.contains(role_current) { roles_set[2] += 1; // if theya re not a current member and have the role then remove it if let Err(e) = member.remove_role(ctx, role_current).await { println!("{:?}", e); } } } for role in remove_roles.iter().flatten() { if let Err(e) = member.remove_role(ctx, role).await { println!("{:?}", e); } } } } set_server_numbers(&db, server, members_all as i64, members.len() as i64).await; // small bit of logging to note changes over time println!("{:?} Changes: New: +{}, Current: +{}/-{}", server.as_u64(), roles_set[0], roles_set[1], roles_set[2]); } pub async fn get_server_member_bulk(db: &Pool, server: &GuildId) -> Vec { sqlx::query_as::<_, ServerMembersWolves>( r#" SELECT * FROM server_members JOIN wolves USING (id_wolves) WHERE ( server = ? AND discord IS NOT NULL AND expiry > ? ) "#, ) .bind(*server.as_u64() as i64) .bind(get_now_iso(true)) .fetch_all(db) .await .unwrap_or_default() } async fn set_server_numbers(db: &Pool, server: &GuildId, past: i64, current: i64) { match sqlx::query_as::<_, Wolves>( " UPDATE servers SET member_past = ?, member_current = ? WHERE server = ? ", ) .bind(past) .bind(current) .bind(*server.as_u64() as i64) .fetch_optional(db) .await { Ok(_) => {} Err(e) => { println!("Failure to insert into {}", server.as_u64()); println!("{:?}", e); } } } } /** For any time ye need to check if a user who calls a command has admin privlages */ pub async fn is_admin(command: &ApplicationCommandInteraction, 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 } }