feat: now properly sets and removes roles for committee members

This commit is contained in:
silver 2025-02-18 21:14:05 +00:00
parent 4eeb7f2135
commit 1aef86ad01
Signed by: silver
GPG key ID: 36F93D61BAD3FD7D
9 changed files with 137 additions and 89 deletions

View file

@ -10,3 +10,5 @@ CREATE TABLE IF NOT EXISTS committees (
link text not null, link text not null,
committee text not null committee text not null
); );
ALTER TABLE servers DROP COLUMN wolves_link;

View file

@ -46,10 +46,6 @@ You (personally) will need a role with ``Administrator`` permission to be able t
4. ``role_past`` (optional) is the role for all current and past members. 4. ``role_past`` (optional) is the role for all current and past members.
5. ``bot_channel`` is a channel that folks are recommended to use the bot. 5. ``bot_channel`` is a channel that folks are recommended to use the bot.
* You can have it so folks cannot see message history * You can have it so folks cannot see message history
6. ``server_name`` For example ``UL Computer Society``
* Will be removed in the future
7. ``wolves_link`` for example <https://ulwolves.ie/society/computer>
* Will be removed in the future
At this point the bot is set up and no further action is required. At this point the bot is set up and no further action is required.

View file

@ -16,7 +16,10 @@ async fn main() {
let config = get_config(); let config = get_config();
let db = match db_init(&config).await { let db = match db_init(&config).await {
Ok(x) => x, Ok(x) => x,
Err(_) => return, Err(e) => {
dbg!(e);
return;
}
}; };
// Intents are a bitflag, bitwise operations can be used to dictate which intents to use // Intents are a bitflag, bitwise operations can be used to dictate which intents to use

View file

@ -67,34 +67,6 @@ pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> Stri
return "Please provide a valid channel for ``Bot Channel``".to_string(); return "Please provide a valid channel for ``Bot Channel``".to_string();
}; };
let server_name = if let CommandDataOptionValue::String(name) = command
.data
.options
.get(3)
.expect("Expected Server Name option")
.resolved
.as_ref()
.expect("Expected Server Name object")
{
name
} else {
&"UL Computer Society".to_string()
};
let wolves_link = if let CommandDataOptionValue::String(wolves) = command
.data
.options
.get(4)
.expect("Expected Wolves Link option")
.resolved
.as_ref()
.expect("Expected Server Name object")
{
wolves
} else {
&"https://ulwolves.ie/society/computer".to_string()
};
let db_lock = { let db_lock = {
let data_read = ctx.data.read().await; let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone() data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
@ -109,8 +81,7 @@ pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> Stri
member_past: 0, member_past: 0,
member_current: 0, member_current: 0,
bot_channel_id, bot_channel_id,
server_name: server_name.to_owned(), server_name: "".to_string(),
wolves_link: wolves_link.to_string(),
}; };
match add_server(&db, ctx, &server_data).await { match add_server(&db, ctx, &server_data).await {
@ -149,20 +120,6 @@ pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicatio
.kind(CommandOptionType::Channel) .kind(CommandOptionType::Channel)
.required(true) .required(true)
}) })
.create_option(|option| {
option
.name("server_name")
.description("Name of the Discord Server.")
.kind(CommandOptionType::String)
.required(true)
})
.create_option(|option| {
option
.name("wolves_link")
.description("Link to the Club/Society on UL Wolves.")
.kind(CommandOptionType::String)
.required(true)
})
.create_option(|option| { .create_option(|option| {
option option
.name("role_past") .name("role_past")
@ -178,8 +135,8 @@ async fn add_server(db: &Pool<Sqlite>, ctx: &Context, server: &Servers) -> Resul
let insert = sqlx::query_as::<_, Servers>( let insert = sqlx::query_as::<_, Servers>(
" "
INSERT OR REPLACE INTO servers (server, wolves_api, role_past, role_current, bot_channel_id, server_name, wolves_link) INSERT OR REPLACE INTO servers (server, wolves_api, role_past, role_current, bot_channel_id)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7) VALUES (?1, ?2, ?3, ?4, ?5)
", ",
) )
.bind(*server.server.as_u64() as i64) .bind(*server.server.as_u64() as i64)
@ -187,8 +144,6 @@ async fn add_server(db: &Pool<Sqlite>, ctx: &Context, server: &Servers) -> Resul
.bind(role_past) .bind(role_past)
.bind(*server.role_current.as_u64() as i64) .bind(*server.role_current.as_u64() as i64)
.bind(*server.bot_channel_id.as_u64() as i64) .bind(*server.bot_channel_id.as_u64() as i64)
.bind(&server.server_name)
.bind(&server.wolves_link)
.fetch_optional(db) .fetch_optional(db)
.await; .await;

View file

@ -135,9 +135,7 @@ pub struct Servers {
pub member_past: i64, pub member_past: i64,
pub member_current: i64, pub member_current: i64,
pub bot_channel_id: ChannelId, pub bot_channel_id: ChannelId,
// TODO: these can be removed in teh future with an API update
pub server_name: String, pub server_name: String,
pub wolves_link: String,
} }
impl<'r> FromRow<'r, SqliteRow> for Servers { impl<'r> FromRow<'r, SqliteRow> for Servers {
@ -175,7 +173,6 @@ impl<'r> FromRow<'r, SqliteRow> for Servers {
member_current: row.try_get("member_current")?, member_current: row.try_get("member_current")?,
bot_channel_id, bot_channel_id,
server_name: row.try_get("server_name")?, server_name: row.try_get("server_name")?,
wolves_link: row.try_get("wolves_link")?,
}) })
} }
} }

