Compare commits
10 Commits
e0c00d68f9
...
2a4ed51824
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a4ed51824 | ||
|
|
d1b25b52c1 | ||
|
|
2c5802eaeb | ||
|
|
bd800e88eb | ||
|
|
a2e1354bee | ||
|
|
55d53bd140 | ||
|
|
ea007127ff | ||
|
|
0a60b0469f | ||
|
|
40eec7d38d | ||
|
|
2ccfc19a6c |
@ -1,5 +1,8 @@
|
|||||||
name: Build && Deploy
|
name: Build && Deploy
|
||||||
on: [push]
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
|||||||
@ -7,6 +7,9 @@ const dialog = {
|
|||||||
state: "start"
|
state: "start"
|
||||||
},
|
},
|
||||||
cancel: {
|
cancel: {
|
||||||
|
buttons: [
|
||||||
|
[{name: {name: "Def"}, callback_name: "defcall"}]
|
||||||
|
],
|
||||||
state: "none"
|
state: "none"
|
||||||
},
|
},
|
||||||
somecomplicatedcmd: {}
|
somecomplicatedcmd: {}
|
||||||
|
|||||||
174
src/botscript.rs
174
src/botscript.rs
@ -1,10 +1,14 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
use quickjs_rusty::serde::from_js;
|
use quickjs_rusty::serde::from_js;
|
||||||
|
use quickjs_rusty::utils::create_null;
|
||||||
use quickjs_rusty::Context;
|
use quickjs_rusty::Context;
|
||||||
use quickjs_rusty::ContextError;
|
use quickjs_rusty::ContextError;
|
||||||
use quickjs_rusty::ExecutionError;
|
use quickjs_rusty::ExecutionError;
|
||||||
|
use quickjs_rusty::JsFunction;
|
||||||
use quickjs_rusty::OwnedJsValue as JsValue;
|
use quickjs_rusty::OwnedJsValue as JsValue;
|
||||||
|
use quickjs_rusty::ValueError;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
@ -16,6 +20,8 @@ pub enum ScriptError {
|
|||||||
ExecutionError(#[from] ExecutionError),
|
ExecutionError(#[from] ExecutionError),
|
||||||
#[error("error from anyhow: {0:?}")]
|
#[error("error from anyhow: {0:?}")]
|
||||||
SerdeError(#[from] quickjs_rusty::serde::Error),
|
SerdeError(#[from] quickjs_rusty::serde::Error),
|
||||||
|
#[error("error value: {0:?}")]
|
||||||
|
ValueError(#[from] ValueError),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ScriptResult<T> = Result<T, ScriptError>;
|
pub type ScriptResult<T> = Result<T, ScriptError>;
|
||||||
@ -37,12 +43,80 @@ pub trait DeserializeJS {
|
|||||||
|
|
||||||
impl DeserializeJS for JsValue {
|
impl DeserializeJS for JsValue {
|
||||||
fn js_into<'a, T: Deserialize<'a>>(&'a self) -> ScriptResult<T> {
|
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)
|
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
|
// TODO: remove this function since it is suitable only for early development
|
||||||
#[allow(clippy::print_stdout)]
|
#[allow(clippy::print_stdout)]
|
||||||
fn print(s: String) {
|
fn print(s: String) {
|
||||||
@ -54,7 +128,99 @@ pub struct BotConfig {
|
|||||||
version: f64,
|
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 {
|
pub struct Button {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
@ -62,7 +228,7 @@ 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>
|
||||||
buttons: Option<BotFunction>,
|
buttons: Option<KeyboardDefinition>,
|
||||||
state: Option<String>,
|
state: Option<String>,
|
||||||
|
|
||||||
handler: Option<BotFunction>,
|
handler: Option<BotFunction>,
|
||||||
@ -145,6 +311,8 @@ mod tests {
|
|||||||
let runner = Runner::init().unwrap();
|
let runner = Runner::init().unwrap();
|
||||||
let val = runner.run_script(include_str!("../mainbot.js")).unwrap();
|
let val = runner.run_script(include_str!("../mainbot.js")).unwrap();
|
||||||
println!("config: {:?}", val);
|
println!("config: {:?}", val);
|
||||||
|
let d: RunnerConfig = DeserializerJS::deserialize_js(&val).unwrap();
|
||||||
|
println!("desr rc: {:?}", d);
|
||||||
let val = runner.run_script("start_buttons()").unwrap();
|
let val = runner.run_script("start_buttons()").unwrap();
|
||||||
println!("Val: {:?}", val.to_string());
|
println!("Val: {:?}", val.to_string());
|
||||||
}
|
}
|
||||||
|
|||||||
18
src/main.rs
18
src/main.rs
@ -6,11 +6,15 @@ pub mod mongodb_storage;
|
|||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
use botscript::{BotMessage, Runner, RunnerConfig};
|
use botscript::{BotMessage, Runner, RunnerConfig};
|
||||||
|
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::sync::RwLock;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use teloxide::sugar::request::RequestReplyExt;
|
use teloxide::sugar::request::RequestReplyExt;
|
||||||
use utils::create_callback_button;
|
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()
|
let handler = dptree::entry()
|
||||||
.inspect(|u: Update| {
|
.inspect(|u: Update| {
|
||||||
info!("{u:#?}"); // Print the update to the console with inspect
|
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(
|
.branch(
|
||||||
Update::filter_callback_query()
|
Update::filter_callback_query()
|
||||||
.filter_async(async |q: CallbackQuery, mut db: DB| {
|
.filter_async(async |q: CallbackQuery, mut db: DB| {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user