Compare commits

..

20 Commits

Author SHA1 Message Date
7147da0cb5 Merge pull request 'dev' (#22) from dev into main
All checks were successful
Build && Deploy / cargo build (push) Successful in 1m5s
Reviewed-on: #22
2025-06-05 20:53:03 +00:00
Akulij
37d2480b4a update mainbot.js
All checks were successful
Build && Deploy / cargo build (push) Successful in 1m5s
2025-06-06 01:52:18 +05:00
Akulij
a869362bf5 notify admin when literal not found instead of silently failing 2025-06-06 01:51:27 +05:00
Akulij
b114c80097 update handling of varianted messages 2025-06-06 01:38:22 +05:00
Akulij
591244b5a1 craate getter for varianted commands 2025-06-06 01:37:35 +05:00
Akulij
06a6542349 change design of variants implementation in BotConfig 2025-06-06 01:37:03 +05:00
Akulij
0bc7978c99 fix deserialization Message's created_at 2025-06-06 01:35:25 +05:00
Akulij
2e356ac067 make BotMessage's handler to recursively generate BotMessage 2025-06-06 00:19:37 +05:00
Akulij
2662a00776 provide MessageInfo to BotMessage's handler 2025-06-05 23:16:06 +05:00
Akulij
5669de716a create MessageInfo and MessageInfoBuilder 2025-06-05 23:13:39 +05:00
Akulij
6d017b1993 fix: provide default serde value for BotMessage.variants 2025-06-05 23:12:56 +05:00
Akulij
8a3e8c4705 filter variant to use only defined in runtime config
All checks were successful
Build && Deploy / cargo build (push) Successful in 1m5s
2025-06-05 22:59:28 +05:00
Akulij
50e2d6e824 use variants of messages 2025-06-05 22:59:12 +05:00
Akulij
c301b72f0c create MessageVariant 2025-06-05 22:56:52 +05:00
Akulij
4103c5dfbe create BotMessage.update_defaults method for some logic for default values 2025-06-05 22:36:30 +05:00
Akulij
303dbfdaa8 make BotMessage.meta optional 2025-06-05 22:35:51 +05:00
Akulij
b202f385fe fix: make BotMessage.fill_literal consuming self 2025-06-05 22:35:16 +05:00
Akulij
3b4ab9b481 implement usage of BotMessage.meta flag in handle_botmessage 2025-06-05 22:21:44 +05:00
Akulij
f58f559f8d create BotMessage.meta flag 2025-06-05 22:21:14 +05:00
Akulij
9dfa7c52d9 add field created_at to db::Message struct
All checks were successful
Build && Deploy / cargo build (push) Successful in 1m3s
2025-06-05 21:48:47 +05:00
5 changed files with 164 additions and 17 deletions

View File

@ -13,7 +13,10 @@ const start_msg = {
}; };
const dialog = { const dialog = {
commands: { commands: {
start: start_msg, start: {
meta: true,
...start_msg
},
}, },
buttons: { buttons: {
more_info: { more_info: {
@ -41,6 +44,17 @@ const dialog = {
state: "none" 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) { function leave_application(user) {

View File

@ -1,5 +1,5 @@
use log::{error, info}; use log::{error, info};
use quickjs_rusty::serde::to_js; use quickjs_rusty::serde::{from_js, to_js};
use std::{ use std::{
str::FromStr, str::FromStr,
sync::{Arc, Mutex, RwLock}, sync::{Arc, Mutex, RwLock},
@ -13,11 +13,11 @@ use teloxide::{
}; };
use crate::{ use crate::{
botscript::{self, BotMessage, RunnerConfig}, botscript::{self, message_info::MessageInfoBuilder, BotMessage, RunnerConfig},
commands::BotCommand, commands::BotCommand,
db::{CallDB, DB}, db::{CallDB, DB},
message_answerer::MessageAnswerer, message_answerer::MessageAnswerer,
update_user_tg, BotError, BotResult, BotRuntime, notify_admin, update_user_tg, BotError, BotResult, BotRuntime,
}; };
pub type BotHandler = pub type BotHandler =
@ -38,7 +38,11 @@ pub fn script_handler(r: Arc<Mutex<BotRuntime>>) -> BotHandler {
let r = r.lock().expect("RwLock lock on commands map failed"); let r = r.lock().expect("RwLock lock on commands map failed");
let rc = &r.rc; 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), .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); let user = update_user_tg(user, &tguser);
user.update_user(&mut db).await?; 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() { let is_propagate: bool = match bm.get_handler() {
Some(handler) => 'prop: { Some(handler) => 'prop: {
let ctx = match handler.context() { 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, None => break 'prop true,
}; };
let jsuser = to_js(ctx, &tguser).unwrap(); let jsuser = to_js(ctx, &tguser).unwrap();
let mi = MessageInfoBuilder::new()
.set_variant(variant.clone())
.build();
let mi = to_js(ctx, &mi).unwrap();
info!( info!(
"Calling handler {:?} with msg literal: {:?}", "Calling handler {:?} with msg literal: {:?}",
handler, handler,
bm.literal() bm.literal()
); );
match handler.call_args(vec![jsuser]) { match handler.call_args(vec![jsuser, mi]) {
Ok(v) => { Ok(v) => {
if v.is_bool() { if v.is_bool() {
v.to_bool().unwrap_or(true) 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 literal = bm.literal().map_or("", |s| s.as_str());
let ma = MessageAnswerer::new(&bot, &mut db, msg.chat.id.0); 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(()) Ok(())
} }
@ -152,12 +172,14 @@ async fn handle_callback(bot: Bot, mut db: DB, bm: BotMessage, q: CallbackQuery)
None => break 'prop true, None => break 'prop true,
}; };
let jsuser = to_js(ctx, &tguser).unwrap(); let jsuser = to_js(ctx, &tguser).unwrap();
let mi = MessageInfoBuilder::new().build();
let mi = to_js(ctx, &mi).unwrap();
println!( println!(
"Calling handler {:?} with msg literal: {:?}", "Calling handler {:?} with msg literal: {:?}",
handler, handler,
bm.literal() bm.literal()
); );
match handler.call_args(vec![jsuser]) { match handler.call_args(vec![jsuser, mi]) {
Ok(v) => { Ok(v) => {
println!("Ok branch, got value: {v:?}"); println!("Ok branch, got value: {v:?}");
if v.is_bool() { if v.is_bool() {

View File

@ -1,11 +1,13 @@
pub mod application; pub mod application;
pub mod db; pub mod db;
pub mod message_info;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, Mutex, PoisonError}; use std::sync::{Arc, Mutex, PoisonError};
use std::time::Duration; use std::time::Duration;
use crate::db::raw_calls::RawCallError; use crate::db::raw_calls::RawCallError;
use crate::db::{CallDB, DbError, User, DB}; use crate::db::{CallDB, DbError, User, DB};
use crate::notify_admin;
use crate::utils::parcelable::{ParcelType, Parcelable, ParcelableError, ParcelableResult}; use crate::utils::parcelable::{ParcelType, Parcelable, ParcelableError, ParcelableResult};
use chrono::{DateTime, Days, NaiveTime, ParseError, TimeDelta, Timelike, Utc}; use chrono::{DateTime, Days, NaiveTime, ParseError, TimeDelta, Timelike, Utc};
use db::attach_db_obj; use db::attach_db_obj;
@ -476,9 +478,12 @@ impl ButtonName {
Ok(match value { Ok(match value {
Some(value) => Ok(value), Some(value) => Ok(value),
None => Err(ResolveError::IncorrectLiteral(format!( None => {
"not found literal `{literal}` in DB" 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<KeyboardDefinition>, buttons: Option<KeyboardDefinition>,
state: Option<String>, state: Option<String>,
/// flag options to command is meta, so it will be appended to user.metas in db
meta: Option<bool>,
handler: Option<BotFunction>, handler: Option<BotFunction>,
} }
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct MessageVariant(String);
impl MessageVariant {
pub fn get_name(&self) -> &str {
&self.0
}
}
impl PartialEq<String> 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 { impl BotMessage {
pub fn fill_literal(&self, l: String) -> Self { pub fn fill_literal(self, l: String) -> Self {
BotMessage { BotMessage {
literal: self.clone().literal.or(Some(l)), 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 { pub fn is_replace(&self) -> bool {
self.replace self.replace
} }
@ -517,6 +564,10 @@ impl BotMessage {
pub fn get_handler(&self) -> Option<&BotFunction> { pub fn get_handler(&self) -> Option<&BotFunction> {
self.handler.as_ref() self.handler.as_ref()
} }
pub fn meta(&self) -> bool {
self.meta.unwrap_or(false)
}
} }
impl BotMessage { impl BotMessage {
@ -589,6 +640,7 @@ pub struct BotDialog {
pub commands: HashMap<String, BotMessage>, pub commands: HashMap<String, BotMessage>,
pub buttons: HashMap<String, BotMessage>, pub buttons: HashMap<String, BotMessage>,
stateful_msg_handlers: HashMap<String, BotMessage>, stateful_msg_handlers: HashMap<String, BotMessage>,
variants: HashMap<String, HashMap<String, BotMessage>>,
} }
impl Parcelable<BotFunction> for BotDialog { impl Parcelable<BotFunction> for BotDialog {
@ -861,7 +913,29 @@ impl RunnerConfig {
pub fn get_command_message(&self, command: &str) -> Option<BotMessage> { pub fn get_command_message(&self, command: &str) -> Option<BotMessage> {
let bm = self.dialog.commands.get(command).cloned(); 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<BotMessage> {
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<BotMessage> { pub fn get_callback_message(&self, callback: &str) -> Option<BotMessage> {

View File

@ -0,0 +1,27 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
pub struct MessageInfo {
variant: Option<String>,
}
pub struct MessageInfoBuilder {
inner: MessageInfo,
}
impl MessageInfoBuilder {
pub fn new() -> Self {
Self {
inner: Default::default(),
}
}
pub fn set_variant(mut self, variant: Option<String>) -> Self {
self.inner.variant = variant;
self
}
pub fn build(self) -> MessageInfo {
self.inner
}
}

View File

@ -7,11 +7,12 @@ pub mod raw_calls;
use std::time::Duration; use std::time::Duration;
use async_trait::async_trait; use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, FixedOffset, Local, Utc};
use enum_stringify::EnumStringify; use enum_stringify::EnumStringify;
use futures::stream::TryStreamExt; use futures::stream::TryStreamExt;
use futures::StreamExt; use futures::StreamExt;
use mongodb::bson::serde_helpers::chrono_datetime_as_bson_datetime;
use mongodb::options::IndexOptions; use mongodb::options::IndexOptions;
use mongodb::{bson::doc, options::ClientOptions, Client}; use mongodb::{bson::doc, options::ClientOptions, Client};
use mongodb::{Collection, Database, IndexModel}; use mongodb::{Collection, Database, IndexModel};
@ -108,6 +109,8 @@ pub struct Message {
pub message_id: i64, pub message_id: i64,
pub token: String, pub token: String,
pub variant: Option<String>, pub variant: Option<String>,
#[serde(with = "chrono_datetime_as_bson_datetime")]
pub created_at: DateTime<Utc>,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -343,7 +346,10 @@ pub trait CallDB {
"message_id": messageid as i64 "message_id": messageid as i64
}, },
doc! { doc! {
"$set": { "token": literal } "$set": {
"token": literal,
"created_at": Into::<DateTime<Utc>>::into(Local::now()),
}
}, },
) )
.upsert(true) .upsert(true)
@ -369,7 +375,11 @@ pub trait CallDB {
"message_id": messageid as i64 "message_id": messageid as i64
}, },
doc! { doc! {
"$set": { "token": literal, "variant": variant } "$set": {
"token": literal,
"variant": variant,
"created_at": Into::<DateTime<Utc>>::into(Local::now()),
}
}, },
) )
.upsert(true) .upsert(true)