Compare commits

..

No commits in common. "main" and "#35_remove-hardcided-servers" have entirely different histories.

29 changed files with 317 additions and 716 deletions

View file

@ -1,7 +1,9 @@
on:
- pull_request
- push
- workflow_dispatch
jobs:
check_lfs:
# nix/docker

View file

@ -1,51 +0,0 @@
on:
- pull_request
jobs:
check_lfs:
# nix/docker
runs-on: nix
steps:
- uses: https://github.com/MPLew-is/lfs-check-action@1
# rust code must be formatted for standardisation
lint_fmt:
# build it using teh base nixos system, helps with caching
runs-on: nix
steps:
# get the repo first
- uses: https://code.forgejo.org/actions/checkout@v4
- uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs/nix@v8
with:
server_url: ${{ gitea.server_url }}
repository: ${{ gitea.repository }}
- run: nix build .#fmt --verbose
# clippy is incredibly useful for making yer code better
lint_clippy:
# build it using teh base nixos system, helps with caching
runs-on: nix
permissions:
checks: write
steps:
# get the repo first
- uses: https://code.forgejo.org/actions/checkout@v4
- uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs/nix@v8
with:
server_url: ${{ gitea.server_url }}
repository: ${{ gitea.repository }}
- run: nix build .#clippy --verbose
build:
# build it using teh base nixos system, helps with caching
runs-on: nix
needs: [ lint_fmt, lint_clippy ]
steps:
# get the repo first
- uses: https://code.forgejo.org/actions/checkout@v4
- uses: https://forgejo.skynet.ie/Skynet/actions/get_lfs/nix@v8
with:
server_url: ${{ gitea.server_url }}
repository: ${{ gitea.repository }}
- name: "Build it locally"
run: nix build --verbose

View file

@ -1,2 +0,0 @@
# Fix typos
7e90f451965b0edbd331765ad295a02f31d2bf24

View file

@ -7,4 +7,4 @@ fn_params_layout = "Compressed"
struct_lit_width = 0
tab_spaces = 2
use_small_heuristics = "Max"
imports_granularity = "Crate"
#imports_granularity="Crate"

View file

@ -1,2 +0,0 @@
[formatting]
column_width = 120

View file

@ -4,9 +4,13 @@ version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "update_data"
[[bin]]
name = "update_users"
[[bin]]
name = "update_committee"
@ -16,9 +20,6 @@ name = "update_minecraft"
[[bin]]
name = "update_server-icon"
[[bin]]
name = "cleanup_committee"
[dependencies]
# discord library
serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] }
@ -34,7 +35,7 @@ surf = "2.3"
dotenvy = "0.15"
# For sqlite
sqlx = { version = "0.8", 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

View file

@ -1,7 +1,7 @@
# Skynet Discord Bot
The Skynet bot is designed to manage users on Discord.
It allows users to link their UL Wolves account with Wolves in a GDPR compliant manner.
Skynet (bot) is hosted by the Computer Society on Skynet (computer cluster).
Skynet (bot) is hosted is hosted by the Computer Society on Skynet (computer cluster).
## Documentation
We have split up the documentation into different segments depending on who the user is.

17
flake.lock generated
View file

@ -32,22 +32,6 @@
"type": "indirect"
}
},
"nixpkgs-mozilla": {
"flake": false,
"locked": {
"lastModified": 1744624473,
"narHash": "sha256-S6zT/w5SyAkJ//dYdjbrXgm+6Vkd/k7qqUl4WgZ6jjk=",
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"rev": "2292d4b35aa854e312ad2e95c4bb5c293656f21a",
"type": "github"
},
"original": {
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1722995383,
@ -67,7 +51,6 @@
"inputs": {
"naersk": "naersk",
"nixpkgs": "nixpkgs_2",
"nixpkgs-mozilla": "nixpkgs-mozilla",
"utils": "utils"
}
},

View file

@ -4,10 +4,6 @@
inputs = {
nixpkgs.url = "nixpkgs/nixos-unstable";
naersk.url = "github:nix-community/naersk";
nixpkgs-mozilla = {
url = "github:mozilla/nixpkgs-mozilla";
flake = false;
};
utils.url = "github:numtide/flake-utils";
};
@ -21,27 +17,12 @@
nixpkgs,
utils,
naersk,
nixpkgs-mozilla,
}:
utils.lib.eachDefaultSystem (
system: let
overrides = (builtins.fromTOML (builtins.readFile ./rust-toolchain.toml));
pkgs = (import nixpkgs) {
inherit system;
overlays = [
(import nixpkgs-mozilla)
];
};
toolchain = (pkgs.rustChannelOf {
rustToolchain = ./rust-toolchain.toml;
sha256 = "sha256-KUm16pHj+cRedf8vxs/Hd2YWxpOrWZ7UOrwhILdSJBU=";
}).rust;
naersk' = pkgs.callPackage naersk {
cargo = toolchain;
rustc = toolchain;
};
pkgs = (import nixpkgs) {inherit system;};
naersk' = pkgs.callPackage naersk {};
package_name = "skynet_discord_bot";
desc = "Skynet Discord Bot";
buildInputs = with pkgs; [
@ -122,14 +103,12 @@
wantedBy = [];
after = ["network-online.target"];
environment = environment_config;
path = with pkgs; [ git git-lfs ];
serviceConfig = {
Type = "oneshot";
User = "${cfg.user}";
Group = "${cfg.user}";
ExecStart = "${self.defaultPackage."${system}"}/bin/${script}";
# kill each service if its ran for 9 min
TimeoutStartSec=540;
EnvironmentFile = [
"${cfg.env.discord}"
"${cfg.env.mail}"
@ -154,14 +133,13 @@
# modify these
scripts = {
# every 10 min
# every 20 min
"update_data" = "*:0,10,20,30,40,50";
# groups are updated every hour, offset from teh ldap
"update_users" = "*:5,15,25,35,45,55";
"update_users" = "*:05:00";
# Committee server has its own timer
"update_committee" = "*:5,15,25,35,45,55";
"update_committee" = "*:15:00";
# minecraft stuff is updated at 5am
# this service does not depend on teh discord cache
"update_minecraft" = "5:10:00";
# server icon gets updated daily at midnight
"update_server-icon" = "0:01:00";
@ -224,7 +202,6 @@
after = ["network-online.target"];
wants = [];
environment = environment_config;
path = with pkgs; [ git git-lfs ];
serviceConfig = {
User = "${cfg.user}";

View file

@ -1,137 +0,0 @@
use serenity::{
all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent},
async_trait,
client::{Context, EventHandler},
model::gateway::GatewayIntents,
Client,
};
use skynet_discord_bot::{
common::{
database::{db_init, DataBase},
set_roles::committee::db_roles_get,
},
get_config, Config,
};
use sqlx::{Pool, Sqlite};
use std::{process, sync::Arc};
use tokio::sync::RwLock;
/// Cleanup teh Committee server
///
/// This removes any invalid roles/channels which have been set up accidentally
/// DO NOT run this locally unless you have a fresh copy of the live database handy.
#[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 {})
.cache_settings(serenity::cache::Settings::default())
.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(db));
}
if let Err(why) = client.start().await {
println!("Client error: {why:?}");
}
}
struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn cache_ready(&self, ctx: Context, _guilds: Vec<GuildId>) {
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;
ctx.shard.chunk_guild(server, Some(2000), false, ChunkGuildFilter::None, None);
println!("Cache loaded");
}
async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) {
if (chunk.chunk_index + 1) == chunk.chunk_count {
println!("Cache built successfully!");
let db = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
};
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;
cleanup(&db, &ctx, &config).await;
// finish up
process::exit(0);
}
}
}
async fn cleanup(db: &Pool<Sqlite>, ctx: &Context, config: &Config) {
let server = config.committee_server;
let committees = db_roles_get(db).await;
if let Ok(channels) = server.channels(ctx).await {
for (id, channel) in &channels {
let name = &channel.name;
let committee_tmp = committees.iter().filter(|x| &x.name_channel == name).collect::<Vec<_>>();
let committee = match committee_tmp.first() {
// if there are no committees which match then this is not a channel we care about
None => {
continue;
}
Some(x) => x,
};
// if the id of the channel does not match then remove it
if id != &committee.id_channel {
println!("Deleting Channel - ID: {} Name: {}", id, &channel.name);
if let Err(e) = channel.delete(ctx).await {
dbg!(e);
}
}
}
}
if let Ok(mut roles) = server.roles(ctx).await {
for (id, role) in &mut roles {
let name = &role.name;
let committee_tmp = committees.iter().filter(|x| &x.name_role == name).collect::<Vec<_>>();
let committee = match committee_tmp.first() {
// if there are no committees which match then this is not a channel we care about
None => {
continue;
}
Some(x) => x,
};
// if the id of the role does not match then remove it
if id != &committee.id_role {
println!("Deleting Role - ID: {} Name: {}", id, &role.name);
if let Err(e) = role.delete(ctx).await {
dbg!(e);
}
}
}
}
}

