use std::collections::HashMap; use crate::db::{CallDB, DbError, DB}; use crate::utils::parcelable::{ParcelType, Parcelable, ParcelableError, ParcelableResult}; use futures::future::join_all; 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 serde::Deserialize; use serde::Serialize; #[derive(thiserror::Error, Debug)] pub enum ScriptError { #[error("error context: {0:?}")] ContextError(#[from] ContextError), #[error("error running: {0:?}")] ExecutionError(#[from] ExecutionError), #[error("error from anyhow: {0:?}")] SerdeError(#[from] quickjs_rusty::serde::Error), #[error("error value: {0:?}")] ValueError(#[from] ValueError), #[error("error bot function execution: {0:?}")] 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 = Result; #[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 for BotFunction { fn get_field( &mut self, _name: &str, ) -> crate::utils::parcelable::ParcelableResult> { todo!() } fn resolve(&mut self) -> ParcelableResult> where Self: Sized + 'static, { Ok(ParcelType::Function(self)) } } impl BotFunction { pub fn by_name(name: String) -> Self { Self { func: FunctionMarker::StrTemplate(name), } } pub fn call_context(&self, runner: &Runner) -> ScriptResult { match &self.func { FunctionMarker::Function(f) => { let val = f.call(Default::default())?; Ok(val) } FunctionMarker::StrTemplate(func_name) => runner.run_script(&format!("{func_name}()")), } } pub fn call(&self) -> ScriptResult { self.call_args(Default::default()) } pub fn call_args(&self, args: Vec) -> ScriptResult { 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); } } pub trait DeserializeJS { fn js_into<'a, T: Deserialize<'a>>(&'a self) -> ScriptResult; } impl DeserializeJS for JsValue { fn js_into<'a, T: Deserialize<'a>>(&'a self) -> ScriptResult { let rc = from_js(self.context(), self)?; Ok(rc) } } #[derive(Default)] pub struct DeserializerJS { fn_map: HashMap, } impl DeserializerJS { pub fn new() -> Self { Self { fn_map: HashMap::new(), } } pub fn deserialize_js<'a, T: Deserialize<'a> + Parcelable + 'static>( value: &'a JsValue, ) -> ScriptResult { let mut s = Self::new(); s.inject_templates(value, "".to_string())?; let mut res = value.js_into()?; for (k, jsf) in s.fn_map { let item: ParcelType<'_, BotFunction> = match Parcelable::::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); } } Ok(res) } pub fn inject_templates( &mut self, value: &JsValue, path: String, ) -> ScriptResult> { 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(_) => { 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()) } 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) { println!("{s}"); } #[derive(Serialize, Deserialize, Debug)] pub struct BotConfig { version: f64, } pub trait ResolveValue { type Value; fn resolve(self, runner: &Runner) -> ScriptResult; } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum KeyboardDefinition { Rows(Vec), Function(BotFunction), } impl Parcelable for KeyboardDefinition { fn get_field(&mut self, _name: &str) -> ParcelableResult> { todo!() } fn resolve(&mut self) -> ParcelableResult> 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<::Value>; fn resolve(self, runner: &Runner) -> ScriptResult { match self { KeyboardDefinition::Rows(rows) => rows.into_iter().map(|r| r.resolve(runner)).collect(), KeyboardDefinition::Function(f) => { ::resolve(f.call_context(runner)?.js_into()?, runner) } } } } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum RowDefinition { Buttons(Vec), Function(BotFunction), } impl Parcelable for RowDefinition { fn get_field(&mut self, _name: &str) -> ParcelableResult> { todo!() } fn resolve(&mut self) -> ParcelableResult> 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<::Value>; fn resolve(self, runner: &Runner) -> ScriptResult { match self { RowDefinition::Buttons(buttons) => { buttons.into_iter().map(|b| b.resolve(runner)).collect() } RowDefinition::Function(f) => { ::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 { match self { ButtonDefinition::Button(button) => Ok(button), ButtonDefinition::ButtonLiteral(l) => Ok(ButtonRaw::from_literal(l)), ButtonDefinition::Function(f) => { ::resolve(f.call_context(runner)?.js_into()?, runner) } } } } impl Parcelable for ButtonDefinition { fn get_field(&mut self, _name: &str) -> ParcelableResult> { todo!() } fn resolve(&mut self) -> ParcelableResult> 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 Parcelable for ButtonRaw { fn get_field(&mut self, _name: &str) -> ParcelableResult> { todo!() } } impl ButtonRaw { pub fn from_literal(literal: String) -> Self { ButtonRaw { name: ButtonName::Literal { literal: literal.clone(), }, callback_name: literal, } } pub fn name(&self) -> &ButtonName { &self.name } pub fn callback_name(&self) -> &str { &self.callback_name } pub fn literal(&self) -> Option { match self.name() { ButtonName::Value { .. } => None, ButtonName::Literal { literal } => Some(literal.to_string()), } } } #[derive(Serialize, Deserialize, Debug, Clone)] #[serde(untagged)] pub enum ButtonName { Value { name: String }, Literal { literal: String }, } impl ButtonName { pub async fn resolve_name(self, db: &mut DB) -> ScriptResult { 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)] pub struct Button { name: String, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct BotMessage { // buttons: Vec