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 sqlx::{Pool, Sqlite}; 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 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.get(), 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.get() 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.get() as i64) .fetch_optional(db) .await { Ok(_) => {} Err(e) => { println!("Failure to insert into {}", server.get()); println!("{:?}", e); } } } } // 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 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 std::collections::HashMap; use std::sync::Arc; pub async fn check_committee(ctx: Arc) { let db_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() }; let db = db_lock.read().await; let config_lock = { let data_read = ctx.data.read().await; data_read.get::().expect("Expected Config in TypeMap.").clone() }; let config_global = config_lock.read().await; let server = config_global.committee_server; // because to use it to update a single user we need to pre-get the members of teh server let mut members = server.members(&ctx, None, None).await.unwrap_or_default(); update_committees(&db, &ctx, &config_global, &mut members).await; } /** This function can take a vec of members (or just one) and gives tehm the appropiate roles on teh committee server */ pub async fn update_committees(db: &Pool, ctx: &Context, config: &Config, members: &mut Vec) { let server = config.committee_server; let committee_member = RoleId::new(1226602779968274573); 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), ]; // information about the server 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 = 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(); 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() { break; } let committee = &committees[i]; // 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) .kind(ChannelType::Text) .category(categories[category_index]), ) .await { Ok(x) => { 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; } 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; }; 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); if let Some(x) = roles_db.get_mut(&committee.id) { x.count += 1; } } } } } // now we have a map of all users that should get roles time to go through all the folks on teh server for member in members { let roles_current = member.roles(ctx).unwrap_or_default(); let roles_required = match users_roles.get(&member.user.id) { None => { vec![] } Some(x) => { let mut tmp = x.to_owned(); if !tmp.is_empty() { tmp.push(committee_member); } tmp } }; let mut roles_rem = vec![]; let mut roles_add = vec![]; // get a list of all the roles to remove from someone let mut roles_current_id = vec![]; for role in &roles_current { roles_current_id.push(role.id.to_owned()); if !roles_required.contains(&role.id) { roles_rem.push(role.id.to_owned()); } } 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 { if !roles_current_id.contains(role) { roles_add.push(role.to_owned()); } } if !roles_rem.is_empty() { member.remove_roles(&ctx, &roles_rem).await.unwrap_or_default(); } if !roles_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(); } } let mut channel_names = 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)); } } 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)); } } } 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#" SELECT * FROM committees "#, ) .fetch_all(db) .await .unwrap_or_else(|e| { dbg!(e); vec![] }) } async fn get_server_member_discord(db: &Pool, user: &i64) -> Option { sqlx::query_as::<_, Wolves>( r#" SELECT * FROM wolves WHERE id_wolves = ? "#, ) .bind(user) .fetch_one(db) .await .ok() } }