View file

@ -1,17 +1,12 @@
use serenity::{
all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent},
async_trait,
client::{Context, EventHandler},
model::gateway::GatewayIntents,
model::gateway::{GatewayIntents, Ready},
Client,
};
use skynet_discord_bot::{
common::{
database::{db_init, DataBase},
set_roles::committee,
},
get_config, Config,
};
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;
@ -28,7 +23,6 @@ async fn main() {
// Build our client.
let mut client = Client::builder(&config.discord_token, intents)
.event_handler(Handler {})
.cache_settings(serenity::cache::Settings::default())
.await
.expect("Error creating client");
@ -36,39 +30,25 @@ async fn main() {
let mut data = client.data.write().await;
data.insert::<Config>(Arc::new(RwLock::new(config)));
data.insert::<DataBase>(Arc::new(db));
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
}
if let Err(why) = client.start().await {
println!("Client error: {why:?}");
println!("Client error: {:?}", why);
}
}
struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn cache_ready(&self, ctx: Context, _guilds: Vec<GuildId>) {
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;
async fn ready(&self, ctx: Context, ready: Ready) {
let ctx = Arc::new(ctx);
println!("{} is connected!", ready.user.name);
let server = config_global.committee_server;
ctx.shard.chunk_guild(server, Some(2000), false, ChunkGuildFilter::None, None);
println!("Cache loaded");
}
async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) {
if (chunk.chunk_index + 1) == chunk.chunk_count {
println!("Cache built successfully!");
// u[date committee server
committee::check_committee(&ctx).await;
committee::check_committee(Arc::clone(&ctx)).await;
// finish up
process::exit(0);
}
}
}

View file

@ -4,13 +4,10 @@ use serenity::{
model::gateway::{GatewayIntents, Ready},
Client,
};
use skynet_discord_bot::{
common::{
database::{db_init, DataBase},
wolves::{cns::get_wolves, committees::get_cns},
},
get_config, Config,
};
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;
@ -30,7 +27,6 @@ async fn main() {
// Build our client.
let mut client = Client::builder(&config.discord_token, intents)
.event_handler(Handler {})
.cache_settings(serenity::cache::Settings::default())
.await
.expect("Error creating client");
@ -38,11 +34,11 @@ async fn main() {
let mut data = client.data.write().await;
data.insert::<Config>(Arc::new(RwLock::new(config)));
data.insert::<DataBase>(Arc::new(db));
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
}
if let Err(why) = client.start().await {
println!("Client error: {why:?}");
println!("Client error: {:?}", why);
}
}

View file

@ -1,10 +1,6 @@
use skynet_discord_bot::{
common::{
database::db_init,
minecraft::{get_minecraft_config, update_server, whitelist_wipe},
},
get_config,
};
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]

View file

