Merge pull request '#17-automate-onboarding-mk-ii' (#30) from #17-automate-onboarding-mk-ii into main
Reviewed-on: #30
This commit is contained in:
commit
25fcc04287
20 changed files with 2131 additions and 1754 deletions
956
Cargo.lock
generated
956
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
22
Cargo.toml
22
Cargo.toml
|
@ -16,28 +16,30 @@ name = "update_minecraft"
|
|||
|
||||
[dependencies]
|
||||
# discord library
|
||||
serenity = { version = "0.11.6", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] }
|
||||
tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] }
|
||||
serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] }
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread", "full"] }
|
||||
|
||||
# wolves api
|
||||
wolves_oxidised = { git = "https://forgejo.skynet.ie/Skynet/wolves-oxidised.git" }
|
||||
wolves_oxidised = { git = "https://forgejo.skynet.ie/Skynet/wolves-oxidised.git", features = ["unstable"] }
|
||||
# wolves_oxidised = { path = "../wolves-oxidised", features = ["unstable"] }
|
||||
|
||||
# to make the http requests
|
||||
surf = "2.3.2"
|
||||
surf = "2.3"
|
||||
|
||||
dotenvy = "0.15.7"
|
||||
dotenvy = "0.15"
|
||||
|
||||
# For sqlite
|
||||
sqlx = { version = "0.7.1", features = [ "runtime-tokio", "sqlite", "migrate" ] }
|
||||
sqlx = { version = "0.8", features = [ "runtime-tokio", "sqlite", "migrate" ] }
|
||||
serde_json = { version = "1.0", features = ["raw_value"] }
|
||||
|
||||
# create random strings
|
||||
rand = "0.8.5"
|
||||
rand = "0.9"
|
||||
|
||||
# fancy time stuff
|
||||
chrono = "0.4.26"
|
||||
chrono = "0.4"
|
||||
|
||||
# for email
|
||||
lettre = "0.10.4"
|
||||
maud = "0.25.0"
|
||||
lettre = "0.11"
|
||||
maud = "0.27"
|
||||
|
||||
serde = "1.0"
|
14
db/migrations/8_committee-mk-ii.sql
Normal file
14
db/migrations/8_committee-mk-ii.sql
Normal file
|
@ -0,0 +1,14 @@
|
|||
-- No longer using the previous committee table
|
||||
DROP TABLE committee;
|
||||
|
||||
-- new table pulling from teh api
|
||||
CREATE TABLE IF NOT EXISTS committees (
|
||||
id integer PRIMARY KEY,
|
||||
name_profile text not null,
|
||||
name_plain text not null,
|
||||
name_full text not null,
|
||||
link text not null,
|
||||
committee text not null
|
||||
);
|
||||
|
||||
ALTER TABLE servers DROP COLUMN wolves_link;
|
|
@ -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.
|
||||
5. ``bot_channel`` is a channel that folks are recommended to use the bot.
|
||||
* 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.
|
||||
|
||||
|
|
|
@ -4,7 +4,10 @@ use serenity::{
|
|||
model::gateway::{GatewayIntents, Ready},
|
||||
Client,
|
||||
};
|
||||
use skynet_discord_bot::{db_init, get_config, get_data::get_wolves, Config, DataBase};
|
||||
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 std::{process, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
|
@ -13,7 +16,10 @@ async fn main() {
|
|||
let config = get_config();
|
||||
let db = match db_init(&config).await {
|
||||
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
|
||||
|
@ -43,8 +49,12 @@ impl EventHandler for Handler {
|
|||
let ctx = Arc::new(ctx);
|
||||
println!("{} is connected!", ready.user.name);
|
||||
|
||||
// get the data for each individual club/soc
|
||||
get_wolves(&ctx).await;
|
||||
|
||||
// get teh data for the clubs/socs committees
|
||||
get_cns(&ctx).await;
|
||||
|
||||
// finish up
|
||||
process::exit(0);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
use skynet_discord_bot::{db_init, get_config, get_minecraft_config, update_server, whitelist_wipe};
|
||||
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::get_config;
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[tokio::main]
|
||||
|
|
|
@ -4,7 +4,9 @@ use serenity::{
|
|||
model::gateway::{GatewayIntents, Ready},
|
||||
Client,
|
||||
};
|
||||
use skynet_discord_bot::{db_init, get_config, get_server_config_bulk, set_roles, Config, DataBase};
|
||||
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 tokio::sync::RwLock;
|
||||
|
||||
|
@ -43,14 +45,18 @@ impl EventHandler for Handler {
|
|||
let ctx = Arc::new(ctx);
|
||||
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
|
||||
process::exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
async fn bulk_check(ctx: Arc<Context>) {
|
||||
async fn check_bulk(ctx: Arc<Context>) {
|
||||
let db_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
|
||||
|
@ -59,6 +65,6 @@ async fn bulk_check(ctx: Arc<Context>) {
|
|||
let db = db_lock.read().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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,98 +1,57 @@
|
|||
use serenity::{
|
||||
builder::CreateApplicationCommand,
|
||||
client::Context,
|
||||
model::{
|
||||
application::interaction::application_command::ApplicationCommandInteraction,
|
||||
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
||||
},
|
||||
};
|
||||
use skynet_discord_bot::get_data::get_wolves;
|
||||
use skynet_discord_bot::{get_server_config, is_admin, set_roles::update_server, DataBase, Servers};
|
||||
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption};
|
||||
use serenity::{builder::CreateCommand, client::Context};
|
||||
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: &CommandInteraction, ctx: &Context) -> String {
|
||||
// check if user has high enough permisssions
|
||||
if let Some(msg) = is_admin(command, ctx).await {
|
||||
return msg;
|
||||
}
|
||||
|
||||
let api_key = if let CommandDataOptionValue::String(key) = command
|
||||
.data
|
||||
.options
|
||||
.first()
|
||||
.expect("Expected user option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected user object")
|
||||
let wolves_api = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::String(key),
|
||||
..
|
||||
}) = command.data.options.first()
|
||||
{
|
||||
key
|
||||
key.to_string()
|
||||
} else {
|
||||
return "Please provide a wolves API key".to_string();
|
||||
};
|
||||
|
||||
let role_current = if let CommandDataOptionValue::Role(role) = command
|
||||
.data
|
||||
.options
|
||||
.get(1)
|
||||
.expect("Expected role option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected role object")
|
||||
let role_current = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::Role(role),
|
||||
..
|
||||
}) = command.data.options.get(1)
|
||||
{
|
||||
role.id.to_owned()
|
||||
role.to_owned()
|
||||
} else {
|
||||
return "Please provide a valid role for ``Role Current``".to_string();
|
||||
};
|
||||
|
||||
let mut role_past = None;
|
||||
if let Some(x) = command.data.options.get(5) {
|
||||
if let Some(CommandDataOptionValue::Role(role)) = &x.resolved {
|
||||
role_past = Some(role.id.to_owned());
|
||||
}
|
||||
let role_past = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::Role(role),
|
||||
..
|
||||
}) = command.data.options.get(5)
|
||||
{
|
||||
Some(role.to_owned())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let bot_channel_id = if let CommandDataOptionValue::Channel(channel) = command
|
||||
.data
|
||||
.options
|
||||
.get(2)
|
||||
.expect("Expected channel option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected channel object")
|
||||
let bot_channel_id = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::Channel(channel),
|
||||
..
|
||||
}) = command.data.options.get(2)
|
||||
{
|
||||
channel.id.to_owned()
|
||||
channel.to_owned()
|
||||
} else {
|
||||
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 data_read = ctx.data.read().await;
|
||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
||||
|
@ -101,14 +60,13 @@ pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> Stri
|
|||
|
||||
let server_data = Servers {
|
||||
server: command.guild_id.unwrap_or_default(),
|
||||
wolves_api: api_key.to_owned(),
|
||||
wolves_api,
|
||||
role_past,
|
||||
role_current,
|
||||
member_past: 0,
|
||||
member_current: 0,
|
||||
bot_channel_id,
|
||||
server_name: server_name.to_owned(),
|
||||
wolves_link: wolves_link.to_string(),
|
||||
server_name: "".to_string(),
|
||||
};
|
||||
|
||||
match add_server(&db, ctx, &server_data).await {
|
||||
|
@ -122,71 +80,30 @@ pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> Stri
|
|||
"Added/Updated server info".to_string()
|
||||
}
|
||||
|
||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||
command
|
||||
.name("add")
|
||||
pub fn register() -> CreateCommand {
|
||||
CreateCommand::new("add")
|
||||
.description("Enable the bot for this discord")
|
||||
.create_option(|option| {
|
||||
option
|
||||
.name("api_key")
|
||||
.description("UL Wolves API Key")
|
||||
.kind(CommandOptionType::String)
|
||||
.required(true)
|
||||
})
|
||||
.create_option(|option| {
|
||||
option
|
||||
.name("role_current")
|
||||
.description("Role for Current members")
|
||||
.kind(CommandOptionType::Role)
|
||||
.required(true)
|
||||
})
|
||||
.create_option(|option| {
|
||||
option
|
||||
.name("bot_channel")
|
||||
.description("Safe space for folks to use the bot commands.")
|
||||
.kind(CommandOptionType::Channel)
|
||||
.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| {
|
||||
option
|
||||
.name("role_past")
|
||||
.description("Role for Past members")
|
||||
.kind(CommandOptionType::Role)
|
||||
.required(false)
|
||||
})
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::String, "api_key", "UL Wolves API Key").required(true))
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::Role, "role_current", "Role for Current members").required(true))
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::Channel, "bot_channel", "Safe space for folks to use the bot commands.").required(true))
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::Role, "role_past", "Role for Past members").required(false))
|
||||
}
|
||||
|
||||
async fn add_server(db: &Pool<Sqlite>, ctx: &Context, server: &Servers) -> Result<Option<Servers>, Error> {
|
||||
let existing = get_server_config(db, &server.server).await;
|
||||
let role_past = server.role_past.map(|x| *x.as_u64() as i64);
|
||||
let role_past = server.role_past.map(|x| x.get() as i64);
|
||||
|
||||
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)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
||||
INSERT OR REPLACE INTO servers (server, wolves_api, role_past, role_current, bot_channel_id)
|
||||
VALUES (?1, ?2, ?3, ?4, ?5)
|
||||
",
|
||||
)
|
||||
.bind(*server.server.as_u64() as i64)
|
||||
.bind(server.server.get() as i64)
|
||||
.bind(&server.wolves_api)
|
||||
.bind(role_past)
|
||||
.bind(*server.role_current.as_u64() as i64)
|
||||
.bind(*server.bot_channel_id.as_u64() as i64)
|
||||
.bind(&server.server_name)
|
||||
.bind(&server.wolves_link)
|
||||
.bind(server.role_current.get() as i64)
|
||||
.bind(server.bot_channel_id.get() as i64)
|
||||
.fetch_optional(db)
|
||||
.await;
|
||||
|
||||
|
|
|
@ -1,311 +0,0 @@
|
|||
use lettre::{
|
||||
message::{header, MultiPart, SinglePart},
|
||||
transport::smtp::{self, authentication::Credentials},
|
||||
Message, SmtpTransport, Transport,
|
||||
};
|
||||
use maud::html;
|
||||
use serenity::{
|
||||
builder::CreateApplicationCommand,
|
||||
client::Context,
|
||||
model::{
|
||||
application::interaction::application_command::ApplicationCommandInteraction,
|
||||
id::UserId,
|
||||
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
||||
},
|
||||
};
|
||||
use skynet_discord_bot::{random_string, Config, DataBase};
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
pub mod link {
|
||||
use super::*;
|
||||
use serenity::model::id::GuildId;
|
||||
use skynet_discord_bot::Committee;
|
||||
|
||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||
let committee_server = GuildId(1220150752656363520);
|
||||
match command.guild_id {
|
||||
None => {
|
||||
return "Not in correct discord server.".to_string();
|
||||
}
|
||||
Some(x) => {
|
||||
if x != committee_server {
|
||||
return "Not in correct discord server.".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let option = command
|
||||
.data
|
||||
.options
|
||||
.first()
|
||||
.expect("Expected email option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected email object");
|
||||
|
||||
let email = if let CommandDataOptionValue::String(email) = option {
|
||||
email.trim()
|
||||
} else {
|
||||
return "Please provide a valid committee email.".to_string();
|
||||
};
|
||||
|
||||
// fail early
|
||||
if !email.ends_with("@ulwolves.ie") {
|
||||
return "Please use a @ulwolves.ie address you have access to.".to_string();
|
||||
}
|
||||
|
||||
let db_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
data_read.get::<DataBase>().expect("Expected Databse 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;
|
||||
|
||||
if get_server_member_discord(&db, &command.user.id).await.is_some() {
|
||||
return "Already linked".to_string();
|
||||
}
|
||||
|
||||
if get_verify_from_db(&db, &command.user.id).await.is_some() {
|
||||
return "Linking already in process, please check email.".to_string();
|
||||
}
|
||||
|
||||
// generate a auth key
|
||||
let auth = random_string(20);
|
||||
match send_mail(&config, email, &auth, &command.user.name) {
|
||||
Ok(_) => match save_to_db(&db, email, &auth, &command.user.id).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
return format!("Unable to save to db {} {e:?}", email);
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
return format!("Unable to send mail to {} {e:?}", email);
|
||||
}
|
||||
}
|
||||
|
||||
format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email)
|
||||
}
|
||||
|
||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||
command
|
||||
.name("link_committee")
|
||||
.description("Verify you are a committee member")
|
||||
.create_option(|option| {
|
||||
option
|
||||
.name("email")
|
||||
.description("UL Wolves Committee Email")
|
||||
.kind(CommandOptionType::String)
|
||||
.required(true)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_server_member_discord(db: &Pool<Sqlite>, user: &UserId) -> Option<Committee> {
|
||||
sqlx::query_as::<_, Committee>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM committee
|
||||
WHERE discord = ?
|
||||
"#,
|
||||
)
|
||||
.bind(*user.as_u64() as i64)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result<smtp::response::Response, smtp::Error> {
|
||||
let sender = format!("UL Computer Society <{}>", &config.mail_user);
|
||||
|
||||
// Create the html we want to send.
|
||||
let html = html! {
|
||||
head {
|
||||
title { "Hello from Skynet!" }
|
||||
style type="text/css" {
|
||||
"h2, h4 { font-family: Arial, Helvetica, sans-serif; }"
|
||||
}
|
||||
}
|
||||
div style="display: flex; flex-direction: column; align-items: center;" {
|
||||
h2 { "Hello from Skynet!" }
|
||||
// Substitute in the name of our recipient.
|
||||
p { "Hi " (user) "," }
|
||||
p {
|
||||
"Please use " pre { "/verify_committee code: " (auth)} " to verify your discord account."
|
||||
}
|
||||
p {
|
||||
"Skynet Team"
|
||||
br;
|
||||
"UL Computer Society"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let body_text = format!(
|
||||
r#"
|
||||
Hi {user}
|
||||
|
||||
Please use "/verify_committee code: {auth}" to verify your discord account.
|
||||
|
||||
Skynet Team
|
||||
UL Computer Society
|
||||
"#
|
||||
);
|
||||
|
||||
// Build the message.
|
||||
let email = Message::builder()
|
||||
.from(sender.parse().unwrap())
|
||||
.to(mail.parse().unwrap())
|
||||
.subject("Skynet-Discord: Link Committee.")
|
||||
.multipart(
|
||||
// This is composed of two parts.
|
||||
// also helps not trip spam settings (uneven number of url's
|
||||
MultiPart::alternative()
|
||||
.singlepart(SinglePart::builder().header(header::ContentType::TEXT_PLAIN).body(body_text))
|
||||
.singlepart(SinglePart::builder().header(header::ContentType::TEXT_HTML).body(html.into_string())),
|
||||
)
|
||||
.expect("failed to build email");
|
||||
|
||||
let creds = Credentials::new(config.mail_user.clone(), config.mail_pass.clone());
|
||||
|
||||
// Open a remote connection to gmail using STARTTLS
|
||||
let mailer = SmtpTransport::starttls_relay(&config.mail_smtp)?.credentials(creds).build();
|
||||
|
||||
// Send the email
|
||||
mailer.send(&email)
|
||||
}
|
||||
|
||||
pub async fn get_verify_from_db(db: &Pool<Sqlite>, user: &UserId) -> Option<Committee> {
|
||||
sqlx::query_as::<_, Committee>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM committee
|
||||
WHERE discord = ?
|
||||
"#,
|
||||
)
|
||||
.bind(*user.as_u64() as i64)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
|
||||
async fn save_to_db(db: &Pool<Sqlite>, email: &str, auth: &str, user: &UserId) -> Result<Option<Committee>, sqlx::Error> {
|
||||
sqlx::query_as::<_, Committee>(
|
||||
"
|
||||
INSERT INTO committee (email, discord, auth_code)
|
||||
VALUES (?1, ?2, ?3)
|
||||
",
|
||||
)
|
||||
.bind(email.to_owned())
|
||||
.bind(*user.as_u64() as i64)
|
||||
.bind(auth.to_owned())
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
pub mod verify {
|
||||
use super::*;
|
||||
use crate::commands::committee::link::get_verify_from_db;
|
||||
use serenity::model::id::{GuildId, RoleId};
|
||||
use serenity::model::user::User;
|
||||
use skynet_discord_bot::Committee;
|
||||
use sqlx::Error;
|
||||
|
||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||
let committee_server = GuildId(1220150752656363520);
|
||||
match command.guild_id {
|
||||
None => {
|
||||
return "Not in correct discord server.".to_string();
|
||||
}
|
||||
Some(x) => {
|
||||
if x != committee_server {
|
||||
return "Not in correct discord server.".to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// check if user has used /link_committee
|
||||
let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await {
|
||||
x
|
||||
} else {
|
||||
return "Please use /link_committee first".to_string();
|
||||
};
|
||||
|
||||
let option = command
|
||||
.data
|
||||
.options
|
||||
.first()
|
||||
.expect("Expected code option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected code object");
|
||||
|
||||
let code = if let CommandDataOptionValue::String(code) = option {
|
||||
code
|
||||
} else {
|
||||
return "Please provide a verification code".to_string();
|
||||
};
|
||||
|
||||
if &details.auth_code != code {
|
||||
return "Invalid verification code".to_string();
|
||||
}
|
||||
|
||||
match set_discord(&db, &command.user.id).await {
|
||||
Ok(_) => {
|
||||
// get teh right roles for the user
|
||||
set_server_roles(&command.user, ctx).await;
|
||||
"Discord username linked to Wolves for committee".to_string()
|
||||
}
|
||||
Err(e) => {
|
||||
println!("{:?}", e);
|
||||
"Failed to save, please try /link_committee again".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||
command
|
||||
.name("verify_committee")
|
||||
.description("Verify Wolves Committee Email")
|
||||
.create_option(|option| {
|
||||
option
|
||||
.name("code")
|
||||
.description("Code from verification email")
|
||||
.kind(CommandOptionType::String)
|
||||
.required(true)
|
||||
})
|
||||
}
|
||||
|
||||
async fn set_discord(db: &Pool<Sqlite>, discord: &UserId) -> Result<Option<Committee>, Error> {
|
||||
sqlx::query_as::<_, Committee>(
|
||||
"
|
||||
UPDATE committee
|
||||
SET committee = 1
|
||||
WHERE discord = ?
|
||||
",
|
||||
)
|
||||
.bind(*discord.as_u64() as i64)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn set_server_roles(discord: &User, ctx: &Context) {
|
||||
let committee_server = GuildId(1220150752656363520);
|
||||
if let Ok(mut member) = committee_server.member(&ctx.http, &discord.id).await {
|
||||
let committee_member = RoleId(1226602779968274573);
|
||||
if let Err(e) = member.add_role(&ctx, committee_member).await {
|
||||
println!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,21 +4,17 @@ use lettre::{
|
|||
Message, SmtpTransport, Transport,
|
||||
};
|
||||
use maud::html;
|
||||
use serenity::{
|
||||
builder::CreateApplicationCommand,
|
||||
client::Context,
|
||||
model::{
|
||||
application::interaction::application_command::ApplicationCommandInteraction,
|
||||
id::UserId,
|
||||
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
||||
},
|
||||
};
|
||||
use skynet_discord_bot::{get_now_iso, random_string, Config, DataBase, Wolves, WolvesVerify};
|
||||
use serenity::{builder::CreateCommand, client::Context, model::id::UserId};
|
||||
use skynet_discord_bot::common::database::{DataBase, Wolves, WolvesVerify};
|
||||
use skynet_discord_bot::{get_now_iso, random_string, Config};
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
pub mod link {
|
||||
use super::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption};
|
||||
|
||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||
let db_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
||||
|
@ -41,16 +37,11 @@ pub mod link {
|
|||
return "Linking already in process, please check email.".to_string();
|
||||
}
|
||||
|
||||
let option = command
|
||||
.data
|
||||
.options
|
||||
.first()
|
||||
.expect("Expected email option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected email object");
|
||||
|
||||
let email = if let CommandDataOptionValue::String(email) = option {
|
||||
let email = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::String(email),
|
||||
..
|
||||
}) = command.data.options.first()
|
||||
{
|
||||
email.trim()
|
||||
} else {
|
||||
return "Please provide a valid user".to_string();
|
||||
|
@ -112,11 +103,10 @@ pub mod link {
|
|||
format!("Verification email sent to {}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.", email)
|
||||
}
|
||||
|
||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||
command
|
||||
.name("link_wolves")
|
||||
pub fn register() -> CreateCommand {
|
||||
CreateCommand::new("link_wolves")
|
||||
.description("Set Wolves Email")
|
||||
.create_option(|option| option.name("email").description("UL Wolves Email").kind(CommandOptionType::String).required(true))
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::String, "email", "UL Wolves Email").required(true))
|
||||
}
|
||||
|
||||
pub async fn get_server_member_discord(db: &Pool<Sqlite>, user: &UserId) -> Option<Wolves> {
|
||||
|
@ -127,7 +117,7 @@ pub mod link {
|
|||
WHERE discord = ?
|
||||
"#,
|
||||
)
|
||||
.bind(*user.as_u64() as i64)
|
||||
.bind(user.get() as i64)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.ok()
|
||||
|
@ -239,7 +229,7 @@ pub mod link {
|
|||
WHERE discord = ?
|
||||
"#,
|
||||
)
|
||||
.bind(*user.as_u64() as i64)
|
||||
.bind(user.get() as i64)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.ok()
|
||||
|
@ -253,13 +243,26 @@ pub mod link {
|
|||
",
|
||||
)
|
||||
.bind(record.email.to_owned())
|
||||
.bind(*user.as_u64() as i64)
|
||||
.bind(user.get() as i64)
|
||||
.bind(auth.to_owned())
|
||||
.bind(get_now_iso(false))
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(untagged)]
|
||||
pub enum WolvesResultUserResult {
|
||||
B(bool),
|
||||
S(String),
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct WolvesResultUser {
|
||||
success: i64,
|
||||
result: WolvesResultUserResult,
|
||||
}
|
||||
|
||||
async fn save_to_db_user(db: &Pool<Sqlite>, id_wolves: i64, email: &str) -> Result<Option<Wolves>, sqlx::Error> {
|
||||
sqlx::query_as::<_, Wolves>(
|
||||
"
|
||||
|
@ -278,11 +281,13 @@ pub mod link {
|
|||
pub mod verify {
|
||||
use super::*;
|
||||
use crate::commands::link_email::link::{db_pending_clear_expired, get_verify_from_db};
|
||||
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption};
|
||||
use serenity::model::user::User;
|
||||
use skynet_discord_bot::{get_server_config, ServerMembersWolves, Servers};
|
||||
use skynet_discord_bot::common::database::get_server_config;
|
||||
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: &CommandInteraction, ctx: &Context) -> String {
|
||||
let db_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
||||
|
@ -296,16 +301,11 @@ pub mod verify {
|
|||
return "Please use /link_wolves first".to_string();
|
||||
};
|
||||
|
||||
let option = command
|
||||
.data
|
||||
.options
|
||||
.first()
|
||||
.expect("Expected code option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected code object");
|
||||
|
||||
let code = if let CommandDataOptionValue::String(code) = option {
|
||||
let code = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::String(code),
|
||||
..
|
||||
}) = command.data.options.first()
|
||||
{
|
||||
code
|
||||
} else {
|
||||
return "Please provide a verification code".to_string();
|
||||
|
@ -337,14 +337,10 @@ pub mod verify {
|
|||
"Failed to verify".to_string()
|
||||
}
|
||||
|
||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||
command.name("verify").description("Verify Wolves Email").create_option(|option| {
|
||||
option
|
||||
.name("code")
|
||||
.description("Code from verification email")
|
||||
.kind(CommandOptionType::String)
|
||||
.required(true)
|
||||
})
|
||||
pub fn register() -> CreateCommand {
|
||||
CreateCommand::new("verify")
|
||||
.description("Verify Wolves Email")
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::String, "code", "Code from verification email").required(true))
|
||||
}
|
||||
|
||||
async fn db_pending_clear_successful(pool: &Pool<Sqlite>, user: &UserId) -> Result<Option<WolvesVerify>, Error> {
|
||||
|
@ -355,7 +351,7 @@ pub mod verify {
|
|||
WHERE discord = ?
|
||||
"#,
|
||||
)
|
||||
.bind(*user.as_u64() as i64)
|
||||
.bind(user.get() as i64)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
}
|
||||
|
@ -368,7 +364,7 @@ pub mod verify {
|
|||
WHERE email = ?
|
||||
",
|
||||
)
|
||||
.bind(*discord.as_u64() as i64)
|
||||
.bind(discord.get() as i64)
|
||||
.bind(email)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
|
@ -377,7 +373,7 @@ pub mod verify {
|
|||
async fn set_server_roles(db: &Pool<Sqlite>, discord: &User, ctx: &Context) {
|
||||
if let Ok(servers) = get_servers(db, &discord.id).await {
|
||||
for server in servers {
|
||||
if let Ok(mut member) = server.server.member(&ctx.http, &discord.id).await {
|
||||
if let Ok(member) = server.server.member(&ctx.http, &discord.id).await {
|
||||
if let Some(config) = get_server_config(db, &server.server).await {
|
||||
let Servers {
|
||||
role_past,
|
||||
|
@ -415,7 +411,7 @@ pub mod verify {
|
|||
WHERE discord = ?
|
||||
",
|
||||
)
|
||||
.bind(*discord.as_u64() as i64)
|
||||
.bind(discord.get() as i64)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
use serenity::{
|
||||
builder::CreateApplicationCommand,
|
||||
client::Context,
|
||||
model::{
|
||||
application::interaction::application_command::ApplicationCommandInteraction,
|
||||
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
||||
},
|
||||
};
|
||||
use serenity::{builder::CreateCommand, client::Context};
|
||||
|
||||
use skynet_discord_bot::DataBase;
|
||||
use skynet_discord_bot::common::database::DataBase;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
pub(crate) mod user {
|
||||
|
@ -16,31 +9,21 @@ pub(crate) mod user {
|
|||
use super::*;
|
||||
use crate::commands::link_email::link::get_server_member_discord;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption};
|
||||
use serenity::model::id::UserId;
|
||||
use skynet_discord_bot::{whitelist_update, Config, Minecraft, Wolves};
|
||||
use skynet_discord_bot::common::database::Wolves;
|
||||
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 {
|
||||
command
|
||||
.name("link_minecraft")
|
||||
pub fn register() -> CreateCommand {
|
||||
CreateCommand::new("link_minecraft")
|
||||
.description("Link your minecraft account")
|
||||
.create_option(|option| {
|
||||
option
|
||||
.name("minecraft_username")
|
||||
.description("Your Minecraft username")
|
||||
.kind(CommandOptionType::String)
|
||||
.required(true)
|
||||
})
|
||||
.create_option(|option| {
|
||||
option
|
||||
.name("bedrock_account")
|
||||
.description("Is this a Bedrock account?")
|
||||
.kind(CommandOptionType::Boolean)
|
||||
.required(false)
|
||||
})
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::String, "minecraft_username", "Your Minecraft username").required(true))
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::Boolean, "bedrock_account", "Is this a Bedrock account?").required(false))
|
||||
}
|
||||
|
||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||
let db_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
|
||||
|
@ -58,14 +41,10 @@ pub(crate) mod user {
|
|||
return "Not linked with wolves, please use ``/link_wolves`` with your wolves email.".to_string();
|
||||
}
|
||||
|
||||
let username = if let CommandDataOptionValue::String(username) = command
|
||||
.data
|
||||
.options
|
||||
.first()
|
||||
.expect("Expected username option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected username object")
|
||||
let username = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::String(username),
|
||||
..
|
||||
}) = command.data.options.first()
|
||||
{
|
||||
username.trim()
|
||||
} else {
|
||||
|
@ -73,12 +52,15 @@ pub(crate) mod user {
|
|||
};
|
||||
|
||||
// this is always true unless they state its not
|
||||
let mut java = true;
|
||||
if let Some(x) = command.data.options.get(1) {
|
||||
if let Some(CommandDataOptionValue::Boolean(z)) = x.to_owned().resolved {
|
||||
java = !z;
|
||||
}
|
||||
}
|
||||
let java = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::Boolean(z),
|
||||
..
|
||||
}) = command.data.options.get(1)
|
||||
{
|
||||
!z
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
let username_mc;
|
||||
if java {
|
||||
|
@ -128,7 +110,7 @@ pub(crate) mod user {
|
|||
WHERE discord = ?1;
|
||||
",
|
||||
)
|
||||
.bind(*user.as_u64() as i64)
|
||||
.bind(user.get() as i64)
|
||||
.bind(minecraft)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
|
@ -173,7 +155,7 @@ pub(crate) mod user {
|
|||
WHERE discord = ?1;
|
||||
",
|
||||
)
|
||||
.bind(*user.as_u64() as i64)
|
||||
.bind(user.get() as i64)
|
||||
.bind(minecraft)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
|
@ -192,7 +174,7 @@ pub(crate) mod user {
|
|||
) sub on minecraft.server_discord = sub.server
|
||||
",
|
||||
)
|
||||
.bind(*discord.as_u64() as i64)
|
||||
.bind(discord.get() as i64)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
}
|
||||
|
@ -203,23 +185,22 @@ pub(crate) mod server {
|
|||
use super::*;
|
||||
|
||||
pub(crate) mod add {
|
||||
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption};
|
||||
use serenity::model::id::GuildId;
|
||||
use sqlx::Error;
|
||||
// this is to managfe the server side of commands related to minecraft
|
||||
use super::*;
|
||||
use skynet_discord_bot::{is_admin, update_server, Config, Minecraft};
|
||||
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 {
|
||||
command.name("minecraft_add").description("Add a minecraft server").create_option(|option| {
|
||||
option
|
||||
.name("server_id")
|
||||
.description("ID of the Minecraft server hosted by the Computer Society")
|
||||
.kind(CommandOptionType::String)
|
||||
.required(true)
|
||||
})
|
||||
pub fn register() -> CreateCommand {
|
||||
CreateCommand::new("minecraft_add").description("Add a minecraft server").add_option(
|
||||
CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society").required(true),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||
// check if user has high enough permisssions
|
||||
if let Some(msg) = is_admin(command, ctx).await {
|
||||
return msg;
|
||||
|
@ -229,16 +210,12 @@ pub(crate) mod server {
|
|||
Some(x) => x,
|
||||
};
|
||||
|
||||
let server_minecraft = if let CommandDataOptionValue::String(id) = command
|
||||
.data
|
||||
.options
|
||||
.first()
|
||||
.expect("Expected server_id option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected server_id object")
|
||||
let server_minecraft = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::String(id),
|
||||
..
|
||||
}) = command.data.options.first()
|
||||
{
|
||||
id.to_owned()
|
||||
id.to_string()
|
||||
} else {
|
||||
return String::from("Expected Server ID");
|
||||
};
|
||||
|
@ -275,7 +252,7 @@ pub(crate) mod server {
|
|||
VALUES (?1, ?2)
|
||||
",
|
||||
)
|
||||
.bind(*discord.as_u64() as i64)
|
||||
.bind(discord.get() as i64)
|
||||
.bind(minecraft)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
|
@ -283,16 +260,18 @@ pub(crate) mod server {
|
|||
}
|
||||
|
||||
pub(crate) mod list {
|
||||
use serenity::builder::CreateApplicationCommand;
|
||||
use serenity::all::CommandInteraction;
|
||||
use serenity::builder::CreateCommand;
|
||||
use serenity::client::Context;
|
||||
use serenity::model::prelude::application_command::ApplicationCommandInteraction;
|
||||
use skynet_discord_bot::{get_minecraft_config_server, is_admin, server_information, Config, DataBase};
|
||||
use skynet_discord_bot::common::database::DataBase;
|
||||
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 {
|
||||
command.name("minecraft_list").description("List your minecraft servers")
|
||||
pub fn register() -> CreateCommand {
|
||||
CreateCommand::new("minecraft_list").description("List your minecraft servers")
|
||||
}
|
||||
|
||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||
if let Some(msg) = is_admin(command, ctx).await {
|
||||
return msg;
|
||||
}
|
||||
|
@ -342,25 +321,22 @@ pub(crate) mod server {
|
|||
}
|
||||
|
||||
pub(crate) mod delete {
|
||||
use serenity::builder::CreateApplicationCommand;
|
||||
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption};
|
||||
use serenity::builder::CreateCommand;
|
||||
use serenity::client::Context;
|
||||
use serenity::model::application::command::CommandOptionType;
|
||||
use serenity::model::id::GuildId;
|
||||
use serenity::model::prelude::application_command::{ApplicationCommandInteraction, CommandDataOptionValue};
|
||||
use skynet_discord_bot::{is_admin, DataBase, Minecraft};
|
||||
use skynet_discord_bot::common::database::DataBase;
|
||||
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 {
|
||||
command.name("minecraft_delete").description("Delete a minecraft server").create_option(|option| {
|
||||
option
|
||||
.name("server_id")
|
||||
.description("ID of the Minecraft server hosted by the Computer Society")
|
||||
.kind(CommandOptionType::String)
|
||||
.required(true)
|
||||
})
|
||||
pub fn register() -> CreateCommand {
|
||||
CreateCommand::new("minecraft_delete").description("Delete a minecraft server").add_option(
|
||||
CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society").required(true),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||
// check if user has high enough permisssions
|
||||
if let Some(msg) = is_admin(command, ctx).await {
|
||||
return msg;
|
||||
|
@ -370,16 +346,12 @@ pub(crate) mod server {
|
|||
Some(x) => x,
|
||||
};
|
||||
|
||||
let server_minecraft = if let CommandDataOptionValue::String(id) = command
|
||||
.data
|
||||
.options
|
||||
.first()
|
||||
.expect("Expected server_id option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected server_id object")
|
||||
let server_minecraft = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::String(id),
|
||||
..
|
||||
}) = command.data.options.first()
|
||||
{
|
||||
id.to_owned()
|
||||
id.to_string()
|
||||
} else {
|
||||
return String::from("Expected Server ID");
|
||||
};
|
||||
|
@ -410,7 +382,7 @@ pub(crate) mod server {
|
|||
WHERE server_discord = ?1 AND server_minecraft = ?2
|
||||
",
|
||||
)
|
||||
.bind(*discord.as_u64() as i64)
|
||||
.bind(discord.get() as i64)
|
||||
.bind(minecraft)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
pub mod add_server;
|
||||
pub mod committee;
|
||||
pub mod link_email;
|
||||
pub mod minecraft;
|
||||
pub mod role_adder;
|
||||
|
|
|
@ -1,62 +1,45 @@
|
|||
use serenity::{
|
||||
builder::CreateApplicationCommand,
|
||||
client::Context,
|
||||
model::{
|
||||
application::interaction::application_command::ApplicationCommandInteraction,
|
||||
prelude::{command::CommandOptionType, interaction::application_command::CommandDataOptionValue},
|
||||
},
|
||||
};
|
||||
use serenity::client::Context;
|
||||
|
||||
use skynet_discord_bot::{is_admin, DataBase, RoleAdder};
|
||||
use skynet_discord_bot::common::database::{DataBase, RoleAdder};
|
||||
use skynet_discord_bot::is_admin;
|
||||
use sqlx::{Error, Pool, Sqlite};
|
||||
|
||||
pub mod edit {
|
||||
use super::*;
|
||||
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption};
|
||||
|
||||
pub async fn run(command: &ApplicationCommandInteraction, ctx: &Context) -> String {
|
||||
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
|
||||
// check if user has high enough permisssions
|
||||
if let Some(msg) = is_admin(command, ctx).await {
|
||||
return msg;
|
||||
}
|
||||
|
||||
let role_a = if let CommandDataOptionValue::Role(role) = command
|
||||
.data
|
||||
.options
|
||||
.first()
|
||||
.expect("Expected role option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected role object")
|
||||
let role_a = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::Role(role),
|
||||
..
|
||||
}) = command.data.options.first()
|
||||
{
|
||||
role.id.to_owned()
|
||||
role.to_owned()
|
||||
} else {
|
||||
return "Please provide a valid role for ``Role Current``".to_string();
|
||||
};
|
||||
|
||||
let role_b = if let CommandDataOptionValue::Role(role) = command
|
||||
.data
|
||||
.options
|
||||
.get(1)
|
||||
.expect("Expected role option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected role object")
|
||||
let role_b = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::Role(role),
|
||||
..
|
||||
}) = command.data.options.get(1)
|
||||
{
|
||||
role.id.to_owned()
|
||||
role.to_owned()
|
||||
} else {
|
||||
return "Please provide a valid role for ``Role Current``".to_string();
|
||||
};
|
||||
|
||||
let role_c = if let CommandDataOptionValue::Role(role) = command
|
||||
.data
|
||||
.options
|
||||
.get(2)
|
||||
.expect("Expected role option")
|
||||
.resolved
|
||||
.as_ref()
|
||||
.expect("Expected role object")
|
||||
let role_c = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::Role(role),
|
||||
..
|
||||
}) = command.data.options.get(2)
|
||||
{
|
||||
role.id.to_owned()
|
||||
role.to_owned()
|
||||
} else {
|
||||
return "Please provide a valid role for ``Role Current``".to_string();
|
||||
};
|
||||
|
@ -69,14 +52,15 @@ pub mod edit {
|
|||
return "Role C cannot be same as A or B".to_string();
|
||||
}
|
||||
|
||||
let mut delete = false;
|
||||
|
||||
if let Some(x) = command.data.options.get(3) {
|
||||
let tmp = x.to_owned();
|
||||
if let Some(CommandDataOptionValue::Boolean(z)) = tmp.resolved {
|
||||
delete = z;
|
||||
}
|
||||
}
|
||||
let delete = if let Some(CommandDataOption {
|
||||
value: CommandDataOptionValue::Boolean(z),
|
||||
..
|
||||
}) = command.data.options.get(3)
|
||||
{
|
||||
*z
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let db_lock = {
|
||||
let data_read = ctx.data.read().await;
|
||||
|
@ -123,32 +107,13 @@ pub mod edit {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn register(command: &mut CreateApplicationCommand) -> &mut CreateApplicationCommand {
|
||||
command
|
||||
.name("roles_adder")
|
||||
pub fn register() -> CreateCommand {
|
||||
CreateCommand::new("roles_adder")
|
||||
.description("Combine roles together to an new one")
|
||||
.create_option(|option| {
|
||||
option
|
||||
.name("role_a")
|
||||
.description("A role you want to add to Role B")
|
||||
.kind(CommandOptionType::Role)
|
||||
.required(true)
|
||||
})
|
||||
.create_option(|option| {
|
||||
option
|
||||
.name("role_b")
|
||||
.description("A role you want to add to Role A")
|
||||
.kind(CommandOptionType::Role)
|
||||
.required(true)
|
||||
})
|
||||
.create_option(|option| option.name("role_c").description("Sum of A and B").kind(CommandOptionType::Role).required(true))
|
||||
.create_option(|option| {
|
||||
option
|
||||
.name("delete")
|
||||
.description("Delete this entry.")
|
||||
.kind(CommandOptionType::Boolean)
|
||||
.required(false)
|
||||
})
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::Role, "role_a", "A role you want to add to Role B").required(true))
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::Role, "role_b", "A role you want to add to Role A").required(true))
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::Role, "role_c", "Sum of A and B").required(true))
|
||||
.add_option(CreateCommandOption::new(CommandOptionType::Boolean, "delete", "Delete this entry.").required(false))
|
||||
}
|
||||
|
||||
async fn add_server(db: &Pool<Sqlite>, server: &RoleAdder, delete: bool) -> Result<Option<RoleAdder>, Error> {
|
||||
|
@ -159,10 +124,10 @@ pub mod edit {
|
|||
WHERE server = ?1 AND role_a = ?2 AND role_b = ?3 AND role_c = ?4
|
||||
",
|
||||
)
|
||||
.bind(*server.server.as_u64() as i64)
|
||||
.bind(*server.role_a.as_u64() as i64)
|
||||
.bind(*server.role_b.as_u64() as i64)
|
||||
.bind(*server.role_c.as_u64() as i64)
|
||||
.bind(server.server.get() as i64)
|
||||
.bind(server.role_a.get() as i64)
|
||||
.bind(server.role_b.get() as i64)
|
||||
.bind(server.role_c.get() as i64)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
} else {
|
||||
|
@ -172,10 +137,10 @@ pub mod edit {
|
|||
VALUES (?1, ?2, ?3, ?4)
|
||||
",
|
||||
)
|
||||
.bind(*server.server.as_u64() as i64)
|
||||
.bind(*server.role_a.as_u64() as i64)
|
||||
.bind(*server.role_b.as_u64() as i64)
|
||||
.bind(*server.role_c.as_u64() as i64)
|
||||
.bind(server.server.get() as i64)
|
||||
.bind(server.role_a.get() as i64)
|
||||
.bind(server.role_b.get() as i64)
|
||||
.bind(server.role_c.get() as i64)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
}
|
||||
|
@ -188,10 +153,10 @@ pub mod list {}
|
|||
pub mod tools {
|
||||
use serenity::client::Context;
|
||||
use serenity::model::guild::Member;
|
||||
use skynet_discord_bot::RoleAdder;
|
||||
use skynet_discord_bot::common::database::RoleAdder;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
pub async fn on_role_change(db: &Pool<Sqlite>, ctx: &Context, mut new_data: Member) {
|
||||
pub async fn on_role_change(db: &Pool<Sqlite>, ctx: &Context, new_data: Member) {
|
||||
// check if the role changed is part of the oens for this server
|
||||
if let Ok(role_adders) = sqlx::query_as::<_, RoleAdder>(
|
||||
r#"
|
||||
|
@ -200,7 +165,7 @@ pub mod tools {
|
|||
WHERE server = ?
|
||||
"#,
|
||||
)
|
||||
.bind(*new_data.guild_id.as_u64() as i64)
|
||||
.bind(new_data.guild_id.get() as i64)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
{
|
||||
|
|
260
src/common/database.rs
Normal file
260
src/common/database.rs
Normal file
|
@ -0,0 +1,260 @@
|
|||
use crate::Config;
|
||||
use serde::{Deserialize, Serialize};
|
||||
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::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct DataBase;
|
||||
impl TypeMapKey for DataBase {
|
||||
type Value = Arc<RwLock<Pool<Sqlite>>>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ServerMembers {
|
||||
pub server: GuildId,
|
||||
pub id_wolves: i64,
|
||||
pub expiry: String,
|
||||
}
|
||||
|
||||
impl<'r> FromRow<'r, SqliteRow> for ServerMembers {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let server_tmp: i64 = row.try_get("server")?;
|
||||
let server = GuildId::from(server_tmp as u64);
|
||||
|
||||
Ok(Self {
|
||||
server,
|
||||
id_wolves: row.try_get("id_wolves")?,
|
||||
expiry: row.try_get("expiry")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ServerMembersWolves {
|
||||
pub server: GuildId,
|
||||
pub id_wolves: i64,
|
||||
pub expiry: String,
|
||||
pub email: String,
|
||||
pub discord: Option<UserId>,
|
||||
pub minecraft: Option<String>,
|
||||
pub minecraft_uid: Option<String>,
|
||||
}
|
||||
|
||||
impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let server_tmp: i64 = row.try_get("server")?;
|
||||
let server = GuildId::from(server_tmp as u64);
|
||||
|
||||
Ok(Self {
|
||||
server,
|
||||
id_wolves: row.try_get("id_wolves")?,
|
||||
expiry: row.try_get("expiry")?,
|
||||
email: row.try_get("email")?,
|
||||
discord: get_discord_from_row(row),
|
||||
minecraft: row.try_get("minecraft")?,
|
||||
minecraft_uid: row.try_get("minecraft_uid")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_discord_from_row(row: &SqliteRow) -> Option<UserId> {
|
||||
match row.try_get("discord") {
|
||||
Ok(x) => {
|
||||
let tmp: i64 = x;
|
||||
if tmp == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(UserId::from(tmp as u64))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Wolves {
|
||||
pub id_wolves: i64,
|
||||
pub email: String,
|
||||
pub discord: Option<UserId>,
|
||||
pub minecraft: Option<String>,
|
||||
}
|
||||
|
||||
impl<'r> FromRow<'r, SqliteRow> for Wolves {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
id_wolves: row.try_get("id_wolves")?,
|
||||
email: row.try_get("email")?,
|
||||
discord: get_discord_from_row(row),
|
||||
minecraft: row.try_get("minecraft")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct WolvesVerify {
|
||||
pub email: String,
|
||||
pub discord: UserId,
|
||||
pub auth_code: String,
|
||||
pub date_expiry: String,
|
||||
}
|
||||
|
||||
impl<'r> FromRow<'r, SqliteRow> for WolvesVerify {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let user_tmp: i64 = row.try_get("discord")?;
|
||||
let discord = UserId::from(user_tmp as u64);
|
||||
|
||||
Ok(Self {
|
||||
email: row.try_get("email")?,
|
||||
discord,
|
||||
auth_code: row.try_get("auth_code")?,
|
||||
date_expiry: row.try_get("date_expiry")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Servers {
|
||||
pub server: GuildId,
|
||||
pub wolves_api: String,
|
||||
pub role_past: Option<RoleId>,
|
||||
pub role_current: RoleId,
|
||||
pub member_past: i64,
|
||||
pub member_current: i64,
|
||||
pub bot_channel_id: ChannelId,
|
||||
pub server_name: String,
|
||||
}
|
||||
|
||||
impl<'r> FromRow<'r, SqliteRow> for Servers {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let server_tmp: i64 = row.try_get("server")?;
|
||||
let server = GuildId::from(server_tmp as u64);
|
||||
let role_past = match row.try_get("role_past") {
|
||||
Ok(x) => {
|
||||
let tmp: i64 = x;
|
||||
if tmp == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(RoleId::from(tmp as u64))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let role_current = match row.try_get("role_current") {
|
||||
Ok(x) => {
|
||||
let tmp: i64 = x;
|
||||
RoleId::from(tmp as u64)
|
||||
}
|
||||
_ => RoleId::from(0u64),
|
||||
};
|
||||
|
||||
let bot_channel_tmp: i64 = row.try_get("bot_channel_id")?;
|
||||
let bot_channel_id = ChannelId::from(bot_channel_tmp as u64);
|
||||
|
||||
Ok(Self {
|
||||
server,
|
||||
wolves_api: row.try_get("wolves_api")?,
|
||||
role_past,
|
||||
role_current,
|
||||
member_past: row.try_get("member_past")?,
|
||||
member_current: row.try_get("member_current")?,
|
||||
bot_channel_id,
|
||||
server_name: row.try_get("server_name")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct RoleAdder {
|
||||
pub server: GuildId,
|
||||
pub role_a: RoleId,
|
||||
pub role_b: RoleId,
|
||||
pub role_c: RoleId,
|
||||
}
|
||||
|
||||
impl<'r> FromRow<'r, SqliteRow> for RoleAdder {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let server_tmp: i64 = row.try_get("server")?;
|
||||
let server = GuildId::from(server_tmp as u64);
|
||||
|
||||
Ok(Self {
|
||||
server,
|
||||
role_a: get_role_from_row(row, "role_a"),
|
||||
role_b: get_role_from_row(row, "role_b"),
|
||||
role_c: get_role_from_row(row, "role_c"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId {
|
||||
match row.try_get(col) {
|
||||
Ok(x) => {
|
||||
let tmp: i64 = x;
|
||||
RoleId::new(tmp as u64)
|
||||
}
|
||||
_ => RoleId::from(0u64),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn db_init(config: &Config) -> Result<Pool<Sqlite>, Error> {
|
||||
let database = format!("{}/{}", &config.home, &config.database);
|
||||
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect_with(
|
||||
SqliteConnectOptions::from_str(&format!("sqlite://{}", database))?
|
||||
.foreign_keys(true)
|
||||
.create_if_missing(true),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// migrations are amazing!
|
||||
sqlx::migrate!("./db/migrations").run(&pool).await?;
|
||||
|
||||
Ok(pool)
|
||||
}
|
||||
|
||||
pub async fn get_server_config(db: &Pool<Sqlite>, server: &GuildId) -> Option<Servers> {
|
||||
sqlx::query_as::<_, Servers>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM servers
|
||||
WHERE server = ?
|
||||
"#,
|
||||
)
|
||||
.bind(server.get() as i64)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId, member: &guild::Member) -> Result<ServerMembersWolves, Error> {
|
||||
sqlx::query_as::<_, ServerMembersWolves>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM server_members
|
||||
JOIN wolves USING (id_wolves)
|
||||
WHERE server = ? AND discord = ?
|
||||
"#,
|
||||
)
|
||||
.bind(server.get() as i64)
|
||||
.bind(member.user.id.get() as i64)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_server_config_bulk(db: &Pool<Sqlite>) -> Vec<Servers> {
|
||||
sqlx::query_as::<_, Servers>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM servers
|
||||
"#,
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
174
src/common/minecraft.rs
Normal file
174
src/common/minecraft.rs
Normal file
|
@ -0,0 +1,174 @@
|
|||
use crate::common::set_roles::normal::get_server_member_bulk;
|
||||
use crate::Config;
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::model::id::GuildId;
|
||||
use sqlx::sqlite::SqliteRow;
|
||||
use sqlx::{Error, FromRow, Pool, Row, Sqlite};
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Minecraft {
|
||||
pub discord: GuildId,
|
||||
pub minecraft: String,
|
||||
}
|
||||
|
||||
impl<'r> FromRow<'r, SqliteRow> for Minecraft {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let server_tmp: i64 = row.try_get("server_discord")?;
|
||||
let discord = GuildId::from(server_tmp as u64);
|
||||
|
||||
Ok(Self {
|
||||
discord,
|
||||
minecraft: row.try_get("server_minecraft")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
loop through all members of server
|
||||
get a list of folks with mc accounts that are members
|
||||
and a list that arent members
|
||||
*/
|
||||
pub async fn update_server(server_id: &str, db: &Pool<Sqlite>, g_id: &GuildId, config: &Config) {
|
||||
let mut usernames = vec![];
|
||||
for member in get_server_member_bulk(db, g_id).await {
|
||||
if let Some(x) = member.minecraft {
|
||||
usernames.push((x, true));
|
||||
}
|
||||
if let Some(x) = member.minecraft_uid {
|
||||
usernames.push((x, false));
|
||||
}
|
||||
}
|
||||
if !usernames.is_empty() {
|
||||
whitelist_update(&usernames, server_id, &config.discord_token_minecraft).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn post<T: Serialize>(url: &str, bearer: &str, data: &T) {
|
||||
match surf::post(url)
|
||||
.header("Authorization", bearer)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "Application/vnd.pterodactyl.v1+json")
|
||||
.body_json(&data)
|
||||
{
|
||||
Ok(req) => {
|
||||
req.await.ok();
|
||||
}
|
||||
Err(e) => {
|
||||
dbg!(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct ServerDetailsResSub {
|
||||
pub identifier: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub is_suspended: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct ServerDetailsRes {
|
||||
pub attributes: ServerDetailsResSub,
|
||||
}
|
||||
|
||||
async fn get<T: Serialize + DeserializeOwned>(url: &str, bearer: &str) -> Option<T> {
|
||||
match surf::get(url)
|
||||
.header("Authorization", bearer)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "Application/vnd.pterodactyl.v1+json")
|
||||
.recv_json()
|
||||
.await
|
||||
{
|
||||
Ok(res) => Some(res),
|
||||
Err(e) => {
|
||||
dbg!(e);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct BodyCommand {
|
||||
command: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct BodyDelete {
|
||||
root: String,
|
||||
files: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn whitelist_wipe(server: &str, token: &str) {
|
||||
let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}");
|
||||
let bearer = format!("Bearer {token}");
|
||||
|
||||
// delete whitelist
|
||||
let deletion = BodyDelete {
|
||||
root: "/".to_string(),
|
||||
files: vec!["whitelist.json".to_string()],
|
||||
};
|
||||
post(&format!("{url_base}/files/delete"), &bearer, &deletion).await;
|
||||
|
||||
// recreate teh file, passing in the type here so the compiler knows what type of vec it is
|
||||
post::<Vec<&str>>(&format!("{url_base}/files/write?file=%2Fwhitelist.json"), &bearer, &vec![]).await;
|
||||
|
||||
// reload the whitelist
|
||||
let data = BodyCommand {
|
||||
command: "whitelist reload".to_string(),
|
||||
};
|
||||
post(&format!("{url_base}/command"), &bearer, &data).await;
|
||||
}
|
||||
|
||||
pub async fn server_information(server: &str, token: &str) -> Option<ServerDetailsRes> {
|
||||
let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}");
|
||||
let bearer = format!("Bearer {token}");
|
||||
get::<ServerDetailsRes>(&format!("{url_base}/"), &bearer).await
|
||||
}
|
||||
|
||||
pub async fn get_minecraft_config(db: &Pool<Sqlite>) -> Vec<Minecraft> {
|
||||
sqlx::query_as::<_, Minecraft>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM minecraft
|
||||
"#,
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub async fn get_minecraft_config_server(db: &Pool<Sqlite>, g_id: GuildId) -> Vec<Minecraft> {
|
||||
sqlx::query_as::<_, Minecraft>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM minecraft
|
||||
WHERE server_discord = ?1
|
||||
"#,
|
||||
)
|
||||
.bind(g_id.get() as i64)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &str) {
|
||||
println!("Update whitelist for {}", server);
|
||||
let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}");
|
||||
let bearer = format!("Bearer {token}");
|
||||
|
||||
for (name, java) in add {
|
||||
let data = if *java {
|
||||
BodyCommand {
|
||||
command: format!("whitelist add {name}"),
|
||||
}
|
||||
} else {
|
||||
BodyCommand {
|
||||
command: format!("fwhitelist add {name}"),
|
||||
}
|
||||
};
|
||||
post(&format!("{url_base}/command"), &bearer, &data).await;
|
||||
}
|
||||
}
|
4
src/common/mod.rs
Normal file
4
src/common/mod.rs
Normal file
|
@ -0,0 +1,4 @@
|
|||
pub mod database;
|
||||
pub mod minecraft;
|
||||
pub mod set_roles;
|
||||
pub mod wolves;
|
393
src/common/set_roles.rs
Normal file
393
src/common/set_roles.rs
Normal file
|
@ -0,0 +1,393 @@
|
|||
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 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<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.get() 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.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::{DataBase, Wolves};
|
||||
use crate::common::wolves::committees::Committees;
|
||||
use crate::Config;
|
||||
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::{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 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;
|
||||
|
||||
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<Sqlite>, ctx: &Context, config: &Config, members: &mut Vec<Member>) {
|
||||
let server = config.committee_server;
|
||||
let committee_member = RoleId::new(1226602779968274573);
|
||||
let committees = get_committees(db).await;
|
||||
let categories = vec![
|
||||
// C&S Chats 1
|
||||
ChannelId::new(1226606560973815839),
|
||||
// C&S Chats 2
|
||||
ChannelId::new(1341457244973305927),
|
||||
// C&S Chats 3
|
||||
ChannelId::new(1341457509717639279),
|
||||
];
|
||||
|
||||
// information about the server
|
||||
let roles = server.roles(&ctx).await.unwrap_or_default();
|
||||
let channels = server.channels(&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());
|
||||
}
|
||||
|
||||
let mut channels_name = HashMap::new();
|
||||
for channel in channels.values() {
|
||||
// we only care about teh channels in teh category
|
||||
if let Some(x) = channel.parent_id {
|
||||
for category in &categories {
|
||||
if x.eq(category) {
|
||||
channels_name.insert(channel.name.to_owned(), channel.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];
|
||||
|
||||
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
|
||||
let role = match roles_name.get(&committee.name_full) {
|
||||
Some(x) => Some(x.to_owned()),
|
||||
None => {
|
||||
// create teh role if it does not exist
|
||||
match server
|
||||
.create_role(&ctx, EditRole::new().name(&committee.name_full).hoist(false).mentionable(true))
|
||||
.await
|
||||
{
|
||||
Ok(x) => Some(x),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// create teh channel if it does nto exist
|
||||
if !channels_name.contains_key(&committee.name_profile) {
|
||||
match server
|
||||
.create_channel(
|
||||
&ctx,
|
||||
CreateChannel::new(&committee.name_profile)
|
||||
.kind(ChannelType::Text)
|
||||
.category(categories[category_index]),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(x) => {
|
||||
// update teh channels name list
|
||||
channels_name.insert(x.name.to_owned(), x.to_owned());
|
||||
|
||||
println!("Created channel: {}", &committee.name_profile);
|
||||
}
|
||||
Err(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)"));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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![committee_member]);
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
// finally re-order teh channels to make them visually apealing
|
||||
let mut channel_names = channels_name.clone().into_keys().collect::<Vec<String>>();
|
||||
channel_names.sort();
|
||||
|
||||
// get a list of all teh new positions
|
||||
let mut new_positions = vec![];
|
||||
for (i, name) in channel_names.iter().enumerate() {
|
||||
if let Some(channel) = channels_name.get_mut(name) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_committees(db: &Pool<Sqlite>) -> Vec<Committees> {
|
||||
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<Sqlite>, user: &i64) -> Option<Wolves> {
|
||||
sqlx::query_as::<_, Wolves>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM wolves
|
||||
WHERE id_wolves = ?
|
||||
"#,
|
||||
)
|
||||
.bind(user)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
}
|
278
src/common/wolves.rs
Normal file
278
src/common/wolves.rs
Normal file
|
@ -0,0 +1,278 @@
|
|||
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
|
||||
*/
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
This is getting data for Clubs and Socs
|
||||
*/
|
||||
pub mod cns {
|
||||
use crate::common::database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers};
|
||||
use crate::common::set_roles::normal::update_server;
|
||||
use crate::common::wolves::{add_users_wolves, WolvesResultUserMin};
|
||||
use crate::Config;
|
||||
use serenity::client::Context;
|
||||
use serenity::model::id::GuildId;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
impl From<&wolves_oxidised::WolvesUser> for WolvesResultUserMin {
|
||||
fn from(value: &wolves_oxidised::WolvesUser) -> Self {
|
||||
Self {
|
||||
member_id: value.member_id.to_owned(),
|
||||
contact_email: value.contact_email.to_owned(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_wolves(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;
|
||||
|
||||
// set up teh client
|
||||
let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api));
|
||||
|
||||
for server_config in get_server_config_bulk(&db).await {
|
||||
let Servers {
|
||||
server,
|
||||
// this is the unique api key for each club/soc
|
||||
wolves_api,
|
||||
server_name,
|
||||
..
|
||||
} = &server_config;
|
||||
// dbg!(&server_config);
|
||||
|
||||
let existing_tmp = get_server_member(&db, server).await;
|
||||
let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::<BTreeMap<_, _>>();
|
||||
|
||||
// list of users that need to be updated for this server
|
||||
let mut user_to_update = vec![];
|
||||
let mut server_name_tmp = None;
|
||||
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();
|
||||
match existing.get(&(id as i64)) {
|
||||
None => {
|
||||
// user does not exist already, add everything
|
||||
add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await;
|
||||
add_users_server_members(&db, server, &user).await;
|
||||
}
|
||||
Some(old) => {
|
||||
// always update wolves table, in case data has changed
|
||||
add_users_wolves(&db, &WolvesResultUserMin::from(&user)).await;
|
||||
if old.expiry != user.expiry {
|
||||
add_users_server_members(&db, server, &user).await;
|
||||
|
||||
if let Some(discord_id) = old.discord {
|
||||
user_to_update.push(discord_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(name) = server_name_tmp {
|
||||
if &name != server_name {
|
||||
set_server_member(&db, server, &name).await;
|
||||
}
|
||||
}
|
||||
if !user_to_update.is_empty() {
|
||||
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.get() as i64)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("Failure to set server name {}", server.get());
|
||||
println!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_server_member(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
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.bind(server.get() as i64)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
async fn add_users_server_members(db: &Pool<Sqlite>, server: &GuildId, user: &wolves_oxidised::WolvesUser) {
|
||||
match sqlx::query_as::<_, ServerMembers>(
|
||||
"
|
||||
INSERT OR REPLACE INTO server_members (server, id_wolves, expiry)
|
||||
VALUES (?1, ?2, ?3)
|
||||
",
|
||||
)
|
||||
.bind(server.get() as i64)
|
||||
.bind(&user.member_id)
|
||||
.bind(&user.expiry)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("Failure to insert into ServerMembers {} {:?}", server.get(), user);
|
||||
println!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Get and store the data on C&S committees
|
||||
*/
|
||||
pub mod committees {
|
||||
use crate::common::database::DataBase;
|
||||
use crate::Config;
|
||||
use serenity::client::Context;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
// Database entry for it
|
||||
#[derive(Debug, Clone, sqlx::FromRow)]
|
||||
pub struct Committees {
|
||||
pub id: i64,
|
||||
pub name_full: String,
|
||||
pub name_profile: String,
|
||||
pub name_plain: String,
|
||||
pub link: String,
|
||||
#[sqlx(json)]
|
||||
pub committee: Vec<i64>,
|
||||
}
|
||||
|
||||
impl From<wolves_oxidised::WolvesCNS> for Committees {
|
||||
fn from(value: wolves_oxidised::WolvesCNS) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
name_full: value.name_full,
|
||||
name_profile: value.name_profile,
|
||||
name_plain: value.name_plain,
|
||||
link: value.link,
|
||||
committee: value.committee,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api));
|
||||
// request data from wolves
|
||||
for committee_wolves in wolves.get_committees().await {
|
||||
let committee = Committees::from(committee_wolves);
|
||||
add_committee(&db, &committee).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn add_committee(db: &Pool<Sqlite>, committee: &Committees) {
|
||||
match sqlx::query_as::<_, Committees>(
|
||||
"
|
||||
INSERT INTO committees (id, name_profile, name_full, name_plain, link, committee)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
ON CONFLICT(id) DO UPDATE SET committee = $6
|
||||
",
|
||||
)
|
||||
.bind(committee.id)
|
||||
.bind(&committee.name_profile)
|
||||
.bind(&committee.name_full)
|
||||
.bind(&committee.name_plain)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
743
src/lib.rs
743
src/lib.rs
|
@ -1,25 +1,13 @@
|
|||
use dotenvy::dotenv;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::{
|
||||
model::{
|
||||
guild,
|
||||
id::{ChannelId, GuildId, RoleId},
|
||||
},
|
||||
prelude::TypeMapKey,
|
||||
};
|
||||
pub mod common;
|
||||
|
||||
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 dotenvy::dotenv;
|
||||
use rand::{distr::Alphanumeric, rng, Rng};
|
||||
use serenity::all::CommandInteraction;
|
||||
use serenity::client::Context;
|
||||
use serenity::model::id::UserId;
|
||||
use serenity::model::prelude::application_command::ApplicationCommandInteraction;
|
||||
use sqlx::{
|
||||
sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow},
|
||||
Error, FromRow, Pool, Row, Sqlite,
|
||||
};
|
||||
use std::{env, str::FromStr, sync::Arc};
|
||||
use serenity::model::id::{ChannelId, GuildId, RoleId};
|
||||
use serenity::prelude::TypeMapKey;
|
||||
use std::{env, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -42,16 +30,16 @@ pub struct Config {
|
|||
pub wolves_url: String,
|
||||
// API key for accessing more general resources
|
||||
pub wolves_api: String,
|
||||
|
||||
// discord server for committee
|
||||
pub committee_server: GuildId,
|
||||
pub committee_role: RoleId,
|
||||
pub committee_category: ChannelId,
|
||||
}
|
||||
impl TypeMapKey for Config {
|
||||
type Value = Arc<RwLock<Config>>;
|
||||
}
|
||||
|
||||
pub struct DataBase;
|
||||
impl TypeMapKey for DataBase {
|
||||
type Value = Arc<RwLock<Pool<Sqlite>>>;
|
||||
}
|
||||
|
||||
pub fn get_config() -> Config {
|
||||
dotenv().ok();
|
||||
|
||||
|
@ -69,6 +57,9 @@ pub fn get_config() -> Config {
|
|||
mail_pass: "".to_string(),
|
||||
wolves_url: "".to_string(),
|
||||
wolves_api: "".to_string(),
|
||||
committee_server: GuildId::new(1),
|
||||
committee_role: RoleId::new(1),
|
||||
committee_category: ChannelId::new(1),
|
||||
};
|
||||
|
||||
if let Ok(x) = env::var("DATABASE_HOME") {
|
||||
|
@ -105,296 +96,23 @@ pub fn get_config() -> Config {
|
|||
config.wolves_api = x.trim().to_string();
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ServerMembers {
|
||||
pub server: GuildId,
|
||||
pub id_wolves: i64,
|
||||
pub expiry: String,
|
||||
}
|
||||
impl<'r> FromRow<'r, SqliteRow> for ServerMembers {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let server_tmp: i64 = row.try_get("server")?;
|
||||
let server = GuildId::from(server_tmp as u64);
|
||||
|
||||
Ok(Self {
|
||||
server,
|
||||
id_wolves: row.try_get("id_wolves")?,
|
||||
expiry: row.try_get("expiry")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ServerMembersWolves {
|
||||
pub server: GuildId,
|
||||
pub id_wolves: i64,
|
||||
pub expiry: String,
|
||||
pub email: String,
|
||||
pub discord: Option<UserId>,
|
||||
pub minecraft: Option<String>,
|
||||
pub minecraft_uid: Option<String>,
|
||||
}
|
||||
impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let server_tmp: i64 = row.try_get("server")?;
|
||||
let server = GuildId::from(server_tmp as u64);
|
||||
let discord = match row.try_get("discord") {
|
||||
Ok(x) => {
|
||||
let tmp: i64 = x;
|
||||
if tmp == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(UserId::from(tmp as u64))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
server,
|
||||
id_wolves: row.try_get("id_wolves")?,
|
||||
expiry: row.try_get("expiry")?,
|
||||
email: row.try_get("email")?,
|
||||
discord,
|
||||
minecraft: row.try_get("minecraft")?,
|
||||
minecraft_uid: row.try_get("minecraft_uid")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Wolves {
|
||||
pub id_wolves: i64,
|
||||
pub email: String,
|
||||
pub discord: Option<UserId>,
|
||||
pub minecraft: Option<String>,
|
||||
}
|
||||
impl<'r> FromRow<'r, SqliteRow> for Wolves {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let discord = match row.try_get("discord") {
|
||||
Ok(x) => {
|
||||
let tmp: i64 = x;
|
||||
if tmp == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(UserId::from(tmp as u64))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
id_wolves: row.try_get("id_wolves")?,
|
||||
email: row.try_get("email")?,
|
||||
discord,
|
||||
minecraft: row.try_get("minecraft")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct WolvesVerify {
|
||||
pub email: String,
|
||||
pub discord: UserId,
|
||||
pub auth_code: String,
|
||||
pub date_expiry: String,
|
||||
}
|
||||
impl<'r> FromRow<'r, SqliteRow> for WolvesVerify {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let user_tmp: i64 = row.try_get("discord")?;
|
||||
let discord = UserId::from(user_tmp as u64);
|
||||
|
||||
Ok(Self {
|
||||
email: row.try_get("email")?,
|
||||
discord,
|
||||
auth_code: row.try_get("auth_code")?,
|
||||
date_expiry: row.try_get("date_expiry")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Committee {
|
||||
pub email: String,
|
||||
pub discord: UserId,
|
||||
pub auth_code: String,
|
||||
pub committee: i64,
|
||||
}
|
||||
impl<'r> FromRow<'r, SqliteRow> for Committee {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let user_tmp: i64 = row.try_get("discord")?;
|
||||
let discord = UserId::from(user_tmp as u64);
|
||||
|
||||
Ok(Self {
|
||||
email: row.try_get("email")?,
|
||||
discord,
|
||||
auth_code: row.try_get("auth_code")?,
|
||||
committee: row.try_get("committee")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Servers {
|
||||
pub server: GuildId,
|
||||
pub wolves_api: String,
|
||||
pub role_past: Option<RoleId>,
|
||||
pub role_current: RoleId,
|
||||
pub member_past: i64,
|
||||
pub member_current: i64,
|
||||
pub bot_channel_id: ChannelId,
|
||||
// these can be removed in teh future with an API update
|
||||
pub server_name: String,
|
||||
pub wolves_link: String,
|
||||
}
|
||||
impl<'r> FromRow<'r, SqliteRow> for Servers {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let server_tmp: i64 = row.try_get("server")?;
|
||||
let server = GuildId::from(server_tmp as u64);
|
||||
let role_past = match row.try_get("role_past") {
|
||||
Ok(x) => {
|
||||
let tmp: i64 = x;
|
||||
if tmp == 0 {
|
||||
None
|
||||
} else {
|
||||
Some(RoleId::from(tmp as u64))
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let role_current = match row.try_get("role_current") {
|
||||
Ok(x) => {
|
||||
let tmp: i64 = x;
|
||||
RoleId::from(tmp as u64)
|
||||
}
|
||||
_ => RoleId::from(0u64),
|
||||
};
|
||||
|
||||
let bot_channel_tmp: i64 = row.try_get("bot_channel_id")?;
|
||||
let bot_channel_id = ChannelId::from(bot_channel_tmp as u64);
|
||||
|
||||
Ok(Self {
|
||||
server,
|
||||
wolves_api: row.try_get("wolves_api")?,
|
||||
role_past,
|
||||
role_current,
|
||||
member_past: row.try_get("member_past")?,
|
||||
member_current: row.try_get("member_current")?,
|
||||
bot_channel_id,
|
||||
server_name: row.try_get("server_name")?,
|
||||
wolves_link: row.try_get("wolves_link")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct Minecraft {
|
||||
pub discord: GuildId,
|
||||
pub minecraft: String,
|
||||
}
|
||||
impl<'r> FromRow<'r, SqliteRow> for Minecraft {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let server_tmp: i64 = row.try_get("server_discord")?;
|
||||
let discord = GuildId::from(server_tmp as u64);
|
||||
|
||||
Ok(Self {
|
||||
discord,
|
||||
minecraft: row.try_get("server_minecraft")?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct RoleAdder {
|
||||
pub server: GuildId,
|
||||
pub role_a: RoleId,
|
||||
pub role_b: RoleId,
|
||||
pub role_c: RoleId,
|
||||
}
|
||||
impl<'r> FromRow<'r, SqliteRow> for RoleAdder {
|
||||
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
|
||||
let server_tmp: i64 = row.try_get("server")?;
|
||||
let server = GuildId::from(server_tmp as u64);
|
||||
|
||||
Ok(Self {
|
||||
server,
|
||||
role_a: get_role_from_row(row, "role_a"),
|
||||
role_b: get_role_from_row(row, "role_b"),
|
||||
role_c: get_role_from_row(row, "role_c"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId {
|
||||
match row.try_get(col) {
|
||||
Ok(x) => {
|
||||
let tmp: i64 = x;
|
||||
RoleId(tmp as u64)
|
||||
if let Ok(x) = env::var("COMMITTEE_DISCORD") {
|
||||
if let Ok(x) = x.trim().parse::<u64>() {
|
||||
config.committee_server = GuildId::new(x);
|
||||
}
|
||||
}
|
||||
if let Ok(x) = env::var("COMMITTEE_DISCORD") {
|
||||
if let Ok(x) = x.trim().parse::<u64>() {
|
||||
config.committee_role = RoleId::new(x);
|
||||
}
|
||||
}
|
||||
if let Ok(x) = env::var("COMMITTEE_CATEGORY") {
|
||||
if let Ok(x) = x.trim().parse::<u64>() {
|
||||
config.committee_category = ChannelId::new(x);
|
||||
}
|
||||
_ => RoleId::from(0u64),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn db_init(config: &Config) -> Result<Pool<Sqlite>, Error> {
|
||||
let database = format!("{}/{}", &config.home, &config.database);
|
||||
|
||||
let pool = SqlitePoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect_with(
|
||||
SqliteConnectOptions::from_str(&format!("sqlite://{}", database))?
|
||||
.foreign_keys(true)
|
||||
.create_if_missing(true),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// migrations are amazing!
|
||||
sqlx::migrate!("./db/migrations").run(&pool).await?;
|
||||
|
||||
Ok(pool)
|
||||
}
|
||||
|
||||
pub async fn get_server_config(db: &Pool<Sqlite>, server: &GuildId) -> Option<Servers> {
|
||||
sqlx::query_as::<_, Servers>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM servers
|
||||
WHERE server = ?
|
||||
"#,
|
||||
)
|
||||
.bind(*server.as_u64() as i64)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub async fn get_server_member(db: &Pool<Sqlite>, server: &GuildId, member: &guild::Member) -> Result<ServerMembersWolves, Error> {
|
||||
sqlx::query_as::<_, ServerMembersWolves>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM server_members
|
||||
JOIN wolves USING (id_wolves)
|
||||
WHERE server = ? AND discord = ?
|
||||
"#,
|
||||
)
|
||||
.bind(*server.as_u64() as i64)
|
||||
.bind(*member.user.id.as_u64() as i64)
|
||||
.fetch_one(db)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_server_config_bulk(db: &Pool<Sqlite>) -> Vec<Servers> {
|
||||
sqlx::query_as::<_, Servers>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM servers
|
||||
"#,
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
config
|
||||
}
|
||||
|
||||
pub fn get_now_iso(short: bool) -> String {
|
||||
|
@ -407,262 +125,13 @@ pub fn get_now_iso(short: bool) -> String {
|
|||
}
|
||||
|
||||
pub fn random_string(len: usize) -> String {
|
||||
thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect()
|
||||
}
|
||||
|
||||
pub mod set_roles {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod get_data {
|
||||
use super::*;
|
||||
use crate::set_roles::update_server;
|
||||
use std::collections::BTreeMap;
|
||||
use wolves_oxidised::WolvesUser;
|
||||
|
||||
pub async fn get_wolves(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;
|
||||
|
||||
// set up teh client
|
||||
let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api));
|
||||
|
||||
for server_config in get_server_config_bulk(&db).await {
|
||||
let Servers {
|
||||
server,
|
||||
wolves_api,
|
||||
..
|
||||
} = &server_config;
|
||||
|
||||
let existing_tmp = get_server_member(&db, server).await;
|
||||
let existing = existing_tmp.iter().map(|data| (data.id_wolves, data)).collect::<BTreeMap<_, _>>();
|
||||
|
||||
// list of users that need to be updated for this server
|
||||
let mut user_to_update = vec![];
|
||||
for user in wolves.get_members(wolves_api).await {
|
||||
let id = user.member_id.parse::<u64>().unwrap_or_default();
|
||||
match existing.get(&(id as i64)) {
|
||||
None => {
|
||||
// user does not exist already, add everything
|
||||
add_users_wolves(&db, &user).await;
|
||||
add_users_server_members(&db, server, &user).await;
|
||||
}
|
||||
Some(old) => {
|
||||
// always update wolves table, in case data has changed
|
||||
add_users_wolves(&db, &user).await;
|
||||
if old.expiry != user.expiry {
|
||||
add_users_server_members(&db, server, &user).await;
|
||||
|
||||
if let Some(discord_id) = old.discord {
|
||||
user_to_update.push(discord_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !user_to_update.is_empty() {
|
||||
update_server(ctx, &server_config, &[], &user_to_update).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_server_member(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
|
||||
)
|
||||
"#,
|
||||
)
|
||||
.bind(*server.as_u64() as i64)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
async fn add_users_wolves(db: &Pool<Sqlite>, user: &WolvesUser) {
|
||||
// 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: &WolvesUser) {
|
||||
match sqlx::query_as::<_, ServerMembers>(
|
||||
"
|
||||
INSERT OR REPLACE INTO server_members (server, id_wolves, expiry)
|
||||
VALUES (?1, ?2, ?3)
|
||||
",
|
||||
)
|
||||
.bind(*server.as_u64() as i64)
|
||||
.bind(&user.member_id)
|
||||
.bind(&user.expiry)
|
||||
.fetch_optional(db)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
println!("Failure to insert into ServerMembers {} {:?}", server.as_u64(), user);
|
||||
println!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect()
|
||||
}
|
||||
|
||||
/**
|
||||
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<String> {
|
||||
pub async fn is_admin(command: &CommandInteraction, ctx: &Context) -> Option<String> {
|
||||
let mut admin = false;
|
||||
|
||||
let g_id = match command.guild_id {
|
||||
|
@ -696,153 +165,3 @@ pub async fn is_admin(command: &ApplicationCommandInteraction, ctx: &Context) ->
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
loop through all members of server
|
||||
get a list of folks with mc accounts that are members
|
||||
and a list that arent members
|
||||
*/
|
||||
pub async fn update_server(server_id: &str, db: &Pool<Sqlite>, g_id: &GuildId, config: &Config) {
|
||||
let mut usernames = vec![];
|
||||
for member in get_server_member_bulk(db, g_id).await {
|
||||
if let Some(x) = member.minecraft {
|
||||
usernames.push((x, true));
|
||||
}
|
||||
if let Some(x) = member.minecraft_uid {
|
||||
usernames.push((x, false));
|
||||
}
|
||||
}
|
||||
if !usernames.is_empty() {
|
||||
whitelist_update(&usernames, server_id, &config.discord_token_minecraft).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn post<T: Serialize>(url: &str, bearer: &str, data: &T) {
|
||||
match surf::post(url)
|
||||
.header("Authorization", bearer)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "Application/vnd.pterodactyl.v1+json")
|
||||
.body_json(&data)
|
||||
{
|
||||
Ok(req) => {
|
||||
req.await.ok();
|
||||
}
|
||||
Err(e) => {
|
||||
dbg!(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct ServerDetailsResSub {
|
||||
pub identifier: String,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub is_suspended: bool,
|
||||
}
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct ServerDetailsRes {
|
||||
pub attributes: ServerDetailsResSub,
|
||||
}
|
||||
|
||||
async fn get<T: Serialize + DeserializeOwned>(url: &str, bearer: &str) -> Option<T> {
|
||||
match surf::get(url)
|
||||
.header("Authorization", bearer)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "Application/vnd.pterodactyl.v1+json")
|
||||
.recv_json()
|
||||
.await
|
||||
{
|
||||
Ok(res) => Some(res),
|
||||
Err(e) => {
|
||||
dbg!(e);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct BodyCommand {
|
||||
command: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
struct BodyDelete {
|
||||
root: String,
|
||||
files: Vec<String>,
|
||||
}
|
||||
|
||||
pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &str) {
|
||||
println!("Update whitelist for {}", server);
|
||||
let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}");
|
||||
let bearer = format!("Bearer {token}");
|
||||
|
||||
for (name, java) in add {
|
||||
let data = if *java {
|
||||
BodyCommand {
|
||||
command: format!("whitelist add {name}"),
|
||||
}
|
||||
} else {
|
||||
BodyCommand {
|
||||
command: format!("fwhitelist add {name}"),
|
||||
}
|
||||
};
|
||||
post(&format!("{url_base}/command"), &bearer, &data).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn whitelist_wipe(server: &str, token: &str) {
|
||||
println!("Wiping whitelist for {}", server);
|
||||
let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}");
|
||||
let bearer = format!("Bearer {token}");
|
||||
|
||||
// delete whitelist
|
||||
let deletion = BodyDelete {
|
||||
root: "/".to_string(),
|
||||
files: vec!["whitelist.json".to_string()],
|
||||
};
|
||||
post(&format!("{url_base}/files/delete"), &bearer, &deletion).await;
|
||||
|
||||
// recreate teh file, passing in the type here so the compiler knows what type of vec it is
|
||||
post::<Vec<&str>>(&format!("{url_base}/files/write?file=%2Fwhitelist.json"), &bearer, &vec![]).await;
|
||||
|
||||
// reload the whitelist
|
||||
let data = BodyCommand {
|
||||
command: "whitelist reload".to_string(),
|
||||
};
|
||||
post(&format!("{url_base}/command"), &bearer, &data).await;
|
||||
}
|
||||
|
||||
pub async fn server_information(server: &str, token: &str) -> Option<ServerDetailsRes> {
|
||||
println!("Get server information for {}", server);
|
||||
let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}");
|
||||
let bearer = format!("Bearer {token}");
|
||||
get::<ServerDetailsRes>(&format!("{url_base}/"), &bearer).await
|
||||
}
|
||||
|
||||
pub async fn get_minecraft_config(db: &Pool<Sqlite>) -> Vec<Minecraft> {
|
||||
sqlx::query_as::<_, Minecraft>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM minecraft
|
||||
"#,
|
||||
)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub async fn get_minecraft_config_server(db: &Pool<Sqlite>, g_id: GuildId) -> Vec<Minecraft> {
|
||||
sqlx::query_as::<_, Minecraft>(
|
||||
r#"
|
||||
SELECT *
|
||||
FROM minecraft
|
||||
WHERE server_discord = ?1
|
||||
"#,
|
||||
)
|
||||
.bind(*g_id.as_u64() as i64)
|
||||
.fetch_all(db)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
|
135
src/main.rs
135
src/main.rs
|
@ -1,19 +1,22 @@
|
|||
pub mod commands;
|
||||
|
||||
use crate::commands::role_adder::tools::on_role_change;
|
||||
use serenity::all::{ActivityData, Command, CreateMessage, EditInteractionResponse, GuildMemberUpdateEvent, Interaction};
|
||||
use serenity::model::guild::Member;
|
||||
use serenity::{
|
||||
async_trait,
|
||||
client::{Context, EventHandler},
|
||||
model::{
|
||||
application::{command::Command, interaction::Interaction},
|
||||
gateway::{GatewayIntents, Ready},
|
||||
prelude::Activity,
|
||||
user::OnlineStatus,
|
||||
},
|
||||
Client,
|
||||
};
|
||||
use skynet_discord_bot::{db_init, get_config, get_server_config, get_server_member, Config, 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::wolves::committees::Committees;
|
||||
use skynet_discord_bot::{get_config, Config};
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
|
@ -22,14 +25,28 @@ struct Handler;
|
|||
#[async_trait]
|
||||
impl EventHandler for Handler {
|
||||
// handles previously linked accounts joining the server
|
||||
async fn guild_member_addition(&self, ctx: Context, mut new_member: Member) {
|
||||
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 = match get_server_config(&db, &new_member.guild_id).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
|
||||
if new_member.guild_id.eq(&config_global.committee_server) {
|
||||
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,
|
||||
};
|
||||
|
@ -37,56 +54,75 @@ impl EventHandler for Handler {
|
|||
if get_server_member(&db, &new_member.guild_id, &new_member).await.is_ok() {
|
||||
let mut roles = vec![];
|
||||
|
||||
if let Some(role) = &config.role_past {
|
||||
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.role_current) {
|
||||
roles.push(config.role_current.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 msg = format!(
|
||||
r#"
|
||||
let tmp = get_committee(&db, &config_server.server_name).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 ``/link_wolves`` to get full access.
|
||||
"#,
|
||||
new_member.display_name(),
|
||||
&config.server_name,
|
||||
&config.wolves_link,
|
||||
&config.server,
|
||||
&config.bot_channel_id
|
||||
);
|
||||
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, |m| m.content(&msg)).await {
|
||||
dbg!(err);
|
||||
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(Activity::playing("with humanity's fate")), OnlineStatus::Online).await;
|
||||
ctx.set_presence(Some(ActivityData::playing("with humanity's fate")), OnlineStatus::Online);
|
||||
|
||||
match Command::set_global_application_commands(&ctx.http, |commands| {
|
||||
commands
|
||||
.create_application_command(|command| commands::add_server::register(command))
|
||||
.create_application_command(|command| commands::role_adder::edit::register(command))
|
||||
.create_application_command(|command| commands::link_email::link::register(command))
|
||||
.create_application_command(|command| commands::link_email::verify::register(command))
|
||||
.create_application_command(|command| commands::minecraft::server::add::register(command))
|
||||
.create_application_command(|command| commands::minecraft::server::list::register(command))
|
||||
.create_application_command(|command| commands::minecraft::server::delete::register(command))
|
||||
.create_application_command(|command| commands::minecraft::user::add::register(command))
|
||||
// for committee server, temp
|
||||
.create_application_command(|command| commands::committee::link::register(command))
|
||||
.create_application_command(|command| commands::committee::verify::register(command))
|
||||
})
|
||||
match Command::set_global_commands(
|
||||
&ctx.http,
|
||||
vec![
|
||||
commands::add_server::register(),
|
||||
commands::role_adder::edit::register(),
|
||||
commands::link_email::link::register(),
|
||||
commands::link_email::verify::register(),
|
||||
commands::minecraft::server::add::register(),
|
||||
commands::minecraft::server::list::register(),
|
||||
commands::minecraft::server::delete::register(),
|
||||
commands::minecraft::user::add::register(),
|
||||
],
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {}
|
||||
|
@ -97,7 +133,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
|
|||
}
|
||||
|
||||
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||
if let Interaction::ApplicationCommand(command) = interaction {
|
||||
if let Interaction::Command(command) = interaction {
|
||||
let _ = command.defer_ephemeral(&ctx.http).await;
|
||||
//println!("Received command interaction: {:#?}", command);
|
||||
|
||||
|
@ -112,31 +148,28 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
|
|||
"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,
|
||||
// for teh committee server, temporary
|
||||
"link_committee" => commands::committee::link::run(&command, &ctx).await,
|
||||
"verify_committee" => commands::committee::verify::run(&command, &ctx).await,
|
||||
_ => "not implemented :(".to_string(),
|
||||
};
|
||||
|
||||
if let Err(why) = command.edit_original_interaction_response(&ctx.http, |response| response.content(content)).await {
|
||||
if let Err(why) = command.edit_response(&ctx.http, EditInteractionResponse::new().content(content)).await {
|
||||
println!("Cannot respond to slash command: {}", why);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handles role updates
|
||||
async fn guild_member_update(&self, ctx: Context, _old_data: Option<Member>, new_data: Member) {
|
||||
// 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
|
||||
on_role_change(&db, &ctx, new_data).await;
|
||||
}
|
||||
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]
|
||||
|
|
Loading…
Add table
Reference in a new issue