Added birthday reminders

+ clippy + fmt
This commit is contained in:
Joey Hines 2023-04-21 15:34:14 -06:00
parent 399d34e769
commit b523c85c81
Signed by: joeyahines
GPG Key ID: 995E531F7A569DDB
8 changed files with 271 additions and 8 deletions

58
Cargo.lock generated
View File

@ -283,14 +283,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.23" version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
dependencies = [ dependencies = [
"iana-time-zone", "iana-time-zone",
"js-sys",
"num-integer", "num-integer",
"num-traits 0.2.15", "num-traits 0.2.15",
"serde", "serde",
"time 0.1.45",
"wasm-bindgen",
"winapi", "winapi",
] ]
@ -301,7 +304,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde" checksum = "29c39203181991a7dd4343b8005bd804e7a9a37afb8ac070e43771e8c820bbde"
dependencies = [ dependencies = [
"chrono", "chrono",
"chrono-tz-build", "chrono-tz-build 0.0.3",
"phf",
]
[[package]]
name = "chrono-tz"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9cc2b23599e6d7479755f3594285efb3f74a1bdca7a7374948bc831e23a552"
dependencies = [
"chrono",
"chrono-tz-build 0.1.0",
"phf", "phf",
] ]
@ -316,6 +330,17 @@ dependencies = [
"phf_codegen", "phf_codegen",
] ]
[[package]]
name = "chrono-tz-build"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9998fb9f7e9b2111641485bf8beb32f92945f97f92a3d061f744cfef335f751"
dependencies = [
"parse-zoneinfo",
"phf",
"phf_codegen",
]
[[package]] [[package]]
name = "cipher" name = "cipher"
version = "0.3.0" version = "0.3.0"
@ -659,6 +684,8 @@ version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"base64 0.21.0", "base64 0.21.0",
"chrono",
"chrono-tz 0.8.2",
"config", "config",
"j_db", "j_db",
"json", "json",
@ -817,7 +844,7 @@ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"wasm-bindgen", "wasm-bindgen",
] ]
@ -1297,7 +1324,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys", "windows-sys",
] ]
@ -2155,7 +2182,7 @@ dependencies = [
"serde-value", "serde-value",
"serde_json", "serde_json",
"static_assertions", "static_assertions",
"time", "time 0.3.17",
"tokio", "tokio",
"tracing", "tracing",
"typemap_rev", "typemap_rev",
@ -2451,7 +2478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3df578c295f9ec044ff1c829daf31bb7581d5b3c2a7a3d87419afe1f2531438c" checksum = "3df578c295f9ec044ff1c829daf31bb7581d5b3c2a7a3d87419afe1f2531438c"
dependencies = [ dependencies = [
"chrono", "chrono",
"chrono-tz", "chrono-tz 0.6.3",
"globwalk", "globwalk",
"humansize", "humansize",
"lazy_static", "lazy_static",
@ -2513,6 +2540,17 @@ dependencies = [
"once_cell", "once_cell",
] ]
[[package]]
name = "time"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.17" version = "0.3.17"
@ -3023,6 +3061,12 @@ dependencies = [
"try-lock", "try-lock",
] ]
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"

View File

@ -22,6 +22,8 @@ axum = "0.6.3"
sha3 = "0.10.6" sha3 = "0.10.6"
base64 = "0.21.0" base64 = "0.21.0"
j_db = { version = "0.1.0", registry = "jojo-dev" } j_db = { version = "0.1.0", registry = "jojo-dev" }
chrono = { version = "0.4.24", features = ["serde"] }
chrono-tz = "0.8.2"
[dependencies.serenity] [dependencies.serenity]
version = "0.11.5" version = "0.11.5"

View File

@ -6,6 +6,7 @@ use config::{Config, File};
use j_db::database::Database; use j_db::database::Database;
use rand::prelude::SliceRandom; use rand::prelude::SliceRandom;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serenity::model::id::ChannelId;
use serenity::model::prelude::{GuildId, UserId}; use serenity::model::prelude::{GuildId, UserId};
use serenity::prelude::TypeMapKey; use serenity::prelude::TypeMapKey;
use std::collections::HashMap; use std::collections::HashMap;
@ -33,6 +34,7 @@ pub struct BotConfig {
pub admins: Vec<UserId>, pub admins: Vec<UserId>,
pub guild_id: GuildId, pub guild_id: GuildId,
pub api_addr: SocketAddr, pub api_addr: SocketAddr,
pub announcement_channel: ChannelId,
} }
impl BotConfig { impl BotConfig {

96
src/discord/birthday.rs Normal file
View File

@ -0,0 +1,96 @@
use crate::models::birthday::BirthdayEntry;
use crate::{command, group, GlobalData};
use rand::{thread_rng, Rng};
use serenity::client::Context;
use serenity::framework::standard::{Args, CommandResult};
use serenity::model::channel::Message;
use serenity::model::id::UserId;
use serenity::utils::MessageBuilder;
#[group]
#[commands(add_birthday, list_birthdays)]
pub struct Birthday;
#[command]
#[description("Add your birthday to the bot. Please use American dates or i will cut u.")]
#[example("add_birthday 04/21/1997")]
pub async fn add_birthday(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
if args.is_empty() {
msg.reply(
&ctx.http,
"Look just give me a day, doesn't matter. I'm not the IRS",
)
.await?;
return Ok(());
}
let date_split: Vec<&str> = args.rest().split('/').collect();
if date_split.len() < 3 {
msg.reply(&ctx.http, "Try again with a real date").await?;
return Ok(());
}
let month: u32 = date_split[0].parse().unwrap_or(0);
let day: u32 = date_split[1].parse().unwrap_or(0);
let year: i32 = date_split[2].parse().unwrap_or(0);
if month == 0 || day == 0 || year == 0 {
msg.reply(&ctx.http, "Try again with a real date").await?;
return Ok(());
}
if let Some(date) = chrono::NaiveDate::from_ymd_opt(year, month, day) {
let data = ctx.data.read().await;
let global_data = data.get::<GlobalData>().unwrap();
BirthdayEntry::add_birthday(&global_data.db, msg.author.id.0, date)?;
msg.reply(
&ctx.http,
format!(
"Thank you subject #{}, I am now {}% closer to making a full AI replica of you.",
msg.author.id.0,
thread_rng().gen_range(0.0..100.0)
),
)
.await?;
} else {
msg.reply(&ctx.http, "Try again with a real date").await?;
return Ok(());
}
Ok(())
}
#[command]
#[description("Add your birthday to the bot. Please use American dates or i will cut u.")]
pub async fn list_birthdays(ctx: &Context, msg: &Message) -> CommandResult {
let data = ctx.data.read().await;
let global_data = data.get::<GlobalData>().unwrap();
let birthdays: Vec<BirthdayEntry> = global_data
.db
.filter(|_, _: &BirthdayEntry| true)?
.collect();
let mut msg_builder = MessageBuilder::new();
msg_builder.push_bold_line("All the birthdays I know:");
for birthday in birthdays {
let user = msg
.guild(&ctx.cache)
.unwrap()
.member(&ctx.http, UserId::from(birthday.discord_id))
.await?;
msg_builder.push_line(format!(
"* {} {}",
user.display_name(),
birthday.birthday.format("%m-%d-%Y")
));
}
msg.reply(&ctx.http, msg_builder.build()).await?;
Ok(())
}

View File

@ -1,5 +1,6 @@
pub mod admin; pub mod admin;
pub mod album; pub mod album;
pub mod birthday;
pub mod celeryman; pub mod celeryman;
pub mod color; pub mod color;
pub mod emoji_race; pub mod emoji_race;
@ -14,7 +15,10 @@ use crate::api::web_server;
use crate::discord::fren_coin::give_coin; use crate::discord::fren_coin::give_coin;
use crate::discord::joke::random; use crate::discord::joke::random;
use crate::discord::shop::restock_shop; use crate::discord::shop::restock_shop;
use crate::models::birthday::BirthdayEntry;
use crate::models::insult_compliment::{RandomResponseTemplate, ResponseType};
use crate::{help, hook, GlobalData}; use crate::{help, hook, GlobalData};
use chrono::{Days, TimeZone, Timelike, Utc};
use rand::prelude::IteratorRandom; use rand::prelude::IteratorRandom;
use rand::thread_rng; use rand::thread_rng;
use serenity::async_trait; use serenity::async_trait;
@ -26,7 +30,7 @@ use serenity::model::channel::{Message, ReactionType};
use serenity::model::gateway::Activity; use serenity::model::gateway::Activity;
use serenity::model::id::UserId; use serenity::model::id::UserId;
use serenity::model::prelude::{GuildId, OnlineStatus, Ready}; use serenity::model::prelude::{GuildId, OnlineStatus, Ready};
use serenity::prelude::EventHandler; use serenity::prelude::{EventHandler, Mentionable};
use std::collections::HashSet; use std::collections::HashSet;
use std::time::Duration; use std::time::Duration;
@ -39,12 +43,63 @@ static ERROR_MSG: &str =
impl EventHandler for Handler { impl EventHandler for Handler {
async fn cache_ready(&self, ctx: Context, _guilds: Vec<GuildId>) { async fn cache_ready(&self, ctx: Context, _guilds: Vec<GuildId>) {
tokio::spawn(async move { tokio::spawn(async move {
let mut next_check = chrono_tz::America::Chicago
.from_utc_datetime(&Utc::now().naive_utc())
.checked_sub_days(Days::new(1))
.unwrap();
loop { loop {
{ {
println!("Restocking shop..."); println!("Restocking shop...");
restock_shop(&ctx).await.unwrap(); restock_shop(&ctx).await.unwrap();
} }
{
let now =
chrono_tz::America::Chicago.from_utc_datetime(&Utc::now().naive_utc());
if now >= next_check {
let data = ctx.data.read().await;
let global_data = data.get::<GlobalData>().unwrap();
let todays_birthdays =
BirthdayEntry::todays_birthdays(&global_data.db, now.date_naive())
.unwrap();
for birth in todays_birthdays {
if let Ok(user) = global_data
.cfg
.guild_id
.member(&ctx.http, birth.discord_id)
.await
{
global_data
.cfg
.announcement_channel
.say(&ctx.http, format!("Happy birthday {}!", user.mention()))
.await
.unwrap();
let compliment = RandomResponseTemplate::get_random_response(
&global_data.db,
ResponseType::Compliment,
user.display_name().as_str(),
)
.unwrap()
.unwrap();
global_data
.cfg
.announcement_channel
.say(&ctx.http, compliment)
.await
.unwrap();
}
}
next_check = next_check
.with_hour(7)
.unwrap()
.checked_add_days(Days::new(1))
.unwrap();
}
}
tokio::time::sleep(Duration::from_secs(60 * 60)).await; tokio::time::sleep(Duration::from_secs(60 * 60)).await;
{ {
{ {

View File

@ -59,6 +59,7 @@ async fn main() {
.group(&discord::motivate::MOTIVATE_GROUP) .group(&discord::motivate::MOTIVATE_GROUP)
.group(&discord::voices::VOICES_GROUP) .group(&discord::voices::VOICES_GROUP)
.group(&discord::shop::SHOP_GROUP) .group(&discord::shop::SHOP_GROUP)
.group(&discord::birthday::BIRTHDAY_GROUP)
.unrecognised_command(unrecognised_command_hook) .unrecognised_command(unrecognised_command_hook)
.bucket("bad_apple", |b| b.delay(60 * 10)) .bucket("bad_apple", |b| b.delay(60 * 10))
.await .await

62
src/models/birthday.rs Normal file
View File

@ -0,0 +1,62 @@
use crate::error::Error;
use chrono::Datelike;
use j_db::database::Database;
use j_db::model::JdbModel;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct BirthdayEntry {
id: Option<u64>,
pub discord_id: u64,
pub birthday: chrono::NaiveDate,
}
impl BirthdayEntry {
pub fn add_birthday(
db: &Database,
discord_id: u64,
birthday: chrono::NaiveDate,
) -> Result<(), Error> {
let mut entry = db
.filter(|_, entry: &BirthdayEntry| entry.discord_id == discord_id)?
.next()
.unwrap_or(BirthdayEntry {
id: None,
discord_id,
birthday,
});
entry.birthday = birthday;
db.insert(entry)?;
Ok(())
}
pub fn todays_birthdays(
db: &Database,
date: chrono::NaiveDate,
) -> Result<Vec<BirthdayEntry>, Error> {
let birthdays: Vec<BirthdayEntry> = db
.filter(|_, entry: &BirthdayEntry| {
entry.birthday.month() == date.month() && entry.birthday.day() == date.day()
})?
.collect();
Ok(birthdays)
}
}
impl JdbModel for BirthdayEntry {
fn id(&self) -> Option<u64> {
self.id
}
fn set_id(&mut self, id: u64) {
self.id = Some(id)
}
fn tree() -> String {
"Birthday".to_string()
}
}

View File

@ -1,5 +1,6 @@
pub mod album; pub mod album;
pub mod api_key; pub mod api_key;
pub mod birthday;
pub mod insult_compliment; pub mod insult_compliment;
pub mod motivation; pub mod motivation;
pub mod random; pub mod random;