@ -4,11 +4,9 @@ use serenity::{
model::gateway::{GatewayIntents, Ready},
Client,
};
use skynet_discord_bot::common::server_icon::{get_config_icons, update_icon};
use skynet_discord_bot::{
common::{
database::{db_init, DataBase},
server_icon::{get_config_icons, update_icon},
},
common::database::{db_init, DataBase},
get_config, Config,
};
use std::{process, sync::Arc};
@ -27,7 +25,6 @@ async fn main() {
// Build our client.
let mut client = Client::builder(&config.discord_token, intents)
.event_handler(Handler {})
.cache_settings(serenity::cache::Settings::default())
.await
.expect("Error creating client");
@ -35,11 +32,11 @@ async fn main() {
let mut data = client.data.write().await;
data.insert::<Config>(Arc::new(RwLock::new(config)));
data.insert::<DataBase>(Arc::new(db));
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
}
if let Err(why) = client.start().await {
println!("Client error: {why:?}");
println!("Client error: {:?}", why);
}
}
@ -50,10 +47,11 @@ impl EventHandler for Handler {
let ctx = Arc::new(ctx);
println!("{} is connected!", ready.user.name);
let 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;
let config_lock = {
let data_read = ctx.data.read().await;

View file

@ -1,24 +1,13 @@
use serenity::{
all::{ChunkGuildFilter, GuildId, GuildMembersChunkEvent},
async_trait,
client::{Context, EventHandler},
model::gateway::GatewayIntents,
model::gateway::{GatewayIntents, Ready},
Client,
};
use skynet_discord_bot::{
common::{
database::{db_init, get_server_config_bulk, DataBase},
set_roles::normal,
},
get_config, Config,
};
use std::{
process,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
};
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::{get_config, Config};
use std::{process, sync::Arc};
use tokio::sync::RwLock;
#[tokio::main]
@ -33,11 +22,7 @@ async fn main() {
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 {
server_count: Default::default(),
server_cached: Default::default(),
})
.cache_settings(serenity::cache::Settings::default())
.event_handler(Handler {})
.await
.expect("Error creating client");
@ -45,51 +30,38 @@ async fn main() {
let mut data = client.data.write().await;
data.insert::<Config>(Arc::new(RwLock::new(config)));
data.insert::<DataBase>(Arc::new(db));
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
}
if let Err(why) = client.start().await {
println!("Client error: {why:?}");
println!("Client error: {:?}", why);
}
}
struct Handler {
server_count: AtomicUsize,
server_cached: AtomicUsize,
}
struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn cache_ready(&self, ctx: Context, guilds: Vec<GuildId>) {
self.server_count.swap(guilds.len(), Ordering::SeqCst);
for guild in guilds {
ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None);
}
println!("Cache loaded {}", &self.server_count.load(Ordering::SeqCst));
}
async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) {
if (chunk.chunk_index + 1) == chunk.chunk_count {
self.server_cached.fetch_add(1, Ordering::SeqCst);
if (self.server_cached.load(Ordering::SeqCst) + 1) == self.server_count.load(Ordering::SeqCst) {
println!("Cache built successfully!");
async fn ready(&self, ctx: Context, ready: Ready) {
let ctx = Arc::new(ctx);
println!("{} is connected!", ready.user.name);
// this goes into each server and sets roles for each wolves member
check_bulk(&ctx).await;
check_bulk(Arc::clone(&ctx)).await;
// finish up
process::exit(0);
}
}
}
}
async fn check_bulk(ctx: &Context) {
let db = {
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()
};
let db = db_lock.read().await;
for server_config in get_server_config_bulk(&db).await {
normal::update_server(ctx, &server_config, &[], &[]).await;
normal::update_server(&ctx, &server_config, &[], &[]).await;
}
}

View file

