Compare commits
11 Commits
bee93b32d1
...
adad94ad43
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
adad94ad43 | ||
|
|
714853730a | ||
|
|
b980a653cb | ||
|
|
b8d07d0ad5 | ||
|
|
2e447e87fd | ||
|
|
d749b57811 | ||
|
|
a106891050 | ||
|
|
9cd0765030 | ||
|
|
bc46e0fda4 | ||
|
|
845071c800 | ||
|
|
c936ea38a9 |
84
mainbot.js
84
mainbot.js
@ -1,21 +1,32 @@
|
||||
// db - is set globally
|
||||
|
||||
const PROJECTS_COUNT = 2
|
||||
|
||||
const start_msg = {
|
||||
buttons: [
|
||||
[{ name: { literal: "show_projects" }, callback_name: "project_0" }],
|
||||
[{ name: { literal: "more_info_btn" }, callback_name: "more_info" }],
|
||||
[{ name: { literal: "leave_application" }, callback_name: "leave_application" }],
|
||||
[{ name: { literal: "ask_question_btn" }, callback_name: "ask_question" }],
|
||||
], // default is `null`
|
||||
replace: true,
|
||||
state: "start"
|
||||
};
|
||||
const dialog = {
|
||||
commands: {
|
||||
start: {
|
||||
buttons: start_buttons, // default is `null`
|
||||
state: "start"
|
||||
},
|
||||
cancel: {
|
||||
buttons: [
|
||||
[{name: {name: "Def"}, callback_name: "defcall"}]
|
||||
],
|
||||
state: "none"
|
||||
},
|
||||
somecomplicatedcmd: {}
|
||||
start: start_msg,
|
||||
},
|
||||
buttons: {
|
||||
more_info: {},
|
||||
more_info: {
|
||||
buttons: [
|
||||
[{ name: { name: "На главную" }, callback_name: "start" }],
|
||||
]
|
||||
},
|
||||
start: start_msg,
|
||||
leave_application: {
|
||||
handler: leave_application
|
||||
},
|
||||
ask_question: {}
|
||||
},
|
||||
stateful_msg_handlers: {
|
||||
start: {}, // everything is by default, so just send message `start`
|
||||
@ -29,36 +40,43 @@ const dialog = {
|
||||
},
|
||||
}
|
||||
|
||||
function enter_name() {}
|
||||
function leave_application(user) {
|
||||
print(JSON.stringify(user))
|
||||
user_application(user)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
function enter_name() { }
|
||||
|
||||
const fmt = (number) => number.toString().padStart(2, '0');
|
||||
|
||||
const formatDate = (date) => {
|
||||
const [h, m, d, M, y] = [
|
||||
date.getHours(),
|
||||
date.getMinutes(),
|
||||
date.getDate(),
|
||||
date.getMonth(),
|
||||
date.getFullYear()
|
||||
];
|
||||
return `${fmt(h)}:${fmt(m)} ${fmt(d)}-${fmt(M + 1)}-${y}`
|
||||
};
|
||||
function add_project_callbacks(point) {
|
||||
for (const i of Array(PROJECTS_COUNT).keys()) {
|
||||
buttons = [
|
||||
[],
|
||||
[{ name: { name: "На главную" }, callback_name: "start" }]
|
||||
]
|
||||
if (i > 0) {
|
||||
buttons[0].push({ name: { literal: "prev_project" }, callback_name: `project_${i - 1}` })
|
||||
}
|
||||
if (i < PROJECTS_COUNT - 1) {
|
||||
buttons[0].push({ name: { literal: "next_project" }, callback_name: `project_${i + 1}` })
|
||||
}
|
||||
|
||||
function start_buttons() {
|
||||
const now = new Date();
|
||||
const dateFormated = formatDate(now);
|
||||
|
||||
// return 1
|
||||
return [
|
||||
[{name: {name: dateFormated}, callback_name: "no"}],
|
||||
[{name: {name: "Hello!"}, callback_name: "no"}],
|
||||
]
|
||||
point[`project_${i}`] = {
|
||||
replace: true,
|
||||
buttons: buttons
|
||||
}
|
||||
}
|
||||
}
|
||||
add_project_callbacks(dialog.buttons)
|
||||
print(JSON.stringify(dialog.buttons))
|
||||
|
||||
const config = {
|
||||
version: 1.1,
|
||||
}
|
||||
|
||||
// {config, dialog}
|
||||
const c = {config: config, dialog: dialog}
|
||||
const c = { config: config, dialog: dialog }
|
||||
c
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use log::info;
|
||||
use log::{error, info};
|
||||
use quickjs_rusty::serde::to_js;
|
||||
use std::{
|
||||
str::FromStr,
|
||||
sync::{Arc, RwLock},
|
||||
@ -66,6 +67,39 @@ 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 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).unwrap();
|
||||
match handler.call_args(vec![jsuser]) {
|
||||
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
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to get return of handler, err: {err}");
|
||||
// falling back to propagation
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
None => true,
|
||||
};
|
||||
|
||||
if !is_propagate {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let buttons = bm
|
||||
.resolve_buttons(&mut db)
|
||||
.await?
|
||||
@ -102,6 +136,39 @@ 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?;
|
||||
|
||||
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).unwrap();
|
||||
match handler.call_args(vec![jsuser]) {
|
||||
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
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Failed to get return of handler, err: {err}");
|
||||
// falling back to propagation
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
None => true,
|
||||
};
|
||||
|
||||
if !is_propagate {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let buttons = bm
|
||||
.resolve_buttons(&mut db)
|
||||
.await?
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
pub mod application;
|
||||
pub mod db;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex, PoisonError};
|
||||
@ -12,12 +13,12 @@ use itertools::Itertools;
|
||||
use quickjs_rusty::serde::from_js;
|
||||
use quickjs_rusty::utils::create_empty_object;
|
||||
use quickjs_rusty::utils::create_string;
|
||||
use quickjs_rusty::Context;
|
||||
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;
|
||||
|
||||
@ -121,6 +122,13 @@ impl BotFunction {
|
||||
}
|
||||
}
|
||||
|
||||
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<JsValue> {
|
||||
self.call_args(Default::default())
|
||||
}
|
||||
@ -498,6 +506,10 @@ impl BotMessage {
|
||||
pub fn is_replace(&self) -> bool {
|
||||
self.replace
|
||||
}
|
||||
|
||||
pub fn get_handler(&self) -> Option<&BotFunction> {
|
||||
self.handler.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl BotMessage {
|
||||
@ -653,6 +665,17 @@ impl 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 mut global = context.global()?;
|
||||
|
||||
let res = f(&context, &mut global);
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn run_script(&self, content: &str) -> ScriptResult<JsValue> {
|
||||
let ctx = match self.context.lock() {
|
||||
Ok(ctx) => ctx,
|
||||
|
||||
81
src/botscript/application.rs
Normal file
81
src/botscript/application.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use std::sync::RwLock;
|
||||
|
||||
use log::info;
|
||||
use quickjs_rusty::{context::Context, serde::from_js, OwnedJsObject};
|
||||
use teloxide::Bot;
|
||||
use tokio::runtime::Handle;
|
||||
|
||||
use crate::{
|
||||
db::{application::Application, message_forward::MessageForward, CallDB, DB},
|
||||
message_answerer::MessageAnswerer,
|
||||
send_application_to_chat, BotError,
|
||||
};
|
||||
|
||||
use super::ScriptError;
|
||||
|
||||
pub fn attach_user_application(
|
||||
c: &Context,
|
||||
o: &mut OwnedJsObject,
|
||||
db: &DB,
|
||||
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> {
|
||||
let db = db.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().unwrap()),
|
||||
)?;
|
||||
|
||||
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
|
||||
})
|
||||
});
|
||||
let msg = match msg {
|
||||
Ok(msg) => msg,
|
||||
Err(err) => {
|
||||
info!("Got err: {err}");
|
||||
return Err(ScriptError::MutexError("🤦♂️".to_string()));
|
||||
}
|
||||
};
|
||||
|
||||
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),
|
||||
)
|
||||
.unwrap();
|
||||
futures::executor::block_on(
|
||||
MessageForward::new(msg.chat.id.0, msg.id.0, chat_id, msg_id, false)
|
||||
.store_db(&mut db.write().unwrap()),
|
||||
)?;
|
||||
|
||||
let ret = true;
|
||||
Ok(ret)
|
||||
})?;
|
||||
|
||||
o.set_property("user_application", user_application.into_value())?;
|
||||
Ok(())
|
||||
}
|
||||
@ -2,6 +2,7 @@ use chrono::{DateTime, FixedOffset, Local};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::DbResult;
|
||||
use super::DB;
|
||||
use crate::query_call_consume;
|
||||
use crate::CallDB;
|
||||
|
||||
@ -36,4 +37,13 @@ where
|
||||
|
||||
Ok(self)
|
||||
});
|
||||
|
||||
pub async fn store_db(self, db: &mut DB) -> DbResult<Self> {
|
||||
let db = db.get_database().await;
|
||||
let ci = db.collection::<Self>("applications");
|
||||
|
||||
ci.insert_one(&self).await?;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ use bson::doc;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::DbResult;
|
||||
use super::DB;
|
||||
use crate::query_call_consume;
|
||||
use crate::CallDB;
|
||||
|
||||
@ -42,6 +43,15 @@ impl MessageForward {
|
||||
Ok(self)
|
||||
});
|
||||
|
||||
pub async fn store_db(self, db: &mut DB) -> DbResult<Self> {
|
||||
let db = db.get_database().await;
|
||||
let ci = db.collection::<Self>("message_forward");
|
||||
|
||||
ci.insert_one(&self).await?;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub async fn get<D: CallDB>(
|
||||
db: &mut D,
|
||||
chat_id: i64,
|
||||
|
||||
@ -23,6 +23,8 @@ use crate::{BotDialogue, BotError, BotResult, CallbackStore, State};
|
||||
|
||||
pub fn admin_handler() -> BotHandler {
|
||||
dptree::entry()
|
||||
// keep on top to cancel any action
|
||||
.branch(cancel_handler())
|
||||
.branch(
|
||||
Update::filter_callback_query()
|
||||
.filter_async(async |q: CallbackQuery, mut db: DB| {
|
||||
@ -197,6 +199,17 @@ async fn button_edit_callback(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cancel_handler() -> BotHandler {
|
||||
Update::filter_message()
|
||||
.filter(|msg: Message| msg.text() == Some("/cancel"))
|
||||
.enter_dialogue::<Message, MongodbStorage<Json>, State>()
|
||||
.endpoint(async |bot: Bot, msg: Message, dialogue: BotDialogue| {
|
||||
dialogue.exit().await?;
|
||||
bot.send_message(msg.chat.id, "Диалог закончен!").await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn command_handler() -> BotHandler {
|
||||
Update::filter_message()
|
||||
.filter_async(async |msg: Message, mut db: DB| {
|
||||
@ -300,6 +313,10 @@ async fn support_reply_handler(
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
let text = format!(
|
||||
"Сообщение от поддержки:\n{}\nЧтобы закончить диалог, нажмите на /cancel",
|
||||
text
|
||||
);
|
||||
let msg = bot
|
||||
.send_message(ChatId(mf.source_chat_id), text)
|
||||
.parse_mode(ParseMode::Html);
|
||||
|
||||
@ -10,6 +10,7 @@ pub mod mongodb_storage;
|
||||
pub mod utils;
|
||||
|
||||
use bot_manager::BotManager;
|
||||
use botscript::application::attach_user_application;
|
||||
use botscript::{BotMessage, Runner, RunnerConfig, ScriptError, ScriptResult};
|
||||
use db::application::Application;
|
||||
use db::bots::BotInstance;
|
||||
@ -131,7 +132,8 @@ impl BotController {
|
||||
pub async fn with_db(mut db: DB, token: &str, script: &str) -> ScriptResult<Self> {
|
||||
let bot = Bot::new(token);
|
||||
|
||||
let runner = Runner::init_with_db(&mut db)?;
|
||||
let mut runner = Runner::init_with_db(&mut db)?;
|
||||
runner.call_attacher(|c, o| attach_user_application(c, o, &db, &bot))??;
|
||||
let rc = runner.init_config(script)?;
|
||||
let rc = Arc::new(RwLock::new(rc));
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user