Compare commits

..

No commits in common. "main" and "#17-automate-onboarding-mk-ii" have entirely different histories.

23 changed files with 344 additions and 917 deletions

2
Cargo.lock generated
View file

@ -4166,7 +4166,7 @@ dependencies = [
[[package]]
name = "wolves_oxidised"
version = "0.1.0"
source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#265c8c81d1eb870a6149da5ce72556d44f57937f"
source = "git+https://forgejo.skynet.ie/Skynet/wolves-oxidised.git#74f00e3dcfd52744583b0ded08efb8bb27fdcec8"
dependencies = [
"reqwest 0.12.9",
"serde",

View file

@ -11,9 +11,6 @@ name = "update_data"
[[bin]]
name = "update_users"
[[bin]]
name = "update_committee"
[[bin]]
name = "update_minecraft"

View file

@ -1,10 +0,0 @@
CREATE TABLE IF NOT EXISTS committee_roles (
id_wolves integer PRIMARY KEY,
id_role integer DEFAULT 1,
id_channel integer DEFAULT 1,
-- not strictly required but for readability and debugging
name_role text NOT NULL DEFAULT '',
name_channel text NOT NULL DEFAULT '',
count integer DEFAULT 0
);

View file

@ -1,6 +0,0 @@
-- No need for this col since it is goign to be in "committees" anyways
ALTER TABLE servers DROP COLUMN server_name;
-- we do care about teh ID of the club/soc though
ALTER TABLE servers ADD COLUMN wolves_id integer DEFAULT 0;

View file

@ -7,7 +7,7 @@ For example is a user links on the CompSoc Discord then they will also get their
## Setup - Committee
You need admin access to run any of the commands in this section.
Either the server owner or a user with the ``Manage Server`` permission.
Either the server owner or a user with the ``Administrator`` permission.
### Get the API Key
The ``api_key`` is used by the Bot in order to request information, it will be used later in the process.
@ -38,9 +38,9 @@ The reason for both roles is ye have one for active members while the second is
### Setup Bot
This is where the bot is configured.
You will need the ``api_key`` from the start of the process.
You (personally) will need a role with ``Manage Server`` permission to be able to do this.
You (personally) will need a role with ``Administrator`` permission to be able to do this.
1. Use the command ``/committee add`` and a list of options will pop up.
1. Use the command ``/add`` and a list of options will pop up.
2. ``api_key`` is the key you got from Keith earlier.
3. ``role_current`` is the ``member-current`` that you created earlier.
4. ``role_past`` (optional) is the role for all current and past members.

View file

@ -8,13 +8,13 @@ This is to link your Discord account with your UL Wolves account.
**You will only need to do this once**.
### Setup
1. In a Discord server with the Skynet Bot enter ``/wolves link YOUR_WOLVES_CONTACT_EMAIL``
1. In a Discord server with the Skynet Bot enter ``/link_wolves YOUR_WOLVES_CONTACT_EMAIL``
<img src="../media/setup_user_01.png" alt="link process start" width="50%" height="50%">
* Your ``YOUR_WOLVES_CONTACT_EMAIL`` is the email in the Contact Email here: <https://ulwolves.ie/memberships/profile>
* This is most likely your student mail
2. An email will be sent to you with a verification code.
<img src="../media/setup_user_02.png" alt="signup email" width="50%" height="50%">
3. Verify the code using ``/wolves verify CODE_FROM_EMAIL`` in Discord.
3. Verify the code using ``/verify CODE_FROM_EMAIL`` in Discord.
<img src="../media/setup_user_03.png" alt="verify in discord" width="50%" height="50%">
4. Once complete your Wolves and Discord accounts will be linked.
@ -23,4 +23,4 @@ You will get member roles on any Discord that is using the bot that you are a me
### Minecraft
You can link your Minecraft username to grant you access to any Minecraft server run by UL Computer Society.
``/wolves link_minecraft MINECRAFT_USERNAME``
``/link_minecraft MINECRAFT_USERNAME``

View file

@ -20,7 +20,6 @@
}:
utils.lib.eachDefaultSystem (
system: let
overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml));
pkgs = (import nixpkgs) {inherit system;};
naersk' = pkgs.callPackage naersk {};
package_name = "skynet_discord_bot";
@ -63,15 +62,7 @@
# `nix develop`
devShell = pkgs.mkShell {
nativeBuildInputs = with pkgs; [rustup rustPlatform.bindgenHook pkg-config openssl];
# libraries here
buildInputs = [ ];
RUSTC_VERSION = overrides.toolchain.channel;
# https://github.com/rust-lang/rust-bindgen#environment-variables
shellHook = ''
export PATH="''${CARGO_HOME:-~/.cargo}/bin":"$PATH"
export PATH="''${RUSTUP_HOME:-~/.rustup}/toolchains/$RUSTC_VERSION-${pkgs.stdenv.hostPlatform.rust.rustcTarget}/bin":"$PATH"
'';
nativeBuildInputs = with pkgs; [rustc cargo pkg-config openssl rustfmt];
};
nixosModule = {
@ -131,8 +122,6 @@
"update_data" = "*:0,10,20,30,40,50";
# groups are updated every hour, offset from teh ldap
"update_users" = "*:05:00";
# Committee server has its own timer
"update_committee" = "*:15:00";
# minecraft stuff is updated at 5am
"update_minecraft" = "5:10:00";
};

BIN
media/setup_user_01.png (Stored with Git LFS)

Binary file not shown.

BIN
media/setup_user_03.png (Stored with Git LFS)

Binary file not shown.

View file

