Compare commits
30 Commits
20af818923
...
7e01186178
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e01186178 | ||
|
|
12af8f3653 | ||
|
|
fd24b6953e | ||
|
|
4b78ebbb7b | ||
|
|
93852b9155 | ||
|
|
1edaac9d8a | ||
|
|
51e4d1a1fc | ||
|
|
c0eb5ba412 | ||
|
|
c3386a1e2f | ||
|
|
3c0ae02139 | ||
|
|
0c1ab767d3 | ||
|
|
6776716faf | ||
|
|
5399fb682e | ||
|
|
c9a3916304 | ||
|
|
18d6331344 | ||
|
|
f6a5a42b71 | ||
|
|
8e57f5da7e | ||
|
|
e6c9cfb0c1 | ||
|
|
0c3fb0788a | ||
|
|
bd8b1e8843 | ||
|
|
5a7bb0e0f6 | ||
|
|
4a090de77b | ||
|
|
3bb03365ed | ||
|
|
b27edd421d | ||
|
|
7752160807 | ||
|
|
3dbfbe48ce | ||
|
|
99403b7282 | ||
|
|
b8bd104f3d | ||
|
|
6e31fa86e6 | ||
|
|
bde3c1a0e1 |
@ -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(
|
||||
|
||||
@ -1,28 +1,34 @@
|
||||
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,
|
||||
types::{CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, Message, Update},
|
||||
prelude::{DependencyMap, Requester},
|
||||
types::{CallbackQuery, InlineKeyboardMarkup, Message, Update},
|
||||
Bot,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
botscript::{self, message_info::MessageInfoBuilder, BotMessage, RunnerConfig},
|
||||
botscript::{self, message_info::MessageInfoBuilder, BotMessage, ScriptError},
|
||||
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<Value>;
|
||||
|
||||
pub fn script_handler(r: Arc<Mutex<BotRuntime>>) -> BotHandler {
|
||||
let cr = r.clone();
|
||||
dptree::entry()
|
||||
@ -48,14 +54,38 @@ pub fn script_handler(r: Arc<Mutex<BotRuntime>>) -> 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),
|
||||
)
|
||||
@ -78,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?;
|
||||
};
|
||||
@ -91,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,
|
||||
@ -126,35 +156,50 @@ 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::<bool>,
|
||||
&mut button_db.clone(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}))
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<_, _>>()
|
||||
}))
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<_, _>>()
|
||||
});
|
||||
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);
|
||||
ma.answer(literal, variant.as_ref().map(|v| v.as_str()), buttons)
|
||||
.await?;
|
||||
ma.answer(literal, variant.as_deref(), buttons).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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
|
||||
@ -163,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() {
|
||||
@ -171,17 +215,11 @@ 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();
|
||||
println!(
|
||||
"Calling handler {:?} with msg literal: {:?}",
|
||||
handler,
|
||||
bm.literal()
|
||||
);
|
||||
let mi = to_js(ctx, &mi).map_err(ScriptError::from)?;
|
||||
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() {
|
||||
@ -192,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
|
||||
@ -206,25 +243,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::<bool>,
|
||||
&mut button_db.clone(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
}))
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<_, _>>()
|
||||
}))
|
||||
.await
|
||||
.into_iter()
|
||||
.collect::<Result<_, _>>()
|
||||
});
|
||||
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) = {
|
||||
@ -252,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?;
|
||||
}
|
||||
};
|
||||
|
||||
@ -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 {
|
||||
@ -78,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
|
||||
@ -90,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) {
|
||||
@ -98,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;
|
||||
}
|
||||
};
|
||||
@ -133,8 +125,7 @@ where
|
||||
bot_runner.controller.db.clone(),
|
||||
handler,
|
||||
)
|
||||
.await
|
||||
.unwrap(),
|
||||
.await?,
|
||||
)
|
||||
}
|
||||
};
|
||||
@ -204,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)
|
||||
@ -223,19 +214,20 @@ pub async fn spawn_notificator_thread(
|
||||
mut c: BotController,
|
||||
) -> BotResult<JoinHandle<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 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) => {
|
||||
// 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,
|
||||
|
||||
@ -2,17 +2,17 @@ pub mod application;
|
||||
pub mod db;
|
||||
pub mod message_info;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, PoisonError};
|
||||
use std::sync::Mutex;
|
||||
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};
|
||||
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;
|
||||
@ -46,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)]
|
||||
@ -543,7 +545,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 +555,7 @@ impl BotMessage {
|
||||
},
|
||||
_ => bm,
|
||||
},
|
||||
};
|
||||
|
||||
bm
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_replace(&self) -> bool {
|
||||
@ -762,7 +763,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)
|
||||
}
|
||||
}
|
||||
@ -770,7 +775,7 @@ impl NotificationFilter {
|
||||
}
|
||||
|
||||
impl Parcelable<BotFunction> for NotificationFilter {
|
||||
fn get_field(&mut self, name: &str) -> ParcelableResult<ParcelType<BotFunction>> {
|
||||
fn get_field(&mut self, _name: &str) -> ParcelableResult<ParcelType<BotFunction>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@ -800,7 +805,7 @@ pub enum NotificationMessage {
|
||||
}
|
||||
|
||||
impl Parcelable<BotFunction> for NotificationMessage {
|
||||
fn get_field(&mut self, name: &str) -> ParcelableResult<ParcelType<BotFunction>> {
|
||||
fn get_field(&mut self, _name: &str) -> ParcelableResult<ParcelType<BotFunction>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@ -822,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)
|
||||
}
|
||||
}
|
||||
@ -946,15 +955,19 @@ impl RunnerConfig {
|
||||
}
|
||||
|
||||
pub fn created_at(&self) -> DateTime<Utc> {
|
||||
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<Utc>) -> DateTime<Utc> {
|
||||
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<NotificationBlock> {
|
||||
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
|
||||
@ -994,9 +1007,8 @@ impl Parcelable<BotFunction> for RunnerConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Runner {
|
||||
context: Arc<Mutex<Context>>,
|
||||
context: Mutex<Context>,
|
||||
}
|
||||
|
||||
impl Runner {
|
||||
@ -1010,31 +1022,22 @@ impl Runner {
|
||||
})?;
|
||||
|
||||
Ok(Runner {
|
||||
context: Arc::new(Mutex::new(context)),
|
||||
context: Mutex::new(context),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn init_with_db(db: &mut DB) -> ScriptResult<Self> {
|
||||
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::<bool>
|
||||
})?;
|
||||
|
||||
Ok(Runner {
|
||||
context: Arc::new(Mutex::new(context)),
|
||||
})
|
||||
Ok(runner)
|
||||
}
|
||||
|
||||
pub fn call_attacher<F, R>(&mut self, f: F) -> ScriptResult<R>
|
||||
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);
|
||||
@ -1071,7 +1074,6 @@ impl Runner {
|
||||
#[allow(clippy::unwrap_used)]
|
||||
#[allow(clippy::print_stdout)]
|
||||
mod tests {
|
||||
use quickjs_rusty::{serde::from_js, OwnedJsObject};
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
@ -1117,22 +1119,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<String> = 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();
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// just keeping track locks in asychronous calls
|
||||
#![allow(clippy::await_holding_lock)]
|
||||
use std::sync::RwLock;
|
||||
|
||||
use log::info;
|
||||
@ -6,9 +8,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;
|
||||
@ -20,39 +22,35 @@ pub fn attach_user_application(
|
||||
bot: &Bot,
|
||||
) -> Result<(), ScriptError> {
|
||||
let db: std::sync::Arc<RwLock<DB>> = 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<RwLock<Bot>> = 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> {
|
||||
println!("user_application is called");
|
||||
let db = db.clone();
|
||||
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 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 lock db")),
|
||||
)?;
|
||||
println!("there1");
|
||||
|
||||
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(),
|
||||
&bot1,
|
||||
&mut db2.write().expect("Can't write lock db"),
|
||||
&application,
|
||||
)
|
||||
.await
|
||||
})
|
||||
});
|
||||
println!("there2");
|
||||
let msg = match msg {
|
||||
Ok(msg) => msg,
|
||||
Err(err) => {
|
||||
@ -63,19 +61,16 @@ pub fn attach_user_application(
|
||||
|
||||
let (chat_id, msg_id) = futures::executor::block_on(
|
||||
MessageAnswerer::new(
|
||||
&bot.read().unwrap(),
|
||||
&mut db.write().unwrap(),
|
||||
&bot2,
|
||||
&mut db.write().expect("Can't write lock db"),
|
||||
user.id.0 as i64,
|
||||
)
|
||||
.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()),
|
||||
.store_db(&mut db.write().expect("Can't write lock db")),
|
||||
)?;
|
||||
println!("there4");
|
||||
|
||||
let ret = true;
|
||||
Ok(ret)
|
||||
|
||||
@ -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<RwLock<DB>> = 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()
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -67,6 +67,7 @@ impl BotCommand {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(clippy::unwrap_used)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
|
||||
@ -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};
|
||||
|
||||
@ -45,19 +43,23 @@ impl BotInstance {
|
||||
Ok(self)
|
||||
});
|
||||
|
||||
pub async fn get_all<D: CallDB>(db: &mut D) -> DbResult<Vec<Self>> {
|
||||
pub async fn get_all<D: GetCollection>(db: &mut D) -> DbResult<Vec<Self>> {
|
||||
let bi = db.get_collection::<Self>().await;
|
||||
|
||||
Ok(bi.find(doc! {}).await?.try_collect().await?)
|
||||
}
|
||||
|
||||
pub async fn get_by_name<D: CallDB>(db: &mut D, name: &str) -> DbResult<Option<Self>> {
|
||||
pub async fn get_by_name<D: GetCollection>(db: &mut D, name: &str) -> DbResult<Option<Self>> {
|
||||
let bi = db.get_collection::<Self>().await;
|
||||
|
||||
Ok(bi.find_one(doc! {"name": name}).await?)
|
||||
}
|
||||
|
||||
pub async fn restart_one<D: CallDB>(db: &mut D, name: &str, restart: bool) -> DbResult<()> {
|
||||
pub async fn restart_one<D: GetCollection>(
|
||||
db: &mut D,
|
||||
name: &str,
|
||||
restart: bool,
|
||||
) -> DbResult<()> {
|
||||
let bi = db.get_collection::<Self>().await;
|
||||
|
||||
bi.update_one(
|
||||
@ -68,7 +70,7 @@ impl BotInstance {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn restart_all<D: CallDB>(db: &mut D, restart: bool) -> DbResult<()> {
|
||||
pub async fn restart_all<D: GetCollection>(db: &mut D, restart: bool) -> DbResult<()> {
|
||||
let bi = db.get_collection::<Self>().await;
|
||||
|
||||
bi.update_many(doc! {}, doc! { "$set": { "restart_flag": restart } })
|
||||
@ -76,7 +78,11 @@ impl BotInstance {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn update_script<D: CallDB>(db: &mut D, name: &str, script: &str) -> DbResult<()> {
|
||||
pub async fn update_script<D: GetCollection>(
|
||||
db: &mut D,
|
||||
name: &str,
|
||||
script: &str,
|
||||
) -> DbResult<()> {
|
||||
let bi = db.get_collection::<Self>().await;
|
||||
|
||||
bi.update_one(
|
||||
|
||||
@ -7,11 +7,10 @@ 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;
|
||||
|
||||
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<D: CallDB>($self, $db: &mut D)
|
||||
pub async fn $func_name<D: $crate::db::GetCollection + CallDB>($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<C: DbCollection + Send + Sync>(&mut self) -> Collection<C>;
|
||||
}
|
||||
@ -222,7 +222,8 @@ impl CallDB for DB {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CallDB> GetCollection for T {
|
||||
#[async_trait]
|
||||
impl<T: CallDB + Send> GetCollection for T {
|
||||
async fn get_collection<C: DbCollection + Send + Sync>(&mut self) -> Collection<C> {
|
||||
self.get_database()
|
||||
.await
|
||||
|
||||
@ -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<T> = Result<T, RawCallError>;
|
||||
|
||||
#[async_trait]
|
||||
pub trait RawCall {
|
||||
async fn get_database(&mut self) -> Database;
|
||||
async fn find_one(&mut self, collection: &str, query: Value) -> RawCallResult<Option<Value>> {
|
||||
@ -31,7 +33,8 @@ pub trait RawCall {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CallDB> RawCall for T {
|
||||
#[async_trait]
|
||||
impl<T: CallDB + Send> RawCall for T {
|
||||
async fn get_database(&mut self) -> Database {
|
||||
CallDB::get_database(self).await
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use itertools::Itertools;
|
||||
use log::{info, warn};
|
||||
use std::time::Duration;
|
||||
@ -19,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()
|
||||
@ -105,18 +103,28 @@ 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<u8> = 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);
|
||||
}
|
||||
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 +137,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(());
|
||||
|
||||
274
src/main.rs
274
src/main.rs
@ -11,16 +11,14 @@ 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 message_answerer::MessageAnswererError;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use crate::db::{CallDB, DB};
|
||||
use crate::mongodb_storage::MongodbStorage;
|
||||
@ -29,13 +27,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<State, MongodbStorage<Json>>;
|
||||
|
||||
@ -53,15 +46,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;
|
||||
}
|
||||
@ -162,6 +146,7 @@ pub enum BotError {
|
||||
ScriptError(#[from] ScriptError),
|
||||
IoError(#[from] std::io::Error),
|
||||
RwLockError(String),
|
||||
MAError(#[from] MessageAnswererError),
|
||||
}
|
||||
|
||||
pub type BotResult<T> = Result<T, BotError>;
|
||||
@ -181,6 +166,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
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();
|
||||
@ -197,167 +184,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
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;
|
||||
}
|
||||
|
||||
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?;
|
||||
}
|
||||
};
|
||||
|
||||
bm.dispatch(&mut db).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -385,9 +215,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? {
|
||||
@ -403,9 +233,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(),
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
@ -431,76 +261,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<InlineKeyboardMarkup> {
|
||||
let mut buttons: Vec<Vec<InlineKeyboardButton>> = 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(),
|
||||
|
||||
@ -8,10 +8,10 @@ use teloxide::{
|
||||
Bot,
|
||||
};
|
||||
|
||||
use crate::db::Media;
|
||||
use crate::db::{DbError, DbResult, Media};
|
||||
use crate::{
|
||||
db::{CallDB, DB},
|
||||
notify_admin, BotResult,
|
||||
notify_admin,
|
||||
};
|
||||
|
||||
macro_rules! send_media {
|
||||
@ -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<T> = Result<T, MessageAnswererError>;
|
||||
|
||||
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<String> {
|
||||
) -> DbResult<String> {
|
||||
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<InlineKeyboardMarkup>,
|
||||
) -> 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<InlineKeyboardMarkup>,
|
||||
) -> 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<InlineKeyboardMarkup>,
|
||||
) -> 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<InlineKeyboardMarkup>,
|
||||
) -> BotResult<()> {
|
||||
) -> MAResult<()> {
|
||||
let variant = self
|
||||
.db
|
||||
.get_message(self.chat_id, message_id)
|
||||
@ -127,7 +139,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 =
|
||||
@ -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<InlineKeyboardMarkup>,
|
||||
) -> 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<InlineKeyboardMarkup>,
|
||||
) -> 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<Media>, text: String) -> BotResult<(i64, i32)> {
|
||||
async fn send_media_group(
|
||||
&self,
|
||||
media: Vec<Media>,
|
||||
text: String,
|
||||
) -> Result<(i64, i32), teloxide::RequestError> {
|
||||
let media: Vec<InputMedia> = media
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
|
||||
19
src/utils.rs
19
src/utils.rs
@ -66,9 +66,26 @@ where
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn callback_button<C, D>(
|
||||
name: &str,
|
||||
callback_name: String,
|
||||
callback_data: C,
|
||||
db: &mut D,
|
||||
) -> BotResult<InlineKeyboardButton>
|
||||
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::*;
|
||||
|
||||
use teloxide::types::InlineKeyboardButton;
|
||||
use teloxide::types::InlineKeyboardMarkup;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user