Compare commits

...

10 commits

17 changed files with 643 additions and 255 deletions

1
Cargo.lock generated
View file

@ -2419,6 +2419,7 @@ dependencies = [
"maud", "maud",
"rand 0.8.5", "rand 0.8.5",
"serde", "serde",
"serde_json",
"serenity", "serenity",
"sqlx", "sqlx",
"surf", "surf",

View file

@ -26,6 +26,7 @@ dotenvy = "0.15.7"
# For sqlite # For sqlite
sqlx = { version = "0.7.1", features = [ "runtime-tokio", "sqlite", "migrate" ] } sqlx = { version = "0.7.1", features = [ "runtime-tokio", "sqlite", "migrate" ] }
serde_json = { version = "1.0", features = ["raw_value"] }
# create random strings # create random strings
rand = "0.8.5" rand = "0.8.5"

View file

@ -3,8 +3,8 @@ DROP TABLE committee;
-- new table pulling from teh api -- new table pulling from teh api
CREATE TABLE IF NOT EXISTS committees ( CREATE TABLE IF NOT EXISTS committees (
id integer PRIMARY KEY, id integer PRIMARY KEY,
name text not null, name text not null,
link text not null, link text not null,
members text not null committee text not null
); );

View file

@ -1,14 +1,15 @@
use serenity::{ use serenity::{
async_trait, async_trait,
client::{Context, EventHandler}, client::{Context, EventHandler},
model::gateway::{GatewayIntents, Ready}, model::gateway::{GatewayIntents, Ready},
Client, 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::{get_config, Config};
use std::{process, sync::Arc}; use std::{process, sync::Arc};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use skynet_discord_bot::common::database::{db_init, DataBase};
use skynet_discord_bot::common::wolves::get_data::get_wolves_cns;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
@ -46,7 +47,10 @@ impl EventHandler for Handler {
println!("{} is connected!", ready.user.name); println!("{} is connected!", ready.user.name);
// get the data for each individual club/soc // get the data for each individual club/soc
get_wolves_cns(&ctx).await; get_wolves(&ctx).await;
// get teh data for the clubs/socs committees
get_cns(&ctx).await;
// finish up // finish up
process::exit(0); process::exit(0);

View file

@ -1,7 +1,7 @@
use skynet_discord_bot::get_config;
use std::collections::HashSet;
use skynet_discord_bot::common::database::db_init; use skynet_discord_bot::common::database::db_init;
use skynet_discord_bot::common::minecraft::{get_minecraft_config, update_server, whitelist_wipe}; use skynet_discord_bot::common::minecraft::{get_minecraft_config, update_server, whitelist_wipe};
use skynet_discord_bot::get_config;
use std::collections::HashSet;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {

View file

@ -1,13 +1,14 @@
use serenity::{ use serenity::{
async_trait, async_trait,
client::{Context, EventHandler}, client::{Context, EventHandler},
model::gateway::{GatewayIntents, Ready}, model::gateway::{GatewayIntents, Ready},
Client, Client,
}; };
use skynet_discord_bot::{get_config, set_roles, Config}; 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 std::{process, sync::Arc};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase};
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
@ -44,14 +45,18 @@ impl EventHandler for Handler {
let ctx = Arc::new(ctx); let ctx = Arc::new(ctx);
println!("{} is connected!", ready.user.name); println!("{} is connected!", ready.user.name);
bulk_check(Arc::clone(&ctx)).await; // this goes into each server and sets roles for each wolves member
check_bulk(Arc::clone(&ctx)).await;
// u[date committee server
committee::check_committee(Arc::clone(&ctx)).await;
// finish up // finish up
process::exit(0); process::exit(0);
} }
} }
async fn bulk_check(ctx: Arc<Context>) { async fn check_bulk(ctx: Arc<Context>) {
let db_lock = { let db_lock = {
let data_read = ctx.data.read().await; let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone() data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
@ -60,6 +65,6 @@ async fn bulk_check(ctx: Arc<Context>) {
let db = db_lock.read().await; let db = db_lock.read().await;
for server_config in get_server_config_bulk(&db).await { for server_config in get_server_config_bulk(&db).await {
set_roles::update_server(&ctx, &server_config, &[], &[]).await; normal::update_server(&ctx, &server_config, &[], &[]).await;
} }
} }

View file

@ -1,15 +1,16 @@
use serenity::{ use serenity::{
builder::CreateApplicationCommand, builder::CreateApplicationCommand,
client::Context, client::Context,
model::{ model::{
application::interaction::application_command::ApplicationCommandInteraction, application::interaction::application_command::ApplicationCommandInteraction,
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
}, },
}; };
use skynet_discord_bot::common::wolves::get_data::get_wolves_cns;
use skynet_discord_bot::{is_admin, set_roles::update_server};
use sqlx::{Error, Pool, Sqlite};
use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers}; use skynet_discord_bot::common::database::{get_server_config, DataBase, Servers};
use skynet_discord_bot::common::set_roles::normal::update_server;
use skynet_discord_bot::common::wolves::cns::get_wolves;
use skynet_discord_bot::is_admin;
use sqlx::{Error, Pool, Sqlite};
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
// check if user has high enough permisssions // check if user has high enough permisssions
@ -216,7 +217,7 @@ async fn add_server(db: &Pool<Sqlite>, ctx: &Context, server: &Servers) -> Resul
// update all users // update all users
if update { if update {
// handle wolves api here // handle wolves api here
get_wolves_cns(ctx).await; get_wolves(ctx).await;
let mut roles_remove = vec![]; let mut roles_remove = vec![];
if current_remove { if current_remove {

View file

@ -13,9 +13,9 @@ use serenity::{
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue}, prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
}, },
}; };
use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify};
use skynet_discord_bot::{get_now_iso, random_string, Config}; use skynet_discord_bot::{get_now_iso, random_string, Config};
use sqlx::{Pool, Sqlite}; use sqlx::{Pool, Sqlite};
use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify};
pub mod link { pub mod link {
use super::*; use super::*;
@ -241,8 +241,8 @@ pub mod verify {
use crate::commands::link_email::link::{db_pending_clear_expired, get_verify_from_db}; use crate::commands::link_email::link::{db_pending_clear_expired, get_verify_from_db};
use serenity::model::user::User; use serenity::model::user::User;
use skynet_discord_bot::common::database::get_server_config; use skynet_discord_bot::common::database::get_server_config;
use sqlx::Error;
use skynet_discord_bot::common::database::{ServerMembersWolves, Servers}; use skynet_discord_bot::common::database::{ServerMembersWolves, Servers};
use sqlx::Error;
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String { pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
let db_lock = { let db_lock = {

View file

@ -16,10 +16,10 @@ pub(crate) mod user {
use super::*; use super::*;
use crate::commands::link_email::link::get_server_member_discord; use crate::commands::link_email::link::get_server_member_discord;
use serenity::model::id::UserId; use serenity::model::id::UserId;
use skynet_discord_bot::Config;
use sqlx::Error;
use skynet_discord_bot::common::database::Wolves; use skynet_discord_bot::common::database::Wolves;
use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft}; use skynet_discord_bot::common::minecraft::{whitelist_update, Minecraft};
use skynet_discord_bot::Config;
use sqlx::Error;
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command.name("link_minecraft").description("Link your minecraft account").create_option(|option| { command.name("link_minecraft").description("Link your minecraft account").create_option(|option| {
@ -124,9 +124,9 @@ pub(crate) mod server {
use sqlx::Error; use sqlx::Error;
// this is to managfe the server side of commands related to minecraft // this is to managfe the server side of commands related to minecraft
use super::*; use super::*;
use skynet_discord_bot::{is_admin, Config};
use skynet_discord_bot::common::minecraft::Minecraft;
use skynet_discord_bot::common::minecraft::update_server; use skynet_discord_bot::common::minecraft::update_server;
use skynet_discord_bot::common::minecraft::Minecraft;
use skynet_discord_bot::{is_admin, Config};
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command.name("minecraft_add").description("Add a minecraft server").create_option(|option| { command.name("minecraft_add").description("Add a minecraft server").create_option(|option| {
@ -205,9 +205,9 @@ pub(crate) mod server {
use serenity::builder::CreateApplicationCommand; use serenity::builder::CreateApplicationCommand;
use serenity::client::Context; use serenity::client::Context;
use serenity::model::prelude::application_command::ApplicationCommandInteraction; use serenity::model::prelude::application_command::ApplicationCommandInteraction;
use skynet_discord_bot::{is_admin, Config};
use skynet_discord_bot::common::database::DataBase; use skynet_discord_bot::common::database::DataBase;
use skynet_discord_bot::common::minecraft::{get_minecraft_config_server, server_information}; use skynet_discord_bot::common::minecraft::{get_minecraft_config_server, server_information};
use skynet_discord_bot::{is_admin, Config};
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command.name("minecraft_list").description("List your minecraft servers") command.name("minecraft_list").description("List your minecraft servers")
@ -268,10 +268,10 @@ pub(crate) mod server {
use serenity::model::application::command::CommandOptionType; use serenity::model::application::command::CommandOptionType;
use serenity::model::id::GuildId; use serenity::model::id::GuildId;
use serenity::model::prelude::application_command::{ApplicationCommandInteraction, CommandDataOptionValue}; use serenity::model::prelude::application_command::{ApplicationCommandInteraction, CommandDataOptionValue};
use skynet_discord_bot::is_admin;
use sqlx::{Error, Pool, Sqlite};
use skynet_discord_bot::common::database::DataBase; use skynet_discord_bot::common::database::DataBase;
use skynet_discord_bot::common::minecraft::Minecraft; use skynet_discord_bot::common::minecraft::Minecraft;
use skynet_discord_bot::is_admin;
use sqlx::{Error, Pool, Sqlite};
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand { pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
command.name("minecraft_delete").description("Delete a minecraft server").create_option(|option| { command.name("minecraft_delete").description("Delete a minecraft server").create_option(|option| {

View file

@ -7,9 +7,9 @@ use serenity::{
}, },
}; };
use skynet_discord_bot::common::database::{DataBase, RoleAdder};
use skynet_discord_bot::is_admin; use skynet_discord_bot::is_admin;
use sqlx::{Error, Pool, Sqlite}; use sqlx::{Error, Pool, Sqlite};
use skynet_discord_bot::common::database::{DataBase, RoleAdder};
pub mod edit { pub mod edit {
use super::*; use super::*;

View file

@ -1,13 +1,13 @@
use serenity::prelude::TypeMapKey; use crate::Config;
use std::sync::Arc;
use tokio::sync::RwLock;
use sqlx::{Error, FromRow, Pool, Row, Sqlite};
use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow};
use serenity::model::id::{ChannelId, GuildId, RoleId, UserId};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serenity::model::guild; 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::str::FromStr;
use crate::Config; use std::sync::Arc;
use tokio::sync::RwLock;
pub struct DataBase; pub struct DataBase;
impl TypeMapKey for DataBase { impl TypeMapKey for DataBase {
@ -267,4 +267,4 @@ pub async fn get_server_config_bulk(db: &Pool<Sqlite>) -> Vec<Servers> {
.fetch_all(db) .fetch_all(db)
.await .await
.unwrap_or_default() .unwrap_or_default()
} }

View file

@ -1,11 +1,10 @@
use serde::{Deserialize, Serialize}; use crate::common::set_roles::normal::get_server_member_bulk;
use crate::Config;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use sqlx::{Error, FromRow, Pool, Row, Sqlite}; use serde::{Deserialize, Serialize};
use serenity::model::id::GuildId; use serenity::model::id::GuildId;
use sqlx::sqlite::SqliteRow; use sqlx::sqlite::SqliteRow;
use crate::Config; use sqlx::{Error, FromRow, Pool, Row, Sqlite};
use crate::set_roles::get_server_member_bulk;
#[derive(Debug, Clone, Deserialize, Serialize)] #[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Minecraft { pub struct Minecraft {
@ -25,7 +24,6 @@ impl<'r> FromRow<'r, SqliteRow> for Minecraft {
} }
} }
/** /**
loop through all members of server loop through all members of server
get a list of folks with mc accounts that are members get a list of folks with mc accounts that are members
@ -164,4 +162,3 @@ pub async fn get_minecraft_config_server(db: &Pool<Sqlite>, g_id: GuildId) -> Ve
.await .await
.unwrap_or_default() .unwrap_or_default()
} }

View file

@ -1,3 +1,4 @@
pub mod wolves;
pub mod database; pub mod database;
pub mod minecraft; pub mod minecraft;
pub mod set_roles;
pub mod wolves;

263
src/common/set_roles.rs Normal file
View file

@ -0,0 +1,263 @@
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<RoleId>], members_changed: &[UserId]) {
let db_lock = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().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<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
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<Sqlite>, server: &GuildId, past: i64, current: i64) {
match sqlx::query_as::<_, Wolves>(
"
UPDATE servers
SET member_past = ?, member_current = ?
WHERE server = ?
",
)
.bind(past)
.bind(current)
.bind(*server.as_u64() as i64)
.fetch_optional(db)
.await
{
Ok(_) => {}
Err(e) => {
println!("Failure to insert into {}", server.as_u64());
println!("{:?}", e);
}
}
}
}
// for updating committee members
pub mod committee {
use crate::common::database::{DataBase, Wolves};
use crate::common::wolves::committees::Committees;
use serenity::client::Context;
use serenity::model::guild::Member;
use serenity::model::id::{GuildId, RoleId};
use sqlx::{Pool, Sqlite};
use std::collections::HashMap;
use std::sync::Arc;
pub async fn check_committee(ctx: Arc<Context>) {
let db_lock = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
};
let db = db_lock.read().await;
let server = GuildId(1220150752656363520);
let mut members = server.members(&ctx, None, None).await.unwrap_or_default();
update_committees(&db, &ctx, &mut members).await;
}
pub async fn update_committees(db: &Pool<Sqlite>, ctx: &Context, members: &mut Vec<Member>) {
let server = GuildId(1220150752656363520);
let committee_member = RoleId(1226602779968274573);
let committees = get_committees(db).await;
// information about the server
let roles = server.roles(&ctx).await.unwrap_or_default();
// make a hashmap of the nameof roles to quickly get them out again
let mut roles_name = HashMap::new();
for role in roles.values() {
roles_name.insert(role.name.to_owned(), role.to_owned());
}
// a map of users and the roles they are goign to be getting
let mut users_roles = HashMap::new();
// a list of all the roles that can be removed from folks who should have them
let mut committee_roles = vec![committee_member];
for committee in &committees {
// get the role for this committee/club/soc
let role = match roles_name.get(&committee.name) {
Some(x) => Some(x.to_owned()),
None => {
// create teh role if it does not exist
match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name)).await {
Ok(x) => Some(x),
Err(_) => None,
}
}
};
// so if the role exists
if let Some(r) = role {
committee_roles.push(r.id);
for id_wolves in &committee.committee {
// ID in this is the wolves ID, so we need to get a matching discord ID (if one exists)
if let Some(x) = get_server_member_discord(db, id_wolves).await {
if let Some(member_tmp) = x.discord {
let values = users_roles.entry(member_tmp).or_insert(vec![]);
values.push(r.id);
}
}
}
}
}
// now we have a map of all users that should get roles time to go through all the folks on teh server
for member in members {
let roles_required = match users_roles.get(&member.user.id) {
None => {
vec![]
}
Some(x) => {
let mut combined = x.to_owned();
// this is the main role, since it provides access to everything.
combined.push(committee_member);
combined
}
};
// get a list of all the roles to remove from someone
let mut roles_rem = vec![];
for role in &committee_roles {
if !roles_required.contains(role) {
roles_rem.push(role.to_owned());
}
}
if !roles_rem.is_empty() {
member.remove_roles(&ctx, &roles_rem).await.unwrap_or_default();
}
if !roles_required.is_empty() {
// these roles are flavor roles, only there to make folks mentionable
member.add_roles(&ctx, &roles_required).await.unwrap_or_default();
}
}
}
async fn get_committees(db: &Pool<Sqlite>) -> Vec<Committees> {
sqlx::query_as::<_, Committees>(
r#"
SELECT *
FROM committees
"#,
)
.fetch_all(db)
.await
.unwrap_or_default()
}
async fn get_server_member_discord(db: &Pool<Sqlite>, user: &i64) -> Option<Wolves> {
sqlx::query_as::<_, Wolves>(
r#"
SELECT *
FROM wolves
WHERE id_wolves = ?
"#,
)
.bind(user)
.fetch_one(db)
.await
.ok()
}
}

View file

@ -1,28 +1,66 @@
use crate::common::database::Wolves;
use serde::{Deserialize, Serialize};
use sqlx::{Pool, Sqlite};
/** /**
This file relates to anything that directly interacts with teh wolves API This file relates to anything that directly interacts with teh wolves API
*/ */
#[derive(Deserialize, Serialize, Debug)]
struct WolvesResultUserMin {
// committee: String,
member_id: String,
// first_name: String,
// last_name: String,
contact_email: String,
// opt_in_email: String,
// student_id: Option<String>,
// note: Option<String>,
// expiry: String,
// requested: String,
// approved: String,
// sitename: String,
// domain: String,
}
async fn add_users_wolves(db: &Pool<Sqlite>, user: &WolvesResultUserMin) {
// expiry
match sqlx::query_as::<_, Wolves>(
"
INSERT INTO wolves (id_wolves, email)
VALUES ($1, $2)
ON CONFLICT(id_wolves) DO UPDATE SET email = $2
",
)
.bind(&user.member_id)
.bind(&user.contact_email)
.fetch_optional(db)
.await
{
Ok(_) => {}
Err(e) => {
println!("Failure to insert into Wolves {:?}", user);
println!("{:?}", e);
}
}
}
// #[derive(Debug, Clone, sqlx::FromRow)] /**
// pub struct Committees { This is getting data for Clubs and Socs
// pub id: i64, */
// pub name: String, pub mod cns {
// pub link: String, use crate::common::database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers};
// pub members: Vec<i64>, use crate::common::set_roles::normal::update_server;
// } use crate::common::wolves::{add_users_wolves, WolvesResultUserMin};
use crate::Config;
pub mod get_data {
use crate::set_roles::update_server;
use std::collections::BTreeMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serenity::client::Context; use serenity::client::Context;
use serenity::model::id::GuildId; use serenity::model::id::GuildId;
use sqlx::{Pool, Sqlite}; use sqlx::{Pool, Sqlite};
use crate::Config; use std::collections::BTreeMap;
use crate::common::database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers, Wolves};
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
struct WolvesResultUser { struct WolvesResultUser {
// TODO: Might be worth trying to get this replaced with the club/soc ID?
committee: String, committee: String,
member_id: String, member_id: String,
first_name: String, first_name: String,
@ -38,6 +76,15 @@ pub mod get_data {
domain: String, domain: String,
} }
impl From<&WolvesResultUser> for WolvesResultUserMin {
fn from(value: &WolvesResultUser) -> Self {
Self {
member_id: value.member_id.to_owned(),
contact_email: value.contact_email.to_owned(),
}
}
}
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
struct WolvesResult { struct WolvesResult {
success: i8, success: i8,
@ -50,7 +97,7 @@ pub mod get_data {
pub email: String, pub email: String,
pub expiry: String, pub expiry: String,
} }
pub async fn get_wolves_cns(ctx: &Context) { pub async fn get_wolves(ctx: &Context) {
let db_lock = { let db_lock = {
let data_read = ctx.data.read().await; let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone() data_read.get::<DataBase>().expect("Expected Database in TypeMap.").clone()
@ -75,17 +122,17 @@ pub mod get_data {
// list of users that need to be updated for this server // list of users that need to be updated for this server
let mut user_to_update = vec![]; let mut user_to_update = vec![];
for user in get_wolves_cns_sub(&config, wolves_api).await { for user in get_wolves_sub(&config, wolves_api).await {
let id = user.member_id.parse::<u64>().unwrap_or_default(); let id = user.member_id.parse::<u64>().unwrap_or_default();
match existing.get(&(id as i64)) { match existing.get(&(id as i64)) {
None => { None => {
// user does not exist already, add everything // user does not exist already, add everything
add_users_wolves(&db, &user).await; add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await;
add_users_server_members(&db, server, &user).await; add_users_server_members(&db, server, &user).await;
} }
Some(old) => { Some(old) => {
// always update wolves table, in case data has changed // always update wolves table, in case data has changed
add_users_wolves(&db, &user).await; add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await;
if old.expiry != user.expiry { if old.expiry != user.expiry {
add_users_server_members(&db, server, &user).await; add_users_server_members(&db, server, &user).await;
@ -103,7 +150,7 @@ pub mod get_data {
} }
} }
pub async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> { async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
sqlx::query_as::<_, ServerMembersWolves>( sqlx::query_as::<_, ServerMembersWolves>(
r#" r#"
SELECT * SELECT *
@ -121,13 +168,15 @@ pub mod get_data {
.unwrap_or_default() .unwrap_or_default()
} }
async fn get_wolves_cns_sub(config: &Config, wolves_api: &str) -> Vec<WolvesResultUser> { async fn get_wolves_sub(config: &Config, wolves_api: &str) -> Vec<WolvesResultUser> {
if config.wolves_url.is_empty() { if config.wolves_url.is_empty() {
return vec![]; return vec![];
} }
let url = format!("{}/get_members", &config.wolves_url);
// get wolves data // get wolves data
if let Ok(mut res) = surf::post(&config.wolves_url).header("X-AM-Identity", wolves_api).await { if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await {
if let Ok(WolvesResult { if let Ok(WolvesResult {
success, success,
result, result,
@ -144,27 +193,6 @@ pub mod get_data {
vec![] vec![]
} }
async fn add_users_wolves(db: &Pool<Sqlite>, user: &WolvesResultUser) {
// expiry
match sqlx::query_as::<_, Wolves>(
"
INSERT INTO wolves (id_wolves, email)
VALUES ($1, $2)
ON CONFLICT(id_wolves) DO UPDATE SET email = $2
",
)
.bind(&user.member_id)
.bind(&user.contact_email)
.fetch_optional(db)
.await
{
Ok(_) => {}
Err(e) => {
println!("Failure to insert into Wolves {:?}", user);
println!("{:?}", e);
}
}
}
async fn add_users_server_members(db: &Pool<Sqlite>, server: &GuildId, user: &WolvesResultUser) { async fn add_users_server_members(db: &Pool<Sqlite>, server: &GuildId, user: &WolvesResultUser) {
match sqlx::query_as::<_, ServerMembers>( match sqlx::query_as::<_, ServerMembers>(
" "
@ -185,4 +213,222 @@ pub mod get_data {
} }
} }
} }
} }
/**
Get and store the data on C&S committees
*/
pub mod committees {
use crate::common::database::DataBase;
use crate::Config;
use serde::{Deserialize, Serialize};
use serenity::client::Context;
use sqlx::{Pool, Sqlite};
// This is what Wolves returns to us
#[derive(Deserialize, Serialize, Debug)]
struct WolvesResult {
success: i8,
result: Vec<WolvesResultCNS>,
}
// this is teh actual data we care about
#[derive(Deserialize, Serialize, Debug)]
struct WolvesResultCNS {
id: String,
name: String,
// Link to their page such as https://ulwolves.ie/society/computer
link: String,
// array of Committee members member_id's
committee: Vec<String>,
}
// Database entry for it
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct Committees {
pub id: i64,
pub name: String,
pub link: String,
#[sqlx(json)]
pub committee: Vec<i64>,
}
impl From<WolvesResultCNS> for Committees {
fn from(value: WolvesResultCNS) -> Self {
Self {
id: value.id.parse().unwrap_or(0),
name: value.name,
link: value.link,
committee: value.committee.iter().map(|x| x.parse::<i64>().unwrap_or(0)).collect(),
}
}
}
pub async fn get_cns(ctx: &Context) {
let db_lock = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().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::<Config>().expect("Expected Config in TypeMap.").clone()
};
let config = config_lock.read().await;
// TODO: proper api key management
let api_key = "";
// request data from wolves
for committee in get_committees(&config, api_key).await {
let tmp = Committees::from(committee);
add_committee(&db, &tmp).await;
}
}
async fn get_committees(config: &Config, wolves_api: &str) -> Vec<WolvesResultCNS> {
if config.wolves_url.is_empty() {
return vec![];
}
// TODO: Change teh stored env value to teh base domain
let url = format!("{}/get_cns", &config.wolves_url);
// get wolves data
if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await {
if let Ok(WolvesResult {
success,
result,
}) = res.body_json().await
{
if success != 1 {
return vec![];
}
return result;
}
}
vec![]
}
async fn add_committee(db: &Pool<Sqlite>, committee: &Committees) {
match sqlx::query_as::<_, Committees>(
"
INSERT INTO committees (id, name, link, committee)
VALUES ($1, $2, $3, $4)
ON CONFLICT(id) DO UPDATE SET committee = $4
",
)
.bind(committee.id)
.bind(&committee.name)
.bind(&committee.link)
.bind(serde_json::to_string(&committee.committee).unwrap_or_default())
.fetch_optional(db)
.await
{
Ok(_) => {}
Err(e) => {
println!("Failure to insert into Committees {:?}", committee);
println!("{:?}", e);
}
}
}
}
/**
get the data for an individual user
*/
pub mod individual {
use crate::common::database::DataBase;
use crate::common::wolves::{add_users_wolves, WolvesResultUserMin};
use crate::Config;
use serde::{Deserialize, Serialize};
use serenity::client::Context;
#[derive(Deserialize, Serialize, Debug)]
struct WolvesResultUser {
// committee: String,
member_id: String,
// first_name: String,
// last_name: String,
contact_email: String,
// opt_in_email: String,
// student_id: Option<String>,
// note: Option<String>,
// expiry: String,
// requested: String,
// approved: String,
// sitename: String,
// domain: String,
}
impl From<&WolvesResultUser> for WolvesResultUserMin {
fn from(value: &WolvesResultUser) -> Self {
Self {
member_id: value.member_id.to_owned(),
contact_email: value.contact_email.to_owned(),
}
}
}
#[derive(Deserialize, Serialize, Debug)]
struct WolvesResult {
success: i8,
result: WolvesResultUser,
}
pub async fn get_user(ctx: &Context, email: &str) -> bool {
let db_lock = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().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::<Config>().expect("Expected Config in TypeMap.").clone()
};
let config = config_lock.read().await;
// TODO: proper api key management
let api_key = "";
// request data from wolves
match get_user_sub(&config, api_key, email).await {
None => false,
// if exists save it and return true
Some(user) => {
// add to db
add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await;
true
}
}
}
async fn get_user_sub(config: &Config, wolves_api: &str, _email: &str) -> Option<WolvesResultUser> {
if config.wolves_url.is_empty() {
return None;
}
// TODO: Change teh stored env value to teh base domain
let url = format!("{}/get_member", &config.wolves_url);
// get wolves data
if let Ok(mut res) = surf::post(&url).header("X-AM-Identity", wolves_api).await {
if let Ok(WolvesResult {
success,
result,
}) = res.body_json().await
{
if success != 1 {
return None;
}
return Some(result);
}
}
None
}
}

View file

@ -1,26 +1,13 @@
pub mod common; 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 chrono::{Datelike, SecondsFormat, Utc};
use dotenvy::dotenv;
use rand::{distributions::Alphanumeric, thread_rng, Rng}; use rand::{distributions::Alphanumeric, thread_rng, Rng};
use serde::de::DeserializeOwned;
use serenity::client::Context; use serenity::client::Context;
use serenity::model::id::UserId;
use serenity::model::prelude::application_command::ApplicationCommandInteraction; use serenity::model::prelude::application_command::ApplicationCommandInteraction;
use sqlx::{ use serenity::prelude::TypeMapKey;
Pool, Sqlite,
};
use std::{env, sync::Arc}; use std::{env, sync::Arc};
use tokio::sync::RwLock; use tokio::sync::RwLock;
use common::database::{ServerMembersWolves, Servers};
pub struct Config { pub struct Config {
// manages where teh database is stored // manages where teh database is stored
pub home: String, pub home: String,
@ -103,133 +90,6 @@ pub fn random_string(len: usize) -> String {
thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect() 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<RoleId>], members_changed: &[UserId]) {
let db_lock = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().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<Sqlite>, server: &GuildId) -> Vec<ServerMembersWolves> {
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<Sqlite>, 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 For any time ye need to check if a user who calls a command has admin privlages
*/ */

View file

@ -2,21 +2,23 @@ pub mod commands;
use crate::commands::role_adder::tools::on_role_change; use crate::commands::role_adder::tools::on_role_change;
use serenity::model::guild::Member; use serenity::model::guild::Member;
use serenity::model::id::GuildId;
use serenity::{ use serenity::{
async_trait, async_trait,
client::{Context, EventHandler}, client::{Context, EventHandler},
model::{ model::{
application::{command::Command, interaction::Interaction}, application::{command::Command, interaction::Interaction},
gateway::{GatewayIntents, Ready}, gateway::{GatewayIntents, Ready},
prelude::Activity, prelude::Activity,
user::OnlineStatus, user::OnlineStatus,
}, },
Client, Client,
}; };
use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_member, DataBase};
use skynet_discord_bot::common::set_roles::committee::update_committees;
use skynet_discord_bot::{get_config, Config}; use skynet_discord_bot::{get_config, Config};
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_member, DataBase};
struct Handler; struct Handler;
@ -35,6 +37,13 @@ impl EventHandler for Handler {
Some(x) => x, Some(x) => x,
}; };
// committee server takes priority
if new_member.guild_id.eq(&GuildId(1220150752656363520)) {
let mut member = vec![new_member.clone()];
update_committees(&db, &ctx, &mut member).await;
return;
}
if get_server_member(&db, &new_member.guild_id, &new_member).await.is_ok() { if get_server_member(&db, &new_member.guild_id, &new_member).await.is_ok() {
let mut roles = vec![]; let mut roles = vec![];