From 269b5df7d78aac8ecaa23f3c1009d93ad0896b5a Mon Sep 17 00:00:00 2001 From: jakubmanczak Date: Fri, 2 May 2025 18:20:08 +0200 Subject: [PATCH] make events a module; guild name changes, misc --- src/discordbot/events.rs | 166 ------------------------------- src/discordbot/events/mod.rs | 107 ++++++++++++++++++++ src/discordbot/events/service.rs | 81 +++++++++++++++ 3 files changed, 188 insertions(+), 166 deletions(-) delete mode 100644 src/discordbot/events.rs create mode 100644 src/discordbot/events/mod.rs create mode 100644 src/discordbot/events/service.rs diff --git a/src/discordbot/events.rs b/src/discordbot/events.rs deleted file mode 100644 index 856549a..0000000 --- a/src/discordbot/events.rs +++ /dev/null @@ -1,166 +0,0 @@ -use super::events; -use chrono::{Datelike, Duration, Local, TimeZone, Utc}; -use chrono_tz::Europe::Warsaw; -use serenity::all::{Context, CreateAttachment, EditGuild, Guild, GuildId}; -use std::time::Duration as StdDuration; -use tracing::{error, info, warn}; - -const MAIN_GUILD_ID: GuildId = GuildId::new(447075692664979466); - -pub enum Event { - Normal, - PolskaGórą, - PrideMonth, - ValentineDay, - Rogaliki, - Halloween, - Christmas, - NewYears, - AnniversaryGractwo, - AnniversaryTF2, - AnniversaryMinecraft, - StarWarsDay, -} - -impl Event { - pub fn icon(&self) -> &str { - use Event as E; - match self { - E::PolskaGórą => "./assets/logo-x512-polish.png", - E::PrideMonth => "./assets/logo-x512-lgbtflag.png", - E::StarWarsDay => "./assets/logo-x512-starwars.png", - _ => "./assets/logo-x512.png", - } - } - pub fn guild_name(&self) -> String { - use Event as E; - match self { - E::PolskaGórą => "Gractwo 🇵🇱".into(), - E::PrideMonth => "Gractwo 🏳️‍🌈".into(), - E::ValentineDay => "Gractwo 💗".into(), - E::Rogaliki => "Rogalictwo 🥐".into(), - E::Halloween => "Spooky Gractwo 🎃".into(), - E::Christmas => "Jolly Gractwo 🎄🎁☃️".into(), - E::NewYears => "New Gractwo, New Me 🎉".into(), - E::AnniversaryTF2 => "Polscy Gracze Team Fortress".into(), - E::AnniversaryGractwo => { - // TODO: this will conjugate badly in 17 years - let xlecie = Utc::now().year_ce().1 - 2020; - format!("{xlecie} lat Gractwa!") - } - _ => "Gractwo".into(), - } - } -} - -pub fn get_current_event() -> Event { - let today = Local::now(); - let (month, day) = (today.month(), today.day()); - - match (day, month) { - (1, 1) => Event::NewYears, - (2..=6, 1) => Event::Christmas, - (14, 2) => Event::ValentineDay, - (1..=3, 5) => Event::PolskaGórą, - (4, 5) => Event::StarWarsDay, - (17, 5) => Event::AnniversaryMinecraft, - (7, 6) => Event::AnniversaryGractwo, - (_, 6) => Event::PrideMonth, - (24, 8) => Event::AnniversaryTF2, - (10, 10) => Event::AnniversaryTF2, - (31, 10) => Event::Halloween, - (11, 11) => Event::Rogaliki, - (24..=30, 12) => Event::Christmas, - (31, 12) => Event::NewYears, - _ => Event::Normal, - } -} - -pub fn init_service(ctx: &Context, guild_id: &GuildId) { - let (ctx, guild_id) = (ctx.clone(), guild_id.clone()); - info!("Initialising event automation service..."); - - if guild_id != MAIN_GUILD_ID { - info!("Guild event automation service not initialised; Bot not running on main guild."); - return; - } - - tokio::spawn(async move { - run_event_service(ctx, guild_id).await; - }); -} - -pub async fn run_event_service(ctx: Context, guild_id: GuildId) { - loop { - update_guild(&ctx, guild_id).await.ok(); - sleep_until_next_midnight().await; - } -} - -async fn update_guild(ctx: &Context, guild_id: GuildId) -> Result<(), String> { - let mut guild = match Guild::get(&ctx.http, guild_id).await { - Ok(g) => g, - Err(e) => { - error!("Could not get guild info..."); - return Err("Could not get guild info: {e}".to_string()); - } - }; - let event = events::get_current_event(); - - let icon = match CreateAttachment::path(event.icon()).await { - Ok(i) => i, - Err(e) => { - error!("Could not create icon attachment..."); - return Err("Could not create icon attachment: {e}".to_string()); - } - }; - - match guild - .edit( - &ctx.http, - EditGuild::new().name(event.guild_name()).icon(Some(&icon)), - ) - .await - { - Ok(_) => info!("Guild name/icon updated."), - Err(e) => { - error!("Could not update guild name/icon..."); - return Err("Could not update guild name/icon: {e}".to_string()); - } - }; - - Ok(()) -} - -async fn sleep_until_next_midnight() { - let now = Utc::now().with_timezone(&Warsaw); - let tomorrow = now.date_naive() + chrono::Duration::days(1); - - let next_midnight = match Warsaw - .with_ymd_and_hms( - tomorrow.year_ce().1 as i32, - tomorrow.month(), - tomorrow.day(), - 0, - 0, - 0, - ) - .earliest() - { - Some(nm) => nm, - None => Utc::now().with_timezone(&Warsaw) + Duration::minutes(61), - // e.g. jumps in time occur at 2AM/3AM, Midnight shouldn't be affected - // but even if it was, returning None, a gap shouldn't last more than an hour. - }; - - let sleeptime = match next_midnight.signed_duration_since(now).to_std() { - Ok(t) => t, - Err(_) => { - warn!("The next midnight is, supposedly, not in the future."); - warn!("Sleeping an hour instead..."); - StdDuration::from_secs(1 * 60 * 60) - } - }; - - tokio::time::sleep(sleeptime).await; -} diff --git a/src/discordbot/events/mod.rs b/src/discordbot/events/mod.rs new file mode 100644 index 0000000..4bcc5fe --- /dev/null +++ b/src/discordbot/events/mod.rs @@ -0,0 +1,107 @@ +use chrono::{Datelike, Utc}; +use chrono_tz::Europe::Warsaw; +use rand::seq::IndexedRandom; +use serenity::all::{Context, GuildId}; +use service::run_event_service; +use tracing::info; + +mod service; + +pub fn init_service(ctx: &Context, guild_id: &GuildId) { + let (ctx, guild_id) = (ctx.clone(), guild_id.clone()); + info!("Initialising event automation service..."); + + if guild_id != MAIN_GUILD_ID { + info!("Guild event automation service not initialised; Bot not running on main guild."); + return; + } + + tokio::spawn(async move { + run_event_service(ctx, guild_id).await; + }); +} + +const MAIN_GUILD_ID: GuildId = GuildId::new(447075692664979466); + +pub enum Event { + Normal, + PolskaGórą, + PrideMonth, + ValentineDay, + Rogaliki, + Halloween, + Christmas, + NewYears, + AnniversaryGractwo, + AnniversaryTF2, + AnniversaryMinecraft, + StarWarsDay, +} + +impl Event { + pub fn get_current() -> Self { + let now = Utc::now().with_timezone(&Warsaw); + match (now.day(), now.month()) { + (1, 1) => Event::NewYears, + (2..=6, 1) => Event::Christmas, + (14, 2) => Event::ValentineDay, + (1..=3, 5) => Event::PolskaGórą, + (4, 5) => Event::StarWarsDay, + (17, 5) => Event::AnniversaryMinecraft, + (7, 6) => Event::AnniversaryGractwo, + (_, 6) => Event::PrideMonth, + (24, 8) => Event::AnniversaryTF2, + (10, 10) => Event::AnniversaryTF2, + (31, 10) => Event::Halloween, + (11, 11) => Event::Rogaliki, + (24..=30, 12) => Event::Christmas, + (31, 12) => Event::NewYears, + _ => Event::Normal, + } + } + pub fn icon(&self) -> &str { + use Event as E; + match self { + E::PolskaGórą => "./assets/logo-x512-polish.png", + E::PrideMonth => "./assets/logo-x512-lgbtflag.png", + E::StarWarsDay => "./assets/logo-x512-starwars.png", + _ => "./assets/logo-x512.png", + } + } + pub fn guild_name(&self) -> String { + use Event as E; + match self { + E::PolskaGórą => { + let now = Utc::now().with_timezone(&Warsaw); + let suffix = match (now.day(), now.month()) { + (1, 5) => ": Święto Pracy", + (2, 5) => ": Dzień Flagi", + (3, 5) => ": Święto Konstytucji", + _ => "", + }; + format!("Gractwo{suffix}") + } + E::PrideMonth => { + let mut rng = rand::rng(); + const VARIANTS: &[&str] = &["Miesiąc Dumy", "Pride Month", "LGBT Rights!"]; + format!("Gractwo: {}", VARIANTS.choose(&mut rng).unwrap()) + } + E::ValentineDay => { + let mut rng = rand::rng(); + const VARIANTS: &[&str] = &["Gractwo is in love!", "Gractwo Dating Sim"]; + VARIANTS.choose(&mut rng).unwrap().to_string() + } + E::Rogaliki => "Rogalictwo 🥐".to_string(), + E::Halloween => "Spooky Gractwo 🎃".to_string(), + E::Christmas => "Jolly Gractwo 🎄🎁☃️".to_string(), + E::NewYears => "New Gractwo, New Me 🎉".to_string(), + E::AnniversaryTF2 => "PGTF: Polscy Gracze Team Fortress".to_string(), + E::AnniversaryGractwo => { + // TODO: this will conjugate badly in 17 years + let xlecie = Utc::now().year_ce().1 - 2020; + format!("{xlecie} lat Gractwa!") + } + _ => "Gractwo".to_string(), + } + } +} diff --git a/src/discordbot/events/service.rs b/src/discordbot/events/service.rs new file mode 100644 index 0000000..873ceee --- /dev/null +++ b/src/discordbot/events/service.rs @@ -0,0 +1,81 @@ +use chrono::{Datelike, Duration, TimeZone, Utc}; +use chrono_tz::Europe::Warsaw; +use serenity::all::{Context, CreateAttachment, EditGuild, Guild, GuildId}; +use tracing::{error, info, warn}; + +use super::Event; + +pub async fn run_event_service(ctx: Context, guild_id: GuildId) { + loop { + update_guild(&ctx, guild_id).await.ok(); + sleep_until_next_midnight().await; + } +} + +async fn update_guild(ctx: &Context, guild_id: GuildId) -> Result<(), String> { + let mut guild = match Guild::get(&ctx.http, guild_id).await { + Ok(g) => g, + Err(e) => { + error!("Could not get guild info: {e}"); + return Err("Could not get guild info: {e}".to_string()); + } + }; + let event = Event::get_current(); + + let icon = match CreateAttachment::path(event.icon()).await { + Ok(i) => i, + Err(e) => { + error!("Could not create icon attachment: {e}"); + return Err("Could not create icon attachment: {e}".to_string()); + } + }; + + match guild + .edit( + &ctx.http, + EditGuild::new().name(event.guild_name()).icon(Some(&icon)), + ) + .await + { + Ok(_) => info!("Guild name/icon updated."), + Err(e) => { + error!("Could not update guild name/icon: {e}"); + return Err("Could not update guild name/icon: {e}".to_string()); + } + }; + + Ok(()) +} + +async fn sleep_until_next_midnight() { + let now = Utc::now().with_timezone(&Warsaw); + let tomorrow = now.date_naive() + chrono::Duration::days(1); + + let next_midnight = match Warsaw + .with_ymd_and_hms( + tomorrow.year_ce().1 as i32, + tomorrow.month(), + tomorrow.day(), + 0, + 0, + 0, + ) + .earliest() + { + Some(nm) => nm, + None => Utc::now().with_timezone(&Warsaw) + Duration::minutes(61), + // e.g. jumps in time occur at 2AM/3AM, Midnight shouldn't be affected + // but even if it was, returning None, a gap shouldn't last more than an hour. + }; + + let sleeptime = match next_midnight.signed_duration_since(now).to_std() { + Ok(t) => t, + Err(_) => { + warn!("The next midnight is, supposedly, not in the future."); + warn!("Sleeping an hour instead..."); + std::time::Duration::from_secs(1 * 60 * 60) + } + }; + + tokio::time::sleep(sleeptime).await; +}