@ -1,54 +0,0 @@
use serenity::{
async_trait,
client::{Context, EventHandler},
model::gateway::{GatewayIntents, Ready},
Client,
};
use skynet_discord_bot::common::database::{db_init, DataBase};
use skynet_discord_bot::common::set_roles::committee;
use skynet_discord_bot::{get_config, Config};
use std::{process, sync::Arc};
use tokio::sync::RwLock;
#[tokio::main]
async fn main() {
let config = get_config();
let db = match db_init(&config).await {
Ok(x) => x,
Err(_) => return,
};
// Intents are a bitflag, bitwise operations can be used to dictate which intents to use
let intents = GatewayIntents::GUILDS | GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::GUILD_MEMBERS;
// Build our client.
let mut client = Client::builder(&config.discord_token, intents)
.event_handler(Handler {})
.await
.expect("Error creating client");
{
let mut data = client.data.write().await;
data.insert::<Config>(Arc::new(RwLock::new(config)));
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
}
if let Err(why) = client.start().await {
println!("Client error: {:?}", why);
}
}
struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn ready(&self, ctx: Context, ready: Ready) {
let ctx = Arc::new(ctx);
println!("{} is connected!", ready.user.name);
// u[date committee server
committee::check_committee(Arc::clone(&ctx)).await;
// finish up
process::exit(0);
}
}

View file

@ -5,7 +5,7 @@ use serenity::{
Client,
};
use skynet_discord_bot::common::database::{db_init, get_server_config_bulk, DataBase};
use skynet_discord_bot::common::set_roles::normal;
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;
@ -48,6 +48,9 @@ impl EventHandler for Handler {
// 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);
}

View file

@ -1,53 +1,53 @@
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction};
use serenity::client::Context;
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: &CommandInteraction, ctx: &Context) -> String {
let sub_options = if let Some(CommandDataOption {
value: CommandDataOptionValue::SubCommand(options),
// check if user has high enough permisssions
if let Some(msg) = is_admin(command, ctx).await {
return msg;
}
let wolves_api = if let Some(CommandDataOption {
value: CommandDataOptionValue::String(key),
..
}) = command.data.options.first()
{
options
} else {
return "Please provide sub options".to_string();
};
let wolves_api = if let Some(x) = sub_options.first() {
match &x.value {
CommandDataOptionValue::String(key) => key.to_string(),
_ => return "Please provide a wolves API key".to_string(),
}
key.to_string()
} else {
return "Please provide a wolves API key".to_string();
};
let role_current = if let Some(x) = sub_options.get(1) {
match &x.value {
CommandDataOptionValue::Role(role) => role.to_owned(),
_ => return "Please provide a valid role for ``Role Current``".to_string(),
}
let role_current = if let Some(CommandDataOption {
value: CommandDataOptionValue::Role(role),
..
}) = command.data.options.get(1)
{
role.to_owned()
} else {
return "Please provide a valid role for ``Role Current``".to_string();
};
let role_past = if let Some(x) = sub_options.get(5) {
match &x.value {
CommandDataOptionValue::Role(role) => Some(role.to_owned()),
_ => None,
}
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 Some(x) = sub_options.get(2) {
match &x.value {
CommandDataOptionValue::Channel(channel) => channel.to_owned(),
_ => return "Please provide a valid channel for ``Bot Channel``".to_string(),
}
let bot_channel_id = if let Some(CommandDataOption {
value: CommandDataOptionValue::Channel(channel),
..
}) = command.data.options.get(2)
{
channel.to_owned()
} else {
return "Please provide a valid channel for ``Bot Channel``".to_string();
};
@ -61,12 +61,12 @@ pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
let server_data = Servers {
server: command.guild_id.unwrap_or_default(),
wolves_api,
wolves_id: 0,
role_past,
role_current,
member_past: 0,
member_current: 0,
bot_channel_id,
server_name: "".to_string(),
};
match add_server(&db, ctx, &server_data).await {
@ -80,6 +80,15 @@ pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
"Added/Updated server info".to_string()
}
pub fn register() -> CreateCommand {
CreateCommand::new("add")
.description("Enable the bot for this discord")
.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.get() as i64);

View file

@ -1,23 +0,0 @@
use serenity::all::{CommandOptionType, CreateCommand, CreateCommandOption};
pub fn register() -> CreateCommand {
CreateCommand::new("committee")
.description("Commands related to what committees can do")
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
.add_option(
CreateCommandOption::new(CommandOptionType::SubCommand, "add", "Enable the bot for this discord")
.add_sub_option(CreateCommandOption::new(CommandOptionType::String, "api_key", "UL Wolves API Key").required(true))
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_current", "Role for Current members").required(true))
.add_sub_option(
CreateCommandOption::new(CommandOptionType::Channel, "bot_channel", "Safe space for folks to use the bot commands.").required(true),
)
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_past", "Role for Past members").required(false)),
)
.add_option(
CreateCommandOption::new(CommandOptionType::SubCommand, "roles_adder", "Combine roles together to an new one")
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_a", "A role you want to add to Role B").required(true))
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_b", "A role you want to add to Role A").required(true))
.add_sub_option(CreateCommandOption::new(CommandOptionType::Role, "role_c", "Sum of A and B").required(true))
.add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "delete", "Delete this entry.").required(false)),
)
}

View file

@ -1,256 +0,0 @@
pub mod committee {
// get the list of all the current clubs/socs members
use serenity::all::{
CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption,
};
use skynet_discord_bot::common::database::DataBase;
use skynet_discord_bot::common::set_roles::committee::db_roles_get;
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
let sub_options = if let Some(CommandDataOption {
value: CommandDataOptionValue::SubCommand(key),
..
}) = command.data.options.first()
{
key
} else {
return "Please provide a wolves API key".to_string();
};
let all = if let Some(x) = sub_options.first() {
match x.value {
CommandDataOptionValue::Boolean(y) => y,
_ => false,
}
} else {
false
};
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 mut cs = vec![];
// pull it from a DB
for committee in db_roles_get(&db).await {
if !all && committee.count == 0 {
continue;
}
cs.push((committee.count, committee.name_role.to_owned()));
}
cs.sort_by_key(|(count, _)| *count);
cs.reverse();
// msg can be a max 2000 chars long
let mut limit = 2000 - 3;
let mut response = vec!["```".to_string()];
for (count, name) in cs {
let leading = if count < 10 { " " } else { "" };
let line = format!("{}{} {}", leading, count, name);
let length = line.len() + 1;
if length < (limit + 3) {
response.push(line);
limit -= length;
} else {
break;
}
}
response.push("```".to_string());
response.join("\n")
}
pub fn register() -> CreateCommand {
CreateCommand::new("count")
.description("Count Committee Members")
// All Committee members are able to add reactions to posts
.default_member_permissions(serenity::model::Permissions::ADD_REACTIONS)
.add_option(
CreateCommandOption::new(CommandOptionType::SubCommand, "committee", "List out the Committee Roles Numbers")
.add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "all", "List out all the Committee Roles Numbers").required(false)),
)
}
}
pub mod servers {
// get the list of all the current clubs/socs
use serde::{Deserialize, Serialize};
use serenity::all::{CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption};
use skynet_discord_bot::common::database::{get_server_config_bulk, DataBase};
use skynet_discord_bot::common::set_roles::committee::get_committees;
use skynet_discord_bot::get_now_iso;
use sqlx::{Pool, Sqlite};
use std::collections::HashMap;
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()
};
let db = db_lock.read().await;
let mut committees = HashMap::new();
for committee in get_committees(&db).await {
committees.insert(committee.id, committee.to_owned());
}
let mut cs = vec![];
// pull it from a DB
for server_config in get_server_config_bulk(&db).await {
if let Some(x) = committees.get(&server_config.wolves_id) {
cs.push((server_config.member_current, server_config.member_past, x.name_full.to_owned()));
}
}
// get all members
let (wolves_current, wolves_past, total) = get_wolves_total(&db).await;
cs.push((total, total, String::from("Skynet Network")));
cs.push((wolves_current, wolves_past, String::from("Clubs/Socs Servers")));
// treat teh committee server as its own thing
let committee_current = get_wolves_committee(&db).await;
cs.push((committee_current, committee_current, String::from("Committee Server")));
cs.sort_by_key(|(current, _, _)| *current);
cs.reverse();
// msg can be a max 2000 chars long
let mut limit = 2000 - 3;
let mut response = vec!["```".to_string()];
for (current, past, name) in cs {
let current_leading = if current < 10 {
" "
} else if current < 100 {
" "
} else {
""
};
let past_leading = if past < 10 {
" "
} else if past < 100 {
" "
} else {
""
};
let line = format!("{}{} {}{} {}", current_leading, current, past_leading, past, name);
let length = line.len() + 1;
// +3 is to account for the closing fense
if length < (limit + 3) {
response.push(line);
limit -= length;
} else {
break;
}
}
response.push("```".to_string());
response.join("\n")
}
#[derive(Debug, Clone, Deserialize, Serialize, sqlx::FromRow)]
pub struct Count {
pub count: i64,
}
async fn get_wolves_total(db: &Pool<Sqlite>) -> (i64, i64, i64) {
let current = match sqlx::query_as::<_, Count>(
r#"
SELECT COUNT(DISTINCT id_wolves) as count
FROM server_members
JOIN wolves USING (id_wolves)
WHERE (
wolves.discord IS NOT NULL
AND server_members.expiry > ?
)
"#,
)
.bind(get_now_iso(true))
.fetch_one(db)
.await
{
Ok(res) => res.count,
Err(e) => {
dbg!(e);
0
}
};
let cns = match sqlx::query_as::<_, Count>(
r#"
SELECT COUNT(DISTINCT id_wolves) as count
FROM server_members
JOIN wolves USING (id_wolves)
WHERE wolves.discord IS NOT NULL
"#,
)
.bind(get_now_iso(true))
.fetch_one(db)
.await
{
Ok(res) => res.count,
Err(e) => {
dbg!(e);
0
}
};
let total = match sqlx::query_as::<_, Count>(
r#"
SELECT COUNT(DISTINCT id_wolves) as count
FROM wolves
WHERE discord IS NOT NULL
"#,
)
.fetch_one(db)
.await
{
Ok(res) => res.count,
Err(e) => {
dbg!(e);
0
}
};
(current, cns, total)
}
async fn get_wolves_committee(db: &Pool<Sqlite>) -> i64 {
// expiry
match sqlx::query_as::<_, Count>(
"
SELECT count
FROM committee_roles
WHERE id_wolves = '0'
",
)
.fetch_one(db)
.await
{
Ok(res) => res.count,
Err(e) => {
dbg!(e);
0
}
}
}
pub fn register() -> CreateCommand {
CreateCommand::new("count")
.description("Count the servers")
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
.add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "servers", "List out all servers using the skynet bot"))
}
}