View file

@ -137,6 +137,8 @@ pub mod committee {
use serenity::client::Context; use serenity::client::Context;
use serenity::model::channel::ChannelType; use serenity::model::channel::ChannelType;
use serenity::model::guild::Member; use serenity::model::guild::Member;
use serenity::model::id::ChannelId;
use serenity::model::prelude::RoleId;
use sqlx::{Pool, Sqlite}; use sqlx::{Pool, Sqlite};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
@ -170,6 +172,7 @@ pub mod committee {
let server = config.committee_server; let server = config.committee_server;
let committee_member = config.committee_role; let committee_member = config.committee_role;
let committees = get_committees(db).await; let committees = get_committees(db).await;
let categories = vec![config.committee_category, ChannelId(1341457244973305927), ChannelId(1341457509717639279)];
// information about the server // information about the server
let roles = server.roles(&ctx).await.unwrap_or_default(); let roles = server.roles(&ctx).await.unwrap_or_default();
@ -185,8 +188,10 @@ pub mod committee {
for channel in channels.values() { for channel in channels.values() {
// we only care about teh channels in teh category // we only care about teh channels in teh category
if let Some(x) = channel.parent_id { if let Some(x) = channel.parent_id {
if x.eq(&config.committee_category) { for category in &categories {
channels_name.insert(channel.name.to_owned(), channel.to_owned()); if x.eq(category) {
channels_name.insert(channel.name.to_owned(), channel.to_owned());
}
} }
} }
} }
@ -197,13 +202,21 @@ pub mod committee {
// a list of all the roles that can be removed from folks who should have them // a list of all the roles that can be removed from folks who should have them
let mut committee_roles = vec![committee_member]; let mut committee_roles = vec![committee_member];
for committee in &committees { let mut category_index = 0;
let mut i = 0;
loop {
if i >= committees.len() {
break;
}
let committee = &committees[i];
// get the role for this committee/club/soc // get the role for this committee/club/soc
let role = match roles_name.get(&committee.name_profile) { let role = match roles_name.get(&committee.name_full) {
Some(x) => Some(x.to_owned()), Some(x) => Some(x.to_owned()),
None => { None => {
// create teh role if it does not exist // create teh role if it does not exist
match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name_profile)).await { match server.create_role(&ctx, |r| r.hoist(false).mentionable(true).name(&committee.name_full)).await {
Ok(x) => Some(x), Ok(x) => Some(x),
Err(_) => None, Err(_) => None,
} }
@ -213,7 +226,7 @@ pub mod committee {
// create teh channel if it does nto exist // create teh channel if it does nto exist
if !channels_name.contains_key(&committee.name_profile) { if !channels_name.contains_key(&committee.name_profile) {
match server match server
.create_channel(&ctx, |c| c.name(&committee.name_profile).kind(ChannelType::Text).category(config.committee_category)) .create_channel(&ctx, |c| c.name(&committee.name_profile).kind(ChannelType::Text).category(categories[category_index]))
.await .await
{ {
Ok(x) => { Ok(x) => {
@ -223,7 +236,14 @@ pub mod committee {
println!("Created channel: {}", &committee.name_profile); println!("Created channel: {}", &committee.name_profile);
} }
Err(x) => { Err(x) => {
dbg!("Unable to create channel: ", x); let tmp = x.to_string();
if x.to_string().contains("Maximum number of channels in category reached (50)") {
category_index += 1;
continue;
}
dbg!("Unable to create channel: ", &tmp, &tmp.contains("Maximum number of channels in category reached (50)"));
} }
} }
}; };
@ -236,42 +256,60 @@ pub mod committee {
// ID in this is the wolves ID, so we need to get a matching discord ID (if one exists) // ID in this is the wolves ID, so we need to get a matching discord ID (if one exists)
if let Some(x) = get_server_member_discord(db, id_wolves).await { if let Some(x) = get_server_member_discord(db, id_wolves).await {
if let Some(member_tmp) = x.discord { if let Some(member_tmp) = x.discord {
let values = users_roles.entry(member_tmp).or_insert(vec![]); let values = users_roles.entry(member_tmp).or_insert(vec![committee_member]);
values.push(r.id); values.push(r.id);
} }
} }
} }
} }
i += 1;
} }
// now we have a map of all users that should get roles time to go through all the folks on teh server // 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 { for member in members {
let roles_current = member.roles(ctx).unwrap_or_default();
let roles_required = match users_roles.get(&member.user.id) { let roles_required = match users_roles.get(&member.user.id) {
None => { None => {
vec![] vec![]
} }
Some(x) => { Some(x) => {
let mut combined = x.to_owned(); let mut tmp = x.to_owned();
// this is the main role, since it provides access to everything. if !tmp.is_empty() {
combined.push(committee_member); tmp.push(RoleId(1226602779968274573));
combined }
tmp
} }
}; };
// get a list of all the roles to remove from someone
let mut roles_rem = vec![]; let mut roles_rem = vec![];
for role in &committee_roles { let mut roles_add = vec![];
if !roles_required.contains(role) { // get a list of all the roles to remove from someone
roles_rem.push(role.to_owned());
let mut roles_current_id = vec![];
for role in &roles_current {
roles_current_id.push(role.id.to_owned());
if !roles_required.contains(&role.id) {
roles_rem.push(role.id.to_owned());
} }
} }
for role in &roles_required {
if !roles_current_id.contains(role) {
roles_add.push(role.to_owned());
}
}
if !roles_rem.is_empty() { if !roles_rem.is_empty() {
member.remove_roles(&ctx, &roles_rem).await.unwrap_or_default(); member.remove_roles(&ctx, &roles_rem).await.unwrap_or_default();
} }
if !roles_required.is_empty() { if !roles_add.is_empty() {
// these roles are flavor roles, only there to make folks mentionable // these roles are flavor roles, only there to make folks mentionable
member.add_roles(&ctx, &roles_required).await.unwrap_or_default(); member.add_roles(&ctx, &roles_add).await.unwrap_or_default();
} else {
member.remove_roles(&ctx, &[RoleId(1226602779968274573)]).await.unwrap_or_default();
} }
} }
@ -303,7 +341,7 @@ pub mod committee {
} }
async fn get_committees(db: &Pool<Sqlite>) -> Vec<Committees> { async fn get_committees(db: &Pool<Sqlite>) -> Vec<Committees> {
sqlx::query_as::<_, Committees>( match sqlx::query_as::<_, Committees>(
r#" r#"
SELECT * SELECT *
FROM committees FROM committees
@ -311,7 +349,13 @@ pub mod committee {
) )
.fetch_all(db) .fetch_all(db)
.await .await
.unwrap_or_default() {
Ok(a) => a,
Err(e) => {
dbg!(e);
vec![]
}
}
} }
async fn get_server_member_discord(db: &Pool<Sqlite>, user: &i64) -> Option<Wolves> { async fn get_server_member_discord(db: &Pool<Sqlite>, user: &i64) -> Option<Wolves> {

View file

@ -87,15 +87,22 @@ pub mod cns {
server, server,
// this is the unique api key for each club/soc // this is the unique api key for each club/soc
wolves_api, wolves_api,
server_name,
.. ..
} = &server_config; } = &server_config;
// dbg!(&server_config);
let existing_tmp = get_server_member(&db, server).await; let existing_tmp = get_server_member(&db, server).await;
let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::<BTreeMap<_, _>>(); let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::<BTreeMap<_, _>>();
// 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![];
let mut server_name_tmp = None;
for user in wolves.get_members(wolves_api).await { for user in wolves.get_members(wolves_api).await {
// dbg!(&user.committee);
if server_name_tmp.is_none() {
server_name_tmp = Some(user.committee.to_owned());
}
let id = user.member_id.parse::<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 => {
@ -117,12 +124,38 @@ pub mod cns {
} }
} }
if let Some(name) = server_name_tmp {
if &name != server_name {
set_server_member(&db, server, &name).await;
}
}
if !user_to_update.is_empty() { if !user_to_update.is_empty() {
update_server(ctx, &server_config, &[], &user_to_update).await; update_server(ctx, &server_config, &[], &user_to_update).await;
} }
} }
} }
async fn set_server_member(db: &Pool<Sqlite>, server: &GuildId, name: &str) {
match sqlx::query_as::<_, Servers>(
"
UPDATE servers
SET server_name = ?
WHERE server = ?
",
)
.bind(name)
.bind(*server.as_u64() as i64)
.fetch_optional(db)
.await
{
Ok(_) => {}
Err(e) => {
println!("Failure to set server name {}", server.as_u64());
println!("{:?}", e);
}
}
}
async fn get_server_member(db: &Pool<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#"
@ -223,7 +256,7 @@ pub mod committees {
" "
INSERT INTO committees (id, name_profile, name_full, name_plain, link, committee) INSERT INTO committees (id, name_profile, name_full, name_plain, link, committee)
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT(id) DO UPDATE SET committee = $4 ON CONFLICT(id) DO UPDATE SET committee = $6
", ",
) )
.bind(committee.id) .bind(committee.id)

