From 3bd6195b7c35dd16b10d2ff02bd60019eaf3d901 Mon Sep 17 00:00:00 2001 From: yu256 <122450013+yu256@users.noreply.github.com> Date: Sat, 16 Mar 2024 16:27:54 +0900 Subject: [PATCH] first commit --- .github/workflows/build.yaml | 52 ++ .gitignore | 1 + Cargo.lock | 1201 ++++++++++++++++++++++++++++++++++ Cargo.toml | 27 + src/authorize.rs | 84 +++ src/fetcher.rs | 111 ++++ src/log/log_reader.rs | 106 +++ src/log/mod.rs | 4 + src/log/processor.rs | 47 ++ src/main.rs | 38 ++ src/udp_client.rs | 36 + src/var.rs | 6 + src/vrc_structs.rs | 117 ++++ src/websocket/mod.rs | 1 + src/websocket/stream.rs | 135 ++++ src/xsoverlay/mod.rs | 3 + src/xsoverlay/notify_join.rs | 97 +++ src/xsoverlay/websocket.rs | 64 ++ 18 files changed, 2130 insertions(+) create mode 100644 .github/workflows/build.yaml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/authorize.rs create mode 100644 src/fetcher.rs create mode 100644 src/log/log_reader.rs create mode 100644 src/log/mod.rs create mode 100644 src/log/processor.rs create mode 100644 src/main.rs create mode 100644 src/udp_client.rs create mode 100644 src/var.rs create mode 100644 src/vrc_structs.rs create mode 100644 src/websocket/mod.rs create mode 100644 src/websocket/stream.rs create mode 100644 src/xsoverlay/mod.rs create mode 100644 src/xsoverlay/notify_join.rs create mode 100644 src/xsoverlay/websocket.rs diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 0000000..7d61424 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,52 @@ +name: 'publish' +on: + push: + branches: + - release + +jobs: + publish: + permissions: + contents: write + strategy: + fail-fast: false + matrix: + platform: [windows-latest] + + runs-on: ${{ matrix.platform }} + steps: + - name: Checkout repository + uses: actions/checkout@v3.3.0 + + - name: Rust setup + uses: dtolnay/rust-toolchain@stable + + - name: Rust cache + uses: swatinem/rust-cache@v2 + with: + shared-key: "vrc-yutils" + + - name: build + run: cargo build --release + + - name: Create Release + id: create_release + uses: actions/create-release@v1.1.4 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: draft-${{ matrix.platform }} + release_name: draft + draft: true + prerelease: false + + - name: Upload Release Asset + id: upload-release-asset + uses: actions/upload-release-asset@v1.0.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./target/release/vrc-yutils.exe + asset_name: vrc-yutils.exe + asset_content_type: application/octet-stream \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..03fd3c6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1201 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" + +[[package]] +name = "async-once-cell" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9338790e78aa95a416786ec8389546c4b6a1dfc3dc36071ed9518a9413a542eb" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[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", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "data-encoding" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "hyper" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "security-framework" +version = "2.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "socket2" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "num_cpus", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +dependencies = [ + "futures-util", + "log", + "native-tls", + "tokio", + "tokio-native-tls", + "tungstenite", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "tungstenite" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "vrc-yutils" +version = "0.0.1-beta1" +dependencies = [ + "anyhow", + "async-once-cell", + "base64", + "bytes", + "futures-util", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "once_cell", + "regex", + "serde", + "serde_json", + "tokio", + "tokio-tungstenite", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1d1e7f5 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "vrc-yutils" +version = "0.0.1-beta1" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "*", features = ["derive"] } +serde_json = "*" +tokio-tungstenite = { version = "*", features = ["native-tls"] } +anyhow = "*" +futures-util = "*" +tokio = { version = "*", features = ["macros", "rt-multi-thread", "sync", "fs", "time"] } +once_cell = "*" +regex = "*" +async-once-cell = "*" +hyper-util = { version = "*", features = ["client-legacy", "http1"] } +bytes = "*" +http-body-util = "*" +hyper = "*" +hyper-tls = "*" +base64 = "*" + +[features] +default = [] +websocket = [] diff --git a/src/authorize.rs b/src/authorize.rs new file mode 100644 index 0000000..fe77e43 --- /dev/null +++ b/src/authorize.rs @@ -0,0 +1,84 @@ +use std::io; + +use anyhow::Context as _; +use base64::{engine::general_purpose, Engine as _}; +use hyper::Method; +use serde_json::json; + +use crate::fetcher::{make_request, request_json, Header, ResponseExt as _}; + +const URL: &str = "https://api.vrchat.cloud/api/1/auth/user"; + +#[allow(non_snake_case)] +#[derive(serde::Deserialize)] +struct TwoFactor { + requiresTwoFactorAuth: Vec, +} + +pub(crate) async fn auth() -> anyhow::Result { + let mut username = String::new(); + let mut password = String::new(); + + println!("Enter your username or email:"); + io::stdin().read_line(&mut username)?; + + println!("Enter your password:"); + io::stdin().read_line(&mut password)?; + + let res = make_request( + Method::GET, + URL, + Header::Auth(( + "Authorization", + &format!( + "Basic {}", + general_purpose::STANDARD_NO_PAD.encode(format!( + "{}:{}", + username.trim(), + password.trim() + )) + ), + )), + None::<()>, + ) + .await?; + + let token = res + .headers() + .get("set-cookie") + .and_then(|h| h.to_str().ok()) + .and_then(|c| c.split(';').next()) + .context("invalid cookie found.")? + .to_owned(); + + let auth_type = res + .json::() + .await? + .requiresTwoFactorAuth + .into_iter() + .find_map(|auth| match auth.as_str() { + "emailOtp" => Some("emailotp"), + "totp" => Some("totp"), + _ => None, + }) + .unwrap_or("otp"); + + let mut two_factor_code = String::new(); + + println!("Enter your code:"); + io::stdin().read_line(&mut two_factor_code)?; + + request_json( + Method::POST, + &format!("https://api.vrchat.cloud/api/1/auth/twofactorauth/{auth_type}/verify"), + &token, + json!({ "code": two_factor_code.trim() }), + ) + .await?; + + let token = token.split_once('=').unwrap().1.to_owned(); + + println!("your token: {token}\n次回から--auth={token}として起動してください。"); + + Ok(token) +} diff --git a/src/fetcher.rs b/src/fetcher.rs new file mode 100644 index 0000000..840f84b --- /dev/null +++ b/src/fetcher.rs @@ -0,0 +1,111 @@ +use crate::var::{APP_NAME, UA}; +use anyhow::{anyhow, Result}; +use bytes::{Buf as _, Bytes}; +use http_body_util::{BodyExt as _, Empty}; +use hyper::{body::Incoming, Method, Request, Response}; +use hyper_tls::HttpsConnector; +use hyper_util::{ + client::legacy::{connect::HttpConnector, Client}, + rt::TokioExecutor, +}; +use once_cell::sync::Lazy; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; + +#[derive(Deserialize)] +struct ErrorMessage { + error: ErrorDetail, +} + +#[derive(Deserialize)] +struct ErrorDetail { + message: String, + // status_code: u32, +} + +static CLIENT: Lazy, String>> = + Lazy::new(|| Client::builder(TokioExecutor::new()).build(HttpsConnector::new())); + +static GET_CLIENT: Lazy, Empty>> = + Lazy::new(|| Client::builder(TokioExecutor::new()).build(HttpsConnector::new())); + +pub(super) enum Header<'a> { + Cookie(&'a str), + Auth((&'a str, &'a str)), +} + +pub(super) async fn make_request( + method: Method, + target: &str, + header: Header<'_>, + serializable: Option, +) -> Result> { + let builder = Request::builder() + .method(method) + .uri(target) + .header(UA, APP_NAME); + + let builder = match header { + Header::Cookie(cookie) => builder.header(hyper::header::COOKIE, cookie), + Header::Auth((header, value)) => builder.header(header, value), + }; + + let response = if let Some(serializable) = serializable { + CLIENT.request( + builder + .header(hyper::header::CONTENT_TYPE, "application/json") + .body(serde_json::to_string(&serializable)?)?, + ) + } else { + GET_CLIENT.request(builder.body(Empty::new())?) + } + .await?; + + if response.status().is_success() { + Ok(response) + } else { + Err(anyhow!( + "{}", + response + .json::() + .await? + .error + .message + .replace('\"', "") + )) + } +} + +// #[inline] +// pub(crate) async fn request( +// method: Method, +// target: &str, +// cookie: &str, +// ) -> Result> { +// make_request(method, target, Header::Cookie(cookie), None::<()>).await +// } + +#[inline] +pub(crate) async fn request_json( + method: Method, + target: &str, + cookie: &str, + serializable: impl Serialize, +) -> Result> { + make_request(method, target, Header::Cookie(cookie), Some(serializable)).await +} + +pub(crate) trait ResponseExt { + async fn json(self) -> Result + where + T: DeserializeOwned; +} + +impl ResponseExt for Response { + async fn json(self) -> Result + where + T: DeserializeOwned, + { + let reader = self.collect().await?.aggregate().reader(); + serde_json::from_reader::<_, T>(reader).map_err(From::from) + } +} diff --git a/src/log/log_reader.rs b/src/log/log_reader.rs new file mode 100644 index 0000000..2b522ba --- /dev/null +++ b/src/log/log_reader.rs @@ -0,0 +1,106 @@ +use super::processor::parse_and_process; +use once_cell::sync::Lazy; +use regex::Regex; +use std::collections::BTreeMap; +use std::env; +use std::io::SeekFrom; +use std::path::PathBuf; +use std::time::SystemTime; +use tokio::fs::{read_dir, File}; +use tokio::io::{self, AsyncBufReadExt as _, AsyncSeekExt as _, BufReader}; +use tokio::time::sleep; + +pub(crate) async fn process_log() { + let mut path = None; + let mut last_pos = 0; + let mut buf = String::new(); + + loop { + let is_fst = path.is_none(); + + let Some(cur_path) = get_latest_log_path().await else { + return eprintln!("VRChatのログが見つかりませんでした。ログ関連の機能を使用するには、VRChatのログ機能を有効化してください。"); + }; + + if !path.as_ref().is_some_and(|prev| prev == &cur_path) { + path = Some(cur_path); + last_pos = 0; + } + + if let Err(e) = read_log(path.as_ref().unwrap(), &mut last_pos, &mut buf, is_fst).await { + eprintln!("{e}"); + }; + + sleep(tokio::time::Duration::from_secs(1)).await; + } +} + +async fn read_log( + path: &PathBuf, + last_pos: &mut usize, + buf: &mut String, + is_fst: bool, +) -> io::Result<()> { + let mut reader = BufReader::new(File::open(path).await?); + + loop { + buf.clear(); + reader.seek(SeekFrom::Start(*last_pos as u64)).await?; + + match reader.read_line(buf).await? { + 0 => break Ok(()), + n => { + *last_pos += n; + if !is_fst { + parse_and_process(buf).await; + } + } + } + } +} + +async fn get_latest_log_path() -> Option { + let mut paths = get_log_paths() + .await + .inspect_err(|e| eprintln!("{e}")) + .ok()?; + + let latest = paths.pop_last()?; + Some(latest.1) +} + +async fn get_log_paths() -> io::Result> { + static PATH: Lazy = Lazy::new(|| { + let mut path = PathBuf::from(env::var("AppData").unwrap()); + path.pop(); + path.push("LocalLow"); + path.push("VRChat"); + path.push("vrchat"); + path + }); + + let mut log_files = read_dir(&*PATH).await?; + let mut paths = BTreeMap::new(); + + while let Ok(Some(entry)) = log_files.next_entry().await { + if entry + .file_type() + .await + .is_ok_and(|file_type| file_type.is_file()) + { + static LOG_PATTERN: Lazy = + Lazy::new(|| Regex::new(r"^output_log_.*\.txt$").unwrap()); + + if entry + .file_name() + .to_str() + .is_some_and(|name| LOG_PATTERN.is_match(name)) + { + let modified = entry.metadata().await?.modified()?; + paths.insert(modified, entry.path()); + } + } + } + + Ok(paths) +} diff --git a/src/log/mod.rs b/src/log/mod.rs new file mode 100644 index 0000000..a586832 --- /dev/null +++ b/src/log/mod.rs @@ -0,0 +1,4 @@ +mod log_reader; +mod processor; + +pub(crate) use log_reader::process_log; diff --git a/src/log/processor.rs b/src/log/processor.rs new file mode 100644 index 0000000..407050b --- /dev/null +++ b/src/log/processor.rs @@ -0,0 +1,47 @@ +use crate::xsoverlay::notify_join::{notify_join, JoinType}; +use once_cell::sync::Lazy; +use regex::{Match, Regex}; + +pub(super) async fn parse_and_process(line: &str) { + if let Some(cap) = parse(line).await { + process_event(cap).await; + } +} + +async fn parse(line: &str) -> Option<(&JoinType, Option)> { + static PATTERNS: Lazy<[(JoinType, regex::Regex); 4]> = Lazy::new(|| { + [ + ( + JoinType::PlayerJoined, + Regex::new(r"\[Behaviour\] OnPlayerJoined (.+)").unwrap(), + ), + ( + JoinType::PlayerLeft, + Regex::new(r"\[Behaviour\] OnPlayerLeft (.+)").unwrap(), + ), + ( + JoinType::JoinedRoom, + Regex::new(r"\[Behaviour\] OnJoinedRoom").unwrap(), + ), + ( + JoinType::LeftRoom, + Regex::new(r"\[Behaviour\] OnLeftRoom").unwrap(), + ), + ] + }); + + PATTERNS.iter().find_map(|(pattern, regex)| match pattern { + JoinType::PlayerJoined | JoinType::PlayerLeft => regex + .captures(line) + .and_then(|c| c.get(1)) + .map(|c| (pattern, Some(c))), + _ if regex.is_match(line) => Some((pattern, None)), + _ => None, + }) +} + +async fn process_event(cap: (&JoinType, Option>)) { + if let (join_type @ (JoinType::PlayerJoined | JoinType::PlayerLeft), Some(cap)) = cap { + notify_join(None, cap.as_str(), *join_type).await; + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..eb4bed2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,38 @@ +mod authorize; +mod fetcher; +mod log; +mod udp_client; +mod var; +mod vrc_structs; +mod websocket; +mod xsoverlay; + +use crate::websocket::stream::process_websocket; +use log::process_log; +use std::env; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let args = env::args().collect::>(); + // let uri = find_arg_value(&args, "--url").and_then(|a| a.parse::().ok()); + let uri = None; + + let auth; + let auth = match find_arg_value(&args, "--auth") { + Some(auth) => auth, + None => { + auth = authorize::auth().await?; + &auth + } + }; + + tokio::join!(process_websocket(auth, uri.as_ref()), process_log()); + + Ok(()) +} + +fn find_arg_value<'a>(args: &'a [String], prefix: &str) -> Option<&'a str> { + args.iter() + .find(|arg| arg.starts_with(prefix)) + .and_then(|arg| arg.split('=').last()) +} diff --git a/src/udp_client.rs b/src/udp_client.rs new file mode 100644 index 0000000..134d3a1 --- /dev/null +++ b/src/udp_client.rs @@ -0,0 +1,36 @@ +use async_once_cell::OnceCell; +use serde::Serialize; +use serde_json::to_vec; +use std::net::{Ipv4Addr, SocketAddrV4}; +use tokio::{io, net::UdpSocket}; + +#[cfg(not(feature = "websocket"))] +pub(crate) async fn send_message(serializable: &T, port: Option) -> io::Result<()> +where + T: Serialize, +{ + send(&to_vec(&serializable).unwrap(), port).await +} + +#[cfg(not(feature = "websocket"))] // OSCを実装したら消す +pub(crate) async fn send(buf: &[u8], port: Option) -> io::Result<()> { + const LOCALHOST: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); + + const XSOVERLAY: SocketAddrV4 = SocketAddrV4::new(LOCALHOST, 42069); + const OSC: SocketAddrV4 = SocketAddrV4::new(LOCALHOST, 9000); + + let to = match port { + Some(42069) | None => XSOVERLAY, + Some(9000) => OSC, + Some(n) => SocketAddrV4::new(LOCALHOST, n), + }; + + static UDPSOCK: OnceCell = OnceCell::new(); + let sock = UDPSOCK + .get_or_try_init(UdpSocket::bind(SocketAddrV4::new(LOCALHOST, 0))) + .await?; + + sock.send_to(buf, to).await?; + + Ok(()) +} diff --git a/src/var.rs b/src/var.rs new file mode 100644 index 0000000..5881cc3 --- /dev/null +++ b/src/var.rs @@ -0,0 +1,6 @@ +use tokio::sync::Mutex; + +pub(crate) const APP_NAME: &str = "vrc-yutils"; +pub(crate) const UA: &str = "User-Agent"; + +pub(crate) static SELF_LOCATION: Mutex = Mutex::const_new(String::new()); diff --git a/src/vrc_structs.rs b/src/vrc_structs.rs new file mode 100644 index 0000000..f3a9afc --- /dev/null +++ b/src/vrc_structs.rs @@ -0,0 +1,117 @@ +use serde::Deserialize; + +#[derive(Deserialize, Ord, PartialEq, PartialOrd, Eq, Clone, Copy, Debug)] +pub enum Status { + #[serde(rename = "join me")] + JoinMe, + #[serde(rename = "active")] + Active, + #[serde(rename = "ask me")] + AskMe, + #[serde(rename = "busy")] + Busy, +} + +impl Ord for User { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.status.cmp(&other.status) + } +} + +impl PartialEq for User { + fn eq(&self, other: &Self) -> bool { + self.status == other.status + } +} + +impl PartialOrd for User { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.status.cmp(&other.status)) + } +} + +#[allow(non_snake_case)] +#[derive(Deserialize)] +pub struct StreamBody { + pub r#type: String, + pub content: String, // json +} + +#[allow(non_snake_case)] +#[derive(Deserialize)] +pub struct FriendLocation { + pub userId: String, + pub location: String, + pub travelingToLocation: Option, + pub worldId: String, + pub canRequestInvite: Option, + pub user: User, + pub world: Option, +} + +#[allow(non_snake_case)] +#[derive(Deserialize, Eq)] +pub struct User { + pub id: String, + pub displayName: String, + #[serde(default)] + pub userIcon: String, + #[serde(default)] + pub bio: String, + #[serde(default)] + pub bioLinks: Vec, + #[serde(default)] + pub profilePicOverride: String, + #[serde(default)] + pub statusDescription: String, + #[serde(default)] + pub currentAvatarImageUrl: String, + #[serde(default)] + pub currentAvatarThumbnailImageUrl: String, + pub currentAvatarTags: Vec, + pub state: String, + pub tags: Vec, + pub developerType: String, + pub last_login: String, + pub last_platform: String, + pub allowAvatarCopying: bool, + pub status: String, + pub date_joined: String, + pub isFriend: bool, + pub friendKey: String, + pub last_activity: String, +} + +#[allow(non_snake_case)] +#[derive(Deserialize, Debug)] +pub struct World { + pub id: String, + pub name: String, + pub description: String, + pub authorId: String, + pub authorName: String, + pub releaseStatus: String, + pub featured: bool, + pub capacity: i32, + pub recommendedCapacity: i32, + pub imageUrl: String, + pub thumbnailImageUrl: String, + pub namespace: String, + pub version: i32, + pub organization: String, + pub previewYoutubeId: Option, + pub udonProducts: Vec, + pub favorites: i32, + pub visits: i32, + pub popularity: i32, + pub heat: i32, + pub publicationDate: String, + pub labsPublicationDate: String, + pub instances: Vec, + pub publicOccupants: i32, + pub privateOccupants: i32, + pub occupants: i32, + pub tags: Vec, + pub created_at: String, + pub updated_at: String, +} diff --git a/src/websocket/mod.rs b/src/websocket/mod.rs new file mode 100644 index 0000000..664809f --- /dev/null +++ b/src/websocket/mod.rs @@ -0,0 +1 @@ +pub(crate) mod stream; diff --git a/src/websocket/stream.rs b/src/websocket/stream.rs new file mode 100644 index 0000000..9c62c3e --- /dev/null +++ b/src/websocket/stream.rs @@ -0,0 +1,135 @@ +use crate::{ + var::{APP_NAME, SELF_LOCATION, UA}, + xsoverlay::notify_join::{notify_join, JoinType}, +}; +use futures_util::StreamExt as _; +use hyper::Uri; +use std::{sync::OnceLock, time::Duration}; +use tokio_tungstenite::{ + connect_async, + tungstenite::{ + self, + client::IntoClientRequest as _, + http::{HeaderValue, Request}, + Message, + }, +}; + +enum WSError { + Disconnected, + Token, + Unknown(String), + IoErr(tungstenite::error::Error), + Other(String), +} + +use WSError::*; + +pub(crate) async fn process_websocket(auth: &str, uri: Option<&Uri>) { + // インターネットに接続されていないときに無限に接続を試みてしまわないように + let mut io_err_cnt = 0u8; + + loop { + match connect_websocket(auth, uri).await { + Disconnected => { + io_err_cnt = 0; + } + Other(e) => { + eprintln!("{e}"); + io_err_cnt = 0; + } + Unknown(e) => { + eprintln!("Unknown Error: {e}"); + break; + } + Token => { + eprintln!("トークンの有効期限が切れました。再認証を行ってください。"); + break; + } + IoErr(e) => { + eprintln!("{e}"); + + io_err_cnt += 1; + + match io_err_cnt { + 1 => (), + 20 => break, + _ => tokio::time::sleep(Duration::from_secs(10)).await, + } + } + } + } + eprintln!("WebSocketから切断されました。"); +} + +async fn connect_websocket(auth: &str, uri: Option<&Uri>) -> WSError { + static REQUEST: OnceLock> = OnceLock::new(); + let request = REQUEST + .get_or_init(|| { + let host = uri + .and_then(|u| u.host()) + .unwrap_or("pipeline.vrchat.cloud"); + let mut req = format!("wss://{host}/?auth={auth}") + .into_client_request() + .unwrap(); + req.headers_mut() + .insert(UA, HeaderValue::from_static(APP_NAME)); + req + }) + .clone(); + + let mut stream = match connect_async(request).await { + Ok(stream) => stream.0, + Err(e @ tungstenite::error::Error::Io(_)) => return IoErr(e), + Err(e) => return Unknown(e.to_string()), + }; + + while let Some(message) = stream.next().await { + use crate::vrc_structs::*; + + let message = match message { + Ok(Message::Text(text)) if text.starts_with(r#"{"err"#) => { + return if !text.contains("authToken") { + Unknown(text) + } else { + Token + }; + } + Ok(Message::Text(text)) => text, + Ok(Message::Close(_)) => return Disconnected, + Err(e) => return Other(e.to_string()), + _ => continue, + }; + + let StreamBody { r#type, content } = serde_json::from_str::(&message).unwrap(); + + match r#type.as_str() { + "friend-location" => { + let FriendLocation { + travelingToLocation, + user, + .. + } = serde_json::from_str(&content).unwrap(); + if let Some(to) = &travelingToLocation { + notify_join(Some(to), &user.displayName, JoinType::PlayerJoining).await; + } + } + + "user-location" => { + let user = serde_json::from_str::(&content).unwrap(); + *SELF_LOCATION.lock().await = user.location + } + + "friend-add" | "friend-online" | "friend-offline" | "friend-delete" + | "friend-active" | "notification" | "notification-v2" => {} + + _ => { + if cfg!(debug_assertions) { + println!("unknown event: {message}") + } + } + } + } + + Disconnected +} diff --git a/src/xsoverlay/mod.rs b/src/xsoverlay/mod.rs new file mode 100644 index 0000000..a51154c --- /dev/null +++ b/src/xsoverlay/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod notify_join; +#[cfg(feature = "websocket")] +mod websocket; diff --git a/src/xsoverlay/notify_join.rs b/src/xsoverlay/notify_join.rs new file mode 100644 index 0000000..aa772f1 --- /dev/null +++ b/src/xsoverlay/notify_join.rs @@ -0,0 +1,97 @@ +#[cfg(feature = "websocket")] +use super::websocket::{send_message, XSOverlay}; +#[cfg(not(feature = "websocket"))] +use crate::udp_client::send_message; +use crate::var::{APP_NAME, SELF_LOCATION}; +use serde::Serialize; +use std::fmt::Display; + +#[derive(Clone, Copy, Debug)] +pub(crate) enum JoinType { + PlayerJoining, + PlayerJoined, + PlayerLeft, + JoinedRoom, + LeftRoom, +} + +impl Display for JoinType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + JoinType::PlayerJoining => write!(f, "is joining"), + JoinType::PlayerJoined => write!(f, "has joined"), + JoinType::PlayerLeft => write!(f, "has left"), + JoinType::JoinedRoom => todo!(), + JoinType::LeftRoom => todo!(), + } + } +} + +/// toがNoneの場合、locationのチェックを行わない +pub(crate) async fn notify_join(to: Option<&str>, display_name: &str, join_type: JoinType) { + if matches!(to, Some(to) if to.is_empty() || to != *SELF_LOCATION.lock().await) { + return; + } + + let notification = Notification { + title: &format!("{join_type:?}"), + content: &format!("{display_name} {join_type}."), + ..Default::default() + }; + + #[cfg(feature = "websocket")] + let notification = XSOverlay { + sender: APP_NAME, + target: "xsoverlay", + command: "SendNotification", + json_data: &serde_json::to_string(¬ification).unwrap(), + raw_data: None, + }; + + if let Err(e) = send_message(¬ification, None).await { + eprintln!("{e}\nXSOverlayへメッセージを送信する際にエラーが発生しました。"); + } +} + +#[derive(Serialize)] +struct Notification<'a> { + #[cfg(not(feature = "websocket"))] + #[serde(rename = "messageType")] + message_type: i32, + #[cfg(feature = "websocket")] + #[serde(rename = "type")] + message_type: i32, + index: i32, + timeout: f32, + height: f32, + opacity: f32, + volume: f32, + #[serde(rename = "audioPath")] + audio_path: &'a str, + title: &'a str, + content: &'a str, + #[serde(rename = "useBase64Icon")] + use_base64_icon: bool, + icon: &'a str, + #[serde(rename = "sourceApp")] + source_app: &'a str, +} + +impl<'a> Default for Notification<'a> { + fn default() -> Self { + Notification { + message_type: 1, + index: 0, + timeout: 1.5, + height: 175.0, + opacity: 1.0, + volume: 0.7, + audio_path: "default", + title: "", + content: "", + use_base64_icon: false, + icon: "default", + source_app: APP_NAME, + } + } +} diff --git a/src/xsoverlay/websocket.rs b/src/xsoverlay/websocket.rs new file mode 100644 index 0000000..e54b54c --- /dev/null +++ b/src/xsoverlay/websocket.rs @@ -0,0 +1,64 @@ +use crate::var::APP_NAME; +use anyhow::Result; +use futures_util::{stream::FusedStream as _, SinkExt}; +use std::sync::OnceLock; +use tokio::{net::TcpStream, sync::Mutex}; +use tokio_tungstenite::{ + connect_async, + tungstenite::{client::IntoClientRequest, http::Request, Message}, + MaybeTlsStream, WebSocketStream, +}; + +type WS = WebSocketStream>; + +#[derive(serde::Serialize)] +pub(crate) struct XSOverlay<'a> { + pub(crate) sender: &'a str, + pub(crate) target: &'a str, + pub(crate) command: &'a str, + #[serde(rename = "jsonData")] + pub(crate) json_data: &'a str, + #[serde(rename = "rawData")] + pub(crate) raw_data: Option<&'a str>, +} + +pub(crate) async fn send_message(message: &XSOverlay<'_>, port: Option) -> Result<()> { + static WS_CONNECTION: Mutex> = Mutex::const_new(None); + + let mut ws = WS_CONNECTION.lock().await; + if !ws.as_ref().is_some_and(|ws| !ws.is_terminated()) { + try_new(&mut ws, None).await?; + } + + let mut tried = false; + loop { + match unsafe { ws.as_mut().unwrap_unchecked() } + .send(Message::Text(serde_json::to_string(&message)?)) + .await + { + Ok(_) => break Ok(()), + Err(_) if !tried => { + try_new(&mut ws, port).await?; + tried = true; + } + Err(e) => return Err(e.into()), + } + } +} + +#[inline] +async fn try_new(ws_ref: &mut Option, port: Option) -> Result<()> { + static REQUEST: OnceLock> = OnceLock::new(); + let request = REQUEST + .get_or_init(|| { + format!( + "ws://localhost:{}/?client={APP_NAME}", + port.unwrap_or(42070) + ) + .into_client_request() + .unwrap() + }) + .clone(); + *ws_ref = Some(connect_async(request).await?.0); + Ok(()) +}