diff --git a/mainbot.js b/mainbot.js index 693892e..2b97256 100644 --- a/mainbot.js +++ b/mainbot.js @@ -13,7 +13,10 @@ const start_msg = { }; const dialog = { commands: { - start: start_msg, + start: { + meta: true, + ...start_msg + }, }, buttons: { more_info: { @@ -41,6 +44,17 @@ const dialog = { state: "none" }, }, + variants: { + start: { + free_tgads: { + ...start_msg, + buttons: [ + [{ name: { literal: "free_doc_btn" }, callback_name: "free_doc" }], + ...start_msg.buttons, + ], + }, + }, + }, } function leave_application(user) { diff --git a/src/bot_handler.rs b/src/bot_handler.rs index 39fd973..b373dc6 100644 --- a/src/bot_handler.rs +++ b/src/bot_handler.rs @@ -1,5 +1,5 @@ use log::{error, info}; -use quickjs_rusty::serde::to_js; +use quickjs_rusty::serde::{from_js, to_js}; use std::{ str::FromStr, sync::{Arc, Mutex, RwLock}, @@ -13,11 +13,11 @@ use teloxide::{ }; use crate::{ - botscript::{self, BotMessage, RunnerConfig}, + botscript::{self, message_info::MessageInfoBuilder, BotMessage, RunnerConfig}, commands::BotCommand, db::{CallDB, DB}, message_answerer::MessageAnswerer, - update_user_tg, BotError, BotResult, BotRuntime, + notify_admin, update_user_tg, BotError, BotResult, BotRuntime, }; pub type BotHandler = @@ -38,7 +38,11 @@ pub fn script_handler(r: Arc>) -> BotHandler { let r = r.lock().expect("RwLock lock on commands map failed"); let rc = &r.rc; - rc.get_command_message(command) + // it's not necessary, but avoiding some hashmap lookups + match bc.args() { + Some(variant) => rc.get_command_message_varianted(command, variant), + None => rc.get_command_message(command), + } }) .endpoint(handle_botmessage), ) @@ -69,6 +73,17 @@ async fn handle_botmessage(bot: Bot, mut db: DB, bm: BotMessage, msg: Message) - let user = update_user_tg(user, &tguser); user.update_user(&mut db).await?; + let variant = match BotCommand::from_str(msg.text().unwrap_or("")) { + Ok(cmd) => cmd.args().map(|m| m.to_string()), + Err(_) => None, + }; + + if bm.meta() == true { + if let Some(ref meta) = variant { + user.insert_meta(&mut db, meta).await?; + }; + }; + let is_propagate: bool = match bm.get_handler() { Some(handler) => 'prop: { let ctx = match handler.context() { @@ -77,12 +92,16 @@ async fn handle_botmessage(bot: Bot, mut db: DB, bm: BotMessage, msg: Message) - None => break 'prop true, }; let jsuser = to_js(ctx, &tguser).unwrap(); + let mi = MessageInfoBuilder::new() + .set_variant(variant.clone()) + .build(); + let mi = to_js(ctx, &mi).unwrap(); info!( "Calling handler {:?} with msg literal: {:?}", handler, bm.literal() ); - match handler.call_args(vec![jsuser]) { + match handler.call_args(vec![jsuser, mi]) { Ok(v) => { if v.is_bool() { v.to_bool().unwrap_or(true) @@ -129,7 +148,8 @@ 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, None, buttons).await?; + ma.answer(literal, variant.as_ref().map(|v| v.as_str()), buttons) + .await?; Ok(()) } @@ -152,12 +172,14 @@ async fn handle_callback(bot: Bot, mut db: DB, bm: BotMessage, q: CallbackQuery) None => break 'prop true, }; 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]) { + match handler.call_args(vec![jsuser, mi]) { Ok(v) => { println!("Ok branch, got value: {v:?}"); if v.is_bool() { diff --git a/src/botscript.rs b/src/botscript.rs index 63231f6..1000e77 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -1,11 +1,13 @@ pub mod application; pub mod db; +pub mod message_info; use std::collections::HashMap; use std::sync::{Arc, Mutex, PoisonError}; use std::time::Duration; use crate::db::raw_calls::RawCallError; use crate::db::{CallDB, DbError, User, DB}; +use crate::notify_admin; use crate::utils::parcelable::{ParcelType, Parcelable, ParcelableError, ParcelableResult}; use chrono::{DateTime, Days, NaiveTime, ParseError, TimeDelta, Timelike, Utc}; use db::attach_db_obj; @@ -476,9 +478,12 @@ impl ButtonName { Ok(match value { Some(value) => Ok(value), - None => Err(ResolveError::IncorrectLiteral(format!( - "not found literal `{literal}` in DB" - ))), + None => { + notify_admin(&format!("Literal `{literal}` is not set!!!")).await; + Err(ResolveError::IncorrectLiteral(format!( + "not found literal `{literal}` in DB" + ))) + } }?) } } @@ -499,17 +504,59 @@ pub struct BotMessage { buttons: Option, state: Option, + /// flag options to command is meta, so it will be appended to user.metas in db + meta: Option, + handler: Option, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct MessageVariant(String); + +impl MessageVariant { + pub fn get_name(&self) -> &str { + &self.0 + } +} + +impl PartialEq for &MessageVariant { + fn eq(&self, other: &String) -> bool { + self.0 == *other + } +} + +impl PartialEq<&str> for &MessageVariant { + fn eq(&self, other: &&str) -> bool { + self.0 == *other + } +} + impl BotMessage { - pub fn fill_literal(&self, l: String) -> Self { + pub fn fill_literal(self, l: String) -> Self { BotMessage { literal: self.clone().literal.or(Some(l)), - ..self.clone() + ..self } } + /// chain of modifications on 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 { + Some(_) => bm, + None => match &bm.literal { + Some(l) if l == "start" => Self { + meta: Some(true), + ..bm + }, + _ => bm, + }, + }; + + bm + } + pub fn is_replace(&self) -> bool { self.replace } @@ -517,6 +564,10 @@ impl BotMessage { pub fn get_handler(&self) -> Option<&BotFunction> { self.handler.as_ref() } + + pub fn meta(&self) -> bool { + self.meta.unwrap_or(false) + } } impl BotMessage { @@ -589,6 +640,7 @@ pub struct BotDialog { pub commands: HashMap, pub buttons: HashMap, stateful_msg_handlers: HashMap, + variants: HashMap>, } impl Parcelable for BotDialog { @@ -861,7 +913,29 @@ impl RunnerConfig { pub fn get_command_message(&self, command: &str) -> Option { let bm = self.dialog.commands.get(command).cloned(); - bm.map(|bm| bm.fill_literal(command.to_string())) + bm.map(|bm| bm.fill_literal(command.to_string()).update_defaults()) + } + + pub fn get_command_message_varianted( + &self, + command: &str, + variant: &str, + ) -> Option { + if !self.dialog.commands.contains_key(command) { + return None; + } + // fallback to regular if not found + let bm = match self.dialog.variants.get(command).cloned() { + Some(bm) => bm, + None => return self.get_command_message(command), + }; + // get variant of message + let bm = match bm.get(variant).cloned() { + Some(bm) => bm, + None => return self.get_command_message(command), + }; + + Some(bm.fill_literal(command.to_string()).update_defaults()) } pub fn get_callback_message(&self, callback: &str) -> Option { diff --git a/src/botscript/message_info.rs b/src/botscript/message_info.rs new file mode 100644 index 0000000..36dcdd9 --- /dev/null +++ b/src/botscript/message_info.rs @@ -0,0 +1,27 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Default, Debug, Clone)] +pub struct MessageInfo { + variant: Option, +} + +pub struct MessageInfoBuilder { + inner: MessageInfo, +} + +impl MessageInfoBuilder { + pub fn new() -> Self { + Self { + inner: Default::default(), + } + } + + pub fn set_variant(mut self, variant: Option) -> Self { + self.inner.variant = variant; + self + } + + pub fn build(self) -> MessageInfo { + self.inner + } +} diff --git a/src/db/mod.rs b/src/db/mod.rs index 508bdbe..df2f41e 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -7,11 +7,12 @@ pub mod raw_calls; use std::time::Duration; use async_trait::async_trait; -use chrono::{DateTime, Utc}; +use chrono::{DateTime, FixedOffset, 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}; use mongodb::{Collection, Database, IndexModel}; @@ -108,6 +109,8 @@ pub struct Message { pub message_id: i64, pub token: String, pub variant: Option, + #[serde(with = "chrono_datetime_as_bson_datetime")] + pub created_at: DateTime, } #[derive(Serialize, Deserialize)] @@ -343,7 +346,10 @@ pub trait CallDB { "message_id": messageid as i64 }, doc! { - "$set": { "token": literal } + "$set": { + "token": literal, + "created_at": Into::>::into(Local::now()), + } }, ) .upsert(true) @@ -369,7 +375,11 @@ pub trait CallDB { "message_id": messageid as i64 }, doc! { - "$set": { "token": literal, "variant": variant } + "$set": { + "token": literal, + "variant": variant, + "created_at": Into::>::into(Local::now()), + } }, ) .upsert(true)