From 0f7cf9fd4d92c34d5c769ea1a935c01e8a9c4440 Mon Sep 17 00:00:00 2001 From: Junko Date: Thu, 15 Feb 2024 18:07:13 +0100 Subject: [PATCH] app refactor 1 --- Cargo.lock | 353 ++++++++++++++++++++++++++- Cargo.toml | 4 + src/api.rs | 290 ++++++++++++++++++++++ src/app.rs | 94 ++++++++ src/clap_ps.rs | 5 +- src/configdata.rs | 40 +++- src/main.rs | 596 ++++------------------------------------------ src/systemdata.rs | 133 ++++++++++- src/utils.rs | 11 + 9 files changed, 954 insertions(+), 572 deletions(-) create mode 100644 src/api.rs create mode 100644 src/app.rs create mode 100644 src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 92f728a..90f82f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,24 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + [[package]] name = "anstream" version = "0.6.7" @@ -116,6 +134,21 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.0.83" @@ -162,7 +195,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -171,12 +204,52 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "compact_str" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -193,6 +266,31 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.4.1", + "crossterm_winapi", + "libc", + "mio", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "dirs" version = "5.0.1" @@ -214,6 +312,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -239,6 +343,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -355,6 +469,10 @@ name = "hashbrown" version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +dependencies = [ + "ahash", + "allocator-api2", +] [[package]] name = "heck" @@ -449,6 +567,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "2.1.0" @@ -459,12 +583,27 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indoc" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" + [[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.9" @@ -525,6 +664,15 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "lru" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22" +dependencies = [ + "hashbrown", +] + [[package]] name = "memchr" version = "2.6.4" @@ -563,6 +711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -633,7 +782,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -660,6 +809,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "parking_lot" version = "0.12.1" @@ -683,6 +838,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "paste" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + [[package]] name = "percent-encoding" version = "2.3.0" @@ -712,7 +873,10 @@ name = "pluralsync" version = "1.2.0" dependencies = [ "clap", + "color-eyre", + "crossterm", "dirs", + "ratatui", "reqwest", "serde", "serde_json", @@ -737,6 +901,26 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "ratatui" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "154b85ef15a5d1719bcaa193c3c81fe645cd120c156874cd660fe49fd21d1373" +dependencies = [ + "bitflags 2.4.1", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "itertools", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -815,6 +999,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.15" @@ -876,7 +1066,7 @@ checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -902,6 +1092,36 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29ad2e15f37ec9a6cc544097b78a1ec90001e9f71b81338ca39f430adaca99af" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -946,12 +1166,61 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "stability" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebd1b177894da2a2d9120208c3386066af06a488255caabc5de8ddca22dbc3ce" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strum" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.39", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.39" @@ -1014,7 +1283,17 @@ checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", ] [[package]] @@ -1059,7 +1338,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", ] [[package]] @@ -1109,6 +1388,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", ] [[package]] @@ -1147,6 +1448,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "url" version = "2.4.1" @@ -1164,6 +1477,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "vcpkg" version = "0.2.15" @@ -1212,7 +1531,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.39", "wasm-bindgen-shared", ] @@ -1246,7 +1565,7 @@ checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.39", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1430,3 +1749,23 @@ dependencies = [ "cfg-if", "windows-sys 0.48.0", ] + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] diff --git a/Cargo.toml b/Cargo.toml index 518d301..68f8466 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,12 @@ serde_json = "1.0" reqwest = { version = "0.11.22", features = ["json", "multipart"] } clap = { version = "4.4.10", features = ["derive"] } tokio = { version = "1", features = ["full"] } +ratatui = "0.26.0" +crossterm = "0.27.0" +color-eyre = "0.6.2" [features] +default = ["avatar"] avatar = [] discord = ["avatar"] fedi = ["avatar"] diff --git a/src/api.rs b/src/api.rs new file mode 100644 index 0000000..21d2139 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,290 @@ +pub mod pk { + + use std::collections::HashMap; + + use reqwest::header::{USER_AGENT, AUTHORIZATION}; + + use serde_json::Value; + + use crate::api::request::*; + use crate::systemdata::*; + + use color_eyre::eyre::{eyre, Result}; + + pub const PK_URL: &str = "https://api.pluralkit.me/v2"; + + pub fn get_system(key: &str) -> Result { + let url = format!("{}/systems/@me", PK_URL); + + let response = http_get(url,key)?; + + Ok(serde_json::from_str(&response)?) + } + + pub fn get_members(key: &str, sysid: &str) -> Result> { + let url = format!("{}/systems/{}/members", PK_URL, sysid); + + let response = http_get(url,key)?; + + Ok(serde_json::from_str(&response)?) + } + + pub fn get_fronters(key: &str, sys: &System) -> Result> { + let url = format!("{}/systems/{}/fronters", PK_URL, sys.pk_userid); + + let result = http_get(url,key)?; + let json: Value = serde_json::from_str(&result)?; + let json_members = &json["members"].as_array(); + + let mut members: Vec = Vec::new(); + for member in json_members { + for m in member.into_iter() { + for dbmem in &sys.members { + if m["name"].to_string() == dbmem.name { + members.push(dbmem.clone()); + } + } + } + } + + Ok(members) + } + + + pub fn set_fronters(key: &str, sys: &System, to_front: &Vec, fronters: &Fronters) -> Result<()> { + let url = format!("{}/systems/{}/switches", PK_URL, sys.pk_userid); + + if to_front != &fronters.pk { + let mut frontcodes = Vec::new(); + for tf in to_front { + frontcodes.push(String::from(&tf.pk_id)); + } + + let mut body: HashMap<&str, Vec> = HashMap::new(); + body.insert("members", frontcodes); + + let client = reqwest::Client::new(); + let rb = client + .post(url) + .json(&body) + .header("content-type", "application/json; charset=utf-8") + .header(USER_AGENT, "Pluralsync") + .header(AUTHORIZATION, key); + + match http_request(rb) { + Ok(_) => Ok(()), + Err(e) => Err(eyre!("{}", e.to_string())), + } + + } else { + println!("Members already fonting"); + Ok(()) + } + } +} + +pub mod sp { + + use std::collections::HashMap; + + use reqwest::header::{USER_AGENT, AUTHORIZATION}; + + use serde::Serialize; + use serde_json::Value; + use crate::api::request::*; + use crate::systemdata::*; + + use color_eyre::eyre::{eyre, Result}; + + pub const SP_URL: &str = "https://api.apparyllis.com/v1"; + + pub fn get_user_id(key: &str) -> Result { + let url = format!("{}/me", SP_URL); + + let response = http_get(url, key)?; + + let json: Value = serde_json::from_str(&response)?; + + Ok(json["id"].to_string()) + } + + pub fn get_member_ids(key: &str, system_id: &str) -> Result> { + let url = format!("{}/members/{}", SP_URL, system_id); + + let response = http_get(url,key)?; + let json: Vec = serde_json::from_str(&response)?; + + let mut sp_member_data: HashMap = HashMap::new(); + for member in json { + sp_member_data.insert( + member["content"]["name"].to_string(), + member["id"].to_string()); + } + + Ok(sp_member_data) + } + + pub fn get_member_id(m: &Member, ids: &HashMap) -> Result { + + for (sp_name, sp_id) in ids { + if &m.name == sp_name || m.aliases.iter().any(|e| sp_name.contains(e)) { + return Ok(sp_id.to_string()); + } + } + + Err(eyre!(format!("SP ID not found for {}", &m.name))) + } + + pub fn get_fronters(key: &str, sys: &System) -> Result> { + let url = format!("{}/fronters", SP_URL); + + let result = http_get(url,key)?; + let json: Vec = serde_json::from_str(&result)?; + + let mut members = Vec::new(); + for data in json { + let sp_id = &data["content"]["member"].to_string(); + for member in &sys.members { + if &member.sp_id == sp_id { + members.push(member.clone()); + } + } + + } + Ok(members) + } + + pub fn get_front_id(key: &str, to_id: &Member) -> Result { + let url = format!("{}/fronters", SP_URL); + + let result = http_get(url,key)?; + let datas: Vec = serde_json::from_str(&result)?; + + for data in datas { + let sp_f_id = &data["id"].to_string(); + let sp_id = &data["content"]["member"].to_string(); + if sp_id.to_string() == to_id.sp_id { + return Ok(sp_f_id.to_string()); + } + } + Err(eyre!("SP ID not found")) + } + + pub fn set_fronters(key: &str, to_front: &Vec, fronters: &Fronters) -> Result<()> { + if to_front == &fronters.sp { + println!("Members already fonting"); + return Ok(()); + } + + for fronting_member in &fronters.sp { + if !to_front.contains(&fronting_member) { + let f_id = get_front_id(&key, &fronting_member)?; + + let url = format!("{}/frontHistory/{}", SP_URL, f_id); + + #[derive (Serialize)] + #[allow (non_snake_case)] + struct SpRem { + live: bool, + endTime: u64 + } + + let end_time = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH)?.as_secs(); + let rem = SpRem { + live: false, + endTime: end_time * 1000 + }; + + let body = serde_json::to_string(&rem)?; + + + let client = reqwest::Client::new(); + let rb = client + .patch(url) + .body(body) + .header("content-type", "application/json; charset=utf-8") + .header(USER_AGENT, "Pluralsync") + .header(AUTHORIZATION, key); + + match http_request(rb) { + Ok(_) => (), + Err(e) => println!("{}", e.to_string()), + } + } + } + + for tf_member in to_front { + if !fronters.sp.contains(&tf_member) { + let url = format!("{}/frontHistory", SP_URL); + + #[derive (Serialize)] + #[allow (non_snake_case)] + struct SpAdd { + member: String, + custom: bool, + live: bool, + startTime: u64 + } + + let start_time = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH)?.as_secs(); + let rem = SpAdd { + member: String::from(&tf_member.sp_id), + custom: false, + live: true, + startTime: start_time * 1000 + }; + + let body = serde_json::to_string(&rem)?; + + let client = reqwest::Client::new(); + let rb = client + .post(url) + .body(body) + .header("content-type", "application/json; charset=utf-8") + .header(USER_AGENT, "Pluralsync") + .header(AUTHORIZATION, key); + + match http_request(rb) { + Ok(_) => (), + Err(e) => println!("{}", e.to_string()), + } + } + } + Ok(()) + } +} + +pub mod request { + + use reqwest::RequestBuilder; + use reqwest::header::{USER_AGENT, AUTHORIZATION}; + #[cfg(feature = "fedi")] + use reqwest::multipart::{Part, Form}; + + use color_eyre::eyre::Result; + + + #[tokio::main] + pub async fn http_get(url: String, key: &str) -> Result { + let client = reqwest::Client::new(); + let rb = client + .get(url) + .header(USER_AGENT, "Pluralsync") + .header(AUTHORIZATION, key); + let res = rb.send() + .await? + .text() + .await?; + + Ok(res) + } + + #[tokio::main] + pub async fn http_request(rb: RequestBuilder) -> Result { + let res = rb.send() + .await? + .text() + .await?; + Ok(res) + } +} diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..6795b62 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,94 @@ +use crate::configdata::*; +use crate::systemdata::*; +use crate::clap_ps::ForceFrom; + +use color_eyre::eyre::Result; + +pub enum CurrentScreen { + Dash, + Configuration, + Exit, +} + +pub enum ConfigEdit { + PkKey, + SpKey, + AvatarEnabled, + AvatarFolder, + AvatarOutput, + AvatarBlacklist, + DiscEnabled, + DiscToken, + FediEnabled, + FediInstance, + FediToken, +} + +pub struct App { + pub input: String, + pub cfg_dir: String, + pub cfg: Option, + pub sys: Option, + pub fronters: Option, + pub current_screen: Option, + pub editing: Option, +} + +impl App { + pub fn new(confpath: String) -> App { + App { + input: String::new(), + cfg_dir: confpath, + cfg: None, + sys: None, + fronters: None, + current_screen: None, + editing: None, + } + } + + pub fn load_system(&mut self, update: bool) -> Result<()> { + + match System::get_system(&self, update) { + Ok(s) => { + self.sys = Some(s); + Ok(()) + }, + Err(e) => { + self.sys = None; + Err(e) + } + } + } + + pub fn load_config(&mut self) -> Result<()> { + + match Config::get_config(&self.cfg_dir) { + Ok(c) => { + self.cfg = Some(c); + Ok(()) + }, + Err(e) => { + self.cfg = None; + Err(e) + }, + } + + } + + pub fn get_fronters(&mut self, ff: ForceFrom) -> Result<()> { + + self.fronters = Some(Fronters::get(self, ff)?); + + Ok(()) + } + + pub fn set_fronters(&mut self, to_front: Vec) -> Result<()> { + + self.get_fronters(ForceFrom::None)?; + + self.fronters = Some(Fronters::set(self, to_front)?); + + Ok(()) + } +} diff --git a/src/clap_ps.rs b/src/clap_ps.rs index 37e0222..02dd6a2 100644 --- a/src/clap_ps.rs +++ b/src/clap_ps.rs @@ -19,10 +19,13 @@ pub struct Args { #[derive(Subcommand)] pub enum Commands { - ///Fetches the system member information from PluralKit and SimplyPlural + /// Fetches the system member information from PluralKit and SimplyPlural Sync, + /// TUI configuration tool + TUI, /// Sets one or more members to the front #[clap(arg_required_else_help = true)] + #[non_exhaustive] Set { /// Members to set members: Vec, diff --git a/src/configdata.rs b/src/configdata.rs index f73449d..6025818 100644 --- a/src/configdata.rs +++ b/src/configdata.rs @@ -1,6 +1,16 @@ -use serde::{Serialize, Deserialize}; +use std::{ + fs::create_dir, + path::Path, +}; -#[derive(Debug, Serialize, Deserialize)] +use crate::utils::*; + +use serde::{Serialize, Deserialize}; +use serde_json::Value; + +use color_eyre::eyre::{eyre, Result}; + +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct Config { pub pk_key: String, pub sp_key: String, @@ -13,7 +23,7 @@ pub struct Config { } #[cfg(feature = "avatar")] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct AvatarModule { pub enabled: bool, pub avatar_folder: String, @@ -22,7 +32,7 @@ pub struct AvatarModule { } #[cfg(feature = "discord")] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct DiscModule { pub enabled: bool, pub token: String, @@ -31,9 +41,29 @@ pub struct DiscModule { } #[cfg(feature = "fedi")] -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct FediModule { pub enabled: bool, pub instance: String, pub token: String, } + +impl Config { + pub fn get_config(cfg_dir: &str) -> Result { + + let cfg_file_path = format!("{}/config.json", cfg_dir); + + if !Path::new(&cfg_dir).exists() { + let _ = create_dir(cfg_dir); + } + + if Path::new(&cfg_file_path).exists() { + let json_config: Value = load_json(&cfg_file_path)?; + let conf: Config = serde_json::from_value(json_config)?; + Ok(conf) + } else { + // TODO Trigger file creation screen if there is no config file. + Err(eyre!("Config file not found")) + } + } +} diff --git a/src/main.rs b/src/main.rs index 3b48c93..6cd76cc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,187 +1,85 @@ +mod app; +mod configdata; +mod systemdata; +mod api; +mod utils; +mod clap_ps; + use std::fs; #[cfg(feature = "avatar")] use std::fs::remove_file; -use std::fs::create_dir; use std::path::Path; use std::collections::HashMap; #[cfg(feature = "avatar")] use std::process::Command; -use reqwest::RequestBuilder; -use reqwest::header::{USER_AGENT, AUTHORIZATION}; -#[cfg(feature = "fedi")] -use reqwest::multipart::{Part, Form}; - use serde::Serialize; use serde_json::Value; use dirs; -mod configdata; +use app::*; + use configdata::*; -mod systemdata; use systemdata::*; -mod clap_ps; use clap_ps::*; -use clap::Parser; +use clap::{builder::TypedValueParser, Parser}; -const PK_URL: &str = "https://api.pluralkit.me/v2"; -const SP_URL: &str = "https://api.apparyllis.com/v1"; +use color_eyre::eyre::{eyre, Result}; -const EXAMPLE_JSON: &str = r#"{ - "pk_key": "// Pluralkit token", - "sp_key": "// Simplplural token", - "avatar_module": { - "enabled": false, - "avatar_folder": "// Folder to grab profile pictures, they follow they member1member2...memberX.png format", - "avatar_output_path": "// Path for the copied selected avatar", - "blacklist": "// Array of members to not include in the avatar module" - }, - "disc_module": { - "enabled": false, - "token": "// Discord user token", - "python_path": "// Path to the python executable", - "script_path": "// Path to updatediscordavatar.py" - }, - "fedi_module": { - "enabled": false, - "instance" : "// Fedi instance url", - "token": "// Fedi bearer token" - } -}"#; - -fn main() { +fn main() -> Result<()> { let cli = Args::parse(); let config_path = match dirs::config_dir() { Some(d) => format!("{}/pluralsync", d.display()), None => { - println!("Could not fetch the config directory"); - return (); + return Err(eyre!("Could not figure out the config directory!")); } }; - let mut res: Result<(), &str> = Ok(()); + let mut app: App = App::new(config_path.clone()); + app.load_config(); + app.load_system(false); match cli.cmd { Commands::Sync => { - res = sync(config_path); + app.load_system(true)?; }, - // SET MEMBER - #[cfg(all(feature = "discord", feature = "fedi"))] - Commands::Set { members, discord, fedi } => { - res = set_member(config_path.clone(), members); + Commands::TUI => { + () + }, + + Commands::Set { members, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi } => { + let res = set_member(&app, members); #[cfg(feature = "avatar")] { match res { Ok(_) => { - let _ = update_avatars(config_path.clone(), discord, fedi); - }, - Err(_) => (), - } - } - }, - #[cfg(all(feature = "discord", not(feature = "fedi")))] - Commands::Set { members, discord } => { - res = set_member(config_path.clone(), members); - - #[cfg(feature = "avatar")] { - match res { - Ok(_) => { - let _ = update_avatars(config_path.clone(), discord, false); - }, - Err(_) => (), - } - } - }, - #[cfg(all(not(feature = "discord"), feature = "fedi"))] - Commands::Set { members, fedi} => { - res = set_member(config_path.clone(), members); - - #[cfg(feature = "avatar")] { - match res { - Ok(_) => { - let _ = update_avatars(config_path.clone(), false, fedi); - }, - Err(_) => (), - } - } - }, - #[cfg(all(not(feature = "discord"), not(feature = "fedi")))] - Commands::Set { members } => { - res = set_member(config_path.clone(), members); - - #[cfg(feature = "avatar")] { - match res { - Ok(_) => { - let _ = update_avatars(config_path.clone(), false, false); + let _ = update_avatars(config_path.clone(), #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi); }, Err(_) => (), } } }, - // ADD MEMBER - #[cfg(all(feature = "discord", feature = "fedi"))] - Commands::Add { members, discord, fedi } => { - res = add_member(config_path.clone(), members); + Commands::Add { members, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi } => { + let res = add_member(config_path.clone(), members); #[cfg(feature = "avatar")] { match res { Ok(_) => { - let _ = update_avatars(config_path.clone(), discord, fedi); - }, - Err(_) => (), - } - } - }, - #[cfg(all(feature = "discord", not(feature = "fedi")))] - Commands::Add { members, discord } => { - res = add_member(config_path.clone(), members); - - #[cfg(feature = "avatar")] { - match res { - Ok(_) => { - let _ = update_avatars(config_path.clone(), discord, false); - }, - Err(_) => (), - } - } - }, - #[cfg(all(not(feature = "discord"), feature = "fedi"))] - Commands::Add { members, fedi} => { - res = add_member(config_path.clone(), members); - - #[cfg(feature = "avatar")] { - match res { - Ok(_) => { - let _ = update_avatars(config_path.clone(), false, fedi); - }, - Err(_) => (), - } - } - }, - #[cfg(all(not(feature = "discord"), not(feature = "fedi")))] - Commands::Add { members } => { - res = add_member(config_path.clone(), members); - - #[cfg(feature = "avatar")] { - match res { - Ok(_) => { - let _ = update_avatars(config_path.clone(), false, false); + let _ = update_avatars(config_path.clone(), #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi); }, Err(_) => (), } } }, - // Get MEMBER - #[cfg(all(feature = "discord", feature = "fedi"))] - Commands::Get { force_from, discord, fedi } => { + Commands::Get { force_from, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi } => { let ff = match force_from { Some(x) => x, None => ForceFrom::None, @@ -190,136 +88,40 @@ fn main() { let _ = get(config_path.clone(), ff); #[cfg(feature = "avatar")] - let _ = update_avatars(config_path.clone(), discord, fedi); - }, - #[cfg(all(feature = "discord", not(feature = "fedi")))] - Commands::Get { force_from, discord } => { - let ff = match force_from { - Some(x) => x, - None => ForceFrom::None, - }; - - let _ = get(config_path.clone(), ff); - - #[cfg(feature = "avatar")] - let _ = update_avatars(config_path.clone(), discord, false); - }, - #[cfg(all(not(feature = "discord"), feature = "fedi"))] - Commands::Get { force_from, fedi} => { - let ff = match force_from { - Some(x) => x, - None => ForceFrom::None, - }; - - let _ = get(config_path.clone(), ff); - - #[cfg(feature = "avatar")] - let _ = update_avatars(config_path.clone(), false, fedi); - }, - #[cfg(all(not(feature = "discord"), not(feature = "fedi")))] - Commands::Get { force_from } => { - let ff = match force_from { - Some(x) => x, - None => ForceFrom::None, - }; - - let _ = get(config_path.clone(), ff); - - #[cfg(feature = "avatar")] - let _ = update_avatars(config_path.clone(), false, false); + let _ = update_avatars(config_path.clone(), #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi); }, Commands::Members => { - res = memberlist(config_path); + let _res = memberlist(config_path); }, } - - match res { - Ok(_) => (), - Err(e) => println!("{}", e), - } -} - -fn sync(config_path: String) -> Result<(), &'static str> { - // Get config - let config: Config = match get_config(&config_path) { - Ok(c) => c, - Err(e) => return Err(e) - }; - - // Get Pluralkit system id - let pk_sys = pk_get_system(&config.pk_key); - let pk_sysid = pk_sys["id"].as_str().unwrap(); - - // Get Simplyplural user id - let sp_user_id = sp_get_userid(&config.sp_key); - - // Get Simplyplural member ids - let sp_member_ids = sp_get_memberids(&config.sp_key, &sp_user_id); - - // get members - let pk_members = pk_get_members(&config.pk_key, pk_sysid); - let mut members: Vec = Vec::new(); - for member in pk_members { - let mut m = Member { - pk_id: member["id"].as_str().unwrap().to_string(), - sp_id: String::new(), - name: member["name"].as_str().unwrap().to_string(), - alias: String::new() - }; - - if member["display_name"].as_str() != None { - m.alias = member["display_name"].as_str().unwrap().to_string(); - } else { - m.alias = String::from(&m.name); - } - - m.sp_id = get_sp_id(&m, &sp_member_ids); - - members.push(m); - } - - let sys = System { - pk_userid: pk_sysid.to_string(), - sp_userid: sp_user_id, - members: members.clone(), - }; - - let json = serde_json::to_string(&sys); - let _ = fs::write(format!("{}/system.json", config_path), &json.unwrap()); - Ok(()) } -fn set_member(config_path: String, tf_members: Vec) -> Result<(), &'static str> { - let config: Config = match get_config(&config_path) { - Ok(c) => c, - Err(e) => return Err(e) - }; +fn set_member(app: &App, tf_members: Vec) -> Result<()> { - let system: System = get_system(&config_path); + let system = &app.sys.ok_or(eyre!("No system data found"))?; let mut to_front: Vec = Vec::new(); for member in &tf_members { for mem in &system.members { - if mem.name.to_lowercase() == member.to_lowercase() || mem.alias.to_lowercase() == member.to_lowercase() { - println!("Member {member} found"); + let lowcase_aliases: Vec = mem.aliases.iter().map(|x| x.to_lowercase()).collect(); + if mem.name.to_lowercase() == member.to_lowercase() || lowcase_aliases.iter().any(|e| member.contains(e)) { + println!("Member {member} found."); to_front.push(mem.clone()); break; } } } - if to_front.len() == tf_members.len() { - let fronters = get_fronters(&config.pk_key, &config.sp_key, &system, ForceFrom::None); - pk_set_fronters(&config.pk_key, &system, &to_front, &fronters); - sp_set_fronters(&config.sp_key, &to_front, &fronters); - } else { + if to_front.len() == tf_members.len() { + app.set_fronters(to_front)?; + } BOOKMARK else { println!("One or more members were not found. Known members:\n--------------------------"); - let _ = memberlist(config_path); + let _ = memberlist(app); println!("--------------------------\nIf a member is missing from the system try running \"pluralsync sync\" to refresh the local database"); - return Err("Missing member"); + return Err(eyre!("Missing member")); } #[cfg(feature = "jlog")] { @@ -328,8 +130,10 @@ fn set_member(config_path: String, tf_members: Vec) -> Result<(), &'stat names.push(String::from(&m.name)); } let log_fronters = names.join(" || "); + #[cfg(target_os = "windows")] std::process::Command::new("jlog").args(["info", format!("Switch registered: {}", log_fronters)]).output().expect("Logging error"); + #[cfg(not(target_os = "windows"))] std::process::Command::new("jlog").arg("info").arg(format!("Switch registered: {}", log_fronters)).output().expect("Logging error"); } @@ -425,7 +229,7 @@ fn get(config_path: String, ff: ForceFrom) -> Result, &'static str> #[cfg(feature = "avatar")] #[allow(unused_variables)] -fn update_avatars(config_path: String, discord: bool, fedi: bool) -> Result<(), &'static str>{ +fn update_avatars(config_path: String, #[cfg(feature = "discord")] discord: bool, #[cfg(feature = "fedi")] fedi: bool) -> Result<(), &'static str>{ #[allow(unused_mut)] let mut config: Config = match get_config(&config_path) { Ok(c) => c, @@ -523,317 +327,3 @@ fn fedi_module(config: &Config) { } } } - -fn pk_get_system(key: &str) -> Value { - let url = format!("{}/systems/@me", PK_URL); - - let res = http_get(url,key); - return serde_json::from_str(&res.unwrap()).unwrap(); -} - -fn pk_get_members(key: &str, sysid: &str) -> Vec { - let url = format!("{}/systems/{}/members", PK_URL, sysid); - - let res = http_get(url,key); - let datas: Vec = serde_json::from_str(&res.unwrap()).unwrap(); - - return datas; -} - -fn pk_get_fronters(key: &str, sys: &System) -> Vec { - let url = format!("{}/systems/{}/fronters", PK_URL, sys.pk_userid); - - let res = http_get(url,key); - let data: Value = serde_json::from_str(&res.unwrap()).unwrap(); - let memberdata = &data["members"].as_array(); - - let mut members: Vec = Vec::new(); - for member in memberdata { - for m in member.into_iter() { - for dbmem in &sys.members { - if m["name"].as_str().unwrap() == dbmem.name { - members.push(dbmem.clone()); - } - } - } - } - - return members; -} - -fn pk_set_fronters(key: &str, sys: &System, to_front: &Vec, fronters: &Fronters) { - let url = format!("{}/systems/{}/switches", PK_URL, sys.pk_userid); - - if to_front != &fronters.pk { - let mut frontcodes = Vec::new(); - for tf in to_front { - frontcodes.push(String::from(&tf.pk_id)); - } - - let mut body: HashMap<&str, Vec> = HashMap::new(); - body.insert("members", frontcodes); - - let client = reqwest::Client::new(); - let rb = client - .post(url) - .json(&body) - .header("content-type", "application/json; charset=utf-8") - .header(USER_AGENT, "Pluralsync") - .header(AUTHORIZATION, key); - - match http_request(rb) { - Ok(_) => (), - Err(e) => println!("{}", e.to_string()), - } - - } else { - println!("Members already fonting"); - } - -} - -fn sp_get_userid(key: &str) -> String { - let url = format!("{}/me", SP_URL); - - let res = http_get(url,key); - let json_res : Value = serde_json::from_str(&res.unwrap()).unwrap(); - return json_res["id"].as_str().unwrap().to_string(); -} - -fn sp_get_memberids(key: &str, system_id: &str) -> HashMap { - let url = format!("{}/members/{}", SP_URL, system_id); - - let res = http_get(url,key); - let datas: Vec = serde_json::from_str(&res.unwrap()).unwrap(); - - let mut sp_memberdata: HashMap = HashMap::new(); - for data in datas { - sp_memberdata.insert(String::from(data["content"]["name"].as_str().unwrap()), String::from(data["id"].as_str().unwrap())); - } - - return sp_memberdata; -} - -fn get_sp_id(mem: &Member, ids: &HashMap) -> String { - - let mut member_id = String::new(); - - for (mn, mid) in ids { - if &mem.name == mn || &mem.alias == mn { - member_id = String::from(mid); - } - } - - return member_id; -} - -fn sp_get_fronters(key: &str, sys: &System) -> Vec { - let url = format!("{}/fronters", SP_URL); - - let res = http_get(url,key); - let datas: Vec = serde_json::from_str(&res.unwrap()).unwrap(); - - let mut members = Vec::new(); - for data in datas { - let sp_id = &data["content"]["member"].as_str().unwrap(); - for member in &sys.members { - if &member.sp_id == sp_id { - members.push(member.clone()); - } - } - - } - return members; -} - -fn sp_get_frontids(key: &str, to_id: &Member) -> String { - let url = format!("{}/fronters", SP_URL); - - let res = http_get(url,key); - let datas: Vec = serde_json::from_str(&res.unwrap()).unwrap(); - - for data in datas { - let sp_f_id = &data["id"].as_str().unwrap(); - let sp_id = &data["content"]["member"].as_str().unwrap(); - if sp_id.to_string() == to_id.sp_id { - return sp_f_id.to_string(); - } - } - return String::new(); -} - -fn sp_set_fronters(key: &str, to_front: &Vec, fronters: &Fronters) { - if to_front == &fronters.sp { - println!("Members already fonting"); - return; - } - - for fronting_member in &fronters.sp { - if !to_front.contains(&fronting_member) { - let f_id = sp_get_frontids(&key, &fronting_member); - - let url = format!("{}/frontHistory/{}", SP_URL, f_id); - - #[derive (Serialize)] - #[allow (non_snake_case)] - struct SpRem { - live: bool, - endTime: u64 - } - - let end_time = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).expect("wa").as_secs(); - let rem = SpRem { - live: false, - endTime: end_time * 1000 - }; - - let body = serde_json::to_string(&rem).expect("Error"); - - - let client = reqwest::Client::new(); - let rb = client - .patch(url) - .body(body) - .header("content-type", "application/json; charset=utf-8") - .header(USER_AGENT, "Pluralsync") - .header(AUTHORIZATION, key); - - match http_request(rb) { - Ok(_) => (), - Err(e) => println!("{}", e.to_string()), - } - } - } - - for tf_member in to_front { - if !fronters.sp.contains(&tf_member) { - let url = format!("{}/frontHistory", SP_URL); - - #[derive (Serialize)] - #[allow (non_snake_case)] - struct SpAdd { - member: String, - custom: bool, - live: bool, - startTime: u64 - } - - let start_time = std::time::SystemTime::now().duration_since(std::time::SystemTime::UNIX_EPOCH).expect("wa").as_secs(); - let rem = SpAdd { - member: String::from(&tf_member.sp_id), - custom: false, - live: true, - startTime: start_time * 1000 - }; - - let body = serde_json::to_string(&rem).expect("Error"); - - let client = reqwest::Client::new(); - let rb = client - .post(url) - .body(body) - .header("content-type", "application/json; charset=utf-8") - .header(USER_AGENT, "Pluralsync") - .header(AUTHORIZATION, key); - - match http_request(rb) { - Ok(_) => (), - Err(e) => println!("{}", e.to_string()), - } - } - } -} - -fn load_json(path: String) -> Value { - if Path::new(&path).exists() { - let config_data = fs::read_to_string(&path).expect("File not found"); - return serde_json::from_str(&config_data).unwrap(); - } else { - println!("Config file in {path} not found"); - return Value::Null; - } -} - -fn get_config(config_path: &str) -> Result { - let path = format!("{}/config.json", config_path); - if Path::new(config_path).exists() { - - let result = load_json(String::from(&path)); - - if result == Value::Null { - let _ = fs::write(path, EXAMPLE_JSON); - return Err("Config file missing, creating template in {path}"); - } else { - let config: Config = serde_json::from_value(result).expect("Error unwrapping"); - return Ok(config); - } - } else { - let _ = create_dir(config_path); - let _ = fs::write(path, EXAMPLE_JSON); - return Err("Directory {config_path} does not exist. Creating with template config"); - } -} - -fn get_system(config_path: &str) -> System { - let path = format!("{}/system.json", config_path); - - let mut result = load_json(String::from(&path)); - - if result == Value::Null { - println!("Syncing system config"); - let _ = sync(String::from(config_path)); - result = load_json(String::from(&path)); - } - - let vec = serde_json::to_vec(&result).unwrap(); - let sys = serde_json::from_slice::(&vec).unwrap(); - - return sys; - -} - -fn get_fronters(pk_key: &str, sp_key: &str, sys: &System, ff: ForceFrom) -> Fronters { - let mut fronters = Fronters::new( - pk_get_fronters(pk_key, sys), - sp_get_fronters(sp_key, sys) - ); - - if fronters.pk != fronters.sp { - match ff { - ForceFrom::PK => { - fronters.sp = fronters.pk.clone() - } - ForceFrom::SP => { - fronters.pk = fronters.sp.clone() - } - ForceFrom::None => { - } - } - } - - return fronters; -} - -#[tokio::main] -async fn http_get(url: String, key: &str) -> Result> { - let client = reqwest::Client::new(); - let rb = client - .get(url) - .header(USER_AGENT, "Pluralsync") - .header(AUTHORIZATION, key); - let res = rb.send() - .await? - .text() - .await?; - - Ok(res) -} - -#[tokio::main] -async fn http_request(rb: RequestBuilder) -> Result> { - let res = rb.send() - .await? - .text() - .await?; - Ok(res) -} diff --git a/src/systemdata.rs b/src/systemdata.rs index 46e9784..5226fc9 100644 --- a/src/systemdata.rs +++ b/src/systemdata.rs @@ -1,4 +1,15 @@ +use std::path::Path; +use std::fs; + use serde::{Serialize, Deserialize}; +use serde_json::Value; + +use color_eyre::eyre::{eyre, Result}; + +use crate::api::{pk, sp}; +use crate::utils::load_json; +use crate::app::App; +use crate::clap_ps::ForceFrom; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct System { @@ -12,17 +23,127 @@ pub struct Member { pub pk_id: String, pub sp_id: String, pub name: String, - pub alias: String + pub aliases: Vec +} + +impl System { + pub fn get_system(app: &App, update: bool) -> Result{ + + let sys_file_path = format!("{}/system.json", app.cfg_dir); + + if Path::new(&sys_file_path).exists() && !update { + let json_system: Value = load_json(&sys_file_path)?; + let sys: System = serde_json::from_value(json_system)?; + Ok(sys) + } else { + match Self::update_system(app) { + Ok(s) => { + Ok(s) + }, + Err(e) => { + Err(e) + } + } + } + } + + fn update_system(app: &App) -> Result { + + if let Some(conf) = &app.cfg { + + // PluralKit + let pk_sys = pk::get_system(&conf.pk_key)?; + let pk_id = pk_sys["id"].to_string(); + let pk_members = pk::get_members(&conf.pk_key, &pk_id)?; + + // Simplyplural + let sp_user_id = sp::get_user_id(&conf.sp_key)?; + let sp_member_ids = sp::get_member_ids(&conf.sp_key, &sp_user_id)?; + + // Get members + let mut members: Vec = Vec::new(); + for pk_mem in pk_members { + let mut m = Member { + pk_id: pk_mem["id"].to_string(), + sp_id: String::new(), + name: pk_mem["name"].to_string(), + aliases: Vec::new(), + }; + + if pk_mem["display_name"].as_str() != None { + m.aliases.push(pk_mem["display_name"].to_string()); + } + + m.sp_id = sp::get_member_id(&m, &sp_member_ids)?; + + members.push(m); + } + + // Create system + let sys = System { + pk_userid: pk_id.to_string(), + sp_userid: sp_user_id, + members: members.clone(), + }; + + let json = serde_json::to_string(&sys)?; + fs::write(format!("{}/system.json", &app.cfg_dir), &json)?; + + return Ok(sys); + } + + Err(eyre!("No configuration loaded!")) + } } #[derive(Debug)] pub struct Fronters { - pub sp: Vec, - pub pk: Vec, + pub sp: Vec, + pub pk: Vec, } impl Fronters { - pub fn new(pk: Vec, sp: Vec) -> Self { - Self {sp, pk} - } + pub fn new(pk: Vec, sp: Vec) -> Self { + Self {sp, pk} + } + + pub fn get(app: &App, ff: ForceFrom) -> Result { + if let Some(cfg) = &app.cfg { + if let Some(sys) = &app.sys { + let mut fronters = Fronters::new( + pk::get_fronters(&cfg.pk_key, &sys)?, + sp::get_fronters(&cfg.sp_key, &sys)?, + ); + + if fronters.pk != fronters.sp { + match ff { + ForceFrom::SP => { + fronters.pk = fronters.sp.clone(); + } + _ => { + fronters.sp = fronters.pk.clone(); + } + } + } + + return Ok(fronters); + } + } + Err(eyre!("System or configuration missing")) + } + + pub fn set(app: &App, to_front: Vec) -> Result { + + if let Some(cfg) = &app.cfg { + if let Some(sys) = &app.sys { + if let Some(fronters) = &app.fronters { + pk::set_fronters(&cfg.pk_key, &sys, &to_front, &fronters)?; + sp::set_fronters(&cfg.sp_key, &to_front, &fronters)?; + + return Ok(Fronters::new(to_front.clone(), to_front.clone())); + } + } + } + Err(eyre!("Missing app information")) + } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..48d6ecd --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,11 @@ +use std::fs; +use serde_json::Value; +use color_eyre::eyre::Result; + +pub fn load_json(file_path: &str) -> Result { + + let file_data = fs::read_to_string(&file_path)?; + let json_value: Value = serde_json::from_str(&file_data)?; + + Ok(json_value) +}