View file

@ -4,8 +4,6 @@ use lettre::{
Message, SmtpTransport, Transport,
};
use maud::html;
use serenity::all::CommandOptionType;
use serenity::builder::CreateCommandOption;
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};
@ -13,7 +11,8 @@ use sqlx::{Pool, Sqlite};
pub mod link {
use super::*;
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction};
use serde::{Deserialize, Serialize};
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption};
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
let db_lock = {
@ -38,23 +37,14 @@ pub mod link {
return "Linking already in process, please check email.".to_string();
}
let sub_options = if let Some(CommandDataOption {
value: CommandDataOptionValue::SubCommand(options),
let email = if let Some(CommandDataOption {
value: CommandDataOptionValue::String(email),
..
}) = command.data.options.first()
{
options
email.trim()
} else {
return "Please provide sub options".to_string();
};
let email = if let Some(x) = sub_options.first() {
match &x.value {
CommandDataOptionValue::String(email) => email.trim(),
_ => return "Please provide a valid email".to_string(),
}
} else {
return "Please provide a valid email".to_string();
return "Please provide a valid user".to_string();
};
// check if email exists
@ -98,7 +88,7 @@ pub mod link {
// generate a auth key
let auth = random_string(20);
match send_mail(&config, &details.email, &auth, &command.user.name) {
match send_mail(&config, &details, &auth, &command.user.name) {
Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await {
Ok(_) => {}
Err(e) => {
@ -113,6 +103,12 @@ 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() -> CreateCommand {
CreateCommand::new("link_wolves")
.description("Set Wolves Email")
.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> {
sqlx::query_as::<_, Wolves>(
r#"
@ -141,35 +137,33 @@ pub mod link {
.ok()
}
fn send_mail(config: &Config, mail: &str, auth: &str, user: &str) -> Result<smtp::response::Response, smtp::Error> {
let discord = "https://computer.discord.skynet.ie";
fn send_mail(config: &Config, email: &Wolves, auth: &str, user: &str) -> Result<smtp::response::Response, smtp::Error> {
let mail = &email.email;
let discord = "https://discord.skynet.ie";
let sender = format!("UL Computer Society <{}>", &config.mail_user);
// Create the html we want to send.
let html = html! {
head {
title { "UL Wolves Discord Linker" }
title { "Hello from Skynet!" }
style type="text/css" {
"h2, h4 { font-family: Arial, Helvetica, sans-serif; }"
}
}
div {
h2 { "UL Wolves Discord Linker" }
h3 { "Link your UL Wolves Account to Discord" }
h2 { "Hello from Skynet!" }
// Substitute in the name of our recipient.
p { "Hi " (user) "," }
p {
"Please paste this line into Discord (and press enter) to verify your discord account:"
br;
pre { "/wolves verify code: " (auth)}
"Please use " pre { "/verify code: " (auth)} " to verify your discord account."
}
hr;
h3 { "Help & Support" }
p {
"If you have issues please refer to our Computer Society Discord Server:"
"If you have issues please refer to our Discord server:"
br;
a href=(discord) { (discord) }
}
p {
"Skynet Team"
br;
"UL Computer Society"
}
@ -178,23 +172,15 @@ pub mod link {
let body_text = format!(
r#"
UL Wolves Discord Linker
Link your UL Wolves Account to Discord
Link your Account
Hi {user}
Hi {user},
Please paste this line into Discord (and press enter) to verify your Discord account:
/wolves verify code: {auth}
Please use "/verify code: {auth}" to verify your discord account.
-------------------------------------------------------------------------
Help & Support
If you have issues please refer to our Computer Society Discord Server:
{discord}
UL Computer Society
If you have issues please refer to our Discord server:
{discord}
Skynet Team
UL Computer Society
"#
);
@ -202,7 +188,7 @@ pub mod link {
let email = Message::builder()
.from(sender.parse().unwrap())
.to(mail.parse().unwrap())
.subject("Skynet: Link Discord to Wolves.")
.subject("Skynet-Discord: Link Wolves.")
.multipart(
// This is composed of two parts.
// also helps not trip spam settings (uneven number of url's
@ -264,6 +250,19 @@ pub mod link {
.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>(
"
@ -281,18 +280,17 @@ pub mod link {
pub mod verify {
use super::*;
use crate::commands::wolves::link::{db_pending_clear_expired, get_server_member_discord, get_verify_from_db};
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, GuildId, RoleId};
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::common::database::get_server_config;
use skynet_discord_bot::common::database::{ServerMembersWolves, Servers};
use skynet_discord_bot::common::wolves::committees::Committees;
use sqlx::Error;
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 Database in TypeMap.").clone()
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
};
let db = db_lock.read().await;
@ -300,31 +298,22 @@ pub mod verify {
let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await {
x
} else {
return "Please use ''/wolves link'' first".to_string();
return "Please use /link_wolves first".to_string();
};
let sub_options = if let Some(CommandDataOption {
value: CommandDataOptionValue::SubCommand(options),
let code = if let Some(CommandDataOption {
value: CommandDataOptionValue::String(code),
..
}) = command.data.options.first()
{
options
} else {
return "Please provide sub options".to_string();
};
let code = if let Some(x) = sub_options.first() {
match &x.value {
CommandDataOptionValue::String(y) => y.trim(),
_ => return "Please provide a verification code".to_string(),
}
code
} else {
return "Please provide a verification code".to_string();
};
db_pending_clear_expired(&db).await;
if details.auth_code != code {
if &details.auth_code != code {
return "Invalid verification code".to_string();
}
@ -334,10 +323,6 @@ pub mod verify {
Ok(_) => {
// get teh right roles for the user
set_server_roles(&db, &command.user, ctx).await;
// check if they are a committee member, and on that server
set_server_roles_committee(&db, &command.user, ctx).await;
"Discord username linked to Wolves".to_string()
}
Err(e) => {
@ -352,6 +337,12 @@ pub mod verify {
"Failed to verify".to_string()
}
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> {
sqlx::query_as::<_, WolvesVerify>(
r#"
@ -411,38 +402,6 @@ pub mod verify {
}
}
async fn get_committees_id(db: &Pool<Sqlite>, wolves_id: i64) -> Vec<Committees> {
sqlx::query_as::<_, Committees>(
r#"
SELECT *
FROM committees
WHERE committee LIKE ?1
"#,
)
.bind(format!("%{}%", wolves_id))
.fetch_all(db)
.await
.unwrap_or_else(|e| {
dbg!(e);
vec![]
})
}
async fn set_server_roles_committee(db: &Pool<Sqlite>, discord: &User, ctx: &Context) {
if let Some(x) = get_server_member_discord(db, &discord.id).await {
// if they are a member of one or more committees, and in teh committee server then give the teh general committee role
// they will get teh more specific vanity role later
if !get_committees_id(db, x.id_wolves).await.is_empty() {
let server = GuildId::new(1220150752656363520);
let committee_member = RoleId::new(1226602779968274573);
if let Ok(member) = server.member(ctx, &discord.id).await {
member.add_roles(&ctx, &[committee_member]).await.unwrap_or_default();
}
}
}
}
async fn get_servers(db: &Pool<Sqlite>, discord: &UserId) -> Result<Vec<ServerMembersWolves>, Error> {
sqlx::query_as::<_, ServerMembersWolves>(
"
@ -457,63 +416,3 @@ pub mod verify {
.await
}
}
pub mod unlink {
use serenity::all::{CommandInteraction, Context, UserId};
use skynet_discord_bot::common::database::{DataBase, Wolves};
use sqlx::{Pool, Sqlite};
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()
};
let db = db_lock.read().await;
// dosent matter if there is one or not, it will be removed regardless
delete_link(&db, &command.user.id).await;
"Discord link removed".to_string()
}
async fn delete_link(db: &Pool<Sqlite>, user: &UserId) {
match sqlx::query_as::<_, Wolves>(
"
UPDATE wolves
SET discord = NULL
WHERE discord = ?1;
",
)
.bind(user.get() as i64)
.fetch_optional(db)
.await
{
Ok(_) => {}
Err(e) => {
dbg!(e);
}
}
}
}
pub fn register() -> CreateCommand {
CreateCommand::new("wolves")
.description("Commands related to UL Wolves")
// link
.add_option(
CreateCommandOption::new(CommandOptionType::SubCommand, "link", "Link your Wolves account to your Discord")
.add_sub_option(CreateCommandOption::new(CommandOptionType::String, "email", "UL Wolves Email").required(true)),
)
// verify
.add_option(
CreateCommandOption::new(CommandOptionType::SubCommand, "verify", "Verify Wolves Email")
.add_sub_option(CreateCommandOption::new(CommandOptionType::String, "code", "Code from verification email").required(true)),
)
// unlink
.add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "unlink", "Unlink your Wolves account from your Discord"))
.add_option(
CreateCommandOption::new(CommandOptionType::SubCommand, "link_minecraft", "Link your minecraft account")
.add_sub_option(CreateCommandOption::new(CommandOptionType::String, "minecraft_username", "Your Minecraft username").required(true))
.add_sub_option(CreateCommandOption::new(CommandOptionType::Boolean, "bedrock_account", "Is this a Bedrock account?").required(false)),
)
}

View file

@ -1,4 +1,4 @@
use serenity::client::Context;
use serenity::{builder::CreateCommand, client::Context};
use skynet_discord_bot::common::database::DataBase;
use sqlx::{Pool, Sqlite};
@ -7,15 +7,22 @@ pub(crate) mod user {
use super::*;
pub(crate) mod add {
use super::*;
use crate::commands::wolves::link::get_server_member_discord;
use crate::commands::link_email::link::get_server_member_discord;
use serde::{Deserialize, Serialize};
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction};
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption};
use serenity::model::id::UserId;
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() -> CreateCommand {
CreateCommand::new("link_minecraft")
.description("Link your minecraft account")
.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: &CommandInteraction, ctx: &Context) -> String {
let db_lock = {
let data_read = ctx.data.read().await;
@ -34,30 +41,23 @@ pub(crate) mod user {
return "Not linked with wolves, please use ``/link_wolves`` with your wolves email.".to_string();
}
let sub_options = if let Some(CommandDataOption {
value: CommandDataOptionValue::SubCommand(options),
let username = if let Some(CommandDataOption {
value: CommandDataOptionValue::String(username),
..
}) = command.data.options.first()
{
options
} else {
return "Please provide sub options".to_string();
};
let username = if let Some(x) = sub_options.first() {
match &x.value {
CommandDataOptionValue::String(username) => username.trim(),
_ => return "Please provide a valid username".to_string(),
}
username.trim()
} else {
return "Please provide a valid username".to_string();
};
let java = if let Some(x) = sub_options.get(1) {
match &x.value {
CommandDataOptionValue::Boolean(z) => !z,
_ => true,
}
// this is always true unless they state its not
let java = if let Some(CommandDataOption {
value: CommandDataOptionValue::Boolean(z),
..
}) = command.data.options.get(1)
{
!z
} else {
true
};
@ -192,19 +192,19 @@ pub(crate) mod server {
use super::*;
use skynet_discord_bot::common::minecraft::update_server;
use skynet_discord_bot::common::minecraft::Minecraft;
use skynet_discord_bot::Config;
use skynet_discord_bot::{is_admin, Config};
pub fn register() -> CreateCommand {
CreateCommand::new("minecraft_add")
.description("Add a minecraft server")
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
.add_option(
CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society")
.required(true),
)
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: &CommandInteraction, ctx: &Context) -> String {
// check if user has high enough permisssions
if let Some(msg) = is_admin(command, ctx).await {
return msg;
}
let g_id = match command.guild_id {
None => return "Not in a server".to_string(),
Some(x) => x,
@ -265,15 +265,16 @@ pub(crate) mod server {
use serenity::client::Context;
use skynet_discord_bot::common::database::DataBase;
use skynet_discord_bot::common::minecraft::{get_minecraft_config_server, server_information};
use skynet_discord_bot::Config;
use skynet_discord_bot::{is_admin, Config};
pub fn register() -> CreateCommand {
CreateCommand::new("minecraft_list")
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
.description("List your minecraft servers")
CreateCommand::new("minecraft_list").description("List your minecraft servers")
}
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
if let Some(msg) = is_admin(command, ctx).await {
return msg;
}
let g_id = match command.guild_id {
None => return "Not in a server".to_string(),
Some(x) => x,
@ -326,19 +327,20 @@ pub(crate) mod server {
use serenity::model::id::GuildId;
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() -> CreateCommand {
CreateCommand::new("minecraft_delete")
.description("Delete a minecraft server")
.default_member_permissions(serenity::model::Permissions::MANAGE_GUILD)
.add_option(
CreateCommandOption::new(CommandOptionType::String, "server_id", "ID of the Minecraft server hosted by the Computer Society")
.required(true),
)
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: &CommandInteraction, ctx: &Context) -> String {
// check if user has high enough permisssions
if let Some(msg) = is_admin(command, ctx).await {
return msg;
}
let g_id = match command.guild_id {
None => return "Not in a server".to_string(),
Some(x) => x,

View file

@ -1,6 +1,4 @@
pub mod add_server;
pub mod committee;
pub mod count;
pub mod link_email;
pub mod minecraft;
pub mod role_adder;
pub mod wolves;

View file

@ -1,48 +1,47 @@
use serenity::client::Context;
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};
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption};
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
let sub_options = if let Some(CommandDataOption {
value: CommandDataOptionValue::SubCommand(options),
// check if user has high enough permisssions
if let Some(msg) = is_admin(command, ctx).await {
return msg;
}
let role_a = if let Some(CommandDataOption {
value: CommandDataOptionValue::Role(role),
..
}) = command.data.options.first()
{
options
role.to_owned()
} else {
return "Please provide sub options".to_string();
return "Please provide a valid role for ``Role Current``".to_string();
};
let role_a = if let Some(x) = sub_options.first() {
match &x.value {
CommandDataOptionValue::Role(role) => role.to_owned(),
_ => return "Please provide a valid role for ``Role A``".to_string(),
}
let role_b = if let Some(CommandDataOption {
value: CommandDataOptionValue::Role(role),
..
}) = command.data.options.get(1)
{
role.to_owned()
} else {
return "Please provide a valid role for ``Role A``".to_string();
return "Please provide a valid role for ``Role Current``".to_string();
};
let role_b = if let Some(x) = sub_options.get(1) {
match &x.value {
CommandDataOptionValue::Role(role) => role.to_owned(),
_ => return "Please provide a valid role for ``Role B``".to_string(),
}
let role_c = if let Some(CommandDataOption {
value: CommandDataOptionValue::Role(role),
..
}) = command.data.options.get(2)
{
role.to_owned()
} else {
return "Please provide a valid role for ``Role B``".to_string();
};
let role_c = if let Some(x) = sub_options.get(2) {
match &x.value {
CommandDataOptionValue::Role(role) => role.to_owned(),
_ => return "Please provide a valid role for ``Role C``".to_string(),
}
} else {
return "Please provide a valid role for ``Role C``".to_string();
return "Please provide a valid role for ``Role Current``".to_string();
};
if role_a == role_b {
@ -53,11 +52,12 @@ pub mod edit {
return "Role C cannot be same as A or B".to_string();
}
let delete = if let Some(x) = sub_options.get(3) {
match &x.value {
CommandDataOptionValue::Boolean(z) => *z,
_ => false,
}
let delete = if let Some(CommandDataOption {
value: CommandDataOptionValue::Boolean(z),
..
}) = command.data.options.get(3)
{
*z
} else {
false
};
@ -107,6 +107,15 @@ pub mod edit {
}
}
pub fn register() -> CreateCommand {
CreateCommand::new("roles_adder")
.description("Combine roles together to an new one")
.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> {
if delete {
sqlx::query_as::<_, RoleAdder>(

View file

@ -121,12 +121,12 @@ impl<'r> FromRow<'r, SqliteRow> for WolvesVerify {
pub struct Servers {
pub server: GuildId,
pub wolves_api: String,
pub wolves_id: i64,
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 {
@ -158,12 +158,12 @@ impl<'r> FromRow<'r, SqliteRow> for Servers {
Ok(Self {
server,
wolves_api: row.try_get("wolves_api")?,
wolves_id: row.try_get("wolves_id").unwrap_or(0),
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")?,
})
}
}
@ -190,28 +190,14 @@ impl<'r> FromRow<'r, SqliteRow> for RoleAdder {
}
}
pub(crate) fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId {
let id = match row.try_get(col) {
fn get_role_from_row(row: &SqliteRow, col: &str) -> RoleId {
match row.try_get(col) {
Ok(x) => {
let tmp: i64 = x;
tmp as u64
RoleId::new(tmp as u64)
}
_ => 0,
};
RoleId::from(id)
}
pub(crate) fn get_channel_from_row(row: &SqliteRow, col: &str) -> ChannelId {
let id = match row.try_get(col) {
Ok(x) => {
let tmp: i64 = x;
tmp as u64
}
_ => 0,
};
ChannelId::from(id)
_ => RoleId::from(0u64),
}
}
pub async fn db_init(config: &Config) -> Result<Pool<Sqlite>, Error> {

View file

@ -131,10 +131,9 @@ pub mod normal {
// for updating committee members
pub mod committee {
use crate::common::database::{get_channel_from_row, get_role_from_row, DataBase, Wolves};
use crate::common::database::{DataBase, Wolves};
use crate::common::wolves::committees::Committees;
use crate::Config;
use serde::{Deserialize, Serialize};
use serenity::all::EditRole;
use serenity::builder::CreateChannel;
use serenity::client::Context;
@ -142,8 +141,7 @@ pub mod committee {
use serenity::model::guild::Member;
use serenity::model::id::ChannelId;
use serenity::model::prelude::RoleId;
use sqlx::sqlite::SqliteRow;
use sqlx::{Error, FromRow, Pool, Row, Sqlite};
use sqlx::{Pool, Sqlite};
use std::collections::HashMap;
use std::sync::Arc;
@ -176,7 +174,8 @@ pub mod committee {
let server = config.committee_server;
let committee_member = RoleId::new(1226602779968274573);
let committees = get_committees(db).await;
let categories = [
let categories = vec![
// C&S Chats 1
ChannelId::new(1226606560973815839),
// C&S Chats 2
ChannelId::new(1341457244973305927),
@ -185,30 +184,35 @@ pub mod committee {
];
// information about the server
let mut roles_db = HashMap::new();
for role in db_roles_get(db).await {
roles_db.insert(
role.id_wolves,
CommitteeRoles {
id_wolves: role.id_wolves,
id_role: role.id_role,
id_channel: role.id_channel,
name_role: role.name_role,
name_channel: role.name_channel,
// always start at 0
count: 0,
},
);
let 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 = server.channels(&ctx).await.unwrap_or_default();
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();
let mut re_order = false;
// we need to create roles and channels if tehy dont already exist
// 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() {
@ -216,17 +220,24 @@ pub mod committee {
}
let committee = &committees[i];
// if a club/soc ever changes their name
if let Some(x) = roles_db.get_mut(&committee.id) {
committee.name_full.clone_into(&mut x.name_role);
committee.name_profile.clone_into(&mut x.name_channel);
}
// 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,
}
}
};
// handle new clubs/socs
if let std::collections::hash_map::Entry::Vacant(e) = roles_db.entry(committee.id) {
// create channel
// channel is first as the categories can only contain 50 channels
let channel = match server
// create 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)
@ -236,74 +247,40 @@ pub mod committee {
.await
{
Ok(x) => {
println!("Created channel: {}", &committee.name_profile);
// update teh channels name list
channels_name.insert(x.name.to_owned(), x.to_owned());
x.id
println!("Created channel: {}", &committee.name_profile);
}
Err(x) => {
let tmp = x.to_string();
dbg!("Unable to create channel: ", &tmp, &tmp.contains("Maximum number of channels in category reached (50)"));
if x.to_string().contains("Maximum number of channels in category reached (50)") {
category_index += 1;
continue;
}
ChannelId::new(1)
dbg!("Unable to create channel: ", &tmp, &tmp.contains("Maximum number of channels in category reached (50)"));
}
};
// create role
let role = match server
.create_role(&ctx, EditRole::new().name(&committee.name_full).hoist(false).mentionable(true))
.await
{
Ok(x) => x.id,
Err(_) => RoleId::new(1),
};
let tmp = CommitteeRoles {
id_wolves: committee.id,
id_role: role,
id_channel: channel,
name_role: committee.name_full.to_owned(),
name_channel: committee.name_profile.to_owned(),
count: 0,
};
// save it to the db in case of crash or error
db_role_set(db, &tmp).await;
// insert it into teh local cache
e.insert(tmp);
re_order = true;
}
i += 1;
}
for committee in &committees {
let r = if let Some(x) = roles_db.get(&committee.id) {
x.id_role
} else {
continue;
}
};
for id_wolves in &committee.committee {
// ID in this is the wolves ID, so we need to get a matching discord ID (if one exists)
if let Some(x) = get_server_member_discord(db, id_wolves).await {
if let Some(member_tmp) = x.discord {
if server.member(ctx, &member_tmp).await.is_ok() {
let values = users_roles.entry(member_tmp).or_insert(vec![]);
values.push(r);
// so if the role exists
if let Some(r) = role {
committee_roles.push(r.id);
if let Some(x) = roles_db.get_mut(&committee.id) {
x.count += 1;
}
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
@ -338,10 +315,6 @@ pub mod committee {
if !roles_required.is_empty() {
// if there are committee roles then give the general purporse role
roles_add.push(committee_member);
if let Some(x) = roles_db.get_mut(&0) {
x.count += 1;
}
}
for role in &roles_required {
@ -362,114 +335,34 @@ pub mod committee {
}
}
let mut channel_names = vec![];
let mut positions = vec![];
for role in roles_db.values() {
// save these to db
db_role_set(db, role).await;
// 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();
if re_order {
let channel_id = role.id_channel.to_owned();
if let Some(channel) = channels.get_mut(&channel_id) {
// record the position of each of teh C&S channels
positions.push(channel.position);
// pull out teh channel names
channel_names.push((role.name_channel.to_owned(), channel_id));
// 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 re_order {
// sort by the position and name
positions.sort();
channel_names.sort_by_key(|(name, _)| name.to_owned());
let mut new_positions = vec![];
for (i, (_, id)) in channel_names.iter().enumerate() {
new_positions.push((id.to_owned(), positions[i] as u64));
}
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);
}
if !new_positions.is_empty() {
match server.reorder_channels(&ctx, new_positions).await {
Ok(_) => {
println!("Successfully re-orderd the committee category");
}
Err(e) => {
dbg!("Failed to re-order ", e);
}
}
}
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CommitteeRoles {
id_wolves: i64,
id_role: RoleId,
id_channel: ChannelId,
pub name_role: String,
name_channel: String,
pub count: i64,
}
impl<'r> FromRow<'r, SqliteRow> for CommitteeRoles {
fn from_row(row: &'r SqliteRow) -> Result<Self, Error> {
Ok(Self {
id_wolves: row.try_get("id_wolves")?,
id_role: get_role_from_row(row, "id_role"),
id_channel: get_channel_from_row(row, "id_channel"),
name_role: row.try_get("name_role")?,
name_channel: row.try_get("name_channel")?,
count: row.try_get("count")?,
})
}
}
async fn db_role_set(db: &Pool<Sqlite>, role: &CommitteeRoles) {
// expiry
match sqlx::query_as::<_, CommitteeRoles>(
"
INSERT INTO committee_roles (id_wolves, id_role, id_channel, name_role, name_channel, count)
VALUES ($1, $2, $3, $4, $5, $6)
ON CONFLICT(id_wolves) DO UPDATE SET name_role = $4, name_channel = $5, count = $6
",
)
.bind(role.id_wolves)
.bind(role.id_role.get() as i64)
.bind(role.id_channel.get() as i64)
.bind(&role.name_role)
.bind(&role.name_channel)
.bind(role.count)
.fetch_optional(db)
.await
{
Ok(_) => {}
Err(e) => {
println!("Failure to insert into Wolves {:?}", role);
println!("{:?}", e);
}
}
}
pub async fn db_roles_get(db: &Pool<Sqlite>) -> Vec<CommitteeRoles> {
// expiry
sqlx::query_as::<_, CommitteeRoles>(
"
SELECT *
FROM committee_roles
",
)
.fetch_all(db)
.await
.unwrap_or_else(|e| {
println!("Failure to get Roles from committee_roles");
println!("{:?}", e);
vec![]
})
}
pub async fn get_committees(db: &Pool<Sqlite>) -> Vec<Committees> {
async fn get_committees(db: &Pool<Sqlite>) -> Vec<Committees> {
sqlx::query_as::<_, Committees>(
r#"
SELECT *

View file

@ -87,7 +87,7 @@ pub mod cns {
server,
// this is the unique api key for each club/soc
wolves_api,
wolves_id,
server_name,
..
} = &server_config;
// dbg!(&server_config);
@ -101,7 +101,7 @@ pub mod cns {
for user in wolves.get_members(wolves_api).await {
// dbg!(&user.committee);
if server_name_tmp.is_none() {
server_name_tmp = Some(user.committee_id);
server_name_tmp = Some(user.committee.to_owned());
}
let id = user.member_id.parse::<u64>().unwrap_or_default();
match existing.get(&(id as i64)) {
@ -124,9 +124,9 @@ pub mod cns {
}
}
if let Some(cs_id) = server_name_tmp {
if &cs_id != wolves_id {
set_server_member(&db, server, cs_id).await;
if let Some(name) = server_name_tmp {
if &name != server_name {
set_server_member(&db, server, &name).await;
}
}
if !user_to_update.is_empty() {
@ -135,15 +135,15 @@ pub mod cns {
}
}
async fn set_server_member(db: &Pool<Sqlite>, server: &GuildId, wolves_id: i64) {
async fn set_server_member(db: &Pool<Sqlite>, server: &GuildId, name: &str) {
match sqlx::query_as::<_, Servers>(
"
UPDATE servers
SET wolves_id = ?
SET server_name = ?
WHERE server = ?
",
)
.bind(wolves_id)
.bind(name)
.bind(server.get() as i64)
.fetch_optional(db)
.await

View file

@ -3,6 +3,8 @@ pub mod common;
use chrono::{Datelike, SecondsFormat, Utc};
use dotenvy::dotenv;
use rand::{distr::Alphanumeric, rng, Rng};
use serenity::all::CommandInteraction;
use serenity::client::Context;
use serenity::model::id::{ChannelId, GuildId, RoleId};
use serenity::prelude::TypeMapKey;
use std::{env, sync::Arc};
@ -125,3 +127,41 @@ pub fn get_now_iso(short: bool) -> String {
pub fn random_string(len: usize) -> String {
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: &CommandInteraction, ctx: &Context) -> Option<String> {
let mut admin = false;
let g_id = match command.guild_id {
None => return Some("Not in a server".to_string()),
Some(x) => x,
};
let roles_server = g_id.roles(&ctx.http).await.unwrap_or_default();
if let Ok(member) = g_id.member(&ctx.http, command.user.id).await {
if let Some(permissions) = member.permissions {
if permissions.administrator() {
admin = true;
}
}
for role_id in member.roles {
if admin {
break;
}
if let Some(role) = roles_server.get(&role_id) {
if role.permissions.administrator() {
admin = true;
}
}
}
}
if !admin {
Some("Administrator permission required".to_string())
} else {
None
}
}

View file

@ -1,7 +1,7 @@
pub mod commands;
use crate::commands::role_adder::tools::on_role_change;
use serenity::all::{ActivityData, Command, CreateMessage, EditInteractionResponse, GuildId, GuildMemberUpdateEvent, Interaction};
use serenity::all::{ActivityData, Command, CreateMessage, EditInteractionResponse, GuildMemberUpdateEvent, Interaction};
use serenity::model::guild::Member;
use serenity::{
async_trait,
@ -68,7 +68,7 @@ impl EventHandler for Handler {
println!("{:?}", e);
}
} else {
let tmp = get_committee(&db, config_server.wolves_id).await;
let tmp = get_committee(&db, &config_server.server_name).await;
if !tmp.is_empty() {
let committee = &tmp[0];
let msg = format!(
@ -113,11 +113,14 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
match Command::set_global_commands(
&ctx.http,
vec![
commands::wolves::register(),
commands::committee::register(),
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
@ -127,33 +130,6 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
println!("{:?}", e)
}
}
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;
match &config_global
.committee_server
.set_commands(&ctx.http, vec![commands::count::committee::register()])
.await
{
Ok(_) => {}
Err(e) => {
println!("{:?}", e)
}
}
match GuildId::new(689189992417067052)
.set_commands(&ctx.http, vec![commands::count::servers::register()])
.await
{
Ok(_) => {}
Err(e) => {
println!("{:?}", e)
}
}
}
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
@ -163,41 +139,16 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
let content = match command.data.name.as_str() {
// user commands
"wolves" => match command.data.options.first() {
None => "Invalid Command".to_string(),
Some(x) => match x.name.as_str() {
"link" => commands::wolves::link::run(&command, &ctx).await,
"verify" => commands::wolves::verify::run(&command, &ctx).await,
"unlink" => commands::wolves::unlink::run(&command, &ctx).await,
"link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await,
// "link" => commands::count::servers::run(&command, &ctx).await,
&_ => format!("not implemented :( wolves {}", x.name.as_str()),
},
},
"link_wolves" => commands::link_email::link::run(&command, &ctx).await,
"verify" => commands::link_email::verify::run(&command, &ctx).await,
"link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await,
// admin commands
"committee" => match command.data.options.first() {
None => "Invalid Command".to_string(),
Some(x) => match x.name.as_str() {
"add" => commands::add_server::run(&command, &ctx).await,
"roles_adder" => commands::role_adder::edit::run(&command, &ctx).await,
// "link" => commands::count::servers::run(&command, &ctx).await,
&_ => format!("not implemented :( committee {}", x.name.as_str()),
},
},
"add" => commands::add_server::run(&command, &ctx).await,
"roles_adder" => commands::role_adder::edit::run(&command, &ctx).await,
"minecraft_add" => commands::minecraft::server::add::run(&command, &ctx).await,
"minecraft_list" => commands::minecraft::server::list::run(&command, &ctx).await,
"minecraft_delete" => commands::minecraft::server::delete::run(&command, &ctx).await,
// sub command
"count" => match command.data.options.first() {
None => "Invalid Command".to_string(),
Some(x) => match x.name.as_str() {
"committee" => commands::count::committee::run(&command, &ctx).await,
"servers" => commands::count::servers::run(&command, &ctx).await,
&_ => format!("not implemented :( count {}", x.name.as_str()),
},
},
_ => format!("not implemented :( {}", command.data.name.as_str()),
_ => "not implemented :(".to_string(),
};
if let Err(why) = command.edit_response(&ctx.http, EditInteractionResponse::new().content(content)).await {
@ -207,15 +158,15 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
}
}
async fn get_committee(db: &Pool<Sqlite>, wolves_id: i64) -> Vec<Committees> {
async fn get_committee(db: &Pool<Sqlite>, committee: &str) -> Vec<Committees> {
sqlx::query_as::<_, Committees>(
r#"
SELECT *
FROM committees
WHERE id = ?
WHERE name_plain = ?
"#,
)
.bind(wolves_id)
.bind(committee)
.fetch_all(db)
.await
.unwrap_or_default()