From bde3c1a0e15ba15226bac25f1902c572506d1ab0 Mon Sep 17 00:00:00 2001 From: Akulij Date: Fri, 6 Jun 2025 02:08:39 +0500 Subject: [PATCH 01/38] fix: call "answer callback query" when handling callback --- src/bot_handler.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bot_handler.rs b/src/bot_handler.rs index b373dc6..34fc360 100644 --- a/src/bot_handler.rs +++ b/src/bot_handler.rs @@ -7,7 +7,7 @@ use std::{ use teloxide::{ dispatching::{dialogue::GetChatId, UpdateFilterExt}, dptree::{self, Handler}, - prelude::DependencyMap, + prelude::{DependencyMap, Requester}, types::{CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message, Update}, Bot, }; @@ -155,6 +155,7 @@ async fn handle_botmessage(bot: Bot, mut db: DB, bm: BotMessage, msg: Message) - } async fn handle_callback(bot: Bot, mut db: DB, bm: BotMessage, q: CallbackQuery) -> BotResult<()> { + bot.answer_callback_query(&q.id).await?; info!("Eval BM: {:?}", bm); let tguser = q.from.clone(); let user = db -- 2.47.2 From 6e31fa86e613dd632329058f93c85f3cd7c12dc6 Mon Sep 17 00:00:00 2001 From: Akulij Date: Fri, 6 Jun 2025 03:32:27 +0500 Subject: [PATCH 02/38] create function that stores button parts and creates tg button wrapper --- src/utils.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/utils.rs b/src/utils.rs index 091cba2..a97e94c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -66,6 +66,23 @@ where )) } +pub async fn callback_button( + name: &str, + callback_name: String, + callback_data: C, + db: &mut D, +) -> BotResult +where + C: Serialize + for<'a> Deserialize<'a> + Send + Sync, + D: CallDB + Send + Sync, +{ + let ci = CallbackInfo::new_with_literal(callback_data, callback_name) + .store(db) + .await?; + + Ok(InlineKeyboardButton::callback(name, ci.get_id())) +} + #[cfg(test)] mod tests { use super::*; -- 2.47.2 From b8bd104f3df82410efb594ae6dbc4d17a306d5dc Mon Sep 17 00:00:00 2001 From: Akulij Date: Fri, 6 Jun 2025 03:32:58 +0500 Subject: [PATCH 03/38] use CallbackInfo for buttons --- src/bot_handler.rs | 150 +++++++++++++++++++++++++++++++-------------- 1 file changed, 105 insertions(+), 45 deletions(-) diff --git a/src/bot_handler.rs b/src/bot_handler.rs index 34fc360..14bf548 100644 --- a/src/bot_handler.rs +++ b/src/bot_handler.rs @@ -1,5 +1,7 @@ +use futures::future::join_all; use log::{error, info}; use quickjs_rusty::serde::{from_js, to_js}; +use serde_json::Value; use std::{ str::FromStr, sync::{Arc, Mutex, RwLock}, @@ -15,14 +17,18 @@ use teloxide::{ use crate::{ botscript::{self, message_info::MessageInfoBuilder, BotMessage, RunnerConfig}, commands::BotCommand, - db::{CallDB, DB}, + db::{callback_info::CallbackInfo, CallDB, DB}, message_answerer::MessageAnswerer, - notify_admin, update_user_tg, BotError, BotResult, BotRuntime, + notify_admin, update_user_tg, + utils::callback_button, + BotError, BotResult, BotRuntime, }; pub type BotHandler = Handler<'static, DependencyMap, BotResult<()>, teloxide::dispatching::DpHandlerDescription>; +type CallbackStore = CallbackInfo; + pub fn script_handler(r: Arc>) -> BotHandler { let cr = r.clone(); dptree::entry() @@ -48,14 +54,38 @@ pub fn script_handler(r: Arc>) -> BotHandler { ) .branch( Update::filter_callback_query() - .filter_map(move |q: CallbackQuery| { - q.data.and_then(|data| { - let r = std::sync::Arc::clone(&cr); + .filter_map_async(move |q: CallbackQuery, mut db: DB| { + let r = Arc::clone(&cr); + async move { + let data = match q.data { + Some(data) => data, + None => return None, + }; + + let ci = match CallbackStore::get(&mut db, &data).await { + Ok(ci) => ci, + Err(err) => { + notify_admin(&format!( + "Failed to get callback from CallbackInfo, err: {err}" + )) + .await; + return None; + } + }; + let ci = match ci { + Some(ci) => ci, + None => return None, + }; + + let data = match ci.literal { + Some(data) => data, + None => return None, + }; + let r = r.lock().expect("RwLock lock on commands map failed"); let rc = &r.rc; - rc.get_callback_message(&data) - }) + } }) .endpoint(handle_callback), ) @@ -126,25 +156,40 @@ async fn handle_botmessage(bot: Bot, mut db: DB, bm: BotMessage, msg: Message) - return Ok(()); } - let buttons = bm - .resolve_buttons(&mut db) - .await? - .map(|buttons| InlineKeyboardMarkup { - inline_keyboard: buttons - .iter() - .map(|r| { - r.iter() - .map(|b| match b { - botscript::ButtonLayout::Callback { - name, - literal: _, - callback, - } => InlineKeyboardButton::callback(name, callback), - }) - .collect() - }) - .collect(), - }); + let button_db = db.clone(); + let buttons = bm.resolve_buttons(&mut db).await?.map(async |buttons| { + join_all(buttons.iter().map(async |r| { + join_all(r.iter().map(async |b| { + match b { + botscript::ButtonLayout::Callback { + name, + literal: _, + callback, + } => { + callback_button( + name, + callback.to_string(), + None::, + &mut button_db.clone(), + ) + .await + } + } + })) + .await + .into_iter() + .collect::>() + })) + .await + .into_iter() + .collect::>() + }); + let buttons = match buttons { + Some(b) => Some(InlineKeyboardMarkup { + inline_keyboard: b.await?, + }), + None => None, + }; let literal = bm.literal().map_or("", |s| s.as_str()); let ma = MessageAnswerer::new(&bot, &mut db, msg.chat.id.0); @@ -207,25 +252,40 @@ async fn handle_callback(bot: Bot, mut db: DB, bm: BotMessage, q: CallbackQuery) return Ok(()); } - let buttons = bm - .resolve_buttons(&mut db) - .await? - .map(|buttons| InlineKeyboardMarkup { - inline_keyboard: buttons - .iter() - .map(|r| { - r.iter() - .map(|b| match b { - botscript::ButtonLayout::Callback { - name, - literal: _, - callback, - } => InlineKeyboardButton::callback(name, callback), - }) - .collect() - }) - .collect(), - }); + let button_db = db.clone(); + let buttons = bm.resolve_buttons(&mut db).await?.map(async |buttons| { + join_all(buttons.iter().map(async |r| { + join_all(r.iter().map(async |b| { + match b { + botscript::ButtonLayout::Callback { + name, + literal: _, + callback, + } => { + callback_button( + name, + callback.to_string(), + None::, + &mut button_db.clone(), + ) + .await + } + } + })) + .await + .into_iter() + .collect::>() + })) + .await + .into_iter() + .collect::>() + }); + let buttons = match buttons { + Some(b) => Some(InlineKeyboardMarkup { + inline_keyboard: b.await?, + }), + None => None, + }; let literal = bm.literal().map_or("", |s| s.as_str()); let (chat_id, msg_id) = { -- 2.47.2 From 99403b7282d6e6632baff1072e643976b0b04843 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 01:28:21 +0500 Subject: [PATCH 04/38] fix: implement js's db callback without box leak --- src/botscript/db.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/botscript/db.rs b/src/botscript/db.rs index 912b48a..1f3329e 100644 --- a/src/botscript/db.rs +++ b/src/botscript/db.rs @@ -16,16 +16,14 @@ pub fn attach_db_obj(c: &Context, o: &mut OwnedJsObject, db: &DB) -> Result<(), .expect("the created object was not an object :/"); let db: std::sync::Arc> = std::sync::Arc::new(RwLock::new(db.clone())); - let dbbox = Box::new(db); - let db: &'static _ = Box::leak(dbbox); let find_one = c.create_callback( - |collection: String, q: OwnedJsObject| -> Result<_, ScriptError> { + move |collection: String, q: OwnedJsObject| -> Result<_, ScriptError> { + // let db = db.clone(); let query: serde_json::Value = match from_js(q.context(), &q) { Ok(q) => q, Err(_) => todo!(), }; - let db = db.clone(); let value = futures::executor::block_on( db.write() -- 2.47.2 From 3dbfbe48ce926a52d7d74989032705b882fe19cb Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 01:37:34 +0500 Subject: [PATCH 05/38] fix: reuse init logic in Runner::init_with_db --- src/botscript.rs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/botscript.rs b/src/botscript.rs index ff2e59c..d4c384f 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -1015,19 +1015,10 @@ impl Runner { } pub fn init_with_db(db: &mut DB) -> ScriptResult { - let context = Context::new(None)?; - let mut global = context.global()?; - attach_db_obj(&context, &mut global, db)?; + let mut runner = Self::init()?; + runner.call_attacher(|c, o| attach_db_obj(c, o, db))??; - context.add_callback("print", |a: String| { - print(a); - - None:: - })?; - - Ok(Runner { - context: Arc::new(Mutex::new(context)), - }) + Ok(runner) } pub fn call_attacher(&mut self, f: F) -> ScriptResult -- 2.47.2 From 7752160807cbd2cc4f63333b54bc7484d30e4b90 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 01:39:01 +0500 Subject: [PATCH 06/38] delete unused functions in main.rs --- src/main.rs | 228 ---------------------------------------------------- 1 file changed, 228 deletions(-) diff --git a/src/main.rs b/src/main.rs index ffb3c8a..0c9a544 100644 --- a/src/main.rs +++ b/src/main.rs @@ -203,164 +203,6 @@ async fn main() -> Result<(), Box> { bm.dispatch(&mut db).await; } -async fn botscript_command_handler( - bot: Bot, - mut db: DB, - bm: BotMessage, - msg: Message, -) -> BotResult<()> { - info!("Eval BM: {:?}", bm); - let buttons = bm - .resolve_buttons(&mut db) - .await? - .map(|buttons| InlineKeyboardMarkup { - inline_keyboard: buttons - .iter() - .map(|r| { - r.iter() - .map(|b| match b { - botscript::ButtonLayout::Callback { - name, - literal: _, - callback, - } => InlineKeyboardButton::callback(name, callback), - }) - .collect() - }) - .collect(), - }); - let literal = bm.literal().map_or("", |s| s.as_str()); - - let ma = MessageAnswerer::new(&bot, &mut db, msg.chat.id.0); - ma.answer(literal, None, buttons).await?; - - Ok(()) -} - -async fn callback_handler(bot: Bot, mut db: DB, q: CallbackQuery) -> BotResult<()> { - bot.answer_callback_query(&q.id).await?; - - let data = match q.data { - Some(ref data) => data, - None => { - // not really our case to handle - return Ok(()); - } - }; - - let callback = match CallbackStore::get_callback(&mut db, data).await? { - Some(callback) => callback, - None => { - warn!("Not found callback for data: {data}"); - // doing this silently beacuse end user shouldn't know about backend internal data - return Ok(()); - } - }; - - match callback { - Callback::MoreInfo => { - let keyboard = Some(single_button_markup!( - create_callback_button("go_home", Callback::GoHome, &mut db).await? - )); - - let chat_id = q.chat_id().map(|i| i.0).unwrap_or(q.from.id.0 as i64); - let message_id = q.message.map_or_else( - || { - Err(BotError::MsgTooOld( - "Failed to get message id, probably message too old".to_string(), - )) - }, - |m| Ok(m.id().0), - )?; - MessageAnswerer::new(&bot, &mut db, chat_id) - .replace_message(message_id, "more_info_msg", keyboard) - .await? - } - Callback::ProjectPage { id } => { - let nextproject = match db - .get_literal_value(&format!("project_{}_msg", id + 1)) - .await? - .unwrap_or("emptyproject".into()) - .as_str() - { - "end" | "empty" | "none" => None, - _ => Some( - create_callback_button( - "next_project", - Callback::ProjectPage { id: id + 1 }, - &mut db, - ) - .await?, - ), - }; - let prevproject = match id.wrapping_sub(1) { - 0 => None, - _ => Some( - create_callback_button( - "prev_project", - Callback::ProjectPage { - id: id.wrapping_sub(1), - }, - &mut db, - ) - .await?, - ), - }; - let keyboard = buttons_markup!( - [prevproject, nextproject].into_iter().flatten(), - [create_callback_button("go_home", Callback::GoHome, &mut db).await?] - ); - - let chat_id = q.chat_id().map(|i| i.0).unwrap_or(q.from.id.0 as i64); - let message_id = q.message.map_or_else( - || { - Err(BotError::MsgTooOld( - "Failed to get message id, probably message too old".to_string(), - )) - }, - |m| Ok(m.id().0), - )?; - MessageAnswerer::new(&bot, &mut db, chat_id) - .replace_message(message_id, &format!("project_{}_msg", id), Some(keyboard)) - .await? - } - Callback::GoHome => { - let keyboard = make_start_buttons(&mut db).await?; - - let chat_id = q.chat_id().map(|i| i.0).unwrap_or(q.from.id.0 as i64); - let message_id = q.message.map_or_else( - || { - Err(BotError::MsgTooOld( - "Failed to get message id, probably message too old".to_string(), - )) - }, - |m| Ok(m.id().0), - )?; - MessageAnswerer::new(&bot, &mut db, chat_id) - .replace_message(message_id, "start", Some(keyboard)) - .await? - } - Callback::LeaveApplication => { - let application = Application::new(q.from.clone()).store(&mut db).await?; - let msg = send_application_to_chat(&bot, &mut db, &application).await?; - - let (chat_id, msg_id) = MessageAnswerer::new(&bot, &mut db, q.from.id.0 as i64) - .answer("left_application_msg", None, None) - .await?; - MessageForward::new(msg.chat.id.0, msg.id.0, chat_id, msg_id, false) - .store(&mut db) - .await?; - } - Callback::AskQuestion => { - MessageAnswerer::new(&bot, &mut db, q.from.id.0 as i64) - .answer("ask_question_msg", None, None) - .await?; - } - }; - - Ok(()) -} - async fn send_application_to_chat( bot: &Bot, db: &mut DB, @@ -431,76 +273,6 @@ async fn notify_admin(text: &str) { } } -async fn user_command_handler( - mut db: DB, - bot: Bot, - msg: Message, - cmd: UserCommands, -) -> BotResult<()> { - let tguser = match msg.from.clone() { - Some(user) => user, - None => return Ok(()), // do nothing, cause its not usecase of function - }; - let user = db - .get_or_init_user(tguser.id.0 as i64, &tguser.first_name) - .await?; - let user = update_user_tg(user, &tguser); - user.update_user(&mut db).await?; - info!( - "MSG: {}", - msg.html_text().unwrap_or("|EMPTY_MESSAGE|".into()) - ); - match cmd { - UserCommands::Start(meta) => { - if !meta.is_empty() { - user.insert_meta(&mut db, &meta).await?; - } - let variant = match meta.as_str() { - "" => None, - variant => Some(variant), - }; - let mut db2 = db.clone(); - MessageAnswerer::new(&bot, &mut db, msg.chat.id.0) - .answer("start", variant, Some(make_start_buttons(&mut db2).await?)) - .await?; - Ok(()) - } - UserCommands::Help => { - bot.send_message(msg.chat.id, UserCommands::descriptions().to_string()) - .await?; - Ok(()) - } - } -} - -async fn make_start_buttons(db: &mut DB) -> BotResult { - let mut buttons: Vec> = Vec::new(); - buttons.push(vec![ - create_callback_button("show_projects", Callback::ProjectPage { id: 1 }, db).await?, - ]); - buttons.push(vec![ - create_callback_button("more_info", Callback::MoreInfo, db).await?, - ]); - buttons.push(vec![ - create_callback_button("leave_application", Callback::LeaveApplication, db).await?, - ]); - buttons.push(vec![ - create_callback_button("ask_question", Callback::AskQuestion, db).await?, - ]); - - Ok(InlineKeyboardMarkup::new(buttons)) -} - -async fn echo(bot: Bot, msg: Message) -> BotResult<()> { - if let Some(photo) = msg.photo() { - info!("File ID: {}", photo[0].file.id); - } - bot.send_message(msg.chat.id, msg.html_text().unwrap_or("UNWRAP".into())) - .parse_mode(teloxide::types::ParseMode::Html) - .await?; - Ok(()) -} - fn update_user_tg(user: db::User, tguser: &teloxide::types::User) -> db::User { db::User { first_name: tguser.first_name.clone(), -- 2.47.2 From b27edd421d89aca7511fa7f2949f95065a7f12fd Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 01:40:12 +0500 Subject: [PATCH 07/38] delete not used anymore UserCommands --- src/main.rs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0c9a544..a8648cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -53,15 +53,6 @@ pub struct Config { pub bot_name: String, } -#[derive(BotCommands, Clone)] -#[command(rename_rule = "lowercase")] -enum UserCommands { - /// The first message of user - Start(String), - /// Shows this message. - Help, -} - trait LogMsg { fn log(self) -> Self; } -- 2.47.2 From 3bb03365ed064aa3665a20db8f47a7230c6a0cdd Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 01:41:25 +0500 Subject: [PATCH 08/38] fix warnings in main.rs --- src/main.rs | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/src/main.rs b/src/main.rs index a8648cb..91fcd5d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,16 +11,13 @@ pub mod utils; use bot_manager::BotManager; use botscript::application::attach_user_application; -use botscript::{BotMessage, Runner, RunnerConfig, ScriptError, ScriptResult}; +use botscript::{Runner, RunnerConfig, ScriptError, ScriptResult}; use db::application::Application; use db::bots::BotInstance; use db::callback_info::CallbackInfo; -use db::message_forward::MessageForward; use handlers::admin::admin_handler; -use log::{error, info, warn}; -use message_answerer::MessageAnswerer; -use std::sync::{Arc, Mutex, RwLock}; -use utils::create_callback_button; +use log::{error, info}; +use std::sync::{Arc, Mutex}; use crate::db::{CallDB, DB}; use crate::mongodb_storage::MongodbStorage; @@ -29,13 +26,8 @@ use db::DbError; use envconfig::Envconfig; use serde::{Deserialize, Serialize}; use teloxide::dispatching::dialogue::serializer::Json; -use teloxide::dispatching::dialogue::{GetChatId, Serializer}; -use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup}; -use teloxide::{ - payloads::SendMessageSetters, - prelude::*, - utils::{command::BotCommands, render::RenderMessageTextHelper}, -}; +use teloxide::dispatching::dialogue::Serializer; +use teloxide::prelude::*; type BotDialogue = Dialogue>; @@ -172,6 +164,8 @@ async fn main() -> Result<(), Box> { let mut db = DB::init(&config.db_url, config.bot_name.to_owned()).await?; BotInstance::restart_all(&mut db, false).await?; + // if we can't get info for main bot, we should stop anyway + #[allow(clippy::unwrap_used)] let bm = BotManager::with( async || { let config = config.clone(); @@ -188,7 +182,7 @@ async fn main() -> Result<(), Box> { BotInstance::restart_all(&mut db, false).await.unwrap(); std::iter::once(bi).chain(instances) }, - async |bi| vec![admin_handler()].into_iter(), + async |_| vec![admin_handler()].into_iter(), ); bm.dispatch(&mut db).await; @@ -218,9 +212,9 @@ async fn send_application_to_chat( app.from )) .await; - return Err(BotError::AdminMisconfiguration(format!( - "admin forget to set support_chat_id" - ))); + return Err(BotError::AdminMisconfiguration( + "admin forget to set support_chat_id".to_string(), + )); } }; let msg = match db.get_literal_value("application_format").await? { @@ -236,9 +230,9 @@ async fn send_application_to_chat( ), None => { notify_admin("format for support_chat_id is not set").await; - return Err(BotError::AdminMisconfiguration(format!( - "admin forget to set application_format" - ))); + return Err(BotError::AdminMisconfiguration( + "admin forget to set application_format".to_string(), + )); } }; -- 2.47.2 From 4a090de77b35103d7c3b7622cf09a9e22b90d6c9 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 01:46:35 +0500 Subject: [PATCH 09/38] fix warnings in src/message_answerer.rs --- src/message_answerer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/message_answerer.rs b/src/message_answerer.rs index 924361c..96d1e60 100644 --- a/src/message_answerer.rs +++ b/src/message_answerer.rs @@ -127,7 +127,7 @@ impl<'a> MessageAnswerer<'a> { .and_then(|m| m.variant); let text = self.get_text(literal, variant.as_deref(), true).await?; let media = self.db.get_media(literal).await?; - let (chat_id, msg_id) = match media.len() { + let (_, msg_id) = match media.len() { // just a text 0 => { let msg = -- 2.47.2 From 5a7bb0e0f6d2e6ca4f09a37d2986922f9e60509d Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 01:50:30 +0500 Subject: [PATCH 10/38] clippy fix --- src/admin.rs | 9 +++------ src/bot_handler.rs | 13 ++++++------- src/bot_manager.rs | 16 ++++------------ src/botscript.rs | 12 +++++------- src/botscript/application.rs | 4 ++-- src/botscript/message_info.rs | 6 ++++++ src/db/bots.rs | 2 -- src/db/mod.rs | 2 +- src/db/tests/mod.rs | 2 +- src/handlers/admin.rs | 10 ++++------ src/utils.rs | 2 +- 11 files changed, 33 insertions(+), 45 deletions(-) diff --git a/src/admin.rs b/src/admin.rs index 0f954f2..8665c76 100644 --- a/src/admin.rs +++ b/src/admin.rs @@ -178,12 +178,9 @@ pub async fn admin_command_handler( } }; - let bi = - BotInstance::new(name.clone(), token.to_string(), DEFAULT_SCRIPT.to_string()) - .store(&mut db) - .await?; - - bi + BotInstance::new(name.clone(), token.to_string(), DEFAULT_SCRIPT.to_string()) + .store(&mut db) + .await? }; bot.send_message( diff --git a/src/bot_handler.rs b/src/bot_handler.rs index 14bf548..25d00fd 100644 --- a/src/bot_handler.rs +++ b/src/bot_handler.rs @@ -1,21 +1,21 @@ use futures::future::join_all; use log::{error, info}; -use quickjs_rusty::serde::{from_js, to_js}; +use quickjs_rusty::serde::to_js; use serde_json::Value; use std::{ str::FromStr, - sync::{Arc, Mutex, RwLock}, + sync::{Arc, Mutex}, }; use teloxide::{ dispatching::{dialogue::GetChatId, UpdateFilterExt}, dptree::{self, Handler}, prelude::{DependencyMap, Requester}, - types::{CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message, Update}, + types::{CallbackQuery, InlineKeyboardMarkup, Message, Update}, Bot, }; use crate::{ - botscript::{self, message_info::MessageInfoBuilder, BotMessage, RunnerConfig}, + botscript::{self, message_info::MessageInfoBuilder, BotMessage}, commands::BotCommand, db::{callback_info::CallbackInfo, CallDB, DB}, message_answerer::MessageAnswerer, @@ -108,7 +108,7 @@ async fn handle_botmessage(bot: Bot, mut db: DB, bm: BotMessage, msg: Message) - Err(_) => None, }; - if bm.meta() == true { + if bm.meta() { if let Some(ref meta) = variant { user.insert_meta(&mut db, meta).await?; }; @@ -193,8 +193,7 @@ async fn handle_botmessage(bot: Bot, mut db: DB, bm: BotMessage, msg: Message) - let literal = bm.literal().map_or("", |s| s.as_str()); let ma = MessageAnswerer::new(&bot, &mut db, msg.chat.id.0); - ma.answer(literal, variant.as_ref().map(|v| v.as_str()), buttons) - .await?; + ma.answer(literal, variant.as_deref(), buttons).await?; Ok(()) } diff --git a/src/bot_manager.rs b/src/bot_manager.rs index af45b65..665c463 100644 --- a/src/bot_manager.rs +++ b/src/bot_manager.rs @@ -1,28 +1,20 @@ use std::{ collections::HashMap, future::Future, - sync::{Arc, Mutex, RwLock}, + sync::{Arc, Mutex}, thread::JoinHandle, time::Duration, }; -use lazy_static::lazy_static; use log::{error, info}; -use teloxide::{ - dispatching::dialogue::serializer::Json, - dptree, - prelude::{Dispatcher, Requester}, - types::{ChatId, UserId}, - Bot, -}; -use tokio::runtime::Handle; +use teloxide::{dispatching::dialogue::serializer::Json, dptree, prelude::Dispatcher, Bot}; use crate::{ bot_handler::{script_handler, BotHandler}, db::{bots::BotInstance, DbError, DB}, message_answerer::MessageAnswerer, mongodb_storage::MongodbStorage, - BotController, BotError, BotResult, BotRuntime, + BotController, BotResult, BotRuntime, }; pub struct BotRunner { @@ -235,7 +227,7 @@ pub async fn spawn_notificator_thread( Some(n) => { // waiting time to send notification tokio::time::sleep(n.wait_for()).await; - 'n: for n in n.notifications().into_iter() { + 'n: for n in n.notifications().iter() { for user in n.get_users(&c.db).await?.into_iter() { let text = match n.resolve_message(&c.db, &user).await? { Some(text) => text, diff --git a/src/botscript.rs b/src/botscript.rs index d4c384f..999fb41 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -2,7 +2,7 @@ pub mod application; pub mod db; pub mod message_info; use std::collections::HashMap; -use std::sync::{Arc, Mutex, PoisonError}; +use std::sync::{Arc, Mutex}; use std::time::Duration; use crate::db::raw_calls::RawCallError; @@ -12,7 +12,6 @@ use crate::utils::parcelable::{ParcelType, Parcelable, ParcelableError, Parcelab use chrono::{DateTime, Days, NaiveTime, ParseError, TimeDelta, Timelike, Utc}; use db::attach_db_obj; use futures::future::join_all; -use futures::lock::MutexGuard; use itertools::Itertools; use quickjs_rusty::serde::{from_js, to_js}; use quickjs_rusty::utils::create_empty_object; @@ -543,7 +542,8 @@ impl BotMessage { pub fn update_defaults(self) -> Self { let bm = self; // if message is `start`, defaulting meta to true, if not set - let bm = match bm.meta { + + match bm.meta { Some(_) => bm, None => match &bm.literal { Some(l) if l == "start" => Self { @@ -552,9 +552,7 @@ impl BotMessage { }, _ => bm, }, - }; - - bm + } } pub fn is_replace(&self) -> bool { @@ -1062,7 +1060,7 @@ impl Runner { #[allow(clippy::unwrap_used)] #[allow(clippy::print_stdout)] mod tests { - use quickjs_rusty::{serde::from_js, OwnedJsObject}; + use quickjs_rusty::OwnedJsObject; use serde_json::json; use super::*; diff --git a/src/botscript/application.rs b/src/botscript/application.rs index af2b00a..3b397bd 100644 --- a/src/botscript/application.rs +++ b/src/botscript/application.rs @@ -6,9 +6,9 @@ use teloxide::Bot; use tokio::runtime::Handle; use crate::{ - db::{application::Application, message_forward::MessageForward, CallDB, DB}, + db::{application::Application, message_forward::MessageForward, DB}, message_answerer::MessageAnswerer, - send_application_to_chat, BotError, + send_application_to_chat, }; use super::ScriptError; diff --git a/src/botscript/message_info.rs b/src/botscript/message_info.rs index 36dcdd9..1b6a513 100644 --- a/src/botscript/message_info.rs +++ b/src/botscript/message_info.rs @@ -9,6 +9,12 @@ pub struct MessageInfoBuilder { inner: MessageInfo, } +impl Default for MessageInfoBuilder { + fn default() -> Self { + Self::new() + } +} + impl MessageInfoBuilder { pub fn new() -> Self { Self { diff --git a/src/db/bots.rs b/src/db/bots.rs index cd412ae..fc9a632 100644 --- a/src/db/bots.rs +++ b/src/db/bots.rs @@ -1,7 +1,5 @@ use bson::doc; -use bson::oid::ObjectId; use chrono::{DateTime, FixedOffset, Local}; -use futures::StreamExt; use futures::TryStreamExt; use serde::{Deserialize, Serialize}; diff --git a/src/db/mod.rs b/src/db/mod.rs index df2f41e..01b1882 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -7,7 +7,7 @@ pub mod raw_calls; use std::time::Duration; use async_trait::async_trait; -use chrono::{DateTime, FixedOffset, Local, Utc}; +use chrono::{DateTime, Local, Utc}; use enum_stringify::EnumStringify; use futures::stream::TryStreamExt; diff --git a/src/db/tests/mod.rs b/src/db/tests/mod.rs index 5dbc6bc..6ddaa7c 100644 --- a/src/db/tests/mod.rs +++ b/src/db/tests/mod.rs @@ -177,7 +177,7 @@ async fn test_drop_media_except() { #[tokio::test] async fn test_get_random_users() { - let mut db = setup_db().await; + let db = setup_db().await; let users = db.get_random_users(1).await.unwrap(); assert_eq!(users.len(), 1); diff --git a/src/handlers/admin.rs b/src/handlers/admin.rs index 7b74aec..d0b419f 100644 --- a/src/handlers/admin.rs +++ b/src/handlers/admin.rs @@ -1,5 +1,3 @@ -use std::str::FromStr; - use itertools::Itertools; use log::{info, warn}; use std::time::Duration; @@ -108,15 +106,15 @@ async fn newscript_handler(bot: Bot, mut db: DB, msg: Message, name: String) -> let mut bytes = bytes.unwrap().to_vec(); buf.append(&mut bytes); } - let script = match String::from_utf8(buf) { + + match String::from_utf8(buf) { Ok(s) => s, Err(err) => { warn!("Failed to parse buf to string, err: {err}"); bot.send_message(msg.chat.id, format!("Failed to Convert file to script: file is not UTF-8, err: {err}")).await?; return Ok(()); } - }; - script + } } _ => todo!(), } @@ -129,7 +127,7 @@ async fn newscript_handler(bot: Bot, mut db: DB, msg: Message, name: String) -> None => { bot.send_message( msg.chat.id, - format!("Failed to set script, possibly bots name is incorrent"), + "Failed to set script, possibly bots name is incorrent".to_string(), ) .await?; return Ok(()); diff --git a/src/utils.rs b/src/utils.rs index a97e94c..bf02c8e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -85,7 +85,7 @@ where #[cfg(test)] mod tests { - use super::*; + use teloxide::types::InlineKeyboardButton; use teloxide::types::InlineKeyboardMarkup; -- 2.47.2 From bd8b1e88432e6d1bf1833905b708e32820a6b60e Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 02:08:00 +0500 Subject: [PATCH 11/38] impl GetCollection via async_trait --- src/db/bots.rs | 18 +++++++++++++----- src/db/mod.rs | 7 ++++--- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/db/bots.rs b/src/db/bots.rs index fc9a632..8b4a005 100644 --- a/src/db/bots.rs +++ b/src/db/bots.rs @@ -43,19 +43,23 @@ impl BotInstance { Ok(self) }); - pub async fn get_all(db: &mut D) -> DbResult> { + pub async fn get_all(db: &mut D) -> DbResult> { let bi = db.get_collection::().await; Ok(bi.find(doc! {}).await?.try_collect().await?) } - pub async fn get_by_name(db: &mut D, name: &str) -> DbResult> { + pub async fn get_by_name(db: &mut D, name: &str) -> DbResult> { let bi = db.get_collection::().await; Ok(bi.find_one(doc! {"name": name}).await?) } - pub async fn restart_one(db: &mut D, name: &str, restart: bool) -> DbResult<()> { + pub async fn restart_one( + db: &mut D, + name: &str, + restart: bool, + ) -> DbResult<()> { let bi = db.get_collection::().await; bi.update_one( @@ -66,7 +70,7 @@ impl BotInstance { Ok(()) } - pub async fn restart_all(db: &mut D, restart: bool) -> DbResult<()> { + pub async fn restart_all(db: &mut D, restart: bool) -> DbResult<()> { let bi = db.get_collection::().await; bi.update_many(doc! {}, doc! { "$set": { "restart_flag": restart } }) @@ -74,7 +78,11 @@ impl BotInstance { Ok(()) } - pub async fn update_script(db: &mut D, name: &str, script: &str) -> DbResult<()> { + pub async fn update_script( + db: &mut D, + name: &str, + script: &str, + ) -> DbResult<()> { let bi = db.get_collection::().await; bi.update_one( diff --git a/src/db/mod.rs b/src/db/mod.rs index 01b1882..442d2cf 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -11,7 +11,6 @@ use chrono::{DateTime, Local, Utc}; use enum_stringify::EnumStringify; use futures::stream::TryStreamExt; -use futures::StreamExt; use mongodb::bson::serde_helpers::chrono_datetime_as_bson_datetime; use mongodb::options::IndexOptions; use mongodb::{bson::doc, options::ClientOptions, Client}; @@ -57,7 +56,7 @@ macro_rules! query_call { #[macro_export] macro_rules! query_call_consume { ($func_name:ident, $self:ident, $db:ident, $return_type:ty, $body:block) => { - pub async fn $func_name($self, $db: &mut D) + pub async fn $func_name($self, $db: &mut D) -> DbResult<$return_type> $body }; } @@ -207,6 +206,7 @@ pub trait DbCollection { const COLLECTION: &str; } +#[async_trait] pub trait GetCollection { async fn get_collection(&mut self) -> Collection; } @@ -222,7 +222,8 @@ impl CallDB for DB { } } -impl GetCollection for T { +#[async_trait] +impl GetCollection for T { async fn get_collection(&mut self) -> Collection { self.get_database() .await -- 2.47.2 From 0c3fb0788a37f0c8c5241658a99a5bd9525ff730 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 02:10:04 +0500 Subject: [PATCH 12/38] delete unnecessary printlns --- src/bot_handler.rs | 10 +--------- src/botscript/application.rs | 5 ----- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/src/bot_handler.rs b/src/bot_handler.rs index 25d00fd..de2238f 100644 --- a/src/bot_handler.rs +++ b/src/bot_handler.rs @@ -208,7 +208,6 @@ async fn handle_callback(bot: Bot, mut db: DB, bm: BotMessage, q: CallbackQuery) let user = update_user_tg(user, &tguser); user.update_user(&mut db).await?; - println!("Is handler set: {}", bm.get_handler().is_some()); let is_propagate: bool = match bm.get_handler() { Some(handler) => 'prop: { let ctx = match handler.context() { @@ -219,14 +218,8 @@ async fn handle_callback(bot: Bot, mut db: DB, bm: BotMessage, q: CallbackQuery) let jsuser = to_js(ctx, &tguser).unwrap(); let mi = MessageInfoBuilder::new().build(); let mi = to_js(ctx, &mi).unwrap(); - println!( - "Calling handler {:?} with msg literal: {:?}", - handler, - bm.literal() - ); match handler.call_args(vec![jsuser, mi]) { Ok(v) => { - println!("Ok branch, got value: {v:?}"); if v.is_bool() { v.to_bool().unwrap_or(true) } else if v.is_int() { @@ -237,7 +230,6 @@ async fn handle_callback(bot: Bot, mut db: DB, bm: BotMessage, q: CallbackQuery) } } Err(err) => { - println!("ERR branch"); error!("Failed to get return of handler, err: {err}"); // falling back to propagation true @@ -312,7 +304,7 @@ async fn handle_callback(bot: Bot, mut db: DB, bm: BotMessage, q: CallbackQuery) Ok(msg_id) => { ma.replace_message(msg_id, literal, buttons).await?; } - Err(err) => { + Err(_) => { ma.answer(literal, None, buttons).await?; } }; diff --git a/src/botscript/application.rs b/src/botscript/application.rs index 3b397bd..9aca213 100644 --- a/src/botscript/application.rs +++ b/src/botscript/application.rs @@ -29,7 +29,6 @@ pub fn attach_user_application( let user_application = c.create_callback(move |q: OwnedJsObject| -> Result<_, ScriptError> { - println!("user_application is called"); let db = db.clone(); let user: teloxide::types::User = match from_js(q.context(), &q) { Ok(q) => q, @@ -39,7 +38,6 @@ pub fn attach_user_application( let application = futures::executor::block_on( Application::new(user.clone()).store_db(&mut db.write().unwrap()), )?; - println!("there1"); let db2 = db.clone(); let msg = tokio::task::block_in_place(move || { @@ -52,7 +50,6 @@ pub fn attach_user_application( .await }) }); - println!("there2"); let msg = match msg { Ok(msg) => msg, Err(err) => { @@ -70,12 +67,10 @@ pub fn attach_user_application( .answer("left_application_msg", None, None), ) .unwrap(); - println!("there3"); futures::executor::block_on( MessageForward::new(msg.chat.id.0, msg.id.0, chat_id, msg_id, false) .store_db(&mut db.write().unwrap()), )?; - println!("there4"); let ret = true; Ok(ret) -- 2.47.2 From e6c9cfb0c13ee122b51fe0edc5a8e9e25a7ddecf Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 02:27:05 +0500 Subject: [PATCH 13/38] fix warnings in botscript.rs --- src/botscript.rs | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/botscript.rs b/src/botscript.rs index 999fb41..f567079 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -760,7 +760,11 @@ impl NotificationFilter { NotificationFilter::Random { random } => Ok(db.get_random_users(*random).await?), NotificationFilter::BotFunction(f) => { let users = f.call()?; - let users = from_js(f.context().unwrap(), &users)?; + // we just called function, so context is definetly valid + let users = from_js( + f.context().expect("Context invalid after function call"), + &users, + )?; Ok(users) } } @@ -768,7 +772,7 @@ impl NotificationFilter { } impl Parcelable for NotificationFilter { - fn get_field(&mut self, name: &str) -> ParcelableResult> { + fn get_field(&mut self, _name: &str) -> ParcelableResult> { todo!() } @@ -798,7 +802,7 @@ pub enum NotificationMessage { } impl Parcelable for NotificationMessage { - fn get_field(&mut self, name: &str) -> ParcelableResult> { + fn get_field(&mut self, _name: &str) -> ParcelableResult> { todo!() } @@ -1060,7 +1064,6 @@ impl Runner { #[allow(clippy::unwrap_used)] #[allow(clippy::print_stdout)] mod tests { - use quickjs_rusty::OwnedJsObject; use serde_json::json; use super::*; @@ -1106,22 +1109,6 @@ mod tests { assert_eq!(sres, "cancelation"); } - fn recursive_format(o: OwnedJsObject) -> String { - let props: Vec<_> = o.properties_iter().unwrap().map(|x| x.unwrap()).collect(); - let sp: Vec = props - .into_iter() - .map(|v| { - if v.is_object() { - recursive_format(v.try_into_object().unwrap()) - } else { - format!("{:?}", v) - } - }) - .collect(); - - format!("{:?}", sp) - } - #[test] fn test_run_script_invalid() { let runner = Runner::init().unwrap(); -- 2.47.2 From 8e57f5da7e07a42ea9f86d94b845fb43fcc5dcc7 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 02:27:42 +0500 Subject: [PATCH 14/38] fix: do not box leak in botscript/application --- src/botscript/application.rs | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/botscript/application.rs b/src/botscript/application.rs index 9aca213..28ea96c 100644 --- a/src/botscript/application.rs +++ b/src/botscript/application.rs @@ -20,16 +20,14 @@ pub fn attach_user_application( bot: &Bot, ) -> Result<(), ScriptError> { let db: std::sync::Arc> = std::sync::Arc::new(RwLock::new(db.clone())); - let dbbox = Box::new(db.clone()); - let db: &'static _ = Box::leak(dbbox); let bot: std::sync::Arc> = std::sync::Arc::new(RwLock::new(bot.clone())); - let botbox = Box::new(bot.clone()); - let bot: &'static _ = Box::leak(botbox); let user_application = c.create_callback(move |q: OwnedJsObject| -> Result<_, ScriptError> { - let db = db.clone(); + let bot1 = bot.clone(); + let bot1 = bot1.read().unwrap(); + let bot2 = bot.read().unwrap(); let user: teloxide::types::User = match from_js(q.context(), &q) { Ok(q) => q, Err(_) => todo!(), @@ -42,12 +40,7 @@ pub fn attach_user_application( let db2 = db.clone(); let msg = tokio::task::block_in_place(move || { Handle::current().block_on(async move { - send_application_to_chat( - &bot.read().unwrap(), - &mut db2.write().unwrap(), - &application, - ) - .await + send_application_to_chat(&bot1, &mut db2.write().unwrap(), &application).await }) }); let msg = match msg { @@ -59,12 +52,11 @@ pub fn attach_user_application( }; let (chat_id, msg_id) = futures::executor::block_on( - MessageAnswerer::new( - &bot.read().unwrap(), - &mut db.write().unwrap(), - user.id.0 as i64, - ) - .answer("left_application_msg", None, None), + MessageAnswerer::new(&bot2, &mut db.write().unwrap(), user.id.0 as i64).answer( + "left_application_msg", + None, + None, + ), ) .unwrap(); futures::executor::block_on( -- 2.47.2 From f6a5a42b71364928cdb9c6dcee014ac938276eaa Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 02:52:37 +0500 Subject: [PATCH 15/38] propagate error instead of unwrap in bot_handler.rs --- src/bot_handler.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/bot_handler.rs b/src/bot_handler.rs index de2238f..a7930cb 100644 --- a/src/bot_handler.rs +++ b/src/bot_handler.rs @@ -15,7 +15,7 @@ use teloxide::{ }; use crate::{ - botscript::{self, message_info::MessageInfoBuilder, BotMessage}, + botscript::{self, message_info::MessageInfoBuilder, BotMessage, ScriptError}, commands::BotCommand, db::{callback_info::CallbackInfo, CallDB, DB}, message_answerer::MessageAnswerer, @@ -121,11 +121,11 @@ async fn handle_botmessage(bot: Bot, mut db: DB, bm: BotMessage, msg: Message) - // falling back to propagation None => break 'prop true, }; - let jsuser = to_js(ctx, &tguser).unwrap(); + let jsuser = to_js(ctx, &tguser).map_err(ScriptError::from)?; let mi = MessageInfoBuilder::new() .set_variant(variant.clone()) .build(); - let mi = to_js(ctx, &mi).unwrap(); + let mi = to_js(ctx, &mi).map_err(ScriptError::from)?; info!( "Calling handler {:?} with msg literal: {:?}", handler, @@ -215,9 +215,9 @@ async fn handle_callback(bot: Bot, mut db: DB, bm: BotMessage, q: CallbackQuery) // falling back to propagation None => break 'prop true, }; - let jsuser = to_js(ctx, &tguser).unwrap(); + let jsuser = to_js(ctx, &tguser).map_err(ScriptError::from)?; let mi = MessageInfoBuilder::new().build(); - let mi = to_js(ctx, &mi).unwrap(); + let mi = to_js(ctx, &mi).map_err(ScriptError::from)?; match handler.call_args(vec![jsuser, mi]) { Ok(v) => { if v.is_bool() { -- 2.47.2 From 18d63313449e1751d9d368511c269426f184d36e Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 02:53:06 +0500 Subject: [PATCH 16/38] bot manager: propagate errors --- src/bot_manager.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/bot_manager.rs b/src/bot_manager.rs index 665c463..1c72e0a 100644 --- a/src/bot_manager.rs +++ b/src/bot_manager.rs @@ -70,7 +70,7 @@ where } } - pub async fn dispatch(mut self, db: &mut DB) -> ! { + pub async fn dispatch(mut self, db: &mut DB) -> BotResult<()> { loop { 'biter: for bi in (self.bi_getter)().await { // removing handler to force restart @@ -82,7 +82,7 @@ where "Trying to restart bot `{}`, new script: {}", bi.name, bi.script ); - let runner = self.bot_pool.remove(&bi.name); + let _runner = self.bot_pool.remove(&bi.name); }; // start, if not started let mut bot_runner = match self.bot_pool.remove(&bi.name) { @@ -90,7 +90,7 @@ where None => { let handlers = (self.h_mapper)(bi.clone()).await; info!("NEW INSTANCE: Starting new instance! bot name: {}", bi.name); - self.start_bot(bi, db, handlers.collect()).await.unwrap(); + self.start_bot(bi, db, handlers.collect()).await?; continue 'biter; } }; @@ -125,8 +125,7 @@ where bot_runner.controller.db.clone(), handler, ) - .await - .unwrap(), + .await?, ) } }; @@ -196,7 +195,7 @@ pub async fn spawn_bot_thread( // let rt = tokio::runtime::Builder::new_current_thread() // .enable_all() // .build()?; - let rt = tokio::runtime::Runtime::new().unwrap(); + let rt = tokio::runtime::Runtime::new()?; rt.block_on( Dispatcher::builder(bot, handler) @@ -215,11 +214,11 @@ pub async fn spawn_notificator_thread( mut c: BotController, ) -> BotResult>> { let thread = std::thread::spawn(move || -> BotResult<()> { - let rt = tokio::runtime::Runtime::new().unwrap(); + let rt = tokio::runtime::Runtime::new()?; rt.block_on(async { loop { - let r = c.runtime.lock().unwrap(); + let r = c.runtime.lock().expect("Poisoned Runtime lock"); let notifications = r.rc.get_nearest_notifications(); drop(r); // unlocking mutex -- 2.47.2 From c9a3916304594ffcda6388578dba93291446f1a2 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 02:53:44 +0500 Subject: [PATCH 17/38] do not use unwrap in botscript/application.rs --- src/botscript/application.rs | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/botscript/application.rs b/src/botscript/application.rs index 28ea96c..ba22334 100644 --- a/src/botscript/application.rs +++ b/src/botscript/application.rs @@ -26,21 +26,27 @@ pub fn attach_user_application( let user_application = c.create_callback(move |q: OwnedJsObject| -> Result<_, ScriptError> { let bot1 = bot.clone(); - let bot1 = bot1.read().unwrap(); - let bot2 = bot.read().unwrap(); + let bot1 = bot1.read().expect("Can't read bot"); + let bot2 = bot.read().expect("Can't read bot"); let user: teloxide::types::User = match from_js(q.context(), &q) { Ok(q) => q, Err(_) => todo!(), }; let application = futures::executor::block_on( - Application::new(user.clone()).store_db(&mut db.write().unwrap()), + Application::new(user.clone()) + .store_db(&mut db.write().expect("Can't write to db rwlock")), )?; let db2 = db.clone(); let msg = tokio::task::block_in_place(move || { Handle::current().block_on(async move { - send_application_to_chat(&bot1, &mut db2.write().unwrap(), &application).await + send_application_to_chat( + &bot1, + &mut db2.write().expect("Can't write to db rwlock"), + &application, + ) + .await }) }); let msg = match msg { @@ -52,13 +58,13 @@ pub fn attach_user_application( }; let (chat_id, msg_id) = futures::executor::block_on( - MessageAnswerer::new(&bot2, &mut db.write().unwrap(), user.id.0 as i64).answer( - "left_application_msg", - None, - None, - ), - ) - .unwrap(); + MessageAnswerer::new( + &bot2, + &mut db.write().expect("Can't write to db rwlock"), + user.id.0 as i64, + ) + .answer("left_application_msg", None, None), + )?; futures::executor::block_on( MessageForward::new(msg.chat.id.0, msg.id.0, chat_id, msg_id, false) .store_db(&mut db.write().unwrap()), -- 2.47.2 From 5399fb682e40bd463c6bc48d55a520765138ab68 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 02:54:08 +0500 Subject: [PATCH 18/38] handle MessageAnswererError in ScriptError --- src/botscript.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/botscript.rs b/src/botscript.rs index f567079..d7118d5 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -7,6 +7,7 @@ use std::time::Duration; use crate::db::raw_calls::RawCallError; use crate::db::{CallDB, DbError, User, DB}; +use crate::message_answerer::MessageAnswererError; use crate::notify_admin; use crate::utils::parcelable::{ParcelType, Parcelable, ParcelableError, ParcelableResult}; use chrono::{DateTime, Days, NaiveTime, ParseError, TimeDelta, Timelike, Utc}; @@ -45,6 +46,8 @@ pub enum ScriptError { RawCallError(#[from] RawCallError), #[error("error while locking mutex: {0:?}")] MutexError(String), + #[error("can't send message to user to user: {0:?}")] + MAError(#[from] MessageAnswererError), } #[derive(thiserror::Error, Debug)] -- 2.47.2 From 6776716faf03a4e32f7913cbe31944aa06d2ab88 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 02:54:35 +0500 Subject: [PATCH 19/38] fix $crate literal in query_call_consume macro --- src/db/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/mod.rs b/src/db/mod.rs index 442d2cf..26256c9 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -56,7 +56,7 @@ macro_rules! query_call { #[macro_export] macro_rules! query_call_consume { ($func_name:ident, $self:ident, $db:ident, $return_type:ty, $body:block) => { - pub async fn $func_name($self, $db: &mut D) + pub async fn $func_name($self, $db: &mut D) -> DbResult<$return_type> $body }; } -- 2.47.2 From 0c1ab767d38e2649ae927d442789bd3db4195cd1 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 02:55:09 +0500 Subject: [PATCH 20/38] handle MessageAnswererError in BotError --- src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main.rs b/src/main.rs index 91fcd5d..acc841e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,6 +17,7 @@ use db::bots::BotInstance; use db::callback_info::CallbackInfo; use handlers::admin::admin_handler; use log::{error, info}; +use message_answerer::MessageAnswererError; use std::sync::{Arc, Mutex}; use crate::db::{CallDB, DB}; @@ -145,6 +146,7 @@ pub enum BotError { ScriptError(#[from] ScriptError), IoError(#[from] std::io::Error), RwLockError(String), + MAError(#[from] MessageAnswererError), } pub type BotResult = Result; -- 2.47.2 From 3c0ae02139c3d0b7dbe4a3382c8e7aee749bf384 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 02:55:29 +0500 Subject: [PATCH 21/38] fix: reutnr error from bot managers dispatcher --- src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index acc841e..bdc362c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -187,7 +187,8 @@ async fn main() -> Result<(), Box> { async |_| vec![admin_handler()].into_iter(), ); - bm.dispatch(&mut db).await; + bm.dispatch(&mut db).await?; + Ok(()) } async fn send_application_to_chat( -- 2.47.2 From c3386a1e2f936365074c375974d9b00b2ee90072 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 02:56:17 +0500 Subject: [PATCH 22/38] use MessageAnswererError in message_answerer.rs --- src/message_answerer.rs | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/message_answerer.rs b/src/message_answerer.rs index 96d1e60..887d7f8 100644 --- a/src/message_answerer.rs +++ b/src/message_answerer.rs @@ -8,7 +8,7 @@ use teloxide::{ Bot, }; -use crate::db::Media; +use crate::db::{DbError, DbResult, Media}; use crate::{ db::{CallDB, DB}, notify_admin, BotResult, @@ -40,6 +40,16 @@ pub struct MessageAnswerer<'a> { db: &'a mut DB, } +#[derive(thiserror::Error, Debug)] +pub enum MessageAnswererError { + #[error("Failed request to DB: {0:?}")] + DbError(#[from] DbError), + #[error("Failed teloxide request: {0:?}")] + RequestError(#[from] teloxide::RequestError), +} + +pub type MAResult = Result; + impl<'a> MessageAnswerer<'a> { pub fn new(bot: &'a Bot, db: &'a mut DB, chat_id: i64) -> Self { Self { bot, chat_id, db } @@ -50,7 +60,7 @@ impl<'a> MessageAnswerer<'a> { literal: &str, variant: Option<&str>, is_replace: bool, - ) -> BotResult { + ) -> DbResult { let variant_text = match variant { Some(variant) => { let value = self @@ -81,7 +91,7 @@ impl<'a> MessageAnswerer<'a> { literal: &str, variant: Option<&str>, keyboard: Option, - ) -> BotResult<(i64, i32)> { + ) -> MAResult<(i64, i32)> { let text = self.get_text(literal, variant, false).await?; self.answer_inner(text, literal, variant, keyboard).await } @@ -90,8 +100,10 @@ impl<'a> MessageAnswerer<'a> { self, text: String, keyboard: Option, - ) -> BotResult<(i64, i32)> { - self.send_message(text, keyboard).await + ) -> MAResult<(i64, i32)> { + self.send_message(text, keyboard) + .await + .map_err(MessageAnswererError::from) } async fn answer_inner( @@ -100,7 +112,7 @@ impl<'a> MessageAnswerer<'a> { literal: &str, variant: Option<&str>, keyboard: Option, - ) -> BotResult<(i64, i32)> { + ) -> MAResult<(i64, i32)> { let media = self.db.get_media(literal).await?; let (chat_id, msg_id) = match media.len() { // just a text @@ -119,7 +131,7 @@ impl<'a> MessageAnswerer<'a> { message_id: i32, literal: &str, keyboard: Option, - ) -> BotResult<()> { + ) -> MAResult<()> { let variant = self .db .get_message(self.chat_id, message_id) @@ -203,7 +215,7 @@ impl<'a> MessageAnswerer<'a> { message_id: i32, literal: &str, variant: Option<&str>, - ) -> BotResult<()> { + ) -> DbResult<()> { match variant { Some(variant) => { self.db @@ -224,7 +236,7 @@ impl<'a> MessageAnswerer<'a> { &self, text: String, keyboard: Option, - ) -> BotResult<(i64, i32)> { + ) -> Result<(i64, i32), teloxide::RequestError> { let msg = self.bot.send_message(ChatId(self.chat_id), text); let msg = match keyboard { Some(kbd) => msg.reply_markup(kbd), @@ -242,7 +254,7 @@ impl<'a> MessageAnswerer<'a> { media: &Media, text: String, keyboard: Option, - ) -> BotResult<(i64, i32)> { + ) -> Result<(i64, i32), teloxide::RequestError> { match media.media_type.as_str() { "photo" => { send_media!( @@ -270,7 +282,11 @@ impl<'a> MessageAnswerer<'a> { } } - async fn send_media_group(&self, media: Vec, text: String) -> BotResult<(i64, i32)> { + async fn send_media_group( + &self, + media: Vec, + text: String, + ) -> Result<(i64, i32), teloxide::RequestError> { let media: Vec = media .into_iter() .enumerate() -- 2.47.2 From c0eb5ba4126d953dd095a6ffb072699c9be5e597 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 02:59:07 +0500 Subject: [PATCH 23/38] bot_manager: fix MutexGuard lifetime --- src/bot_manager.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/bot_manager.rs b/src/bot_manager.rs index 1c72e0a..5d91685 100644 --- a/src/bot_manager.rs +++ b/src/bot_manager.rs @@ -218,9 +218,10 @@ pub async fn spawn_notificator_thread( rt.block_on(async { loop { - let r = c.runtime.lock().expect("Poisoned Runtime lock"); - let notifications = r.rc.get_nearest_notifications(); - drop(r); // unlocking mutex + let notifications = { + let r = c.runtime.lock().expect("Poisoned Runtime lock"); + r.rc.get_nearest_notifications() + }; match notifications { Some(n) => { -- 2.47.2 From 51e4d1a1fc83d7b88dfb301cb47a1bb8515a9638 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 03:17:34 +0500 Subject: [PATCH 24/38] fix warnings in botscript/application --- src/botscript/application.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/botscript/application.rs b/src/botscript/application.rs index ba22334..694e4fe 100644 --- a/src/botscript/application.rs +++ b/src/botscript/application.rs @@ -1,3 +1,5 @@ +// just keeping track locks in asychronous calls +#![allow(clippy::await_holding_lock)] use std::sync::RwLock; use log::info; @@ -26,8 +28,8 @@ pub fn attach_user_application( let user_application = c.create_callback(move |q: OwnedJsObject| -> Result<_, ScriptError> { let bot1 = bot.clone(); - let bot1 = bot1.read().expect("Can't read bot"); - let bot2 = bot.read().expect("Can't read bot"); + let bot1 = bot1.read().expect("Can't read lock bot"); + let bot2 = bot.read().expect("Can't read lock bot"); let user: teloxide::types::User = match from_js(q.context(), &q) { Ok(q) => q, Err(_) => todo!(), @@ -35,7 +37,7 @@ pub fn attach_user_application( let application = futures::executor::block_on( Application::new(user.clone()) - .store_db(&mut db.write().expect("Can't write to db rwlock")), + .store_db(&mut db.write().expect("Can't write lock db")), )?; let db2 = db.clone(); @@ -43,7 +45,7 @@ pub fn attach_user_application( Handle::current().block_on(async move { send_application_to_chat( &bot1, - &mut db2.write().expect("Can't write to db rwlock"), + &mut db2.write().expect("Can't write lock db"), &application, ) .await @@ -60,14 +62,14 @@ pub fn attach_user_application( let (chat_id, msg_id) = futures::executor::block_on( MessageAnswerer::new( &bot2, - &mut db.write().expect("Can't write to db rwlock"), + &mut db.write().expect("Can't write lock db"), user.id.0 as i64, ) .answer("left_application_msg", None, None), )?; futures::executor::block_on( MessageForward::new(msg.chat.id.0, msg.id.0, chat_id, msg_id, false) - .store_db(&mut db.write().unwrap()), + .store_db(&mut db.write().expect("Can't write lock db")), )?; let ret = true; -- 2.47.2 From 1edaac9d8a174e4f6d6c0c7b108e0c1a94047dc4 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 03:18:06 +0500 Subject: [PATCH 25/38] remove unnecessary use on Arc in Runner --- src/botscript.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/botscript.rs b/src/botscript.rs index d7118d5..c36f1c8 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -2,7 +2,7 @@ pub mod application; pub mod db; pub mod message_info; use std::collections::HashMap; -use std::sync::{Arc, Mutex}; +use std::sync::Mutex; use std::time::Duration; use crate::db::raw_calls::RawCallError; @@ -827,9 +827,13 @@ impl NotificationMessage { NotificationMessage::Literal { literal } => Ok(db.get_literal_value(literal).await?), NotificationMessage::Text { text } => Ok(Some(text.to_string())), NotificationMessage::BotFunction(f) => { - let jsuser = to_js(f.context().expect("Function is not js"), user).unwrap(); + let jsuser = to_js(f.context().expect("Function is not js"), user)?; let text = f.call_args(vec![jsuser])?; - let text = from_js(f.context().unwrap(), &text)?; + let text = from_js( + f.context() + .expect("Context was not provided after function call"), + &text, + )?; Ok(text) } } @@ -999,9 +1003,8 @@ impl Parcelable for RunnerConfig { } } -#[derive(Clone)] pub struct Runner { - context: Arc>, + context: Mutex, } impl Runner { @@ -1015,7 +1018,7 @@ impl Runner { })?; Ok(Runner { - context: Arc::new(Mutex::new(context)), + context: Mutex::new(context), }) } @@ -1030,7 +1033,7 @@ impl Runner { where F: FnOnce(&Context, &mut OwnedJsObject) -> R, { - let context = self.context.lock().unwrap(); + let context = self.context.lock().expect("Can't lock context"); let mut global = context.global()?; let res = f(&context, &mut global); -- 2.47.2 From 93852b91557d8328205e249d0466d169e434f6db Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 03:23:35 +0500 Subject: [PATCH 26/38] refactor timezoned time creation in botscript --- src/botscript.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/botscript.rs b/src/botscript.rs index c36f1c8..b912e22 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -955,15 +955,19 @@ impl RunnerConfig { } pub fn created_at(&self) -> DateTime { - self.created_at.at + TimeDelta::try_hours(self.config.timezone.into()).unwrap() + self.timezoned_time(self.created_at.at) + } + + pub fn timezoned_time(&self, dt: DateTime) -> DateTime { + dt + TimeDelta::try_hours(self.config.timezone.into()) + .unwrap_or_else(|| TimeDelta::try_hours(0).expect("Timezone UTC+0 does not exists")) } /// if None is returned, then garanteed that later calls will also return None, /// so, if you'll get None, no notifications will be provided later pub fn get_nearest_notifications(&self) -> Option { let start_time = self.created_at(); - let now = - chrono::offset::Utc::now() + TimeDelta::try_hours(self.config.timezone.into()).unwrap(); + let now = self.timezoned_time(chrono::offset::Utc::now()); let ordered = self .notifications -- 2.47.2 From 4b78ebbb7b609b5005cc2327349e32cbb419e19e Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 03:30:45 +0500 Subject: [PATCH 27/38] allow unwrap in src/commands.rs's tests --- src/commands.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands.rs b/src/commands.rs index 5e8f7ca..cbf8c8b 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -67,6 +67,7 @@ impl BotCommand { } #[cfg(test)] +#[allow(clippy::unwrap_used)] mod tests { use super::*; -- 2.47.2 From fd24b6953e04ba5835540cd2c8ba7b8515b9df19 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 03:31:03 +0500 Subject: [PATCH 28/38] on downloading new script notify admin about failure --- src/handlers/admin.rs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/handlers/admin.rs b/src/handlers/admin.rs index d0b419f..0ec65c4 100644 --- a/src/handlers/admin.rs +++ b/src/handlers/admin.rs @@ -17,7 +17,7 @@ use crate::db::bots::BotInstance; use crate::db::message_forward::MessageForward; use crate::db::{CallDB, DB}; use crate::mongodb_storage::MongodbStorage; -use crate::{BotDialogue, BotError, BotResult, CallbackStore, State}; +use crate::{notify_admin, BotDialogue, BotError, BotResult, CallbackStore, State}; pub fn admin_handler() -> BotHandler { dptree::entry() @@ -103,7 +103,17 @@ async fn newscript_handler(bot: Bot, mut db: DB, msg: Message, name: String) -> let mut stream = bot.download_file_stream(&file.path); let mut buf: Vec = Vec::new(); while let Some(bytes) = stream.next().await { - let mut bytes = bytes.unwrap().to_vec(); + let mut bytes = match bytes { + Ok(bytes) => bytes.to_vec(), + Err(err) => { + notify_admin(&format!( + "Failed to download file: {}, err: {err}", + file.path + )) + .await; + return Ok(()); + } + }; buf.append(&mut bytes); } -- 2.47.2 From 12af8f3653084456c742f6a14a6a863592f41b4b Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 03:31:34 +0500 Subject: [PATCH 29/38] delete unused import --- src/message_answerer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/message_answerer.rs b/src/message_answerer.rs index 887d7f8..5c7a546 100644 --- a/src/message_answerer.rs +++ b/src/message_answerer.rs @@ -11,7 +11,7 @@ use teloxide::{ use crate::db::{DbError, DbResult, Media}; use crate::{ db::{CallDB, DB}, - notify_admin, BotResult, + notify_admin, }; macro_rules! send_media { -- 2.47.2 From 7e01186178be3460b47c6ffcb887ffe5486bc324 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 7 Jun 2025 03:33:13 +0500 Subject: [PATCH 30/38] use async_trait for RawCall --- src/db/raw_calls.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/db/raw_calls.rs b/src/db/raw_calls.rs index 36488fd..6e12528 100644 --- a/src/db/raw_calls.rs +++ b/src/db/raw_calls.rs @@ -1,3 +1,4 @@ +use async_trait::async_trait; use mongodb::Database; use super::CallDB; @@ -14,6 +15,7 @@ pub enum RawCallError { } pub type RawCallResult = Result; +#[async_trait] pub trait RawCall { async fn get_database(&mut self) -> Database; async fn find_one(&mut self, collection: &str, query: Value) -> RawCallResult> { @@ -31,7 +33,8 @@ pub trait RawCall { } } -impl RawCall for T { +#[async_trait] +impl RawCall for T { async fn get_database(&mut self) -> Database { CallDB::get_database(self).await } -- 2.47.2 From b86a8f4a52b4208583d1221bda07038262044b1e Mon Sep 17 00:00:00 2001 From: Akulij Date: Sun, 8 Jun 2025 15:01:14 +0500 Subject: [PATCH 31/38] impl ScriptError::from for types BotError and PoisonError --- src/botscript.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/botscript.rs b/src/botscript.rs index b912e22..6f2ed43 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -2,14 +2,14 @@ pub mod application; pub mod db; pub mod message_info; use std::collections::HashMap; -use std::sync::Mutex; +use std::sync::{Mutex, PoisonError}; use std::time::Duration; use crate::db::raw_calls::RawCallError; use crate::db::{CallDB, DbError, User, DB}; use crate::message_answerer::MessageAnswererError; -use crate::notify_admin; use crate::utils::parcelable::{ParcelType, Parcelable, ParcelableError, ParcelableResult}; +use crate::{notify_admin, BotError}; use chrono::{DateTime, Days, NaiveTime, ParseError, TimeDelta, Timelike, Utc}; use db::attach_db_obj; use futures::future::join_all; @@ -48,6 +48,23 @@ pub enum ScriptError { MutexError(String), #[error("can't send message to user to user: {0:?}")] MAError(#[from] MessageAnswererError), + #[error("other script error: {0:?}")] + Other(String), +} + +impl From for ScriptError { + fn from(value: BotError) -> Self { + match value { + crate::BotError::DBError(db_error) => ScriptError::DBError(db_error), + error => ScriptError::Other(format!("BotError: {error}")), + } + } +} + +impl From> for ScriptError { + fn from(value: PoisonError) -> Self { + Self::MutexError(format!("Can't lock Mutex in script, err: {}", value)) + } } #[derive(thiserror::Error, Debug)] -- 2.47.2 From e3e8a0cf796154fe263915f163bb18217bd97ee4 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sun, 8 Jun 2025 15:01:58 +0500 Subject: [PATCH 32/38] refactor /src/botscript/application.rs --- src/botscript/application.rs | 64 ++++++++++++------------------------ src/main.rs | 2 +- 2 files changed, 22 insertions(+), 44 deletions(-) diff --git a/src/botscript/application.rs b/src/botscript/application.rs index 694e4fe..4471d5a 100644 --- a/src/botscript/application.rs +++ b/src/botscript/application.rs @@ -1,8 +1,3 @@ -// just keeping track locks in asychronous calls -#![allow(clippy::await_holding_lock)] -use std::sync::RwLock; - -use log::info; use quickjs_rusty::{context::Context, serde::from_js, OwnedJsObject}; use teloxide::Bot; use tokio::runtime::Handle; @@ -18,58 +13,41 @@ use super::ScriptError; pub fn attach_user_application( c: &Context, o: &mut OwnedJsObject, - db: &DB, - bot: &Bot, + db: DB, + bot: Bot, ) -> Result<(), ScriptError> { - let db: std::sync::Arc> = std::sync::Arc::new(RwLock::new(db.clone())); - - let bot: std::sync::Arc> = std::sync::Arc::new(RwLock::new(bot.clone())); + // To guarantee that closure is valid if thread panics + let db: std::sync::Mutex = std::sync::Mutex::new(db); + let bot: std::sync::Mutex = std::sync::Mutex::new(bot); let user_application = c.create_callback(move |q: OwnedJsObject| -> Result<_, ScriptError> { - let bot1 = bot.clone(); - let bot1 = bot1.read().expect("Can't read lock bot"); - let bot2 = bot.read().expect("Can't read lock bot"); + let mut db = { db.lock().map_err(ScriptError::from)?.clone() }; + let bot = { bot.lock().map_err(ScriptError::from)?.clone() }; let user: teloxide::types::User = match from_js(q.context(), &q) { Ok(q) => q, Err(_) => todo!(), }; - let application = futures::executor::block_on( - Application::new(user.clone()) - .store_db(&mut db.write().expect("Can't write lock db")), - )?; + let application = + futures::executor::block_on(Application::new(user.clone()).store_db(&mut db))?; - let db2 = db.clone(); - let msg = tokio::task::block_in_place(move || { - Handle::current().block_on(async move { - send_application_to_chat( - &bot1, - &mut db2.write().expect("Can't write lock db"), - &application, - ) - .await - }) + let msg = tokio::task::block_in_place(|| { + Handle::current() + .block_on(async { send_application_to_chat(&bot, &mut db, &application).await }) }); - let msg = match msg { - Ok(msg) => msg, - Err(err) => { - info!("Got err: {err}"); - return Err(ScriptError::MutexError("🤦‍♂️".to_string())); - } - }; + let msg = msg.map_err(ScriptError::from)?; - let (chat_id, msg_id) = futures::executor::block_on( - MessageAnswerer::new( - &bot2, - &mut db.write().expect("Can't write lock db"), - user.id.0 as i64, - ) - .answer("left_application_msg", None, None), - )?; + let (chat_id, msg_id) = tokio::task::block_in_place(|| { + Handle::current().block_on(async { + MessageAnswerer::new(&bot, &mut db, user.id.0 as i64) + .answer("left_application_msg", None, None) + .await + }) + })?; futures::executor::block_on( MessageForward::new(msg.chat.id.0, msg.id.0, chat_id, msg_id, false) - .store_db(&mut db.write().expect("Can't write lock db")), + .store_db(&mut db), )?; let ret = true; diff --git a/src/main.rs b/src/main.rs index bdc362c..0774541 100644 --- a/src/main.rs +++ b/src/main.rs @@ -126,7 +126,7 @@ impl BotController { let bot = Bot::new(token); let mut runner = Runner::init_with_db(&mut db)?; - runner.call_attacher(|c, o| attach_user_application(c, o, &db, &bot))??; + runner.call_attacher(|c, o| attach_user_application(c, o, db.clone(), bot.clone()))??; let rc = runner.init_config(script)?; let runtime = Arc::new(Mutex::new(BotRuntime { rc, runner })); -- 2.47.2 From 2fefe22846ecdab0b769f191dc30f90b130cc4bf Mon Sep 17 00:00:00 2001 From: Akulij Date: Sun, 8 Jun 2025 15:35:50 +0500 Subject: [PATCH 33/38] fix test_drop_media --- src/db/tests/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/db/tests/mod.rs b/src/db/tests/mod.rs index 6ddaa7c..97f0897 100644 --- a/src/db/tests/mod.rs +++ b/src/db/tests/mod.rs @@ -72,6 +72,8 @@ async fn test_add_media() { async fn test_drop_media() { let mut db = setup_db().await; + let _result = db.drop_media("test_drop_media_literal").await.unwrap(); + let _result = db .add_media("test_drop_media_literal", "photo", "file_id_1", None) .await -- 2.47.2 From e239ff1c4445d4fffab4cf3148c92617929f9f93 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sun, 8 Jun 2025 15:38:51 +0500 Subject: [PATCH 34/38] fix test_get_random_users --- src/db/tests/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/db/tests/mod.rs b/src/db/tests/mod.rs index 97f0897..ee01a72 100644 --- a/src/db/tests/mod.rs +++ b/src/db/tests/mod.rs @@ -179,7 +179,9 @@ async fn test_drop_media_except() { #[tokio::test] async fn test_get_random_users() { - let db = setup_db().await; + let mut db = setup_db().await; + + let _ = db.get_or_init_user(1, "Nick").await; let users = db.get_random_users(1).await.unwrap(); assert_eq!(users.len(), 1); -- 2.47.2 From f16554b764fc1856b042353ddb7e4b2860ca1d4e Mon Sep 17 00:00:00 2001 From: Akulij Date: Sun, 8 Jun 2025 15:38:58 +0500 Subject: [PATCH 35/38] fix: use separate db for tests --- src/db/tests/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/db/tests/mod.rs b/src/db/tests/mod.rs index ee01a72..8c4f5cd 100644 --- a/src/db/tests/mod.rs +++ b/src/db/tests/mod.rs @@ -10,7 +10,7 @@ async fn setup_db() -> DB { dotenvy::dotenv().unwrap(); let db_url = std::env::var("DATABASE_URL").unwrap(); - DB::new(db_url, "gongbot".to_string()).await.unwrap() + DB::new(db_url, "tests".to_string()).await.unwrap() } #[tokio::test] -- 2.47.2 From 3d60f31d99db9725b5e74b77eb91e2d6b37e4906 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sun, 8 Jun 2025 17:52:18 +0500 Subject: [PATCH 36/38] optimize algorithm in NotificationTime --- src/botscript.rs | 80 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/src/botscript.rs b/src/botscript.rs index 6f2ed43..d5d917a 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -688,8 +688,7 @@ pub enum NotificationTime { } impl NotificationTime { - pub fn when_next(&self, start_time: &DateTime, now: &DateTime) -> DateTime { - let now = *now; + pub fn when_next(&self, start_time: DateTime, now: DateTime) -> DateTime { match self { NotificationTime::Delta { delta_hours, @@ -697,15 +696,15 @@ impl NotificationTime { } => { let delta = TimeDelta::minutes((delta_minutes + delta_hours * 60).into()); - let mut estimation = *start_time; - // super non-optimal, but fun :) - loop { - if estimation < now + Duration::from_secs(1) { - estimation += delta; - } else { - break estimation; - } - } + let secs_period = delta.num_seconds(); + if secs_period == 0 { + return now; + }; + + let diff = now - start_time; + let passed = diff.num_seconds().abs() % secs_period; + + now - Duration::from_secs(passed as u64) + delta } NotificationTime::Specific(time) => { let estimation = now; @@ -713,13 +712,11 @@ impl NotificationTime { let mut estimation = estimation .with_minute(time.minutes.into()) .unwrap_or(estimation); - // super non-optimal, but fun :) - loop { - if estimation < now { - estimation = estimation + Days::new(1); - } else { - break estimation; - } + + if estimation < now { + estimation + Days::new(1) + } else { + estimation } } } @@ -878,11 +875,11 @@ impl Parcelable for BotNotification { } impl BotNotification { - pub fn left_time(&self, start_time: &DateTime, now: &DateTime) -> Duration { + pub fn left_time(&self, start_time: DateTime, now: DateTime) -> Duration { let next = self.time.when_next(start_time, now); // immidate notification if time to do it passed - let duration = (next - now).to_std().unwrap_or(Duration::from_secs(1)); + let duration = (next - now).to_std().unwrap_or(Duration::from_secs(0)); // Rounding partitions of seconds Duration::from_secs(duration.as_secs()) @@ -989,19 +986,19 @@ impl RunnerConfig { let ordered = self .notifications .iter() - .filter(|f| f.left_time(&start_time, &now) > Duration::from_secs(1)) - .sorted_by_key(|f| f.left_time(&start_time, &now)) + .filter(|f| f.left_time(start_time, now) > Duration::from_secs(1)) + .sorted_by_key(|f| f.left_time(start_time, now)) .collect::>(); let left = match ordered.first() { - Some(notification) => notification.left_time(&start_time, &now), + Some(notification) => notification.left_time(start_time, now), // No notifications provided None => return None, }; // get all that should be sent at the same time let notifications = ordered .into_iter() - .filter(|n| n.left_time(&start_time, &now) == left) + .filter(|n| n.left_time(start_time, now) == left) .cloned() .collect::>(); @@ -1189,7 +1186,7 @@ mod tests { let start_time = chrono::offset::Utc::now(); // let start_time = chrono::offset::Utc::now() + TimeDelta::try_hours(5).unwrap(); let start_time = start_time.with_hour(13).unwrap().with_minute(23).unwrap(); - let left = n.left_time(&start_time, &start_time); + let left = n.left_time(start_time, start_time); let secs = left.as_secs(); let minutes = secs / 60; let hours = minutes / 60; @@ -1207,4 +1204,37 @@ mod tests { assert_eq!(left, should_left) } + + #[test] + fn test_notification_time_nextday() { + let botn = json!({ + "time": "11:00", + "filter": {"random": 2}, + "message": {"text": "some"}, + }); + let n: BotNotification = serde_json::from_value(botn).unwrap(); + println!("BotNotification: {n:#?}"); + let start_time = chrono::offset::Utc::now(); + // let start_time = chrono::offset::Utc::now() + TimeDelta::try_hours(5).unwrap(); + let start_time = start_time.with_hour(13).unwrap().with_minute(23).unwrap(); + let left = n.left_time(start_time, start_time); + let secs = left.as_secs(); + let minutes = secs / 60; + let hours = minutes / 60; + let minutes = minutes % 60; + println!("Left: {hours}:{minutes}"); + + let when_should = chrono::offset::Utc::now() + .with_hour(11) + .unwrap() + .with_minute(00) + .unwrap(); + + let should_left = (when_should + TimeDelta::days(1) - start_time) + .to_std() + .unwrap(); + let should_left = Duration::from_secs(should_left.as_secs()); + + assert_eq!(left, should_left) + } } -- 2.47.2 From d3495c9a44d04668cc45fb17f3130724e549647b Mon Sep 17 00:00:00 2001 From: Akulij Date: Sun, 8 Jun 2025 18:12:16 +0500 Subject: [PATCH 37/38] refactor bot manager --- src/bot_manager.rs | 115 +++++++++++++++++++++++++++------------------ 1 file changed, 69 insertions(+), 46 deletions(-) diff --git a/src/bot_manager.rs b/src/bot_manager.rs index 5d91685..7b7595e 100644 --- a/src/bot_manager.rs +++ b/src/bot_manager.rs @@ -17,15 +17,18 @@ use crate::{ BotController, BotResult, BotRuntime, }; +pub type BotThread = JoinHandle>; + pub struct BotRunner { controller: BotController, info: BotInfo, notificator: NotificatorThread, - thread: Option>>, + thread: Option, } +#[derive(Debug)] pub enum NotificatorThread { - Running(Option>>), + Running(Option), Done, } @@ -72,7 +75,7 @@ where pub async fn dispatch(mut self, db: &mut DB) -> BotResult<()> { loop { - 'biter: for bi in (self.bi_getter)().await { + for bi in (self.bi_getter)().await { // removing handler to force restart // TODO: wait till all updates are processed in bot // Temporarly disabling code, because it's free of js runtime @@ -88,26 +91,12 @@ where let mut bot_runner = match self.bot_pool.remove(&bi.name) { Some(br) => br, None => { - let handlers = (self.h_mapper)(bi.clone()).await; info!("NEW INSTANCE: Starting new instance! bot name: {}", bi.name); - self.start_bot(bi, db, handlers.collect()).await?; - continue 'biter; + self.create_bot_runner(&bi, db).await? } }; - // checking if thread is not finished, otherwise clearing handler - bot_runner.thread = match bot_runner.thread { - Some(thread) => { - if thread.is_finished() { - let err = thread.join(); - error!("Thread bot `{}` finished with error: {:?}", bi.name, err); - None - } else { - Some(thread) - } - } - None => None, - }; + bot_runner.thread = clear_finished_thread(bot_runner.thread, &bi); // checking if thread is running, otherwise start thread bot_runner.thread = match bot_runner.thread { @@ -129,41 +118,84 @@ where ) } }; + + bot_runner.notificator = check_notificator_done(bot_runner.notificator); + + bot_runner.notificator = match bot_runner.notificator { + NotificatorThread::Done => NotificatorThread::Done, + NotificatorThread::Running(thread) => { + NotificatorThread::Running(match thread { + Some(thread) => Some(thread), + None => { + let thread = + spawn_notificator_thread(bot_runner.controller.clone()).await?; + Some(thread) + } + }) + } + }; + self.bot_pool.insert(bi.name.clone(), bot_runner); } tokio::time::sleep(Duration::from_secs(1)).await; } } - pub async fn start_bot( + pub async fn create_bot_runner( &mut self, - bi: BotInstance, + bi: &BotInstance, db: &mut DB, - plug_handlers: Vec, - ) -> BotResult { + ) -> BotResult { let db = db.clone().with_name(bi.name.clone()); let controller = BotController::with_db(db.clone(), &bi.token, &bi.script).await?; - let handler = script_handler_gen(controller.runtime.clone(), plug_handlers).await; - - let thread = - spawn_bot_thread(controller.bot.clone(), controller.db.clone(), handler).await?; - let notificator = spawn_notificator_thread(controller.clone()).await?; - let notificator = NotificatorThread::Running(Some(notificator)); - let info = BotInfo { name: bi.name.clone(), }; let runner = BotRunner { controller, - info: info.clone(), - notificator, - thread: Some(thread), + info, + notificator: NotificatorThread::Running(None), + thread: None, }; - self.bot_pool.insert(bi.name.clone(), runner); + Ok(runner) + } +} - Ok(info) +/// checking if thread is not finished, otherwise clearing handler +fn clear_finished_thread(thread: Option, bi: &BotInstance) -> Option { + thread.and_then(|thread| match thread.is_finished() { + false => Some(thread), + // if finished, join it (should return immidiatly), and print cause of stop + true => { + let err = thread.join(); + error!("Thread bot `{}` finished with error: {:?}", bi.name, err); + None + } + }) +} + +// sets NotificatorThread to Done if running thread returned Ok(...) +fn check_notificator_done(n: NotificatorThread) -> NotificatorThread { + match n { + NotificatorThread::Running(Some(thread)) if thread.is_finished() => { + match thread.join() { + // if thread returns Ok(_), then do not run it again + Ok(result) if result.is_ok() => NotificatorThread::Done, + + // but try to restart, if returned an error + Ok(result) => { + error!("Notificator thread returned error: {result:?}"); + NotificatorThread::Running(None) + } + Err(panicerr) => { + error!("Notificator thread paniced: {panicerr:?}"); + NotificatorThread::Running(None) + } + } + } + other => other, } } @@ -181,20 +213,13 @@ async fn script_handler_gen( handler } -pub async fn spawn_bot_thread( - bot: Bot, - mut db: DB, - handler: BotHandler, -) -> BotResult>> { +pub async fn spawn_bot_thread(bot: Bot, mut db: DB, handler: BotHandler) -> BotResult { let state_mgr = MongodbStorage::from_db(&mut db, Json) .await .map_err(DbError::from)?; let thread = std::thread::spawn(move || -> BotResult<()> { let state_mgr = state_mgr; - // let rt = tokio::runtime::Builder::new_current_thread() - // .enable_all() - // .build()?; let rt = tokio::runtime::Runtime::new()?; rt.block_on( @@ -210,9 +235,7 @@ pub async fn spawn_bot_thread( Ok(thread) } -pub async fn spawn_notificator_thread( - mut c: BotController, -) -> BotResult>> { +pub async fn spawn_notificator_thread(mut c: BotController) -> BotResult { let thread = std::thread::spawn(move || -> BotResult<()> { let rt = tokio::runtime::Runtime::new()?; -- 2.47.2 From 3d9e0f7ee708e7ec7f531a2012c309aa75633ddd Mon Sep 17 00:00:00 2001 From: Akulij Date: Wed, 18 Jun 2025 21:59:47 +0600 Subject: [PATCH 38/38] fast commit --- Cargo.lock | 538 +++++++++++++++++++++++- Cargo.toml | 5 + mainbot.js | 14 +- src/bot_handler.rs | 104 +++-- src/bot_manager.rs | 32 +- src/botscript.rs | 992 +-------------------------------------------- src/db/mod.rs | 11 + src/main.rs | 15 +- 8 files changed, 664 insertions(+), 1047 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be62640..37c5543 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + [[package]] name = "backtrace" version = "0.3.74" @@ -139,6 +145,25 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bindgen" version = "0.71.1" @@ -159,6 +184,21 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -216,6 +256,16 @@ dependencies = [ "uuid", ] +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "build-time" version = "0.1.3" @@ -253,6 +303,26 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "capacity_builder" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f2d24a6dcf0cd402a21b65d35340f3a49ff3475dc5fdac91d22d2733e6641c6" +dependencies = [ + "capacity_builder_macros", + "itoa", +] + +[[package]] +name = "capacity_builder_macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b4a6cae9efc04cc6cbb8faf338d2c497c165c83e74509cf4dbedea948bbf6e5" +dependencies = [ + "quote", + "syn 2.0.100", +] + [[package]] name = "cc" version = "1.2.17" @@ -370,6 +440,12 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cooked-waker" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147be55d677052dabc6b22252d5dd0fd4c29c8c27aa4f2fbef0f94aa003b406f" + [[package]] name = "copy_dir" version = "0.1.3" @@ -419,6 +495,15 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-queue" version = "0.3.12" @@ -491,6 +576,126 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "serde", + "uuid", +] + +[[package]] +name = "deno_core" +version = "0.350.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e273b731fce500130790e777cb2631dc451db412975304d23816c1e444a10c5b" +dependencies = [ + "anyhow", + "az", + "bincode", + "bit-set", + "bit-vec", + "bytes", + "capacity_builder", + "cooked-waker", + "deno_core_icudata", + "deno_error", + "deno_ops", + "deno_path_util", + "deno_unsync", + "futures", + "indexmap 2.8.0", + "libc", + "parking_lot", + "percent-encoding", + "pin-project", + "serde", + "serde_json", + "serde_v8", + "smallvec", + "sourcemap", + "static_assertions", + "thiserror 2.0.12", + "tokio", + "url", + "v8", + "wasm_dep_analyzer", +] + +[[package]] +name = "deno_core_icudata" +version = "0.74.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4dccb6147bb3f3ba0c7a48e993bfeb999d2c2e47a81badee80e2b370c8d695" + +[[package]] +name = "deno_error" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612ec3fc481fea759141b0c57810889b0a4fb6fee8f10748677bfe492fd30486" +dependencies = [ + "deno_error_macro", + "libc", + "serde", + "serde_json", + "tokio", + "url", +] + +[[package]] +name = "deno_error_macro" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8380a4224d5d2c3f84da4d764c4326cac62e9a1e3d4960442d29136fc07be863" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "deno_ops" +version = "0.226.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28b12489187c71fa123731cc783d48beb17ae5df04da991909cc2ae5a3d0ef9" +dependencies = [ + "indexmap 2.8.0", + "proc-macro-rules", + "proc-macro2", + "quote", + "stringcase", + "strum", + "strum_macros", + "syn 2.0.100", + "thiserror 2.0.12", +] + +[[package]] +name = "deno_path_util" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516f813389095889776b81cc9108ff6f336fd9409b4b12fc0138aea23d2708e1" +dependencies = [ + "deno_error", + "percent-encoding", + "sys_traits", + "thiserror 2.0.12", + "url", +] + +[[package]] +name = "deno_unsync" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6742a724e8becb372a74c650a1aefb8924a5b8107f7d75b3848763ea24b27a87" +dependencies = [ + "futures-util", + "parking_lot", + "tokio", +] + [[package]] name = "deranged" version = "0.4.1" @@ -676,6 +881,16 @@ dependencies = [ "scopeguard", ] +[[package]] +name = "erased-serde" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e004d887f51fcb9fef17317a2f3525c887d8aa3f4f50fed920816a688284a5b7" +dependencies = [ + "serde", + "typeid", +] + [[package]] name = "errno" version = "0.3.10" @@ -761,6 +976,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fslock" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "funty" version = "2.0.0" @@ -926,11 +1151,13 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" name = "gongbotrs" version = "0.1.0" dependencies = [ + "anyhow", "async-trait", "bson", "build-time", "chrono", "chrono-tz", + "deno_core", "dotenvy", "enum_stringify", "envconfig", @@ -939,14 +1166,26 @@ dependencies = [ "itertools 0.14.0", "lazy_static", "log", + "mlua", "mongodb", "pretty_env_logger", "quickjs-rusty", "serde", "serde_json", + "serde_v8", "teloxide", "thiserror 2.0.12", "tokio", + "v8", +] + +[[package]] +name = "gzip-header" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95cc527b92e6029a62960ad99aa8a6660faa4555fe5f731aab13aa6a921795a2" +dependencies = [ + "crc32fast", ] [[package]] @@ -1345,6 +1584,12 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "if_chain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed" + [[package]] name = "include_dir" version = "0.7.4" @@ -1508,6 +1753,12 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.9.3" @@ -1545,6 +1796,15 @@ dependencies = [ "linked-hash-map", ] +[[package]] +name = "luau0-src" +version = "0.12.3+luau663" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ae337c644bbf86a8d8e9ce3ee023311833d41741baf5e51acc31b37843aba1" +dependencies = [ + "cc", +] + [[package]] name = "macro_magic" version = "0.5.1" @@ -1633,9 +1893,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", ] @@ -1651,6 +1911,37 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "mlua" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1f5f8fbebc7db5f671671134b9321c4b9aa9adeafccfd9a8c020ae45c6a35d0" +dependencies = [ + "bstr", + "either", + "erased-serde", + "libloading", + "mlua-sys", + "num-traits", + "parking_lot", + "rustc-hash", + "rustversion", + "serde", + "serde-value", +] + +[[package]] +name = "mlua-sys" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "380c1f7e2099cafcf40e51d3a9f20a346977587aa4d012eae1f043149a728a93" +dependencies = [ + "cc", + "cfg-if", + "luau0-src", + "pkg-config", +] + [[package]] name = "mongodb" version = "3.2.3" @@ -1747,6 +2038,7 @@ checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", + "rand 0.8.5", ] [[package]] @@ -1832,6 +2124,21 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "parking" version = "2.2.1" @@ -1870,6 +2177,12 @@ dependencies = [ "regex", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pbkdf2" version = "0.11.0" @@ -2017,6 +2330,29 @@ dependencies = [ "quote", ] +[[package]] +name = "proc-macro-rules" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c277e4e643ef00c1233393c673f655e3672cf7eb3ba08a00bdd0ea59139b5f" +dependencies = [ + "proc-macro-rules-macros", + "proc-macro2", + "syn 2.0.100", +] + +[[package]] +name = "proc-macro-rules-macros" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "207fffb0fe655d1d47f6af98cc2793405e85929bdbc420d685554ff07be27ac7" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -2275,6 +2611,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.0.3" @@ -2284,7 +2633,7 @@ dependencies = [ "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.9.3", "windows-sys 0.59.0", ] @@ -2418,6 +2767,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_bytes" version = "0.11.17" @@ -2463,6 +2822,20 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_v8" +version = "0.259.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e4c5f439cffc03021c8bfd380cd1ef6acb4788a72b7e54bf4a83c73f91f8a0" +dependencies = [ + "deno_error", + "num-bigint", + "serde", + "smallvec", + "thiserror 2.0.12", + "v8", +] + [[package]] name = "serde_with" version = "3.12.0" @@ -2564,6 +2937,24 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "sourcemap" +version = "9.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22afbcb92ce02d23815b9795523c005cb9d3c214f8b7a66318541c240ea7935" +dependencies = [ + "base64-simd", + "bitvec", + "data-encoding", + "debugid", + "if_chain", + "rustc-hash", + "serde", + "serde_json", + "unicode-id-start", + "url", +] + [[package]] name = "spin" version = "0.9.8" @@ -2723,6 +3114,18 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stringcase" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72abeda133c49d7bddece6c154728f83eec8172380c80ab7096da9487e20d27c" + [[package]] name = "stringprep" version = "0.1.5" @@ -2740,6 +3143,28 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.100", +] + [[package]] name = "subtle" version = "2.6.1" @@ -2788,6 +3213,26 @@ dependencies = [ "syn 2.0.100", ] +[[package]] +name = "sys_traits" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc4707edf3196e8037ee45018d1bb1bfb233b0e4fc440fa3d3f25bc69bfdaf26" +dependencies = [ + "sys_traits_macros", +] + +[[package]] +name = "sys_traits_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "181f22127402abcf8ee5c83ccd5b408933fec36a6095cf82cda545634692657e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "take_mut" version = "0.2.2" @@ -2885,7 +3330,7 @@ dependencies = [ "fastrand", "getrandom 0.3.2", "once_cell", - "rustix", + "rustix 1.0.3", "windows-sys 0.59.0", ] @@ -3013,6 +3458,7 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -3152,6 +3598,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.18.0" @@ -3170,6 +3622,12 @@ version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" +[[package]] +name = "unicode-id-start" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f322b60f6b9736017344fa0635d64be2f458fbc04eef65f6be22976dd1ffd5b" + [[package]] name = "unicode-ident" version = "1.0.18" @@ -3245,6 +3703,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "v8" +version = "137.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2b387c1c5731284e756c03280032068e68e5b52f6c4714492403c30f650ad52" +dependencies = [ + "bindgen", + "bitflags 2.9.0", + "fslock", + "gzip-header", + "home", + "miniz_oxide", + "paste", + "which", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -3257,6 +3731,12 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "walkdir" version = "2.5.0" @@ -3381,6 +3861,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasm_dep_analyzer" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51cf5f08b357e64cd7642ab4bbeb11aecab9e15520692129624fb9908b8df2c" +dependencies = [ + "deno_error", + "thiserror 2.0.12", +] + [[package]] name = "web-sys" version = "0.3.77" @@ -3397,6 +3887,18 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "which" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ee928febd44d98f2f459a4a79bd4d928591333a494a10a868418ac1b39cf1f" +dependencies = [ + "either", + "home", + "rustix 0.38.44", + "winsafe", +] + [[package]] name = "whoami" version = "1.6.0" @@ -3413,6 +3915,22 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" @@ -3422,6 +3940,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.52.0" @@ -3688,6 +4212,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winsafe" +version = "0.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" + [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/Cargo.toml b/Cargo.toml index 97d5367..a83a47c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.98" async-trait = "0.1.88" bson = { version = "2.14.0", features = ["chrono-0_4"] } build-time = "0.1.3" chrono = { version = "0.4.40", features = ["serde"] } chrono-tz = "0.10.3" +deno_core = "0.350.0" dotenvy = "0.15.7" enum_stringify = "0.6.3" envconfig = "0.11.0" @@ -19,14 +21,17 @@ git-const = "1.1.0" itertools = "0.14.0" lazy_static = "1.5.0" log = "0.4.27" +mlua = { version = "0.10.5", features = ["luau", "serialize"] } mongodb = "3.2.3" pretty_env_logger = "0.5.0" quickjs-rusty = { git = "https://github.com/akulij/quickjs-rusty.git", rev = "549f830" } serde = { version = "1.0.219", features = ["derive", "serde_derive"] } serde_json = "1.0.140" +serde_v8 = "0.259.0" teloxide = { version = "0.14.0", features = ["macros", "postgres-storage-nativetls"] } thiserror = "2.0.12" tokio = { version = "1.44.1", features = ["rt-multi-thread", "macros"] } +v8 = "137.2.0" [lints.clippy] print_stdout = "warn" diff --git a/mainbot.js b/mainbot.js index 828655f..9f39da9 100644 --- a/mainbot.js +++ b/mainbot.js @@ -93,14 +93,22 @@ print(JSON.stringify(dialog.buttons)) const config = { version: 1.1, - timezone: 3, + timezone: 5, } const notifications = [ + { + // time: "17:38", + time: {once: "17:49"}, + message: {literal: "show_projects"}, + }, // { - // time: "18:14", + // time: { + // hour: 0, + // delta_minutes: 2, + // }, // message: {literal: "show_projects"}, - // }, + // } ] // {config, dialog} diff --git a/src/bot_handler.rs b/src/bot_handler.rs index a7930cb..405b75f 100644 --- a/src/bot_handler.rs +++ b/src/bot_handler.rs @@ -15,8 +15,13 @@ use teloxide::{ }; use crate::{ - botscript::{self, message_info::MessageInfoBuilder, BotMessage, ScriptError}, + botscript::{self, message_info::MessageInfoBuilder, ScriptError}, commands::BotCommand, + config::{ + dialog::{button::ButtonLayout, message::BotMessage}, + traits::ProviderSerialize, + Provider, + }, db::{callback_info::CallbackInfo, CallDB, DB}, message_answerer::MessageAnswerer, notify_admin, update_user_tg, @@ -29,7 +34,7 @@ pub type BotHandler = type CallbackStore = CallbackInfo; -pub fn script_handler(r: Arc>) -> BotHandler { +pub fn script_handler(r: Arc>>) -> BotHandler { let cr = r.clone(); dptree::entry() .branch( @@ -91,8 +96,13 @@ pub fn script_handler(r: Arc>) -> BotHandler { ) } -async fn handle_botmessage(bot: Bot, mut db: DB, bm: BotMessage, msg: Message) -> BotResult<()> { - info!("Eval BM: {:?}", bm); +async fn handle_botmessage( + bot: Bot, + mut db: DB, + bm: BotMessage

