Compare commits

..

6 commits
main ... main

Author SHA1 Message Date
313be247d9
db: these changes should make the "db locked" error less likely
relying more on the underlying storage to keep everything safe
2025-09-19 16:17:29 +01:00
6353d77360 Merge pull request 'Remove RwLock for database' (#45) from cordlesscoder/discord-bot:#45_Remove_RwLock into main
Reviewed-on: Skynet/discord-bot#45
2025-09-11 12:02:31 +00:00
Roman Moisieiev
062f826d28 Remove RwLock for database 2025-09-11 12:54:54 +01:00
d8f785b0db Merge pull request 'Fix typos' (#44) from cordlesscoder/discord-bot:#44_Typos into main
Reviewed-on: Skynet/discord-bot#44
2025-09-11 11:49:03 +00:00
Roman Moisieiev
d70a037057 Blame: ignore Fix Typos commit 2025-09-11 12:36:48 +01:00
Roman Moisieiev
7e90f45196 Fix typos 2025-09-11 12:36:03 +01:00
19 changed files with 1035 additions and 1147 deletions

View file

@ -1,2 +1,2 @@
# Fix typos
4b4e5cb2894346684034cba93a5ac1ec6f884f9f
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"

1891
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -19,21 +19,16 @@ name = "update_server-icon"
[[bin]]
name = "cleanup_committee"
# discord library
[dependencies.serenity]
version = "0.12"
default-features = false
features = ["client", "gateway", "rustls_backend", "model", "cache"]
[dependencies]
# discord library
serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "cache"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "full"] }
# wolves api
wolves_oxidised = { git = "https://forgejo.skynet.ie/Skynet/wolves-oxidised.git", features = ["unstable"] }
# wolves_oxidised = { path = "../wolves-oxidised", features = ["unstable"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "full"] }
# to make the http requests
# to make the http requests
surf = "2.3"
dotenvy = "0.15"
@ -52,12 +47,13 @@ chrono = "0.4"
lettre = "0.11"
maud = "0.27"
toml = "0.9.5"
toml = "0.8.23"
serde = "1.0"
# for image conversion
eyre = "0.6.12"
color-eyre = "0.6.5"
usvg = "0.45.1"
resvg = "0.45.1"
tiny-skia = "0.11.4"
eyre = "0.6.8"
color-eyre = "0.6.2"
usvg-text-layout = "0.29.0"
usvg = "0.29.0"
resvg = "0.29.0"
tiny-skia = "0.8.3"

View file

@ -16,7 +16,7 @@ use sqlx::{Pool, Sqlite};
use std::{process, sync::Arc};
use tokio::sync::RwLock;
/// Cleanup the Committee server
/// 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.

View file

@ -56,7 +56,7 @@ impl EventHandler for Handler {
// get the data for each individual club/soc
get_wolves(&ctx).await;
// get the data for the clubs/socs committees
// get teh data for the clubs/socs committees
get_cns(&ctx).await;
// finish up

View file

@ -22,7 +22,7 @@ async fn main() {
// wipe whitelist first
if !wiped.contains(&server.minecraft) {
whitelist_wipe(&server.minecraft, &config.discord_token_minecraft).await;
// add it to the done list so its not done again
// add it to teh done list so its not done again
wiped.insert(&server.minecraft);
}

View file

@ -12,8 +12,13 @@ use skynet_discord_bot::{
},
get_config, Config,
};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::{process, sync::Arc};
use std::{
process,
sync::{
atomic::{AtomicUsize, Ordering},
Arc,
},
};
use tokio::sync::RwLock;
#[tokio::main]

View file

@ -119,7 +119,7 @@ pub mod servers {
cs.push((total, total, String::from("Skynet Network")));
cs.push((wolves_current, wolves_past, String::from("Clubs/Socs Servers")));
// treat the committee server as its own thing
// 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")));

View file

@ -376,7 +376,7 @@ pub(crate) mod server {
}
}
// no need to clear the whitelist as it will be reset within 24hr anyways
// no need to clear teh whitelist as it will be reset within 24hr anyways
"Removed minecraft_server info".to_string()
}

View file

