Created cron job for events and birthdays announcements

This commit is contained in:
Ladislav Hano 2024-02-16 21:54:07 +01:00
parent 970b8c9327
commit bbc34312bb
8 changed files with 149 additions and 73 deletions

2
.gitignore vendored
View file

@ -10,4 +10,4 @@
# IDE config # IDE config
.vscode .vscode
mooverdb.db mooverdb.db*

58
Cargo.lock generated
View file

@ -286,6 +286,17 @@ dependencies = [
"cfg-if", "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]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.3.11" version = "0.3.11"
@ -966,7 +977,7 @@ dependencies = [
"serenity_utils", "serenity_utils",
"sqlx", "sqlx",
"tokio", "tokio",
"tokio_schedule", "tokio-cron-scheduler",
] ]
[[package]] [[package]]
@ -1007,6 +1018,17 @@ dependencies = [
"zeroize", "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]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.46" version = "0.1.46"
@ -2021,6 +2043,21 @@ dependencies = [
"windows-sys 0.48.0", "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]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.2.0" version = "2.2.0"
@ -2093,16 +2130,6 @@ dependencies = [
"tracing", "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]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.2" version = "0.3.2"
@ -2284,6 +2311,15 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "uuid"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"

View file

@ -8,8 +8,7 @@ edition = "2021"
[dependencies] [dependencies]
rand = "0.8.5" rand = "0.8.5"
anyhow = "1.0.71" anyhow = "1.0.71"
# tokio-cron-scheduler = "0.9.4" tokio-cron-scheduler = "0.10.0"
tokio_schedule = "0.3.1"
dotenv = "0.15.0" dotenv = "0.15.0"
# poise = "0.5.5" # poise = "0.5.5"
serenity = { version = "0.12.0", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "http", "cache"] } serenity = { version = "0.12.0", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "http", "cache"] }

View file

@ -1,24 +1,23 @@
use std::sync::atomic::AtomicUsize;
use chrono::Utc;
use serenity::async_trait; use serenity::async_trait;
use serenity::model::channel::Message; use serenity::prelude::GatewayIntents;
use serenity::model::gateway::Ready;
use serenity::prelude::*;
use serenity::client::Context; 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_cron_scheduler::{JobScheduler, Job};
use tokio_schedule::{every, EveryDay, Job};
use util::security::dotenv_var; use util::security::dotenv_var;
use std::sync::Arc;
mod message_handler; mod message_handler;
use message_handler::handle; use message_handler::handle;
use std::future::Future;
use std::pin::Pin;
// use chrono::Utc;
mod commands; mod commands;
mod util; mod util;
use util::debug::send_error;
mod other; mod other;
use other::notice; use other::notice;
@ -38,28 +37,18 @@ impl EventHandler for Handler {
use util::debug::hello; use util::debug::hello;
hello(ctx.http.clone()).await; hello(ctx.http.clone()).await;
} }
let sched = JobScheduler::new().await.unwrap();
// notice::notice_wrapper(ctx).await; let job_closure = move |_, _| -> Pin<Box<dyn Future<Output = ()> + 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) sched.add(Job::new_async("0 0 13 * * *", job_closure).expect("Cron job not set up correctly")).await.unwrap();
// .perform(|| async { sched.start().await.unwrap();
// 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())
// }));
} }
} }

View file

