discord-bot/src/main.rs
Brendan Golden 7403f531eb
All checks were successful
/ check_lfs (push) Successful in 11s
/ check_lfs (pull_request) Successful in 12s
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
2025-06-24 00:13:29 +01:00

301 lines
10 KiB
Rust

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::{
async_trait,
client::{Context, EventHandler},
model::{
gateway::{GatewayIntents, Ready},
user::OnlineStatus,
},
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::common::wolves::committees::Committees;
use skynet_discord_bot::{get_config, Config};
use sqlx::{Pool, Sqlite};
use std::sync::Arc;
use tokio::sync::RwLock;
struct Handler;
#[async_trait]
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 data_read = ctx.data.read().await;
data_read.get::<DataBase>().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::<Config>().expect("Expected Config in TypeMap.").clone()
};
let config_global = config_lock.read().await;
// committee server takes priority
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;
return;
}
let config_server = match get_server_config(&db, &new_member.guild_id).await {
None => return,
Some(x) => x,
};
if get_server_member(&db, &new_member.guild_id, &new_member).await.is_ok() {
let mut roles = vec![];
if let Some(role) = &config_server.role_past {
if !new_member.roles.contains(role) {
roles.push(role.to_owned());
}
}
if !new_member.roles.contains(&config_server.role_current) {
roles.push(config_server.role_current.to_owned());
}
if let Err(e) = new_member.add_roles(&ctx, &roles).await {
println!("{:?}", e);
}
} else {
let tmp = get_committee(&db, config_server.wolves_id).await;
if !tmp.is_empty() {
let committee = &tmp[0];
let msg = format!(
r#"
Welcome {} to the {} server!
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,
committee.link,
&config_server.server,
&config_server.bot_channel_id
);
if let Err(err) = new_member.user.direct_message(&ctx, CreateMessage::new().content(&msg)).await {
dbg!(err);
}
}
}
}
// handles role updates
async fn guild_member_update(&self, ctx: Context, _old_data: Option<Member>, new_data: Option<Member>, _: GuildMemberUpdateEvent) {
// get config/db
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;
// check if the role changed is part of the oens for this server
if let Some(x) = new_data {
on_role_change(&db, &ctx, x).await;
}
}
async fn ready(&self, ctx: Context, ready: Ready) {
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::<Config>().expect("Expected Config in TypeMap.").clone()
};
let config = config_lock.read().await;
match Command::set_global_commands(
&ctx.http,
vec![
commands::wolves::register(),
commands::committee::register(),
commands::minecraft::server::add::register(),
commands::minecraft::server::list::register(),
commands::minecraft::server::delete::register(),
],
)
.await
{
Ok(_) => {}
Err(e) => {
println!("{:?}", e)
}
}
// Inter-Committee server
match config.committee_server.set_commands(&ctx.http, vec![commands::count::committee::register()]).await {
Ok(_) => {}
Err(e) => {
println!("{:?}", e)
}
}
// compsoc Server
match config
.compsoc_server
.set_commands(
&ctx.http,
vec![
// commands just for the compsoc server
commands::count::servers::register(),
commands::server_icon::user::register(),
],
)
.await
{
Ok(_) => {}
Err(e) => {
println!("{:?}", e)
}
}
}
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);
let content = match command.data.name.as_str() {
// user commands
"wolves" => match command.data.options.first() {
None => "Invalid Command".to_string(),
Some(x) => match x.name.as_str() {
"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,
"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()),
},
},
// admin commands
"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,
"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())
}
},
// 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()),
},
},
"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,
// 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()),
},
},
"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()),
};
if let Err(why) = command.edit_response(&ctx.http, EditInteractionResponse::new().content(content)).await {
println!("Cannot respond to slash command: {}", why);
}
}
}
}
async fn get_committee(db: &Pool<Sqlite>, wolves_id: i64) -> Vec<Committees> {
sqlx::query_as::<_, Committees>(
r#"
SELECT *
FROM committees
WHERE id = ?
"#,
)
.bind(wolves_id)
.fetch_all(db)
.await
.unwrap_or_default()
}
#[tokio::main]
async fn main() {
let config = get_config();
let db = match db_init(&config).await {
Ok(x) => x,
Err(err) => {
dbg!(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::<Config>(Arc::new(RwLock::new(config)));
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
}
// Finally, start a single shard, and start listening to events.
//
// 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);
}
}