feat: split out the functions so they can be shared with commands

This commit is contained in:
silver 2025-06-15 22:07:52 +01:00
parent 9d50efb757
commit 6d5ad8e418
Signed by untrusted user: silver
GPG key ID: 36F93D61BAD3FD7D
3 changed files with 283 additions and 274 deletions

263
src/common/server_icon.rs Normal file
View file

@ -0,0 +1,263 @@
use serde::Deserialize;
use std::{ffi::OsString, path::PathBuf};
#[derive(Deserialize)]
pub struct ConfigToml {
pub source: ConfigTomlSource,
pub festivals: Vec<ConfigTomlFestivals>,
}
#[derive(Deserialize, Debug)]
pub struct ConfigTomlSource {
pub repo: String,
pub directory: String,
}
#[derive(Deserialize, Debug)]
pub struct ConfigTomlFestivals {
pub name: String,
pub all_year: bool,
pub start: ConfigTomlFestivalsTime,
pub end: ConfigTomlFestivalsTime,
}
#[derive(Deserialize, Debug)]
pub struct ConfigTomlFestivalsTime {
pub day: u32,
pub month: u32,
pub year: i32,
}
pub fn get_config_icons() -> ConfigToml {
let toml_raw = include_str!("../../.server-icons.toml");
let config: ConfigToml = toml::from_str(toml_raw).unwrap();
config
}
#[derive(Debug)]
pub struct LogoData {
pub name: OsString,
pub path: PathBuf,
}
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct ServerIcons {
pub id: i64,
pub name: String,
pub date: String,
}
pub mod update_icon {
use super::*;
use crate::{
common::renderer::{Args, Renderer},
get_now_iso, Config,
};
use chrono::{Datelike, Utc};
use rand::{rngs::SmallRng, seq::IndexedRandom, SeedableRng};
use serenity::{
all::GuildId,
builder::{CreateAttachment, EditGuild},
client::Context,
};
use sqlx::{Pool, Sqlite};
use std::{fs, process::Command};
/// Update the server icon, pulling from open governance.
pub async fn update_icon_main(ctx: &Context, db: &Pool<Sqlite>, config_global: &Config, config_toml: &ConfigToml) {
let server = GuildId::new(689189992417067052);
// clone repo into local folder
clone_repo(config_global, config_toml);
// see if there is a current festival
let festival_data = get_festival(config_toml);
// get a list of all the graphics files
let logos = get_logos(config_global, config_toml);
// filter them so only the current season (if any) are active
let logos_filtered = logos_filter(&festival_data, logos);
let mut rng = SmallRng::from_os_rng();
let logo_selected = logos_filtered.choose(&mut rng).unwrap();
logo_set(ctx, db, &server, logo_selected).await;
}
#[derive(Debug)]
struct FestivalData {
current: Vec<String>,
exclusions: Vec<String>,
}
fn get_festival(config_toml: &ConfigToml) -> FestivalData {
let today = Utc::now();
let day = today.day();
let month = today.month();
let year = today.year();
let mut result = FestivalData {
current: vec![],
exclusions: vec![],
};
for festival in &config_toml.festivals {
if (day >= festival.start.day && day <= festival.end.day) && (month >= festival.start.month && month <= festival.end.month) {
if festival.start.year == 0 || festival.end.year == 0 || (year >= festival.start.year && year <= festival.end.year) {
result.current.push(festival.name.to_owned());
}
} else if !festival.all_year {
result.exclusions.push(festival.name.to_owned());
}
}
result
}
fn clone_repo(config: &Config, config_toml: &ConfigToml) {
let url = &config_toml.source.repo;
let folder = format!("{}/open-governance", &config.home);
Command::new("git").arg("clone").arg(url).arg(&folder).output().expect("failed to execute process");
Command::new("git")
.arg("pull")
.arg("origin")
.arg("main")
.current_dir(&folder)
.output()
.expect("failed to execute process");
}
fn get_logos(config: &Config, config_toml: &ConfigToml) -> Vec<LogoData> {
let folder = format!("{}/open-governance/{}", &config.home, &config_toml.source.directory);
let folder_path = PathBuf::from(&folder);
let mut folder_output = folder_path.clone();
folder_output.push("converted");
let paths = fs::read_dir(folder).unwrap();
let args = Args {
input: folder_path.clone(),
output: folder_output,
colors: String::from(""),
width: 1024,
height: 1024,
};
let mut r = Renderer::new(&args).unwrap();
let mut logos = vec![];
for tmp in paths.flatten() {
let path_local = tmp.path().to_owned();
let path_local2 = tmp.path().to_owned();
let name = path_local2.file_name().unwrap().to_owned();
let mut path = tmp.path();
if path.is_dir() {
continue;
}
match tmp.path().extension() {
None => {}
Some(ext) => {
if ext == "svg" {
let mut path_new = path_local.clone();
path_new.set_extension("png");
let filename_tmp = path_new.clone();
let filename = filename_tmp.file_name().unwrap_or_default();
path_new.pop();
path_new.push("converted");
path_new.push(filename);
// check if exists
if !path_new.exists() {
// convert if it hasnt been converted already
match r.render(&path_local, &args) {
Ok(_) => {}
Err(_e) => {
dbg!("Failed to render {path_local:?}: {}");
}
}
}
path = path_new;
}
}
};
logos.push(LogoData {
name,
path,
});
// println!("Name: {}", &tmp.path().display());
}
logos
}
fn logos_filter(festival_data: &FestivalData, existing: Vec<LogoData>) -> Vec<LogoData> {
let mut filtered: Vec<LogoData> = vec![];
'outer: for logo in existing {
let name_lowercase0 = logo.name.to_ascii_lowercase();
let name_lowercase = name_lowercase0.to_str().unwrap_or_default();
if !festival_data.current.is_empty() {
// if its a current festival filter based on it
for festival in &festival_data.current {
if name_lowercase.contains(festival) {
filtered.push(logo);
continue 'outer;
}
}
} else {
// else filter using the excluded ones
let mut excluded = false;
for festival in &festival_data.exclusions {
if name_lowercase.contains(festival) {
excluded = true;
}
}
if !excluded {
filtered.push(logo);
}
}
}
filtered
}
async fn logo_set(ctx: &Context, db: &Pool<Sqlite>, server: &GuildId, logo_selected: &LogoData) {
// add to teh database
logo_set_db(db, logo_selected).await;
let icon = CreateAttachment::path(logo_selected.path.to_str().unwrap_or_default()).await.unwrap();
// assuming a `guild` has already been bound
let builder = EditGuild::new().icon(Some(&icon));
server.edit(ctx, builder).await.unwrap();
}
async fn logo_set_db(db: &Pool<Sqlite>, logo_selected: &LogoData) {
let name = logo_selected.name.to_str().unwrap_or_default();
match sqlx::query_as::<_, ServerIcons>(
"
INSERT OR REPLACE INTO server_icons (name, date)
VALUES (?1, ?2)
",
)
.bind(name)
.bind(get_now_iso(false))
.fetch_optional(db)
.await
{
Ok(_) => {}
Err(e) => {
dbg!(e);
}
}
}
}