From d39d2c81440a954cfa5e9b89dfef3a32f4608687 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 10 May 2025 03:25:53 +0300 Subject: [PATCH 001/131] cargo add quickjs-rusty --- Cargo.lock | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + 2 files changed, 182 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index d244f23..561ed82 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,6 +60,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + [[package]] name = "aquamarine" version = "0.6.0" @@ -133,6 +139,26 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags 2.9.0", + "cexpr", + "clang-sys", + "itertools 0.10.5", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.100", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -220,9 +246,20 @@ version = "1.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" dependencies = [ + "jobserver", + "libc", "shlex", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -265,6 +302,17 @@ dependencies = [ "phf_codegen", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -309,6 +357,15 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "copy_dir" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3" +dependencies = [ + "walkdir", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -840,6 +897,12 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "glob" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" + [[package]] name = "gongbotrs" version = "0.1.0" @@ -856,6 +919,7 @@ dependencies = [ "log", "mongodb", "pretty_env_logger", + "quickjs-rusty", "serde", "teloxide", "thiserror 2.0.12", @@ -1352,6 +1416,16 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +[[package]] +name = "jobserver" +version = "0.1.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" +dependencies = [ + "getrandom 0.3.2", + "libc", +] + [[package]] name = "js-sys" version = "0.3.77" @@ -1368,6 +1442,27 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] + +[[package]] +name = "libquickjs-ng-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c98c1ad542ec61348faba7ce5386fef9060e35fbeea19dda64ce41862084e0a" +dependencies = [ + "bindgen", + "cc", + "copy_dir", +] + [[package]] name = "libsqlite3-sys" version = "0.30.1" @@ -1502,6 +1597,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.5" @@ -1600,12 +1701,41 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1828,6 +1958,16 @@ dependencies = [ "log", ] +[[package]] +name = "prettyplease" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6" +dependencies = [ + "proc-macro2", + "syn 2.0.100", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -1858,6 +1998,22 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quickjs-rusty" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b4d659d1bc37e9112a14ad9a7727d182b0fb12216eb6684bdbada3e9991a22" +dependencies = [ + "anyhow", + "chrono", + "libquickjs-ng-sys", + "log", + "num-bigint", + "num-traits", + "serde", + "thiserror 2.0.12", +] + [[package]] name = "quote" version = "1.0.40" @@ -2067,6 +2223,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2157,6 +2319,15 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "schannel" version = "0.1.27" @@ -3059,6 +3230,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index e8b707a..ffa5767 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ itertools = "0.14.0" log = "0.4.27" mongodb = "3.2.3" pretty_env_logger = "0.5.0" +quickjs-rusty = "0.9.0" serde = { version = "1.0.219", features = ["derive", "serde_derive"] } teloxide = { version = "0.14.0", features = ["macros", "postgres-storage-nativetls"] } thiserror = "2.0.12" From 08c1b67f021940045c7701471e9cfeb8d2aa7541 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 10 May 2025 03:27:35 +0300 Subject: [PATCH 002/131] create botscript runner --- src/botscript.rs | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + 2 files changed, 97 insertions(+) create mode 100644 src/botscript.rs diff --git a/src/botscript.rs b/src/botscript.rs new file mode 100644 index 0000000..8724e27 --- /dev/null +++ b/src/botscript.rs @@ -0,0 +1,96 @@ +use quickjs_rusty::Context; +use quickjs_rusty::ContextError; +use quickjs_rusty::ExecutionError; +use quickjs_rusty::OwnedJsValue as JsValue; + +#[derive(thiserror::Error, Debug)] +pub enum ScriptError { + #[error("error context: {0:?}")] + ContextError(#[from] ContextError), + #[error("error running: {0:?}")] + ExecutionError(#[from] ExecutionError), +} + +pub type ScriptResult = Result; + +// TODO: remove this function since it is suitable only for early development +#[allow(clippy::print_stdout)] +fn print(s: String) { + println!("{s}"); +} + +pub struct Runner { + context: Context, +} + +impl Runner { + pub fn init() -> ScriptResult { + let context = Context::new(None)?; + + context.add_callback("print", |a: String| { + print(a); + + None:: + })?; + + Ok(Runner { context }) + } + + pub fn run_script(&self, content: &str) -> ScriptResult { + let ctx = &self.context; + + let val = ctx.eval(content, false)?; + + Ok(val) + } +} + +#[cfg(test)] +// allowing this since it is better for debugging tests) +#[allow(clippy::unwrap_used)] +#[allow(clippy::print_stdout)] +mod tests { + use super::*; + + #[test] + fn test_run_script_valid() { + let runner = Runner::init().unwrap(); + let val = runner.run_script(r#"print"#).unwrap(); + println!("Val: {:?}", val); + let val = runner.run_script(r#"print('Hello from JS!');"#).unwrap(); + println!("Val: {:?}", val); + assert!(val.is_null()); + let val = runner.run_script(r#"const a = 1+2; a"#).unwrap(); + println!("Val: {:?}", val); + assert_eq!(val.to_int(), Ok(3)); + let val = runner.run_script(r#"a + 39"#).unwrap(); + println!("Val: {:?}", val); + assert_eq!(val.to_int(), Ok(42)); + } + + #[test] + fn test_run_script_file_main() { + let runner = Runner::init().unwrap(); + let val = runner.run_script(include_str!("../mainbot.js")).unwrap(); + println!("config: {:?}", val); + let val = runner.run_script("start_buttons()").unwrap(); + println!("Val: {:?}", val.to_string()); + } + + #[test] + fn test_run_script_invalid() { + let runner = Runner::init().unwrap(); + let result = runner.run_script(r#"invalid_script();"#); + + assert!(result.is_err()); + let errstr = + if let Err(ScriptError::ExecutionError(ExecutionError::Exception(errstr))) = result { + errstr.to_string().unwrap() + } else { + panic!("test returned wrong error!, {result:?}"); + }; + if errstr != "ReferenceError: invalid_script is not defined" { + panic!("test returned an error, but the wrong one, {errstr}") + } + } +} diff --git a/src/main.rs b/src/main.rs index a5fd667..1e47d0b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ pub mod admin; +pub mod botscript; pub mod db; pub mod mongodb_storage; pub mod utils; From 0c927448d26a0a77bf869afbe050287881d92976 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sat, 10 May 2025 03:28:00 +0300 Subject: [PATCH 003/131] create mainbot.js where main bot's logic will be contained --- mainbot.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 mainbot.js diff --git a/mainbot.js b/mainbot.js new file mode 100644 index 0000000..5bc56b3 --- /dev/null +++ b/mainbot.js @@ -0,0 +1,35 @@ +// db - is set globally + +const dialog = { + start: { + buttons: start_buttons + } +} + +const fmt = (number) => number.toString().padStart(2, '0'); + +const formatDate = (date) => { + const [h, m, d, M, y] = [ + date.getHours(), + date.getMinutes(), + date.getDate(), + date.getMonth(), + date.getFullYear() + ]; + return `${fmt(h)}:${fmt(m)} ${fmt(d)}-${fmt(M + 1)}-${y}` +}; + +function start_buttons() { + const now = new Date(); + const dateFormated = formatDate(now); + print(console) + + // return 1 + return dateFormated +} + +const config = { + version: 1.0 +} + +{config, dialog} From 9d5bf86289567e44b7c80492aeac403aad3a223f Mon Sep 17 00:00:00 2001 From: Akulij Date: Sun, 18 May 2025 16:05:10 +0300 Subject: [PATCH 004/131] create recursive_format function just for tests --- src/botscript.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/botscript.rs b/src/botscript.rs index 8724e27..980a2ea 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -77,6 +77,22 @@ mod tests { println!("Val: {:?}", val.to_string()); } + fn recursive_format(o: OwnedJsObject) -> String { + let props: Vec<_> = o.properties_iter().unwrap().map(|x| x.unwrap()).collect(); + let sp: Vec = props + .into_iter() + .map(|v| { + if v.is_object() { + recursive_format(v.try_into_object().unwrap()) + } else { + format!("{:?}", v) + } + }) + .collect(); + + format!("{:?}", sp) + } + #[test] fn test_run_script_invalid() { let runner = Runner::init().unwrap(); From bf032e6ce403081282a968d5baae7397469d9aea Mon Sep 17 00:00:00 2001 From: Akulij Date: Sun, 18 May 2025 16:07:40 +0300 Subject: [PATCH 005/131] create test for deserealization of mainbot.js --- src/botscript.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/botscript.rs b/src/botscript.rs index 980a2ea..89bb9ca 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -77,6 +77,16 @@ mod tests { println!("Val: {:?}", val.to_string()); } + #[test] + fn test_deserialization_main() { + let runner = Runner::init().unwrap(); + let val = runner.run_script(include_str!("../mainbot.js")).unwrap(); + let s: RunnerConfig = from_js(unsafe { runner.context.context_raw() }, &val).unwrap(); + println!("deser: {:#?}", s); + let o = val.try_into_object().unwrap(); + println!("o: {:?}", recursive_format(o)); + } + fn recursive_format(o: OwnedJsObject) -> String { let props: Vec<_> = o.properties_iter().unwrap().map(|x| x.unwrap()).collect(); let sp: Vec = props From 96996fd33dff4dda7f0cdde9f461c13e2cf90659 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sun, 18 May 2025 16:08:34 +0300 Subject: [PATCH 006/131] update mainbot.js with new specification --- mainbot.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mainbot.js b/mainbot.js index 5bc56b3..162f31b 100644 --- a/mainbot.js +++ b/mainbot.js @@ -2,7 +2,7 @@ const dialog = { start: { - buttons: start_buttons + buttons: "start_buttons" } } @@ -22,14 +22,15 @@ const formatDate = (date) => { function start_buttons() { const now = new Date(); const dateFormated = formatDate(now); - print(console) // return 1 return dateFormated } const config = { - version: 1.0 + version: 1.1 } -{config, dialog} +// {config, dialog} +const c = {config: config, dialog: dialog} +c From a6206d3d6fb3f7bbf12aceb883ce6308dea0cbd9 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sun, 18 May 2025 16:09:34 +0300 Subject: [PATCH 007/131] use quickjs_rusty's from_js in tests --- src/botscript.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/botscript.rs b/src/botscript.rs index 89bb9ca..966565c 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -50,6 +50,8 @@ impl Runner { #[allow(clippy::unwrap_used)] #[allow(clippy::print_stdout)] mod tests { + use quickjs_rusty::{serde::from_js, OwnedJsObject}; + use super::*; #[test] From e7d43adc40551a8f6e5a92b643886daf61afbc80 Mon Sep 17 00:00:00 2001 From: Akulij Date: Sun, 18 May 2025 16:10:03 +0300 Subject: [PATCH 008/131] create structs for bot's configuration definition --- src/botscript.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/botscript.rs b/src/botscript.rs index 966565c..3868b91 100644 --- a/src/botscript.rs +++ b/src/botscript.rs @@ -1,7 +1,11 @@ +use std::collections::HashMap; + use quickjs_rusty::Context; use quickjs_rusty::ContextError; use quickjs_rusty::ExecutionError; use quickjs_rusty::OwnedJsValue as JsValue; +use serde::Deserialize; +use serde::Serialize; #[derive(thiserror::Error, Debug)] pub enum ScriptError { @@ -13,12 +17,39 @@ pub enum ScriptError { pub type ScriptResult = Result; +pub type BotFunction = String; // temporal workaround + // 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, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Button { + name: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct BotMessage { + // buttons: Vec