Add JSON manipulation action

This commit is contained in:
NGnius (Graham) 2022-09-28 20:01:51 -04:00
parent 1e916a644b
commit 83135b3045
13 changed files with 378 additions and 25 deletions

34
backend/Cargo.lock generated
View file

@ -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"

View file

@ -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

View file

@ -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<usdpl_back::core::serdes::Primitive>;

View file

@ -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<SteamBluetoothAdapter>,
pub vecDevices: Vec<SteamBluetoothDevice>,
}
#[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,
}

View file

@ -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<super::SteamDownloadInfo>,
}

View file

@ -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,
}

View file

@ -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;

View file

@ -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();

View file

@ -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),
}
}
}

View file

@ -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<Self, ActError> {
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
},
}
}
}

View file

@ -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;

View file

@ -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",

View file

@ -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 <div>Unsupported</div>;
}