diff --git a/.gitignore b/.gitignore index 9857dba..2c549b2 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,4 @@ # IDE config .vscode -mooverdb.db \ No newline at end of file +mooverdb.db* \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ae1fbb0..9281f27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -286,6 +286,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "cron" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff76b51e4c068c52bfd2866e1567bee7c567ae8f24ada09fd4307019e25eab7" +dependencies = [ + "chrono", + "nom", + "once_cell", +] + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -966,7 +977,7 @@ dependencies = [ "serenity_utils", "sqlx", "tokio", - "tokio_schedule", + "tokio-cron-scheduler", ] [[package]] @@ -1007,6 +1018,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -2021,6 +2043,21 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tokio-cron-scheduler" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1215c91d6f74868e18a65bda99853e012cfb2a0e42f3438382e318277da76a0" +dependencies = [ + "chrono", + "cron", + "num-derive", + "num-traits", + "tokio", + "tracing", + "uuid", +] + [[package]] name = "tokio-macros" version = "2.2.0" @@ -2093,16 +2130,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tokio_schedule" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b4a14ab1658c39d137ebcc5fbaab364c9922a6cc04ab48b364546c2e6022256" -dependencies = [ - "chrono", - "tokio", -] - [[package]] name = "tower-service" version = "0.3.2" @@ -2284,6 +2311,15 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" +[[package]] +name = "uuid" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +dependencies = [ + "getrandom", +] + [[package]] name = "vcpkg" version = "0.2.15" diff --git a/Cargo.toml b/Cargo.toml index e4eb15a..565e269 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,8 +8,7 @@ edition = "2021" [dependencies] rand = "0.8.5" anyhow = "1.0.71" -# tokio-cron-scheduler = "0.9.4" -tokio_schedule = "0.3.1" +tokio-cron-scheduler = "0.10.0" dotenv = "0.15.0" # poise = "0.5.5" serenity = { version = "0.12.0", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "http", "cache"] } diff --git a/src/main.rs b/src/main.rs index adf8600..5bd624d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,23 @@ -use std::sync::atomic::AtomicUsize; - -use chrono::Utc; use serenity::async_trait; -use serenity::model::channel::Message; -use serenity::model::gateway::Ready; -use serenity::prelude::*; +use serenity::prelude::GatewayIntents; use serenity::client::Context; +use serenity::model::gateway::Ready; +use serenity::all::{Message, EventHandler}; +use serenity::Client; -// use tokio_cron_scheduler::{JobScheduler, JobToRun, Job}; -use tokio_schedule::{every, EveryDay, Job}; +use tokio_cron_scheduler::{JobScheduler, Job}; use util::security::dotenv_var; -use std::sync::Arc; - mod message_handler; use message_handler::handle; +use std::future::Future; +use std::pin::Pin; + +// use chrono::Utc; + mod commands; mod util; -use util::debug::send_error; mod other; use other::notice; @@ -38,28 +37,18 @@ impl EventHandler for Handler { use util::debug::hello; hello(ctx.http.clone()).await; } + + let sched = JobScheduler::new().await.unwrap(); - // notice::notice_wrapper(ctx).await; + let job_closure = move |_, _| -> Pin + Send>> { + let ctx_clone = ctx.clone(); + Box::pin( async move { + notice::notice_wrapper(ctx_clone).await; + }) + }; - // let scheduler = every(1).day().at(13, 30, 0) - // .perform(|| async { - // notice::notice_wrapper(ctx.clone()).await - // }).await; - - // let mut scheduler = JobScheduler::new().await; - // scheduler. - // scheduler.add(match Job::new_async("5 * * * * * *", |uuid, mut l| Box::pin( async { - // notice::notice(ctx.clone()).await; - // })) { - // Ok(_) => {} - // Err(e) => { - // send_error(ctx.http.clone(), e.to_string()); - // panic!() - // } - // }); - // scheduler.add(Job::new(daily("22"), move || { - // notice::notice(ctx.clone()) - // })); + sched.add(Job::new_async("0 0 13 * * *", job_closure).expect("Cron job not set up correctly")).await.unwrap(); + sched.start().await.unwrap(); } } diff --git a/src/other/notice.rs b/src/other/notice.rs index b2d1ea2..512438c 100644 --- a/src/other/notice.rs +++ b/src/other/notice.rs @@ -1,18 +1,20 @@ use chrono::{Datelike, Local}; -use serenity::{all::{GuildId, UserId}, http::Http, prelude::*}; -use crate::util::debug::send_error; +use serenity::{all::{GuildId, UserId}, builder::{CreateEmbed, CreateMessage}, client::Context, http::Http, model::Colour}; use anyhow::Ok; -use std::sync::Arc; +use sqlx::{Connection, FromRow, SqliteConnection}; use crate::util::security::dotenv_var; +use crate::util::debug::send_error; +use crate::util::utilities; -use sqlx::{FromRow, Connection, SqliteConnection}; +use std::sync::Arc; +// pub async fn notice_wrapper(http: Arc) { pub async fn notice_wrapper(ctx: Context) { - match notice(ctx.clone()).await { + match notice(ctx.http.clone()).await { Err(e) => { send_error(ctx.http.clone(), e.to_string()).await; return; @@ -21,44 +23,71 @@ pub async fn notice_wrapper(ctx: Context) { } } -async fn announce_event(guild_id: GuildId, name: &str, special_message: &str, http: Arc) { +async fn announce_event(guild_id: GuildId, name: &str, special_message: &str, http: Arc) -> anyhow::Result<()> { + let mut event_embed = CreateEmbed::new() + .color(Colour::new(rand::random::() % 0xFFFFFF)) + .title("Today's event is:"); + + let system_channel = utilities::get_system_channel(guild_id, http.clone()).await?; + + if special_message.contains("http") { + event_embed = event_embed.description(name); + system_channel.send_message(http.clone(), + CreateMessage::new().add_embed(event_embed.clone()).content(special_message)).await?; + // Ok(()); + } + + event_embed = event_embed.field(name, special_message, true); + system_channel.send_message(http.clone(), + CreateMessage::new().add_embed(event_embed)).await?; + + Ok(()) } -async fn celebrate_birthday(guild_id: GuildId, user_id: UserId, nick: &str, http: Arc) { +async fn celebrate_birthday(guild_id: GuildId, user_id: UserId, nick: &str, http: Arc) -> anyhow::Result<()> { + let system_channel = utilities::get_system_channel(guild_id, http.clone()).await?; + + let embed = CreateEmbed::new() + .color(Colour::new(rand::random::() % 0xFFFFFF)) + .title(format!("HAPPY BIRTHDAY {}!", nick)) + .description(format!("<@{}>'s birthday is today!!! Yay!", user_id.get())); + + system_channel.send_message(http.clone(), CreateMessage::new().add_embed(embed.clone())).await?; + + Ok(()) } #[derive(Clone, FromRow, Debug)] struct BirthdayRow { #[sqlx(try_from="i64")] id: u64, - day: u8, - month: u8, nick: String, } #[derive(Clone, FromRow, Debug)] struct EventRow { + id: u32, #[sqlx(try_from="i64")] guild: u64, name: String, - day: u8, - month: u8, + year: i32, special_message: String, } -async fn notice(ctx: serenity::client::Context) -> anyhow::Result<()> { +async fn notice(http: Arc) -> anyhow::Result<()> { use anyhow::Context; let local = Local::now(); let day = local.day(); let month = local.month(); + let year = local.year(); let db_path = dotenv_var("DATABASE_URL").context("DATABASE_URL not found in env")?; let mut db = SqliteConnection::connect(db_path.as_str()).await?; let birtdays = sqlx::query_as::<_, BirthdayRow>( - "SELECT * FROM birthdays + "SELECT id, nick FROM birthdays WHERE day=? AND month=?;" ) .bind(day) @@ -67,7 +96,7 @@ async fn notice(ctx: serenity::client::Context) -> anyhow::Result<()> { .await?; let global_events = sqlx::query_as::<_, EventRow>( - "SELECT guild, name, day, month, specialMessage from events + "SELECT id, guild, name, year, special_message from events WHERE day=? AND month=? AND guild=0;" ) .bind(day) @@ -75,25 +104,31 @@ async fn notice(ctx: serenity::client::Context) -> anyhow::Result<()> { .fetch_all(&mut db) .await?; - let guilds = ctx.http.get_guilds(None, None).await?; + let guilds = http.get_guilds(None, None).await?; for guild in guilds { let guild_id = guild.id; for bd in &birtdays { let user_id = UserId::new(bd.id); - guild_id.member(ctx.http(), user_id).await?; + guild_id.member(http.clone(), user_id).await?; - celebrate_birthday(guild_id, user_id, bd.nick.as_str(), ctx.http.clone()).await; + celebrate_birthday(guild_id, user_id, bd.nick.as_str(), http.clone()).await?; } + // TODO if has year delete it from announce and delete + for e in &global_events { - announce_event(guild_id, e.name.as_str(), e.special_message.as_str(), ctx.http.clone()).await; + if e.year != 0 && e.year != year { + continue; + } + + announce_event(guild_id, e.name.as_str(), e.special_message.as_str(), http.clone()).await?; } } let global_events = sqlx::query_as::<_, EventRow>( - "SELECT guild, name, day, month, specialMessage from events + "SELECT id, guild, name, year, special_message from events WHERE day=? AND month=? AND guild!=0;" ) .bind(day) @@ -101,8 +136,10 @@ async fn notice(ctx: serenity::client::Context) -> anyhow::Result<()> { .fetch_all(&mut db) .await?; + // TODO if has year delete it from announce and delete + for e in &global_events { - announce_event(GuildId::new(e.guild), e.name.as_str(), e.special_message.as_str(), ctx.http.clone()).await; + announce_event(GuildId::new(e.guild), e.name.as_str(), e.special_message.as_str(), http.clone()).await?; } Ok(()) diff --git a/src/util/debug.rs b/src/util/debug.rs index 384bf5c..39818d2 100644 --- a/src/util/debug.rs +++ b/src/util/debug.rs @@ -1,18 +1,22 @@ -use serenity::{all::ChannelId, builder::CreateMessage, http::Http}; -use std::{process::exit, sync::Arc}; +use std::sync::Arc; -pub async fn send_error(http: Arc, msg: String) { +use serenity::http::Http; + +pub async fn send_error(_http: Arc, msg: String) { println!("ERROR: {msg}"); - + #[cfg(feature="RELEASE")] match ChannelId::new(1199495008416440491) - .send_message(http, CreateMessage::new().content(msg)).await { + .send_message(_http, CreateMessage::new().content(msg)).await { Ok(_) => { return; } Err(_) => { exit(-1) } }; -} - + } + +#[cfg(feature="RELEASE")] pub async fn hello(http: Arc) { + use serenity::http::Http; + let messages = [ "AAAAAAAAAAAAAAAAAAAA", "Henlooo", diff --git a/src/util/mod.rs b/src/util/mod.rs index e68c6ff..74e5d6d 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,2 +1,3 @@ pub mod security; -pub mod debug; \ No newline at end of file +pub mod debug; +pub mod utilities; \ No newline at end of file diff --git a/src/util/utilities.rs b/src/util/utilities.rs index e69de29..781ee0c 100644 --- a/src/util/utilities.rs +++ b/src/util/utilities.rs @@ -0,0 +1,10 @@ +use std::sync::Arc; + +use serenity::{all::{GuildId, ChannelId}, http::Http}; + +use anyhow::Context; + +pub async fn get_system_channel(guild_id: GuildId, http: Arc) -> anyhow::Result { + return http.get_guild(guild_id).await?.system_channel_id + .context(format!("System channel of guild: {} not found", guild_id.get())); +} \ No newline at end of file