Compare commits
No commits in common. "d1b6d153d4ed57b0ff9a3df1f4f5ac76c6119d06" and "2a4ed51824f6fc29d00c42320748c382e25cc780" have entirely different histories.
d1b6d153d4
...
2a4ed51824
11
mainbot.js
11
mainbot.js
@ -3,7 +3,7 @@
|
||||
const dialog = {
|
||||
commands: {
|
||||
start: {
|
||||
buttons: start_buttons, // default is `null`
|
||||
buttons: "start_buttons", // default is `null`
|
||||
state: "start"
|
||||
},
|
||||
cancel: {
|
||||
@ -20,17 +20,14 @@ const dialog = {
|
||||
stateful_msg_handlers: {
|
||||
start: {}, // everything is by default, so just send message `start`
|
||||
enter_name: {
|
||||
// name of the handler function. This field has a side effect:
|
||||
// when is set, no automatic sending of message, should be sent
|
||||
// manually in handler
|
||||
handler: enter_name,
|
||||
handler: "enter_name", // name of the handler function. This field has a
|
||||
// side effect: when is set, no automatic sending
|
||||
// of message, should be sent manually in handler
|
||||
state: "none"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function enter_name() {}
|
||||
|
||||
const fmt = (number) => number.toString().padStart(2, '0');
|
||||
|
||||
const formatDate = (date) => {
|
||||
|
||||
228
src/botscript.rs
228
src/botscript.rs
@ -1,10 +1,8 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::utils::parcelable::{ParcelType, Parcelable, ParcelableError, ParcelableResult};
|
||||
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::utils::create_null;
|
||||
use quickjs_rusty::Context;
|
||||
use quickjs_rusty::ContextError;
|
||||
use quickjs_rusty::ExecutionError;
|
||||
@ -24,99 +22,18 @@ pub enum ScriptError {
|
||||
SerdeError(#[from] quickjs_rusty::serde::Error),
|
||||
#[error("error value: {0:?}")]
|
||||
ValueError(#[from] ValueError),
|
||||
#[error("error bot function execution: {0:?}")]
|
||||
BotFunctionError(String),
|
||||
}
|
||||
|
||||
pub type ScriptResult<T> = Result<T, ScriptError>;
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct BotFunction {
|
||||
func: FunctionMarker,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
#[serde(untagged)]
|
||||
pub enum FunctionMarker {
|
||||
/// serde is not able to (de)serialize this, so ignore it and fill
|
||||
/// in runtime with injection in DeserializeJS
|
||||
#[serde(skip)]
|
||||
Function(JsFunction),
|
||||
StrTemplate(String),
|
||||
}
|
||||
|
||||
impl FunctionMarker {
|
||||
pub fn as_str_template(&self) -> Option<&String> {
|
||||
if let Self::StrTemplate(v) = self {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_function(&self) -> Option<&JsFunction> {
|
||||
if let Self::Function(v) = self {
|
||||
Some(v)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_js_function(&mut self, f: JsFunction) {
|
||||
*self = Self::Function(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Parcelable<Self> for BotFunction {
|
||||
fn get_field(
|
||||
&mut self,
|
||||
_name: &str,
|
||||
) -> crate::utils::parcelable::ParcelableResult<ParcelType<Self>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn resolve(&mut self) -> ParcelableResult<ParcelType<Self>>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
Ok(ParcelType::Function(self))
|
||||
}
|
||||
}
|
||||
pub struct BotFunction(String); // temporal workaround
|
||||
|
||||
impl BotFunction {
|
||||
pub fn by_name(name: String) -> Self {
|
||||
Self {
|
||||
func: FunctionMarker::StrTemplate(name),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_context(&self, runner: &Runner) -> ScriptResult<JsValue> {
|
||||
match &self.func {
|
||||
FunctionMarker::Function(f) => {
|
||||
let val = f.call(Default::default())?;
|
||||
Ok(val)
|
||||
}
|
||||
FunctionMarker::StrTemplate(func_name) => runner.run_script(&format!("{func_name}()")),
|
||||
}
|
||||
}
|
||||
let func_name = &self.0;
|
||||
|
||||
pub fn call(&self) -> ScriptResult<JsValue> {
|
||||
self.call_args(Default::default())
|
||||
}
|
||||
|
||||
pub fn call_args(&self, args: Vec<JsValue>) -> ScriptResult<JsValue> {
|
||||
if let FunctionMarker::Function(f) = &self.func {
|
||||
let val = f.call(args)?;
|
||||
Ok(val)
|
||||
} else {
|
||||
Err(ScriptError::BotFunctionError(
|
||||
"Js Function is not defined".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_js_function(&mut self, f: JsFunction) {
|
||||
self.func.set_js_function(f);
|
||||
runner.run_script(&format!("{func_name}()"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -144,28 +61,14 @@ impl DeserializerJS {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deserialize_js<'a, T: Deserialize<'a> + Parcelable<BotFunction> + 'static>(
|
||||
value: &'a JsValue,
|
||||
) -> ScriptResult<T> {
|
||||
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 mut res = value.js_into()?;
|
||||
let res = value.js_into()?;
|
||||
|
||||
for (k, jsf) in s.fn_map {
|
||||
let item: ParcelType<'_, BotFunction> =
|
||||
match Parcelable::<BotFunction>::get_nested(&mut res, &k) {
|
||||
Ok(item) => item,
|
||||
Err(err) => {
|
||||
log::error!("Failed to inject original functions to structs, error: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let ParcelType::Function(f) = item {
|
||||
f.set_js_function(jsf);
|
||||
}
|
||||
}
|
||||
// val.map_functions(s.fn_map);
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
@ -199,24 +102,7 @@ impl DeserializerJS {
|
||||
Err(err) => return Err(ScriptError::ValueError(err)),
|
||||
};
|
||||
let res = match self.inject_templates(&p, path.clone() + &k)? {
|
||||
Some(_) => {
|
||||
let fo = JsValue::new(
|
||||
o.context(),
|
||||
create_empty_object(o.context()).expect("couldn't create object"),
|
||||
)
|
||||
.try_into_object()
|
||||
.expect("the object created was not an object :/");
|
||||
fo.set_property(
|
||||
"func",
|
||||
JsValue::new(
|
||||
o.context(),
|
||||
create_string(o.context(), "somefunc")
|
||||
.expect("couldn't create string"),
|
||||
),
|
||||
)
|
||||
.expect("wasn't able to set property on object :/");
|
||||
o.set_property(&k, fo.into_value())
|
||||
}
|
||||
Some(_) => o.set_property(&k, JsValue::new(o.context(), create_null())),
|
||||
None => Ok(()),
|
||||
};
|
||||
match res {
|
||||
@ -255,21 +141,6 @@ pub enum KeyboardDefinition {
|
||||
Function(BotFunction),
|
||||
}
|
||||
|
||||
impl Parcelable<BotFunction> for KeyboardDefinition {
|
||||
fn get_field(&mut self, _name: &str) -> ParcelableResult<ParcelType<BotFunction>> {
|
||||
todo!()
|
||||
}
|
||||
fn resolve(&mut self) -> ParcelableResult<ParcelType<BotFunction>>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
match self {
|
||||
KeyboardDefinition::Rows(rows) => Ok(rows.resolve()?),
|
||||
KeyboardDefinition::Function(f) => Ok(f.resolve()?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolveValue for KeyboardDefinition {
|
||||
type Value = Vec<<RowDefinition as ResolveValue>::Value>;
|
||||
|
||||
@ -277,7 +148,7 @@ impl ResolveValue for KeyboardDefinition {
|
||||
match self {
|
||||
KeyboardDefinition::Rows(rows) => rows.into_iter().map(|r| r.resolve(runner)).collect(),
|
||||
KeyboardDefinition::Function(f) => {
|
||||
<Self as ResolveValue>::resolve(f.call_context(runner)?.js_into()?, runner)
|
||||
Self::resolve(f.call_context(runner)?.js_into()?, runner)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -290,21 +161,6 @@ pub enum RowDefinition {
|
||||
Function(BotFunction),
|
||||
}
|
||||
|
||||
impl Parcelable<BotFunction> for RowDefinition {
|
||||
fn get_field(&mut self, _name: &str) -> ParcelableResult<ParcelType<BotFunction>> {
|
||||
todo!()
|
||||
}
|
||||
fn resolve(&mut self) -> ParcelableResult<ParcelType<BotFunction>>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
match self {
|
||||
Self::Buttons(buttons) => Ok(buttons.resolve()?),
|
||||
Self::Function(f) => Ok(f.resolve()?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ResolveValue for RowDefinition {
|
||||
type Value = Vec<<ButtonDefinition as ResolveValue>::Value>;
|
||||
|
||||
@ -313,9 +169,7 @@ impl ResolveValue for RowDefinition {
|
||||
RowDefinition::Buttons(buttons) => {
|
||||
buttons.into_iter().map(|b| b.resolve(runner)).collect()
|
||||
}
|
||||
RowDefinition::Function(f) => {
|
||||
<Self as ResolveValue>::resolve(f.call_context(runner)?.js_into()?, runner)
|
||||
}
|
||||
RowDefinition::Function(f) => Self::resolve(f.call_context(runner)?.js_into()?, runner),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -336,40 +190,18 @@ impl ResolveValue for ButtonDefinition {
|
||||
ButtonDefinition::Button(button) => Ok(button),
|
||||
ButtonDefinition::ButtonLiteral(l) => Ok(ButtonRaw::from_literal(l)),
|
||||
ButtonDefinition::Function(f) => {
|
||||
<Self as ResolveValue>::resolve(f.call_context(runner)?.js_into()?, runner)
|
||||
Self::resolve(f.call_context(runner)?.js_into()?, runner)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Parcelable<BotFunction> for ButtonDefinition {
|
||||
fn get_field(&mut self, _name: &str) -> ParcelableResult<ParcelType<BotFunction>> {
|
||||
todo!()
|
||||
}
|
||||
fn resolve(&mut self) -> ParcelableResult<ParcelType<BotFunction>>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
match self {
|
||||
Self::Button(braw) => Ok(braw.resolve()?),
|
||||
Self::ButtonLiteral(s) => Ok(s.resolve()?),
|
||||
Self::Function(f) => Ok(f.resolve()?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct ButtonRaw {
|
||||
name: ButtonName,
|
||||
callback_name: String,
|
||||
}
|
||||
|
||||
impl<F> Parcelable<F> for ButtonRaw {
|
||||
fn get_field(&mut self, _name: &str) -> ParcelableResult<ParcelType<F>> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ButtonRaw {
|
||||
pub fn from_literal(literal: String) -> Self {
|
||||
ButtonRaw {
|
||||
@ -402,54 +234,18 @@ pub struct BotMessage {
|
||||
handler: Option<BotFunction>,
|
||||
}
|
||||
|
||||
impl Parcelable<BotFunction> for BotMessage {
|
||||
fn get_field(&mut self, name: &str) -> ParcelableResult<ParcelType<BotFunction>> {
|
||||
match name {
|
||||
"buttons" => Ok(self.buttons.resolve()?),
|
||||
"state" => Ok(self.state.resolve()?),
|
||||
"handler" => Ok(self.handler.resolve()?),
|
||||
field => Err(ParcelableError::FieldError(format!(
|
||||
"tried to get field {field}, but this field does not exists or private"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct BotDialog {
|
||||
pub commands: HashMap<String, BotMessage>,
|
||||
stateful_msg_handlers: HashMap<String, BotMessage>,
|
||||
}
|
||||
|
||||
impl Parcelable<BotFunction> for BotDialog {
|
||||
fn get_field(&mut self, name: &str) -> Result<ParcelType<BotFunction>, ParcelableError> {
|
||||
match name {
|
||||
"commands" => Ok(ParcelType::Parcelable(&mut self.commands)),
|
||||
"stateful_msg_handlers" => Ok(ParcelType::Parcelable(&mut self.stateful_msg_handlers)),
|
||||
field => Err(ParcelableError::FieldError(format!(
|
||||
"tried to get field {field}, but this field does not exists or private"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct RunnerConfig {
|
||||
config: BotConfig,
|
||||
pub dialog: BotDialog,
|
||||
}
|
||||
|
||||
impl Parcelable<BotFunction> for RunnerConfig {
|
||||
fn get_field(&mut self, name: &str) -> Result<ParcelType<BotFunction>, ParcelableError> {
|
||||
match name {
|
||||
"dialog" => Ok(ParcelType::Parcelable(&mut self.dialog)),
|
||||
field => Err(ParcelableError::FieldError(format!(
|
||||
"tried to get field {field}, but this field does not exists or private"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Runner {
|
||||
context: Context,
|
||||
}
|
||||
@ -538,7 +334,7 @@ mod tests {
|
||||
.run_script("function cancel_buttons() {return 'cancelation'}")
|
||||
.unwrap();
|
||||
|
||||
let f = BotFunction::by_name("cancel_buttons".to_string());
|
||||
let f = BotFunction("cancel_buttons".to_string());
|
||||
let res = f.call_context(&runner).unwrap();
|
||||
|
||||
println!("RES: {res:?}");
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
pub mod parcelable;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use teloxide::types::InlineKeyboardButton;
|
||||
|
||||
|
||||
@ -1,110 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub enum ParcelType<'a, F> {
|
||||
Function(&'a mut F),
|
||||
Parcelable(&'a mut dyn Parcelable<F>),
|
||||
Other(()),
|
||||
}
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum ParcelableError {
|
||||
#[error("error to get field: {0:?}")]
|
||||
FieldError(String),
|
||||
#[error("error when addressing nested element: {0:?}")]
|
||||
NestError(String),
|
||||
#[error("error to resolve Parcelable: {0:?}")]
|
||||
ResolveError(String),
|
||||
}
|
||||
|
||||
pub type ParcelableResult<T> = Result<T, ParcelableError>;
|
||||
|
||||
pub trait Parcelable<F> {
|
||||
fn get_field(&mut self, name: &str) -> ParcelableResult<ParcelType<F>>;
|
||||
|
||||
fn resolve(&mut self) -> ParcelableResult<ParcelType<F>>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
let root = ParcelableResult::Ok(ParcelType::Parcelable(self));
|
||||
root
|
||||
}
|
||||
|
||||
/// Get nested field by name, which is fields joined by dot
|
||||
/// for example: passing name "field1.somefield" will be the same
|
||||
/// as using `struct.field1.somefield`, by dynamically
|
||||
fn get_nested(&mut self, name: &str) -> ParcelableResult<ParcelType<F>>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
let root = ParcelableResult::Ok(ParcelType::Parcelable(self));
|
||||
name.split('.')
|
||||
.fold(root, |s: ParcelableResult<ParcelType<F>>, field| match s? {
|
||||
ParcelType::Parcelable(p) => p.get_field(field),
|
||||
_ => Err(ParcelableError::NestError(format!(
|
||||
"Failed to get field {field}. End of nestment"
|
||||
))),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> Parcelable<F> for String {
|
||||
fn get_field(&mut self, _name: &str) -> ParcelableResult<ParcelType<F>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn resolve(&mut self) -> ParcelableResult<ParcelType<F>>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
Ok(ParcelType::Other(()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T: Parcelable<F>> Parcelable<F> for Option<T> {
|
||||
fn get_field(&mut self, name: &str) -> ParcelableResult<ParcelType<F>> {
|
||||
Err(ParcelableError::FieldError(format!(
|
||||
"tried to get field {name}, but calls of get_field are not allowed on Option"
|
||||
)))
|
||||
}
|
||||
|
||||
fn resolve(&mut self) -> crate::utils::parcelable::ParcelableResult<ParcelType<F>>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
match self {
|
||||
Some(v) => Ok(v.resolve()?),
|
||||
None => Err(ParcelableError::ResolveError("Option was None".to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, V: Parcelable<F> + 'static> Parcelable<F> for HashMap<String, V> {
|
||||
fn get_field(&mut self, name: &str) -> ParcelableResult<ParcelType<F>> {
|
||||
match self.get_mut(name) {
|
||||
Some(v) => Ok(Parcelable::resolve(v)?),
|
||||
None => Err(ParcelableError::FieldError(format!(
|
||||
"tried to get value by key {name}, but this key does not exists"
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, T: Parcelable<F> + 'static> Parcelable<F> for Vec<T> {
|
||||
fn get_field(&mut self, name: &str) -> ParcelableResult<ParcelType<F>> {
|
||||
let index: usize = match name.parse() {
|
||||
Ok(index) => index,
|
||||
Err(err) => {
|
||||
return Err(ParcelableError::FieldError(format!(
|
||||
"Failed to parse field name `{name}` as an array index, err: {err}"
|
||||
)))
|
||||
}
|
||||
};
|
||||
let veclen = self.len();
|
||||
let value = match self.get_mut(index) {
|
||||
Some(value) => value,
|
||||
None => return Err(ParcelableError::FieldError(format!("Failed to get vec element with index {index}, probably out of bound (vec len: {veclen})"))),
|
||||
};
|
||||
|
||||
Parcelable::resolve(value)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user