diff --git a/Cargo.lock b/Cargo.lock index 92f728a..6533d4f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,10 +18,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "anstream" -version = "0.6.7" +name = "ahash" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" +checksum = "42cd52102d3df161c77a887b608d7a4897d7cc112886a9537b738a887a03aaff" +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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", @@ -33,9 +51,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" @@ -88,9 +106,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "bitflags" @@ -100,15 +118,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "d32a994c2b3ca201d9b263612a374263f05e7adde37c4707f693dcd375076d1f" [[package]] name = "bytes" @@ -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" @@ -133,9 +166,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.4.17" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80932e03c33999b9235edb8655bc9df3204adc9887c2f95b50cb1deb9fd54253" +checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" dependencies = [ "clap_builder", "clap_derive", @@ -143,9 +176,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.17" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c0db58c659eef1c73e444d298c27322a1b52f6927d2ad470c0c0f96fa7b8fa" +checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" dependencies = [ "anstream", "anstyle", @@ -155,21 +188,48 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.49", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[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" @@ -178,10 +238,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] -name = "core-foundation" -version = "0.9.3" +name = "compact_str" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "ryu", + "static_assertions", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -189,9 +262,34 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.4.2", + "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" @@ -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" @@ -231,12 +335,22 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f258a7194e7f7c2a7837a8913aeab7fd8c383457034fa20ce4dd3dcb813e8eb8" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" dependencies = [ "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.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]] @@ -268,45 +382,45 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-task", @@ -316,9 +430,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -327,15 +441,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.0" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" [[package]] name = "h2" -version = "0.3.22" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d6250322ef6e60f93f9a2162799302cd6f68f79f6e5d85c8c16f14d1d958178" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -352,9 +466,13 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] [[package]] name = "heck" @@ -364,9 +482,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd" [[package]] name = "http" @@ -381,9 +499,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", "http", @@ -404,9 +522,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" dependencies = [ "bytes", "futures-channel", @@ -419,7 +537,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", @@ -441,24 +559,36 @@ dependencies = [ [[package]] name = "idna" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] -name = "indexmap" -version = "2.1.0" +name = "indenter" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" dependencies = [ "equivalent", "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" @@ -466,16 +596,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] -name = "itoa" -version = "1.0.9" +name = "itertools" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.65" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c0c35952f67de54bb584e9fd912b3023117cbafc0a77d8f3dee1fb5f572fe8" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -488,9 +627,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libredox" @@ -498,16 +637,16 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "libc", "redox_syscall", ] [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -526,10 +665,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] -name = "memchr" -version = "2.6.4" +name = "lru" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "db2c024b41519440580066ba82aab04092b333e09066a5eb86c7c4890df31f22" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "mime" @@ -549,20 +697,21 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.48.0", ] @@ -597,26 +746,26 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.59" +version = "0.10.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a257ad03cd8fb16ad4172fedf8094451e1af1c4b70097636ef2eac9a5f0cc33" +checksum = "15c9d69dd87a29568d4d017cfe8ec518706046a05184e5aea92d0af890b803c8" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if", "foreign-types", "libc", @@ -633,7 +782,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.49", ] [[package]] @@ -644,9 +793,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.95" +version = "0.9.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a4130519a360279579c2053038317e40eff64d13fd3f004f9e1b72b8a6aaf9" +checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae" dependencies = [ "cc", "libc", @@ -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" @@ -684,10 +839,16 @@ dependencies = [ ] [[package]] -name = "percent-encoding" -version = "2.3.0" +name = "paste" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" @@ -703,16 +864,19 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "pluralsync" version = "1.2.0" dependencies = [ "clap", + "color-eyre", + "crossterm", "dirs", + "ratatui", "reqwest", "serde", "serde_json", @@ -721,22 +885,42 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] +[[package]] +name = "ratatui" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcb12f8fbf6c62614b0d56eb352af54f6a22410c3b079eb53ee93c7b97dd31d8" +dependencies = [ + "bitflags 2.4.2", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "itertools", + "lru", + "paste", + "stability", + "strum", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "redox_syscall" version = "0.4.1" @@ -759,9 +943,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.22" +version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ "base64", "bytes", @@ -782,9 +966,11 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", @@ -804,30 +990,45 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustix" -version = "0.38.24" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ad981d6c340a49cdc40a1028d9c6084ec7e9fa33fcb839cab656a267071e234" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] -name = "ryu" -version = "1.0.15" +name = "rustls-pemfile" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64", +] + +[[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.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -861,29 +1062,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.192" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.49", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ "itoa", "ryu", @@ -902,6 +1103,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" @@ -922,19 +1153,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" - -[[package]] -name = "socket2" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" -dependencies = [ - "libc", - "winapi", -] +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" @@ -947,22 +1168,77 @@ dependencies = [ ] [[package]] -name = "strsim" -version = "0.10.0" +name = "stability" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +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.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[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.49", +] [[package]] name = "syn" -version = "2.0.39" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "system-configuration" version = "0.5.1" @@ -986,35 +1262,44 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.49", +] + +[[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]] @@ -1034,9 +1319,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -1046,7 +1331,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2", "tokio-macros", "windows-sys 0.48.0", ] @@ -1059,7 +1344,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.49", ] [[package]] @@ -1109,13 +1394,35 @@ 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]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicase" @@ -1128,9 +1435,9 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -1148,10 +1455,22 @@ dependencies = [ ] [[package]] -name = "url" -version = "2.4.1" +name = "unicode-segmentation" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +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.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", @@ -1164,6 +1483,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" @@ -1193,9 +1518,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.88" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7daec296f25a1bae309c0cd5c29c4b260e510e6d813c286b19eaadf409d40fce" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1203,24 +1528,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.88" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e397f4664c0e4e428e8313a469aaa58310d302159845980fd23b0f22a847f217" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.49", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.38" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afec9963e3d0994cac82455b2b3502b81a7f40f9a0d32181f7528d9f4b43e02" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" dependencies = [ "cfg-if", "js-sys", @@ -1230,9 +1555,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.88" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5961017b3b08ad5f3fe39f1e79877f8ee7c23c5e5fd5eb80de95abc41f1f16b2" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1240,28 +1565,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.88" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5353b8dab669f5e10f5bd76df26a9360c748f054f862ff5f3f8aae0c7fb3907" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.49", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.88" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "web-sys" -version = "0.3.65" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5db499c5f66323272151db0e666cd34f78617522fb0c1604d31a27c50c206a85" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", @@ -1430,3 +1755,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.49", +] 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..14a1c62 --- /dev/null +++ b/src/api.rs @@ -0,0 +1,292 @@ +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)?; + + let values: Vec = serde_json::from_str(&response)?; + + Ok(values) + } + + 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).unwrap(); + 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"].as_str().unwrap().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"].as_str().unwrap().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"].as_str().unwrap().to_string(), + member["id"].as_str().unwrap().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"].as_str().unwrap().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..e6e37a0 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,186 @@ +use crate::configdata::*; +use crate::systemdata::*; +use crate::clap_ps::ForceFrom; + +use color_eyre::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) + }, + } + + } + + fn get(&mut self, ff: ForceFrom) -> Result<()> { + + self.fronters = Some(Fronters::get(self, ff)?); + + Ok(()) + } + + fn set(&mut self, to_front: Vec) -> Result<()> { + + self.get(ForceFrom::None)?; + + self.fronters = Some(Fronters::set(self, to_front)?); + + Ok(()) + } + + fn add(&mut self, to_front: Vec) -> Result<()> { + + let aux_fronters: Fronters = Fronters::get(self, ForceFrom::None)?; + + let mut addfronters: Vec = Vec::new(); + addfronters.append(&mut aux_fronters.pk.clone()); + addfronters.append(&mut to_front.clone()); + + self.fronters = Some(Fronters::set(self, addfronters)?); + + Ok(()) + } + + pub fn memberlist(&self) -> Result<()> { + + if let Some(sys) = &self.sys { + for member in &sys.members { + println!("{} / {}", member.name, member.aliases.join(" | ")); + } + } + + Ok(()) + } + + pub fn set_member(&mut self, tf_members: Vec, append: bool) -> Result<()> { + + if let Some(sys) = &self.sys { + let mut to_front: Vec = Vec::new(); + for member in &tf_members { + for mem in &sys.members { + 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() { + if !append { + self.set(to_front)?; + } else { + self.add(to_front)?; + } + } else { + println!("One or more members were not found. Known members:\n--------------------------"); + self.memberlist()?; + println!("--------------------------\nIf a member is missing from the system try running \"pluralsync update\" to refresh the local database"); + return Err(eyre!("Missing member")); + } + + #[cfg(feature = "jlog")] { + let mut names = Vec::new(); + for m in &to_front { + 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"); + } + + return Ok(()); + } + Err(eyre!("No system found")) + } + + pub fn get_front(&mut self, ff: ForceFrom) -> Result> { + + self.get(ff)?; + + if let Some(fronters) = &self.fronters { + let mut names: Vec = Vec::new(); + for member in &fronters.pk { + names.push(String::from(&member.name)); + } + + let fronters = names.join(" || "); + println!("Currently fronting: {}", fronters); + std::fs::write(format!("{}/.front", &self.cfg_dir), &fronters)?; + + return Ok(names); + } + Err(eyre!("No fronters found")) + } +} diff --git a/src/clap_ps.rs b/src/clap_ps.rs index 37e0222..6eb5fcd 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 - Sync, + /// Fetches the system member information from PluralKit and SimplyPlural + Update, + /// 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..ce602b1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,839 +1,74 @@ -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; +mod app; +mod configdata; +mod systemdata; +mod api; +mod utils; +mod clap_ps; use dirs; -mod configdata; -use configdata::*; +use app::*; +use utils::update_avatars; -mod systemdata; -use systemdata::*; - -mod clap_ps; use clap_ps::*; use clap::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); + Commands::Update => { + app.load_system(true)?; }, - // SET MEMBER - #[cfg(all(feature = "discord", feature = "fedi"))] - Commands::Set { members, discord, fedi } => { - res = set_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::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); - }, - Err(_) => (), - } - } + Commands::TUI => { + () }, - // ADD MEMBER - #[cfg(all(feature = "discord", feature = "fedi"))] - Commands::Add { members, discord, fedi } => { - res = add_member(config_path.clone(), members); + Commands::Set { members, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi } => { + app.set_member(members, false)?; - #[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); - }, - Err(_) => (), - } - } + #[cfg(feature = "avatar")] + update_avatars(&mut app, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi)?; }, - // Get MEMBER - #[cfg(all(feature = "discord", feature = "fedi"))] - Commands::Get { force_from, discord, fedi } => { + Commands::Add { members, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi } => { + app.set_member(members, true)?; + + #[cfg(feature = "avatar")] + update_avatars(&mut app, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi)?; + }, + + Commands::Get { force_from, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi } => { let ff = match force_from { Some(x) => x, None => ForceFrom::None, }; - let _ = get(config_path.clone(), ff); + app.get_front(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); + update_avatars(&mut app, #[cfg(feature = "discord")] discord, #[cfg(feature = "fedi")] fedi)?; }, Commands::Members => { - res = memberlist(config_path); + app.memberlist()?; }, } - - 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) - }; - - let system: System = get_system(&config_path); - - 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"); - 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 { - println!("One or more members were not found. Known members:\n--------------------------"); - let _ = memberlist(config_path); - println!("--------------------------\nIf a member is missing from the system try running \"pluralsync sync\" to refresh the local database"); - return Err("Missing member"); - } - - #[cfg(feature = "jlog")] { - let mut names = Vec::new(); - for m in &to_front { - 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"); - } - - Ok(()) -} - -fn add_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) - }; - - let system: System = get_system(&config_path); - - 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() { - 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); - - let mut aux: Vec = Vec::new(); - aux.append(&mut fronters.pk.clone()); - aux.append(&mut to_front); - to_front = aux; - - pk_set_fronters(&config.pk_key, &system, &to_front, &fronters); - sp_set_fronters(&config.sp_key, &to_front, &fronters); - - let _ = get(config_path, ForceFrom::None); - - } else { - println!("One or more members were not found. Known members:\n--------------------------"); - let _ = memberlist(config_path); - println!("--------------------------\nIf a member is missing from the system try running \"pluralsync sync\" to refresh the local database"); - return Err("Missing member"); - } - - #[cfg(feature = "jlog")] { - let mut names = Vec::new(); - for m in &to_front { - 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"); - } - - Ok(()) -} - -fn memberlist(config_path: String) -> Result<(), &'static str> { - let sys = get_system(&config_path); - - for mem in sys.members { - if mem.name != mem.alias { - println!("{} / {}", mem.name, mem.alias); - } else { - println!("{}", mem.name); - } - } - - Ok(()) - -} - -fn get(config_path: String, ff: ForceFrom) -> Result, &'static str> { - let config: Config = match get_config(&config_path) { - Ok(c) => c, - Err(e) => return Err(e) - }; - let sys = get_system(&config_path); - - let f = get_fronters(&config.pk_key, &config.sp_key, &sys, ff); - let mut names = Vec::new(); - for m in &f.pk { - names.push(String::from(&m.name)); - } - let fronters = names.join(" || "); - println!("Currently fronting: {}", fronters); - let _ = fs::write(format!("{}/.front", config_path), fronters); - - Ok(names) -} - -#[cfg(feature = "avatar")] -#[allow(unused_variables)] -fn update_avatars(config_path: String, discord: bool, fedi: bool) -> Result<(), &'static str>{ - #[allow(unused_mut)] - let mut config: Config = match get_config(&config_path) { - Ok(c) => c, - Err(e) => return Err(e) - }; - - let names = get(config_path, ForceFrom::None).unwrap(); - - - #[cfg(feature = "avatar")] - match avatar_module(&config, &names) { - Ok(_) => { - #[cfg(feature = "discord")] { - config.disc_module.enabled |= discord; - discord_module(&config); - } - #[cfg(feature = "fedi")] { - config.fedi_module.enabled |= fedi; - fedi_module(&config); - } - }, - Err(e) => println!("{}", e), - } - - - Ok(()) -} - -#[cfg(feature = "avatar")] -fn avatar_module(config: &Config, names: &Vec) -> Result<(), &'static str>{ - if config.avatar_module.enabled { - let mut whitelisted_names: Vec = names.iter().map(|i| i.to_lowercase()).collect(); - let blacklist: Vec = config.avatar_module.blacklist.iter().map(|i| i.to_lowercase()).collect(); - for name in names { - if blacklist.contains(&name.to_lowercase()) { - let index = whitelisted_names.iter().position(|x| *x == name.to_lowercase()).unwrap(); - whitelisted_names.remove(index); - println!("{} blacklisted from avatar module", name); - } - } - - let avatarnames = whitelisted_names.join("").to_lowercase() + ".png"; - - if Path::new(&config.avatar_module.avatar_output_path).exists() { - let _ = remove_file(&config.avatar_module.avatar_output_path); - } - - if cfg!(target_os = "windows") { - let cmdaug = format!("copy {}\\{} {}", &config.avatar_module.avatar_folder, avatarnames, &config.avatar_module.avatar_output_path); - Command::new("cmd").args(["/C", &cmdaug]).output().expect("Avatar module error"); - } else { - Command::new("sh").arg("-c").arg(format!("cp {}/{} {}", &config.avatar_module.avatar_folder, avatarnames, &config.avatar_module.avatar_output_path)).output().expect("Avatar module error"); - } - if Path::new(&config.avatar_module.avatar_output_path).exists() { - println!("Avatar module finished"); - return Ok(()) - } else { - return Err("Avatar module failed") - } - } else { - Err("Avatar mode disabled") - } - -} - -#[cfg(feature = "discord")] -fn discord_module(config: &Config) { - if config.disc_module.enabled { - if cfg!(target_os = "windows") { - let mut c = Command::new("cmd").args(["/C", format!("{} {}", &config.disc_module.python_path, &config.disc_module.script_path).as_str()]).spawn().expect("Discord module error"); - let _ = c.wait().expect("Error"); - } else { - let mut c = Command::new("sh").arg("-c").arg(format!("{} {}", &config.disc_module.python_path, &config.disc_module.script_path)).spawn().expect("Discord module error"); - let _ = c.wait().expect("Error"); - } - } -} - -#[cfg(feature = "fedi")] -fn fedi_module(config: &Config) { - if config.fedi_module.enabled { - - let client = reqwest::Client::new(); - let form = Form::new().part("avatar", Part::bytes(fs::read(config.avatar_module.avatar_output_path.clone()).unwrap()).file_name("face.png").mime_str("image/png").unwrap()); - let rb = client - .patch(config.fedi_module.instance.clone() + "/api/v1/accounts/update_credentials") - .multipart(form) - .header(USER_AGENT, "Pluralsync") - .header(AUTHORIZATION, format!("Bearer {}", &config.fedi_module.token).as_str()); - - println!("Fedi module finished"); - match http_request(rb) { - Ok(_) => (), - Err(e) => println!("{}", e.to_string()), - } - } -} - -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..34eaae5 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,129 @@ 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"].as_str().unwrap(); + 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"].as_str().unwrap().to_string(), + sp_id: String::new(), + name: pk_mem["name"].as_str().unwrap().to_string(), + aliases: Vec::new(), + }; + + if pk_mem["display_name"].as_str() != None { + m.aliases.push(pk_mem["display_name"].as_str().unwrap().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())); + } + return Err(eyre!("Missing Fronters")); + } + return Err(eyre!("Missing System")); + } + Err(eyre!("Missing Config")) + } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..645054b --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,120 @@ +use std::fs; +use serde_json::Value; +use color_eyre::eyre::Result; + +#[cfg(feature = "avatar")] +use { + std::{ + process::Command, + fs::remove_file, + path::Path, + }, + crate::{ + configdata::Config, + app::App, + clap_ps::ForceFrom, + }, + color_eyre::eyre::eyre, +}; + +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) +} + +#[cfg(feature = "avatar")] +pub fn update_avatars(app: &mut App, #[cfg(feature = "discord")] discord: bool, #[cfg(feature = "fedi")] fedi: bool) -> Result<()>{ + + if let Some(cfg) = app.cfg.clone() { + let names = &app.get_front(ForceFrom::None)?; // TODO CHANGE INTO USING LOCAL FRONT + + match avatar_module(&cfg, &names) { + Ok(_) => { + #[cfg(feature = "discord")] { + config.disc_module.enabled |= discord; + discord_module(&config); + } + #[cfg(feature = "fedi")] { + config.fedi_module.enabled |= fedi; + fedi_module(&config); + } + }, + Err(e) => println!("{}", e), + } + return Ok(()); + } + Err(eyre!("No config found")) +} + +#[cfg(feature = "avatar")] +fn avatar_module(config: &Config, names: &Vec) -> Result<(), &'static str>{ + if config.avatar_module.enabled { + let mut whitelisted_names: Vec = names.iter().map(|i| i.to_lowercase()).collect(); + let blacklist: Vec = config.avatar_module.blacklist.iter().map(|i| i.to_lowercase()).collect(); + for name in names { + if blacklist.contains(&name.to_lowercase()) { + let index = whitelisted_names.iter().position(|x| *x == name.to_lowercase()).unwrap(); + whitelisted_names.remove(index); + println!("{} blacklisted from avatar module", name); + } + } + + let avatarnames = whitelisted_names.join("").to_lowercase() + ".png"; + + if Path::new(&config.avatar_module.avatar_output_path).exists() { + let _ = remove_file(&config.avatar_module.avatar_output_path); + } + + if cfg!(target_os = "windows") { + let cmdaug = format!("copy {}\\{} {}", &config.avatar_module.avatar_folder, avatarnames, &config.avatar_module.avatar_output_path); + Command::new("cmd").args(["/C", &cmdaug]).output().expect("Avatar module error"); + } else { + Command::new("sh").arg("-c").arg(format!("cp {}/{} {}", &config.avatar_module.avatar_folder, avatarnames, &config.avatar_module.avatar_output_path)).output().expect("Avatar module error"); + } + if Path::new(&config.avatar_module.avatar_output_path).exists() { + println!("Avatar module finished"); + Ok(()) + } else { + Err("Avatar module failed") + } + } else { + Err("Avatar mode disabled") + } + +} + +#[cfg(feature = "discord")] +fn discord_module(config: &Config) { + if config.disc_module.enabled { + if cfg!(target_os = "windows") { + let mut c = Command::new("cmd").args(["/C", format!("{} {}", &config.disc_module.python_path, &config.disc_module.script_path).as_str()]).spawn().expect("Discord module error"); + let _ = c.wait().expect("Error"); + } else { + let mut c = Command::new("sh").arg("-c").arg(format!("{} {}", &config.disc_module.python_path, &config.disc_module.script_path)).spawn().expect("Discord module error"); + let _ = c.wait().expect("Error"); + } + } +} + +#[cfg(feature = "fedi")] +fn fedi_module(config: &Config) { + if config.fedi_module.enabled { + + let client = reqwest::Client::new(); + let form = Form::new().part("avatar", Part::bytes(fs::read(config.avatar_module.avatar_output_path.clone()).unwrap()).file_name("face.png").mime_str("image/png").unwrap()); + let rb = client + .patch(config.fedi_module.instance.clone() + "/api/v1/accounts/update_credentials") + .multipart(form) + .header(USER_AGENT, "Pluralsync") + .header(AUTHORIZATION, format!("Bearer {}", &config.fedi_module.token).as_str()); + + println!("Fedi module finished"); + match http_request(rb) { + Ok(_) => (), + Err(e) => println!("{}", e.to_string()), + } + } +}