@ -1,18 +1,20 @@
use chrono::{Datelike, Local}; 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 anyhow::Ok;
use std::sync::Arc; use sqlx::{Connection, FromRow, SqliteConnection};
use crate::util::security::dotenv_var; 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<Http>) {
pub async fn notice_wrapper(ctx: Context) { pub async fn notice_wrapper(ctx: Context) {
match notice(ctx.clone()).await { match notice(ctx.http.clone()).await {
Err(e) => { Err(e) => {
send_error(ctx.http.clone(), e.to_string()).await; send_error(ctx.http.clone(), e.to_string()).await;
return; 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<Http>) { async fn announce_event(guild_id: GuildId, name: &str, special_message: &str, http: Arc<Http>) -> anyhow::Result<()> {
let mut event_embed = CreateEmbed::new()
.color(Colour::new(rand::random::<u32>() % 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<Http>) { async fn celebrate_birthday(guild_id: GuildId, user_id: UserId, nick: &str, http: Arc<Http>) -> anyhow::Result<()> {
let system_channel = utilities::get_system_channel(guild_id, http.clone()).await?;
let embed = CreateEmbed::new()
.color(Colour::new(rand::random::<u32>() % 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)] #[derive(Clone, FromRow, Debug)]
struct BirthdayRow { struct BirthdayRow {
#[sqlx(try_from="i64")] #[sqlx(try_from="i64")]
id: u64, id: u64,
day: u8,
month: u8,
nick: String, nick: String,
} }
#[derive(Clone, FromRow, Debug)] #[derive(Clone, FromRow, Debug)]
struct EventRow { struct EventRow {
id: u32,
#[sqlx(try_from="i64")] #[sqlx(try_from="i64")]
guild: u64, guild: u64,
name: String, name: String,
day: u8, year: i32,
month: u8,
special_message: String, special_message: String,
} }
async fn notice(ctx: serenity::client::Context) -> anyhow::Result<()> { async fn notice(http: Arc<Http>) -> anyhow::Result<()> {
use anyhow::Context; use anyhow::Context;
let local = Local::now(); let local = Local::now();
let day = local.day(); let day = local.day();
let month = local.month(); let month = local.month();
let year = local.year();
let db_path = dotenv_var("DATABASE_URL").context("DATABASE_URL not found in env")?; 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 mut db = SqliteConnection::connect(db_path.as_str()).await?;
let birtdays = sqlx::query_as::<_, BirthdayRow>( let birtdays = sqlx::query_as::<_, BirthdayRow>(
"SELECT * FROM birthdays "SELECT id, nick FROM birthdays
WHERE day=? AND month=?;" WHERE day=? AND month=?;"
) )
.bind(day) .bind(day)
@ -67,7 +96,7 @@ async fn notice(ctx: serenity::client::Context) -> anyhow::Result<()> {
.await?; .await?;
let global_events = sqlx::query_as::<_, EventRow>( 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;" WHERE day=? AND month=? AND guild=0;"
) )
.bind(day) .bind(day)
@ -75,25 +104,31 @@ async fn notice(ctx: serenity::client::Context) -> anyhow::Result<()> {
.fetch_all(&mut db) .fetch_all(&mut db)
.await?; .await?;
let guilds = ctx.http.get_guilds(None, None).await?; let guilds = http.get_guilds(None, None).await?;
for guild in guilds { for guild in guilds {
let guild_id = guild.id; let guild_id = guild.id;
for bd in &birtdays { for bd in &birtdays {
let user_id = UserId::new(bd.id); 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 { 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>( 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;" WHERE day=? AND month=? AND guild!=0;"
) )
.bind(day) .bind(day)
@ -101,8 +136,10 @@ async fn notice(ctx: serenity::client::Context) -> anyhow::Result<()> {
.fetch_all(&mut db) .fetch_all(&mut db)
.await?; .await?;
// TODO if has year delete it from announce and delete
for e in &global_events { 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(()) Ok(())

View file

@ -1,18 +1,22 @@
use serenity::{all::ChannelId, builder::CreateMessage, http::Http}; use std::sync::Arc;
use std::{process::exit, sync::Arc};
pub async fn send_error(http: Arc<Http>, msg: String) { use serenity::http::Http;
pub async fn send_error(_http: Arc<Http>, msg: String) {
println!("ERROR: {msg}"); println!("ERROR: {msg}");
#[cfg(feature="RELEASE")] #[cfg(feature="RELEASE")]
match ChannelId::new(1199495008416440491) match ChannelId::new(1199495008416440491)
.send_message(http, CreateMessage::new().content(msg)).await { .send_message(_http, CreateMessage::new().content(msg)).await {
Ok(_) => { return; } Ok(_) => { return; }
Err(_) => { exit(-1) } Err(_) => { exit(-1) }
}; };
} }
#[cfg(feature="RELEASE")]
pub async fn hello(http: Arc<Http>) { pub async fn hello(http: Arc<Http>) {
use serenity::http::Http;
let messages = [ let messages = [
"AAAAAAAAAAAAAAAAAAAA", "AAAAAAAAAAAAAAAAAAAA",
"Henlooo", "Henlooo",

View file

@ -1,2 +1,3 @@
pub mod security; pub mod security;
pub mod debug; pub mod debug;
pub mod utilities;

View file

@ -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<Http>) -> anyhow::Result<ChannelId> {
return http.get_guild(guild_id).await?.system_channel_id
.context(format!("System channel of guild: {} not found", guild_id.get()));
}