@ -57,7 +57,7 @@ pub(crate) mod user {
format!("{}/src/branch/main/{}/{}", &config_toml.source.repo, &config_toml.source.directory, logo_name)
}
/// Regular users can get the link to the current icon
/// Regular users can get teh link to teh current icon
pub(crate) mod current {
use super::*;

View file

@ -76,7 +76,7 @@ pub mod link {
Some(x) => x,
};
// save the user id and email to the db
// save teh user id and email to teh db
match save_to_db_user(&db, id, email).await {
Ok(x) => x,
Err(x) => {
@ -354,7 +354,7 @@ pub mod verify {
Ok(_) => {
return match set_discord(&db, &command.user.id, &details.email).await {
Ok(_) => {
// get the right roles for the user
// 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
@ -458,8 +458,8 @@ 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 the committee server then give the general committee role
// they will get the more specific vanity role later
// if they are a member of one or more committees, and in teh committee server then give them the 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;
let committee_member = config.committee_role;
@ -497,7 +497,7 @@ pub mod unlink {
data_read.get::<DataBase>().expect("Expected Databse in TypeMap.").clone()
};
// Doesn't matter if there is one or not, it will be removed regardless
// doesn't matter if there is one or not, it will be removed regardless
delete_link(&db, &command.user.id).await;
"Discord link removed".to_string()

View file

@ -7,10 +7,12 @@ use serenity::{
},
prelude::TypeMapKey,
};
use sqlx::sqlite::SqliteJournalMode;
use sqlx::{
sqlite::{SqliteConnectOptions, SqlitePoolOptions, SqliteRow},
Error, FromRow, Pool, Row, Sqlite,
};
use std::time::Duration;
use std::{str::FromStr, sync::Arc};
pub struct DataBase;
@ -67,11 +69,17 @@ impl<'r> FromRow<'r, SqliteRow> for ServerMembersWolves {
}
fn get_discord_from_row(row: &SqliteRow) -> Option<UserId> {
let x: i64 = row.try_get("discord").ok()?;
if x == 0 {
return None;
match row.try_get("discord") {
Ok(x) => {
let tmp: i64 = x;
if tmp == 0 {
None
} else {
Some(UserId::from(tmp as u64))
}
}
_ => None,
}
Some(UserId::from(x as u64))
}
#[derive(Debug, Clone, Deserialize, Serialize)]
@ -220,7 +228,9 @@ pub async fn db_init(config: &Config) -> Result<Pool<Sqlite>, Error> {
.connect_with(
SqliteConnectOptions::from_str(&format!("sqlite://{database}"))?
.foreign_keys(true)
.create_if_missing(true),
.create_if_missing(true)
.journal_mode(SqliteJournalMode::Wal)
.busy_timeout(Duration::from_secs(10)),
)
.await?;

View file

@ -4,10 +4,10 @@
use std::{
ffi::OsStr,
path::{Path, PathBuf},
sync::Arc,
};
use color_eyre::{eyre::bail, Result};
use usvg_text_layout::TreeTextToPath;
#[derive(Debug, Clone)]
pub struct Args {
@ -36,7 +36,7 @@ enum ColorType {
#[derive(Debug, Clone)]
pub struct Renderer {
fontdb: Arc<usvg::fontdb::Database>,
fontdb: usvg_text_layout::fontdb::Database,
colors: ColorType,
size: (u32, u32),
pub count: u64,
@ -44,48 +44,60 @@ pub struct Renderer {
impl Renderer {
pub fn new(args: &Args) -> Result<Self> {
let mut db = usvg::fontdb::Database::default();
let mut db = usvg_text_layout::fontdb::Database::new();
db.load_system_fonts();
let mut this = Self {
fontdb: Arc::new(db),
fontdb: db,
colors: ColorType::None,
size: (args.width, args.height),
count: 0,
};
this.colors = if args.colors.contains(':') {
let obj = args.colors.split(',').map(|s| {
let mut iter = s.split(':');
let [Some(a), Some(b), None] = std::array::from_fn(|_| iter.next()) else {
dbg!("Invalid color object, try checking help");
return None;
};
let colors = if args.colors.contains(':') {
//? object
let obj = args
.colors
.split(',')
.map(|s| {
let s = s.split(':').collect::<Vec<&str>>();
Some((a.to_string(), b.to_string()))
});
if s.len() < 2 {
dbg!("Invalid color object, try checking help");
return None;
}
let colors = obj
.flatten()
.map(|c| {
std::fs::create_dir_all(args.output.join(&c.0))?;
Ok(c)
Some((s[0].to_string(), s[1].to_string()))
})
.collect::<std::io::Result<_>>()?;
.collect::<Vec<Option<(String, String)>>>();
let mut colors = Vec::new();
for c in obj.into_iter().flatten() {
std::fs::create_dir_all(args.output.join(&c.0))?;
colors.push(c);
}
ColorType::Object(colors)
} else {
let colors = args
.colors
.split(',')
.map(|color| -> std::io::Result<String> {
std::fs::create_dir_all(args.output.join(color))?;
Ok(color.to_string())
})
.collect::<std::io::Result<_>>()?;
//? list
// let colors = args.colors.split(",").map(|s| {
// s.to_string()
// })
// .collect::<Vec<String>>();
let mut colors = Vec::new();
for color in args.colors.split(',') {
std::fs::create_dir_all(args.output.join(color))?;
colors.push(color.to_string())
}
ColorType::Array(colors)
};
this.colors = colors;
Ok(this)
}
@ -131,11 +143,10 @@ impl Renderer {
let opt = usvg::Options {
// Get file's absolute directory.
resources_dir: std::fs::canonicalize(fi).ok().and_then(|p| p.parent().map(|p| p.to_path_buf())),
fontdb: self.fontdb.clone(),
..Default::default()
};
let tree = match usvg::Tree::from_data(svg.as_bytes(), &opt) {
let mut tree = match usvg::Tree::from_data(svg.as_bytes(), &opt) {
Ok(v) => v,
Err(_) => {
dbg!("Failed to parse {fi:?}");
@ -143,14 +154,14 @@ impl Renderer {
}
};
let mut pixmap = tiny_skia::Pixmap::new(self.size.0, self.size.1).unwrap();
let scale = {
let x = tree.size().width() / self.size.0 as f32;
let y = tree.size().height() / self.size.0 as f32;
x.min(y)
};
tree.convert_text(&self.fontdb);
resvg::render(&tree, usvg::Transform::default().post_scale(scale, scale), &mut pixmap.as_mut());
let mut pixmap = tiny_skia::Pixmap::new(self.size.0, self.size.1).unwrap();
// log::info!("Rendering {fo:?}");
//? maybe handle this and possibly throw error if its none
let _ = resvg::render(&tree, usvg::FitTo::Size(self.size.0, self.size.1), tiny_skia::Transform::default(), pixmap.as_mut());
pixmap.save_png(fo)?;
self.count += 1;

View file

@ -60,11 +60,17 @@ pub mod get_config_icons {
let config_source = minimal();
let file_path = format!("{}/open-governance/{}/{}", &config.home, &config_source.source.directory, &config_source.source.file);
let contents = fs::read_to_string(file_path).map_err(|e| dbg!(e)).unwrap_or_default();
let festivals = toml::from_str::<ConfigTomlRemote>(&contents)
.map(|config| config.festivals)
.map_err(|e| dbg!(e))
.unwrap_or_default();
let contents = fs::read_to_string(file_path).unwrap_or_else(|e| {
dbg!(e);
"".to_string()
});
let festivals = match toml::from_str::<ConfigTomlRemote>(&contents) {
Ok(config_festivals) => config_festivals.festivals,
Err(e) => {
dbg!(e);
vec![]
}
};
ConfigToml {
source: config_source.source,
@ -302,15 +308,16 @@ pub mod update_icon {
fn logos_filter(festival_data: &FestivalData, existing: Vec<LogoData>) -> Vec<LogoData> {
let mut filtered: Vec<LogoData> = vec![];
let allowed_extensions = ["png", "jpeg", "gif", "svg"];
let allowed_files = vec![".png", ".jpeg", ".gif", ".svg"];
'outer: for logo in existing {
let name_lowercase = logo.name.to_ascii_lowercase();
let name_lowercase = name_lowercase.to_str().unwrap_or_default();
let allowed = {
let extension = name_lowercase.split('.').next_back().unwrap_or_default();
allowed_extensions.contains(&extension)
};
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;
}
@ -325,7 +332,13 @@ pub mod update_icon {
}
} else {
// else filter using the excluded ones
let excluded = festival_data.exclusions.iter().any(|festival| name_lowercase.contains(festival));
let mut excluded = false;
for festival in &festival_data.exclusions {
if name_lowercase.contains(festival) {
excluded = true;
}
}
if !excluded {
filtered.push(logo);
}
@ -336,34 +349,39 @@ pub mod update_icon {
}
async fn logo_set(ctx: &Context, db: &Pool<Sqlite>, server: &GuildId, logo_selected: &LogoData) {
// add to the database
if logo_set_db(db, logo_selected).await.is_err() {
// add to teh database
if !logo_set_db(db, logo_selected).await {
// something went wrong
return;
}
let Some(logo_path) = logo_selected.path.to_str() else {
return;
};
match CreateAttachment::path(logo_path).await {
Ok(icon) => {
// assuming a `guild` has already been bound
let builder = EditGuild::new().icon(Some(&icon));
if let Err(e) = server.edit(ctx, builder).await {
if let Some(logo_path) = logo_selected.path.to_str() {
match CreateAttachment::path(logo_path).await {
Ok(icon) => {
// assuming a `guild` has already been bound
let builder = EditGuild::new().icon(Some(&icon));
if let Err(e) = server.edit(ctx, builder).await {
dbg!(e);
}
}
Err(e) => {
dbg!(e);
}
}
Err(e) => {
dbg!(e);
}
}
}
async fn logo_set_db(db: &Pool<Sqlite>, logo_selected: &LogoData) -> Result<(), ()> {
let name = logo_selected.name.to_str().ok_or(())?;
let path = logo_selected.path.to_str().ok_or(())?;
async fn logo_set_db(db: &Pool<Sqlite>, logo_selected: &LogoData) -> bool {
let name = match logo_selected.name.to_str() {
None => return false,
Some(x) => x,
};
let path = match logo_selected.path.to_str() {
None => return false,
Some(x) => x,
};
sqlx::query_as::<_, ServerIcons>(
match sqlx::query_as::<_, ServerIcons>(
"
INSERT OR REPLACE INTO server_icons (name, date, path)
VALUES (?1, ?2, ?3)
@ -374,9 +392,13 @@ pub mod update_icon {
.bind(path)
.fetch_optional(db)
.await
.map_err(|e| {
dbg!(e);
})?;
Ok(())
{
Ok(_) => {}
Err(e) => {
dbg!(e);
return false;
}
}
true
}
}

View file

@ -185,14 +185,14 @@ pub mod committee {
let server = config_global.committee_server;
// because to use it to update a single user we need to pre-get the members of the server
// because to use it to update a single user we need to pre-get the members of teh server
let mut members = server.members(&ctx, None, None).await.unwrap_or_default();
update_committees(&db, ctx, &config_global, &mut members).await;
}
/**
This function can take a Vec of members (or just one) and gives them the appropriate roles on the committee server
This function can take a Vec of members (or just one) and gives them the appropriate 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;
@ -294,7 +294,7 @@ pub mod committee {
// save it to the db in case of crash or error
db_role_set(db, &tmp).await;
// insert it into the local cache
// insert it into teh local cache
e.insert(tmp);
re_order = true;
@ -327,7 +327,7 @@ pub mod committee {
}
}
// now we have a map of all users that should get roles time to go through all the folks on the server
// now we have a map of all users that should get roles time to go through all the folks on teh server
for member in members {
// if member.user.id != 136522490632601600 {
// continue;
@ -401,10 +401,10 @@ pub mod committee {
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 the C&S channels
// record the position of each of teh C&S channels
positions.push(channel.position);
// pull out the channel names
// pull out teh channel names
channel_names.push((role.name_channel.to_owned(), channel_id));
}
}

View file

@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
use sqlx::{Pool, Sqlite};
/**
This file relates to anything that directly interacts with the wolves API
This file relates to anything that directly interacts with teh wolves API
*/
#[derive(Deserialize, Serialize, Debug)]
@ -80,7 +80,7 @@ pub mod cns {
};
let config = config_lock.read().await;
// set up the client
// set up teh client
let wolves = wolves_oxidised::Client::new(&config.wolves_url, Some(&config.wolves_api));
for server_config in get_server_config_bulk(&db).await {

View file

@ -12,7 +12,7 @@ use tokio::sync::RwLock;
#[derive(Debug)]
pub struct Config {
// manages where the database is stored
// manages where teh database is stored
pub home: String,
pub database: String,
@ -106,20 +106,21 @@ pub fn get_config() -> Config {
}
}
if let Ok(x) = env::var("COMMITTEE_ROLE") {
if let Ok(x) = x.trim().parse() {
if let Ok(x) = x.trim().parse::<u64>() {
config.committee_role = RoleId::new(x);
}
}
if let Ok(x) = env::var("COMMITTEE_CATEGORY") {
for part in x.split(',') {
let Ok(x) = part.trim().parse() else { continue };
config.committee_category.push(ChannelId::new(x));
if let Ok(x) = part.trim().parse::<u64>() {
config.committee_category.push(ChannelId::new(x));
}
}
}
if let Ok(x) = env::var("COMPSOC_DISCORD") {
if let Ok(x) = x.trim().parse() {
config.compsoc_server = GuildId::new(x)
if let Ok(x) = x.trim().parse::<u64>() {
config.compsoc_server = GuildId::new(x);
}
}

View file

@ -31,7 +31,7 @@ struct Handler;
#[async_trait]
impl EventHandler for Handler {
// this caches members of all servers the bot is in
// 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);
@ -212,7 +212,7 @@ Sign up on [UL Wolves]({}) and go to https://discord.com/channels/{}/{} and use
format!("not implemented :( committee {}", x.name.as_str())
}
},
// TODO: move the minecraft commands in here as a subgroup
// TODO: move teh minecraft commands in here as a subgroup
// "link" => commands::count::servers::run(&command, &ctx).await,
&_ => format!("not implemented :( committee {}", x.name.as_str()),
},