Compare commits

...

10 Commits

4 changed files with 196 additions and 4 deletions

View File

@ -1,5 +1,8 @@
name: Build && Deploy
on: [push]
on:
push:
branches:
- main
jobs:
build:

View File

@ -7,6 +7,9 @@ const dialog = {
state: "start"
},
cancel: {
buttons: [
[{name: {name: "Def"}, callback_name: "defcall"}]
],
state: "none"
},
somecomplicatedcmd: {}

View File

@ -1,10 +1,14 @@
use std::collections::HashMap;
use itertools::Itertools;
use quickjs_rusty::serde::from_js;
use quickjs_rusty::utils::create_null;
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 serde::Deserialize;
use serde::Serialize;
@ -16,6 +20,8 @@ pub enum ScriptError {
ExecutionError(#[from] ExecutionError),
#[error("error from anyhow: {0:?}")]
SerdeError(#[from] quickjs_rusty::serde::Error),
#[error("error value: {0:?}")]
ValueError(#[from] ValueError),
}
pub type ScriptResult<T> = Result<T, ScriptError>;
@ -37,12 +43,80 @@ pub trait DeserializeJS {
impl DeserializeJS for JsValue {
fn js_into<'a, T: Deserialize<'a>>(&'a self) -> ScriptResult<T> {
let rc = from_js(self.context(), &self)?;
let rc = from_js(self.context(), self)?;
Ok(rc)
}
}
#[derive(Default)]
pub struct DeserializerJS {
fn_map: HashMap<String, JsFunction>,
}
impl DeserializerJS {
pub fn new() -> Self {
Self {
fn_map: HashMap::new(),
}
}
pub fn deserialize_js<'a, T: Deserialize<'a>>(value: &'a JsValue) -> ScriptResult<T> {
let mut s = Self::new();
s.inject_templates(value, "".to_string())?;
let res = value.js_into()?;
// val.map_functions(s.fn_map);
Ok(res)
}
pub fn inject_templates(
&mut self,
value: &JsValue,
path: String,
) -> ScriptResult<Option<String>> {
if let Ok(f) = value.clone().try_into_function() {
self.fn_map.insert(path.clone(), f);
return Ok(Some(path));
} else if let Ok(o) = value.clone().try_into_object() {
let path = if path.is_empty() { path } else { path + "." }; // trying to avoid . in the start
// of stringified path
let res = o
.properties_iter()?
.chunks(2)
.into_iter()
// since chunks(2) is used and properties iterator over object
// always has even elements, unwrap will not fail
.map(
#[allow(clippy::unwrap_used)]
|mut chunk| (chunk.next().unwrap(), chunk.next().unwrap()),
)
.map(|(k, p)| k.and_then(|k| p.map(|p| (k, p))))
.filter_map(|m| m.ok())
.try_for_each(|(k, p)| {
let k = match k.to_string() {
Ok(k) => k,
Err(err) => return Err(ScriptError::ValueError(err)),
};
let res = match self.inject_templates(&p, path.clone() + &k)? {
Some(_) => o.set_property(&k, JsValue::new(o.context(), create_null())),
None => Ok(()),
};
match res {
Ok(res) => Ok(res),
Err(err) => Err(ScriptError::ExecutionError(err)),
}
});
res?;
};
Ok(None)
}
}
// TODO: remove this function since it is suitable only for early development
#[allow(clippy::print_stdout)]
fn print(s: String) {
@ -54,7 +128,99 @@ pub struct BotConfig {
version: f64,
}
#[derive(Serialize, Deserialize, Debug)]
pub trait ResolveValue {
type Value;
fn resolve(self, runner: &Runner) -> ScriptResult<Self::Value>;
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum KeyboardDefinition {
Rows(Vec<RowDefinition>),
Function(BotFunction),
}
impl ResolveValue for KeyboardDefinition {
type Value = Vec<<RowDefinition as ResolveValue>::Value>;
fn resolve(self, runner: &Runner) -> ScriptResult<Self::Value> {
match self {
KeyboardDefinition::Rows(rows) => rows.into_iter().map(|r| r.resolve(runner)).collect(),
KeyboardDefinition::Function(f) => {
Self::resolve(f.call_context(runner)?.js_into()?, runner)
}
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum RowDefinition {
Buttons(Vec<ButtonDefinition>),
Function(BotFunction),
}
impl ResolveValue for RowDefinition {
type Value = Vec<<ButtonDefinition as ResolveValue>::Value>;
fn resolve(self, runner: &Runner) -> ScriptResult<Self::Value> {
match self {
RowDefinition::Buttons(buttons) => {
buttons.into_iter().map(|b| b.resolve(runner)).collect()
}
RowDefinition::Function(f) => Self::resolve(f.call_context(runner)?.js_into()?, runner),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum ButtonDefinition {
Button(ButtonRaw),
ButtonLiteral(String),
Function(BotFunction),
}
impl ResolveValue for ButtonDefinition {
type Value = ButtonRaw;
fn resolve(self, runner: &Runner) -> ScriptResult<Self::Value> {
match self {
ButtonDefinition::Button(button) => Ok(button),
ButtonDefinition::ButtonLiteral(l) => Ok(ButtonRaw::from_literal(l)),
ButtonDefinition::Function(f) => {
Self::resolve(f.call_context(runner)?.js_into()?, runner)
}
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ButtonRaw {
name: ButtonName,
callback_name: String,
}
impl ButtonRaw {
pub fn from_literal(literal: String) -> Self {
ButtonRaw {
name: ButtonName::Literal {
literal: literal.clone(),
},
callback_name: literal,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum ButtonName {
Value { name: String },
Literal { literal: String },
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Button {
name: String,
}
@ -62,7 +228,7 @@ pub struct Button {
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct BotMessage {
// buttons: Vec<Button>
buttons: Option<BotFunction>,
buttons: Option<KeyboardDefinition>,
state: Option<String>,
handler: Option<BotFunction>,
@ -145,6 +311,8 @@ mod tests {
let runner = Runner::init().unwrap();
let val = runner.run_script(include_str!("../mainbot.js")).unwrap();
println!("config: {:?}", val);
let d: RunnerConfig = DeserializerJS::deserialize_js(&val).unwrap();
println!("desr rc: {:?}", d);
let val = runner.run_script("start_buttons()").unwrap();
println!("Val: {:?}", val.to_string());
}

View File

@ -6,11 +6,15 @@ pub mod mongodb_storage;
pub mod utils;
use botscript::{BotMessage, Runner, RunnerConfig};
use commands::BotCommand;
use db::application::Application;
use db::callback_info::CallbackInfo;
use db::message_forward::MessageForward;
use itertools::Itertools;
use log::{error, info, warn};
use std::collections::HashMap;
use std::str::FromStr;
use std::sync::RwLock;
use std::time::Duration;
use teloxide::sugar::request::RequestReplyExt;
use utils::create_callback_button;
@ -158,11 +162,25 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}
//
let cmdmap: std::sync::Arc<RwLock<HashMap<_, _>>> =
std::sync::Arc::new(RwLock::new(bc.rc.dialog.commands));
let handler = dptree::entry()
.inspect(|u: Update| {
info!("{u:#?}"); // Print the update to the console with inspect
})
.branch(
Update::filter_message()
.filter_map(|m: Message| m.text().and_then(|t| BotCommand::from_str(t).ok()))
.filter_map(move |bc: BotCommand| {
let rc = std::sync::Arc::clone(&cmdmap);
let cmdmap = rc.read().expect("RwLock lock on commands map failed");
cmdmap.get(bc.command()).cloned()
})
.endpoint(botscript_command_handler),
)
.branch(
Update::filter_callback_query()
.filter_async(async |q: CallbackQuery, mut db: DB| {