, + msg: Message, +) -> BotResult<()> { + // info!("Eval BM: {:?}", bm); let tguser = match msg.from.clone() { Some(user) => user, None => return Ok(()), // do nothing, cause its not usecase of function @@ -116,31 +126,34 @@ async fn handle_botmessage(bot: Bot, mut db: DB, bm: BotMessage, msg: Message) - let is_propagate: bool = match bm.get_handler() { Some(handler) => 'prop: { - let ctx = match handler.context() { - Some(ctx) => ctx, - // falling back to propagation - None => break 'prop true, - }; - let jsuser = to_js(ctx, &tguser).map_err(ScriptError::from)?; + // let ctx = match handler.context() { + // Some(ctx) => ctx, + // // falling back to propagation + // None => break 'prop true, + // }; + // let jsuser = to_js(ctx, &tguser).map_err(ScriptError::from)?; + let puser = ::se_from(&tguser).unwrap(); let mi = MessageInfoBuilder::new() .set_variant(variant.clone()) .build(); - let mi = to_js(ctx, &mi).map_err(ScriptError::from)?; - info!( - "Calling handler {:?} with msg literal: {:?}", - handler, - bm.literal() - ); - match handler.call_args(vec![jsuser, mi]) { + // let mi = to_js(ctx, &mi).map_err(ScriptError::from)?; + let pmi = ::se_from(&mi).unwrap(); + // info!( + // "Calling handler {:?} with msg literal: {:?}", + // handler, + // bm.literal() + // ); + match handler.call_args(vec![puser, pmi]) { Ok(v) => { - if v.is_bool() { - v.to_bool().unwrap_or(true) - } else if v.is_int() { - v.to_int().unwrap_or(1) != 0 - } else { - // falling back to propagation - true - } + todo!() + // if v.is_bool() { + // v.to_bool().unwrap_or(true) + // } else if v.is_int() { + // v.to_int().unwrap_or(1) != 0 + // } else { + // // falling back to propagation + // true + // } } Err(err) => { error!("Failed to get return of handler, err: {err}"); @@ -161,7 +174,7 @@ async fn handle_botmessage(bot: Bot, mut db: DB, bm: BotMessage, msg: Message) - join_all(buttons.iter().map(async |r| { join_all(r.iter().map(async |b| { match b { - botscript::ButtonLayout::Callback { + ButtonLayout::Callback { name, literal: _, callback, @@ -198,9 +211,14 @@ async fn handle_botmessage(bot: Bot, mut db: DB, bm: BotMessage, msg: Message) - Ok(()) } -async fn handle_callback(bot: Bot, mut db: DB, bm: BotMessage, q: CallbackQuery) -> BotResult<()> { +async fn handle_callback( + bot: Bot, + mut db: DB, + bm: BotMessage

, + q: CallbackQuery, +) -> BotResult<()> { bot.answer_callback_query(&q.id).await?; - info!("Eval BM: {:?}", bm); + // info!("Eval BM: {:?}", bm); let tguser = q.from.clone(); let user = db .get_or_init_user(tguser.id.0 as i64, &tguser.first_name) @@ -210,24 +228,20 @@ async fn handle_callback(bot: Bot, mut db: DB, bm: BotMessage, q: CallbackQuery) let is_propagate: bool = match bm.get_handler() { Some(handler) => 'prop: { - let ctx = match handler.context() { - Some(ctx) => ctx, - // falling back to propagation - None => break 'prop true, - }; - let jsuser = to_js(ctx, &tguser).map_err(ScriptError::from)?; + let puser = ::se_from(&tguser).unwrap(); let mi = MessageInfoBuilder::new().build(); - let mi = to_js(ctx, &mi).map_err(ScriptError::from)?; - match handler.call_args(vec![jsuser, mi]) { + let pmi = ::se_from(&mi).unwrap(); + match handler.call_args(vec![puser, pmi]) { Ok(v) => { - if v.is_bool() { - v.to_bool().unwrap_or(true) - } else if v.is_int() { - v.to_int().unwrap_or(1) != 0 - } else { - // falling back to propagation - true - } + todo!() + // if v.is_bool() { + // v.to_bool().unwrap_or(true) + // } else if v.is_int() { + // v.to_int().unwrap_or(1) != 0 + // } else { + // // falling back to propagation + // true + // } } Err(err) => { error!("Failed to get return of handler, err: {err}"); @@ -248,7 +262,7 @@ async fn handle_callback(bot: Bot, mut db: DB, bm: BotMessage, q: CallbackQuery) join_all(buttons.iter().map(async |r| { join_all(r.iter().map(async |b| { match b { - botscript::ButtonLayout::Callback { + ButtonLayout::Callback { name, literal: _, callback, diff --git a/src/bot_manager.rs b/src/bot_manager.rs index 7b7595e..d6c6c0c 100644 --- a/src/bot_manager.rs +++ b/src/bot_manager.rs @@ -40,32 +40,28 @@ pub struct BotInfo { pub static DEFAULT_SCRIPT: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/default_script.js")); -pub struct BotManager +pub struct BotManager where - BIG: FnMut() -> FBIS, - FBIS: Future, - BIS: Iterator, - HM: FnMut(BotInstance) -> FHI, - FHI: Future, - HI: Iterator, + BIG: AsyncFnMut() -> BII, // BotInstance Getter + BII: Iterator, // BotInstance Iterator + BHG: AsyncFnMut(BotInstance) -> BHI, // BotHandler Getter + BHI: Iterator, // BotHandler Iterator { bot_pool: HashMap, bi_getter: BIG, - h_mapper: HM, + h_mapper: BHG, } -impl BotManager +impl BotManager where - BIG: FnMut() -> FBIS, - FBIS: Future, - BIS: Iterator, - HM: FnMut(BotInstance) -> FHI, - FHI: Future, - HI: Iterator, + BIG: AsyncFnMut() -> BII, // BotInstance Getter + BII: Iterator, // BotInstance Iterator + BHG: AsyncFnMut(BotInstance) -> BHI, // BotHandler Getter + BHI: Iterator, // BotHandler Iterator { - /// bi_getter - fnmut that returns iterator over BotInstance - /// h_map - fnmut that returns iterator over handlers by BotInstance - pub fn with(bi_getter: BIG, h_mapper: HM) -> Self { + /// bi_getter - async fnmut that returns iterator over BotInstance + /// h_map - async fnmut that returns iterator over handlers by BotInstance + pub fn with(bi_getter: BIG, h_mapper: BHG) -> Self { Self { bot_pool: Default::default(), bi_getter, diff --git a/src/botscript.rs b/src/botscript.rs index d5d917a..6b35edb 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -5,24 +5,17 @@ use std::collections::HashMap; use std::sync::{Mutex, PoisonError}; use std::time::Duration; +use crate::config::Provider; use crate::db::raw_calls::RawCallError; use crate::db::{CallDB, DbError, User, DB}; use crate::message_answerer::MessageAnswererError; +use crate::runtimes::v8::V8Runtime; use crate::utils::parcelable::{ParcelType, Parcelable, ParcelableError, ParcelableResult}; -use crate::{notify_admin, BotError}; +use crate::{notify_admin, runtimes, BotError}; use chrono::{DateTime, Days, NaiveTime, ParseError, TimeDelta, Timelike, Utc}; use db::attach_db_obj; use futures::future::join_all; use itertools::Itertools; -use quickjs_rusty::serde::{from_js, to_js}; -use quickjs_rusty::utils::create_empty_object; -use quickjs_rusty::utils::create_string; -use quickjs_rusty::ContextError; -use quickjs_rusty::ExecutionError; -use quickjs_rusty::JsFunction; -use quickjs_rusty::OwnedJsValue as JsValue; -use quickjs_rusty::ValueError; -use quickjs_rusty::{Context, OwnedJsObject}; use serde::Deserialize; use serde::Serialize; @@ -75,987 +68,42 @@ pub enum ResolveError { pub type ScriptResult = Result; -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct BotFunction { - func: FunctionMarker, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum FunctionMarker { - /// serde is not able to (de)serialize this, so ignore it and fill - /// in runtime with injection in DeserializeJS - #[serde(skip)] - Function(JsFunction), - StrTemplate(String), -} - -impl FunctionMarker { - pub fn as_str_template(&self) -> Option<&String> { - if let Self::StrTemplate(v) = self { - Some(v) - } else { - None - } - } - - pub fn as_function(&self) -> Option<&JsFunction> { - if let Self::Function(v) = self { - Some(v) - } else { - None - } - } - - pub fn set_js_function(&mut self, f: JsFunction) { - *self = Self::Function(f) - } -} - -impl Parcelable for BotFunction { - fn get_field( - &mut self, - _name: &str, - ) -> crate::utils::parcelable::ParcelableResult> { - todo!() - } - - fn resolve(&mut self) -> ParcelableResult> - where - Self: Sized + 'static, - { - Ok(ParcelType::Function(self)) - } -} - -impl BotFunction { - pub fn by_name(name: String) -> Self { - Self { - func: FunctionMarker::StrTemplate(name), - } - } - - pub fn call_context(&self, runner: &Runner) -> ScriptResult { - match &self.func { - FunctionMarker::Function(f) => { - let val = f.call(Default::default())?; - Ok(val) - } - FunctionMarker::StrTemplate(func_name) => runner.run_script(&format!("{func_name}()")), - } - } - - pub fn context(&self) -> Option<*mut quickjs_rusty::JSContext> { - match &self.func { - FunctionMarker::Function(js_function) => Some(js_function.context()), - FunctionMarker::StrTemplate(_) => None, - } - } - - pub fn call(&self) -> ScriptResult { - self.call_args(Default::default()) - } - - pub fn call_args(&self, args: Vec) -> ScriptResult { - if let FunctionMarker::Function(f) = &self.func { - let val = f.call(args)?; - Ok(val) - } else { - Err(ScriptError::BotFunctionError( - "Js Function is not defined".to_string(), - )) - } - } - - pub fn set_js_function(&mut self, f: JsFunction) { - self.func.set_js_function(f); - } -} - -pub trait DeserializeJS { - fn js_into<'a, T: Deserialize<'a>>(&'a self) -> ScriptResult; -} - -impl DeserializeJS for JsValue { - fn js_into<'a, T: Deserialize<'a>>(&'a self) -> ScriptResult { - let rc = from_js(self.context(), self)?; - - Ok(rc) - } -} - -#[derive(Default)] -pub struct DeserializerJS { - fn_map: HashMap, -} - -impl DeserializerJS { - pub fn new() -> Self { - Self { - fn_map: HashMap::new(), - } - } - - pub fn deserialize_js<'a, T: Deserialize<'a> + Parcelable + 'static>( - value: &'a JsValue, - ) -> ScriptResult { - let mut s = Self::new(); - - s.inject_templates(value, "".to_string())?; - - let mut res = value.js_into()?; - - for (k, jsf) in s.fn_map { - let item: ParcelType<'_, BotFunction> = - match Parcelable::::get_nested(&mut res, &k) { - Ok(item) => item, - Err(err) => { - log::error!("Failed to inject original functions to structs, error: {err}"); - continue; - } - }; - if let ParcelType::Function(f) = item { - f.set_js_function(jsf); - } - } - - Ok(res) - } - - pub fn inject_templates( - &mut self, - value: &JsValue, - path: String, - ) -> ScriptResult> { - if let Ok(f) = value.clone().try_into_function() { - self.fn_map.insert(path.clone(), f); - return Ok(Some(path)); - } else if let Ok(o) = value.clone().try_into_object() { - let path = if path.is_empty() { path } else { path + "." }; // trying to avoid . in the start - // of stringified path - let res = o - .properties_iter()? - .chunks(2) - .into_iter() - // since chunks(2) is used and properties iterator over object - // always has even elements, unwrap will not fail - .map( - #[allow(clippy::unwrap_used)] - |mut chunk| (chunk.next().unwrap(), chunk.next().unwrap()), - ) - .map(|(k, p)| k.and_then(|k| p.map(|p| (k, p)))) - .filter_map(|m| m.ok()) - .try_for_each(|(k, p)| { - let k = match k.to_string() { - Ok(k) => k, - Err(err) => return Err(ScriptError::ValueError(err)), - }; - let res = match self.inject_templates(&p, path.clone() + &k)? { - Some(_) => { - let fo = JsValue::new( - o.context(), - create_empty_object(o.context()).expect("couldn't create object"), - ) - .try_into_object() - .expect("the object created was not an object :/"); - fo.set_property( - "func", - JsValue::new( - o.context(), - create_string(o.context(), "somefunc") - .expect("couldn't create string"), - ), - ) - .expect("wasn't able to set property on object :/"); - o.set_property(&k, fo.into_value()) - } - None => Ok(()), - }; - match res { - Ok(res) => Ok(res), - Err(err) => Err(ScriptError::ExecutionError(err)), - } - }); - res?; - }; - - Ok(None) - } -} - // TODO: remove this function since it is suitable only for early development #[allow(clippy::print_stdout)] fn print(s: String) { println!("{s}"); } -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct BotConfig { - version: f64, - /// relative to UTC, for e.g., - /// timezone = 3 will be UTC+3, - /// timezone =-2 will be UTC-2, - #[serde(default)] - timezone: i8, -} - -pub trait ResolveValue { - type Value; - - fn resolve(self) -> ScriptResult; - fn resolve_with(self, runner: &Runner) -> ScriptResult; -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum KeyboardDefinition { - Rows(Vec), - Function(BotFunction), -} - -impl Parcelable for KeyboardDefinition { - fn get_field(&mut self, _name: &str) -> ParcelableResult> { - todo!() - } - fn resolve(&mut self) -> ParcelableResult> - where - Self: Sized + 'static, - { - match self { - KeyboardDefinition::Rows(rows) => Ok(rows.resolve()?), - KeyboardDefinition::Function(f) => Ok(f.resolve()?), - } - } -} - -impl ResolveValue for KeyboardDefinition { - type Value = Vec<::Value>; - - fn resolve(self) -> ScriptResult { - match self { - KeyboardDefinition::Rows(rows) => rows.into_iter().map(|r| r.resolve()).collect(), - KeyboardDefinition::Function(f) => { - ::resolve(f.call()?.js_into()?) - } - } - } - - fn resolve_with(self, runner: &Runner) -> ScriptResult { - match self { - KeyboardDefinition::Rows(rows) => { - rows.into_iter().map(|r| r.resolve_with(runner)).collect() - } - KeyboardDefinition::Function(f) => { - ::resolve_with(f.call_context(runner)?.js_into()?, runner) - } - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum RowDefinition { - Buttons(Vec), - Function(BotFunction), -} - -impl Parcelable for RowDefinition { - fn get_field(&mut self, _name: &str) -> ParcelableResult> { - todo!() - } - fn resolve(&mut self) -> ParcelableResult> - where - Self: Sized + 'static, - { - match self { - Self::Buttons(buttons) => Ok(buttons.resolve()?), - Self::Function(f) => Ok(f.resolve()?), - } - } -} - -impl ResolveValue for RowDefinition { - type Value = Vec<::Value>; - - fn resolve(self) -> ScriptResult { - match self { - RowDefinition::Buttons(buttons) => buttons.into_iter().map(|b| b.resolve()).collect(), - RowDefinition::Function(f) => ::resolve(f.call()?.js_into()?), - } - } - - fn resolve_with(self, runner: &Runner) -> ScriptResult { - match self { - RowDefinition::Buttons(buttons) => buttons - .into_iter() - .map(|b| b.resolve_with(runner)) - .collect(), - RowDefinition::Function(f) => { - ::resolve_with(f.call_context(runner)?.js_into()?, runner) - } - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum ButtonDefinition { - Button(ButtonRaw), - ButtonLiteral(String), - Function(BotFunction), -} - -impl ResolveValue for ButtonDefinition { - type Value = ButtonRaw; - - fn resolve(self) -> ScriptResult { - match self { - ButtonDefinition::Button(button) => Ok(button), - ButtonDefinition::ButtonLiteral(l) => Ok(ButtonRaw::from_literal(l)), - ButtonDefinition::Function(f) => ::resolve(f.call()?.js_into()?), - } - } - - fn resolve_with(self, runner: &Runner) -> ScriptResult { - match self { - ButtonDefinition::Button(button) => Ok(button), - ButtonDefinition::ButtonLiteral(l) => Ok(ButtonRaw::from_literal(l)), - ButtonDefinition::Function(f) => { - ::resolve_with(f.call_context(runner)?.js_into()?, runner) - } - } - } -} - -impl Parcelable for ButtonDefinition { - fn get_field(&mut self, _name: &str) -> ParcelableResult> { - todo!() - } - fn resolve(&mut self) -> ParcelableResult> - where - Self: Sized + 'static, - { - match self { - Self::Button(braw) => Ok(braw.resolve()?), - Self::ButtonLiteral(s) => Ok(s.resolve()?), - Self::Function(f) => Ok(f.resolve()?), - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct ButtonRaw { - name: ButtonName, - callback_name: String, -} - -impl Parcelable for ButtonRaw { - fn get_field(&mut self, _name: &str) -> ParcelableResult> { - todo!() - } -} - -impl ButtonRaw { - pub fn from_literal(literal: String) -> Self { - ButtonRaw { - name: ButtonName::Literal { - literal: literal.clone(), - }, - callback_name: literal, - } - } - - pub fn name(&self) -> &ButtonName { - &self.name - } - - pub fn callback_name(&self) -> &str { - &self.callback_name - } - - pub fn literal(&self) -> Option { - match self.name() { - ButtonName::Value { .. } => None, - ButtonName::Literal { literal } => Some(literal.to_string()), - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -#[serde(untagged)] -pub enum ButtonName { - Value { name: String }, - Literal { literal: String }, -} - -impl ButtonName { - pub async fn resolve_name(self, db: &mut DB) -> ScriptResult { - match self { - ButtonName::Value { name } => Ok(name), - ButtonName::Literal { literal } => { - let value = db.get_literal_value(&literal).await?; - - Ok(match value { - Some(value) => Ok(value), - None => { - notify_admin(&format!("Literal `{literal}` is not set!!!")).await; - Err(ResolveError::IncorrectLiteral(format!( - "not found literal `{literal}` in DB" - ))) - } - }?) - } - } - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct Button { - name: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -pub struct BotMessage { - // buttons: Vec