From 83135b30456fcb384b7295684d875badf343169d Mon Sep 17 00:00:00 2001 From: "NGnius (Graham)" Date: Wed, 28 Sep 2022 20:01:51 -0400 Subject: [PATCH] Add JSON manipulation action --- backend/Cargo.lock | 34 ++++ backend/Cargo.toml | 9 +- backend/src/api/mod.rs | 2 + backend/src/api/steam_types.rs | 198 +++++++++++++++++++++++ backend/src/api/types.rs | 23 +++ backend/src/config/action.rs | 9 ++ backend/src/config/mod.rs | 2 +- backend/src/main.rs | 3 +- backend/src/runtime/actors/actor.rs | 12 +- backend/src/runtime/actors/json_actor.rs | 77 +++++++++ backend/src/runtime/actors/mod.rs | 2 + package.json | 2 +- src/index.tsx | 30 ++-- 13 files changed, 378 insertions(+), 25 deletions(-) create mode 100644 backend/src/api/steam_types.rs create mode 100644 backend/src/runtime/actors/json_actor.rs diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 2b3243a..065535c 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -97,6 +97,7 @@ version = "0.1.0" dependencies = [ "async-trait", "clap", + "jmespath", "log", "regex", "serde", @@ -167,6 +168,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "deunicode" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "850878694b7933ca4c9569d30a34b55031b9b139ee1fc7b94a527c4ef960d690" + [[package]] name = "digest" version = "0.9.0" @@ -433,6 +440,24 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" +[[package]] +name = "jmespath" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "017f8f53dd3b8ada762acb1f850da2a742d0ef3f921c60849a644380de1d683a" +dependencies = [ + "lazy_static", + "serde", + "serde_json", + "slug", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "libc" version = "0.2.126" @@ -805,6 +830,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "slug" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373" +dependencies = [ + "deunicode", +] + [[package]] name = "socket2" version = "0.4.4" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index b8fd237..88c53e2 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -14,17 +14,18 @@ clap = { version = "3.2", features = ["derive", "std"], default-features = false # async tokio = { version = "*", features = ["time"] } -async-trait = "0.1.57" +async-trait = { version = "0.1.57" } # json serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +serde_json = { version = "1.0" } +jmespath = { version = "0.3", features = [ "sync" ] } regex = { version = "1" } # logging -log = "0.4" -simplelog = "0.12" +log = { version = "0.4", features = ["max_level_debug", "release_max_level_warn"] } +simplelog = { version = "0.12" } [profile.release] debug = false diff --git a/backend/src/api/mod.rs b/backend/src/api/mod.rs index 7ccae0e..37805bc 100644 --- a/backend/src/api/mod.rs +++ b/backend/src/api/mod.rs @@ -6,6 +6,7 @@ mod on_javascript_result; mod on_update; mod reload; mod run_js; +mod steam_types; mod types; pub use about::get_about; @@ -15,6 +16,7 @@ pub use on_javascript_result::on_javascript_result; pub use on_update::on_update; pub use reload::reload; pub use run_js::{GetJavascriptEndpoint, JavascriptData}; +pub use steam_types::*; pub(super) use types::*; pub(super) type ApiParameterType = Vec; diff --git a/backend/src/api/steam_types.rs b/backend/src/api/steam_types.rs new file mode 100644 index 0000000..c76e6e3 --- /dev/null +++ b/backend/src/api/steam_types.rs @@ -0,0 +1,198 @@ +#![allow(non_snake_case)] +use serde::{Serialize, Deserialize}; + +// list of these is second callback param for SteamClient.Downloads.RegisterForDownloadItems +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamDownloadInfo { + pub active: bool, + pub appid: usize, + pub buildid: usize, + pub target_buildid: usize, + pub paused: bool, +} + +// only callback param for SteamClient.Downloads.RegisterForDownloadOverview +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamDownloadOverview { + pub paused: bool, + pub throttling_suspended: bool, + pub update_appid: usize, + pub update_bytes_downloaded: usize, + pub update_bytes_processed: usize, + pub update_bytes_staged: usize, + pub update_bytes_to_download: usize, + pub update_bytes_to_process: usize, + pub update_bytes_to_stage: usize, + pub update_disc_bytes_per_second: usize, + pub update_is_install: bool, + pub update_is_prefetch_estimate: bool, + pub update_is_shader: bool, + pub update_is_workshop: bool, + pub update_network_bytes_per_second: usize, + pub update_peak_network_bytes_per_second: usize, + pub update_seconds_remaining: isize, // -1 seems to indicate not-applicable + pub update_start_time: usize, + pub update_state: String, +} + +// only param of callback for SteamClient.GameSessions.RegisterForAchievementNotification +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamAchievementNotification { + pub achievement: SteamAchievement, + pub nCurrentProgress: usize, + pub nMaxProgress: usize, + pub unAppID: usize, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamAchievement { + pub bAchieved: bool, + pub bHidden: bool, + pub flAchieved: f64, + pub flCurrentProgress: f64, + pub flMaxProgress: f64, + pub flMinProgress: f64, + pub rtUnlocked: usize, // time since unix epoch + pub strDescription: String, + pub strID: String, + pub strImage: String, + pub strName: String, +} + +// only param of callback for SteamClient.System.Bluetooth.RegisterForStateChanges +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamBluetoothState { + pub bEnabled: bool, + pub vecAdapters: Vec, + pub vecDevices: Vec, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamBluetoothDevice { + pub bConnected: bool, + pub bPaired: bool, + pub eType: usize, // enum, idk the options + pub nAdapterId: usize, // corresponds to SteamBluetoothAdapter.nId + pub nStrengthRaw: usize, // units??? + pub sMAC: String, + pub sName: String, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamBluetoothAdapter { + pub bDiscovering: bool, + pub bEnabled: bool, + pub nId: usize, + pub sMAC: String, + pub sName: String, +} + +// only param of callback for SteamClient.System.Network.RegisterForConnectivityTestChanges +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamConnectivityTestChange { + pub bChecking: bool, + pub eConnectivityTestResult: usize, // enum, idk the options + pub eFakeState: usize, // enum, idk the options +} + +// only param of callback for SteamClient.System.Network.RegisterForNetworkDiagnostics +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamNetworkDiagnostic { + pub status: bool, + pub total_bytes: usize, +} + +// only param of callback for SteamClient.System.Audio.RegisterForDeviceAdded +// and SteamClient.System.Audio.RegisterForDeviceAdded +// Also type of vecDevices of await SteamClient.System.Audio.GetDevices() +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamAudioDevice { + pub bHasInput: bool, + pub bHasOutput: bool, + pub bIsDefaultInputDevice: bool, + pub bIsDefaultOutputDevice: bool, + pub flInputVolume: f64, + pub flOutputVolume: f64, + pub id: usize, + pub sName: String, +} + +// only param of callback for SteamClient.System.Display.RegisterForBrightnessChanges +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamBrightness { + pub flBrightness: f64, +} + +// not a callback; await SteamClient.System.GetSystemInfo() +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamSystemInfo { + pub nCPUHz: usize, + pub nCPULogicalCores: usize, + pub nCPUPhysicalCores: usize, + pub nSteamVersion: usize, + pub nSystemRAMSizeMB: usize, + pub nVideoRAMSizeMB: usize, + pub sBIOSVersion: String, + pub sCPUName: String, + pub sCPUVendor: String, + pub sHostname: String, + pub sKernelVersion: String, + pub sOSBuildId: String, + pub sOSCodename: String, + pub sOSName: String, + pub sOSVariantId: String, + pub sOSVersionId: String, + pub sSteamAPI: String, + pub sSteamBuildDate: String, + pub sVideoCardName: String, + pub sVideoDriverVersion: String, +} + +// only param of callback for SteamClient.System.RegisterForAirplaneModeChanges +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamAirplane { + pub bEnabled: bool, +} + +// only param of callback for SteamClient.System.RegisterForBatteryStateChanges +// periodic +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamBattery { + pub bHasBattery: bool, + pub bShutdownRequested: bool, + pub eACState: usize, + pub eBatteryState: usize, + pub flLevel: f64, + pub nSecondsRemaining: usize, +} + +// only param of callback for SteamClient.GameSessions.RegisterForScreenshotNotification +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamScreenshotNotification { + pub details: SteamScreenshot, + pub hScreenshot: usize, + pub strOperation: String, + pub unAppID: usize, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamScreenshot { + pub bSpoilers: bool, + pub bUploaded: bool, + pub ePrivacy: usize, // enum + pub hHandle: usize, + pub nAppID: usize, + pub nCreated: usize, + pub nHeight: usize, + pub nWidth: usize, + pub strCaption: String, + pub strUrl: String, +} + +// list of these is only param of callback for SteamClient.Input.RegisterForControllerInputMessages +#[derive(Serialize, Deserialize, Clone)] +pub struct SteamControllerInputMessage { + pub bState: bool, + pub nController: usize, + pub strActionName: String, +} diff --git a/backend/src/api/types.rs b/backend/src/api/types.rs index 35841ef..704a3b5 100644 --- a/backend/src/api/types.rs +++ b/backend/src/api/types.rs @@ -109,3 +109,26 @@ impl ApiJavascriptResult { Self::Error(ApiError::new(msg, err)) } } + +#[derive(Serialize, Deserialize, Clone)] +#[serde(tag = "event")] +pub enum ApiRegisteredEvent { + #[serde(rename = "resume-from-suspend")] + ResumeFromSuspend, + #[serde(rename = "suspend-from-awake")] + SuspendRequest, + #[serde(rename = "download-items")] + DownloadList(ApiDownloadList), + #[serde(rename = "download-overview")] + DownloadOverview(super::SteamDownloadOverview), + #[serde(rename = "achievement-notification")] + AchievementNotification(super::SteamAchievementNotification), + #[serde(rename = "error")] + Error(ApiError), +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct ApiDownloadList { + pub is_paused: bool, + pub list: Vec, +} diff --git a/backend/src/config/action.rs b/backend/src/config/action.rs index afd7edb..013032f 100644 --- a/backend/src/config/action.rs +++ b/backend/src/config/action.rs @@ -13,6 +13,8 @@ pub enum TopLevelActionConfig { Mirror(MirrorAction), #[serde(rename = "javascript")] Javascript(JavascriptAction), + #[serde(rename = "json")] + Json(JsonAction), } #[derive(Serialize, Deserialize, Clone)] @@ -24,6 +26,8 @@ pub enum ActionConfig { Transform(super::TransformAction), #[serde(rename = "javascript")] Javascript(JavascriptAction), + #[serde(rename = "json")] + Json(JsonAction), } #[derive(Serialize, Deserialize, Clone)] @@ -43,3 +47,8 @@ pub struct MirrorAction; pub struct JavascriptAction { pub run: String, } + +#[derive(Serialize, Deserialize, Clone)] +pub struct JsonAction { + pub jmespath: String, +} diff --git a/backend/src/config/mod.rs b/backend/src/config/mod.rs index 82eddb5..d36cc79 100644 --- a/backend/src/config/mod.rs +++ b/backend/src/config/mod.rs @@ -10,7 +10,7 @@ mod toggle; mod transformer; pub use about::AboutConfig; -pub use action::{TopLevelActionConfig, ActionConfig, CommandAction, MirrorAction, SequenceAction, JavascriptAction}; +pub use action::{TopLevelActionConfig, ActionConfig, CommandAction, MirrorAction, SequenceAction, JavascriptAction, JsonAction}; pub use base::BaseConfig; pub use button::ButtonConfig; pub use element::ElementConfig; diff --git a/backend/src/main.rs b/backend/src/main.rs index 3fd998f..56f3ccb 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -13,8 +13,7 @@ fn main() -> Result<(), ()> { let cli_args = cli::CliArgs::cli(); let log_filepath = cli_args.log.unwrap_or_else(|| format!("/tmp/{}.log", consts::PACKAGE_NAME).into()); WriteLogger::init( - #[cfg(debug_assertions)]{LevelFilter::Debug}, - #[cfg(not(debug_assertions))]{LevelFilter::Info}, + LevelFilter::Off, Default::default(), std::fs::File::create(&log_filepath).expect(&format!("Failed create log file {}", log_filepath.display())) ).unwrap(); diff --git a/backend/src/runtime/actors/actor.rs b/backend/src/runtime/actors/actor.rs index c4f3bea..cc194a0 100644 --- a/backend/src/runtime/actors/actor.rs +++ b/backend/src/runtime/actors/actor.rs @@ -52,6 +52,7 @@ pub enum TopLevelActorType { Mirror, Sequence(super::SequenceActor), Javascript(super::JavascriptActor), + Json(super::JsonActor), } impl<'a> SeqAct<'a> for TopLevelActorType { @@ -69,7 +70,9 @@ impl<'a> SeqAct<'a> for TopLevelActorType { TopLevelActionConfig::Mirror(_) => Self::Mirror, TopLevelActionConfig::Javascript(j) => - Self::Javascript(super::JavascriptActor::build(j, parameter)?) + Self::Javascript(super::JavascriptActor::build(j, parameter)?), + TopLevelActionConfig::Json(j) => + Self::Json(super::JsonActor::build(j, ())?), }) } @@ -80,6 +83,7 @@ impl<'a> SeqAct<'a> for TopLevelActorType { Self::Mirror => p, Self::Sequence(s) => s.run(p), Self::Javascript(j) => j.run(p), + Self::Json(j) => j.run(p), } } } @@ -88,6 +92,7 @@ pub enum ActorType { Command(super::CommandActor), Transform(super::TransformActor), Javascript(super::JavascriptActor), + Json(super::JsonActor), } impl<'a> SeqAct<'a> for ActorType { @@ -101,7 +106,9 @@ impl<'a> SeqAct<'a> for ActorType { ActionConfig::Transform(t) => Self::Transform(super::TransformActor::build(t, ())?), ActionConfig::Javascript(j) => - Self::Javascript(super::JavascriptActor::build(j, parameter)?) + Self::Javascript(super::JavascriptActor::build(j, parameter)?), + ActionConfig::Json(j) => + Self::Json(super::JsonActor::build(j, ())?), }) } @@ -110,6 +117,7 @@ impl<'a> SeqAct<'a> for ActorType { Self::Command(c) => c.run(p), Self::Transform(t) => t.run(p), Self::Javascript(j) => j.run(p), + Self::Json(j) => j.run(p), } } } diff --git a/backend/src/runtime/actors/json_actor.rs b/backend/src/runtime/actors/json_actor.rs new file mode 100644 index 0000000..ce8d628 --- /dev/null +++ b/backend/src/runtime/actors/json_actor.rs @@ -0,0 +1,77 @@ +use usdpl_back::core::serdes::Primitive; + +use jmespath::{Expression, Variable}; + +use crate::config::JsonAction; +use super::{SeqAct, ActError}; + +pub struct JsonActor { + expr: Expression<'static>, +} + +impl JsonActor { + fn jmespath_value_to_primitive(var: Variable) -> Primitive { + match var { + Variable::Null => Primitive::Empty, + Variable::String(s) => Primitive::String(s), + Variable::Bool(b) => Primitive::Bool(b), + Variable::Number(f) => f.as_f64().map(|x| Primitive::F64(x)).unwrap_or(Primitive::Empty), + Variable::Array(arr) => serde_json::to_string(&arr) + .map(Primitive::Json) + .unwrap_or(Primitive::Empty), + Variable::Object(obj) => serde_json::to_string(&obj) + .map(Primitive::Json) + .unwrap_or(Primitive::Empty), + Variable::Expref(_) => { + log::warn!("The jmespath result cannot be another jmespath"); + Primitive::Empty + } + } + } +} + +impl<'a> SeqAct<'a> for JsonActor { + type BuildParam = (); + type Config = JsonAction; + + fn build(config: &'a Self::Config, _: Self::BuildParam) -> Result { + Ok( + Self { + expr: jmespath::compile(&config.jmespath) + .map_err(|e| format!("Failed to compile jmespath `{}`: {}", config.jmespath, e))?, + } + ) + } + + fn run(self, parameter: Primitive) -> Primitive { + match parameter { + Primitive::Json(json) => { + match Variable::from_json(&json) { + Ok(var) => { + match self.expr.search(var) { + Ok(result) => Self::jmespath_value_to_primitive( + std::sync::Arc::try_unwrap(result) + .unwrap_or_else(|e| { + log::debug!("Cloning jmespath search result"); + (*e).clone() + }) + ), + Err(e) => { + log::error!("Cannot search through JSON `{}`: {}", json, e); + Primitive::Empty + } + } + }, + Err(e) => { + log::error!("Cannot convert to jmespath Variable from JSON `{}`: {}", json, e); + Primitive::Empty + } + } + }, + _ => { + log::error!("Cannot apply JSON action to non-JSON primitive"); + Primitive::Empty + }, + } + } +} diff --git a/backend/src/runtime/actors/mod.rs b/backend/src/runtime/actors/mod.rs index 00ccaeb..58b3b3e 100644 --- a/backend/src/runtime/actors/mod.rs +++ b/backend/src/runtime/actors/mod.rs @@ -1,6 +1,7 @@ mod actor; mod command_actor; mod javascript_actor; +mod json_actor; mod periodic_actor; mod sequential_actor; mod transform_actor; @@ -8,6 +9,7 @@ mod transform_actor; pub use actor::{Actor, Act, ActError, ActorType, SeqAct, SeqActor, TopLevelActorType}; pub use command_actor::CommandActor; pub use javascript_actor::JavascriptActor; +pub use json_actor::JsonActor; pub use periodic_actor::PeriodicActor; pub use sequential_actor::SequenceActor; pub use transform_actor::TransformActor; diff --git a/package.json b/package.json index 97abca7..989942c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "caylon", - "version": "0.0.1", + "version": "0.1.0", "description": "Better than the Borg", "scripts": { "build": "shx rm -rf dist && rollup -c", diff --git a/src/index.tsx b/src/index.tsx index 690db7d..b3ba287 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -40,19 +40,19 @@ function displayCallback(index: number) { switch (newVal.result) { case "value": let val = newVal as backend.CValueResult; - console.log("KAYLON: Got display for " + index.toString(), val); + console.log("CAYLON: Got display for " + index.toString(), val); set_value(DISPLAY_KEY + index.toString(), val.value); break; case "error": let err = newVal as backend.CErrorResult; - console.warn("KAYLON: Got display error for " + index.toString(), err); + console.warn("CAYLON: Got display error for " + index.toString(), err); break; default: - console.error("KAYLON: Got invalid display response for " + index.toString(), newVal); + console.error("CAYLON: Got invalid display response for " + index.toString(), newVal); break; } } else { - console.warn("KAYLON: Ignoring null display result for " + index.toString()); + console.warn("CAYLON: Ignoring null display result for " + index.toString()); } updateTasks.push(() => backend.resolve(backend.getDisplay(index), displayCallback(index))); update(); @@ -61,7 +61,7 @@ function displayCallback(index: number) { function onGetElements() { for (let i = 0; i < items.length; i++) { - console.log("KAYLON: req display for item #" + i.toString()); + console.log("CAYLON: req display for item #" + i.toString()); backend.resolve(backend.getDisplay(i), displayCallback(i)); } backend.resolve(backend.getJavascriptToRun(), jsCallback()); @@ -77,16 +77,16 @@ function jsCallback() { switch (script.result) { case "javascript": let toRun = script as backend.CJavascriptResult; - console.log("KAYLON: Got javascript " + toRun.id.toString(), toRun); + console.log("CAYLON: Got javascript " + toRun.id.toString(), toRun); let result = eval2(toRun.raw); backend.onJavascriptResult(toRun.id, result); break; case "error": let err = script as backend.CErrorResult; - console.warn("KAYLON: Got javascript retrieval error", err); + console.warn("CAYLON: Got javascript retrieval error", err); break; default: - console.error("KAYLON: Got invalid javascript response", script); + console.error("CAYLON: Got invalid javascript response", script); break; } } @@ -100,14 +100,14 @@ function jsCallback() { let about_promise = backend.getAbout(); let elements_promise = backend.getElements(); about = await about_promise; - console.log("KAYLON: got about", about); + console.log("CAYLON: got about", about); let result = await elements_promise; - console.log("KAYLON: got elements", result); + console.log("CAYLON: got elements", result); if (result != null) { items = result; onGetElements(); } else { - console.warn("KAYLON: backend connection failed"); + console.warn("CAYLON: backend connection failed"); } backend.resolve(backend.getJavascriptToRun(), jsCallback()); })(); @@ -146,19 +146,19 @@ const Content: VFC<{ serverAPI: ServerAPI }> = ({}) => { backend.resolve(backend.reload(), (reload_items: backend.CElement[]) => { items = reload_items; - console.log("KAYLON: got elements", reload_items); + console.log("CAYLON: got elements", reload_items); if (reload_items != null) { items = reload_items; onGetElements(); } else { - console.warn("KAYLON: backend connection failed"); + console.warn("CAYLON: backend connection failed"); } update(); }); backend.resolve(backend.getAbout(), (new_about: backend.CAbout) => { about = new_about; - console.log("KAYLON: got about", about); + console.log("CAYLON: got about", about); update(); }); }}> @@ -182,7 +182,7 @@ function buildHtmlElement(element: backend.CElement, index: number, updateIdc: a case "result-display": return buildResultDisplay(element as backend.CResultDisplay, index, updateIdc); } - console.error("KAYLON: Unsupported element", element); + console.error("CAYLON: Unsupported element", element); return
Unsupported
; }