@ -1,12 +1,8 @@
use serenity::{
all::{CommandDataOption, CommandDataOptionValue, CommandInteraction},
client::Context,
};
use skynet_discord_bot::common::{
database::{get_server_config, DataBase, Servers},
set_roles::normal::update_server,
wolves::cns::get_wolves,
};
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction};
use serenity::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 sqlx::{Error, Pool, Sqlite};
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
@ -56,10 +52,11 @@ pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
return "Please provide a valid channel for ``Bot Channel``".to_string();
};
let db = {
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 server_data = Servers {
server: command.guild_id.unwrap_or_default(),
@ -75,8 +72,8 @@ pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
match add_server(&db, ctx, &server_data).await {
Ok(_) => {}
Err(e) => {
println!("{e:?}");
return format!("Failure to insert into Servers {server_data:?}");
println!("{:?}", e);
return format!("Failure to insert into Servers {:?}", server_data);
}
}
@ -101,7 +98,7 @@ async fn add_server(db: &Pool<Sqlite>, ctx: &Context, server: &Servers) -> Resul
.fetch_optional(db)
.await;
// if the entry does not exist already then do a user update
// if the entry does not exist already tehn do a user update
let (update, current_remove, current_role, past_remove, past_role) = match &existing {
None => (true, false, None, false, None),
Some(x) => {

View file

@ -5,7 +5,8 @@ pub mod committee {
use serenity::all::{
CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, Context, CreateCommand, CreateCommandOption,
};
use skynet_discord_bot::common::{database::DataBase, set_roles::committee::db_roles_get};
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 {
@ -27,10 +28,11 @@ pub mod committee {
false
};
let db = {
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
@ -51,7 +53,7 @@ pub mod committee {
for (count, name) in cs {
let leading = if count < 10 { " " } else { "" };
let line = format!("{leading}{count} {name}");
let line = format!("{}{} {}", leading, count, name);
let length = line.len() + 1;
@ -83,28 +85,23 @@ 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},
set_roles::committee::get_committees,
},
get_now_iso,
};
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 = {
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();
if let Some(x) = get_committees(&db).await {
for committee in x {
for committee in get_committees(&db).await {
committees.insert(committee.id, committee.to_owned());
}
}
let mut cs = vec![];
// pull it from a DB
@ -146,11 +143,11 @@ pub mod servers {
""
};
let line = format!("{current_leading}{current} {past_leading}{past} {name}");
let line = format!("{}{} {}{} {}", current_leading, current, past_leading, past, name);
let length = line.len() + 1;
// +3 is to account for the closing fence
// +3 is to account for the closing fense
if length < (limit + 3) {
response.push(line);
limit -= length;

View file

@ -9,24 +9,19 @@ pub(crate) mod user {
use super::*;
use crate::commands::wolves::link::get_server_member_discord;
use serde::{Deserialize, Serialize};
use serenity::{
all::{CommandDataOption, CommandDataOptionValue, CommandInteraction},
model::id::UserId,
};
use skynet_discord_bot::{
common::{
database::Wolves,
minecraft::{whitelist_update, Minecraft},
},
Config,
};
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction};
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 async fn run(command: &CommandInteraction, ctx: &Context) -> String {
let db = {
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;
@ -74,14 +69,14 @@ pub(crate) mod user {
Ok(_) => {}
Err(e) => {
dbg!("{:?}", e);
return format!("Failure to minecraft username {username:?}");
return format!("Failure to minecraft username {:?}", username);
}
}
username_mc = username.to_string();
} else {
match get_minecraft_bedrock(username, &config.minecraft_mcprofile).await {
None => {
return format!("No UID found for {username:?}");
return format!("No UID found for {:?}", username);
}
Some(x) => {
match add_minecraft_bedrock(&db, &command.user.id, &x.floodgateuid).await {
@ -190,17 +185,14 @@ pub(crate) mod server {
use super::*;
pub(crate) mod add {
use serenity::{
all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption},
model::id::GuildId,
};
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommand, CreateCommandOption};
use serenity::model::id::GuildId;
use sqlx::Error;
// this is to manage the server side of commands related to minecraft
// this is to managfe the server side of commands related to minecraft
use super::*;
use skynet_discord_bot::{
common::minecraft::{update_server, Minecraft},
Config,
};
use skynet_discord_bot::common::minecraft::update_server;
use skynet_discord_bot::common::minecraft::Minecraft;
use skynet_discord_bot::Config;
pub fn register() -> CreateCommand {
CreateCommand::new("minecraft_add")
@ -228,15 +220,16 @@ pub(crate) mod server {
return String::from("Expected Server ID");
};
let db = {
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;
match add_server(&db, &g_id, &server_minecraft).await {
Ok(_) => {}
Err(e) => {
println!("{e:?}");
println!("{:?}", e);
return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft);
}
}
@ -267,14 +260,12 @@ pub(crate) mod server {
}
pub(crate) mod list {
use serenity::{all::CommandInteraction, builder::CreateCommand, client::Context};
use skynet_discord_bot::{
common::{
database::DataBase,
minecraft::{get_minecraft_config_server, server_information},
},
Config,
};
use serenity::all::CommandInteraction;
use serenity::builder::CreateCommand;
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;
pub fn register() -> CreateCommand {
CreateCommand::new("minecraft_list")
@ -288,10 +279,11 @@ pub(crate) mod server {
Some(x) => x,
};
let db = {
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 servers = get_minecraft_config_server(&db, g_id).await;
@ -328,13 +320,12 @@ pub(crate) mod server {
}
pub(crate) mod delete {
use serenity::{
all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption},
builder::CreateCommand,
client::Context,
model::id::GuildId,
};
use skynet_discord_bot::common::{database::DataBase, minecraft::Minecraft};
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction, CommandOptionType, CreateCommandOption};
use serenity::builder::CreateCommand;
use serenity::client::Context;
use serenity::model::id::GuildId;
use skynet_discord_bot::common::database::DataBase;
use skynet_discord_bot::common::minecraft::Minecraft;
use sqlx::{Error, Pool, Sqlite};
pub fn register() -> CreateCommand {
@ -363,15 +354,16 @@ pub(crate) mod server {
return String::from("Expected Server ID");
};
let db = {
let data = ctx.data.read().await;
data.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
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;
match server_remove(&db, &g_id, &server_minecraft).await {
Ok(_) => {}
Err(e) => {
println!("{e:?}");
println!("{:?}", e);
return format!("Failure to insert into Minecraft {} {}", &g_id, &server_minecraft);
}
}

View file

@ -62,10 +62,11 @@ pub mod edit {
false
};
let db = {
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 server = command.guild_id.unwrap_or_default();
let server_data = RoleAdder {
@ -78,8 +79,8 @@ pub mod edit {
match add_server(&db, &server_data, delete).await {
Ok(_) => {}
Err(e) => {
println!("{e:?}");
return format!("Failure to insert into Servers {server_data:?}");
println!("{:?}", e);
return format!("Failure to insert into Servers {:?}", server_data);
}
}
@ -100,9 +101,9 @@ pub mod edit {
}
if delete {
format!("Removed {role_a_name} + {role_b_name} = {role_c_name}")
format!("Removed {} + {} = {}", role_a_name, role_b_name, role_c_name)
} else {
format!("Added {role_a_name} + {role_b_name} = {role_c_name}")
format!("Added {} + {} = {}", role_a_name, role_b_name, role_c_name)
}
}
@ -141,12 +142,13 @@ pub mod edit {
pub mod list {}
pub mod tools {
use serenity::{client::Context, model::guild::Member};
use serenity::client::Context;
use serenity::model::guild::Member;
use skynet_discord_bot::common::database::RoleAdder;
use sqlx::{Pool, Sqlite};
pub async fn on_role_change(db: &Pool<Sqlite>, ctx: &Context, new_data: Member) {
// check if the role changed is part of the ones for this server
// check if the role changed is part of the oens for this server
if let Ok(role_adders) = sqlx::query_as::<_, RoleAdder>(
r#"
SELECT *
@ -162,7 +164,7 @@ pub mod tools {
let mut roles_remove = vec![];
for role_adder in role_adders {
// if the user has both A and B give them C
// if the user has both A dnd B give them C
if new_data.roles.contains(&role_adder.role_a) && new_data.roles.contains(&role_adder.role_b) && !new_data.roles.contains(&role_adder.role_c)
{
roles_add.push(role_adder.role_c);
@ -178,13 +180,13 @@ pub mod tools {
if !roles_add.is_empty() {
if let Err(e) = new_data.add_roles(&ctx, &roles_add).await {
println!("{e:?}");
println!("{:?}", e);
}
}
if !roles_remove.is_empty() {
if let Err(e) = new_data.remove_roles(&ctx, &roles_remove).await {
println!("{e:?}");
println!("{:?}", e);
}
}
}

View file

@ -18,10 +18,11 @@ pub(crate) mod admin {
use super::*;
pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String {
let db = {
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;
@ -68,10 +69,11 @@ pub(crate) mod user {
use sqlx::{Pool, Sqlite};
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
let db = {
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_toml = get_config_icons::minimal();
@ -91,7 +93,7 @@ pub(crate) mod user {
}
}
pub async fn get_current_icon(db: &Pool<Sqlite>) -> Option<ServerIcons> {
async fn get_current_icon(db: &Pool<Sqlite>) -> Option<ServerIcons> {
match sqlx::query_as::<_, ServerIcons>(
"
SELECT * from server_icons ORDER BY id DESC LIMIT 1
@ -111,10 +113,8 @@ pub(crate) mod user {
pub(crate) mod festival {
use serenity::all::{CommandInteraction, Context};
use skynet_discord_bot::{
common::server_icon::{get_config_icons, update_icon::get_festival},
Config,
};
use skynet_discord_bot::common::server_icon::{get_config_icons, update_icon::get_festival};
use skynet_discord_bot::Config;
// use this to return what current festivals are active?
pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String {
@ -143,10 +143,11 @@ pub(crate) mod user {
use sqlx::{Pool, Sqlite};
pub async fn run(_command: &CommandInteraction, ctx: &Context) -> String {
let db = {
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_toml = get_config_icons::minimal();
@ -204,11 +205,11 @@ pub(crate) mod user {
// the `` is so that the numbers will be rendered in monospaced font
// the <> is to suppress the URL embed
let line = format!("``{current_leading}{times}`` [{name}](<{url}>)");
let line = format!("``{}{}`` [{}](<{}>)", current_leading, times, name, url);
let length = line.len() + 1;
// +3 is to account for the closing fence
// +3 is to account for the closing fense
if length < (limit + 3) {
response.push(line);
limit -= length;

View file

@ -4,16 +4,11 @@ use lettre::{
Message, SmtpTransport, Transport,
};
use maud::html;
use serenity::{
all::CommandOptionType,
builder::{CreateCommand, CreateCommandOption},
client::Context,
model::id::UserId,
};
use skynet_discord_bot::{
common::database::{DataBase, Wolves, WolvesVerify},
get_now_iso, random_string, Config,
};
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};
use sqlx::{Pool, Sqlite};
pub mod link {
@ -21,10 +16,11 @@ pub mod link {
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction};
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
let db = {
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;
@ -100,7 +96,7 @@ pub mod link {
return "Email already verified".to_string();
}
// generate an auth key
// generate a auth key
let auth = random_string(20);
match send_mail(&config, &details.email, &auth, &command.user.name) {
Ok(_) => match save_to_db(&db, &details, &auth, &command.user.id).await {
@ -114,7 +110,7 @@ pub mod link {
}
}
format!("Verification email sent to {email}, it may take up to 15 min for it to arrive. If it takes longer check the Junk folder.")
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 async fn get_server_member_discord(db: &Pool<Sqlite>, user: &UserId) -> Option<Wolves> {
@ -209,7 +205,7 @@ pub mod link {
.subject("Skynet: Link Discord to Wolves.")
.multipart(
// This is composed of two parts.
// also helps not trip spam settings (uneven number of urls)
// 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())),
@ -283,40 +279,22 @@ pub mod link {
}
}
pub mod link_docs {
use super::*;
pub mod users {
use super::*;
use serenity::all::CommandInteraction;
pub async fn run(_command: &CommandInteraction, _ctx: &Context) -> String {
"https://forgejo.skynet.ie/Skynet/discord-bot/src/branch/main/doc/User.md".to_string()
}
}
// pub mod committee {
//
// }
}
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},
model::user::User,
};
use skynet_discord_bot::common::{
database::{get_server_config, ServerMembersWolves, Servers},
wolves::committees::Committees,
};
use serenity::all::{CommandDataOption, CommandDataOptionValue, CommandInteraction};
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 = {
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_wolves
let details = if let Some(x) = get_verify_from_db(&db, &command.user.id).await {
@ -363,12 +341,12 @@ pub mod verify {
"Discord username linked to Wolves".to_string()
}
Err(e) => {
println!("{e:?}");
println!("{:?}", e);
"Failed to save, please try /link_wolves again".to_string()
}
};
}
Err(e) => println!("{e:?}"),
Err(e) => println!("{:?}", e),
}
"Failed to verify".to_string()
@ -425,7 +403,7 @@ pub mod verify {
}
if let Err(e) = member.add_roles(&ctx, &roles).await {
println!("{e:?}");
println!("{:?}", e);
}
}
}
@ -441,7 +419,7 @@ pub mod verify {
WHERE committee LIKE ?1
"#,
)
.bind(format!("%{wolves_id}%"))
.bind(format!("%{}%", wolves_id))
.fetch_all(db)
.await
.unwrap_or_else(|e| {
@ -458,7 +436,7 @@ pub mod verify {
let config = config_lock.read().await;
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 them the general committee role
// 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 = config.committee_server;
@ -492,12 +470,13 @@ pub mod unlink {
use sqlx::{Pool, Sqlite};
pub async fn run(command: &CommandInteraction, ctx: &Context) -> String {
let db = {
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;
// doesn't matter if there is one or not, it will be removed regardless
// 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()
@ -543,5 +522,4 @@ pub fn register() -> CreateCommand {
.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)),
)
.add_option(CreateCommandOption::new(CommandOptionType::SubCommand, "docs", "Link to where the documentation can be found."))
}

View file

@ -1,21 +1,17 @@
use crate::Config;
use serde::{Deserialize, Serialize};
use serenity::{
model::{
guild,
id::{ChannelId, GuildId, RoleId, UserId},
},
prelude::TypeMapKey,
};
use sqlx::{
sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow},
Error, FromRow, Pool, Row, Sqlite,
};
use std::{str::FromStr, sync::Arc};
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<Pool<Sqlite>>;
type Value = Arc<RwLock<Pool<Sqlite>>>;
}
#[derive(Debug, Clone, Deserialize, Serialize)]
@ -224,7 +220,7 @@ pub async fn db_init(config: &Config) -> Result<Pool<Sqlite>, Error> {
let pool = SqlitePoolOptions::new()
.max_connections(5)
.connect_with(
SqliteConnectOptions::from_str(&format!("sqlite://{database}"))?
SqliteConnectOptions::from_str(&format!("sqlite://{}", database))?
.foreign_keys(true)
.create_if_missing(true),
)

View file

@ -1,7 +1,10 @@
use crate::{common::set_roles::normal::get_server_member_bulk, Config};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
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, Error, FromRow, Pool, Row, Sqlite};
use sqlx::sqlite::SqliteRow;
use sqlx::{Error, FromRow, Pool, Row, Sqlite};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Minecraft {
@ -24,7 +27,7 @@ impl<'r> FromRow<'r, SqliteRow> for Minecraft {
/**
loop through all members of server
get a list of folks with mc accounts that are members
and a list that aren't 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![];
@ -109,7 +112,7 @@ pub async fn whitelist_wipe(server: &str, token: &str) {
};
post(&format!("{url_base}/files/delete"), &bearer, &deletion).await;
// recreate the file, passing in the type here so the compiler knows what type of Vec it is
// 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
@ -152,7 +155,7 @@ pub async fn get_minecraft_config_server(db: &Pool<Sqlite>, g_id: GuildId) -> Ve
}
pub async fn whitelist_update(add: &Vec<(String, bool)>, server: &str, token: &str) {
println!("Update whitelist for {server}");
println!("Update whitelist for {}", server);
let url_base = format!("https://panel.games.skynet.ie/api/client/servers/{server}");
let bearer = format!("Bearer {token}");

View file

@ -1,11 +1,10 @@
// this code is taken from https://github.com/MCorange99/svg2colored-png/tree/main
// I was unable to figure out how to use usvg myself so yoinked it from here.
// I was unable to figure out how to use usvg myself so younked it from here.
use std::{
ffi::OsStr,
path::{Path, PathBuf},
};
use std::ffi::OsStr;
use std::path::{Path, PathBuf};
// use clap::builder::OsStr;
use color_eyre::{eyre::bail, Result};
use usvg_text_layout::TreeTextToPath;
@ -16,7 +15,7 @@ pub struct Args {
/// Output folder where the PNG's will be placed
pub output: PathBuf,
/// Comma separated colors that will be used in HEX Eg. 000000,ffffff
/// Comma seperated colors that will be used in HEX Eg. 000000,ffffff
/// Can be like an object: black:000000,white:ffffff
pub colors: String,
@ -178,7 +177,7 @@ impl Renderer {
}
fn set_color(&self, svg: &str, color: &String) -> String {
svg.replace("fill=\"currentColor\"", &format!("fill=\"#{color}\""))
svg.replace("fill=\"currentColor\"", &format!("fill=\"#{}\"", color))
}
fn get_svg_data(&self, fi: &Path) -> Result<String> {

View file

@ -191,28 +191,6 @@ pub mod update_icon {
{
dbg!(e);
}
if let Err(e) = Command::new("git")
// Install LFS for the repo
.arg("lfs")
.arg("install")
.current_dir(&folder)
.output()
{
dbg!(e);
}
if let Err(e) = Command::new("git")
// clone the repo, gracefully "fails"
.arg("lfs")
.arg("pull")
.arg("origin")
.arg("main")
.current_dir(&folder)
.output()
{
dbg!(e);
}
}
fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec<LogoData> {
@ -281,7 +259,7 @@ pub mod update_icon {
// check if exists
if !path_new.exists() {
// convert if it hasn't been converted already
// convert if it hasnt been converted already
match r.render(&path_local, &args) {
Ok(_) => {}
Err(_e) => {
@ -308,19 +286,9 @@ pub mod update_icon {
fn logos_filter(festival_data: &FestivalData, existing: Vec<LogoData>) -> Vec<LogoData> {
let mut filtered: Vec<LogoData> = vec![];
let allowed_files = vec![".png", ".jpeg", ".gif", ".svg"];
'outer: for logo in existing {
let name_lowercase0 = logo.name.to_ascii_lowercase();
let name_lowercase = name_lowercase0.to_str().unwrap_or_default();
let mut allowed = false;
for allowed_type in &allowed_files {
if name_lowercase.ends_with(allowed_type) {
allowed = true;
}
}
if !allowed {
continue;
}
if !festival_data.current.is_empty() {
// if its a current festival filter based on it

View file

@ -1,27 +1,18 @@
pub mod normal {
use crate::{
common::database::{DataBase, ServerMembersWolves, Servers, Wolves},
get_now_iso,
};
use serenity::{
client::Context,
model::id::{GuildId, RoleId, UserId},
};
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};
struct RolesChange {
total: i32,
new: i32,
current_add: i32,
current_rem: i32,
}
pub async fn update_server(ctx: &Context, server: &Servers, remove_roles: &[Option<RoleId>], members_changed: &[UserId]) {
let db = {
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,
@ -29,12 +20,7 @@ pub mod normal {
..
} = server;
let mut roles_set = RolesChange {
total: 0,
new: 0,
current_add: 0,
current_rem: 0,
};
let mut roles_set = [0, 0, 0];
let mut members = vec![];
for member in get_server_member_bulk(&db, server).await {
@ -46,30 +32,28 @@ pub mod normal {
if let Ok(x) = server.members(ctx, None, None).await {
for member in x {
// members_changed acts as an override to only deal with the users in it
// 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) {
roles_set.total += 1;
let mut roles = vec![];
if let Some(role) = &role_past {
if !member.roles.contains(role) {
roles_set.new += 1;
roles_set[0] += 1;
roles.push(role.to_owned());
}
}
if !member.roles.contains(role_current) {
roles_set.current_add += 1;
roles_set[1] += 1;
roles.push(role_current.to_owned());
}
if let Err(e) = member.add_roles(ctx, &roles).await {
println!("{e:?}");
println!("{:?}", e);
}
} else {
// old and never
@ -81,16 +65,16 @@ pub mod normal {
}
if member.roles.contains(role_current) {
roles_set.current_rem += 1;
// if they're not a current member and have the role then remove it
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:?}");
println!("{:?}", e);
}
}
}
for role in remove_roles.iter().flatten() {
if let Err(e) = member.remove_role(ctx, role).await {
println!("{e:?}");
println!("{:?}", e);
}
}
}
@ -99,14 +83,7 @@ pub mod normal {
set_server_numbers(&db, server, members_all as i64, members.len() as i64).await;
// small bit of logging to note changes over time
println!(
"{:?} Total: {} Changes: New: +{}, Current: +{}/-{}",
server.get(),
roles_set.total,
roles_set.new,
roles_set.current_add,
roles_set.current_rem
);
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> {
@ -146,7 +123,7 @@ pub mod normal {
Ok(_) => {}
Err(e) => {
println!("Failure to insert into {}", server.get());
println!("{e:?}");
println!("{:?}", e);
}
}
}
@ -154,29 +131,30 @@ pub mod normal {
// for updating committee members
pub mod committee {
use crate::{
common::{
database::{get_channel_from_row, get_role_from_row, DataBase, Wolves},
wolves::committees::Committees,
},
Config,
};
use crate::common::database::{get_channel_from_row, get_role_from_row, DataBase, Wolves};
use crate::common::wolves::committees::Committees;
use crate::Config;
use serde::{Deserialize, Serialize};
use serenity::{
all::EditRole,
builder::CreateChannel,
client::Context,
model::{channel::ChannelType, guild::Member, id::ChannelId, prelude::RoleId},
};
use sqlx::{sqlite::SqliteRow, Error, FromRow, Pool, Row, Sqlite};
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::sqlite::SqliteRow;
use sqlx::{Error, FromRow, Pool, Row, Sqlite};
use std::collections::HashMap;
use std::sync::Arc;
pub async fn check_committee(ctx: &Context) {
let db = {
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()
@ -188,21 +166,16 @@ pub mod committee {
// 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;
update_committees(&db, &ctx, &config_global, &mut members).await;
}
/**
This function can take a Vec of members (or just one) and gives them the appropriate roles on teh committee server
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 = config.committee_role;
let committees = match get_committees(db).await {
None => {
return;
}
Some(x) => x,
};
let committees = get_committees(db).await;
let categories = config.committee_category.clone();
// information about the server
@ -224,11 +197,11 @@ pub mod committee {
let mut channels = server.channels(&ctx).await.unwrap_or_default();
// a map of users and the roles they are going to be getting
// 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 they don't already exist
// we need to create roles and channels if tehy dont already exist
let mut category_index = 0;
let mut i = 0;
loop {
@ -329,21 +302,21 @@ pub mod committee {
// 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 {
// if member.user.id != 136522490632601600 {
// continue;
// }
//
let roles_current = member.roles(ctx).unwrap_or_default();
let roles_required = match users_roles.get(&member.user.id) {
None => {
vec![]
}
Some(x) => x.to_owned(),
Some(x) => {
let mut tmp = x.to_owned();
if !tmp.is_empty() {
tmp.push(committee_member);
}
tmp
}
};
let on_committee = !roles_required.is_empty();
let mut roles_rem = vec![];
let mut roles_add = vec![];
// get a list of all the roles to remove from someone
@ -352,25 +325,14 @@ pub mod committee {
for role in &roles_current {
roles_current_id.push(role.id.to_owned());
if !roles_required.contains(&role.id) {
if role.id == committee_member && on_committee {
continue;
}
roles_rem.push(role.id.to_owned());
}
}
let has_committee_role = roles_current_id.contains(&committee_member);
if on_committee && !has_committee_role {
// if there are committee roles then give the general purpose role
roles_add.push(committee_member);
}
if !on_committee && has_committee_role {
roles_rem.push(committee_member);
}
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;
}
@ -389,6 +351,8 @@ pub mod committee {
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();
}
}
@ -436,10 +400,10 @@ pub mod committee {
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct CommitteeRoles {
id_wolves: i64,
pub id_role: RoleId,
pub id_channel: ChannelId,
id_role: RoleId,
id_channel: ChannelId,
pub name_role: String,
pub name_channel: String,
name_channel: String,
pub count: i64,
}
@ -476,8 +440,8 @@ pub mod committee {
{
Ok(_) => {}
Err(e) => {
println!("Failure to insert into Wolves {role:?}");
println!("{e:?}");
println!("Failure to insert into Wolves {:?}", role);
println!("{:?}", e);
}
}
}
@ -494,13 +458,13 @@ pub mod committee {
.await
.unwrap_or_else(|e| {
println!("Failure to get Roles from committee_roles");
println!("{e:?}");
println!("{:?}", e);
vec![]
})
}
pub async fn get_committees(db: &Pool<Sqlite>) -> Option<Vec<Committees>> {
match sqlx::query_as::<_, Committees>(
pub async fn get_committees(db: &Pool<Sqlite>) -> Vec<Committees> {
sqlx::query_as::<_, Committees>(
r#"
SELECT *
FROM committees
@ -508,13 +472,10 @@ pub mod committee {
)
.fetch_all(db)
.await
{
Ok(x) => Some(x),
Err(e) => {
.unwrap_or_else(|e| {
dbg!(e);
None
}
}
vec![]
})
}
async fn get_server_member_discord(db: &Pool<Sqlite>, user: &i64) -> Option<Wolves> {

View file

@ -38,8 +38,8 @@ async fn add_users_wolves(db: &Pool<Sqlite>, user: &WolvesResultUserMin) {
{
Ok(_) => {}
Err(e) => {
println!("Failure to insert into Wolves {user:?}");
println!("{e:?}");
println!("Failure to insert into Wolves {:?}", user);
println!("{:?}", e);
}
}
}
@ -48,14 +48,12 @@ async fn add_users_wolves(db: &Pool<Sqlite>, user: &WolvesResultUserMin) {
This is getting data for Clubs and Socs
*/
pub mod cns {
use crate::{
common::{
database::{get_server_config_bulk, DataBase, ServerMembers, ServerMembersWolves, Servers},
wolves::{add_users_wolves, WolvesResultUserMin},
},
Config,
};
use serenity::{client::Context, model::id::GuildId};
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;
@ -69,10 +67,11 @@ pub mod cns {
}
pub async fn get_wolves(ctx: &Context) {
let db = {
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;
@ -97,6 +96,7 @@ pub mod cns {
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);
@ -115,6 +115,10 @@ pub mod cns {
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);
}
}
}
}
@ -125,6 +129,9 @@ pub mod cns {
set_server_member(&db, server, cs_id).await;
}
}
if !user_to_update.is_empty() {
update_server(ctx, &server_config, &[], &user_to_update).await;
}
}
}
@ -144,7 +151,7 @@ pub mod cns {
Ok(_) => {}
Err(e) => {
println!("Failure to set server name {}", server.get());
println!("{e:?}");
println!("{:?}", e);
}
}
}
@ -183,7 +190,7 @@ pub mod cns {
Ok(_) => {}
Err(e) => {
println!("Failure to insert into ServerMembers {} {:?}", server.get(), user);
println!("{e:?}");
println!("{:?}", e);
}
}
}
@ -193,7 +200,8 @@ pub mod cns {
Get and store the data on C&S committees
*/
pub mod committees {
use crate::{common::database::DataBase, Config};
use crate::common::database::DataBase;
use crate::Config;
use serenity::client::Context;
use sqlx::{Pool, Sqlite};
@ -223,10 +231,11 @@ pub mod committees {
}
pub async fn get_cns(ctx: &Context) {
let db = {
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;
@ -261,8 +270,8 @@ pub mod committees {
{
Ok(_) => {}
Err(e) => {
println!("Failure to insert into Committees {committee:?}");
println!("{e:?}");
println!("Failure to insert into Committees {:?}", committee);
println!("{:?}", e);
}
}
}

View file

@ -3,10 +3,8 @@ pub mod common;
use chrono::{Datelike, SecondsFormat, Utc};
use dotenvy::dotenv;
use rand::{distr::Alphanumeric, rng, Rng};
use serenity::{
model::id::{ChannelId, GuildId, RoleId},
prelude::TypeMapKey,
};
use serenity::model::id::{ChannelId, GuildId, RoleId};
use serenity::prelude::TypeMapKey;
use std::{env, sync::Arc};
use tokio::sync::RwLock;
@ -36,7 +34,7 @@ pub struct Config {
pub committee_role: RoleId,
pub committee_category: Vec<ChannelId>,
// items pertaining to CompSoc only
// items pertaining to compsoc only
pub compsoc_server: GuildId,
}
impl TypeMapKey for Config {
@ -111,7 +109,7 @@ pub fn get_config() -> Config {
}
}
if let Ok(x) = env::var("COMMITTEE_CATEGORY") {
for part in x.split(',') {
for part in x.split(",") {
if let Ok(x) = part.trim().parse::<u64>() {
config.committee_category.push(ChannelId::new(x));
}

View file

@ -1,28 +1,21 @@
pub mod commands;
use crate::commands::role_adder::tools::on_role_change;
use serenity::all::{ActivityData, Command, CommandDataOptionValue, CreateMessage, EditInteractionResponse, GuildMemberUpdateEvent, Interaction};
use serenity::model::guild::Member;
use serenity::{
all::{Command, CommandDataOptionValue, CreateMessage, EditInteractionResponse, Interaction},
async_trait,
client::{Context, EventHandler},
gateway::{ActivityData, ChunkGuildFilter},
model::{
event::GuildMemberUpdateEvent,
gateway::{GatewayIntents, Ready},
guild::Member,
id::GuildId,
user::OnlineStatus,
},
Client,
};
use skynet_discord_bot::{
common::{
database::{db_init, get_server_config, get_server_member, DataBase},
set_roles::committee::update_committees,
wolves::committees::Committees,
},
get_config, Config,
};
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;
@ -31,21 +24,15 @@ struct Handler;
#[async_trait]
impl EventHandler for Handler {
// this caches members of all servers teh bot is in
async fn cache_ready(&self, ctx: Context, guilds: Vec<GuildId>) {
for guild in guilds {
ctx.shard.chunk_guild(guild, Some(2000), false, ChunkGuildFilter::None, None);
}
println!("Cache built successfully!");
}
// handles previously linked accounts joining the server
async fn guild_member_addition(&self, ctx: Context, new_member: Member) {
let 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;
let config_lock = {
let data_read = ctx.data.read().await;
data_read.get::<Config>().expect("Expected Config in TypeMap.").clone()
@ -79,7 +66,7 @@ impl EventHandler for Handler {
}
if let Err(e) = new_member.add_roles(&ctx, &roles).await {
println!("{e:?}");
println!("{:?}", e);
}
} else {
let tmp = get_committee(&db, config_server.wolves_id).await;
@ -107,12 +94,14 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
// 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 = {
let db_lock = {
let data_read = ctx.data.read().await;
data_read.get::<DataBase>().expect("Expected Config in TypeMap.").clone()
};
// check if the role changed is part of the ones for this server
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;
}
@ -142,7 +131,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
{
Ok(_) => {}
Err(e) => {
println!("{e:?}")
println!("{:?}", e)
}
}
@ -150,17 +139,17 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
match config.committee_server.set_commands(&ctx.http, vec![commands::count::committee::register()]).await {
Ok(_) => {}
Err(e) => {
println!("{e:?}")
println!("{:?}", e)
}
}
// CompSoc Server
// compsoc Server
match config
.compsoc_server
.set_commands(
&ctx.http,
vec![
// commands just for the CompSoc server
// commands just for the compsoc server
commands::count::servers::register(),
commands::server_icon::user::register(),
],
@ -169,7 +158,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
{
Ok(_) => {}
Err(e) => {
println!("{e:?}")
println!("{:?}", e)
}
}
}
@ -177,7 +166,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::Command(command) = interaction {
let _ = command.defer_ephemeral(&ctx.http).await;
// println!("Received command interaction: {:#?}", command);
//println!("Received command interaction: {:#?}", command);
let content = match command.data.name.as_str() {
// user commands
@ -188,7 +177,6 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
"verify" => commands::wolves::verify::run(&command, &ctx).await,
"unlink" => commands::wolves::unlink::run(&command, &ctx).await,
"link_minecraft" => commands::minecraft::user::add::run(&command, &ctx).await,
"docs" => commands::wolves::link_docs::users::run(&command, &ctx).await,
// "link" => commands::count::servers::run(&command, &ctx).await,
&_ => format!("not implemented :( wolves {}", x.name.as_str()),
},
@ -256,7 +244,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
};
if let Err(why) = command.edit_response(&ctx.http, EditInteractionResponse::new().content(content)).await {
println!("Cannot respond to slash command: {why}");
println!("Cannot respond to slash command: {}", why);
}
}
}
@ -291,8 +279,7 @@ async fn main() {
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)
.cache_settings(serenity::cache::Settings::default())
.event_handler(Handler {})
.await
.expect("Error creating client");
@ -300,7 +287,7 @@ async fn main() {
let mut data = client.data.write().await;
data.insert::<Config>(Arc::new(RwLock::new(config)));
data.insert::<DataBase>(Arc::new(db));
data.insert::<DataBase>(Arc::new(RwLock::new(db)));
}
// Finally, start a single shard, and start listening to events.
@ -308,6 +295,6 @@ async fn main() {
// Shards will automatically attempt to reconnect, and will perform
// exponential backoff until it reconnects.
if let Err(why) = client.start().await {
println!("Client error: {why:?}");
println!("Client error: {:?}", why);
}
}