View file

@ -262,7 +262,6 @@ pub struct Servers {
pub bot_channel_id: ChannelId, pub bot_channel_id: ChannelId,
// these can be removed in teh future with an API update // these can be removed in teh future with an API update
pub server_name: String, pub server_name: String,
pub wolves_link: String,
} }
impl<'r> FromRow<'r, SqliteRow> for Servers { impl<'r> FromRow<'r, SqliteRow> for Servers {
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> { fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
@ -299,7 +298,6 @@ impl<'r> FromRow<'r, SqliteRow> for Servers {
member_current: row.try_get("member_current")?, member_current: row.try_get("member_current")?,
bot_channel_id, bot_channel_id,
server_name: row.try_get("server_name")?, server_name: row.try_get("server_name")?,
wolves_link: row.try_get("wolves_link")?,
}) })
} }
} }

View file

@ -15,7 +15,9 @@ use serenity::{
}; };
use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_member, DataBase}; use skynet_discord_bot::common::database::{db_init, get_server_config, get_server_member, DataBase};
use skynet_discord_bot::common::set_roles::committee::update_committees; use skynet_discord_bot::common::set_roles::committee::update_committees;
use skynet_discord_bot::common::wolves::committees::Committees;
use skynet_discord_bot::{get_config, Config}; use skynet_discord_bot::{get_config, Config};
use sqlx::{Pool, Sqlite};
use std::sync::Arc; use std::sync::Arc;
use tokio::sync::RwLock; use tokio::sync::RwLock;
@ -67,20 +69,24 @@ impl EventHandler for Handler {
println!("{:?}", e); println!("{:?}", e);
} }
} else { } else {
let msg = format!( let tmp = get_committee(&db, &config_server.server_name).await;
r#" if !tmp.is_empty() {
let committee = &tmp[0];
let msg = format!(
r#"
Welcome {} to the {} server! 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 ``/link_wolves`` to get full access.
"#, "#,
new_member.display_name(), new_member.display_name(),
&config_server.server_name, committee.name_full,
&config_server.wolves_link, committee.link,
&config_server.server, &config_server.server,
&config_server.bot_channel_id &config_server.bot_channel_id
); );
if let Err(err) = new_member.user.direct_message(&ctx, |m| m.content(&msg)).await { if let Err(err) = new_member.user.direct_message(&ctx, |m| m.content(&msg)).await {
dbg!(err); dbg!(err);
}
} }
} }
} }
@ -149,6 +155,20 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
} }
} }
async fn get_committee(db: &Pool<Sqlite>, committee: &str) -> Vec<Committees> {
sqlx::query_as::<_, Committees>(
r#"
SELECT *
FROM committees
WHERE name_plain = ?
"#,
)
.bind(committee)
.fetch_all(db)
.await
.unwrap_or_default()
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let config = get_config(); let config = get_config();