Compare commits
16 Commits
d174ee7bc7
...
4548419946
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4548419946 | ||
|
|
e8dbf3db76 | ||
|
|
1ff86f641f | ||
|
|
31e78be68f | ||
|
|
217a074c95 | ||
|
|
1c17639c0e | ||
|
|
9e35f4168e | ||
|
|
178f2a2399 | ||
|
|
1730107e9a | ||
|
|
506fdcb260 | ||
|
|
6d5f748ab8 | ||
|
|
cbb9c0c335 | ||
|
|
f8c63e5315 | ||
|
|
66180e0cfb | ||
|
|
1117af0724 | ||
|
|
99758500b3 |
@ -49,7 +49,10 @@ function start_buttons() {
|
|||||||
const dateFormated = formatDate(now);
|
const dateFormated = formatDate(now);
|
||||||
|
|
||||||
// return 1
|
// return 1
|
||||||
return dateFormated
|
return [
|
||||||
|
[{name: {name: dateFormated}, callback_name: "no"}],
|
||||||
|
[{name: {name: "Hello!"}, callback_name: "no"}],
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
|
|||||||
166
src/botscript.rs
166
src/botscript.rs
@ -1,6 +1,8 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::db::{CallDB, DbError, DB};
|
||||||
use crate::utils::parcelable::{ParcelType, Parcelable, ParcelableError, ParcelableResult};
|
use crate::utils::parcelable::{ParcelType, Parcelable, ParcelableError, ParcelableResult};
|
||||||
|
use futures::future::join_all;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use quickjs_rusty::serde::from_js;
|
use quickjs_rusty::serde::from_js;
|
||||||
use quickjs_rusty::utils::create_empty_object;
|
use quickjs_rusty::utils::create_empty_object;
|
||||||
@ -26,6 +28,16 @@ pub enum ScriptError {
|
|||||||
ValueError(#[from] ValueError),
|
ValueError(#[from] ValueError),
|
||||||
#[error("error bot function execution: {0:?}")]
|
#[error("error bot function execution: {0:?}")]
|
||||||
BotFunctionError(String),
|
BotFunctionError(String),
|
||||||
|
#[error("error from DB: {0:?}")]
|
||||||
|
DBError(#[from] DbError),
|
||||||
|
#[error("error resolving data: {0:?}")]
|
||||||
|
ResolveError(#[from] ResolveError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(thiserror::Error, Debug)]
|
||||||
|
pub enum ResolveError {
|
||||||
|
#[error("wrong literal: {0:?}")]
|
||||||
|
IncorrectLiteral(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ScriptResult<T> = Result<T, ScriptError>;
|
pub type ScriptResult<T> = Result<T, ScriptError>;
|
||||||
@ -245,7 +257,8 @@ pub struct BotConfig {
|
|||||||
pub trait ResolveValue {
|
pub trait ResolveValue {
|
||||||
type Value;
|
type Value;
|
||||||
|
|
||||||
fn resolve(self, runner: &Runner) -> ScriptResult<Self::Value>;
|
fn resolve(self) -> ScriptResult<Self::Value>;
|
||||||
|
fn resolve_with(self, runner: &Runner) -> ScriptResult<Self::Value>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@ -273,11 +286,22 @@ impl Parcelable<BotFunction> for KeyboardDefinition {
|
|||||||
impl ResolveValue for KeyboardDefinition {
|
impl ResolveValue for KeyboardDefinition {
|
||||||
type Value = Vec<<RowDefinition as ResolveValue>::Value>;
|
type Value = Vec<<RowDefinition as ResolveValue>::Value>;
|
||||||
|
|
||||||
fn resolve(self, runner: &Runner) -> ScriptResult<Self::Value> {
|
fn resolve(self) -> ScriptResult<Self::Value> {
|
||||||
match self {
|
match self {
|
||||||
KeyboardDefinition::Rows(rows) => rows.into_iter().map(|r| r.resolve(runner)).collect(),
|
KeyboardDefinition::Rows(rows) => rows.into_iter().map(|r| r.resolve()).collect(),
|
||||||
KeyboardDefinition::Function(f) => {
|
KeyboardDefinition::Function(f) => {
|
||||||
<Self as ResolveValue>::resolve(f.call_context(runner)?.js_into()?, runner)
|
<Self as ResolveValue>::resolve(f.call()?.js_into()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_with(self, runner: &Runner) -> ScriptResult<Self::Value> {
|
||||||
|
match self {
|
||||||
|
KeyboardDefinition::Rows(rows) => {
|
||||||
|
rows.into_iter().map(|r| r.resolve_with(runner)).collect()
|
||||||
|
}
|
||||||
|
KeyboardDefinition::Function(f) => {
|
||||||
|
<Self as ResolveValue>::resolve_with(f.call_context(runner)?.js_into()?, runner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -308,13 +332,21 @@ impl Parcelable<BotFunction> for RowDefinition {
|
|||||||
impl ResolveValue for RowDefinition {
|
impl ResolveValue for RowDefinition {
|
||||||
type Value = Vec<<ButtonDefinition as ResolveValue>::Value>;
|
type Value = Vec<<ButtonDefinition as ResolveValue>::Value>;
|
||||||
|
|
||||||
fn resolve(self, runner: &Runner) -> ScriptResult<Self::Value> {
|
fn resolve(self) -> ScriptResult<Self::Value> {
|
||||||
match self {
|
match self {
|
||||||
RowDefinition::Buttons(buttons) => {
|
RowDefinition::Buttons(buttons) => buttons.into_iter().map(|b| b.resolve()).collect(),
|
||||||
buttons.into_iter().map(|b| b.resolve(runner)).collect()
|
RowDefinition::Function(f) => <Self as ResolveValue>::resolve(f.call()?.js_into()?),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_with(self, runner: &Runner) -> ScriptResult<Self::Value> {
|
||||||
|
match self {
|
||||||
|
RowDefinition::Buttons(buttons) => buttons
|
||||||
|
.into_iter()
|
||||||
|
.map(|b| b.resolve_with(runner))
|
||||||
|
.collect(),
|
||||||
RowDefinition::Function(f) => {
|
RowDefinition::Function(f) => {
|
||||||
<Self as ResolveValue>::resolve(f.call_context(runner)?.js_into()?, runner)
|
<Self as ResolveValue>::resolve_with(f.call_context(runner)?.js_into()?, runner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -331,12 +363,20 @@ pub enum ButtonDefinition {
|
|||||||
impl ResolveValue for ButtonDefinition {
|
impl ResolveValue for ButtonDefinition {
|
||||||
type Value = ButtonRaw;
|
type Value = ButtonRaw;
|
||||||
|
|
||||||
fn resolve(self, runner: &Runner) -> ScriptResult<Self::Value> {
|
fn resolve(self) -> ScriptResult<Self::Value> {
|
||||||
|
match self {
|
||||||
|
ButtonDefinition::Button(button) => Ok(button),
|
||||||
|
ButtonDefinition::ButtonLiteral(l) => Ok(ButtonRaw::from_literal(l)),
|
||||||
|
ButtonDefinition::Function(f) => <Self as ResolveValue>::resolve(f.call()?.js_into()?),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn resolve_with(self, runner: &Runner) -> ScriptResult<Self::Value> {
|
||||||
match self {
|
match self {
|
||||||
ButtonDefinition::Button(button) => Ok(button),
|
ButtonDefinition::Button(button) => Ok(button),
|
||||||
ButtonDefinition::ButtonLiteral(l) => Ok(ButtonRaw::from_literal(l)),
|
ButtonDefinition::ButtonLiteral(l) => Ok(ButtonRaw::from_literal(l)),
|
||||||
ButtonDefinition::Function(f) => {
|
ButtonDefinition::Function(f) => {
|
||||||
<Self as ResolveValue>::resolve(f.call_context(runner)?.js_into()?, runner)
|
<Self as ResolveValue>::resolve_with(f.call_context(runner)?.js_into()?, runner)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -379,6 +419,21 @@ impl ButtonRaw {
|
|||||||
callback_name: literal,
|
callback_name: literal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> &ButtonName {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn callback_name(&self) -> &str {
|
||||||
|
&self.callback_name
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn literal(&self) -> Option<String> {
|
||||||
|
match self.name() {
|
||||||
|
ButtonName::Value { .. } => None,
|
||||||
|
ButtonName::Literal { literal } => Some(literal.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@ -388,6 +443,24 @@ pub enum ButtonName {
|
|||||||
Literal { literal: String },
|
Literal { literal: String },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ButtonName {
|
||||||
|
pub async fn resolve_name(self, db: &mut DB) -> ScriptResult<String> {
|
||||||
|
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 => Err(ResolveError::IncorrectLiteral(format!(
|
||||||
|
"not found literal `{literal}` in DB"
|
||||||
|
))),
|
||||||
|
}?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct Button {
|
pub struct Button {
|
||||||
name: String,
|
name: String,
|
||||||
@ -396,12 +469,74 @@ pub struct Button {
|
|||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct BotMessage {
|
pub struct BotMessage {
|
||||||
// buttons: Vec<Button>
|
// buttons: Vec<Button>
|
||||||
|
literal: Option<String>,
|
||||||
buttons: Option<KeyboardDefinition>,
|
buttons: Option<KeyboardDefinition>,
|
||||||
state: Option<String>,
|
state: Option<String>,
|
||||||
|
|
||||||
handler: Option<BotFunction>,
|
handler: Option<BotFunction>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl BotMessage {
|
||||||
|
pub fn fill_literal(&self, l: String) -> Self {
|
||||||
|
BotMessage {
|
||||||
|
literal: self.clone().literal.or(Some(l)),
|
||||||
|
..self.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BotMessage {
|
||||||
|
pub async fn resolve_buttons(
|
||||||
|
&self,
|
||||||
|
db: &mut DB,
|
||||||
|
) -> ScriptResult<Option<Vec<Vec<ButtonLayout>>>> {
|
||||||
|
let raw_buttons = self.buttons.clone().map(|b| b.resolve()).transpose()?;
|
||||||
|
match raw_buttons {
|
||||||
|
Some(braws) => {
|
||||||
|
let kbd: Vec<Vec<_>> = join_all(braws.into_iter().map(|rows| async {
|
||||||
|
join_all(rows.into_iter().map(|b| async {
|
||||||
|
let mut db = db.clone();
|
||||||
|
ButtonLayout::resolve_raw(b, &mut db).await
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.collect()
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
Ok(Some(kbd))
|
||||||
|
}
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn literal(&self) -> Option<&String> {
|
||||||
|
self.literal.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ButtonLayout {
|
||||||
|
Callback {
|
||||||
|
name: String,
|
||||||
|
literal: Option<String>,
|
||||||
|
callback: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ButtonLayout {
|
||||||
|
pub async fn resolve_raw(braw: ButtonRaw, db: &mut DB) -> ScriptResult<Self> {
|
||||||
|
let name = braw.name().clone().resolve_name(db).await?;
|
||||||
|
let literal = braw.literal();
|
||||||
|
let callback = braw.callback_name().to_string();
|
||||||
|
Ok(Self::Callback {
|
||||||
|
name,
|
||||||
|
literal,
|
||||||
|
callback,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Parcelable<BotFunction> for BotMessage {
|
impl Parcelable<BotFunction> for BotMessage {
|
||||||
fn get_field(&mut self, name: &str) -> ParcelableResult<ParcelType<BotFunction>> {
|
fn get_field(&mut self, name: &str) -> ParcelableResult<ParcelType<BotFunction>> {
|
||||||
match name {
|
match name {
|
||||||
@ -439,6 +574,15 @@ pub struct RunnerConfig {
|
|||||||
pub dialog: BotDialog,
|
pub dialog: BotDialog,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl RunnerConfig {
|
||||||
|
/// command without starting `/`
|
||||||
|
pub fn get_command_message(&self, command: &str) -> Option<BotMessage> {
|
||||||
|
let bm = self.dialog.commands.get(command).cloned();
|
||||||
|
|
||||||
|
bm.map(|bm| bm.fill_literal(command.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Parcelable<BotFunction> for RunnerConfig {
|
impl Parcelable<BotFunction> for RunnerConfig {
|
||||||
fn get_field(&mut self, name: &str) -> Result<ParcelType<BotFunction>, ParcelableError> {
|
fn get_field(&mut self, name: &str) -> Result<ParcelType<BotFunction>, ParcelableError> {
|
||||||
match name {
|
match name {
|
||||||
|
|||||||
43
src/main.rs
43
src/main.rs
@ -5,14 +5,13 @@ pub mod db;
|
|||||||
pub mod mongodb_storage;
|
pub mod mongodb_storage;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
use botscript::{BotMessage, Runner, RunnerConfig};
|
use botscript::{BotMessage, Runner, RunnerConfig, ScriptError};
|
||||||
use commands::BotCommand;
|
use commands::BotCommand;
|
||||||
use db::application::Application;
|
use db::application::Application;
|
||||||
use db::callback_info::CallbackInfo;
|
use db::callback_info::CallbackInfo;
|
||||||
use db::message_forward::MessageForward;
|
use db::message_forward::MessageForward;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use log::{error, info, warn};
|
use log::{error, info, warn};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -134,6 +133,7 @@ pub enum BotError {
|
|||||||
MsgTooOld(String),
|
MsgTooOld(String),
|
||||||
BotLogicError(String),
|
BotLogicError(String),
|
||||||
AdminMisconfiguration(String),
|
AdminMisconfiguration(String),
|
||||||
|
ScriptError(#[from] ScriptError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type BotResult<T> = Result<T, BotError>;
|
pub type BotResult<T> = Result<T, BotError>;
|
||||||
@ -169,8 +169,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
let cmdmap: std::sync::Arc<RwLock<HashMap<_, _>>> =
|
let rc: std::sync::Arc<RwLock<RunnerConfig>> = std::sync::Arc::new(RwLock::new(bc.rc));
|
||||||
std::sync::Arc::new(RwLock::new(bc.rc.dialog.commands));
|
|
||||||
|
|
||||||
let handler = dptree::entry()
|
let handler = dptree::entry()
|
||||||
.inspect(|u: Update| {
|
.inspect(|u: Update| {
|
||||||
@ -180,11 +179,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
Update::filter_message()
|
Update::filter_message()
|
||||||
.filter_map(|m: Message| m.text().and_then(|t| BotCommand::from_str(t).ok()))
|
.filter_map(|m: Message| m.text().and_then(|t| BotCommand::from_str(t).ok()))
|
||||||
.filter_map(move |bc: BotCommand| {
|
.filter_map(move |bc: BotCommand| {
|
||||||
let rc = std::sync::Arc::clone(&cmdmap);
|
let rc = std::sync::Arc::clone(&rc);
|
||||||
|
let command = bc.command();
|
||||||
|
|
||||||
let cmdmap = rc.read().expect("RwLock lock on commands map failed");
|
let rc = rc.read().expect("RwLock lock on commands map failed");
|
||||||
|
|
||||||
cmdmap.get(bc.command()).cloned()
|
rc.get_command_message(command)
|
||||||
})
|
})
|
||||||
.endpoint(botscript_command_handler),
|
.endpoint(botscript_command_handler),
|
||||||
)
|
)
|
||||||
@ -255,8 +255,35 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn botscript_command_handler(bot: Bot, bm: BotMessage) -> BotResult<()> {
|
async fn botscript_command_handler(
|
||||||
|
bot: Bot,
|
||||||
|
mut db: DB,
|
||||||
|
bm: BotMessage,
|
||||||
|
msg: Message,
|
||||||
|
) -> BotResult<()> {
|
||||||
info!("Eval BM: {:?}", bm);
|
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());
|
||||||
|
|
||||||
|
answer_message_varianted(&bot, msg.chat.id.0, &mut db, literal, None, buttons).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user