From 121a65d12f06b3d6fb7fbf2a5f650785606a9676 Mon Sep 17 00:00:00 2001 From: "Yuki.N" Date: Wed, 27 Nov 2024 16:33:13 +0800 Subject: [PATCH 1/9] Added `quotes` table and impl random selector --- Cargo.lock | 67 +++++++++++++++++++++++++++++++ Cargo.toml | 6 +++ db_helper.py | 100 +++++++++++++++++++++++++++++++++++++++++++++++ src/db/action.rs | 8 ++++ src/db/model.rs | 12 +++++- src/db/schema.rs | 12 ++++++ src/utils.rs | 36 +++++++++++++++++ 7 files changed, 240 insertions(+), 1 deletion(-) create mode 100644 db_helper.py diff --git a/Cargo.lock b/Cargo.lock index ecf2071..ea8466f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,7 @@ dependencies = [ "dotenvy", "log", "pretty_env_logger", + "rand", "teloxide", "tokio", ] @@ -113,6 +114,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.8.0" @@ -1113,6 +1120,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "pretty_env_logger" version = "0.5.0" @@ -1165,6 +1181,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "rc-box" version = "1.2.0" @@ -2147,6 +2193,27 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "zerofrom" version = "0.1.4" diff --git a/Cargo.toml b/Cargo.toml index 4a54903..a4fb595 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,9 @@ tokio = { version = "1.8", features = ["rt-multi-thread", "macros"] } diesel = { version = "2.2.4", features = ["sqlite"] } chrono = "0.4.38" dotenvy = "0.15.7" +rand = "0.8.5" + +[profile.release] +debug = false +lto = true +codegen-units = 1 diff --git a/db_helper.py b/db_helper.py new file mode 100644 index 0000000..932b7ea --- /dev/null +++ b/db_helper.py @@ -0,0 +1,100 @@ +import sqlite3 + +conn = sqlite3.connect("database.db") +cursor = conn.cursor() + +cursor.execute(""" +CREATE TABLE IF NOT EXISTS quotes ( + ID INTEGER PRIMARY KEY AUTOINCREMENT, + msg TEXT NOT NULL +) +""") + +messages = [ + "自然が呼んでいる、外に出て歩こう!🌿", + "椅子は動けないけど、あなたは動ける!立ってストレッチ!🚀", + "宠猫喊你起身活动,来吧喵 🐱", + "Your pet cat says it’s time to move, meow! 🐱", + "ネコが「動く時間だよ」、ニャー!🐱", + "锻炼身体,心情也会变好哦! 🌞", + "Exercise your body, uplift your mood! 🌈", + "体を動かして、気分もスッキリ!🌞", + "别只是动动手指,起来扭扭腰吧! 💃", + "Don't only move your fingers, get up and wiggle your waist! 💃", + "指だけ動かさないで、腰を振ろう!💃", + "起身运动一下,犒劳自己一片小饼干! 🍪", + "Move around and reward yourself with a cookie! 🍪", + "動いたら、クッキーを自分にご褒美!🍪", + "再多努力五分钟,然后起来活动一下吧! ✊", + "Work hard for another five minutes, then get up and move! ✊", + "あと 5 分頑張って、そして動こう!✊", + "用挤牙膏的劲去运动,身体会感谢你! 🏋️", + "Squeeze in some exercise like you squeeze toothpaste, your body will thank you! 🏋️", + "歯磨き粉を絞るように運動して、体が感謝してくれるよ!🏋️", + "会健身的椅子才是好椅子!让你的动起来! 🪑", + "A good chair gets some exercise too! Let's move yours! 🪑", + "健康な椅子は運動する椅子だ!動かそう!🪑", + "保持健康,今天走一万步啦! 💪", + "Keep fit, hit 10,000 steps today! 💪", + "健康を保つために、今日は 1 万歩歩こう!👟", + "左脚,右脚,一步两步,动起来! 🚶", + "Left foot, right foot, one two step, let's move! 🚶‍♀️", + "左右の足、一歩二歩、動こう!🚶", + "玩个小游戏,站起来跳十次! 🎮", + "Play a little game, jump up ten times! 🎮", + "ゲームしよう、10 回ジャンプ!🎮", + "起来动一动,心情棒棒哒! 🌟", + "Get up and move, feel super awesome! 🌟", + "立ち上がって動くと、気分が最高!✨", + "给自己来个 “动感时刻” 吧! 🕺", + "自分に「動の時間」を贈ろう!🕺", + "收拾收拾,活动一下筋骨! 🚶", + "Tidy up and stretch your muscles! 🚶‍♂️", + "身の回りを整理して、筋肉を伸ばそう!🧹", + "休息片刻,能量满满! ✨", + "Take a break, feel energized! ✨", + "休憩して、エネルギーを補充しよう!🔋", + "运动一下,神清气爽! 🌿", + "A quick workout, clear mind! 🌿", + "少し運動して、頭をリフレッシュ!🍃", + "每天一小步,健康一大步! 🤸", + "A little step each day, a big leap to health! 🤸", + "毎日少し動いて、大きな健康を得よう!🏃‍♂️", + "起身喝口水,再散步五分钟! 🚶", + "Stand up, have a sip of water, and take a five-minute walk! 🚶‍♀️", + "立ち上がって水を飲んで、5 分歩こう!🚶‍♂️", + "时间到!该起来动动啦! ⏰", + "Time's up! Get up and move around! ⏰", + "時間だよ!立ち上がって動こう!⏳", + "只需几步,就能解锁健康! 🚶", + "Just a few steps to unlock health! 🚶‍♀️", + "健康へのカギは、数歩歩くだけ!🔑", + "活动一下,用笑容迎接每一天! 😊", + "Move around, greet each day with a smile! 😊", + "動いて、毎日を笑顔で迎えよう!😊", + "别让自己 “长在” 椅子上,起来动动吧! 🌟", + "椅子に「根を張らない」ように、立ち上がって動こう!🌿", + "让你的小腿跑一跑,充电完毕!⚡", + "Let your legs run a bit, recharge complete! ⚡", + "少し足を動かして、充電完了!⚡", + "记得活动哦!短暂休息,长久健康! 🚶", + "Don't forget to move! Short breaks, long health! 🚶‍♂️", + "動くのを忘れないで!短い休憩、長い健康!🚶‍♀️", + "起身摇摆,工作更自在! 💃", + "Stand up and sway, work feels like play! 💃", + "立ち上がって揺れよう、仕事が楽しくなるよ!💃 ", + "久坐不好,起来活动活动吧! 🚶", + "记得起身活动活动,松松背拉拉筋。😊", +] + + +for idx, msg in enumerate(messages, start=1): + cursor.execute( + """ + INSERT INTO quotes (ID, msg) VALUES (?, ?) + """, + (idx, msg), + ) + +conn.commit() +conn.close() diff --git a/src/db/action.rs b/src/db/action.rs index 483990f..ede7b01 100644 --- a/src/db/action.rs +++ b/src/db/action.rs @@ -1,3 +1,4 @@ +use crate::db::schema::quotes::dsl::*; use crate::db::schema::users::dsl::*; use diesel::prelude::*; use teloxide::types::{ChatId, UserId}; @@ -62,3 +63,10 @@ pub fn clear_reminder_time(conn: &mut SqliteConnection, _chat_id: ChatId, _user_ pub fn get_user_reminders(conn: &mut SqliteConnection) -> Vec { users.load::(conn).expect("Error loading user") } + +pub fn get_quotes(conn: &mut SqliteConnection) -> Vec { + quotes + .select(msg) + .load::(conn) + .expect("Error loading Quotes") +} diff --git a/src/db/model.rs b/src/db/model.rs index 4efc291..f1e11c2 100644 --- a/src/db/model.rs +++ b/src/db/model.rs @@ -1,4 +1,4 @@ -use crate::db::schema::users; +use crate::db::schema::{quotes, users}; use diesel::prelude::*; #[derive(Queryable, Selectable)] @@ -11,3 +11,13 @@ pub struct Users { pub reminder_time: String, pub tz_offset: Option, } + +#[derive(Queryable, Selectable)] +#[diesel(table_name = quotes)] +#[diesel(check_for_backend(diesel::sqlite::Sqlite))] +pub struct Quotes { + #[allow(dead_code)] + pub id: i32, + #[allow(dead_code)] + pub msg: String, +} diff --git a/src/db/schema.rs b/src/db/schema.rs index f703e6f..28e09f7 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -1,5 +1,12 @@ // @generated automatically by Diesel CLI. +diesel::table! { + quotes (id) { + id -> Integer, + msg -> Text, + } +} + diesel::table! { users (chat_id) { chat_id -> BigInt, @@ -9,3 +16,8 @@ diesel::table! { tz_offset -> Nullable, } } + +diesel::allow_tables_to_appear_in_same_query!( + quotes, + users, +); diff --git a/src/utils.rs b/src/utils.rs index 70c203e..dc2182d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,5 @@ +use rand::Rng; + #[derive(Debug, PartialEq)] pub struct TimezoneOffest { offset_hours: i32, @@ -47,6 +49,19 @@ impl TimezoneOffest { } } +pub fn get_random_quote(quotes: &[String]) -> Option<&str> { + if !quotes.is_empty() { + let length = quotes.len(); + let mut rng = rand::thread_rng(); + let random_index = rng.gen_range(0..length); + let random_quote = "es[random_index]; + + Some(random_quote) + } else { + None + } +} + #[cfg(test)] mod tests { @@ -87,4 +102,25 @@ mod tests { let ew_offset = Utc::now().with_timezone(&FixedOffset::east_opt(duration).unwrap()); println!("UTC+8:00 -> {}", ew_offset); } + + #[test] + fn test_get_random_quote() { + let quotes = vec![ + String::from("wwww"), + String::from("vvvv"), + String::from("xxxx"), + ]; + + let random_quote = get_random_quote("es); + assert!(random_quote.is_some()); + + if let Some(quote) = random_quote { + println!("{quote}"); + } + + let empty_quotes: Vec = vec![]; + + let random_quote = get_random_quote(&empty_quotes); + assert!(random_quote.is_none()); + } } From 2abffa695a7db13562b375443032fe7ed858440b Mon Sep 17 00:00:00 2001 From: "Yuki.N" Date: Wed, 27 Nov 2024 16:35:17 +0800 Subject: [PATCH 2/9] Fixed crash when setting a reminder in private chat --- src/db/action.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/db/action.rs b/src/db/action.rs index ede7b01..97ce09c 100644 --- a/src/db/action.rs +++ b/src/db/action.rs @@ -61,7 +61,11 @@ pub fn clear_reminder_time(conn: &mut SqliteConnection, _chat_id: ChatId, _user_ } pub fn get_user_reminders(conn: &mut SqliteConnection) -> Vec { - users.load::(conn).expect("Error loading user") + // Only work for group members + users + .filter(chat_id.ne(user_id)) + .load::(conn) + .expect("Error loading user") } pub fn get_quotes(conn: &mut SqliteConnection) -> Vec { From 1bec737eee6825d73b8f30711658d9e5b975200d Mon Sep 17 00:00:00 2001 From: "Yuki.N" Date: Wed, 27 Nov 2024 16:36:19 +0800 Subject: [PATCH 3/9] Updated reminder text to a random quote --- src/scheduler/reminder.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/scheduler/reminder.rs b/src/scheduler/reminder.rs index 2d65c0b..5a6eb1a 100644 --- a/src/scheduler/reminder.rs +++ b/src/scheduler/reminder.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use crate::db::action as operator; -use crate::utils::TimezoneOffest; +use crate::utils::{get_random_quote, TimezoneOffest}; use chrono::{FixedOffset, Utc}; use teloxide::adaptors::DefaultParseMode; use teloxide::prelude::*; @@ -16,6 +16,10 @@ pub async fn schedule_reminders(bot: &Arc) { let conn = &mut establish_connection(); let users_to_remind = operator::get_user_reminders(conn); + let all_quotes = operator::get_quotes(conn); + let random_quote = get_random_quote(&all_quotes); + let selected_quote = random_quote.unwrap_or("记得起身活动活动,松松背拉拉筋。"); + for user in users_to_remind { if let Some(tzoffset) = user.tz_offset.as_ref() { let user_timezone: TimezoneOffest = tzoffset.parse().unwrap(); @@ -26,14 +30,11 @@ pub async fn schedule_reminders(bot: &Arc) { .format("%H:%M") .to_string(); - // println!("User UTC time: {}", user_utc_time); - if user_utc_time == user.reminder_time { - // println!("{}: {}", user.reminder_time, user.username); let _user_id = UserId(u64::try_from(user.user_id).unwrap()); let _username = user.username; let mentioned_user = html::user_mention(_user_id, &_username); - let notification = format!("{mentioned_user},记得起身活动活动,松松背拉拉筋。"); + let notification = format!("{mentioned_user} {selected_quote}"); bot.send_message(ChatId(user.chat_id), notification) .await .unwrap(); From 9a18c33afbd56548044721720703a26817ed929f Mon Sep 17 00:00:00 2001 From: "Yuki.N" Date: Wed, 27 Nov 2024 16:37:11 +0800 Subject: [PATCH 4/9] Updated help message --- src/bot/handler.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bot/handler.rs b/src/bot/handler.rs index 4a9775a..946aca1 100644 --- a/src/bot/handler.rs +++ b/src/bot/handler.rs @@ -20,7 +20,7 @@ pub async fn reply(bot: Bot, msg: Message, command: Command) -> ResponseResult<( Command::Help => { bot.send_message( msg.chat.id, - format!("Hi, {mentioned_user}, Welcome to the Exercise Reminder Bot! 😉\n\nPlease use /settime HH:MM to set the reminder time.\n\nAnd use /settimezone +01:00 (according to your location) to accurate the reminder.\n\nIf you are not sure your timezone, please check this page.", + format!("Hi, {mentioned_user}, Welcome to the Exercise Reminder Bot! 😉\n\nPlease use /settime HH:MM to set the reminder time. And use /settimezone +01:00 (according to your location) to accurate the reminder.\n\nIf you are not sure your timezone, please check this page.\n\nPlease settime first before settimezone.", ), ) .await?; From 7278bf249fef5a110bf7b54d0130c2cce764fbbc Mon Sep 17 00:00:00 2001 From: "Yuki.N" Date: Wed, 27 Nov 2024 17:07:04 +0800 Subject: [PATCH 5/9] README --- README.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..8f8d1ab --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ApollousaBot + +A Telegram bot to remind members in Posthuman-Community to take breaks and exercise. \ No newline at end of file From 7f046a5aa538fadea1ed161418a905856d62121e Mon Sep 17 00:00:00 2001 From: "Yuki.N" Date: Thu, 28 Nov 2024 17:26:53 +0800 Subject: [PATCH 6/9] Fixed: handle invalid timezone input to prevent crash --- src/bot/handler.rs | 31 +++++++++++++++++++------- src/db/action.rs | 47 +++++++++++++++++++++++++++++++-------- src/db/model.rs | 4 ++-- src/db/schema.rs | 2 +- src/scheduler/reminder.rs | 41 +++++++++++++++++++++------------- 5 files changed, 90 insertions(+), 35 deletions(-) diff --git a/src/bot/handler.rs b/src/bot/handler.rs index 946aca1..c11cc49 100644 --- a/src/bot/handler.rs +++ b/src/bot/handler.rs @@ -2,6 +2,7 @@ use crate::{ bot::commands::Command, db::action::{clear_reminder_time, set_reminder_time, set_user_timezone}, }; +use regex::Regex; use teloxide::{adaptors::DefaultParseMode, prelude::*, utils::html}; use chrono::naive::NaiveTime; @@ -54,14 +55,28 @@ pub async fn reply(bot: Bot, msg: Message, command: Command) -> ResponseResult<( } } Command::SetTimezone(timezone) => { - set_user_timezone(conn, user_id, msg.chat.id, timezone.as_str()); - bot.send_message( - msg.chat.id, - format!( - "Hi, {mentioned_user}, your timezone is set to UTC{timezone}." - ), - ) - .await?; + let tz_pattern = + Regex::new(r"^(\+|-)\d{2}:\d{2}$").expect("Failed to initialize regex pattern"); + + match tz_pattern.is_match(&timezone) { + true => { + set_user_timezone(conn, user_id, msg.chat.id, &username, timezone.as_str()); + bot.send_message( + msg.chat.id, + format!( + "Hi, {mentioned_user}, your timezone is set to UTC{timezone}." + ), + ) + .await?; + } + false => { + bot.send_message( + msg.chat.id, + "Invalid timezone format. Please use the format ±HH:MM.", + ) + .await?; + } + } } Command::Stop => { clear_reminder_time(conn, msg.chat.id, user_id); diff --git a/src/db/action.rs b/src/db/action.rs index 97ce09c..ba9cdb0 100644 --- a/src/db/action.rs +++ b/src/db/action.rs @@ -12,6 +12,10 @@ pub fn set_reminder_time( _username: &str, time: &str, ) { + println!( + "set_reminder_time -> UserId: {}, ChatId: {}, Username: {} , time: {}", + _user_id.0, _chat_id.0, _username, time + ); diesel::insert_into(users) .values(( chat_id.eq(_chat_id.0), @@ -30,22 +34,46 @@ pub fn set_user_timezone( conn: &mut SqliteConnection, _user_id: UserId, _chat_id: ChatId, + _username: &str, _user_timezone: &str, ) { println!( - "UserId: {}, ChatId: {}, UserTimezone: {}", - _user_id.0, _chat_id.0, _user_timezone + "set_user_timezone -> UserId: {}, ChatId: {}, Username: {} ,UserTimezone: {}", + _user_id.0, _chat_id.0, _username, _user_timezone ); - diesel::update( - users.filter( + let user_exists = users + .filter( chat_id .eq(_chat_id.0) .and(user_id.eq(i64::try_from(_user_id.0).unwrap())), - ), - ) - .set(tz_offset.eq(_user_timezone)) - .execute(conn) - .expect("Error update user timezone"); + ) + .select((chat_id, user_id)) + .first::<(i64, i64)>(conn) + .optional() + .expect("Error checking if user exists"); + + if user_exists.is_some() { + diesel::update( + users.filter( + chat_id + .eq(_chat_id.0) + .and(user_id.eq(i64::try_from(_user_id.0).unwrap())), + ), + ) + .set(tz_offset.eq(_user_timezone)) + .execute(conn) + .expect("Error updating user timezone"); + } else { + diesel::insert_into(users) + .values(( + chat_id.eq(_chat_id.0), + user_id.eq(i64::try_from(_user_id.0).unwrap()), + username.eq(_username), + tz_offset.eq(_user_timezone), + )) + .execute(conn) + .expect("Error inserting new user with timezone"); + } } pub fn clear_reminder_time(conn: &mut SqliteConnection, _chat_id: ChatId, _user_id: UserId) { @@ -63,6 +91,7 @@ pub fn clear_reminder_time(conn: &mut SqliteConnection, _chat_id: ChatId, _user_ pub fn get_user_reminders(conn: &mut SqliteConnection) -> Vec { // Only work for group members users + .select((chat_id, user_id, username, reminder_time, tz_offset)) .filter(chat_id.ne(user_id)) .load::(conn) .expect("Error loading user") diff --git a/src/db/model.rs b/src/db/model.rs index f1e11c2..51e2b0f 100644 --- a/src/db/model.rs +++ b/src/db/model.rs @@ -1,14 +1,14 @@ use crate::db::schema::{quotes, users}; use diesel::prelude::*; -#[derive(Queryable, Selectable)] +#[derive(Debug, Queryable, Selectable)] #[diesel(table_name = users)] #[diesel(check_for_backend(diesel::sqlite::Sqlite))] pub struct Users { pub chat_id: i64, pub user_id: i64, pub username: String, - pub reminder_time: String, + pub reminder_time: Option, pub tz_offset: Option, } diff --git a/src/db/schema.rs b/src/db/schema.rs index 28e09f7..4dbb2cc 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -12,8 +12,8 @@ diesel::table! { chat_id -> BigInt, user_id -> BigInt, username -> Text, - reminder_time -> Text, tz_offset -> Nullable, + reminder_time -> Nullable, } } diff --git a/src/scheduler/reminder.rs b/src/scheduler/reminder.rs index 5a6eb1a..b57a21a 100644 --- a/src/scheduler/reminder.rs +++ b/src/scheduler/reminder.rs @@ -22,23 +22,34 @@ pub async fn schedule_reminders(bot: &Arc) { for user in users_to_remind { if let Some(tzoffset) = user.tz_offset.as_ref() { - let user_timezone: TimezoneOffest = tzoffset.parse().unwrap(); + if let Ok(user_timezone) = tzoffset.parse::() { + let duration_secs = user_timezone.to_duration(); + let user_utc_time = Utc::now() + .with_timezone(&FixedOffset::east_opt(duration_secs).unwrap()) + .format("%H:%M") + .to_string(); - let duration_secs = user_timezone.to_duration(); - let user_utc_time = Utc::now() - .with_timezone(&FixedOffset::east_opt(duration_secs).unwrap()) - .format("%H:%M") - .to_string(); - - if user_utc_time == user.reminder_time { - let _user_id = UserId(u64::try_from(user.user_id).unwrap()); - let _username = user.username; - let mentioned_user = html::user_mention(_user_id, &_username); - let notification = format!("{mentioned_user} {selected_quote}"); - bot.send_message(ChatId(user.chat_id), notification) - .await - .unwrap(); + if let Some(reminder) = &user.reminder_time { + if user_utc_time == *reminder { + let _user_id = UserId(u64::try_from(user.user_id).unwrap()); + let _username = user.username; + let mentioned_user = html::user_mention(_user_id, &_username); + let notification = format!("{mentioned_user} {selected_quote}"); + bot.send_message(ChatId(user.chat_id), notification) + .await + .unwrap(); + } + } else { + eprintln!("Invalid reminder: {:?}", &user.reminder_time) + } + } else { + eprintln!("Failed to parse timezone offset: {}", tzoffset) } + } else { + eprintln!( + "User {} does not have a timezone offset set.", + user.username + ) } } } From 059f9650efcfe6fdce54f09ac4cdf910d453eec6 Mon Sep 17 00:00:00 2001 From: "Yuki.N" Date: Thu, 28 Nov 2024 17:27:16 +0800 Subject: [PATCH 7/9] Added dependency --- Cargo.lock | 1 + Cargo.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index ea8466f..940476d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,6 +12,7 @@ dependencies = [ "log", "pretty_env_logger", "rand", + "regex", "teloxide", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index a4fb595..96e1273 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ diesel = { version = "2.2.4", features = ["sqlite"] } chrono = "0.4.38" dotenvy = "0.15.7" rand = "0.8.5" +regex = "1.11.1" [profile.release] debug = false From 33c4f45b35870829c0a483099d4d37ba1a207ca5 Mon Sep 17 00:00:00 2001 From: "Yuki.N" Date: Thu, 28 Nov 2024 22:21:27 +0800 Subject: [PATCH 8/9] Fixed unique constraint error --- src/db/action.rs | 6 +++--- src/db/model.rs | 2 +- src/db/schema.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/db/action.rs b/src/db/action.rs index ba9cdb0..359ac2a 100644 --- a/src/db/action.rs +++ b/src/db/action.rs @@ -13,7 +13,7 @@ pub fn set_reminder_time( time: &str, ) { println!( - "set_reminder_time -> UserId: {}, ChatId: {}, Username: {} , time: {}", + "set_reminder_time -> UserId: {}, ChatId: {}, Username: {}, Time: {}", _user_id.0, _chat_id.0, _username, time ); diesel::insert_into(users) @@ -23,7 +23,7 @@ pub fn set_reminder_time( username.eq(_username), reminder_time.eq(time), )) - .on_conflict(chat_id) + .on_conflict((chat_id, user_id)) .do_update() .set(reminder_time.eq(time)) .execute(conn) @@ -38,7 +38,7 @@ pub fn set_user_timezone( _user_timezone: &str, ) { println!( - "set_user_timezone -> UserId: {}, ChatId: {}, Username: {} ,UserTimezone: {}", + "set_user_timezone -> UserId: {}, ChatId: {}, Username: {}, UserTimezone: {}", _user_id.0, _chat_id.0, _username, _user_timezone ); let user_exists = users diff --git a/src/db/model.rs b/src/db/model.rs index 51e2b0f..9a87d0e 100644 --- a/src/db/model.rs +++ b/src/db/model.rs @@ -1,7 +1,7 @@ use crate::db::schema::{quotes, users}; use diesel::prelude::*; -#[derive(Debug, Queryable, Selectable)] +#[derive(Debug, Queryable, Selectable, QueryableByName, Insertable)] #[diesel(table_name = users)] #[diesel(check_for_backend(diesel::sqlite::Sqlite))] pub struct Users { diff --git a/src/db/schema.rs b/src/db/schema.rs index 4dbb2cc..4e044ad 100644 --- a/src/db/schema.rs +++ b/src/db/schema.rs @@ -8,7 +8,7 @@ diesel::table! { } diesel::table! { - users (chat_id) { + users (chat_id, user_id) { chat_id -> BigInt, user_id -> BigInt, username -> Text, From bd8a224797fd4556e85ba3468a301a70e79db6cb Mon Sep 17 00:00:00 2001 From: "Yuki.N" Date: Fri, 29 Nov 2024 11:05:43 +0800 Subject: [PATCH 9/9] Refactor reminder scheduling for improved error handling --- src/scheduler/reminder.rs | 71 ++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/src/scheduler/reminder.rs b/src/scheduler/reminder.rs index b57a21a..88e5242 100644 --- a/src/scheduler/reminder.rs +++ b/src/scheduler/reminder.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use crate::db::action as operator; +use crate::db::model::Users; use crate::utils::{get_random_quote, TimezoneOffest}; use chrono::{FixedOffset, Utc}; use teloxide::adaptors::DefaultParseMode; @@ -12,44 +13,52 @@ use crate::db::establish_connection; type Bot = DefaultParseMode; +async fn handle_user_reminder( + user: &Users, + bot: &Arc, + selected_quote: &str, +) -> Result<(), Box> { + let tzoffset = user.tz_offset.as_ref().ok_or(format!( + "User {} does not have a timezone offset set.", + user.username + ))?; + let user_timezone = tzoffset + .parse::() + .map_err(|e| format!("Failed to parse timezone offset: {}", e))?; + let duration_secs = user_timezone.to_duration(); + let user_utc_time = FixedOffset::east_opt(duration_secs).map_or( + "Invalid timezone offset".to_string(), + |offset| { + Utc::now() + .with_timezone(&offset) + .format("%H:%M") + .to_string() + }, + ); + + if let Some(reminder) = &user.reminder_time { + if user_utc_time == *reminder { + let _user_id = UserId(u64::try_from(user.user_id)?); + let mentioned_user = html::user_mention(_user_id, &user.username); + let notification = format!("{mentioned_user} {selected_quote}"); + bot.send_message(ChatId(user.chat_id), notification).await?; + } + } + + Ok(()) +} + pub async fn schedule_reminders(bot: &Arc) { let conn = &mut establish_connection(); let users_to_remind = operator::get_user_reminders(conn); let all_quotes = operator::get_quotes(conn); - let random_quote = get_random_quote(&all_quotes); - let selected_quote = random_quote.unwrap_or("记得起身活动活动,松松背拉拉筋。"); + let selected_quote = + get_random_quote(&all_quotes).unwrap_or("记得起身活动活动,松松背拉拉筋。"); for user in users_to_remind { - if let Some(tzoffset) = user.tz_offset.as_ref() { - if let Ok(user_timezone) = tzoffset.parse::() { - let duration_secs = user_timezone.to_duration(); - let user_utc_time = Utc::now() - .with_timezone(&FixedOffset::east_opt(duration_secs).unwrap()) - .format("%H:%M") - .to_string(); - - if let Some(reminder) = &user.reminder_time { - if user_utc_time == *reminder { - let _user_id = UserId(u64::try_from(user.user_id).unwrap()); - let _username = user.username; - let mentioned_user = html::user_mention(_user_id, &_username); - let notification = format!("{mentioned_user} {selected_quote}"); - bot.send_message(ChatId(user.chat_id), notification) - .await - .unwrap(); - } - } else { - eprintln!("Invalid reminder: {:?}", &user.reminder_time) - } - } else { - eprintln!("Failed to parse timezone offset: {}", tzoffset) - } - } else { - eprintln!( - "User {} does not have a timezone offset set.", - user.username - ) + if let Err(e) = handle_user_reminder(&user, bot, selected_quote).await { + eprintln!("Error handling reminder for user {}: {}", user.username, e); } } }