diff --git a/.dockerignore b/.dockerignore index c4f2c30e..ee5756d6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,6 +4,7 @@ # Include project code and build files !PluralKit.*/ !Myriad/ +!Serilog/ !.git !dashboard !crates/ diff --git a/.github/workflows/dotnet-docker.yml b/.github/workflows/dotnet-docker.yml index af20524a..9939320b 100644 --- a/.github/workflows/dotnet-docker.yml +++ b/.github/workflows/dotnet-docker.yml @@ -2,7 +2,9 @@ name: Build and push Docker image on: push: paths: - - '.github/workflows/docker.yml' + - '.dockerignore' + - '.github/workflows/dotnet-docker.yml' + - 'ci/Dockerfile.dotnet' - 'ci/dotnet-version.sh' - 'Myriad/**' - 'PluralKit.API/**' @@ -23,6 +25,9 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.CR_PAT }} - uses: actions/checkout@v2 + with: + submodules: true + - run: echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" | sed 's|/|-|g' >> $GITHUB_ENV - name: Extract Docker metadata @@ -41,6 +46,7 @@ jobs: with: # https://github.com/docker/build-push-action/issues/378 context: . + file: ci/Dockerfile.dotnet push: true tags: ${{ steps.meta.outputs.tags }} cache-from: type=registry,ref=ghcr.io/pluralkit/pluralkit:${{ env.BRANCH_NAME }} diff --git a/.github/workflows/rust-docker.yml b/.github/workflows/rust-docker.yml index 293a2106..a46adf67 100644 --- a/.github/workflows/rust-docker.yml +++ b/.github/workflows/rust-docker.yml @@ -3,6 +3,7 @@ on: push: paths: - 'crates/**' + - '.dockerignore' - '.github/workflows/rust.yml' - 'ci/Dockerfile.rust' - 'ci/rust-docker-target.sh' diff --git a/.gitmodules b/.gitmodules index e69de29b..96fb8310 100644 --- a/.gitmodules +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "Serilog"] + path = Serilog + url = https://github.com/pluralkit/serilog + branch = f5eb991cb4c4a0c1e2407de7504c543536786598 diff --git a/Cargo.lock b/Cargo.lock index 1ff2ecc2..1aa37dd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,27 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 4 +version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "ahash" @@ -29,16 +23,16 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" dependencies = [ - "getrandom", + "getrandom 0.2.16", "once_cell", "version_check", ] [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "once_cell", @@ -57,9 +51,9 @@ dependencies = [ [[package]] name = "allocator-api2" -version = "0.2.18" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-tzdata" @@ -78,31 +72,31 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.69" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "api" version = "0.1.0" dependencies = [ "anyhow", - "axum 0.7.5", + "axum 0.8.4", "fred", - "hyper 1.5.0", + "hyper 1.6.0", "hyper-util", "lazy_static", "libpk", "metrics", "pluralkit_models", - "reqwest 0.12.8", + "reqwest 0.12.22", "reverse-proxy-service", "serde", "serde_json", "serde_urlencoded", "sqlx", "tokio", - "tower", + "tower 0.4.13", "tower-http 0.5.2", "tracing", "twilight-http", @@ -114,6 +108,12 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + [[package]] name = "arrayvec" version = "0.7.6" @@ -143,7 +143,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.87", + "syn", ] [[package]] @@ -163,13 +163,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -193,7 +193,7 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7" dependencies = [ - "http 0.2.8", + "http 0.2.12", "log", "rustls 0.20.9", "serde", @@ -205,28 +205,28 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "avatars" version = "0.1.0" dependencies = [ "anyhow", - "axum 0.7.5", + "axum 0.8.4", "data-encoding", "form_urlencoded", "futures", "gif", "image", "libpk", - "reqwest 0.12.8", + "reqwest 0.12.22", "rust-s3", "serde", "sha2", "sqlx", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", "uuid", @@ -242,39 +242,62 @@ dependencies = [ "attohttpc", "dirs", "log", - "quick-xml", + "quick-xml 0.26.0", "rust-ini 0.18.0", "serde", - "thiserror", + "thiserror 1.0.69", "time", "url", ] +[[package]] +name = "aws-lc-rs" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", +] + [[package]] name = "aws-region" version = "0.25.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9aed3f9c7eac9be28662fdb3b0f4d1951e812f7c64fed4f0327ba702f459b3b" dependencies = [ - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "axum" -version = "0.6.7" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fb79c228270dcf2426e74864cabc94babb5dbab01a4314e702d2f16540e1591" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core 0.3.4", "bitflags 1.3.2", "bytes", "futures-util", - "http 0.2.8", - "http-body 0.4.5", - "hyper 0.14.24", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", "itoa", - "matchit", + "matchit 0.7.3", "memchr", "mime", "percent-encoding", @@ -286,29 +309,27 @@ dependencies = [ "serde_urlencoded", "sync_wrapper 0.1.2", "tokio", - "tower", - "tower-http 0.3.5", + "tower 0.4.13", "tower-layer", "tower-service", ] [[package]] name = "axum" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +version = "0.8.4" +source = "git+https://github.com/pluralkit/axum?branch=v0.8.4-pluralkit#3b45806719f27e69aed912bac724056b910f4aa6" dependencies = [ - "async-trait", - "axum-core 0.4.3", + "axum-core 0.5.2", "bytes", + "form_urlencoded", "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.6.0", "hyper-util", "itoa", - "matchit", + "matchit 0.8.4", "memchr", "mime", "percent-encoding", @@ -318,9 +339,9 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tokio", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", "tracing", @@ -335,8 +356,8 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 0.2.8", - "http-body 0.4.5", + "http 0.2.12", + "http-body 0.4.6", "mime", "rustversion", "tower-layer", @@ -345,20 +366,18 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +version = "0.5.2" +source = "git+https://github.com/pluralkit/axum?branch=v0.8.4-pluralkit#3b45806719f27e69aed912bac724056b910f4aa6" dependencies = [ - "async-trait", "bytes", - "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper 0.1.2", + "sync_wrapper 1.0.2", "tower-layer", "tower-service", "tracing", @@ -366,17 +385,17 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -399,15 +418,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "basic-toml" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "823388e228f614e9558c6804262db37960ec8821856535f5c3f59913140558f8" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" dependencies = [ "serde", ] @@ -421,6 +440,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" +dependencies = [ + "bitflags 2.9.1", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash 1.1.0", + "shlex", + "syn", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -429,42 +471,42 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.5.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" dependencies = [ "serde", ] [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "borsh" -version = "1.5.3" +version = "1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2506947f73ad44e344215ccd6403ac2ae18cd8e046e581a441bf8d199f257f03" +checksum = "ad8646f98db542e39fc66e68a20b2144f6a732636df7c2354e74645faaa433ce" dependencies = [ "cfg_aliases", ] [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "bytemuck" -version = "1.19.0" +version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" +checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" [[package]] name = "byteorder" @@ -474,15 +516,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bytes-utils" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47d3a8076e283f3acd27400535992edb3ba4b5bb72f8891ad8fbe7932a7d4b9" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" dependencies = [ "bytes", "either", @@ -490,9 +532,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.9" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +checksum = "5d07aa9a93b00c76f71bc35d598bed923f6d4f3a9ca5c24b7737ae1a292841c0" dependencies = [ "serde", ] @@ -517,14 +559,14 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "cc" -version = "1.1.31" +version = "1.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +checksum = "2352e5597e9c544d5e6d9c95190d5d27738ade584fa8db0a16e130e5c2b5296e" dependencies = [ "jobserver", "libc", @@ -532,10 +574,25 @@ dependencies = [ ] [[package]] -name = "cfg-if" -version = "1.0.0" +name = "cesu8" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cfg_aliases" @@ -545,9 +602,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", @@ -555,7 +612,27 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", +] + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "cmake" +version = "0.1.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +dependencies = [ + "cc", ] [[package]] @@ -564,6 +641,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "command_definitions" version = "0.1.0" @@ -601,22 +688,21 @@ dependencies = [ [[package]] name = "config" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" +checksum = "68578f196d2a33ff61b27fae256c3164f65e36382648e30666dde05b8cc9dfdf" dependencies = [ "async-trait", "convert_case", "json5", - "lazy_static", "nom", "pathdiff", "ron", - "rust-ini 0.19.0", + "rust-ini 0.20.0", "serde", "serde_json", - "toml 0.8.19", - "yaml-rust", + "toml 0.8.23", + "yaml-rust2", ] [[package]] @@ -640,7 +726,7 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" dependencies = [ - "getrandom", + "getrandom 0.2.16", "once_cell", "tiny-keccak", ] @@ -671,25 +757,35 @@ dependencies = [ ] [[package]] -name = "core-foundation-sys" -version = "0.8.6" +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc" -version = "3.2.1" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" dependencies = [ "crc-catalog", ] @@ -708,55 +804,51 @@ checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff" [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "croner" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38fd53511eaf0b00a185613875fee58b208dfce016577d0ad4bb548e1c4fb3ee" +checksum = "c344b0690c1ad1c7176fe18eb173e0c927008fdaaa256e40dfd43ddd149c0843" dependencies = [ "chrono", ] [[package]] name = "crossbeam-epoch" -version = "0.9.14" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-queue" -version = "0.3.11" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-common" @@ -770,9 +862,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -780,36 +872,37 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.87", + "syn", ] [[package]] name = "dashmap" -version = "5.4.0" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", - "hashbrown 0.12.3", + "crossbeam-utils", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -817,9 +910,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" [[package]] name = "debugid" @@ -833,9 +926,9 @@ dependencies = [ [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", @@ -844,9 +937,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", "serde", @@ -889,9 +982,10 @@ name = "dispatch" version = "0.1.0" dependencies = [ "anyhow", - "axum 0.7.5", + "axum 0.8.4", "hickory-client", - "reqwest 0.12.8", + "libpk", + "reqwest 0.12.22", "serde", "serde_json", "tokio", @@ -899,6 +993,17 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dlv-list" version = "0.3.0" @@ -921,19 +1026,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] -name = "either" -version = "1.8.1" +name = "dunce" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" dependencies = [ "serde", ] [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] @@ -946,30 +1057,30 @@ checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "enum-as-inner" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck 0.4.1", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] @@ -985,9 +1096,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.3.1" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -996,15 +1107,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fdeflate" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8090f921a24b04994d9929e204f50b498a33ea6ba559ffaa05e04f7ee7fb5ab" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" dependencies = [ "simd-adler32", ] @@ -1023,13 +1134,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.30" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", - "libz-sys", - "miniz_oxide 0.7.4", + "miniz_oxide", ] [[package]] @@ -1043,9 +1153,9 @@ dependencies = [ [[package]] name = "flume" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", @@ -1058,6 +1168,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -1083,11 +1199,11 @@ dependencies = [ "futures", "log", "parking_lot", - "rand", + "rand 0.8.5", "redis-protocol", "semver", "sha-1", - "socket2 0.5.7", + "socket2 0.5.10", "tokio", "tokio-stream", "tokio-util", @@ -1104,7 +1220,7 @@ checksum = "1458c6e22d36d61507034d5afecc64f105c1d39712b7ac6ec3b352c423f715cc" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -1117,10 +1233,16 @@ dependencies = [ ] [[package]] -name = "futures" -version = "0.3.30" +name = "fs_extra" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -1133,9 +1255,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -1143,15 +1265,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -1171,38 +1293,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -1221,7 +1343,7 @@ name = "gateway" version = "0.1.0" dependencies = [ "anyhow", - "axum 0.7.5", + "axum 0.8.4", "bytes", "chrono", "fred", @@ -1229,9 +1351,10 @@ dependencies = [ "lazy_static", "libpk", "metrics", + "reqwest 0.12.22", + "serde", "serde_json", "serde_variant", - "signal-hook", "tokio", "tracing", "twilight-cache-inmemory", @@ -1241,11 +1364,26 @@ dependencies = [ "twilight-util", ] +[[package]] +name = "gdpr_worker" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum 0.8.4", + "futures", + "libpk", + "sqlx", + "tokio", + "tracing", + "twilight-http", + "twilight-model", +] + [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", @@ -1253,20 +1391,36 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] name = "gif" -version = "0.13.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2" +checksum = "4ae047235e33e2829703574b54fdec96bfbad892062d97fed2f76022287de61b" dependencies = [ "color_quant", "weezl", @@ -1274,15 +1428,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "goblin" @@ -1297,16 +1451,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http 0.2.8", + "http 0.2.12", "indexmap", "slab", "tokio", @@ -1316,16 +1470,16 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.6" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "http 1.1.0", + "http 1.3.1", "indexmap", "slab", "tokio", @@ -1342,37 +1496,45 @@ dependencies = [ "ahash 0.7.8", ] -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" - [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", "allocator-api2", ] [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] [[package]] name = "hashlink" -version = "0.9.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + [[package]] name = "heck" version = "0.4.1" @@ -1387,18 +1549,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.2.6" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -1408,9 +1561,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hickory-client" -version = "0.24.1" +version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab9683b08d8f8957a857b0236455d80e1886eaa8c6178af556aa7871fb61b55" +checksum = "156579a5cd8d1fc6f0df87cc21b6ee870db978a163a1ba484acd98a4eff5a6de" dependencies = [ "cfg-if", "data-encoding", @@ -1419,17 +1572,17 @@ dependencies = [ "hickory-proto", "once_cell", "radix_trie", - "rand", - "thiserror", + "rand 0.8.5", + "thiserror 1.0.69", "tokio", "tracing", ] [[package]] name = "hickory-proto" -version = "0.24.1" +version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07698b8420e2f0d6447a436ba999ec85d8fbf2a398bbd737b82cac4a2e96e512" +checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" dependencies = [ "async-trait", "cfg-if", @@ -1438,11 +1591,11 @@ dependencies = [ "futures-channel", "futures-io", "futures-util", - "idna 0.4.0", + "idna", "ipnet", "once_cell", - "rand", - "thiserror", + "rand 0.8.5", + "thiserror 1.0.69", "tinyvec", "tokio", "tracing", @@ -1469,29 +1622,29 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "hostname" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" +checksum = "a56f203cd1c76362b69e3863fd987520ac36cf70a8c92627449b2f64a8cf7d65" dependencies = [ "cfg-if", "libc", - "windows", + "windows-link", ] [[package]] name = "http" -version = "0.2.8" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -1500,9 +1653,9 @@ dependencies = [ [[package]] name = "http" -version = "1.1.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -1511,74 +1664,68 @@ 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 0.2.8", + "http 0.2.12", "pin-project-lite", ] [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.1.0", + "http 1.3.1", ] [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "futures-core", + "http 1.3.1", + "http-body 1.0.1", "pin-project-lite", ] -[[package]] -name = "http-range-header" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" - [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.24" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "h2 0.3.26", - "http 0.2.8", - "http-body 0.4.5", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.7", + "socket2 0.5.10", "tokio", "tower-service", "tracing", @@ -1587,16 +1734,16 @@ dependencies = [ [[package]] name = "hyper" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.6", - "http 1.1.0", - "http-body 1.0.0", + "h2 0.4.12", + "http 1.3.1", + "http-body 1.0.1", "httparse", "httpdate", "itoa", @@ -1613,8 +1760,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http 0.2.8", - "hyper 0.14.24", + "http 0.2.12", + "hyper 0.14.32", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", @@ -1622,54 +1769,41 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.26.0" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "futures-util", - "http 1.1.0", - "hyper 1.5.0", + "http 1.3.1", + "hyper 1.6.0", "hyper-util", - "rustls 0.22.4", + "rustls 0.23.31", "rustls-native-certs", "rustls-pki-types", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls 0.26.2", "tower-service", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" -dependencies = [ - "futures-util", - "http 1.1.0", - "hyper 1.5.0", - "hyper-util", - "rustls 0.23.23", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.26.0", - "tower-service", - "webpki-roots 0.26.6", + "webpki-roots 1.0.2", ] [[package]] name = "hyper-util" -version = "0.1.10" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" +checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" dependencies = [ + "base64 0.22.1", "bytes", "futures-channel", + "futures-core", "futures-util", - "http 1.1.0", - "http-body 1.0.0", - "hyper 1.5.0", + "http 1.3.1", + "http-body 1.0.1", + "hyper 1.6.0", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", - "socket2 0.5.7", + "socket2 0.6.0", "tokio", "tower-service", "tracing", @@ -1677,14 +1811,15 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -1698,6 +1833,92 @@ dependencies = [ "cc", ] +[[package]] +name = "icu_collections" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -1706,22 +1927,23 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "idna" -version = "0.4.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "idna_adapter", + "smallvec", + "utf8_iter", ] [[package]] -name = "idna" -version = "0.5.0" +name = "idna_adapter" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "icu_normalizer", + "icu_properties", ] [[package]] @@ -1742,66 +1964,120 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.7.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.5", ] [[package]] name = "inherent" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0122b7114117e64a63ac49f752a5ca4624d534c7b1c7de796ac196381cd2d947" +checksum = "6c38228f24186d9cc68c729accb4d413be9eaed6ad07ff79e0270d9e56f3de13" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", +] + +[[package]] +name = "io-uring" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", ] [[package]] name = "ipnet" -version = "2.7.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] [[package]] name = "itoa" -version = "1.0.5" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.3", "libc", ] [[package]] name = "jpeg-decoder" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" +checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "json-subscriber" -version = "0.2.2" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d0a86fd2fba3a8721e7086b2c9fceb0994f71cdbd64ad2dfc1b202a5c062b4" +checksum = "39891c6f2a8e066540984e1050747baa3ad274fd416d2e79dec79f91c6221c33" dependencies = [ "serde", "serde_json", @@ -1825,24 +2101,40 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.5.2", + "spin 0.9.8", ] [[package]] -name = "libc" -version = "0.2.161" +name = "lazycell" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libloading" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667" +dependencies = [ + "cfg-if", + "windows-targets 0.53.3", +] [[package]] name = "libm" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "libpk" @@ -1855,6 +2147,7 @@ dependencies = [ "lazy_static", "metrics", "metrics-exporter-prometheus", + "pk_macros", "sentry", "sentry-tracing", "serde", @@ -1869,11 +2162,11 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.9.1", "libc", ] @@ -1883,7 +2176,6 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ - "cc", "pkg-config", "vcpkg", ] @@ -1898,34 +2190,23 @@ dependencies = [ "glob", ] -[[package]] -name = "libz-sys" -version = "1.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1933,9 +2214,15 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "matchers" @@ -1948,9 +2235,15 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "maybe-async" @@ -1960,7 +2253,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -1981,26 +2274,17 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" -dependencies = [ - "autocfg", -] +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "metrics" -version = "0.23.0" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884adb57038347dfbaf2d5065887b6cf4312330dc8e94bc30a1a839bd79d3261" +checksum = "3045b4193fbdc5b5681f32f11070da9be3609f189a79f3390706d42587f46bb5" dependencies = [ - "ahash 0.8.11", + "ahash 0.8.12", "portable-atomic", ] @@ -2012,14 +2296,14 @@ checksum = "b4f0c8427b39666bf970460908b213ec09b3b350f20c0c2eabcbba51704a08e6" dependencies = [ "base64 0.22.1", "http-body-util", - "hyper 1.5.0", + "hyper 1.6.0", "hyper-util", "indexmap", "ipnet", "metrics", "metrics-util", "quanta", - "thiserror", + "thiserror 1.0.69", "tokio", "tracing", ] @@ -2039,11 +2323,22 @@ dependencies = [ "sketches-ddsketch", ] +[[package]] +name = "migrate" +version = "0.1.0" +dependencies = [ + "anyhow", + "libpk", + "sqlx", + "tokio", + "tracing", +] + [[package]] name = "mime" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" @@ -2063,18 +2358,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.4" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ "adler2", "simd-adler32", @@ -2082,23 +2368,13 @@ dependencies = [ [[package]] name = "mio" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ - "hermit-abi 0.3.9", "libc", - "wasi", - "windows-sys 0.52.0", -] - -[[package]] -name = "model_macros" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] @@ -2142,7 +2418,7 @@ dependencies = [ "num-integer", "num-iter", "num-traits", - "rand", + "rand 0.8.5", "smallvec", "zeroize", ] @@ -2185,9 +2461,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", @@ -2195,28 +2471,28 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] [[package]] name = "object" -version = "0.36.3" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oneshot-uniffi" @@ -2226,9 +2502,9 @@ checksum = "6c548d5c78976f6955d72d0ced18c48ca07030f7a1d4024529fedd7c1c01b29c" [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "ordered-float" @@ -2251,30 +2527,31 @@ dependencies = [ [[package]] name = "ordered-multimap" -version = "0.6.0" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" dependencies = [ "dlv-list 0.5.2", - "hashbrown 0.13.2", + "hashbrown 0.14.5", ] [[package]] name = "ordermap" -version = "0.5.4" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f80a48eb68b6a7da9829b8b0429011708f775af80676a91063d023a66a656106" +checksum = "6d6bff06e4a5dc6416bead102d3e63c480dd852ffbb278bf8cfeb4966b329609" dependencies = [ "indexmap", ] [[package]] name = "os_info" -version = "3.8.2" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" +checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3" dependencies = [ "log", + "plist", "serde", "windows-sys 0.52.0", ] @@ -2293,9 +2570,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -2303,15 +2580,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.52.6", ] [[package]] @@ -2322,9 +2599,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathdiff" -version = "0.2.1" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "pem-rfc7468" @@ -2343,19 +2620,20 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.5.5" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" +checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323" dependencies = [ - "thiserror", + "memchr", + "thiserror 2.0.12", "ucd-trie", ] [[package]] name = "pest_derive" -version = "2.5.5" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ac3922aac69a40733080f53c1ce7f91dcf57e1a5f6c52f421fadec7fbdc4b69" +checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc" dependencies = [ "pest", "pest_generator", @@ -2363,53 +2641,52 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.5.5" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d06646e185566b5961b4058dd107e0a7f56e77c3f484549fb119867773c0f202" +checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 1.0.107", + "syn", ] [[package]] name = "pest_meta" -version = "2.5.5" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6f60b2ba541577e2a0c307c8f39d1439108120eb7903adeb6497fa880c59616" +checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5" dependencies = [ - "once_cell", "pest", "sha2", ] [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 1.0.107", + "syn", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -2417,6 +2694,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pk_macros" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -2440,9 +2726,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "plain" @@ -2450,12 +2736,25 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "plist" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1" +dependencies = [ + "base64 0.22.1", + "indexmap", + "quick-xml 0.38.1", + "serde", + "time", +] + [[package]] name = "pluralkit_models" version = "0.1.0" dependencies = [ "chrono", - "model_macros", + "pk_macros", "sea-query", "serde", "serde_json", @@ -2465,22 +2764,31 @@ dependencies = [ [[package]] name = "png" -version = "0.17.14" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] name = "portable-atomic" -version = "1.8.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d30538d42559de6b034bc76fd6dd4c38961b1ee5c6c56e3808c50128fdbc22ce" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] [[package]] name = "powerfmt" @@ -2490,30 +2798,43 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2" +dependencies = [ + "proc-macro2", + "syn", +] [[package]] name = "proc-macro2" -version = "1.0.84" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quanta" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" dependencies = [ "crossbeam-utils", "libc", "once_cell", "raw-cpuid", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -2529,62 +2850,84 @@ dependencies = [ ] [[package]] -name = "quinn" -version = "0.11.5" +name = "quick-xml" +version = "0.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" dependencies = [ "bytes", + "cfg_aliases", "pin-project-lite", "quinn-proto", "quinn-udp", - "rustc-hash", - "rustls 0.23.23", - "socket2 0.5.7", - "thiserror", + "rustc-hash 2.1.1", + "rustls 0.23.31", + "socket2 0.5.10", + "thiserror 2.0.12", "tokio", "tracing", + "web-time", ] [[package]] name = "quinn-proto" -version = "0.11.8" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" dependencies = [ "bytes", - "rand", - "ring 0.17.8", - "rustc-hash", - "rustls 0.23.23", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.2", + "ring 0.17.14", + "rustc-hash 2.1.1", + "rustls 0.23.31", + "rustls-pki-types", "slab", - "thiserror", + "thiserror 2.0.12", "tinyvec", "tracing", + "web-time", ] [[package]] name = "quinn-udp" -version = "0.5.5" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" dependencies = [ + "cfg_aliases", "libc", "once_cell", - "socket2 0.5.7", + "socket2 0.5.10", "tracing", "windows-sys 0.59.0", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radix_trie" version = "0.2.1" @@ -2602,8 +2945,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.3", ] [[package]] @@ -2613,7 +2966,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", ] [[package]] @@ -2622,16 +2985,25 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.3", ] [[package]] name = "raw-cpuid" -version = "11.1.0" +version = "11.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" +checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.9.1", ] [[package]] @@ -2650,20 +3022,11 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", + "bitflags 2.9.1", ] [[package]] @@ -2672,21 +3035,21 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.16", "libredox", - "thiserror", + "thiserror 1.0.69", ] [[package]] name = "regex" -version = "1.9.4" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.7", - "regex-syntax 0.7.5", + "regex-automata 0.4.9", + "regex-syntax 0.8.5", ] [[package]] @@ -2695,31 +3058,31 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax 0.6.28", + "regex-syntax 0.6.29", ] [[package]] name = "regex-automata" -version = "0.3.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.7.5", + "regex-syntax 0.8.5", ] [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" @@ -2732,10 +3095,10 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2 0.3.26", - "http 0.2.8", - "http-body 0.4.5", - "hyper 0.14.24", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", "hyper-rustls 0.24.2", "ipnet", "js-sys", @@ -2745,7 +3108,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.21.12", - "rustls-pemfile 1.0.4", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", @@ -2766,45 +3129,42 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", "futures-core", "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", - "hyper 1.5.0", - "hyper-rustls 0.27.3", + "hyper 1.6.0", + "hyper-rustls 0.27.7", "hyper-util", - "ipnet", "js-sys", "log", - "mime", - "once_cell", "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.23", - "rustls-pemfile 2.1.2", + "rustls 0.23.31", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper 1.0.2", "tokio", - "tokio-rustls 0.26.0", + "tokio-rustls 0.26.2", + "tower 0.5.2", + "tower-http 0.6.6", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots 0.26.6", - "windows-registry", + "webpki-roots 1.0.2", ] [[package]] @@ -2813,9 +3173,9 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7c5828ba3be8842e97b1c96b9df9449b326e5d252c5fb76d73605288b326b75" dependencies = [ - "axum 0.6.7", - "http 0.2.8", - "hyper 0.14.24", + "axum 0.6.20", + "http 0.2.12", + "hyper 0.14.32", "log", "regex", "tower-service", @@ -2838,15 +3198,14 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.16", "libc", - "spin 0.9.8", "untrusted 0.9.0", "windows-sys 0.52.0", ] @@ -2858,16 +3217,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ "base64 0.21.7", - "bitflags 2.5.0", + "bitflags 2.9.1", "serde", "serde_derive", ] [[package]] name = "rsa" -version = "0.9.6" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest", @@ -2876,7 +3235,7 @@ dependencies = [ "num-traits", "pkcs1", "pkcs8", - "rand_core", + "rand_core 0.6.4", "signature", "spki", "subtle", @@ -2895,12 +3254,12 @@ dependencies = [ [[package]] name = "rust-ini" -version = "0.19.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" +checksum = "3e0698206bcb8882bf2a9ecb4c1e7785db57ff052297085a6efd4fe42302068a" dependencies = [ "cfg-if", - "ordered-multimap 0.6.0", + "ordered-multimap 0.7.3", ] [[package]] @@ -2918,17 +3277,17 @@ dependencies = [ "futures", "hex", "hmac", - "http 0.2.8", + "http 0.2.12", "log", "maybe-async", "md5", "percent-encoding", - "quick-xml", + "quick-xml 0.26.0", "reqwest 0.11.27", "serde", "serde_derive", "sha2", - "thiserror", + "thiserror 1.0.69", "time", "tokio", "tokio-stream", @@ -2937,15 +3296,21 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" [[package]] name = "rustc-hash" -version = "2.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[package]] name = "rustc_version" @@ -2958,15 +3323,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2988,47 +3353,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", + "ring 0.17.14", "rustls-webpki 0.101.7", "sct", ] [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" -dependencies = [ - "ring 0.17.8", - "rustls-pki-types", - "rustls-webpki 0.102.8", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls" -version = "0.23.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" +checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" dependencies = [ + "aws-lc-rs", "log", "once_cell", - "ring 0.17.8", + "ring 0.17.14", "rustls-pki-types", - "rustls-webpki 0.102.8", + "rustls-webpki 0.103.4", "subtle", "zeroize", ] [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.2", "rustls-pki-types", "schannel", "security-framework", @@ -3044,20 +3396,41 @@ dependencies = [ ] [[package]] -name = "rustls-pemfile" -version = "2.1.2" +name = "rustls-pki-types" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" dependencies = [ - "base64 0.22.1", - "rustls-pki-types", + "web-time", + "zeroize", ] [[package]] -name = "rustls-pki-types" -version = "1.10.0" +name = "rustls-platform-verifier" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" +checksum = "19787cda76408ec5404443dc8b31795c87cd8fec49762dc75fa727740d34acc1" +dependencies = [ + "core-foundation 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls 0.23.31", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki 0.103.4", + "security-framework", + "security-framework-sys", + "webpki-root-certs 0.26.11", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" @@ -3065,40 +3438,50 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", + "ring 0.17.14", "untrusted 0.9.0", ] [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ - "ring 0.17.8", + "aws-lc-rs", + "ring 0.17.14", "rustls-pki-types", "untrusted 0.9.0", ] [[package]] name = "rustversion" -version = "1.0.11" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3112,7 +3495,7 @@ dependencies = [ "libpk", "metrics", "num-format", - "reqwest 0.12.8", + "reqwest 0.12.22", "serde", "serde_json", "sqlx", @@ -3123,9 +3506,9 @@ dependencies = [ [[package]] name = "scopeguard" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "scroll" @@ -3144,7 +3527,7 @@ checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -3153,15 +3536,15 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", + "ring 0.17.14", "untrusted 0.9.0", ] [[package]] name = "sea-query" -version = "0.32.1" +version = "0.32.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "085e94f7d7271c0393ac2d164a39994b1dff1b06bc40cd9a0da04f3d672b0fee" +checksum = "8a5d1c518eaf5eda38e5773f902b26ab6d5e9e9e2bb2349ca6c64cf96f80448c" dependencies = [ "inherent", "sea-query-derive", @@ -3169,26 +3552,26 @@ dependencies = [ [[package]] name = "sea-query-derive" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9834af2c4bd8c5162f00c89f1701fb6886119a88062cf76fe842ea9e232b9839" +checksum = "bae0cbad6ab996955664982739354128c58d16e126114fe88c2a493642502aab" dependencies = [ "darling", "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.87", - "thiserror", + "syn", + "thiserror 2.0.12", ] [[package]] name = "security-framework" -version = "2.11.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "80fb1d92c5028aa318b4b8bd7302a5bfcf48be96a37fc6fc790f806b0004ee0c" dependencies = [ - "bitflags 2.5.0", - "core-foundation", + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -3196,9 +3579,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", @@ -3206,9 +3589,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.16" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" dependencies = [ "serde", ] @@ -3220,8 +3603,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a7332159e544e34db06b251b1eda5e546bd90285c3f58d9c8ff8450b484e0da" dependencies = [ "httpdate", - "reqwest 0.12.8", - "rustls 0.23.23", + "reqwest 0.12.22", + "rustls 0.23.31", "sentry-backtrace", "sentry-contexts", "sentry-core", @@ -3230,7 +3613,7 @@ dependencies = [ "sentry-tracing", "tokio", "ureq", - "webpki-roots 0.26.6", + "webpki-roots 0.26.11", ] [[package]] @@ -3266,7 +3649,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "653942e6141f16651273159f4b8b1eaeedf37a7554c00cd798953e64b8a9bf72" dependencies = [ "once_cell", - "rand", + "rand 0.8.5", "sentry-types", "serde", "serde_json", @@ -3313,10 +3696,10 @@ checksum = "2d4203359e60724aa05cf2385aaf5d4f147e837185d7dd2b9ccf1ee77f4420c8" dependencies = [ "debugid", "hex", - "rand", + "rand 0.8.5", "serde", "serde_json", - "thiserror", + "thiserror 1.0.69", "time", "url", "uuid", @@ -3324,9 +3707,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] @@ -3343,52 +3726,54 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.217" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7" dependencies = [ "indexmap", "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serde_path_to_error" -version = "0.1.9" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b04f22b563c91331a10074bda3dd5492e3cc39d56bd557e91c0af42b6c7341" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" dependencies = [ + "itoa", "serde", ] [[package]] name = "serde_repr" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] @@ -3438,15 +3823,15 @@ dependencies = [ [[package]] name = "sha1_smol" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" +checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -3455,9 +3840,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] @@ -3468,21 +3853,11 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" -[[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-registry" -version = "1.4.1" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" dependencies = [ "libc", ] @@ -3494,7 +3869,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -3505,9 +3880,9 @@ checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" [[package]] name = "simdutf8" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] name = "siphasher" @@ -3517,24 +3892,21 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "sketches-ddsketch" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ceb945e54128e09c43d8e4f1277851bd5044c6fc540bbaa2ad888f60b3da9ae7" +checksum = "85636c14b73d81f541e525f585c0a2109e6744e1565b5c1668e31c70c10ed65c" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg", -] +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" dependencies = [ "serde", ] @@ -3551,22 +3923,22 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.7" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] name = "socket2" -version = "0.5.7" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3594,21 +3966,11 @@ dependencies = [ "der", ] -[[package]] -name = "sqlformat" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" -dependencies = [ - "nom", - "unicode_categories", -] - [[package]] name = "sqlx" -version = "0.8.2" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" dependencies = [ "sqlx-core", "sqlx-macros", @@ -3619,38 +3981,33 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.8.2" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" dependencies = [ - "atoi", - "byteorder", + "base64 0.22.1", "bytes", "chrono", "crc", "crossbeam-queue", "either", "event-listener", - "futures-channel", "futures-core", "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.14.5", - "hashlink", - "hex", + "hashbrown 0.15.5", + "hashlink 0.10.0", "indexmap", "log", "memchr", "once_cell", - "paste", "percent-encoding", "serde", "serde_json", "sha2", "smallvec", - "sqlformat", - "thiserror", + "thiserror 2.0.12", "time", "tokio", "tokio-stream", @@ -3661,22 +4018,22 @@ dependencies = [ [[package]] name = "sqlx-macros" -version = "0.8.2" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" dependencies = [ "proc-macro2", "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.87", + "syn", ] [[package]] name = "sqlx-macros-core" -version = "0.8.2" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", @@ -3692,21 +4049,20 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.87", - "tempfile", + "syn", "tokio", "url", ] [[package]] name = "sqlx-mysql" -version = "0.8.2" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.5.0", + "bitflags 2.9.1", "byteorder", "bytes", "chrono", @@ -3728,7 +4084,7 @@ dependencies = [ "memchr", "once_cell", "percent-encoding", - "rand", + "rand 0.8.5", "rsa", "serde", "sha1", @@ -3736,7 +4092,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.12", "time", "tracing", "uuid", @@ -3745,13 +4101,13 @@ dependencies = [ [[package]] name = "sqlx-postgres" -version = "0.8.2" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.5.0", + "bitflags 2.9.1", "byteorder", "chrono", "crc", @@ -3759,7 +4115,6 @@ dependencies = [ "etcetera", "futures-channel", "futures-core", - "futures-io", "futures-util", "hex", "hkdf", @@ -3770,14 +4125,14 @@ dependencies = [ "md-5", "memchr", "once_cell", - "rand", + "rand 0.8.5", "serde", "serde_json", "sha2", "smallvec", "sqlx-core", "stringprep", - "thiserror", + "thiserror 2.0.12", "time", "tracing", "uuid", @@ -3786,9 +4141,9 @@ dependencies = [ [[package]] name = "sqlx-sqlite" -version = "0.8.2" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" dependencies = [ "atoi", "chrono", @@ -3804,12 +4159,19 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", + "thiserror 2.0.12", "time", "tracing", "url", "uuid", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "static_assertions" version = "1.1.0" @@ -3829,26 +4191,15 @@ dependencies = [ [[package]] name = "subtle" -version = "2.5.0" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "1.0.107" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -3863,13 +4214,24 @@ checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "sync_wrapper" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -3877,7 +4239,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", - "core-foundation", + "core-foundation 0.9.4", "system-configuration-sys", ] @@ -3892,44 +4254,52 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.10.1" +name = "thiserror" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "cfg-if", - "fastrand", - "rustix", - "windows-sys 0.52.0", + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "1.0.64" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.12", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ - "once_cell", + "cfg-if", ] [[package]] @@ -3945,9 +4315,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.36" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -3960,15 +4330,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -3984,10 +4354,20 @@ dependencies = [ ] [[package]] -name = "tinyvec" -version = "1.6.0" +name = "tinystr" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -4000,31 +4380,33 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.47.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.7", + "slab", + "socket2 0.6.0", "tokio-macros", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "tokio-macros" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -4039,31 +4421,19 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ - "rustls 0.22.4", - "rustls-pki-types", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" -dependencies = [ - "rustls 0.23.23", - "rustls-pki-types", + "rustls 0.23.31", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" dependencies = [ "futures-core", "pin-project-lite", @@ -4072,9 +4442,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.12" +version = "0.7.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5" dependencies = [ "bytes", "futures-core", @@ -4085,26 +4455,25 @@ dependencies = [ [[package]] name = "tokio-websockets" -version = "0.7.0" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "988c6e20955aa5043e0822cb27093ebaabb430a126cda0223824b6d65ea900c1" +checksum = "9fcaf159b4e7a376b05b5bfd77bfd38f3324f5fce751b4213bfc7eaa47affb4e" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", "fastrand", "futures-core", "futures-sink", - "http 1.1.0", + "http 1.3.1", "httparse", - "ring 0.17.8", - "rustls-native-certs", + "ring 0.17.14", "rustls-pki-types", + "rustls-platform-verifier", "sha1_smol", "simdutf8", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls 0.26.2", "tokio-util", - "tracing", ] [[package]] @@ -4118,9 +4487,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", "serde_spanned", @@ -4130,26 +4499,33 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", + "toml_write", "winnow", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tower" version = "0.4.13" @@ -4167,22 +4543,19 @@ dependencies = [ ] [[package]] -name = "tower-http" -version = "0.3.5" +name = "tower" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ - "bitflags 1.3.2", - "bytes", "futures-core", "futures-util", - "http 0.2.8", - "http-body 0.4.5", - "http-range-header", "pin-project-lite", - "tower", + "sync_wrapper 1.0.2", + "tokio", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -4191,11 +4564,11 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.9.1", "bytes", "futures-util", - "http 1.1.0", - "http-body 1.0.0", + "http 1.3.1", + "http-body 1.0.1", "http-body-util", "pin-project-lite", "tower-layer", @@ -4204,22 +4577,40 @@ dependencies = [ ] [[package]] -name = "tower-layer" -version = "0.3.2" +name = "tower-http" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +dependencies = [ + "bitflags 2.9.1", + "bytes", + "futures-util", + "http 1.3.1", + "http-body 1.0.1", + "iri-string", + "pin-project-lite", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -4229,20 +4620,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -4261,9 +4652,9 @@ dependencies = [ [[package]] name = "tracing-serde" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ "serde", "tracing-core", @@ -4271,9 +4662,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "matchers", "nu-ansi-term", @@ -4292,31 +4683,29 @@ dependencies = [ [[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 = "twilight-cache-inmemory" -version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" +version = "0.16.0" +source = "git+https://github.com/pluralkit/twilight?branch=pluralkit-70105ef#941604f888bb8d25239f4f3d4dfb1a9ecdb9851b" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.9.1", "dashmap", "serde", - "tracing", "twilight-model", "twilight-util", ] [[package]] name = "twilight-gateway" -version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" +version = "0.16.0" +source = "git+https://github.com/pluralkit/twilight?branch=pluralkit-70105ef#941604f888bb8d25239f4f3d4dfb1a9ecdb9851b" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.9.1", "fastrand", - "flate2", "futures-core", "futures-sink", "serde", @@ -4327,12 +4716,13 @@ dependencies = [ "twilight-gateway-queue", "twilight-http", "twilight-model", + "zstd-safe", ] [[package]] name = "twilight-gateway-queue" -version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" +version = "0.16.0" +source = "git+https://github.com/pluralkit/twilight?branch=pluralkit-70105ef#941604f888bb8d25239f4f3d4dfb1a9ecdb9851b" dependencies = [ "tokio", "tracing", @@ -4340,16 +4730,17 @@ dependencies = [ [[package]] name = "twilight-http" -version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" +version = "0.16.0" +source = "git+https://github.com/pluralkit/twilight?branch=pluralkit-70105ef#941604f888bb8d25239f4f3d4dfb1a9ecdb9851b" dependencies = [ "fastrand", - "http 1.1.0", + "http 1.3.1", "http-body-util", - "hyper 1.5.0", - "hyper-rustls 0.26.0", + "hyper 1.6.0", + "hyper-rustls 0.27.7", "hyper-util", "percent-encoding", + "rustls 0.23.31", "serde", "serde_json", "tokio", @@ -4361,8 +4752,8 @@ dependencies = [ [[package]] name = "twilight-http-ratelimiting" -version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" +version = "0.16.0" +source = "git+https://github.com/pluralkit/twilight?branch=pluralkit-70105ef#941604f888bb8d25239f4f3d4dfb1a9ecdb9851b" dependencies = [ "tokio", "tracing", @@ -4370,10 +4761,10 @@ dependencies = [ [[package]] name = "twilight-model" -version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" +version = "0.16.0" +source = "git+https://github.com/pluralkit/twilight?branch=pluralkit-70105ef#941604f888bb8d25239f4f3d4dfb1a9ecdb9851b" dependencies = [ - "bitflags 2.5.0", + "bitflags 2.9.1", "serde", "serde-value", "serde_repr", @@ -4382,31 +4773,31 @@ dependencies = [ [[package]] name = "twilight-util" -version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" +version = "0.16.0" +source = "git+https://github.com/pluralkit/twilight?branch=pluralkit-70105ef#941604f888bb8d25239f4f3d4dfb1a9ecdb9851b" dependencies = [ "twilight-model", ] [[package]] name = "twilight-validate" -version = "0.16.0-rc.1" -source = "git+https://github.com/pluralkit/twilight#b346757b3054d461f5652b5ba0148a70eda5697e" +version = "0.16.0" +source = "git+https://github.com/pluralkit/twilight?branch=pluralkit-70105ef#941604f888bb8d25239f4f3d4dfb1a9ecdb9851b" dependencies = [ "twilight-model", ] [[package]] name = "typenum" -version = "1.16.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "ucd-trie" -version = "0.1.5" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uname" @@ -4425,42 +4816,36 @@ checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-bidi" -version = "0.3.10" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-properties" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-segmentation" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - -[[package]] -name = "unicode_categories" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] name = "uniffi" @@ -4515,7 +4900,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55137c122f712d9330fd985d66fa61bdc381752e89c35708c13ce63049a3002c" dependencies = [ "quote", - "syn 2.0.87", + "syn", ] [[package]] @@ -4547,7 +4932,7 @@ dependencies = [ "proc-macro2", "quote", "serde", - "syn 2.0.87", + "syn", "toml 0.5.11", "uniffi_build", "uniffi_meta", @@ -4604,27 +4989,27 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.10.1" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" dependencies = [ "base64 0.22.1", "log", "once_cell", - "rustls 0.23.23", + "rustls 0.23.31", "rustls-pki-types", "url", - "webpki-roots 0.26.6", + "webpki-roots 0.26.11", ] [[package]] name = "url" -version = "2.5.2" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", - "idna 0.5.0", + "idna", "percent-encoding", "serde", ] @@ -4636,20 +5021,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" [[package]] -name = "uuid" -version = "1.11.0" +name = "utf8_iter" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d" dependencies = [ - "getrandom", + "getrandom 0.3.3", + "js-sys", "serde", + "wasm-bindgen", ] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -4659,25 +5052,43 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] [[package]] name = "wasite" @@ -4687,46 +5098,48 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.87", + "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -4734,28 +5147,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-streams" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -4766,9 +5182,19 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -4790,10 +5216,28 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.17.8", + "ring 0.17.14", "untrusted 0.9.0", ] +[[package]] +name = "webpki-root-certs" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c7f0ef91146ebfb530314f5f1d24528d7f0767efbfd31dce919275413e393e" +dependencies = [ + "webpki-root-certs 1.0.2", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4ffd8df1c57e87c325000a3d6ef93db75279dc3a231125aac571650f22b12a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" version = "0.22.6" @@ -4811,9 +5255,18 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.6" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841c67bff177718f1d4dfefde8d8f0e78f9b6589319ba88312f567fc5841a958" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.2", +] + +[[package]] +name = "webpki-roots" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] @@ -4829,17 +5282,29 @@ dependencies = [ [[package]] name = "weezl" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" +checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] [[package]] name = "whoami" -version = "1.5.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" +checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7" dependencies = [ - "redox_syscall 0.4.1", + "redox_syscall", "wasite", ] @@ -4859,59 +5324,78 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core", - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" -version = "0.52.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-registry" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", "windows-result", "windows-strings", - "windows-targets 0.52.6", ] +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + [[package]] name = "windows-result" -version = "0.2.0" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-targets 0.52.6", + "windows-link", ] [[package]] name = "windows-strings" -version = "0.1.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-result", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -4920,7 +5404,7 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.42.1", + "windows-targets 0.42.2", ] [[package]] @@ -4951,18 +5435,27 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.42.1" +name = "windows-sys" +version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" dependencies = [ - "windows_aarch64_gnullvm 0.42.1", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm 0.42.1", - "windows_x86_64_msvc 0.42.1", + "windows-targets 0.53.3", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] @@ -4989,7 +5482,7 @@ dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", + "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", @@ -4997,10 +5490,27 @@ dependencies = [ ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.1" +name = "windows-targets" +version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" +checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" [[package]] name = "windows_aarch64_gnullvm" @@ -5015,10 +5525,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] -name = "windows_aarch64_msvc" -version = "0.42.1" +name = "windows_aarch64_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" @@ -5033,10 +5549,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] -name = "windows_i686_gnu" -version = "0.42.1" +name = "windows_aarch64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" @@ -5050,6 +5572,12 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" +[[package]] +name = "windows_i686_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" @@ -5057,10 +5585,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] -name = "windows_i686_msvc" -version = "0.42.1" +name = "windows_i686_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" @@ -5075,10 +5609,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] -name = "windows_x86_64_gnu" -version = "0.42.1" +name = "windows_i686_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" @@ -5093,10 +5633,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.1" +name = "windows_x86_64_gnu" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" [[package]] name = "windows_x86_64_gnullvm" @@ -5111,10 +5657,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] -name = "windows_x86_64_msvc" -version = "0.42.1" +name = "windows_x86_64_gnullvm" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" @@ -5129,10 +5681,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] -name = "winnow" -version = "0.6.20" +name = "windows_x86_64_msvc" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95" dependencies = [ "memchr", ] @@ -5148,32 +5706,94 @@ dependencies = [ ] [[package]] -name = "yaml-rust" -version = "0.4.5" +name = "wit-bindgen-rt" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "linked-hash-map", + "bitflags 2.9.1", +] + +[[package]] +name = "writeable" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" + +[[package]] +name = "yaml-rust2" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8902160c4e6f2fb145dbe9d6760a75e3c9522d8bf796ed7047c85919ac7115f8" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink 0.8.4", +] + +[[package]] +name = "yoke" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", ] [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.87", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", ] [[package]] @@ -5181,3 +5801,55 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.15+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index fac957d7..c001250a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ members = [ [workspace.dependencies] anyhow = "1" -axum = "0.7.5" axum-macros = "0.4.1" bytes = "1.6.0" chrono = "0.4" @@ -18,21 +17,22 @@ reqwest = { version = "0.12.7" , default-features = false, features = ["rustls-t sentry = { version = "0.36.0", default-features = false, features = ["backtrace", "contexts", "panic", "debug-images", "reqwest", "rustls"] } # replace native-tls with rustls serde = { version = "1.0.196", features = ["derive"] } serde_json = "1.0.117" -signal-hook = "0.3.17" -sqlx = { version = "0.8.2", features = ["runtime-tokio", "postgres", "time", "chrono", "macros", "uuid"] } +sqlx = { version = "0.8.2", features = ["runtime-tokio", "postgres", "time", "macros", "uuid"] } tokio = { version = "1.36.0", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3.16", features = ["env-filter", "json"] } uuid = { version = "1.7.0", features = ["serde"] } -twilight-gateway = { git = "https://github.com/pluralkit/twilight" } -twilight-cache-inmemory = { git = "https://github.com/pluralkit/twilight", features = ["permission-calculator"] } -twilight-util = { git = "https://github.com/pluralkit/twilight", features = ["permission-calculator"] } -twilight-model = { git = "https://github.com/pluralkit/twilight" } -twilight-http = { git = "https://github.com/pluralkit/twilight", default-features = false, features = ["rustls-native-roots"] } +axum = { git = "https://github.com/pluralkit/axum", branch = "v0.8.4-pluralkit" } -#twilight-gateway = { path = "../twilight/twilight-gateway" } -#twilight-cache-inmemory = { path = "../twilight/twilight-cache-inmemory", features = ["permission-calculator"] } -#twilight-util = { path = "../twilight/twilight-util", features = ["permission-calculator"] } -#twilight-model = { path = "../twilight/twilight-model" } -#twilight-http = { path = "../twilight/twilight-http", default-features = false, features = ["rustls-native-roots"] } +twilight-gateway = { git = "https://github.com/pluralkit/twilight", branch = "pluralkit-70105ef" } +twilight-cache-inmemory = { git = "https://github.com/pluralkit/twilight", branch = "pluralkit-70105ef", features = ["permission-calculator"] } +twilight-util = { git = "https://github.com/pluralkit/twilight", branch = "pluralkit-70105ef", features = ["permission-calculator"] } +twilight-model = { git = "https://github.com/pluralkit/twilight", branch = "pluralkit-70105ef" } +twilight-http = { git = "https://github.com/pluralkit/twilight", branch = "pluralkit-70105ef", default-features = false, features = ["rustls-aws_lc_rs", "rustls-native-roots"] } + +# twilight-gateway = { path = "../twilight/twilight-gateway" } +# twilight-cache-inmemory = { path = "../twilight/twilight-cache-inmemory", features = ["permission-calculator"] } +# twilight-util = { path = "../twilight/twilight-util", features = ["permission-calculator"] } +# twilight-model = { path = "../twilight/twilight-model" } +# twilight-http = { path = "../twilight/twilight-http", default-features = false, features = ["rustls-aws_lc_rs", "rustls-native-roots"] } diff --git a/Myriad/Cache/HTTPDiscordCache.cs b/Myriad/Cache/HTTPDiscordCache.cs index 31035660..2314b957 100644 --- a/Myriad/Cache/HTTPDiscordCache.cs +++ b/Myriad/Cache/HTTPDiscordCache.cs @@ -1,7 +1,10 @@ using Serilog; using System.Net; +using System.Text; using System.Text.Json; +using NodaTime; + using Myriad.Serialization; using Myriad.Types; @@ -11,7 +14,8 @@ public class HttpDiscordCache: IDiscordCache { private readonly ILogger _logger; private readonly HttpClient _client; - private readonly Uri _cacheEndpoint; + private readonly string _cacheEndpoint; + private readonly string? _eventTarget; private readonly int _shardCount; private readonly ulong _ownUserId; @@ -21,11 +25,12 @@ public class HttpDiscordCache: IDiscordCache public EventHandler<(bool?, string)> OnDebug; - public HttpDiscordCache(ILogger logger, HttpClient client, string cacheEndpoint, int shardCount, ulong ownUserId, bool useInnerCache) + public HttpDiscordCache(ILogger logger, HttpClient client, string cacheEndpoint, string? eventTarget, int shardCount, ulong ownUserId, bool useInnerCache) { _logger = logger; _client = client; - _cacheEndpoint = new Uri(cacheEndpoint); + _cacheEndpoint = cacheEndpoint; + _eventTarget = eventTarget; _shardCount = shardCount; _ownUserId = ownUserId; _jsonSerializerOptions = new JsonSerializerOptions().ConfigureForMyriad(); @@ -47,13 +52,12 @@ public class HttpDiscordCache: IDiscordCache private async Task QueryCache(string endpoint, ulong guildId) { - var cluster = _cacheEndpoint.Authority; - // todo: there should not be infra-specific code here - if (cluster.Contains(".service.consul") || cluster.Contains("process.pluralkit-gateway.internal")) - // int(((guild_id >> 22) % shard_count) / 16) - cluster = $"cluster{(int)(((guildId >> 22) % (ulong)_shardCount) / 16)}.{cluster}"; + var cluster = _cacheEndpoint; - var response = await _client.GetAsync($"{_cacheEndpoint.Scheme}://{cluster}{endpoint}"); + if (cluster.Contains("{clusterid}")) + cluster = cluster.Replace("{clusterid}", $"{(int)(((guildId >> 22) % (ulong)_shardCount) / 16)}"); + + var response = await _client.GetAsync($"http://{cluster}{endpoint}"); if (response.StatusCode == HttpStatusCode.NotFound) return default; @@ -65,6 +69,70 @@ public class HttpDiscordCache: IDiscordCache return JsonSerializer.Deserialize(plaintext, _jsonSerializerOptions); } + public Task GetLastMessage(ulong guildId, ulong channelId) + => QueryCache($"/guilds/{guildId}/channels/{channelId}/last_message", guildId); + + private Task AwaitEvent(ulong guildId, object data) + => AwaitEventShard((int)((guildId >> 22) % (ulong)_shardCount), data); + + private async Task AwaitEventShard(int shardId, object data) + { + if (_eventTarget == null) + throw new Exception("missing event target for remote await event"); + + var cluster = _cacheEndpoint; + + if (cluster.Contains("{clusterid}")) + cluster = cluster.Replace("{clusterid}", $"{(int)(shardId / 16)}"); + + var response = await _client.PostAsync( + $"http://{cluster}/await_event", + new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8) + ); + + if (response.StatusCode != HttpStatusCode.NoContent) + throw new Exception($"failed to await event from gateway: {response.StatusCode}"); + } + + public async Task AwaitReaction(ulong guildId, ulong messageId, ulong userId, Duration? timeout) + { + var obj = new + { + message_id = messageId, + user_id = userId, + target = _eventTarget!, + timeout = timeout?.TotalSeconds, + }; + + await AwaitEvent(guildId, obj); + } + + public async Task AwaitMessage(ulong guildId, ulong channelId, ulong authorId, Duration? timeout, string[] options = null) + { + var obj = new + { + channel_id = channelId, + author_id = authorId, + target = _eventTarget!, + timeout = timeout?.TotalSeconds, + options = options, + }; + + await AwaitEvent(guildId, obj); + } + + public async Task AwaitInteraction(int shardId, string id, Duration? timeout) + { + var obj = new + { + id = id, + target = _eventTarget!, + timeout = timeout?.TotalSeconds, + }; + + await AwaitEventShard(shardId, obj); + } + public async Task TryGetGuild(ulong guildId) { var hres = await QueryCache($"/guilds/{guildId}", guildId); diff --git a/Myriad/Gateway/Events/MessageUpdateEvent.cs b/Myriad/Gateway/Events/MessageUpdateEvent.cs index 2e918fb8..45925549 100644 --- a/Myriad/Gateway/Events/MessageUpdateEvent.cs +++ b/Myriad/Gateway/Events/MessageUpdateEvent.cs @@ -10,6 +10,8 @@ public record MessageUpdateEvent(ulong Id, ulong ChannelId): IGatewayEvent public Optional Member { get; init; } public Optional Attachments { get; init; } + public Message.MessageType Type { get; init; } + public Optional GuildId { get; init; } // TODO: lots of partials } \ No newline at end of file diff --git a/Myriad/Myriad.csproj b/Myriad/Myriad.csproj index 3b92ea30..fb7ed9bb 100644 --- a/Myriad/Myriad.csproj +++ b/Myriad/Myriad.csproj @@ -21,9 +21,14 @@ + + + + + + - diff --git a/Myriad/packages.lock.json b/Myriad/packages.lock.json index 945321a9..7a4bc2c0 100644 --- a/Myriad/packages.lock.json +++ b/Myriad/packages.lock.json @@ -2,6 +2,22 @@ "version": 1, "dependencies": { "net8.0": { + "NodaTime": { + "type": "Direct", + "requested": "[3.2.0, )", + "resolved": "3.2.0", + "contentHash": "yoRA3jEJn8NM0/rQm78zuDNPA3DonNSZdsorMUj+dltc1D+/Lc5h9YXGqbEEZozMGr37lAoYkcSM/KjTVqD0ow==" + }, + "NodaTime.Serialization.JsonNet": { + "type": "Direct", + "requested": "[3.1.0, )", + "resolved": "3.1.0", + "contentHash": "eEr9lXUz50TYr4rpeJG4TDAABkpxjIKr5mDSi/Zav8d6Njy6fH7x4ZtNwWFj0Vd+vIvEZNrHFQ4Gfy8j4BqRGg==", + "dependencies": { + "Newtonsoft.Json": "13.0.3", + "NodaTime": "[3.0.0, 4.0.0)" + } + }, "Polly": { "type": "Direct", "requested": "[8.5.0, )", @@ -17,12 +33,6 @@ "resolved": "1.1.1", "contentHash": "1MUQLiSo4KDkQe6nzQRhIU05lm9jlexX5BVsbuw0SL82ynZ+GzAHQxJVDPVBboxV37Po3SG077aX8DuSy8TkaA==" }, - "Serilog": { - "type": "Direct", - "requested": "[4.2.0, )", - "resolved": "4.2.0", - "contentHash": "gmoWVOvKgbME8TYR+gwMf7osROiWAURterc6Rt2dQyX7wtjZYpqFiA/pY6ztjGQKKV62GGCyOcmtP1UKMHgSmA==" - }, "StackExchange.Redis": { "type": "Direct", "requested": "[2.8.22, )", @@ -52,6 +62,11 @@ "resolved": "6.0.0", "contentHash": "/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==" }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.3", + "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==" + }, "Pipelines.Sockets.Unofficial": { "type": "Transitive", "resolved": "2.2.8", @@ -69,6 +84,9 @@ "type": "Transitive", "resolved": "5.0.1", "contentHash": "qEePWsaq9LoEEIqhbGe6D5J8c9IqQOUuTzzV6wn1POlfdLkJliZY3OlB0j0f17uMWlqZYjH7txj+2YbyrIA8Yg==" + }, + "serilog": { + "type": "Project" } } } diff --git a/PluralKit.API/ApiConfig.cs b/PluralKit.API/ApiConfig.cs index fc34d515..8da70196 100644 --- a/PluralKit.API/ApiConfig.cs +++ b/PluralKit.API/ApiConfig.cs @@ -6,4 +6,6 @@ public class ApiConfig public string? ClientId { get; set; } public string? ClientSecret { get; set; } public bool TrustAuth { get; set; } = false; + public string? AvatarServiceUrl { get; set; } + public bool SearchGuildSettings = false; } \ No newline at end of file diff --git a/PluralKit.API/AuthorizationTokenHandlerMiddleware.cs b/PluralKit.API/AuthorizationTokenHandlerMiddleware.cs index a09c869e..5f1c4011 100644 --- a/PluralKit.API/AuthorizationTokenHandlerMiddleware.cs +++ b/PluralKit.API/AuthorizationTokenHandlerMiddleware.cs @@ -21,6 +21,12 @@ public class AuthorizationTokenHandlerMiddleware && int.TryParse(sidHeaders[0], out var systemId)) ctx.Items.Add("SystemId", new SystemId(systemId)); + if (cfg.TrustAuth + && ctx.Request.Headers.TryGetValue("X-PluralKit-AppId", out var aidHeaders) + && aidHeaders.Count > 0 + && int.TryParse(aidHeaders[0], out var appId)) + ctx.Items.Add("AppId", appId); + await _next.Invoke(ctx); } } \ No newline at end of file diff --git a/PluralKit.API/Controllers/v2/DiscordControllerV2.cs b/PluralKit.API/Controllers/v2/DiscordControllerV2.cs index 340c8698..7547a751 100644 --- a/PluralKit.API/Controllers/v2/DiscordControllerV2.cs +++ b/PluralKit.API/Controllers/v2/DiscordControllerV2.cs @@ -20,7 +20,7 @@ public class DiscordControllerV2: PKControllerBase if (ContextFor(system) != LookupContext.ByOwner) throw Errors.GenericMissingPermissions; - var settings = await _repo.GetSystemGuild(guild_id, system.Id, false); + var settings = await _repo.GetSystemGuild(guild_id, system.Id, false, _config.SearchGuildSettings); if (settings == null) throw Errors.SystemGuildNotFound; @@ -34,7 +34,7 @@ public class DiscordControllerV2: PKControllerBase if (ContextFor(system) != LookupContext.ByOwner) throw Errors.GenericMissingPermissions; - var settings = await _repo.GetSystemGuild(guild_id, system.Id, false); + var settings = await _repo.GetSystemGuild(guild_id, system.Id, false, _config.SearchGuildSettings); if (settings == null) throw Errors.SystemGuildNotFound; @@ -58,7 +58,7 @@ public class DiscordControllerV2: PKControllerBase if (member.System != system.Id) throw Errors.NotOwnMemberError; - var settings = await _repo.GetMemberGuild(guild_id, member.Id, false); + var settings = await _repo.GetMemberGuild(guild_id, member.Id, false, _config.SearchGuildSettings ? system.Id : null); if (settings == null) throw Errors.MemberGuildNotFound; @@ -75,7 +75,7 @@ public class DiscordControllerV2: PKControllerBase if (member.System != system.Id) throw Errors.NotOwnMemberError; - var settings = await _repo.GetMemberGuild(guild_id, member.Id, false); + var settings = await _repo.GetMemberGuild(guild_id, member.Id, false, _config.SearchGuildSettings ? system.Id : null); if (settings == null) throw Errors.MemberGuildNotFound; diff --git a/PluralKit.API/Controllers/v2/MemberControllerV2.cs b/PluralKit.API/Controllers/v2/MemberControllerV2.cs index 25163dff..6c37fa81 100644 --- a/PluralKit.API/Controllers/v2/MemberControllerV2.cs +++ b/PluralKit.API/Controllers/v2/MemberControllerV2.cs @@ -1,3 +1,7 @@ +using System.Net; +using System.Net.Http; +using System.Net.Http.Json; + using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json.Linq; @@ -50,6 +54,9 @@ public class MemberControllerV2: PKControllerBase if (patch.Errors.Count > 0) throw new ModelParseError(patch.Errors); + if (patch.AvatarUrl.Value != null) + patch.AvatarUrl = await TryUploadAvatar(patch.AvatarUrl.Value, system); + using var conn = await _db.Obtain(); using var tx = await conn.BeginTransactionAsync(); @@ -110,6 +117,9 @@ public class MemberControllerV2: PKControllerBase if (patch.Errors.Count > 0) throw new ModelParseError(patch.Errors); + if (patch.AvatarUrl.Value != null) + patch.AvatarUrl = await TryUploadAvatar(patch.AvatarUrl.Value, system); + var newMember = await _repo.UpdateMember(member.Id, patch); return Ok(newMember.ToJson(LookupContext.ByOwner, systemStr: system.Hid)); } @@ -129,4 +139,28 @@ public class MemberControllerV2: PKControllerBase return NoContent(); } + + private async Task TryUploadAvatar(string avatarUrl, PKSystem system) + { + if (!avatarUrl.StartsWith("https://serve.apparyllis.com/")) return avatarUrl; + if (_config.AvatarServiceUrl == null) return avatarUrl; + if (!HttpContext.Items.TryGetValue("AppId", out var appId) || (int)appId != 1) return avatarUrl; + + using var client = new HttpClient(); + var response = await client.PostAsJsonAsync(_config.AvatarServiceUrl + "/pull", + new { url = avatarUrl, kind = "avatar", uploaded_by = (string)null, system_id = system.Uuid.ToString() }); + + if (response.StatusCode != HttpStatusCode.OK) + { + var error = await response.Content.ReadFromJsonAsync(); + throw new PKError(500, 0, $"Error uploading image to CDN: {error.Error}"); + } + + var success = await response.Content.ReadFromJsonAsync(); + return success.Url; + } + + public record ErrorResponse(string Error); + + public record SuccessResponse(string Url, bool New); } \ No newline at end of file diff --git a/PluralKit.API/PluralKit.API.csproj b/PluralKit.API/PluralKit.API.csproj index e35518dd..b1d88098 100644 --- a/PluralKit.API/PluralKit.API.csproj +++ b/PluralKit.API/PluralKit.API.csproj @@ -32,6 +32,6 @@ - + diff --git a/PluralKit.API/Startup.cs b/PluralKit.API/Startup.cs index ecb91710..06af238a 100644 --- a/PluralKit.API/Startup.cs +++ b/PluralKit.API/Startup.cs @@ -35,7 +35,7 @@ public class Startup builder.RegisterInstance(InitUtils.BuildConfiguration(Environment.GetCommandLineArgs()).Build()) .As(); builder.RegisterModule(new ConfigModule("API")); - builder.RegisterModule(new LoggingModule("api", + builder.RegisterModule(new LoggingModule("dotnet-api", cfg: new LoggerConfiguration().Filter.ByExcluding( exc => exc.Exception is PKError || exc.Exception.IsUserError() ))); diff --git a/PluralKit.API/packages.lock.json b/PluralKit.API/packages.lock.json index adc4279b..0107414c 100644 --- a/PluralKit.API/packages.lock.json +++ b/PluralKit.API/packages.lock.json @@ -36,17 +36,20 @@ }, "Serilog.AspNetCore": { "type": "Direct", - "requested": "[9.0.0, )", - "resolved": "9.0.0", - "contentHash": "JslDajPlBsn3Pww1554flJFTqROvK9zz9jONNQgn0D8Lx2Trw8L0A8/n6zEQK1DAZWXrJwiVLw8cnTR3YFuYsg==", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "FAjtKPZ4IzqFQBqZKPv6evcXK/F0ls7RoXI/62Pnx2igkDZ6nZ/jn/C/FxVATqQbEQvtqP+KViWYIe4NZIHa2w==", "dependencies": { - "Serilog": "4.2.0", - "Serilog.Extensions.Hosting": "9.0.0", - "Serilog.Formatting.Compact": "3.0.0", - "Serilog.Settings.Configuration": "9.0.0", - "Serilog.Sinks.Console": "6.0.0", - "Serilog.Sinks.Debug": "3.0.0", - "Serilog.Sinks.File": "6.0.0" + "Microsoft.Extensions.DependencyInjection": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0", + "Serilog": "3.1.1", + "Serilog.Extensions.Hosting": "8.0.0", + "Serilog.Extensions.Logging": "8.0.0", + "Serilog.Formatting.Compact": "2.0.0", + "Serilog.Settings.Configuration": "8.0.0", + "Serilog.Sinks.Console": "5.0.0", + "Serilog.Sinks.Debug": "2.0.0", + "Serilog.Sinks.File": "5.0.0" } }, "App.Metrics": { @@ -296,21 +299,21 @@ }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "saxr2XzwgDU77LaQfYFXmddEDRUKHF4DaGMZkNB3qjdVSZlax3//dGJagJkKrGMIPNZs2jVFXITyCCR6UHJNdA==", + "resolved": "8.0.0", + "contentHash": "NSmDw3K0ozNDgShSIpsZcbFIzBX4w28nDag+TfaQujkXGazBm+lid5onlWoCBy4VsLxqnnKjEBbGSJVWJMf43g==", "dependencies": { - "System.Text.Encodings.Web": "9.0.0", - "System.Text.Json": "9.0.0" + "System.Text.Encodings.Web": "8.0.0", + "System.Text.Json": "8.0.0" } }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "1K8P7XzuzX8W8pmXcZjcrqS6x5eSSdvhQohmcpgiQNY/HlDAlnrhR9dvlURfFz428A+RTCJpUyB+aKTA6AgVcQ==", + "resolved": "8.0.0", + "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", - "Microsoft.Extensions.Options": "9.0.0", - "System.Diagnostics.DiagnosticSource": "9.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "System.Diagnostics.DiagnosticSource": "8.0.0" } }, "Microsoft.Extensions.FileProviders.Abstractions": { @@ -338,14 +341,14 @@ }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "yUKJgu81ExjvqbNWqZKshBbLntZMbMVz/P7Way2SBx7bMqA08Mfdc9O7hWDKAiSp+zPUGT6LKcSCQIPeDK+CCw==", + "resolved": "8.0.0", + "contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "9.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.0", - "Microsoft.Extensions.FileProviders.Abstractions": "9.0.0", - "Microsoft.Extensions.Logging.Abstractions": "9.0.0" + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0" } }, "Microsoft.Extensions.Logging": { @@ -461,30 +464,25 @@ "System.IO.Pipelines": "5.0.1" } }, - "Serilog": { - "type": "Transitive", - "resolved": "4.2.0", - "contentHash": "gmoWVOvKgbME8TYR+gwMf7osROiWAURterc6Rt2dQyX7wtjZYpqFiA/pY6ztjGQKKV62GGCyOcmtP1UKMHgSmA==" - }, "Serilog.Extensions.Hosting": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "u2TRxuxbjvTAldQn7uaAwePkWxTHIqlgjelekBtilAGL5sYyF3+65NWctN4UrwwGLsDC7c3Vz3HnOlu+PcoxXg==", + "resolved": "8.0.0", + "contentHash": "db0OcbWeSCvYQkHWu6n0v40N4kKaTAXNjlM3BKvcbwvNzYphQFcBR+36eQ/7hMMwOkJvAyLC2a9/jNdUL5NjtQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", - "Microsoft.Extensions.Hosting.Abstractions": "9.0.0", - "Microsoft.Extensions.Logging.Abstractions": "9.0.0", - "Serilog": "4.2.0", - "Serilog.Extensions.Logging": "9.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Serilog": "3.1.1", + "Serilog.Extensions.Logging": "8.0.0" } }, "Serilog.Extensions.Logging": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "NwSSYqPJeKNzl5AuXVHpGbr6PkZJFlNa14CdIebVjK3k/76kYj/mz5kiTRNVSsSaxM8kAIa1kpy/qyT9E4npRQ==", + "resolved": "8.0.0", + "contentHash": "YEAMWu1UnWgf1c1KP85l1SgXGfiVo0Rz6x08pCiPOIBt2Qe18tcZLvdBUuV5o1QHvrs8FAry9wTIhgBRtjIlEg==", "dependencies": { - "Microsoft.Extensions.Logging": "9.0.0", - "Serilog": "4.2.0" + "Microsoft.Extensions.Logging": "8.0.0", + "Serilog": "3.1.1" } }, "Serilog.Formatting.Compact": { @@ -514,12 +512,12 @@ }, "Serilog.Settings.Configuration": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "4/Et4Cqwa+F88l5SeFeNZ4c4Z6dEAIKbu3MaQb2Zz9F/g27T5a3wvfMcmCOaAiACjfUb4A6wrlTVfyYUZk3RRQ==", + "resolved": "8.0.0", + "contentHash": "nR0iL5HwKj5v6ULo3/zpP8NMcq9E2pxYA6XKTSWCbugVs4YqPyvaqaKOY+OMpPivKp7zMEpax2UKHnDodbRB0Q==", "dependencies": { - "Microsoft.Extensions.Configuration.Binder": "9.0.0", - "Microsoft.Extensions.DependencyModel": "9.0.0", - "Serilog": "4.2.0" + "Microsoft.Extensions.Configuration.Binder": "8.0.0", + "Microsoft.Extensions.DependencyModel": "8.0.0", + "Serilog": "3.1.1" } }, "Serilog.Sinks.Async": { @@ -540,10 +538,10 @@ }, "Serilog.Sinks.Debug": { "type": "Transitive", - "resolved": "3.0.0", - "contentHash": "4BzXcdrgRX7wde9PmHuYd9U6YqycCC28hhpKonK7hx0wb19eiuRj16fPcPSVp0o/Y1ipJuNLYQ00R3q2Zs8FDA==", + "resolved": "2.0.0", + "contentHash": "Y6g3OBJ4JzTyyw16fDqtFcQ41qQAydnEvEqmXjhwhgjsnG/FaJ8GUqF5ldsC/bVkK8KYmqrPhDO+tm4dF6xx4A==", "dependencies": { - "Serilog": "4.0.0" + "Serilog": "2.10.0" } }, "Serilog.Sinks.Elasticsearch": { @@ -837,8 +835,8 @@ "NodaTime.Serialization.JsonNet": "[3.1.0, )", "Npgsql": "[9.0.2, )", "Npgsql.NodaTime": "[9.0.2, )", - "Serilog": "[4.2.0, )", - "Serilog.Extensions.Logging": "[9.0.0, )", + "Serilog": "[4.1.0, )", + "Serilog.Extensions.Logging": "[8.0.0, )", "Serilog.Formatting.Compact": "[3.0.0, )", "Serilog.NodaTime": "[3.0.0, )", "Serilog.Sinks.Async": "[2.1.0, )", @@ -852,6 +850,9 @@ "System.Interactive.Async": "[6.0.1, )", "ipnetwork2": "[3.0.667, )" } + }, + "serilog": { + "type": "Project" } } } diff --git a/PluralKit.Bot/ApplicationCommands/Message.cs b/PluralKit.Bot/ApplicationCommands/Message.cs index a425b31c..2f7be692 100644 --- a/PluralKit.Bot/ApplicationCommands/Message.cs +++ b/PluralKit.Bot/ApplicationCommands/Message.cs @@ -33,7 +33,10 @@ public class ApplicationCommandProxiedMessage var messageId = ctx.Event.Data!.TargetId!.Value; var msg = await ctx.Repository.GetFullMessage(messageId); if (msg == null) - throw Errors.MessageNotFound(messageId); + { + await QueryCommandMessage(ctx); + return; + } var showContent = true; var channel = await _rest.GetChannelOrNull(msg.Message.Channel); @@ -58,6 +61,20 @@ public class ApplicationCommandProxiedMessage await ctx.Reply(embeds: embeds.ToArray()); } + private async Task QueryCommandMessage(InteractionContext ctx) + { + var messageId = ctx.Event.Data!.TargetId!.Value; + var msg = await ctx.Repository.GetCommandMessage(messageId); + if (msg == null) + throw Errors.MessageNotFound(messageId); + + var embeds = new List(); + + embeds.Add(await _embeds.CreateCommandMessageInfoEmbed(msg, true)); + + await ctx.Reply(embeds: embeds.ToArray()); + } + public async Task DeleteMessage(InteractionContext ctx) { var messageId = ctx.Event.Data!.TargetId!.Value; diff --git a/PluralKit.Bot/Bot.cs b/PluralKit.Bot/Bot.cs index d74428ee..c0b33c99 100644 --- a/PluralKit.Bot/Bot.cs +++ b/PluralKit.Bot/Bot.cs @@ -32,13 +32,14 @@ public class Bot private readonly DiscordApiClient _rest; private readonly RedisService _redis; private readonly ILifetimeScope _services; + private readonly RuntimeConfigService _runtimeConfig; private Timer _periodicTask; // Never read, just kept here for GC reasons public Bot(ILifetimeScope services, ILogger logger, PeriodicStatCollector collector, IMetrics metrics, BotConfig config, RedisService redis, ErrorMessageService errorMessageService, CommandMessageService commandMessageService, - Cluster cluster, DiscordApiClient rest, IDiscordCache cache) + Cluster cluster, DiscordApiClient rest, IDiscordCache cache, RuntimeConfigService runtimeConfig) { _logger = logger.ForContext(); _services = services; @@ -51,6 +52,7 @@ public class Bot _rest = rest; _redis = redis; _cache = cache; + _runtimeConfig = runtimeConfig; } private string BotStatus => $"{(_config.Prefixes ?? BotConfig.DefaultPrefixes)[0]}help" @@ -97,13 +99,15 @@ public class Bot private async Task OnEventReceived(int shardId, IGatewayEvent evt) { + if (_runtimeConfig.Exists("disable_events")) return; + // we HandleGatewayEvent **before** getting the own user, because the own user is set in HandleGatewayEvent for ReadyEvent await _cache.HandleGatewayEvent(evt); await _cache.TryUpdateSelfMember(_config.ClientId, evt); await OnEventReceivedInner(shardId, evt); } - private async Task OnEventReceivedInner(int shardId, IGatewayEvent evt) + public async Task OnEventReceivedInner(int shardId, IGatewayEvent evt) { // HandleEvent takes a type parameter, automatically inferred by the event type // It will then look up an IEventHandler in the DI container and call that object's handler method @@ -278,7 +282,7 @@ public class Bot _logger.Debug("Running once-per-minute scheduled tasks"); // Check from a new custom status from Redis and update Discord accordingly - if (true) + if (!_config.DisableGateway) { var newStatus = await _redis.Connection.GetDatabase().StringGetAsync("pluralkit:botstatus"); if (newStatus != CustomStatusMessage) diff --git a/PluralKit.Bot/BotConfig.cs b/PluralKit.Bot/BotConfig.cs index fdc54ad2..1e6e0f0b 100644 --- a/PluralKit.Bot/BotConfig.cs +++ b/PluralKit.Bot/BotConfig.cs @@ -24,6 +24,10 @@ public class BotConfig public string? HttpCacheUrl { get; set; } public bool HttpUseInnerCache { get; set; } = false; + public string? HttpListenerAddr { get; set; } + public bool DisableGateway { get; set; } = false; + public string? EventAwaiterTarget { get; set; } + public string? DiscordBaseUrl { get; set; } public string? AvatarServiceUrl { get; set; } diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index 63539cca..d1264a2f 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -108,7 +108,7 @@ public partial class CommandTree ctx.Reply( $"{Emojis.Error} Parsed command {ctx.Parameters.Callback().AsCode()} not implemented in PluralKit.Bot!"), }; - if (ctx.Match("system", "s")) + if (ctx.Match("system", "s", "account", "acc")) return HandleSystemCommand(ctx); if (ctx.Match("member", "m")) return HandleMemberCommand(ctx); @@ -554,6 +554,8 @@ public partial class CommandTree case "system": case "systems": case "s": + case "account": + case "acc": await PrintCommandList(ctx, "systems", SystemCommands); break; case "member": diff --git a/PluralKit.Bot/CommandSystem/Context/Context.cs b/PluralKit.Bot/CommandSystem/Context/Context.cs index 69c695da..34bc61e5 100644 --- a/PluralKit.Bot/CommandSystem/Context/Context.cs +++ b/PluralKit.Bot/CommandSystem/Context/Context.cs @@ -80,7 +80,7 @@ public class Context internal readonly ModelRepository Repository; internal readonly RedisService Redis; - public async Task Reply(string text = null, Embed embed = null, AllowedMentions? mentions = null) + public async Task Reply(string text = null, Embed embed = null, AllowedMentions? mentions = null, MultipartFile[]? files = null) { var botPerms = await BotPermissions; @@ -91,20 +91,28 @@ public class Context if (embed != null && !botPerms.HasFlag(PermissionSet.EmbedLinks)) throw new PKError("PluralKit does not have permission to send embeds in this channel. Please ensure I have the **Embed Links** permission enabled."); + if (files != null && !botPerms.HasFlag(PermissionSet.AttachFiles)) + throw new PKError("PluralKit does not have permission to attach files in this channel. Please ensure I have the **Attach Files** permission enabled."); + var msg = await Rest.CreateMessage(Channel.Id, new MessageRequest { Content = text, Embeds = embed != null ? new[] { embed } : null, // Default to an empty allowed mentions object instead of null (which means no mentions allowed) AllowedMentions = mentions ?? new AllowedMentions() - }); + }, files: files); - // if (embed != null) - // { - // Sensitive information that might want to be deleted by :x: reaction is typically in an embed format (member cards, for example) - // but since we can, we just store all sent messages for possible deletion - await _commandMessageService.RegisterMessage(msg.Id, Guild?.Id ?? 0, msg.ChannelId, Author.Id); - // } + // store log of sent message, so it can be queried or deleted later + // skip DMs as DM messages can always be deleted + if (Guild != null) + await Repository.AddCommandMessage(new Core.CommandMessage + { + Mid = msg.Id, + Guild = Guild!.Id, + Channel = Channel.Id, + Sender = Author.Id, + OriginalMid = Message.Id, + }); return msg; } diff --git a/PluralKit.Bot/Commands/Groups.cs b/PluralKit.Bot/Commands/Groups.cs index a314b248..45c5f8b4 100644 --- a/PluralKit.Bot/Commands/Groups.cs +++ b/PluralKit.Bot/Commands/Groups.cs @@ -443,10 +443,11 @@ public class Groups await ctx.Reply(embed: new EmbedBuilder() .Title("Group color") .Color(target.Color.ToDiscordColor()) - .Thumbnail(new Embed.EmbedThumbnail($"https://fakeimg.pl/256x256/{target.Color}/?text=%20")) + .Thumbnail(new Embed.EmbedThumbnail($"attachment://color.gif")) .Description($"This group's color is **#{target.Color}**." + (isOwnSystem ? $" To clear it, type `{ctx.DefaultPrefix}group {target.Reference(ctx)} color -clear`." : "")) - .Build()); + .Build(), + files: [MiscUtils.GenerateColorPreview(target.Color)]); return; } @@ -471,8 +472,9 @@ public class Groups await ctx.Reply(embed: new EmbedBuilder() .Title($"{Emojis.Success} Group color changed.") .Color(color.ToDiscordColor()) - .Thumbnail(new Embed.EmbedThumbnail($"https://fakeimg.pl/256x256/{color}/?text=%20")) - .Build()); + .Thumbnail(new Embed.EmbedThumbnail($"attachment://color.gif")) + .Build(), + files: [MiscUtils.GenerateColorPreview(color)]); } } diff --git a/PluralKit.Bot/Commands/MemberEdit.cs b/PluralKit.Bot/Commands/MemberEdit.cs index 0585abd7..525d60ab 100644 --- a/PluralKit.Bot/Commands/MemberEdit.cs +++ b/PluralKit.Bot/Commands/MemberEdit.cs @@ -308,10 +308,11 @@ public class MemberEdit await ctx.Reply(embed: new EmbedBuilder() .Title("Member color") .Color(target.Color.ToDiscordColor()) - .Thumbnail(new Embed.EmbedThumbnail($"https://fakeimg.pl/256x256/{target.Color}/?text=%20")) + .Thumbnail(new Embed.EmbedThumbnail($"attachment://color.gif")) .Description($"This member's color is **#{target.Color}**." + (isOwnSystem ? $" To clear it, type `{ctx.DefaultPrefix}member {target.Reference(ctx)} color -clear`." : "")) - .Build()); + .Build(), + files: [MiscUtils.GenerateColorPreview(target.Color)]); return; } @@ -336,8 +337,9 @@ public class MemberEdit await ctx.Reply(embed: new EmbedBuilder() .Title($"{Emojis.Success} Member color changed.") .Color(color.ToDiscordColor()) - .Thumbnail(new Embed.EmbedThumbnail($"https://fakeimg.pl/256x256/{color}/?text=%20")) - .Build()); + .Thumbnail(new Embed.EmbedThumbnail($"attachment://color.gif")) + .Build(), + files: [MiscUtils.GenerateColorPreview(color)]); } } diff --git a/PluralKit.Bot/Commands/Message.cs b/PluralKit.Bot/Commands/Message.cs index 692f75b1..ec34cea9 100644 --- a/PluralKit.Bot/Commands/Message.cs +++ b/PluralKit.Bot/Commands/Message.cs @@ -305,7 +305,7 @@ public class ProxiedMessage throw new PKError(error); } - var lastMessage = _lastMessageCache.GetLastMessage(ctx.Message.ChannelId); + var lastMessage = await _lastMessageCache.GetLastMessage(ctx.Message.GuildId ?? 0, ctx.Message.ChannelId); var isLatestMessage = lastMessage?.Current.Id == ctx.Message.Id ? lastMessage?.Previous?.Id == msg.Mid @@ -347,13 +347,8 @@ public class ProxiedMessage var message = await ctx.Repository.GetFullMessage(messageId.Value); if (message == null) { - if (isDelete) - { - await DeleteCommandMessage(ctx, messageId.Value); - return; - } - - throw Errors.MessageNotFound(messageId.Value); + await GetCommandMessage(ctx, messageId.Value, isDelete); + return; } var showContent = true; @@ -448,20 +443,35 @@ public class ProxiedMessage await ctx.Reply(embed: await _embeds.CreateMessageInfoEmbed(message, showContent, ctx.Config)); } - private async Task DeleteCommandMessage(Context ctx, ulong messageId) + private async Task GetCommandMessage(Context ctx, ulong messageId, bool isDelete) { - var cmessage = await ctx.Services.Resolve().GetCommandMessage(messageId); - if (cmessage == null) + var msg = await _repo.GetCommandMessage(messageId); + if (msg == null) throw Errors.MessageNotFound(messageId); - if (cmessage!.AuthorId != ctx.Author.Id) - throw new PKError("You can only delete command messages queried by this account."); + if (isDelete) + { + if (msg.Sender != ctx.Author.Id) + throw new PKError("You can only delete command messages queried by this account."); - await ctx.Rest.DeleteMessage(cmessage.ChannelId, messageId); + await ctx.Rest.DeleteMessage(msg.Channel, messageId); - if (ctx.Guild != null) - await ctx.Rest.DeleteMessage(ctx.Message); - else - await ctx.Rest.CreateReaction(ctx.Message.ChannelId, ctx.Message.Id, new Emoji { Name = Emojis.Success }); + if (ctx.Guild != null) + await ctx.Rest.DeleteMessage(ctx.Message); + else + await ctx.Rest.CreateReaction(ctx.Message.ChannelId, ctx.Message.Id, new Emoji { Name = Emojis.Success }); + + return; + } + + var showContent = true; + + var channel = await _rest.GetChannelOrNull(msg.Channel); + if (channel == null) + showContent = false; + else if (!await ctx.CheckPermissionsInGuildChannel(channel, PermissionSet.ViewChannel)) + showContent = false; + + await ctx.Reply(embed: await _embeds.CreateCommandMessageInfoEmbed(msg, showContent)); } } \ No newline at end of file diff --git a/PluralKit.Bot/Commands/System.cs b/PluralKit.Bot/Commands/System.cs index 3c1cadde..db372f51 100644 --- a/PluralKit.Bot/Commands/System.cs +++ b/PluralKit.Bot/Commands/System.cs @@ -37,10 +37,13 @@ public class System .Field(new Embed.Field("Getting Started", "New to PK? Check out our Getting Started guide on setting up members and proxies: https://pluralkit.me/start\n" + $"Otherwise, type `{ctx.DefaultPrefix}system` to view your system and `{ctx.DefaultPrefix}system help` for more information about commands you can use.")) - .Field(new Embed.Field($"{Emojis.Warn} Notice {Emojis.Warn}", "PluralKit is a bot meant to help you share information about your system. " + + .Field(new Embed.Field($"{Emojis.Warn} Notice: Public By Default {Emojis.Warn}", "PluralKit is a bot meant to help you share information about your system. " + "Member descriptions are meant to be the equivalent to a Discord About Me. Because of this, any info you put in PK is **public by default**.\n" + "Note that this does **not** include message content, only member fields. For more information, check out " + "[the privacy section of the user guide](https://pluralkit.me/guide/#privacy). ")) + .Field(new Embed.Field($"{Emojis.Warn} Notice: Implicit Acceptance of ToS {Emojis.Warn}", "By using the PluralKit bot you implicitly agree to our " + + "[Terms of Service](https://pluralkit.me/terms-of-service/). For questions please ask in our [support server]() or " + + "email legal@pluralkit.me")) .Field(new Embed.Field("System Recovery", "In the case of your Discord account getting lost or deleted, the PluralKit staff can help you recover your system. " + "In order to do so, we will need your **PluralKit token**. This is the *only* way you can prove ownership so we can help you recover your system. " + $"To get it, run `{ctx.DefaultPrefix}token` and then store it in a safe place.\n\n" + diff --git a/PluralKit.Bot/Commands/SystemEdit.cs b/PluralKit.Bot/Commands/SystemEdit.cs index 3e72def3..245625c5 100644 --- a/PluralKit.Bot/Commands/SystemEdit.cs +++ b/PluralKit.Bot/Commands/SystemEdit.cs @@ -233,8 +233,9 @@ public class SystemEdit await ctx.Reply(embed: new EmbedBuilder() .Title($"{Emojis.Success} System color changed.") .Color(newColor.ToDiscordColor()) - .Thumbnail(new Embed.EmbedThumbnail($"https://fakeimg.pl/256x256/{newColor}/?text=%20")) - .Build()); + .Thumbnail(new Embed.EmbedThumbnail($"attachment://color.gif")) + .Build(), + files: [MiscUtils.GenerateColorPreview(color)]);); } public async Task ClearColor(Context ctx, PKSystem target, bool flagConfirmYes) @@ -274,10 +275,11 @@ public class SystemEdit await ctx.Reply(embed: new EmbedBuilder() .Title("System color") .Color(target.Color.ToDiscordColor()) - .Thumbnail(new Embed.EmbedThumbnail($"https://fakeimg.pl/256x256/{target.Color}/?text=%20")) + .Thumbnail(new Embed.EmbedThumbnail($"attachment://color.gif")) .Description( $"This system's color is **#{target.Color}**." + (isOwnSystem ? $" To clear it, type `{ctx.DefaultPrefix}s color -clear`." : "")) - .Build()); + .Build(), + files: [MiscUtils.GenerateColorPreview(target.Color)]); } public async Task ClearTag(Context ctx, PKSystem target, bool flagConfirmYes) @@ -475,7 +477,7 @@ public class SystemEdit else str += " Member names will now use the global system tag when proxied in the current server, if there is one set." - + "\n\nTo check or change where your tag appears in your name use the command `{ctx.DefaultPrefix}cfg name format`."; + + $"\n\nTo check or change where your tag appears in your name use the command `{ctx.DefaultPrefix}cfg name format`."; } } diff --git a/PluralKit.Bot/Handlers/MessageCreated.cs b/PluralKit.Bot/Handlers/MessageCreated.cs index cd16d6c9..82d01d6f 100644 --- a/PluralKit.Bot/Handlers/MessageCreated.cs +++ b/PluralKit.Bot/Handlers/MessageCreated.cs @@ -55,7 +55,9 @@ public class MessageCreated: IEventHandler public (ulong?, ulong?) ErrorChannelFor(MessageCreateEvent evt, ulong userId) => (evt.GuildId, evt.ChannelId); private bool IsDuplicateMessage(Message msg) => // We consider a message duplicate if it has the same ID as the previous message that hit the gateway - _lastMessageCache.GetLastMessage(msg.ChannelId)?.Current.Id == msg.Id; + // use only the local cache here + // http gateway sets last message before forwarding the message here, so this will always return true + _lastMessageCache._GetLastMessage(msg.ChannelId)?.Current.Id == msg.Id; public async Task Handle(int shardId, MessageCreateEvent evt) { diff --git a/PluralKit.Bot/Handlers/MessageEdited.cs b/PluralKit.Bot/Handlers/MessageEdited.cs index 8e131c0c..a732fa5b 100644 --- a/PluralKit.Bot/Handlers/MessageEdited.cs +++ b/PluralKit.Bot/Handlers/MessageEdited.cs @@ -65,7 +65,7 @@ public class MessageEdited: IEventHandler var guild = await _cache.TryGetGuild(channel.GuildId!.Value); if (guild == null) throw new Exception("could not find self guild in MessageEdited event"); - var lastMessage = _lastMessageCache.GetLastMessage(evt.ChannelId)?.Current; + var lastMessage = (await _lastMessageCache.GetLastMessage(evt.GuildId.HasValue ? evt.GuildId.Value ?? 0 : 0, evt.ChannelId))?.Current; // Only react to the last message in the channel if (lastMessage?.Id != evt.Id) @@ -107,10 +107,6 @@ public class MessageEdited: IEventHandler ? new Message.Reference(channel.GuildId, evt.ChannelId, lastMessage.ReferencedMessage.Value) : null; - var messageType = lastMessage.ReferencedMessage != null - ? Message.MessageType.Reply - : Message.MessageType.Default; - // TODO: is this missing anything? var equivalentEvt = new MessageCreateEvent { @@ -123,7 +119,7 @@ public class MessageEdited: IEventHandler Attachments = evt.Attachments.Value ?? Array.Empty(), MessageReference = messageReference, ReferencedMessage = referencedMessage, - Type = messageType, + Type = evt.Type, }; return equivalentEvt; } diff --git a/PluralKit.Bot/Handlers/ReactionAdded.cs b/PluralKit.Bot/Handlers/ReactionAdded.cs index 5caa7d6f..c073119d 100644 --- a/PluralKit.Bot/Handlers/ReactionAdded.cs +++ b/PluralKit.Bot/Handlers/ReactionAdded.cs @@ -111,6 +111,7 @@ public class ReactionAdded: IEventHandler case "\U0001F514": // Bell case "\U0001F6CE": // Bellhop bell case "\U0001F3D3": // Ping pong paddle (lol) + case "\U0001FAD1": // Bell pepper case "\u23F0": // Alarm clock case "\u2757": // Exclamation mark { diff --git a/PluralKit.Bot/Init.cs b/PluralKit.Bot/Init.cs index c4b9ed65..25b98f1b 100644 --- a/PluralKit.Bot/Init.cs +++ b/PluralKit.Bot/Init.cs @@ -57,16 +57,6 @@ public class Init var cache = services.Resolve(); - if (config.Cluster == null) - { - // "Connect to the database" (ie. set off database migrations and ensure state) - logger.Information("Connecting to database"); - await services.Resolve().ApplyMigrations(); - - // Clear shard status from Redis - await redis.Connection.GetDatabase().KeyDeleteAsync("pluralkit:shardstatus"); - } - logger.Information("Initializing bot"); var bot = services.Resolve(); @@ -76,10 +66,19 @@ public class Init // Init the bot instance itself, register handlers and such to the client before beginning to connect bot.Init(); - // Start the Discord shards themselves (handlers already set up) - logger.Information("Connecting to Discord"); - await StartCluster(services); + // load runtime config from redis + await services.Resolve().LoadConfig(); + // Start HTTP server + if (config.HttpListenerAddr != null) + services.Resolve().Start(config.HttpListenerAddr); + + // Start the Discord shards themselves (handlers already set up) + if (!config.DisableGateway) + { + logger.Information("Connecting to Discord"); + await StartCluster(services); + } logger.Information("Connected! All is good (probably)."); // Lastly, we just... wait. Everything else is handled in the DiscordClient event loop @@ -149,7 +148,7 @@ public class Init var builder = new ContainerBuilder(); builder.RegisterInstance(config); builder.RegisterModule(new ConfigModule("Bot")); - builder.RegisterModule(new LoggingModule("bot")); + builder.RegisterModule(new LoggingModule("dotnet-bot")); builder.RegisterModule(new MetricsModule()); builder.RegisterModule(); builder.RegisterModule(); diff --git a/PluralKit.Bot/Interactive/BaseInteractive.cs b/PluralKit.Bot/Interactive/BaseInteractive.cs index c2ae33ee..779058fe 100644 --- a/PluralKit.Bot/Interactive/BaseInteractive.cs +++ b/PluralKit.Bot/Interactive/BaseInteractive.cs @@ -28,7 +28,7 @@ public abstract class BaseInteractive ButtonStyle style = ButtonStyle.Secondary, bool disabled = false) { var dispatch = _ctx.Services.Resolve(); - var customId = dispatch.Register(handler, Timeout); + var customId = dispatch.Register(_ctx.ShardId, handler, Timeout); var button = new Button { @@ -89,7 +89,7 @@ public abstract class BaseInteractive { var dispatch = ctx.Services.Resolve(); foreach (var button in _buttons) - button.CustomId = dispatch.Register(button.Handler, Timeout); + button.CustomId = dispatch.Register(_ctx.ShardId, button.Handler, Timeout); } public abstract Task Start(); diff --git a/PluralKit.Bot/Interactive/YesNoPrompt.cs b/PluralKit.Bot/Interactive/YesNoPrompt.cs index 110e4bb9..194dd1f1 100644 --- a/PluralKit.Bot/Interactive/YesNoPrompt.cs +++ b/PluralKit.Bot/Interactive/YesNoPrompt.cs @@ -1,5 +1,6 @@ using Autofac; +using Myriad.Cache; using Myriad.Gateway; using Myriad.Rest.Types; using Myriad.Types; @@ -69,6 +70,9 @@ public class YesNoPrompt: BaseInteractive return true; } + // no need to reawait message + // gateway will already have sent us only matching messages + return false; } @@ -88,6 +92,17 @@ public class YesNoPrompt: BaseInteractive { try { + // check if http gateway and set listener + // todo: this one needs to handle options for message + if (_ctx.Cache is HttpDiscordCache) + await (_ctx.Cache as HttpDiscordCache).AwaitMessage( + _ctx.Guild?.Id ?? 0, + _ctx.Channel.Id, + _ctx.Author.Id, + Timeout, + options: new[] { "yes", "y", "no", "n" } + ); + await queue.WaitFor(MessagePredicate, Timeout, cts.Token); } catch (TimeoutException e) diff --git a/PluralKit.Bot/Modules.cs b/PluralKit.Bot/Modules.cs index 6ceea629..668ea9c3 100644 --- a/PluralKit.Bot/Modules.cs +++ b/PluralKit.Bot/Modules.cs @@ -49,8 +49,15 @@ public class BotModule: Module if (botConfig.HttpCacheUrl != null) { - var cache = new HttpDiscordCache(c.Resolve(), - c.Resolve(), botConfig.HttpCacheUrl, botConfig.Cluster?.TotalShards ?? 1, botConfig.ClientId, botConfig.HttpUseInnerCache); + var cache = new HttpDiscordCache( + c.Resolve(), + c.Resolve(), + botConfig.HttpCacheUrl, + botConfig.EventAwaiterTarget, + botConfig.Cluster?.TotalShards ?? 1, + botConfig.ClientId, + botConfig.HttpUseInnerCache + ); var metrics = c.Resolve(); @@ -153,6 +160,8 @@ public class BotModule: Module builder.RegisterType().AsSelf().SingleInstance(); builder.RegisterType().AsSelf().SingleInstance(); builder.RegisterType().AsSelf().SingleInstance(); + builder.RegisterType().AsSelf().SingleInstance(); + builder.RegisterType().AsSelf().SingleInstance(); // Sentry stuff builder.Register(_ => new Scope(null)).AsSelf().InstancePerLifetimeScope(); diff --git a/PluralKit.Bot/PluralKit.Bot.csproj b/PluralKit.Bot/PluralKit.Bot.csproj index 9dee3bad..b16382e9 100644 --- a/PluralKit.Bot/PluralKit.Bot.csproj +++ b/PluralKit.Bot/PluralKit.Bot.csproj @@ -25,5 +25,6 @@ + diff --git a/PluralKit.Bot/Proxy/ProxyService.cs b/PluralKit.Bot/Proxy/ProxyService.cs index 43e1ed5e..8a59957d 100644 --- a/PluralKit.Bot/Proxy/ProxyService.cs +++ b/PluralKit.Bot/Proxy/ProxyService.cs @@ -246,7 +246,7 @@ public class ProxyService ChannelId = rootChannel.Id, ThreadId = threadId, MessageId = trigger.Id, - Name = await FixSameName(messageChannel.Id, ctx, match.Member), + Name = await FixSameName(trigger.GuildId!.Value, messageChannel.Id, ctx, match.Member), AvatarUrl = AvatarUtils.TryRewriteCdnUrl(match.Member.ProxyAvatar(ctx)), Content = content, Attachments = trigger.Attachments, @@ -458,11 +458,11 @@ public class ProxyService }; } - private async Task FixSameName(ulong channelId, MessageContext ctx, ProxyMember member) + private async Task FixSameName(ulong guildId, ulong channelId, MessageContext ctx, ProxyMember member) { var proxyName = member.ProxyName(ctx); - var lastMessage = _lastMessage.GetLastMessage(channelId)?.Previous; + var lastMessage = (await _lastMessage.GetLastMessage(guildId, channelId))?.Previous; if (lastMessage == null) // cache is out of date or channel is empty. return proxyName; diff --git a/PluralKit.Bot/Services/CommandMessageService.cs b/PluralKit.Bot/Services/CommandMessageService.cs index 796f7f0a..684daf86 100644 --- a/PluralKit.Bot/Services/CommandMessageService.cs +++ b/PluralKit.Bot/Services/CommandMessageService.cs @@ -9,35 +9,35 @@ namespace PluralKit.Bot; public class CommandMessageService { private readonly RedisService _redis; + private readonly ModelRepository _repo; private readonly ILogger _logger; private static readonly TimeSpan CommandMessageRetention = TimeSpan.FromHours(24); - public CommandMessageService(RedisService redis, IClock clock, ILogger logger) + public CommandMessageService(RedisService redis, ModelRepository repo, IClock clock, ILogger logger) { _redis = redis; + _repo = repo; _logger = logger.ForContext(); } - public async Task RegisterMessage(ulong messageId, ulong guildId, ulong channelId, ulong authorId) - { - if (_redis.Connection == null) return; - - _logger.Debug( - "Registering command response {MessageId} from author {AuthorId} in {ChannelId}", - messageId, authorId, channelId - ); - - await _redis.Connection.GetDatabase().StringSetAsync(messageId.ToString(), $"{authorId}-{channelId}-{guildId}", expiry: CommandMessageRetention); - } - public async Task GetCommandMessage(ulong messageId) { + var repoMsg = await _repo.GetCommandMessage(messageId); + if (repoMsg != null) + return new CommandMessage(repoMsg.Sender, repoMsg.Channel, repoMsg.Guild); + var str = await _redis.Connection.GetDatabase().StringGetAsync(messageId.ToString()); if (str.HasValue) { var split = ((string)str).Split("-"); return new CommandMessage(ulong.Parse(split[0]), ulong.Parse(split[1]), ulong.Parse(split[2])); } + str = await _redis.Connection.GetDatabase().StringGetAsync("command_message:" + messageId.ToString()); + if (str.HasValue) + { + var split = ((string)str).Split("-"); + return new CommandMessage(ulong.Parse(split[0]), ulong.Parse(split[1]), ulong.Parse(split[2])); + } return null; } } diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index 53551199..8f2d6486 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -420,6 +420,24 @@ public class EmbedService return eb.Build(); } + public async Task CreateCommandMessageInfoEmbed(Core.CommandMessage msg, bool showContent) + { + var content = "*(command message deleted or inaccessible)*"; + if (showContent) + { + var discordMessage = await _rest.GetMessageOrNull(msg.Channel, msg.OriginalMid); + if (discordMessage != null) + content = discordMessage.Content; + } + + return new EmbedBuilder() + .Title("Command response message") + .Description(content) + .Field(new("Original message", $"https://discord.com/channels/{msg.Guild}/{msg.Channel}/{msg.OriginalMid}", true)) + .Field(new("Sent by", $"<@{msg.Sender}>", true)) + .Build(); + } + public Task CreateFrontPercentEmbed(FrontBreakdown breakdown, PKSystem system, PKGroup group, DateTimeZone tz, LookupContext ctx, string embedTitle, bool ignoreNoFronters, bool showFlat) diff --git a/PluralKit.Bot/Services/HttpListenerService.cs b/PluralKit.Bot/Services/HttpListenerService.cs new file mode 100644 index 00000000..bafeae41 --- /dev/null +++ b/PluralKit.Bot/Services/HttpListenerService.cs @@ -0,0 +1,146 @@ +using System.Text; +using System.Text.Json; + +using Serilog; + +using WatsonWebserver.Lite; +using WatsonWebserver.Core; + +using Myriad.Gateway; +using Myriad.Serialization; + +namespace PluralKit.Bot; + +public class HttpListenerService +{ + private readonly ILogger _logger; + private readonly RuntimeConfigService _runtimeConfig; + private readonly Bot _bot; + + public HttpListenerService(ILogger logger, RuntimeConfigService runtimeConfig, Bot bot) + { + _logger = logger.ForContext(); + _runtimeConfig = runtimeConfig; + _bot = bot; + } + + public void Start(string host) + { + var hosts = new[] { host }; + if (host == "allv4v6") + { + hosts = new[] { "[::]", "0.0.0.0" }; + } + foreach (var h in hosts) + { + var server = new WebserverLite(new WebserverSettings(h, 5002), DefaultRoute); + + server.Routes.PreAuthentication.Static.Add(WatsonWebserver.Core.HttpMethod.GET, "/runtime_config", RuntimeConfigGet); + server.Routes.PreAuthentication.Parameter.Add(WatsonWebserver.Core.HttpMethod.POST, "/runtime_config/{key}", RuntimeConfigSet); + server.Routes.PreAuthentication.Parameter.Add(WatsonWebserver.Core.HttpMethod.DELETE, "/runtime_config/{key}", RuntimeConfigDelete); + + server.Routes.PreAuthentication.Parameter.Add(WatsonWebserver.Core.HttpMethod.POST, "/events/{shard_id}", GatewayEvent); + + server.Start(); + } + } + + private async Task DefaultRoute(HttpContextBase ctx) + => await ctx.Response.Send("hellorld"); + + private async Task RuntimeConfigGet(HttpContextBase ctx) + { + var config = _runtimeConfig.GetAll(); + ctx.Response.Headers.Add("content-type", "application/json"); + await ctx.Response.Send(JsonSerializer.Serialize(config)); + } + + private async Task RuntimeConfigSet(HttpContextBase ctx) + { + var key = ctx.Request.Url.Parameters["key"]; + var value = ReadStream(ctx.Request.Data, ctx.Request.ContentLength); + await _runtimeConfig.Set(key, value); + await RuntimeConfigGet(ctx); + } + + private async Task RuntimeConfigDelete(HttpContextBase ctx) + { + var key = ctx.Request.Url.Parameters["key"]; + await _runtimeConfig.Delete(key); + await RuntimeConfigGet(ctx); + } + + private JsonSerializerOptions _jsonSerializerOptions = new JsonSerializerOptions().ConfigureForMyriad(); + + private async Task GatewayEvent(HttpContextBase ctx) + { + var shardIdString = ctx.Request.Url.Parameters["shard_id"]; + if (!int.TryParse(shardIdString, out var shardId)) return; + + var packet = JsonSerializer.Deserialize(ReadStream(ctx.Request.Data, ctx.Request.ContentLength), _jsonSerializerOptions); + var evt = DeserializeEvent(shardId, packet.EventType!, (JsonElement)packet.Payload!); + if (evt != null) + { + await _bot.OnEventReceivedInner(shardId, evt); + } + await ctx.Response.Send("a"); + } + + private IGatewayEvent? DeserializeEvent(int shardId, string eventType, JsonElement payload) + { + if (!IGatewayEvent.EventTypes.TryGetValue(eventType, out var clrType)) + { + _logger.Debug("Shard {ShardId}: Received unknown event type {EventType}", shardId, eventType); + return null; + } + + try + { + _logger.Verbose("Shard {ShardId}: Deserializing {EventType} to {ClrType}", shardId, eventType, + clrType); + return JsonSerializer.Deserialize(payload.GetRawText(), clrType, _jsonSerializerOptions) + as IGatewayEvent; + } + catch (JsonException e) + { + _logger.Error(e, "Shard {ShardId}: Error deserializing event {EventType} to {ClrType}", shardId, + eventType, clrType); + return null; + } + } + + //temporary re-implementation of the ReadStream function found in WatsonWebserver.Lite, but with handling for closed connections + //https://github.com/dotnet/WatsonWebserver/issues/171 + private static string ReadStream(Stream input, long contentLength) + { + if (input == null) throw new ArgumentNullException(nameof(input)); + if (!input.CanRead) throw new InvalidOperationException("Input stream is not readable"); + if (contentLength < 1) return ""; + + byte[] buffer = new byte[65536]; + long bytesRemaining = contentLength; + + using (MemoryStream ms = new MemoryStream()) + { + int read; + + while (bytesRemaining > 0) + { + read = input.Read(buffer, 0, buffer.Length); + if (read > 0) + { + ms.Write(buffer, 0, read); + bytesRemaining -= read; + } + else + { + throw new IOException("Connection closed before reading end of stream."); + } + } + + if (ms.Length < 1) return null; + var str = Encoding.Default.GetString(ms.ToArray()); + return str; + } + } +} \ No newline at end of file diff --git a/PluralKit.Bot/Services/InteractionDispatchService.cs b/PluralKit.Bot/Services/InteractionDispatchService.cs index 968ea35a..f900a792 100644 --- a/PluralKit.Bot/Services/InteractionDispatchService.cs +++ b/PluralKit.Bot/Services/InteractionDispatchService.cs @@ -1,5 +1,7 @@ using System.Collections.Concurrent; +using Myriad.Cache; + using NodaTime; using Serilog; @@ -16,9 +18,12 @@ public class InteractionDispatchService: IDisposable private readonly ConcurrentDictionary _handlers = new(); private readonly ILogger _logger; - public InteractionDispatchService(IClock clock, ILogger logger) + private readonly IDiscordCache _cache; + + public InteractionDispatchService(IClock clock, ILogger logger, IDiscordCache cache) { _clock = clock; + _cache = cache; _logger = logger.ForContext(); _cleanupWorker = CleanupLoop(_cts.Token); @@ -50,9 +55,15 @@ public class InteractionDispatchService: IDisposable _handlers.TryRemove(customIdGuid, out _); } - public string Register(Func callback, Duration? expiry = null) + public string Register(int shardId, Func callback, Duration? expiry = null) { var key = Guid.NewGuid(); + + // if http_cache, return RegisterRemote + // not awaited here, it's probably fine + if (_cache is HttpDiscordCache) + (_cache as HttpDiscordCache).AwaitInteraction(shardId, key.ToString(), expiry); + var handler = new RegisteredInteraction { Callback = callback, diff --git a/PluralKit.Bot/Services/LastMessageCacheService.cs b/PluralKit.Bot/Services/LastMessageCacheService.cs index a06da7fa..46a51c64 100644 --- a/PluralKit.Bot/Services/LastMessageCacheService.cs +++ b/PluralKit.Bot/Services/LastMessageCacheService.cs @@ -1,6 +1,7 @@ #nullable enable using System.Collections.Concurrent; +using Myriad.Cache; using Myriad.Types; namespace PluralKit.Bot; @@ -9,9 +10,18 @@ public class LastMessageCacheService { private readonly IDictionary _cache = new ConcurrentDictionary(); + private readonly IDiscordCache _maybeHttp; + + public LastMessageCacheService(IDiscordCache cache) + { + _maybeHttp = cache; + } + public void AddMessage(Message msg) { - var previous = GetLastMessage(msg.ChannelId); + if (_maybeHttp is HttpDiscordCache) return; + + var previous = _GetLastMessage(msg.ChannelId); var current = ToCachedMessage(msg); _cache[msg.ChannelId] = new CacheEntry(current, previous?.Current); } @@ -19,12 +29,26 @@ public class LastMessageCacheService private CachedMessage ToCachedMessage(Message msg) => new(msg.Id, msg.ReferencedMessage.Value?.Id, msg.Author.Username); - public CacheEntry? GetLastMessage(ulong channel) => - _cache.TryGetValue(channel, out var message) ? message : null; + public async Task GetLastMessage(ulong guild, ulong channel) + { + if (_maybeHttp is HttpDiscordCache) + return await (_maybeHttp as HttpDiscordCache).GetLastMessage(guild, channel); + + return _cache.TryGetValue(channel, out var message) ? message : null; + } + + public CacheEntry? _GetLastMessage(ulong channel) + { + if (_maybeHttp is HttpDiscordCache) return null; + + return _cache.TryGetValue(channel, out var message) ? message : null; + } public void HandleMessageDeletion(ulong channel, ulong message) { - var storedMessage = GetLastMessage(channel); + if (_maybeHttp is HttpDiscordCache) return; + + var storedMessage = _GetLastMessage(channel); if (storedMessage == null) return; @@ -39,7 +63,9 @@ public class LastMessageCacheService public void HandleMessageDeletion(ulong channel, List messages) { - var storedMessage = GetLastMessage(channel); + if (_maybeHttp is HttpDiscordCache) return; + + var storedMessage = _GetLastMessage(channel); if (storedMessage == null) return; diff --git a/PluralKit.Bot/Services/LoggerCleanService.cs b/PluralKit.Bot/Services/LoggerCleanService.cs index 178ce95b..84cc8a1f 100644 --- a/PluralKit.Bot/Services/LoggerCleanService.cs +++ b/PluralKit.Bot/Services/LoggerCleanService.cs @@ -45,6 +45,7 @@ public class LoggerCleanService private static readonly Regex _AnnabelleRegex = new("```\n(\\d{17,19})\n```"); private static readonly Regex _AnnabelleRegexFuzzy = new("\\ A message from \\*\\*[\\w.]{2,32}\\*\\* \\(`(\\d{17,19})`\\) was deleted in <#\\d{17,19}>"); private static readonly Regex _koiraRegex = new("ID:\\*\\* (\\d{17,19})"); + private static readonly Regex _zeppelinRegex = new("🗑 Message \\(`(\\d{17,19})`\\)"); private static readonly Regex _VortexRegex = new("`\\[(\\d\\d:\\d\\d:\\d\\d)\\]` .* \\(ID:(\\d{17,19})\\).* <#\\d{17,19}>:"); @@ -83,7 +84,8 @@ public class LoggerCleanService new LoggerBot("Dozer", 356535250932858885, ExtractDozer), new LoggerBot("Skyra", 266624760782258186, ExtractSkyra), new LoggerBot("Annabelle", 231241068383961088, ExtractAnnabelle, fuzzyExtractFunc: ExtractAnnabelleFuzzy), - new LoggerBot("Koira", 1247013404569239624, ExtractKoira) + new LoggerBot("Koira", 1247013404569239624, ExtractKoira), + new LoggerBot("Zeppelin", 473868086773153793, ExtractZeppelin) // webhook }.ToDictionary(b => b.Id); private static Dictionary _botsByApplicationId @@ -441,6 +443,23 @@ public class LoggerCleanService return match.Success ? ulong.Parse(match.Groups[1].Value) : null; } + private static ulong? ExtractZeppelin(Message msg) + { + // zeppelin uses a non-embed format by default but can be configured to use a customizable embed + // if it's an embed, assume the footer contains the message ID + var embed = msg.Embeds?.FirstOrDefault(); + if (embed == null) + { + var match = _zeppelinRegex.Match(msg.Content ?? ""); + return match.Success ? ulong.Parse(match.Groups[1].Value) : null; + } + else + { + var match = _basicRegex.Match(embed.Footer?.Text ?? ""); + return match.Success ? ulong.Parse(match.Groups[1].Value) : null; + } + } + public class LoggerBot { public ulong Id; diff --git a/PluralKit.Bot/Services/RuntimeConfigService.cs b/PluralKit.Bot/Services/RuntimeConfigService.cs new file mode 100644 index 00000000..2d71a2a2 --- /dev/null +++ b/PluralKit.Bot/Services/RuntimeConfigService.cs @@ -0,0 +1,58 @@ +using Newtonsoft.Json; + +using Serilog; + +using StackExchange.Redis; + +using PluralKit.Core; + +namespace PluralKit.Bot; + +public class RuntimeConfigService +{ + private readonly RedisService _redis; + private readonly ILogger _logger; + + private Dictionary settings = new(); + + private string RedisKey; + + public RuntimeConfigService(ILogger logger, RedisService redis, BotConfig config) + { + _logger = logger.ForContext(); + _redis = redis; + + var clusterId = config.Cluster?.NodeIndex ?? 0; + RedisKey = $"remote_config:dotnet_bot:{clusterId}"; + } + + public async Task LoadConfig() + { + var redisConfig = await _redis.Connection.GetDatabase().HashGetAllAsync(RedisKey); + foreach (var entry in redisConfig) + settings.Add(entry.Name, entry.Value); + + var configStr = JsonConvert.SerializeObject(settings); + _logger.Information($"starting with runtime config: {configStr}"); + } + + public async Task Set(string key, string value) + { + await _redis.Connection.GetDatabase().HashSetAsync(RedisKey, new[] { new HashEntry(key, new RedisValue(value)) }); + settings.Add(key, value); + _logger.Information($"updated runtime config: {key}={value}"); + } + + public async Task Delete(string key) + { + await _redis.Connection.GetDatabase().HashDeleteAsync(RedisKey, key); + settings.Remove(key); + _logger.Information($"updated runtime config: {key} removed"); + } + + public object? Get(string key) => settings.GetValueOrDefault(key); + + public bool Exists(string key) => settings.ContainsKey(key); + + public Dictionary GetAll() => settings; +} \ No newline at end of file diff --git a/PluralKit.Bot/Utils/ContextUtils.cs b/PluralKit.Bot/Utils/ContextUtils.cs index fbadc2e5..4b0a4f5b 100644 --- a/PluralKit.Bot/Utils/ContextUtils.cs +++ b/PluralKit.Bot/Utils/ContextUtils.cs @@ -1,6 +1,7 @@ using Autofac; using Myriad.Builders; +using Myriad.Cache; using Myriad.Gateway; using Myriad.Rest.Exceptions; using Myriad.Rest.Types.Requests; @@ -40,8 +41,12 @@ public static class ContextUtils } public static async Task AwaitReaction(this Context ctx, Message message, - User user = null, Func predicate = null, Duration? timeout = null) + User user, Func predicate = null, Duration? timeout = null) { + // check if http gateway and set listener + if (ctx.Cache is HttpDiscordCache) + await (ctx.Cache as HttpDiscordCache).AwaitReaction(ctx.Guild?.Id ?? 0, message.Id, user!.Id, timeout); + bool ReactionPredicate(MessageReactionAddEvent evt) { if (message.Id != evt.MessageId) return false; // Ignore reactions for different messages @@ -57,11 +62,17 @@ public static class ContextUtils public static async Task ConfirmWithReply(this Context ctx, string expectedReply, bool treatAsHid = false) { + var timeout = Duration.FromMinutes(1); + + // check if http gateway and set listener + if (ctx.Cache is HttpDiscordCache) + await (ctx.Cache as HttpDiscordCache).AwaitMessage(ctx.Guild?.Id ?? 0, ctx.Channel.Id, ctx.Author.Id, timeout); + bool Predicate(MessageCreateEvent e) => e.Author.Id == ctx.Author.Id && e.ChannelId == ctx.Channel.Id; var msg = await ctx.Services.Resolve>() - .WaitFor(Predicate, Duration.FromMinutes(1)); + .WaitFor(Predicate, timeout); var content = msg.Content; if (treatAsHid) @@ -96,11 +107,17 @@ public static class ContextUtils async Task PromptPageNumber() { + var timeout = Duration.FromMinutes(0.5); + + // check if http gateway and set listener + if (ctx.Cache is HttpDiscordCache) + await (ctx.Cache as HttpDiscordCache).AwaitMessage(ctx.Guild?.Id ?? 0, ctx.Channel.Id, ctx.Author.Id, timeout); + bool Predicate(MessageCreateEvent e) => e.Author.Id == ctx.Author.Id && e.ChannelId == ctx.Channel.Id; var msg = await ctx.Services.Resolve>() - .WaitFor(Predicate, Duration.FromMinutes(0.5)); + .WaitFor(Predicate, timeout); int.TryParse(msg.Content, out var num); diff --git a/PluralKit.Bot/Utils/MiscUtils.cs b/PluralKit.Bot/Utils/MiscUtils.cs index 5ee99901..8cc24029 100644 --- a/PluralKit.Bot/Utils/MiscUtils.cs +++ b/PluralKit.Bot/Utils/MiscUtils.cs @@ -1,7 +1,8 @@ using System.Net; using System.Net.Sockets; - +using System.Globalization; using Myriad.Rest.Exceptions; +using Myriad.Rest.Types; using Newtonsoft.Json; @@ -102,4 +103,26 @@ public static class MiscUtils return true; } + + public static MultipartFile GenerateColorPreview(string color) + { + //generate a 128x128 solid color gif from bytes + //image data is a 1x1 pixel, using the background color to fill the rest of the canvas + var imgBytes = new byte[] + { + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, // Header + 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x00, // Logical Screen Descriptor + 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, // Global Color Table + 0x21, 0xF9, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, // Graphics Control Extension + 0x2C, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, // Image Descriptor + 0x02, 0x02, 0x4C, 0x01, 0x00, // Image Data + 0x3B // Trailer + }; //indices 13, 14 and 15 are the R, G, and B values respectively + + imgBytes[13] = byte.Parse(color.Substring(0, 2), NumberStyles.HexNumber); + imgBytes[14] = byte.Parse(color.Substring(2, 2), NumberStyles.HexNumber); + imgBytes[15] = byte.Parse(color.Substring(4, 2), NumberStyles.HexNumber); + + return new MultipartFile("color.gif", new MemoryStream(imgBytes), null, null, null); + } } \ No newline at end of file diff --git a/PluralKit.Bot/packages.lock.json b/PluralKit.Bot/packages.lock.json index e2eda930..449923d9 100644 --- a/PluralKit.Bot/packages.lock.json +++ b/PluralKit.Bot/packages.lock.json @@ -14,6 +14,16 @@ "resolved": "4.13.0", "contentHash": "Wfw3M1WpFcrYaGzPm7QyUTfIOYkVXQ1ry6p4WYjhbLz9fPwV23SGQZTFDpdox67NHM0V0g1aoQ4YKLm4ANtEEg==" }, + "Watson.Lite": { + "type": "Direct", + "requested": "[6.3.5, )", + "resolved": "6.3.5", + "contentHash": "YF8+se3IVenn8YlyNeb4wSJK6QMnVD0QHIOEiZ22wS4K2wkwoSDzWS+ZAjk1MaPeB+XO5gRoENUN//pOc+wI2g==", + "dependencies": { + "CavemanTcp": "2.0.5", + "Watson.Core": "6.3.5" + } + }, "App.Metrics": { "type": "Transitive", "resolved": "4.3.0", @@ -107,6 +117,11 @@ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1" } }, + "CavemanTcp": { + "type": "Transitive", + "resolved": "2.0.5", + "contentHash": "90wywmGpjrj26HMAkufYZwuZI8sVYB1mRwEdqugSR3kgDnPX+3l0jO86gwtFKsPvsEpsS4Dn/1EbhguzUxMU8Q==" + }, "Dapper": { "type": "Transitive", "resolved": "2.1.35", @@ -130,6 +145,11 @@ "System.Diagnostics.DiagnosticSource": "5.0.0" } }, + "IpMatcher": { + "type": "Transitive", + "resolved": "1.0.5", + "contentHash": "WXNlWERj+0GN699AnMNsuJ7PfUAbU4xhOHP3nrNXLHqbOaBxybu25luSYywX1133NSlitA4YkSNmJuyPvea4sw==" + }, "IPNetwork2": { "type": "Transitive", "resolved": "3.0.667", @@ -391,18 +411,18 @@ "resolved": "8.5.0", "contentHash": "VYYMZNitZ85UEhwOKkTQI63WEMvzUqwQc74I2mm8h/DBVAMcBBxqYPni4DmuRtbCwngmuONuK2yBJfWNRKzI+A==" }, - "Serilog": { + "RegexMatcher": { "type": "Transitive", - "resolved": "4.2.0", - "contentHash": "gmoWVOvKgbME8TYR+gwMf7osROiWAURterc6Rt2dQyX7wtjZYpqFiA/pY6ztjGQKKV62GGCyOcmtP1UKMHgSmA==" + "resolved": "1.0.9", + "contentHash": "RkQGXIrqHjD5h1mqefhgCbkaSdRYNRG5rrbzyw5zeLWiS0K1wq9xR3cNhQdzYR2MsKZ3GN523yRUsEQIMPxh3Q==" }, "Serilog.Extensions.Logging": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "NwSSYqPJeKNzl5AuXVHpGbr6PkZJFlNa14CdIebVjK3k/76kYj/mz5kiTRNVSsSaxM8kAIa1kpy/qyT9E4npRQ==", + "resolved": "8.0.0", + "contentHash": "YEAMWu1UnWgf1c1KP85l1SgXGfiVo0Rz6x08pCiPOIBt2Qe18tcZLvdBUuV5o1QHvrs8FAry9wTIhgBRtjIlEg==", "dependencies": { - "Microsoft.Extensions.Logging": "9.0.0", - "Serilog": "4.2.0" + "Microsoft.Extensions.Logging": "8.0.0", + "Serilog": "3.1.1" } }, "Serilog.Formatting.Compact": { @@ -714,12 +734,36 @@ "System.Runtime": "4.3.0" } }, + "Timestamps": { + "type": "Transitive", + "resolved": "1.0.11", + "contentHash": "SnWhXm3FkEStQGgUTfWMh9mKItNW032o/v8eAtFrOGqG0/ejvPPA1LdLZx0N/qqoY0TH3x11+dO00jeVcM8xNQ==" + }, + "UrlMatcher": { + "type": "Transitive", + "resolved": "3.0.1", + "contentHash": "hHBZVzFSfikrx4XsRsnCIwmGLgbNKtntnlqf4z+ygcNA6Y/L/J0x5GiZZWfXdTfpxhy5v7mlt2zrZs/L9SvbOA==" + }, + "Watson.Core": { + "type": "Transitive", + "resolved": "6.3.5", + "contentHash": "Y5YxKOCSLe2KDmfwvI/J0qApgmmZR77LwyoufRVfKH7GLdHiE7fY0IfoNxWTG7nNv8knBfgwyOxdehRm+4HaCg==", + "dependencies": { + "IpMatcher": "1.0.5", + "RegexMatcher": "1.0.9", + "System.Text.Json": "8.0.5", + "Timestamps": "1.0.11", + "UrlMatcher": "3.0.1" + } + }, "myriad": { "type": "Project", "dependencies": { + "NodaTime": "[3.2.0, )", + "NodaTime.Serialization.JsonNet": "[3.1.0, )", "Polly": "[8.5.0, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", - "Serilog": "[4.2.0, )", + "Serilog": "[4.1.0, )", "StackExchange.Redis": "[2.8.22, )", "System.Linq.Async": "[6.0.1, )" } @@ -747,8 +791,8 @@ "NodaTime.Serialization.JsonNet": "[3.1.0, )", "Npgsql": "[9.0.2, )", "Npgsql.NodaTime": "[9.0.2, )", - "Serilog": "[4.2.0, )", - "Serilog.Extensions.Logging": "[9.0.0, )", + "Serilog": "[4.1.0, )", + "Serilog.Extensions.Logging": "[8.0.0, )", "Serilog.Formatting.Compact": "[3.0.0, )", "Serilog.NodaTime": "[3.0.0, )", "Serilog.Sinks.Async": "[2.1.0, )", @@ -762,6 +806,9 @@ "System.Interactive.Async": "[6.0.1, )", "ipnetwork2": "[3.0.667, )" } + }, + "serilog": { + "type": "Project" } } } diff --git a/PluralKit.Core/CoreConfig.cs b/PluralKit.Core/CoreConfig.cs index 4adf815b..1e77271b 100644 --- a/PluralKit.Core/CoreConfig.cs +++ b/PluralKit.Core/CoreConfig.cs @@ -19,5 +19,4 @@ public class CoreConfig public LogEventLevel ConsoleLogLevel { get; set; } = LogEventLevel.Debug; public LogEventLevel ElasticLogLevel { get; set; } = LogEventLevel.Information; - public LogEventLevel FileLogLevel { get; set; } = LogEventLevel.Information; } \ No newline at end of file diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs b/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs index f0d70900..361a2bf3 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs @@ -19,16 +19,40 @@ public partial class ModelRepository } - public Task GetSystemGuild(ulong guild, SystemId system, bool defaultInsert = true) + public async Task GetSystemGuild(ulong guild, SystemId system, bool defaultInsert = true, bool search = false) { if (!defaultInsert) - return _db.QueryFirst(new Query("system_guild") + { + var simpleRes = await _db.QueryFirst(new Query("system_guild") .Where("guild", guild) .Where("system", system) ); + if (simpleRes != null || !search) + return simpleRes; + + var accounts = await GetSystemAccounts(system); + + var searchRes = await _db.QueryFirst( + "select exists(select 1 from command_messages where guild = @guild and sender = any(@accounts))", + new { guild = guild, accounts = accounts.Select(u => (long)u).ToArray() }, + queryName: "find_system_from_commands", + messages: true + ); + + if (!searchRes) + searchRes = await _db.QueryFirst( + "select exists(select 1 from command_messages where guild = @guild and sender = any(@accounts))", + new { guild = guild, accounts = accounts.Select(u => (long)u).ToArray() }, + queryName: "find_system_from_messages", + messages: true + ); + + if (!searchRes) + return null; + } var query = new Query("system_guild").AsInsert(new { guild, system }); - return _db.QueryFirst(query, + return await _db.QueryFirst(query, "on conflict (guild, system) do update set guild = $1, system = $2 returning *" ); } @@ -42,16 +66,25 @@ public partial class ModelRepository return settings; } - public Task GetMemberGuild(ulong guild, MemberId member, bool defaultInsert = true) + public async Task GetMemberGuild(ulong guild, MemberId member, bool defaultInsert = true, SystemId? search = null) { if (!defaultInsert) - return _db.QueryFirst(new Query("member_guild") + { + var simpleRes = await _db.QueryFirst(new Query("member_guild") .Where("guild", guild) .Where("member", member) ); + if (simpleRes != null || !search.HasValue) + return simpleRes; + + var systemConfig = await GetSystemGuild(guild, search.Value, defaultInsert: false, search: true); + + if (systemConfig == null) + return null; + } var query = new Query("member_guild").AsInsert(new { guild, member }); - return _db.QueryFirst(query, + return await _db.QueryFirst(query, "on conflict (guild, member) do update set guild = $1, member = $2 returning *" ); } diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Message.cs b/PluralKit.Core/Database/Repository/ModelRepository.Message.cs index 706e3a13..05313c89 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Message.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Message.cs @@ -42,12 +42,37 @@ public partial class ModelRepository }; } + public async Task AddCommandMessage(CommandMessage msg) + { + var query = new Query("command_messages").AsInsert(new + { + mid = msg.Mid, + guild = msg.Guild, + channel = msg.Channel, + sender = msg.Sender, + original_mid = msg.OriginalMid + }); + await _db.ExecuteQuery(query, messages: true); + + _logger.Debug("Stored command message {@StoredMessage} in channel {Channel}", msg, msg.Channel); + } + + public Task GetCommandMessage(ulong id) + => _db.QueryFirst(new Query("command_messages").Where("mid", id), messages: true); + public async Task DeleteMessage(ulong id) { var query = new Query("messages").AsDelete().Where("mid", id); var rowCount = await _db.ExecuteQuery(query, messages: true); if (rowCount > 0) _logger.Information("Deleted message {MessageId} from database", id); + else + { + var cquery = new Query("command_messages").AsDelete().Where("mid", id); + var crowCount = await _db.ExecuteQuery(query, messages: true); + if (crowCount > 0) + _logger.Information("Deleted command message {MessageId} from database", id); + } } public async Task DeleteMessagesBulk(IReadOnlyCollection ids) @@ -59,5 +84,19 @@ public partial class ModelRepository if (rowCount > 0) _logger.Information("Bulk deleted messages ({FoundCount} found) from database: {MessageIds}", rowCount, ids); + var cquery = new Query("command_messages").AsDelete().WhereIn("mid", ids.Select(id => (long)id).ToArray()); + var crowCount = await _db.ExecuteQuery(query, messages: true); + if (crowCount > 0) + _logger.Information("Bulk deleted command messages ({FoundCount} found) from database: {MessageIds}", rowCount, + ids); } +} + +public class CommandMessage +{ + public ulong Mid { get; set; } + public ulong Guild { get; set; } + public ulong Channel { get; set; } + public ulong Sender { get; set; } + public ulong OriginalMid { get; set; } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs index 11c6e2a9..d4d58093 100644 --- a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs +++ b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs @@ -9,7 +9,7 @@ namespace PluralKit.Core; internal class DatabaseMigrator { private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files - private const int TargetSchemaVersion = 51; + private const int TargetSchemaVersion = 52; private readonly ILogger _logger; public DatabaseMigrator(ILogger logger) diff --git a/PluralKit.Core/Modules/LoggingModule.cs b/PluralKit.Core/Modules/LoggingModule.cs index 18ffa58b..4be07b50 100644 --- a/PluralKit.Core/Modules/LoggingModule.cs +++ b/PluralKit.Core/Modules/LoggingModule.cs @@ -10,8 +10,7 @@ using NodaTime; using Serilog; using Serilog.Events; -using Serilog.Formatting.Compact; -using Serilog.Sinks.Seq; +using Serilog.Formatting.Json; using Serilog.Sinks.SystemConsole.Themes; using ILogger = Serilog.ILogger; @@ -50,14 +49,9 @@ public class LoggingModule: Module private ILogger InitLogger(CoreConfig config) { - var consoleTemplate = "[{Timestamp:HH:mm:ss.fff}] {Level:u3} {Message:lj}{NewLine}{Exception}"; - var outputTemplate = "[{Timestamp:yyyy-MM-dd HH:mm:ss.ffffff}] {Level:u3} {Message:lj}{NewLine}{Exception}"; - var logCfg = _cfg .Enrich.FromLogContext() - .Enrich.WithProperty("GitCommitHash", BuildInfoService.FullVersion) .ConfigureForNodaTime(DateTimeZoneProviders.Tzdb) - .Enrich.WithProperty("Component", _component) .MinimumLevel.Is(config.ConsoleLogLevel) // Don't want App.Metrics/D#+ spam @@ -73,35 +67,10 @@ public class LoggingModule: Module .Destructure.AsScalar() .Destructure.ByTransforming(t => new { t.Prefix, t.Suffix }) .Destructure.With() - .WriteTo.Async(a => - { - // Both the same output, except one is raw compact JSON and one is plain text. - // Output simultaneously. May remove the JSON formatter later, keeping it just in cast. - // Flush interval is 50ms (down from 10s) to make "tail -f" easier. May be too low? - a.File( - (config.LogDir ?? "logs") + $"/pluralkit.{_component}.log", - outputTemplate: outputTemplate, - retainedFileCountLimit: 10, - rollingInterval: RollingInterval.Day, - fileSizeLimitBytes: null, - flushToDiskInterval: TimeSpan.FromMilliseconds(50), - restrictedToMinimumLevel: config.FileLogLevel, - formatProvider: new UTCTimestampFormatProvider(), - buffered: true); - - a.File( - new RenderedCompactJsonFormatter(new ScalarFormatting.JsonValue()), - (config.LogDir ?? "logs") + $"/pluralkit.{_component}.json", - rollingInterval: RollingInterval.Day, - flushToDiskInterval: TimeSpan.FromMilliseconds(50), - restrictedToMinimumLevel: config.FileLogLevel, - buffered: true); - }) .WriteTo.Async(a => a.Console( - theme: AnsiConsoleTheme.Code, - outputTemplate: consoleTemplate, - restrictedToMinimumLevel: config.ConsoleLogLevel)); + new CustomJsonFormatter(_component), + config.ConsoleLogLevel)); if (config.ElasticUrl != null) { @@ -113,15 +82,6 @@ public class LoggingModule: Module ); } - if (config.SeqLogUrl != null) - { - logCfg.WriteTo.Seq( - config.SeqLogUrl, - restrictedToMinimumLevel: LogEventLevel.Verbose - ); - } - - _fn.Invoke(logCfg); return Log.Logger = logCfg.CreateLogger(); } diff --git a/PluralKit.Core/PluralKit.Core.csproj b/PluralKit.Core/PluralKit.Core.csproj index e4312988..a13bcefb 100644 --- a/PluralKit.Core/PluralKit.Core.csproj +++ b/PluralKit.Core/PluralKit.Core.csproj @@ -15,6 +15,10 @@ true + + + + @@ -37,8 +41,7 @@ - - + diff --git a/PluralKit.Core/Utils/BulkImporter/PluralKitImport.cs b/PluralKit.Core/Utils/BulkImporter/PluralKitImport.cs index 816dd618..f972c728 100644 --- a/PluralKit.Core/Utils/BulkImporter/PluralKitImport.cs +++ b/PluralKit.Core/Utils/BulkImporter/PluralKitImport.cs @@ -205,7 +205,7 @@ public partial class BulkImporter ? existingSwitches.Select(sw => sw.Id).Max() : (SwitchId?)null; - if (switches.Count > 10000) + if (switches.Count > 100000) throw new ImportException("Too many switches present in import file."); // Import switch definitions diff --git a/PluralKit.Core/Utils/SerilogJsonFormatter.cs b/PluralKit.Core/Utils/SerilogJsonFormatter.cs new file mode 100644 index 00000000..8bcc5731 --- /dev/null +++ b/PluralKit.Core/Utils/SerilogJsonFormatter.cs @@ -0,0 +1,215 @@ +using System.Runtime.CompilerServices; + +using Serilog.Events; +using Serilog.Formatting; +using Serilog.Formatting.Json; +using Serilog.Parsing; +using Serilog.Rendering; + +// Customized Serilog JSON output for PluralKit + +// Copyright 2013-2015 Serilog Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +namespace PluralKit.Core; + +static class Guard +{ + public static T AgainstNull( + T? argument, + [CallerArgumentExpression("argument")] string? paramName = null) + where T : class + { + if (argument is null) + { + throw new ArgumentNullException(paramName); + } + + return argument; + } +} + +/// +/// Formats log events in a simple JSON structure. Instances of this class +/// are safe for concurrent access by multiple threads. +/// +/// New code should prefer formatters from Serilog.Formatting.Compact, or ExpressionTemplate from +/// Serilog.Expressions. +public sealed class CustomJsonFormatter: ITextFormatter +{ + readonly JsonValueFormatter _jsonValueFormatter = new(); + readonly string _component; + + /// + /// Construct a . + /// + /// A string that will be written after each log event is formatted. + /// If null, will be used. + /// If , the message will be rendered and written to the output as a + /// property named RenderedMessage. + /// Supplies culture-specific formatting information, or null. + public CustomJsonFormatter(string component) + { + _component = component; + } + + private string CustomLevelString(LogEventLevel level) + { + switch (level) + { + case LogEventLevel.Verbose: + return "TRACE"; + case LogEventLevel.Debug: + return "DEBUG"; + case LogEventLevel.Information: + return "INFO"; + case LogEventLevel.Warning: + return "WARN"; + case LogEventLevel.Error: + return "ERROR"; + case LogEventLevel.Fatal: + return "FATAL"; + }; + + return "UNKNOWN"; + } + + /// + /// Format the log event into the output. + /// + /// The event to format. + /// The output. + /// When is null + /// When is null + public void Format(LogEvent logEvent, TextWriter output) + { + Guard.AgainstNull(logEvent); + Guard.AgainstNull(output); + + output.Write("{\"component\":\""); + output.Write(_component); + output.Write("\",\"timestamp\":\""); + output.Write(logEvent.Timestamp.ToString("O").Replace("+00:00", "Z")); + output.Write("\",\"level\":\""); + output.Write(CustomLevelString(logEvent.Level)); + + output.Write("\",\"message\":"); + var message = logEvent.MessageTemplate.Render(logEvent.Properties); + JsonValueFormatter.WriteQuotedJsonString(message, output); + + if (logEvent.TraceId != null) + { + output.Write(",\"TraceId\":"); + JsonValueFormatter.WriteQuotedJsonString(logEvent.TraceId.ToString()!, output); + } + + if (logEvent.SpanId != null) + { + output.Write(",\"SpanId\":"); + JsonValueFormatter.WriteQuotedJsonString(logEvent.SpanId.ToString()!, output); + } + + if (logEvent.Exception != null) + { + output.Write(",\"Exception\":"); + JsonValueFormatter.WriteQuotedJsonString(logEvent.Exception.ToString(), output); + } + + if (logEvent.Properties.Count != 0) + { + output.Write(",\"Properties\":{"); + + char? propertyDelimiter = null; + foreach (var property in logEvent.Properties) + { + if (propertyDelimiter != null) + output.Write(propertyDelimiter.Value); + else + propertyDelimiter = ','; + + JsonValueFormatter.WriteQuotedJsonString(property.Key, output); + output.Write(':'); + _jsonValueFormatter.Format(property.Value, output); + } + + output.Write('}'); + } + + var tokensWithFormat = logEvent.MessageTemplate.Tokens + .OfType() + .Where(pt => pt.Format != null) + .GroupBy(pt => pt.PropertyName) + .ToArray(); + + if (tokensWithFormat.Length != 0) + { + output.Write(",\"Renderings\":{"); + WriteRenderingsValues(tokensWithFormat, logEvent.Properties, output); + output.Write('}'); + } + + output.Write('}'); + output.Write("\n"); + } + + void WriteRenderingsValues(IEnumerable> tokensWithFormat, IReadOnlyDictionary properties, TextWriter output) + { + static void WriteNameValuePair(string name, string value, ref char? precedingDelimiter, TextWriter output) + { + if (precedingDelimiter != null) + output.Write(precedingDelimiter.Value); + + JsonValueFormatter.WriteQuotedJsonString(name, output); + output.Write(':'); + JsonValueFormatter.WriteQuotedJsonString(value, output); + precedingDelimiter = ','; + } + + char? propertyDelimiter = null; + foreach (var propertyFormats in tokensWithFormat) + { + if (propertyDelimiter != null) + output.Write(propertyDelimiter.Value); + else + propertyDelimiter = ','; + + output.Write('"'); + output.Write(propertyFormats.Key); + output.Write("\":["); + + char? formatDelimiter = null; + foreach (var format in propertyFormats) + { + if (formatDelimiter != null) + output.Write(formatDelimiter.Value); + + formatDelimiter = ','; + + output.Write('{'); + char? elementDelimiter = null; + + // Caller ensures that `tokensWithFormat` contains only property tokens that have non-null `Format`s. + WriteNameValuePair("Format", format.Format!, ref elementDelimiter, output); + + using var sw = ReusableStringWriter.GetOrCreate(); + MessageTemplateRenderer.RenderPropertyToken(format, properties, sw, null, isLiteral: true, isJson: false); + WriteNameValuePair("Rendering", sw.ToString(), ref elementDelimiter, output); + + output.Write('}'); + } + + output.Write(']'); + } + } +} \ No newline at end of file diff --git a/PluralKit.Core/packages.lock.json b/PluralKit.Core/packages.lock.json index a4768566..4f9ec5d0 100644 --- a/PluralKit.Core/packages.lock.json +++ b/PluralKit.Core/packages.lock.json @@ -198,20 +198,14 @@ "Npgsql": "9.0.2" } }, - "Serilog": { - "type": "Direct", - "requested": "[4.2.0, )", - "resolved": "4.2.0", - "contentHash": "gmoWVOvKgbME8TYR+gwMf7osROiWAURterc6Rt2dQyX7wtjZYpqFiA/pY6ztjGQKKV62GGCyOcmtP1UKMHgSmA==" - }, "Serilog.Extensions.Logging": { "type": "Direct", - "requested": "[9.0.0, )", - "resolved": "9.0.0", - "contentHash": "NwSSYqPJeKNzl5AuXVHpGbr6PkZJFlNa14CdIebVjK3k/76kYj/mz5kiTRNVSsSaxM8kAIa1kpy/qyT9E4npRQ==", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "YEAMWu1UnWgf1c1KP85l1SgXGfiVo0Rz6x08pCiPOIBt2Qe18tcZLvdBUuV5o1QHvrs8FAry9wTIhgBRtjIlEg==", "dependencies": { - "Microsoft.Extensions.Logging": "9.0.0", - "Serilog": "4.2.0" + "Microsoft.Extensions.Logging": "8.0.0", + "Serilog": "3.1.1" } }, "Serilog.Formatting.Compact": { @@ -722,6 +716,9 @@ "Microsoft.NETCore.Targets": "1.1.0", "System.Runtime": "4.3.0" } + }, + "serilog": { + "type": "Project" } } } diff --git a/PluralKit.Tests/packages.lock.json b/PluralKit.Tests/packages.lock.json index 2ba02316..ce0dbc15 100644 --- a/PluralKit.Tests/packages.lock.json +++ b/PluralKit.Tests/packages.lock.json @@ -128,6 +128,11 @@ "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.1" } }, + "CavemanTcp": { + "type": "Transitive", + "resolved": "2.0.5", + "contentHash": "90wywmGpjrj26HMAkufYZwuZI8sVYB1mRwEdqugSR3kgDnPX+3l0jO86gwtFKsPvsEpsS4Dn/1EbhguzUxMU8Q==" + }, "Dapper": { "type": "Transitive", "resolved": "2.1.35", @@ -156,6 +161,11 @@ "resolved": "2.14.1", "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" }, + "IpMatcher": { + "type": "Transitive", + "resolved": "1.0.5", + "contentHash": "WXNlWERj+0GN699AnMNsuJ7PfUAbU4xhOHP3nrNXLHqbOaBxybu25luSYywX1133NSlitA4YkSNmJuyPvea4sw==" + }, "IPNetwork2": { "type": "Transitive", "resolved": "3.0.667", @@ -310,21 +320,21 @@ }, "Microsoft.Extensions.DependencyModel": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "saxr2XzwgDU77LaQfYFXmddEDRUKHF4DaGMZkNB3qjdVSZlax3//dGJagJkKrGMIPNZs2jVFXITyCCR6UHJNdA==", + "resolved": "8.0.0", + "contentHash": "NSmDw3K0ozNDgShSIpsZcbFIzBX4w28nDag+TfaQujkXGazBm+lid5onlWoCBy4VsLxqnnKjEBbGSJVWJMf43g==", "dependencies": { - "System.Text.Encodings.Web": "9.0.0", - "System.Text.Json": "9.0.0" + "System.Text.Encodings.Web": "8.0.0", + "System.Text.Json": "8.0.0" } }, "Microsoft.Extensions.Diagnostics.Abstractions": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "1K8P7XzuzX8W8pmXcZjcrqS6x5eSSdvhQohmcpgiQNY/HlDAlnrhR9dvlURfFz428A+RTCJpUyB+aKTA6AgVcQ==", + "resolved": "8.0.0", + "contentHash": "JHYCQG7HmugNYUhOl368g+NMxYE/N/AiclCYRNlgCY9eVyiBkOHMwK4x60RYMxv9EL3+rmj1mqHvdCiPpC+D4Q==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", - "Microsoft.Extensions.Options": "9.0.0", - "System.Diagnostics.DiagnosticSource": "9.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "System.Diagnostics.DiagnosticSource": "8.0.0" } }, "Microsoft.Extensions.FileProviders.Abstractions": { @@ -352,14 +362,14 @@ }, "Microsoft.Extensions.Hosting.Abstractions": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "yUKJgu81ExjvqbNWqZKshBbLntZMbMVz/P7Way2SBx7bMqA08Mfdc9O7hWDKAiSp+zPUGT6LKcSCQIPeDK+CCw==", + "resolved": "8.0.0", + "contentHash": "AG7HWwVRdCHlaA++1oKDxLsXIBxmDpMPb3VoyOoAghEWnkUvEAdYQUwnV4jJbAaa/nMYNiEh5ByoLauZBEiovg==", "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "9.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", - "Microsoft.Extensions.Diagnostics.Abstractions": "9.0.0", - "Microsoft.Extensions.FileProviders.Abstractions": "9.0.0", - "Microsoft.Extensions.Logging.Abstractions": "9.0.0" + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Diagnostics.Abstractions": "8.0.0", + "Microsoft.Extensions.FileProviders.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0" } }, "Microsoft.Extensions.Logging": { @@ -510,49 +520,52 @@ "resolved": "8.5.0", "contentHash": "VYYMZNitZ85UEhwOKkTQI63WEMvzUqwQc74I2mm8h/DBVAMcBBxqYPni4DmuRtbCwngmuONuK2yBJfWNRKzI+A==" }, + "RegexMatcher": { + "type": "Transitive", + "resolved": "1.0.9", + "contentHash": "RkQGXIrqHjD5h1mqefhgCbkaSdRYNRG5rrbzyw5zeLWiS0K1wq9xR3cNhQdzYR2MsKZ3GN523yRUsEQIMPxh3Q==" + }, "Sentry": { "type": "Transitive", "resolved": "4.13.0", "contentHash": "Wfw3M1WpFcrYaGzPm7QyUTfIOYkVXQ1ry6p4WYjhbLz9fPwV23SGQZTFDpdox67NHM0V0g1aoQ4YKLm4ANtEEg==" }, - "Serilog": { - "type": "Transitive", - "resolved": "4.2.0", - "contentHash": "gmoWVOvKgbME8TYR+gwMf7osROiWAURterc6Rt2dQyX7wtjZYpqFiA/pY6ztjGQKKV62GGCyOcmtP1UKMHgSmA==" - }, "Serilog.AspNetCore": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "JslDajPlBsn3Pww1554flJFTqROvK9zz9jONNQgn0D8Lx2Trw8L0A8/n6zEQK1DAZWXrJwiVLw8cnTR3YFuYsg==", + "resolved": "8.0.0", + "contentHash": "FAjtKPZ4IzqFQBqZKPv6evcXK/F0ls7RoXI/62Pnx2igkDZ6nZ/jn/C/FxVATqQbEQvtqP+KViWYIe4NZIHa2w==", "dependencies": { - "Serilog": "4.2.0", - "Serilog.Extensions.Hosting": "9.0.0", - "Serilog.Formatting.Compact": "3.0.0", - "Serilog.Settings.Configuration": "9.0.0", - "Serilog.Sinks.Console": "6.0.0", - "Serilog.Sinks.Debug": "3.0.0", - "Serilog.Sinks.File": "6.0.0" + "Microsoft.Extensions.DependencyInjection": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0", + "Serilog": "3.1.1", + "Serilog.Extensions.Hosting": "8.0.0", + "Serilog.Extensions.Logging": "8.0.0", + "Serilog.Formatting.Compact": "2.0.0", + "Serilog.Settings.Configuration": "8.0.0", + "Serilog.Sinks.Console": "5.0.0", + "Serilog.Sinks.Debug": "2.0.0", + "Serilog.Sinks.File": "5.0.0" } }, "Serilog.Extensions.Hosting": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "u2TRxuxbjvTAldQn7uaAwePkWxTHIqlgjelekBtilAGL5sYyF3+65NWctN4UrwwGLsDC7c3Vz3HnOlu+PcoxXg==", + "resolved": "8.0.0", + "contentHash": "db0OcbWeSCvYQkHWu6n0v40N4kKaTAXNjlM3BKvcbwvNzYphQFcBR+36eQ/7hMMwOkJvAyLC2a9/jNdUL5NjtQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.0", - "Microsoft.Extensions.Hosting.Abstractions": "9.0.0", - "Microsoft.Extensions.Logging.Abstractions": "9.0.0", - "Serilog": "4.2.0", - "Serilog.Extensions.Logging": "9.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Hosting.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Serilog": "3.1.1", + "Serilog.Extensions.Logging": "8.0.0" } }, "Serilog.Extensions.Logging": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "NwSSYqPJeKNzl5AuXVHpGbr6PkZJFlNa14CdIebVjK3k/76kYj/mz5kiTRNVSsSaxM8kAIa1kpy/qyT9E4npRQ==", + "resolved": "8.0.0", + "contentHash": "YEAMWu1UnWgf1c1KP85l1SgXGfiVo0Rz6x08pCiPOIBt2Qe18tcZLvdBUuV5o1QHvrs8FAry9wTIhgBRtjIlEg==", "dependencies": { - "Microsoft.Extensions.Logging": "9.0.0", - "Serilog": "4.2.0" + "Microsoft.Extensions.Logging": "8.0.0", + "Serilog": "3.1.1" } }, "Serilog.Formatting.Compact": { @@ -582,12 +595,12 @@ }, "Serilog.Settings.Configuration": { "type": "Transitive", - "resolved": "9.0.0", - "contentHash": "4/Et4Cqwa+F88l5SeFeNZ4c4Z6dEAIKbu3MaQb2Zz9F/g27T5a3wvfMcmCOaAiACjfUb4A6wrlTVfyYUZk3RRQ==", + "resolved": "8.0.0", + "contentHash": "nR0iL5HwKj5v6ULo3/zpP8NMcq9E2pxYA6XKTSWCbugVs4YqPyvaqaKOY+OMpPivKp7zMEpax2UKHnDodbRB0Q==", "dependencies": { - "Microsoft.Extensions.Configuration.Binder": "9.0.0", - "Microsoft.Extensions.DependencyModel": "9.0.0", - "Serilog": "4.2.0" + "Microsoft.Extensions.Configuration.Binder": "8.0.0", + "Microsoft.Extensions.DependencyModel": "8.0.0", + "Serilog": "3.1.1" } }, "Serilog.Sinks.Async": { @@ -608,10 +621,10 @@ }, "Serilog.Sinks.Debug": { "type": "Transitive", - "resolved": "3.0.0", - "contentHash": "4BzXcdrgRX7wde9PmHuYd9U6YqycCC28hhpKonK7hx0wb19eiuRj16fPcPSVp0o/Y1ipJuNLYQ00R3q2Zs8FDA==", + "resolved": "2.0.0", + "contentHash": "Y6g3OBJ4JzTyyw16fDqtFcQ41qQAydnEvEqmXjhwhgjsnG/FaJ8GUqF5ldsC/bVkK8KYmqrPhDO+tm4dF6xx4A==", "dependencies": { - "Serilog": "4.0.0" + "Serilog": "2.10.0" } }, "Serilog.Sinks.Elasticsearch": { @@ -887,6 +900,37 @@ "System.Runtime": "4.3.0" } }, + "Timestamps": { + "type": "Transitive", + "resolved": "1.0.11", + "contentHash": "SnWhXm3FkEStQGgUTfWMh9mKItNW032o/v8eAtFrOGqG0/ejvPPA1LdLZx0N/qqoY0TH3x11+dO00jeVcM8xNQ==" + }, + "UrlMatcher": { + "type": "Transitive", + "resolved": "3.0.1", + "contentHash": "hHBZVzFSfikrx4XsRsnCIwmGLgbNKtntnlqf4z+ygcNA6Y/L/J0x5GiZZWfXdTfpxhy5v7mlt2zrZs/L9SvbOA==" + }, + "Watson.Core": { + "type": "Transitive", + "resolved": "6.3.5", + "contentHash": "Y5YxKOCSLe2KDmfwvI/J0qApgmmZR77LwyoufRVfKH7GLdHiE7fY0IfoNxWTG7nNv8knBfgwyOxdehRm+4HaCg==", + "dependencies": { + "IpMatcher": "1.0.5", + "RegexMatcher": "1.0.9", + "System.Text.Json": "8.0.5", + "Timestamps": "1.0.11", + "UrlMatcher": "3.0.1" + } + }, + "Watson.Lite": { + "type": "Transitive", + "resolved": "6.3.5", + "contentHash": "YF8+se3IVenn8YlyNeb4wSJK6QMnVD0QHIOEiZ22wS4K2wkwoSDzWS+ZAjk1MaPeB+XO5gRoENUN//pOc+wI2g==", + "dependencies": { + "CavemanTcp": "2.0.5", + "Watson.Core": "6.3.5" + } + }, "xunit.abstractions": { "type": "Transitive", "resolved": "2.0.3", @@ -930,9 +974,11 @@ "myriad": { "type": "Project", "dependencies": { + "NodaTime": "[3.2.0, )", + "NodaTime.Serialization.JsonNet": "[3.1.0, )", "Polly": "[8.5.0, )", "Polly.Contrib.WaitAndRetry": "[1.1.1, )", - "Serilog": "[4.2.0, )", + "Serilog": "[4.1.0, )", "StackExchange.Redis": "[2.8.22, )", "System.Linq.Async": "[6.0.1, )" } @@ -945,7 +991,7 @@ "Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer": "[5.1.0, )", "PluralKit.Core": "[1.0.0, )", "Sentry": "[4.13.0, )", - "Serilog.AspNetCore": "[9.0.0, )" + "Serilog.AspNetCore": "[8.0.0, )" } }, "pluralkit.bot": { @@ -954,7 +1000,8 @@ "Humanizer.Core": "[2.14.1, )", "Myriad": "[1.0.0, )", "PluralKit.Core": "[1.0.0, )", - "Sentry": "[4.13.0, )" + "Sentry": "[4.13.0, )", + "Watson.Lite": "[6.3.5, )" } }, "pluralkit.core": { @@ -980,8 +1027,8 @@ "NodaTime.Serialization.JsonNet": "[3.1.0, )", "Npgsql": "[9.0.2, )", "Npgsql.NodaTime": "[9.0.2, )", - "Serilog": "[4.2.0, )", - "Serilog.Extensions.Logging": "[9.0.0, )", + "Serilog": "[4.1.0, )", + "Serilog.Extensions.Logging": "[8.0.0, )", "Serilog.Formatting.Compact": "[3.0.0, )", "Serilog.NodaTime": "[3.0.0, )", "Serilog.Sinks.Async": "[2.1.0, )", @@ -995,6 +1042,9 @@ "System.Interactive.Async": "[6.0.1, )", "ipnetwork2": "[3.0.667, )" } + }, + "serilog": { + "type": "Project" } } } diff --git a/README.md b/README.md index 5060756a..26b96c6d 100644 --- a/README.md +++ b/README.md @@ -5,75 +5,35 @@ PluralKit is a Discord bot meant for plural communities. It has features like me PluralKit has a Discord server for support, feedback, and discussion: https://discord.gg/PczBt78 -# Requirements -Running the bot requires [.NET 5](https://dotnet.microsoft.com/download), a PostgreSQL database and a Redis database. It should function on any system where the prerequisites are set up (including Windows). - -Optionally, it can integrate with [Sentry](https://sentry.io/welcome/) for error reporting and [InfluxDB](https://www.influxdata.com/products/influxdb-overview/) for aggregate statistics. - -# Configuration -Configuring the bot is done through a JSON configuration file. An example of the configuration format can be seen in [`pluralkit.conf.example`](https://github.com/PluralKit/PluralKit/blob/master/pluralkit.conf.example). -The configuration file needs to be placed in the bot's working directory (usually the repository root) and must be called `pluralkit.conf`. - -The configuration file is in JSON format (albeit with a `.conf` extension). The following keys are available (using `.` to indicate a nested object level), bolded key names are required: -* **`PluralKit.Bot.Token`**: the Discord bot token to connect with -* **`PluralKit.Database`**: the URI of the database to connect to (in [ADO.NET Npgsql format](https://www.connectionstrings.com/npgsql/)) -* **`PluralKit.RedisAddr`**: the `host:port` of a Redis database to connect to -* `PluralKit.Bot.Prefixes`: an array of command prefixes to use (default `["pk;", "pk!"]`). -* **`PluralKit.Bot.ClientId`**: the ID of the bot's user account, used for calculating the bot's own permissions and for the link in `pk;invite`. -* `PluralKit.SentryUrl` *(optional)*: the [Sentry](https://sentry.io/welcome/) client key/DSN to report runtime errors to. If absent, disables Sentry integration. -* `PluralKit.InfluxUrl` *(optional)*: the URL to an [InfluxDB](https://www.influxdata.com/products/influxdb-overview/) server to report aggregate statistics to. An example of these stats can be seen on [the public stats page](https://stats.pluralkit.me). -* `PluralKit.InfluxDb` *(optional)*: the name of an InfluxDB database to report statistics to. If either this field or `PluralKit.InfluxUrl` are absent, InfluxDB reporting will be disabled. -* `PluralKit.LogDir` *(optional)*: the directory to save information and error logs to. If left blank, will default to `logs/` in the current working directory. - -The bot can also take configuration from environment variables, which will override the values read from the file. Here, use `:` (colon) or `__` (double underscore) as a level separator (eg. `export PluralKit__Bot__Token=foobar123`) as per [ASP.NET config](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1#environment-variables). - # Running +In production, we run PluralKit using Kubernetes. The configuration can be found in the infra repo. -## Docker -The easiest way to get the bot running is with Docker. The repository contains a `docker-compose.yml` file ready to use. +For self-hosting, it's simpler to use Docker, with the provided [docker-compose](./docker-compose.yml) file. -* Clone this repository: `git clone https://github.com/PluralKit/PluralKit` -* Create a `pluralkit.conf` file in the same directory as `docker-compose.yml` containing at least `PluralKit.Bot.Token` and `PluralKit.Bot.ClientId` fields - * (`PluralKit.Database` is overridden in `docker-compose.yml` to point to the Postgres container) -* Build the bot: `docker-compose build` -* Run the bot: `docker-compose up` - -In other words: +Create a `.env` file with the Discord client ID and bot token: ``` -$ git clone https://github.com/PluralKit/PluralKit -$ cd PluralKit -$ cp pluralkit.conf.example pluralkit.conf -$ nano pluralkit.conf # (or vim, or whatever) -$ docker-compose up -d +CLIENT_ID=198622483471925248 +BOT_TOKEN=MTk4NjIyNDgzNDcxOTI1MjQ4.Cl2FMQ.ZnCjm1XVW7vRze4b7Cq4se7kKWs ``` -## Manually -* Install the .NET 6 SDK (see https://dotnet.microsoft.com/download) -* Clone this repository: `git clone https://github.com/PluralKit/PluralKit` -* Create and fill in a `pluralkit.conf` file in the same directory as `docker-compose.yml` -* Run the bot: `dotnet run --project PluralKit.Bot` - * Alternatively, `dotnet build -c Release -o build/`, then `dotnet build/PluralKit.Bot.dll` +If you want to use `pk;admin` commands (to raise member limits and such), set `ADMIN_ROLE` to a Discord role ID: -(tip: use `scripts/run-test-db.sh` to run a temporary PostgreSQL database on your local system. Requires Docker.) +``` +ADMIN_ROLE=682632767057428509 +``` -## Scheduled Tasks worker +*If you didn't clone the repository with submodules, run `git submodule update --init` first to pull the required submodules.* +Run `docker compose build`, then `docker compose up -d`. -There is a scheduled tasks worker that needs to be ran separately from the bot. This handles cleaning up the database, and updating statistics (system/member/etc counts, shown in the `pk;stats` embed). +To view logs, use `docker compose logs`. -Note: This worker is *not required*, and the bot will function correctly without it. +Postgres data is stored in a `pluralkit_data` [Docker volume](https://docs.docker.com/engine/storage/volumes/). -If you are running the bot via docker-compose, this is set up automatically. - -If you run the bot manually you can run the worker as such: -* `dotnet run --project PluralKit.ScheduledTasks` -* or if you used `dotnet build` rather than `dotnet run` to run the bot: `dotnet build/PluralKit.ScheduledTasks.dll` - -# Upgrading database from legacy version -If you have an instance of the Python version of the bot (from the `legacy` branch), you may need to take extra database migration steps. -For more information, see [LEGACYMIGRATE.md](./LEGACYMIGRATE.md). +# Development +See [the dev-docs/ directory](./dev-docs/README.md) # User documentation See [the docs/ directory](./docs/README.md) # License -This project is under the GNU Affero General Public License, Version 3. It is available at the following link: https://www.gnu.org/licenses/agpl-3.0.en.html \ No newline at end of file +This project is under the GNU Affero General Public License, Version 3. It is available at the following link: https://www.gnu.org/licenses/agpl-3.0.en.html diff --git a/Serilog b/Serilog new file mode 160000 index 00000000..f5eb991c --- /dev/null +++ b/Serilog @@ -0,0 +1 @@ +Subproject commit f5eb991cb4c4a0c1e2407de7504c543536786598 diff --git a/Dockerfile b/ci/Dockerfile.dotnet similarity index 97% rename from Dockerfile rename to ci/Dockerfile.dotnet index dedc085d..c10952b3 100644 --- a/Dockerfile +++ b/ci/Dockerfile.dotnet @@ -10,6 +10,7 @@ COPY PluralKit.Bot/PluralKit.Bot.csproj /app/PluralKit.Bot/ COPY PluralKit.Core/PluralKit.Core.csproj /app/PluralKit.Core/ COPY PluralKit.Tests/PluralKit.Tests.csproj /app/PluralKit.Tests/ COPY .git/ /app/.git +COPY Serilog/ /app/Serilog/ RUN dotnet restore PluralKit.sln # Copy the rest of the code and build diff --git a/ci/Dockerfile.rust b/ci/Dockerfile.rust index 0452cb4b..e320fb00 100644 --- a/ci/Dockerfile.rust +++ b/ci/Dockerfile.rust @@ -25,18 +25,22 @@ COPY Cargo.lock /build/ COPY crates/ /build/crates +RUN cargo build --bin migrate --release --target x86_64-unknown-linux-musl RUN cargo build --bin api --release --target x86_64-unknown-linux-musl RUN cargo build --bin dispatch --release --target x86_64-unknown-linux-musl RUN cargo build --bin gateway --release --target x86_64-unknown-linux-musl RUN cargo build --bin avatars --release --target x86_64-unknown-linux-musl RUN cargo build --bin avatar_cleanup --release --target x86_64-unknown-linux-musl RUN cargo build --bin scheduled_tasks --release --target x86_64-unknown-linux-musl +RUN cargo build --bin gdpr_worker --release --target x86_64-unknown-linux-musl -FROM scratch +FROM alpine:latest +COPY --from=binary-builder /build/target/x86_64-unknown-linux-musl/release/migrate /migrate COPY --from=binary-builder /build/target/x86_64-unknown-linux-musl/release/api /api COPY --from=binary-builder /build/target/x86_64-unknown-linux-musl/release/dispatch /dispatch COPY --from=binary-builder /build/target/x86_64-unknown-linux-musl/release/gateway /gateway COPY --from=binary-builder /build/target/x86_64-unknown-linux-musl/release/avatars /avatars COPY --from=binary-builder /build/target/x86_64-unknown-linux-musl/release/avatar_cleanup /avatar_cleanup COPY --from=binary-builder /build/target/x86_64-unknown-linux-musl/release/scheduled_tasks /scheduled_tasks +COPY --from=binary-builder /build/target/x86_64-unknown-linux-musl/release/gdpr_worker /gdpr_worker diff --git a/ci/rust-docker-target.sh b/ci/rust-docker-target.sh index 8e87dbca..ba6df5e9 100755 --- a/ci/rust-docker-target.sh +++ b/ci/rust-docker-target.sh @@ -37,8 +37,10 @@ EOF } # add rust binaries here to build +build migrate build api build dispatch build gateway build avatars "COPY .docker-bin/avatar_cleanup /bin/avatar_cleanup" build scheduled_tasks +build gdpr_worker diff --git a/crates/api/src/auth.rs b/crates/api/src/auth.rs new file mode 100644 index 00000000..c084eafe --- /dev/null +++ b/crates/api/src/auth.rs @@ -0,0 +1,48 @@ +use pluralkit_models::{PKSystem, PrivacyLevel, SystemId}; + +pub const INTERNAL_SYSTEMID_HEADER: &'static str = "x-pluralkit-systemid"; +pub const INTERNAL_APPID_HEADER: &'static str = "x-pluralkit-appid"; + +#[derive(Clone)] +pub struct AuthState { + system_id: Option, + app_id: Option, +} + +impl AuthState { + pub fn new(system_id: Option, app_id: Option) -> Self { + Self { system_id, app_id } + } + + pub fn system_id(&self) -> Option { + self.system_id + } + + pub fn app_id(&self) -> Option { + self.app_id + } + + pub fn access_level_for(&self, a: &impl Authable) -> PrivacyLevel { + if self + .system_id + .map(|id| id == a.authable_system_id()) + .unwrap_or(false) + { + PrivacyLevel::Private + } else { + PrivacyLevel::Public + } + } +} + +// authable trait/impls + +pub trait Authable { + fn authable_system_id(&self) -> SystemId; +} + +impl Authable for PKSystem { + fn authable_system_id(&self) -> SystemId { + self.id + } +} diff --git a/crates/api/src/endpoints/mod.rs b/crates/api/src/endpoints/mod.rs index f19f44d8..c311367c 100644 --- a/crates/api/src/endpoints/mod.rs +++ b/crates/api/src/endpoints/mod.rs @@ -1 +1,2 @@ pub mod private; +pub mod system; diff --git a/crates/api/src/endpoints/private.rs b/crates/api/src/endpoints/private.rs index ad76e275..df67421c 100644 --- a/crates/api/src/endpoints/private.rs +++ b/crates/api/src/endpoints/private.rs @@ -52,7 +52,7 @@ use axum::{ }; use hyper::StatusCode; use libpk::config; -use pluralkit_models::{PKSystem, PKSystemConfig}; +use pluralkit_models::{PKSystem, PKSystemConfig, PrivacyLevel}; use reqwest::ClientBuilder; #[derive(serde::Deserialize, Debug)] @@ -151,14 +151,12 @@ pub async fn discord_callback( .await .expect("failed to query"); - if system.is_none() { + let Some(system) = system else { return json_err( StatusCode::BAD_REQUEST, "user does not have a system registered".to_string(), ); - } - - let system = system.unwrap(); + }; let system_config: Option = sqlx::query_as( r#" @@ -179,7 +177,7 @@ pub async fn discord_callback( ( StatusCode::OK, serde_json::to_string(&serde_json::json!({ - "system": system.to_json(), + "system": system.to_json(PrivacyLevel::Private), "config": system_config.to_json(), "user": user, "token": token, diff --git a/crates/api/src/endpoints/system.rs b/crates/api/src/endpoints/system.rs new file mode 100644 index 00000000..e510f7c5 --- /dev/null +++ b/crates/api/src/endpoints/system.rs @@ -0,0 +1,69 @@ +use axum::{ + extract::State, + http::StatusCode, + response::{IntoResponse, Response}, + Extension, Json, +}; +use serde_json::json; +use sqlx::Postgres; +use tracing::error; + +use pluralkit_models::{PKSystem, PKSystemConfig, PrivacyLevel}; + +use crate::{auth::AuthState, util::json_err, ApiContext}; + +pub async fn get_system_settings( + Extension(auth): Extension, + Extension(system): Extension, + State(ctx): State, +) -> Response { + let access_level = auth.access_level_for(&system); + + let mut config = match sqlx::query_as::( + "select * from system_config where system = $1", + ) + .bind(system.id) + .fetch_optional(&ctx.db) + .await + { + Ok(Some(config)) => config, + Ok(None) => { + error!( + system = system.id, + "failed to find system config for existing system" + ); + return json_err( + StatusCode::INTERNAL_SERVER_ERROR, + r#"{"message": "500: Internal Server Error", "code": 0}"#.to_string(), + ); + } + Err(err) => { + error!(?err, "failed to query system config"); + return json_err( + StatusCode::INTERNAL_SERVER_ERROR, + r#"{"message": "500: Internal Server Error", "code": 0}"#.to_string(), + ); + } + }; + + // fix this + if config.name_format.is_none() { + config.name_format = Some("{name} {tag}".to_string()); + } + + Json(&match access_level { + PrivacyLevel::Private => config.to_json(), + PrivacyLevel::Public => json!({ + "pings_enabled": config.pings_enabled, + "latch_timeout": config.latch_timeout, + "case_sensitive_proxy_tags": config.case_sensitive_proxy_tags, + "proxy_error_message_enabled": config.proxy_error_message_enabled, + "hid_display_split": config.hid_display_split, + "hid_display_caps": config.hid_display_caps, + "hid_list_padding": config.hid_list_padding, + "proxy_switch": config.proxy_switch, + "name_format": config.name_format, + }), + }) + .into_response() +} diff --git a/crates/api/src/main.rs b/crates/api/src/main.rs index bf07ff8f..e3a201cb 100644 --- a/crates/api/src/main.rs +++ b/crates/api/src/main.rs @@ -1,10 +1,13 @@ +#![feature(let_chains)] + +use auth::{AuthState, INTERNAL_APPID_HEADER, INTERNAL_SYSTEMID_HEADER}; use axum::{ body::Body, extract::{Request as ExtractRequest, State}, http::{Response, StatusCode, Uri}, response::IntoResponse, routing::{delete, get, patch, post}, - Router, + Extension, Router, }; use hyper_util::{ client::legacy::{connect::HttpConnector, Client}, @@ -12,6 +15,7 @@ use hyper_util::{ }; use tracing::{error, info}; +mod auth; mod endpoints; mod error; mod middleware; @@ -27,6 +31,7 @@ pub struct ApiContext { } async fn rproxy( + Extension(auth): Extension, State(ctx): State, mut req: ExtractRequest, ) -> Result, StatusCode> { @@ -41,12 +46,25 @@ async fn rproxy( *req.uri_mut() = Uri::try_from(uri).unwrap(); + let headers = req.headers_mut(); + + headers.remove(INTERNAL_SYSTEMID_HEADER); + headers.remove(INTERNAL_APPID_HEADER); + + if let Some(sid) = auth.system_id() { + headers.append(INTERNAL_SYSTEMID_HEADER, sid.into()); + } + + if let Some(aid) = auth.app_id() { + headers.append(INTERNAL_APPID_HEADER, aid.into()); + } + Ok(ctx .rproxy_client .request(req) .await - .map_err(|err| { - error!("failed to serve reverse proxy to dotnet-api: {:?}", err); + .map_err(|error| { + error!(?error, "failed to serve reverse proxy to dotnet-api"); StatusCode::BAD_GATEWAY })? .into_response()) @@ -57,52 +75,52 @@ async fn rproxy( fn router(ctx: ApiContext) -> Router { // processed upside down (???) so we have to put middleware at the end Router::new() - .route("/v2/systems/:system_id", get(rproxy)) - .route("/v2/systems/:system_id", patch(rproxy)) - .route("/v2/systems/:system_id/settings", get(rproxy)) - .route("/v2/systems/:system_id/settings", patch(rproxy)) + .route("/v2/systems/{system_id}", get(rproxy)) + .route("/v2/systems/{system_id}", patch(rproxy)) + .route("/v2/systems/{system_id}/settings", get(endpoints::system::get_system_settings)) + .route("/v2/systems/{system_id}/settings", patch(rproxy)) - .route("/v2/systems/:system_id/members", get(rproxy)) + .route("/v2/systems/{system_id}/members", get(rproxy)) .route("/v2/members", post(rproxy)) - .route("/v2/members/:member_id", get(rproxy)) - .route("/v2/members/:member_id", patch(rproxy)) - .route("/v2/members/:member_id", delete(rproxy)) + .route("/v2/members/{member_id}", get(rproxy)) + .route("/v2/members/{member_id}", patch(rproxy)) + .route("/v2/members/{member_id}", delete(rproxy)) - .route("/v2/systems/:system_id/groups", get(rproxy)) + .route("/v2/systems/{system_id}/groups", get(rproxy)) .route("/v2/groups", post(rproxy)) - .route("/v2/groups/:group_id", get(rproxy)) - .route("/v2/groups/:group_id", patch(rproxy)) - .route("/v2/groups/:group_id", delete(rproxy)) + .route("/v2/groups/{group_id}", get(rproxy)) + .route("/v2/groups/{group_id}", patch(rproxy)) + .route("/v2/groups/{group_id}", delete(rproxy)) - .route("/v2/groups/:group_id/members", get(rproxy)) - .route("/v2/groups/:group_id/members/add", post(rproxy)) - .route("/v2/groups/:group_id/members/remove", post(rproxy)) - .route("/v2/groups/:group_id/members/overwrite", post(rproxy)) + .route("/v2/groups/{group_id}/members", get(rproxy)) + .route("/v2/groups/{group_id}/members/add", post(rproxy)) + .route("/v2/groups/{group_id}/members/remove", post(rproxy)) + .route("/v2/groups/{group_id}/members/overwrite", post(rproxy)) - .route("/v2/members/:member_id/groups", get(rproxy)) - .route("/v2/members/:member_id/groups/add", post(rproxy)) - .route("/v2/members/:member_id/groups/remove", post(rproxy)) - .route("/v2/members/:member_id/groups/overwrite", post(rproxy)) + .route("/v2/members/{member_id}/groups", get(rproxy)) + .route("/v2/members/{member_id}/groups/add", post(rproxy)) + .route("/v2/members/{member_id}/groups/remove", post(rproxy)) + .route("/v2/members/{member_id}/groups/overwrite", post(rproxy)) - .route("/v2/systems/:system_id/switches", get(rproxy)) - .route("/v2/systems/:system_id/switches", post(rproxy)) - .route("/v2/systems/:system_id/fronters", get(rproxy)) + .route("/v2/systems/{system_id}/switches", get(rproxy)) + .route("/v2/systems/{system_id}/switches", post(rproxy)) + .route("/v2/systems/{system_id}/fronters", get(rproxy)) - .route("/v2/systems/:system_id/switches/:switch_id", get(rproxy)) - .route("/v2/systems/:system_id/switches/:switch_id", patch(rproxy)) - .route("/v2/systems/:system_id/switches/:switch_id/members", patch(rproxy)) - .route("/v2/systems/:system_id/switches/:switch_id", delete(rproxy)) + .route("/v2/systems/{system_id}/switches/{switch_id}", get(rproxy)) + .route("/v2/systems/{system_id}/switches/{switch_id}", patch(rproxy)) + .route("/v2/systems/{system_id}/switches/{switch_id}/members", patch(rproxy)) + .route("/v2/systems/{system_id}/switches/{switch_id}", delete(rproxy)) - .route("/v2/systems/:system_id/guilds/:guild_id", get(rproxy)) - .route("/v2/systems/:system_id/guilds/:guild_id", patch(rproxy)) + .route("/v2/systems/{system_id}/guilds/{guild_id}", get(rproxy)) + .route("/v2/systems/{system_id}/guilds/{guild_id}", patch(rproxy)) - .route("/v2/members/:member_id/guilds/:guild_id", get(rproxy)) - .route("/v2/members/:member_id/guilds/:guild_id", patch(rproxy)) + .route("/v2/members/{member_id}/guilds/{guild_id}", get(rproxy)) + .route("/v2/members/{member_id}/guilds/{guild_id}", patch(rproxy)) - .route("/v2/systems/:system_id/autoproxy", get(rproxy)) - .route("/v2/systems/:system_id/autoproxy", patch(rproxy)) + .route("/v2/systems/{system_id}/autoproxy", get(rproxy)) + .route("/v2/systems/{system_id}/autoproxy", patch(rproxy)) - .route("/v2/messages/:message_id", get(rproxy)) + .route("/v2/messages/{message_id}", get(rproxy)) .route("/private/bulk_privacy/member", post(rproxy)) .route("/private/bulk_privacy/group", post(rproxy)) @@ -111,16 +129,19 @@ fn router(ctx: ApiContext) -> Router { .route("/private/discord/shard_state", get(endpoints::private::discord_state)) .route("/private/stats", get(endpoints::private::meta)) - .route("/v2/systems/:system_id/oembed.json", get(rproxy)) - .route("/v2/members/:member_id/oembed.json", get(rproxy)) - .route("/v2/groups/:group_id/oembed.json", get(rproxy)) + .route("/v2/systems/{system_id}/oembed.json", get(rproxy)) + .route("/v2/members/{member_id}/oembed.json", get(rproxy)) + .route("/v2/groups/{group_id}/oembed.json", get(rproxy)) .layer(middleware::ratelimit::ratelimiter(middleware::ratelimit::do_request_ratelimited)) // this sucks - .layer(axum::middleware::from_fn_with_state(ctx.clone(), middleware::authnz)) - .layer(axum::middleware::from_fn(middleware::ignore_invalid_routes)) - .layer(axum::middleware::from_fn(middleware::cors)) - .layer(axum::middleware::from_fn(middleware::logger)) + .layer(axum::middleware::from_fn(middleware::ignore_invalid_routes::ignore_invalid_routes)) + .layer(axum::middleware::from_fn(middleware::logger::logger)) + + .layer(axum::middleware::from_fn_with_state(ctx.clone(), middleware::params::params)) + .layer(axum::middleware::from_fn_with_state(ctx.clone(), middleware::auth::auth)) + + .layer(axum::middleware::from_fn(middleware::cors::cors)) .layer(tower_http::catch_panic::CatchPanicLayer::custom(util::handle_panic)) .with_state(ctx) @@ -128,8 +149,8 @@ fn router(ctx: ApiContext) -> Router { .route("/", get(|| async { axum::response::Redirect::to("https://pluralkit.me/api") })) } -libpk::main!("api"); -async fn real_main() -> anyhow::Result<()> { +#[libpk::main] +async fn main() -> anyhow::Result<()> { let db = libpk::db::init_data_db().await?; let redis = libpk::db::init_redis().await?; diff --git a/crates/api/src/middleware/auth.rs b/crates/api/src/middleware/auth.rs new file mode 100644 index 00000000..08981c3a --- /dev/null +++ b/crates/api/src/middleware/auth.rs @@ -0,0 +1,62 @@ +use axum::{ + extract::{Request, State}, + http::StatusCode, + middleware::Next, + response::Response, +}; + +use tracing::error; + +use crate::auth::AuthState; +use crate::{util::json_err, ApiContext}; + +pub async fn auth(State(ctx): State, mut req: Request, next: Next) -> Response { + let mut authed_system_id: Option = None; + let mut authed_app_id: Option = None; + + // fetch user authorization + if let Some(system_auth_header) = req + .headers() + .get("authorization") + .map(|h| h.to_str().ok()) + .flatten() + && let Some(system_id) = + match libpk::db::repository::legacy_token_auth(&ctx.db, system_auth_header).await { + Ok(val) => val, + Err(err) => { + error!(?err, "failed to query authorization token in postgres"); + return json_err( + StatusCode::INTERNAL_SERVER_ERROR, + r#"{"message": "500: Internal Server Error", "code": 0}"#.to_string(), + ); + } + } + { + authed_system_id = Some(system_id); + } + + // fetch app authorization + // todo: actually fetch it from db + if let Some(app_auth_header) = req + .headers() + .get("x-pluralkit-app") + .map(|h| h.to_str().ok()) + .flatten() + && let Some(config_token2) = libpk::config + .api + .as_ref() + .expect("missing api config") + .temp_token2 + .as_ref() + // this is NOT how you validate tokens + // but this is low abuse risk so we're keeping it for now + && app_auth_header == config_token2 + { + authed_app_id = Some(1); + } + + req.extensions_mut() + .insert(AuthState::new(authed_system_id, authed_app_id)); + + next.run(req).await +} diff --git a/crates/api/src/middleware/authnz.rs b/crates/api/src/middleware/authnz.rs deleted file mode 100644 index 4544e6bf..00000000 --- a/crates/api/src/middleware/authnz.rs +++ /dev/null @@ -1,45 +0,0 @@ -use axum::{ - extract::{Request, State}, - http::HeaderValue, - middleware::Next, - response::Response, -}; -use tracing::error; - -use crate::ApiContext; - -use super::logger::DID_AUTHENTICATE_HEADER; - -pub async fn authnz(State(ctx): State, mut request: Request, next: Next) -> Response { - let headers = request.headers_mut(); - headers.remove("x-pluralkit-systemid"); - let auth_header = headers - .get("authorization") - .map(|h| h.to_str().ok()) - .flatten(); - let mut authenticated = false; - if let Some(auth_header) = auth_header { - if let Some(system_id) = - match libpk::db::repository::legacy_token_auth(&ctx.db, auth_header).await { - Ok(val) => val, - Err(err) => { - error!(?err, "failed to query authorization token in postgres"); - None - } - } - { - headers.append( - "x-pluralkit-systemid", - HeaderValue::from_str(format!("{system_id}").as_str()).unwrap(), - ); - authenticated = true; - } - } - let mut response = next.run(request).await; - if authenticated { - response - .headers_mut() - .insert(DID_AUTHENTICATE_HEADER, HeaderValue::from_static("1")); - } - response -} diff --git a/crates/api/src/middleware/logger.rs b/crates/api/src/middleware/logger.rs index 020de2e2..38e45e2c 100644 --- a/crates/api/src/middleware/logger.rs +++ b/crates/api/src/middleware/logger.rs @@ -4,27 +4,30 @@ use axum::{extract::MatchedPath, extract::Request, middleware::Next, response::R use metrics::{counter, histogram}; use tracing::{info, span, warn, Instrument, Level}; -use crate::util::header_or_unknown; +use crate::{auth::AuthState, util::header_or_unknown}; // log any requests that take longer than 2 seconds // todo: change as necessary const MIN_LOG_TIME: u128 = 2_000; -pub const DID_AUTHENTICATE_HEADER: &'static str = "x-pluralkit-didauthenticate"; - pub async fn logger(request: Request, next: Next) -> Response { let method = request.method().clone(); let remote_ip = header_or_unknown(request.headers().get("X-PluralKit-Client-IP")); let user_agent = header_or_unknown(request.headers().get("User-Agent")); - let endpoint = request - .extensions() + let extensions = request.extensions().clone(); + + let endpoint = extensions .get::() .cloned() .map(|v| v.as_str().to_string()) .unwrap_or("unknown".to_string()); + let auth = extensions + .get::() + .expect("should always have AuthState"); + let uri = request.uri().clone(); let request_span = span!( @@ -37,25 +40,26 @@ pub async fn logger(request: Request, next: Next) -> Response { ); let start = Instant::now(); - let mut response = next.run(request).instrument(request_span).await; + let response = next.run(request).instrument(request_span).await; let elapsed = start.elapsed().as_millis(); - let authenticated = { - let headers = response.headers_mut(); - if headers.contains_key(DID_AUTHENTICATE_HEADER) { - headers.remove(DID_AUTHENTICATE_HEADER); - true - } else { - false - } - }; + let system_id = auth + .system_id() + .map(|v| v.to_string()) + .unwrap_or("none".to_string()); + + let app_id = auth + .app_id() + .map(|v| v.to_string()) + .unwrap_or("none".to_string()); counter!( "pluralkit_api_requests", "method" => method.to_string(), "endpoint" => endpoint.clone(), "status" => response.status().to_string(), - "authenticated" => authenticated.to_string(), + "system_id" => system_id.to_string(), + "app_id" => app_id.to_string(), ) .increment(1); histogram!( @@ -63,7 +67,8 @@ pub async fn logger(request: Request, next: Next) -> Response { "method" => method.to_string(), "endpoint" => endpoint.clone(), "status" => response.status().to_string(), - "authenticated" => authenticated.to_string(), + "system_id" => system_id.to_string(), + "app_id" => app_id.to_string(), ) .record(elapsed as f64 / 1_000_f64); @@ -81,7 +86,8 @@ pub async fn logger(request: Request, next: Next) -> Response { "method" => method.to_string(), "endpoint" => endpoint.clone(), "status" => response.status().to_string(), - "authenticated" => authenticated.to_string(), + "system_id" => system_id.to_string(), + "app_id" => app_id.to_string(), ) .increment(1); diff --git a/crates/api/src/middleware/mod.rs b/crates/api/src/middleware/mod.rs index dbdfba13..5c2b04dc 100644 --- a/crates/api/src/middleware/mod.rs +++ b/crates/api/src/middleware/mod.rs @@ -1,13 +1,6 @@ -mod cors; -pub use cors::cors; - -mod logger; -pub use logger::logger; - -mod ignore_invalid_routes; -pub use ignore_invalid_routes::ignore_invalid_routes; - +pub mod auth; +pub mod cors; +pub mod ignore_invalid_routes; +pub mod logger; +pub mod params; pub mod ratelimit; - -mod authnz; -pub use authnz::authnz; diff --git a/crates/api/src/middleware/params.rs b/crates/api/src/middleware/params.rs new file mode 100644 index 00000000..06a76f64 --- /dev/null +++ b/crates/api/src/middleware/params.rs @@ -0,0 +1,139 @@ +use axum::{ + extract::{Request, State}, + http::StatusCode, + middleware::Next, + response::Response, + routing::url_params::UrlParams, +}; + +use sqlx::{types::Uuid, Postgres}; +use tracing::error; + +use crate::auth::AuthState; +use crate::{util::json_err, ApiContext}; +use pluralkit_models::PKSystem; + +// move this somewhere else +fn parse_hid(hid: &str) -> String { + if hid.len() > 7 || hid.len() < 5 { + hid.to_string() + } else { + hid.to_lowercase().replace("-", "") + } +} + +pub async fn params(State(ctx): State, mut req: Request, next: Next) -> Response { + let pms = match req.extensions().get::() { + None => Vec::new(), + Some(UrlParams::Params(pms)) => pms.clone(), + _ => { + return json_err( + StatusCode::BAD_REQUEST, + r#"{"message":"400: Bad Request","code": 0}"#.to_string(), + ) + .into() + } + }; + + for (key, value) in pms { + match key.as_ref() { + "system_id" => match value.as_str() { + "@me" => { + let Some(system_id) = req + .extensions() + .get::() + .expect("missing auth state") + .system_id() + else { + return json_err( + StatusCode::UNAUTHORIZED, + r#"{"message":"401: Missing or invalid Authorization header","code": 0}"#.to_string(), + ) + .into(); + }; + + match sqlx::query_as::( + "select * from systems where id = $1", + ) + .bind(system_id) + .fetch_optional(&ctx.db) + .await + { + Ok(Some(system)) => { + req.extensions_mut().insert(system); + } + Ok(None) => { + error!( + ?system_id, + "could not find previously authenticated system in db" + ); + return json_err( + StatusCode::INTERNAL_SERVER_ERROR, + r#"{"message": "500: Internal Server Error", "code": 0}"# + .to_string(), + ); + } + Err(err) => { + error!( + ?err, + "failed to query previously authenticated system in db" + ); + return json_err( + StatusCode::INTERNAL_SERVER_ERROR, + r#"{"message": "500: Internal Server Error", "code": 0}"# + .to_string(), + ); + } + } + } + id => { + println!("a {id}"); + match match Uuid::parse_str(id) { + Ok(uuid) => sqlx::query_as::( + "select * from systems where uuid = $1", + ) + .bind(uuid), + Err(_) => match id.parse::() { + Ok(parsed) => sqlx::query_as::( + "select * from systems where id = (select system from accounts where uid = $1)" + ) + .bind(parsed), + Err(_) => sqlx::query_as::( + "select * from systems where hid = $1", + ) + .bind(parse_hid(id)) + }, + } + .fetch_optional(&ctx.db) + .await + { + Ok(Some(system)) => { + req.extensions_mut().insert(system); + } + Ok(None) => { + return json_err( + StatusCode::NOT_FOUND, + r#"{"message":"System not found.","code":20001}"#.to_string(), + ) + } + Err(err) => { + error!(?err, ?id, "failed to query system from path in db"); + return json_err( + StatusCode::INTERNAL_SERVER_ERROR, + r#"{"message": "500: Internal Server Error", "code": 0}"# + .to_string(), + ); + } + } + } + }, + "member_id" => {} + "group_id" => {} + "switch_id" => {} + "guild_id" => {} + _ => {} + } + } + + next.run(req).await +} diff --git a/crates/api/src/middleware/ratelimit.rs b/crates/api/src/middleware/ratelimit.rs index e7bb1dd0..f4a63f7e 100644 --- a/crates/api/src/middleware/ratelimit.rs +++ b/crates/api/src/middleware/ratelimit.rs @@ -10,7 +10,10 @@ use fred::{clients::RedisPool, interfaces::ClientLike, prelude::LuaInterface, ut use metrics::counter; use tracing::{debug, error, info, warn}; -use crate::util::{header_or_unknown, json_err}; +use crate::{ + auth::AuthState, + util::{header_or_unknown, json_err}, +}; const LUA_SCRIPT: &str = include_str!("ratelimit.lua"); @@ -50,7 +53,7 @@ pub fn ratelimiter(f: F) -> FromFnLayer, T> { .await { Ok(_) => info!("connected to redis for request rate limiting"), - Err(err) => error!("could not load redis script: {}", err), + Err(error) => error!(?error, "could not load redis script"), } } else { error!("could not wait for connection to load redis script!"); @@ -103,37 +106,28 @@ pub async fn do_request_ratelimited( if let Some(redis) = redis { let headers = request.headers().clone(); let source_ip = header_or_unknown(headers.get("X-PluralKit-Client-IP")); - let authenticated_system_id = header_or_unknown(headers.get("x-pluralkit-systemid")); - // https://github.com/rust-lang/rust/issues/53667 - let is_temp_token2 = if let Some(header) = request.headers().clone().get("X-PluralKit-App") - { - if let Some(token2) = &libpk::config - .api - .as_ref() - .expect("missing api config") - .temp_token2 - { - if header.to_str().unwrap_or("invalid") == token2 { - true - } else { - false - } - } else { - false - } - } else { - false - }; + let extensions = request.extensions().clone(); - let endpoint = request - .extensions() + let endpoint = extensions .get::() .cloned() .map(|v| v.as_str().to_string()) .unwrap_or("unknown".to_string()); - let rlimit = if is_temp_token2 { + let auth = extensions + .get::() + .expect("should always have AuthState"); + + // looks like this chooses the tokens/sec by app_id or endpoint + // then chooses the key by system_id or source_ip + // todo: key should probably be chosen by app_id when it's present + // todo: make x-ratelimit-scope actually meaningful + + // hack: for now, we only have one "registered app", so we hardcode the app id + let rlimit = if let Some(app_id) = auth.app_id() + && app_id == 1 + { RatelimitType::TempCustom } else if endpoint == "/v2/messages/:message_id" { RatelimitType::Message @@ -145,12 +139,12 @@ pub async fn do_request_ratelimited( let rl_key = format!( "{}:{}", - if authenticated_system_id != "unknown" + if let Some(system_id) = auth.system_id() && matches!(rlimit, RatelimitType::GenericUpdate) { - authenticated_system_id + system_id.to_string() } else { - source_ip + source_ip.to_string() }, rlimit.key() ); @@ -224,8 +218,8 @@ pub async fn do_request_ratelimited( return response; } - Err(err) => { - tracing::error!("error getting ratelimit info: {}", err); + Err(error) => { + tracing::error!(?error, "error getting ratelimit info"); return json_err( StatusCode::INTERNAL_SERVER_ERROR, r#"{"message": "500: internal server error", "code": 0}"#.to_string(), diff --git a/crates/api/src/util.rs b/crates/api/src/util.rs index 03121659..35a5bf0d 100644 --- a/crates/api/src/util.rs +++ b/crates/api/src/util.rs @@ -11,7 +11,7 @@ pub fn header_or_unknown(header: Option<&HeaderValue>) -> &str { match value.to_str() { Ok(v) => v, Err(err) => { - error!("failed to parse header value {:#?}: {:#?}", value, err); + error!(?err, ?value, "failed to parse header value"); "failed to parse" } } @@ -34,11 +34,7 @@ where .unwrap(), ), None => { - error!( - "error in handler {}: {:#?}", - std::any::type_name::(), - error - ); + error!(?error, "error in handler {}", std::any::type_name::(),); json_err( StatusCode::INTERNAL_SERVER_ERROR, r#"{"message": "500: Internal Server Error", "code": 0}"#.to_string(), @@ -48,14 +44,15 @@ where } } -pub fn handle_panic(err: Box) -> axum::response::Response { - error!("caught panic from handler: {:#?}", err); +pub fn handle_panic(error: Box) -> axum::response::Response { + error!(?error, "caught panic from handler"); json_err( StatusCode::INTERNAL_SERVER_ERROR, r#"{"message": "500: Internal Server Error", "code": 0}"#.to_string(), ) } +// todo: make 500 not duplicated pub fn json_err(code: StatusCode, text: String) -> axum::response::Response { let mut response = (code, text).into_response(); let headers = response.headers_mut(); diff --git a/crates/avatars/src/cleanup.rs b/crates/avatars/src/cleanup.rs index 0fb815e3..48b25f98 100644 --- a/crates/avatars/src/cleanup.rs +++ b/crates/avatars/src/cleanup.rs @@ -4,8 +4,8 @@ use sqlx::prelude::FromRow; use std::{sync::Arc, time::Duration}; use tracing::{error, info}; -libpk::main!("avatar_cleanup"); -async fn real_main() -> anyhow::Result<()> { +#[libpk::main] +async fn main() -> anyhow::Result<()> { let config = libpk::config .avatars .as_ref() @@ -13,7 +13,7 @@ async fn real_main() -> anyhow::Result<()> { let bucket = { let region = s3::Region::Custom { - region: "s3".to_string(), + region: "auto".to_string(), endpoint: config.s3.endpoint.to_string(), }; @@ -38,8 +38,8 @@ async fn real_main() -> anyhow::Result<()> { tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; match cleanup_job(pool.clone(), bucket.clone()).await { Ok(()) => {} - Err(err) => { - error!("failed to run avatar cleanup job: {}", err); + Err(error) => { + error!(?error, "failed to run avatar cleanup job"); // sentry } } @@ -55,9 +55,10 @@ async fn cleanup_job(pool: sqlx::PgPool, bucket: Arc) -> anyhow::Res let mut tx = pool.begin().await?; let image_id: Option = sqlx::query_as( + // no timestamp checking here + // images are only added to the table after 24h r#" select id from image_cleanup_jobs - where ts < now() - interval '1 day' for update skip locked limit 1;"#, ) .fetch_optional(&mut *tx) @@ -72,6 +73,7 @@ async fn cleanup_job(pool: sqlx::PgPool, bucket: Arc) -> anyhow::Res let image_data = libpk::db::repository::avatars::get_by_id(&pool, image_id.clone()).await?; if image_data.is_none() { + // unsure how this can happen? there is a FK reference info!("image {image_id} was already deleted, skipping"); sqlx::query("delete from image_cleanup_jobs where id = $1") .bind(image_id) diff --git a/crates/avatars/src/main.rs b/crates/avatars/src/main.rs index 3b621f52..c8399086 100644 --- a/crates/avatars/src/main.rs +++ b/crates/avatars/src/main.rs @@ -93,7 +93,7 @@ async fn pull( ) -> Result, PKAvatarError> { let parsed = pull::parse_url(&req.url) // parsing beforehand to "normalize" .map_err(|_| PKAvatarError::InvalidCdnUrl)?; - if !req.force { + if !(req.force || req.url.contains("https://serve.apparyllis.com/")) { if let Some(existing) = db::get_by_attachment_id(&state.pool, parsed.attachment_id).await? { // remove any pending image cleanup db::remove_deletion_queue(&state.pool, parsed.attachment_id).await?; @@ -170,8 +170,8 @@ pub struct AppState { config: Arc, } -libpk::main!("avatars"); -async fn real_main() -> anyhow::Result<()> { +#[libpk::main] +async fn main() -> anyhow::Result<()> { let config = libpk::config .avatars .as_ref() @@ -179,7 +179,7 @@ async fn real_main() -> anyhow::Result<()> { let bucket = { let region = s3::Region::Custom { - region: "s3".to_string(), + region: "auto".to_string(), endpoint: config.s3.endpoint.to_string(), }; @@ -232,26 +232,11 @@ async fn real_main() -> anyhow::Result<()> { Ok(()) } -struct AppError(anyhow::Error); - #[derive(Serialize)] struct ErrorResponse { error: String, } -impl IntoResponse for AppError { - fn into_response(self) -> Response { - error!("error handling request: {}", self.0); - ( - StatusCode::INTERNAL_SERVER_ERROR, - Json(ErrorResponse { - error: self.0.to_string(), - }), - ) - .into_response() - } -} - impl IntoResponse for PKAvatarError { fn into_response(self) -> Response { let status_code = match self { @@ -278,12 +263,3 @@ impl IntoResponse for PKAvatarError { .into_response() } } - -impl From for AppError -where - E: Into, -{ - fn from(err: E) -> Self { - Self(err.into()) - } -} diff --git a/crates/avatars/src/migrate.rs b/crates/avatars/src/migrate.rs index 87a5747d..8c72267c 100644 --- a/crates/avatars/src/migrate.rs +++ b/crates/avatars/src/migrate.rs @@ -129,9 +129,9 @@ pub async fn worker(worker_id: u32, state: Arc) { Ok(()) => {} Err(e) => { error!( - "error in migrate worker {}: {}", - worker_id, - e.source().unwrap_or(&e) + error = e.source().unwrap_or(&e) + ?worker_id, + "error in migrate worker", ); tokio::time::sleep(Duration::from_secs(5)).await; } diff --git a/crates/avatars/src/process.rs b/crates/avatars/src/process.rs index 61eaaef2..024f40de 100644 --- a/crates/avatars/src/process.rs +++ b/crates/avatars/src/process.rs @@ -84,7 +84,7 @@ pub fn process(data: &[u8], kind: ImageKind) -> Result anyhow::Result { match (url.scheme(), url.domain()) { ("https", Some("media.discordapp.net" | "cdn.discordapp.com")) => {} + ("https", Some("serve.apparyllis.com")) => { + return Ok(ParsedUrl { + channel_id: 0, + attachment_id: 0, + filename: "".to_string(), + full_url: url.to_string(), + }) + } _ => anyhow::bail!("not a discord cdn url"), } diff --git a/crates/dispatch/Cargo.toml b/crates/dispatch/Cargo.toml index 81e35811..c76856d1 100644 --- a/crates/dispatch/Cargo.toml +++ b/crates/dispatch/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] anyhow = { workspace = true } axum = { workspace = true } +libpk = { path = "../libpk" } reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/dispatch/src/main.rs b/crates/dispatch/src/main.rs index 51c73577..6570cf19 100644 --- a/crates/dispatch/src/main.rs +++ b/crates/dispatch/src/main.rs @@ -19,17 +19,8 @@ use axum::{extract::State, http::Uri, routing::post, Json, Router}; mod logger; -// this package does not currently use libpk - -#[tokio::main] +#[libpk::main] async fn main() -> anyhow::Result<()> { - tracing_subscriber::fmt() - .json() - .with_env_filter(EnvFilter::from_default_env()) - .init(); - - info!("hello world"); - let address = std::env::var("DNS_UPSTREAM").unwrap().parse().unwrap(); let stream = UdpClientStream::::with_timeout(address, Duration::from_secs(3)); let (client, bg) = AsyncClient::connect(stream).await?; @@ -86,11 +77,11 @@ async fn dispatch( let uri = match req.url.parse::() { Ok(v) if v.scheme_str() == Some("https") && v.host().is_some() => v, Err(error) => { - error!(?error, "failed to parse uri {}", req.url); + error!(?error, uri = req.url, "failed to parse uri"); return DispatchResponse::BadData.to_string(); } _ => { - error!("uri {} is invalid", req.url); + error!(uri = req.url, "uri is invalid"); return DispatchResponse::BadData.to_string(); } }; diff --git a/crates/gateway/Cargo.toml b/crates/gateway/Cargo.toml index bde62f22..c707b29b 100644 --- a/crates/gateway/Cargo.toml +++ b/crates/gateway/Cargo.toml @@ -13,8 +13,9 @@ futures = { workspace = true } lazy_static = { workspace = true } libpk = { path = "../libpk" } metrics = { workspace = true } +reqwest = { workspace = true } +serde = { workspace = true } serde_json = { workspace = true } -signal-hook = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } diff --git a/crates/gateway/src/cache_api.rs b/crates/gateway/src/api.rs similarity index 68% rename from crates/gateway/src/cache_api.rs rename to crates/gateway/src/api.rs index c73424f9..f8c3f556 100644 --- a/crates/gateway/src/cache_api.rs +++ b/crates/gateway/src/api.rs @@ -1,19 +1,24 @@ use axum::{ - extract::{Path, State}, + extract::{ConnectInfo, Path, State}, http::StatusCode, response::{IntoResponse, Response}, - routing::get, + routing::{delete, get, post}, Router, }; +use libpk::runtime_config::RuntimeConfig; use serde_json::{json, to_string}; use tracing::{error, info}; -use twilight_model::id::Id; +use twilight_model::id::{marker::ChannelMarker, Id}; -use crate::discord::{ - cache::{dm_channel, DiscordCache, DM_PERMISSIONS}, - gateway::cluster_config, +use crate::{ + discord::{ + cache::{dm_channel, DiscordCache, DM_PERMISSIONS}, + gateway::cluster_config, + shard_state::ShardStateManager, + }, + event_awaiter::{AwaitEventRequest, EventAwaiter}, }; -use std::sync::Arc; +use std::{net::SocketAddr, sync::Arc}; fn status_code(code: StatusCode, body: String) -> Response { (code, body).into_response() @@ -21,10 +26,15 @@ fn status_code(code: StatusCode, body: String) -> Response { // this function is manually formatted for easier legibility of route_services #[rustfmt::skip] -pub async fn run_server(cache: Arc) -> anyhow::Result<()> { +pub async fn run_server(cache: Arc, shard_state: Arc, runtime_config: Arc, awaiter: Arc) -> anyhow::Result<()> { + // hacky fix for `move` + let runtime_config_for_post = runtime_config.clone(); + let runtime_config_for_delete = runtime_config.clone(); + let awaiter_for_clear = awaiter.clone(); + let app = Router::new() .route( - "/guilds/:guild_id", + "/guilds/{guild_id}", get(|State(cache): State>, Path(guild_id): Path| async move { match cache.guild(Id::new(guild_id)) { Some(guild) => status_code(StatusCode::FOUND, to_string(&guild).unwrap()), @@ -33,7 +43,7 @@ pub async fn run_server(cache: Arc) -> anyhow::Result<()> { }), ) .route( - "/guilds/:guild_id/members/@me", + "/guilds/{guild_id}/members/@me", get(|State(cache): State>, Path(guild_id): Path| async move { match cache.0.member(Id::new(guild_id), libpk::config.discord.as_ref().expect("missing discord config").client_id) { Some(member) => status_code(StatusCode::FOUND, to_string(member.value()).unwrap()), @@ -42,7 +52,7 @@ pub async fn run_server(cache: Arc) -> anyhow::Result<()> { }), ) .route( - "/guilds/:guild_id/permissions/@me", + "/guilds/{guild_id}/permissions/@me", get(|State(cache): State>, Path(guild_id): Path| async move { match cache.guild_permissions(Id::new(guild_id), libpk::config.discord.as_ref().expect("missing discord config").client_id).await { Ok(val) => { @@ -56,7 +66,7 @@ pub async fn run_server(cache: Arc) -> anyhow::Result<()> { }), ) .route( - "/guilds/:guild_id/permissions/:user_id", + "/guilds/{guild_id}/permissions/{user_id}", get(|State(cache): State>, Path((guild_id, user_id)): Path<(u64, u64)>| async move { match cache.guild_permissions(Id::new(guild_id), Id::new(user_id)).await { Ok(val) => status_code(StatusCode::FOUND, to_string(&val.bits()).unwrap()), @@ -69,7 +79,7 @@ pub async fn run_server(cache: Arc) -> anyhow::Result<()> { ) .route( - "/guilds/:guild_id/channels", + "/guilds/{guild_id}/channels", get(|State(cache): State>, Path(guild_id): Path| async move { let channel_ids = match cache.0.guild_channels(Id::new(guild_id)) { Some(channels) => channels.to_owned(), @@ -95,7 +105,7 @@ pub async fn run_server(cache: Arc) -> anyhow::Result<()> { }) ) .route( - "/guilds/:guild_id/channels/:channel_id", + "/guilds/{guild_id}/channels/{channel_id}", get(|State(cache): State>, Path((guild_id, channel_id)): Path<(u64, u64)>| async move { if guild_id == 0 { return status_code(StatusCode::FOUND, to_string(&dm_channel(Id::new(channel_id))).unwrap()); @@ -107,7 +117,7 @@ pub async fn run_server(cache: Arc) -> anyhow::Result<()> { }) ) .route( - "/guilds/:guild_id/channels/:channel_id/permissions/@me", + "/guilds/{guild_id}/channels/{channel_id}/permissions/@me", get(|State(cache): State>, Path((guild_id, channel_id)): Path<(u64, u64)>| async move { if guild_id == 0 { return status_code(StatusCode::FOUND, to_string(&*DM_PERMISSIONS).unwrap()); @@ -122,16 +132,19 @@ pub async fn run_server(cache: Arc) -> anyhow::Result<()> { }), ) .route( - "/guilds/:guild_id/channels/:channel_id/permissions/:user_id", + "/guilds/{guild_id}/channels/{channel_id}/permissions/{user_id}", get(|| async { "todo" }), ) .route( - "/guilds/:guild_id/channels/:channel_id/last_message", - get(|| async { status_code(StatusCode::NOT_IMPLEMENTED, "".to_string()) }), + "/guilds/{guild_id}/channels/{channel_id}/last_message", + get(|State(cache): State>, Path((_guild_id, channel_id)): Path<(u64, Id)>| async move { + let lm = cache.get_last_message(channel_id).await; + status_code(StatusCode::FOUND, to_string(&lm).unwrap()) + }), ) .route( - "/guilds/:guild_id/roles", + "/guilds/{guild_id}/roles", get(|State(cache): State>, Path(guild_id): Path| async move { let role_ids = match cache.0.guild_roles(Id::new(guild_id)) { Some(roles) => roles.to_owned(), @@ -171,13 +184,45 @@ pub async fn run_server(cache: Arc) -> anyhow::Result<()> { status_code(StatusCode::FOUND, to_string(&stats).unwrap()) })) + .route("/runtime_config", get(|| async move { + status_code(StatusCode::FOUND, to_string(&runtime_config.get_all().await).unwrap()) + })) + .route("/runtime_config/{key}", post(|Path(key): Path, body: String| async move { + let runtime_config = runtime_config_for_post; + runtime_config.set(key, body).await.expect("failed to update runtime config"); + status_code(StatusCode::FOUND, to_string(&runtime_config.get_all().await).unwrap()) + })) + .route("/runtime_config/{key}", delete(|Path(key): Path| async move { + let runtime_config = runtime_config_for_delete; + runtime_config.delete(key).await.expect("failed to update runtime config"); + status_code(StatusCode::FOUND, to_string(&runtime_config.get_all().await).unwrap()) + })) + + .route("/await_event", post(|ConnectInfo(addr): ConnectInfo, body: String| async move { + info!("got request: {body} from: {addr}"); + let Ok(req) = serde_json::from_str::(&body) else { + return status_code(StatusCode::BAD_REQUEST, "".to_string()); + }; + + awaiter.handle_request(req, addr).await; + status_code(StatusCode::NO_CONTENT, "".to_string()) + })) + .route("/clear_awaiter", post(|| async move { + awaiter_for_clear.clear().await; + status_code(StatusCode::NO_CONTENT, "".to_string()) + })) + + .route("/shard_status", get(|| async move { + status_code(StatusCode::FOUND, to_string(&shard_state.get().await).unwrap()) + })) + .layer(axum::middleware::from_fn(crate::logger::logger)) .with_state(cache); let addr: &str = libpk::config.discord.as_ref().expect("missing discord config").cache_api_addr.as_ref(); let listener = tokio::net::TcpListener::bind(addr).await?; info!("listening on {}", addr); - axum::serve(listener, app).await?; + axum::serve(listener, app.into_make_service_with_connect_info::()).await?; Ok(()) } diff --git a/crates/gateway/src/discord/cache.rs b/crates/gateway/src/discord/cache.rs index b4a81664..e0a4aacf 100644 --- a/crates/gateway/src/discord/cache.rs +++ b/crates/gateway/src/discord/cache.rs @@ -1,6 +1,7 @@ use anyhow::format_err; use lazy_static::lazy_static; -use std::sync::Arc; +use serde::Serialize; +use std::{collections::HashMap, sync::Arc}; use tokio::sync::RwLock; use twilight_cache_inmemory::{ model::CachedMember, @@ -8,11 +9,12 @@ use twilight_cache_inmemory::{ traits::CacheableChannel, InMemoryCache, ResourceType, }; +use twilight_gateway::Event; use twilight_model::{ channel::{Channel, ChannelType}, guild::{Guild, Member, Permissions}, id::{ - marker::{ChannelMarker, GuildMarker, UserMarker}, + marker::{ChannelMarker, GuildMarker, MessageMarker, UserMarker}, Id, }, }; @@ -123,16 +125,134 @@ pub fn new() -> DiscordCache { .build(), ); - DiscordCache(cache, client, RwLock::new(Vec::new())) + DiscordCache( + cache, + client, + RwLock::new(Vec::new()), + RwLock::new(HashMap::new()), + ) +} + +#[derive(Clone, Serialize)] +pub struct CachedMessage { + id: Id, + referenced_message: Option>, + author_username: String, +} + +#[derive(Clone, Serialize)] +pub struct LastMessageCacheEntry { + pub current: CachedMessage, + pub previous: Option, } pub struct DiscordCache( pub Arc, pub Arc, pub RwLock>, + pub RwLock, LastMessageCacheEntry>>, ); impl DiscordCache { + pub async fn get_last_message( + &self, + channel: Id, + ) -> Option { + self.3.read().await.get(&channel).cloned() + } + + pub async fn update(&self, event: &twilight_gateway::Event) { + self.0.update(event); + + match event { + Event::MessageCreate(m) => match self.3.write().await.entry(m.channel_id) { + std::collections::hash_map::Entry::Occupied(mut e) => { + let cur = e.get(); + e.insert(LastMessageCacheEntry { + current: CachedMessage { + id: m.id, + referenced_message: m.referenced_message.as_ref().map(|v| v.id), + author_username: m.author.name.clone(), + }, + previous: Some(cur.current.clone()), + }); + } + std::collections::hash_map::Entry::Vacant(e) => { + e.insert(LastMessageCacheEntry { + current: CachedMessage { + id: m.id, + referenced_message: m.referenced_message.as_ref().map(|v| v.id), + author_username: m.author.name.clone(), + }, + previous: None, + }); + } + }, + Event::MessageDelete(m) => { + self.handle_message_deletion(m.channel_id, vec![m.id]).await; + } + Event::MessageDeleteBulk(m) => { + self.handle_message_deletion(m.channel_id, m.ids.clone()) + .await; + } + _ => {} + }; + } + + async fn handle_message_deletion( + &self, + channel_id: Id, + mids: Vec>, + ) { + let mut lm = self.3.write().await; + + let Some(entry) = lm.get(&channel_id) else { + return; + }; + + let mut entry = entry.clone(); + + // if none of the deleted messages are relevant, just return + if !mids.contains(&entry.current.id) + && entry + .previous + .clone() + .map(|v| !mids.contains(&v.id)) + .unwrap_or(false) + { + return; + } + + // remove "previous" entry if it was deleted + if let Some(prev) = entry.previous.clone() + && mids.contains(&prev.id) + { + entry.previous = None; + } + + // set "current" entry to "previous" if current entry was deleted + // (if the "previous" entry still exists, it was not deleted) + if let Some(prev) = entry.previous.clone() + && mids.contains(&entry.current.id) + { + entry.current = prev; + entry.previous = None; + } + + // if the current entry was already deleted, but previous wasn't, + // we would've set current to previous + // so if current is deleted this means both current and previous have + // been deleted + // so just drop the cache entry here + if mids.contains(&entry.current.id) && entry.previous.is_none() { + lm.remove(&channel_id); + return; + } + + // ok, update the entry + lm.insert(channel_id, entry.clone()); + } + pub async fn guild_permissions( &self, guild_id: Id, @@ -356,12 +476,14 @@ impl DiscordCache { system_channel_flags: guild.system_channel_flags(), system_channel_id: guild.system_channel_id(), threads: vec![], - unavailable: false, + unavailable: Some(false), vanity_url_code: guild.vanity_url_code().map(ToString::to_string), verification_level: guild.verification_level(), voice_states: vec![], widget_channel_id: guild.widget_channel_id(), widget_enabled: guild.widget_enabled(), + guild_scheduled_events: guild.guild_scheduled_events().to_vec(), + max_stage_video_channel_users: guild.max_stage_video_channel_users(), } }) } diff --git a/crates/gateway/src/discord/gateway.rs b/crates/gateway/src/discord/gateway.rs index 6aedca23..92acdeaa 100644 --- a/crates/gateway/src/discord/gateway.rs +++ b/crates/gateway/src/discord/gateway.rs @@ -1,7 +1,9 @@ +use anyhow::anyhow; use futures::StreamExt; -use libpk::_config::ClusterSettings; +use libpk::{_config::ClusterSettings, runtime_config::RuntimeConfig, state::ShardStateEvent}; use metrics::counter; -use std::sync::{mpsc::Sender, Arc}; +use std::sync::Arc; +use tokio::sync::mpsc::Sender; use tracing::{error, info, warn}; use twilight_gateway::{ create_iterator, ConfigBuilder, Event, EventTypeFlags, Message, Shard, ShardId, @@ -12,9 +14,12 @@ use twilight_model::gateway::{ Intents, }; -use crate::discord::identify_queue::{self, RedisQueue}; +use crate::{ + discord::identify_queue::{self, RedisQueue}, + RUNTIME_CONFIG_KEY_EVENT_TARGET, +}; -use super::{cache::DiscordCache, shard_state::ShardStateManager}; +use super::cache::DiscordCache; pub fn cluster_config() -> ClusterSettings { libpk::config @@ -44,6 +49,12 @@ pub fn create_shards(redis: fred::clients::RedisPool) -> anyhow::Result anyhow::Result anyhow::Result anyhow::Result, - _tx: Sender<(ShardId, String)>, - shard_state: ShardStateManager, + tx: Sender<(ShardId, Event, String)>, + tx_state: Sender<(ShardId, ShardStateEvent, Option, Option)>, cache: Arc, + runtime_config: Arc, ) { // let _span = info_span!("shard_runner", shard_id = shard.id().number()).entered(); let shard_id = shard.id().number(); + let our_user_id = libpk::config + .discord + .as_ref() + .expect("missing discord config") + .client_id; + info!("waiting for events"); while let Some(item) = shard.next().await { let raw_event = match item { @@ -105,7 +131,9 @@ pub async fn runner( ) .increment(1); - if let Err(error) = shard_state.socket_closed(shard_id).await { + if let Err(error) = + tx_state.try_send((shard.id(), ShardStateEvent::Closed, None, None)) + { error!("failed to update shard state for socket closure: {error}"); } @@ -127,7 +155,7 @@ pub async fn runner( continue; } Err(error) => { - error!("shard {shard_id} failed to parse gateway event: {error}"); + error!(?error, ?shard_id, "failed to parse gateway event"); continue; } }; @@ -147,14 +175,31 @@ pub async fn runner( .increment(1); // update shard state and discord cache - if let Err(error) = shard_state.handle_event(shard_id, event.clone()).await { - tracing::error!(?error, "error updating redis state"); + if matches!(event, Event::Ready(_)) || matches!(event, Event::Resumed) { + if let Err(error) = tx_state.try_send(( + shard.id(), + ShardStateEvent::Other, + Some(event.clone()), + None, + )) { + tracing::error!(?error, "error updating shard state"); + } } // need to do heartbeat separately, to get the latency + let latency_num = shard + .latency() + .recent() + .first() + .map_or_else(|| 0, |d| d.as_millis()) as i32; if let Event::GatewayHeartbeatAck = event - && let Err(error) = shard_state.heartbeated(shard_id, shard.latency()).await + && let Err(error) = tx_state.try_send(( + shard.id(), + ShardStateEvent::Heartbeat, + Some(event.clone()), + Some(latency_num), + )) { - tracing::error!(?error, "error updating redis state for latency"); + tracing::error!(?error, "error updating shard state for latency"); } if let Event::Ready(_) = event { @@ -162,10 +207,28 @@ pub async fn runner( cache.2.write().await.push(shard_id); } } - cache.0.update(&event); + cache.update(&event).await; // okay, we've handled the event internally, let's send it to consumers - // tx.send((shard.id(), raw_event)).unwrap(); + + // some basic filtering here is useful + // we can't use if matching using the | operator, so anything matched does nothing + // and the default match skips the next block (continues to the next event) + match event { + Event::InteractionCreate(_) => {} + Event::MessageCreate(ref m) if m.author.id != our_user_id => {} + Event::MessageUpdate(ref m) if m.author.id != our_user_id && !m.author.bot => {} + Event::MessageDelete(_) => {} + Event::MessageDeleteBulk(_) => {} + Event::ReactionAdd(ref r) if r.user_id != our_user_id => {} + _ => { + continue; + } + } + + if runtime_config.exists(RUNTIME_CONFIG_KEY_EVENT_TARGET).await { + tx.send((shard.id(), event, raw_event)).await.unwrap(); + } } } diff --git a/crates/gateway/src/discord/identify_queue.rs b/crates/gateway/src/discord/identify_queue.rs index 2d523dfa..4df8dc4f 100644 --- a/crates/gateway/src/discord/identify_queue.rs +++ b/crates/gateway/src/discord/identify_queue.rs @@ -78,8 +78,8 @@ async fn request_inner(redis: RedisPool, concurrency: u32, shard_id: u32, tx: on Ok(None) => { // not allowed yet, waiting } - Err(e) => { - error!(shard_id, bucket, "error getting shard allowance: {}", e) + Err(error) => { + error!(?error, ?shard_id, ?bucket, "error getting shard allowance") } } diff --git a/crates/gateway/src/discord/shard_state.rs b/crates/gateway/src/discord/shard_state.rs index a7579583..c85e02c8 100644 --- a/crates/gateway/src/discord/shard_state.rs +++ b/crates/gateway/src/discord/shard_state.rs @@ -1,49 +1,63 @@ use fred::{clients::RedisPool, interfaces::HashesInterface}; use metrics::{counter, gauge}; +use tokio::sync::RwLock; use tracing::info; -use twilight_gateway::{Event, Latency}; +use twilight_gateway::Event; + +use std::collections::HashMap; use libpk::state::ShardState; -#[derive(Clone)] +use super::gateway::cluster_config; + pub struct ShardStateManager { redis: RedisPool, + shards: RwLock>, } pub fn new(redis: RedisPool) -> ShardStateManager { - ShardStateManager { redis } + ShardStateManager { + redis: redis, + shards: RwLock::new(HashMap::new()), + } } impl ShardStateManager { pub async fn handle_event(&self, shard_id: u32, event: Event) -> anyhow::Result<()> { match event { + // also update gateway.rs with event types Event::Ready(_) => self.ready_or_resumed(shard_id, false).await, Event::Resumed => self.ready_or_resumed(shard_id, true).await, _ => Ok(()), } } - async fn get_shard(&self, shard_id: u32) -> anyhow::Result { - let data: Option = self.redis.hget("pluralkit:shardstatus", shard_id).await?; - match data { - Some(buf) => Ok(serde_json::from_str(&buf).expect("could not decode shard data!")), - None => Ok(ShardState::default()), + async fn save_shard(&self, id: u32, state: ShardState) -> anyhow::Result<()> { + { + let mut shards = self.shards.write().await; + shards.insert(id, state.clone()); } - } - - async fn save_shard(&self, shard_id: u32, info: ShardState) -> anyhow::Result<()> { self.redis .hset::<(), &str, (String, String)>( "pluralkit:shardstatus", ( - shard_id.to_string(), - serde_json::to_string(&info).expect("could not serialize shard"), + id.to_string(), + serde_json::to_string(&state).expect("could not serialize shard"), ), ) .await?; Ok(()) } + async fn get_shard(&self, id: u32) -> Option { + let shards = self.shards.read().await; + shards.get(&id).cloned() + } + + pub async fn get(&self) -> Vec { + self.shards.read().await.values().cloned().collect() + } + async fn ready_or_resumed(&self, shard_id: u32, resumed: bool) -> anyhow::Result<()> { info!( "shard {} {}", @@ -57,32 +71,52 @@ impl ShardStateManager { ) .increment(1); gauge!("pluralkit_gateway_shard_up").increment(1); - let mut info = self.get_shard(shard_id).await?; + + let mut info = self + .get_shard(shard_id) + .await + .unwrap_or(ShardState::default()); + + info.shard_id = shard_id as i32; + info.cluster_id = Some(cluster_config().node_id as i32); info.last_connection = chrono::offset::Utc::now().timestamp() as i32; info.up = true; + self.save_shard(shard_id, info).await?; Ok(()) } pub async fn socket_closed(&self, shard_id: u32) -> anyhow::Result<()> { gauge!("pluralkit_gateway_shard_up").decrement(1); - let mut info = self.get_shard(shard_id).await?; + + let mut info = self + .get_shard(shard_id) + .await + .unwrap_or(ShardState::default()); + + info.shard_id = shard_id as i32; + info.cluster_id = Some(cluster_config().node_id as i32); info.up = false; info.disconnection_count += 1; + self.save_shard(shard_id, info).await?; Ok(()) } - pub async fn heartbeated(&self, shard_id: u32, latency: &Latency) -> anyhow::Result<()> { - let mut info = self.get_shard(shard_id).await?; + pub async fn heartbeated(&self, shard_id: u32, latency: i32) -> anyhow::Result<()> { + gauge!("pluralkit_gateway_shard_latency", "shard_id" => shard_id.to_string()).set(latency); + + let mut info = self + .get_shard(shard_id) + .await + .unwrap_or(ShardState::default()); + + info.shard_id = shard_id as i32; + info.cluster_id = Some(cluster_config().node_id as i32); info.up = true; info.last_heartbeat = chrono::offset::Utc::now().timestamp() as i32; - info.latency = latency - .recent() - .first() - .map_or_else(|| 0, |d| d.as_millis()) as i32; - gauge!("pluralkit_gateway_shard_latency", "shard_id" => shard_id.to_string()) - .set(info.latency); + info.latency = latency; + self.save_shard(shard_id, info).await?; Ok(()) } diff --git a/crates/gateway/src/event_awaiter.rs b/crates/gateway/src/event_awaiter.rs new file mode 100644 index 00000000..765ad8e5 --- /dev/null +++ b/crates/gateway/src/event_awaiter.rs @@ -0,0 +1,242 @@ +// - reaction: (message_id, user_id) +// - message: (author_id, channel_id, ?options) +// - interaction: (custom_id where not_includes "help-menu") + +use std::{ + collections::{hash_map::Entry, HashMap}, + net::{IpAddr, SocketAddr}, + time::Duration, +}; + +use serde::Deserialize; +use tokio::{sync::RwLock, time::Instant}; +use tracing::info; +use twilight_gateway::Event; +use twilight_model::{ + application::interaction::InteractionData, + id::{ + marker::{ChannelMarker, MessageMarker, UserMarker}, + Id, + }, +}; + +static DEFAULT_TIMEOUT: Duration = Duration::from_mins(15); + +#[derive(Deserialize)] +#[serde(untagged)] +pub enum AwaitEventRequest { + Reaction { + message_id: Id, + user_id: Id, + target: String, + timeout: Option, + }, + Message { + channel_id: Id, + author_id: Id, + target: String, + timeout: Option, + options: Option>, + }, + Interaction { + id: String, + target: String, + timeout: Option, + }, +} + +pub struct EventAwaiter { + reactions: RwLock, Id), (Instant, String)>>, + messages: RwLock< + HashMap<(Id, Id), (Instant, String, Option>)>, + >, + interactions: RwLock>, +} + +impl EventAwaiter { + pub fn new() -> Self { + let v = Self { + reactions: RwLock::new(HashMap::new()), + messages: RwLock::new(HashMap::new()), + interactions: RwLock::new(HashMap::new()), + }; + + v + } + + pub async fn cleanup_loop(&self) { + loop { + tokio::time::sleep(Duration::from_secs(30)).await; + info!("running event_awaiter cleanup loop"); + let mut counts = (0, 0, 0); + let now = Instant::now(); + { + let mut reactions = self.reactions.write().await; + for key in reactions.clone().keys() { + if let Entry::Occupied(entry) = reactions.entry(key.clone()) + && entry.get().0 < now + { + counts.0 += 1; + entry.remove(); + } + } + } + { + let mut messages = self.messages.write().await; + for key in messages.clone().keys() { + if let Entry::Occupied(entry) = messages.entry(key.clone()) + && entry.get().0 < now + { + counts.1 += 1; + entry.remove(); + } + } + } + { + let mut interactions = self.interactions.write().await; + for key in interactions.clone().keys() { + if let Entry::Occupied(entry) = interactions.entry(key.clone()) + && entry.get().0 < now + { + counts.2 += 1; + entry.remove(); + } + } + } + info!("ran event_awaiter cleanup loop, took {}us, {} reactions, {} messages, {} interactions", Instant::now().duration_since(now).as_micros(), counts.0, counts.1, counts.2); + } + } + + pub async fn target_for_event(&self, event: Event) -> Option { + match event { + Event::MessageCreate(message) => { + let mut messages = self.messages.write().await; + + messages + .remove(&(message.channel_id, message.author.id)) + .map(|(timeout, target, options)| { + if let Some(options) = options + && !options.contains(&message.content.to_lowercase()) + { + messages.insert( + (message.channel_id, message.author.id), + (timeout, target, Some(options)), + ); + return None; + } + Some((*target).to_string()) + })? + } + Event::ReactionAdd(reaction) + if let Some((_, target)) = self + .reactions + .write() + .await + .remove(&(reaction.message_id, reaction.user_id)) => + { + Some((*target).to_string()) + } + Event::InteractionCreate(interaction) + if let Some(data) = interaction.data.clone() + && let InteractionData::MessageComponent(component) = data + && !component.custom_id.contains("help-menu") + && let Some((_, target)) = + self.interactions.write().await.remove(&component.custom_id) => + { + Some((*target).to_string()) + } + + _ => None, + } + } + + pub async fn handle_request(&self, req: AwaitEventRequest, addr: SocketAddr) { + match req { + AwaitEventRequest::Reaction { + message_id, + user_id, + target, + timeout, + } => { + self.reactions.write().await.insert( + (message_id, user_id), + ( + Instant::now() + .checked_add( + timeout + .map(|i| Duration::from_secs(i)) + .unwrap_or(DEFAULT_TIMEOUT), + ) + .expect("invalid time"), + target_or_addr(target, addr), + ), + ); + } + AwaitEventRequest::Message { + channel_id, + author_id, + target, + timeout, + options, + } => { + self.messages.write().await.insert( + (channel_id, author_id), + ( + Instant::now() + .checked_add( + timeout + .map(|i| Duration::from_secs(i)) + .unwrap_or(DEFAULT_TIMEOUT), + ) + .expect("invalid time"), + target_or_addr(target, addr), + options, + ), + ); + } + AwaitEventRequest::Interaction { + id, + target, + timeout, + } => { + self.interactions.write().await.insert( + id, + ( + Instant::now() + .checked_add( + timeout + .map(|i| Duration::from_secs(i)) + .unwrap_or(DEFAULT_TIMEOUT), + ) + .expect("invalid time"), + target_or_addr(target, addr), + ), + ); + } + } + } + + pub async fn clear(&self) { + self.reactions.write().await.clear(); + self.messages.write().await.clear(); + self.interactions.write().await.clear(); + } +} + +fn target_or_addr(target: String, addr: SocketAddr) -> String { + if target == "source-addr" { + let ip_str = match addr.ip() { + IpAddr::V4(v4) => v4.to_string(), + IpAddr::V6(v6) => { + if let Some(v4) = v6.to_ipv4_mapped() { + v4.to_string() + } else { + format!("[{v6}]") + } + } + }; + format!("http://{ip_str}:5002/events") + } else { + target + } +} diff --git a/crates/gateway/src/main.rs b/crates/gateway/src/main.rs index 6bc33e13..e61c3445 100644 --- a/crates/gateway/src/main.rs +++ b/crates/gateway/src/main.rs @@ -1,39 +1,79 @@ #![feature(let_chains)] #![feature(if_let_guard)] +#![feature(duration_constructors)] use chrono::Timelike; +use discord::gateway::cluster_config; +use event_awaiter::EventAwaiter; use fred::{clients::RedisPool, interfaces::*}; -use signal_hook::{ - consts::{SIGINT, SIGTERM}, - iterator::Signals, +use libpk::{runtime_config::RuntimeConfig, state::ShardStateEvent}; +use reqwest::{ClientBuilder, StatusCode}; +use std::{sync::Arc, time::Duration, vec::Vec}; +use tokio::{ + signal::unix::{signal, SignalKind}, + sync::mpsc::channel, + task::JoinSet, }; -use std::{ - sync::{mpsc::channel, Arc}, - time::Duration, - vec::Vec, -}; -use tokio::task::JoinSet; -use tracing::{info, warn}; +use tracing::{error, info, warn}; use twilight_gateway::{MessageSender, ShardId}; use twilight_model::gateway::payload::outgoing::UpdatePresence; -mod cache_api; +mod api; mod discord; +mod event_awaiter; mod logger; -libpk::main!("gateway"); -async fn real_main() -> anyhow::Result<()> { - let (shutdown_tx, shutdown_rx) = channel::<()>(); - let shutdown_tx = Arc::new(shutdown_tx); +const RUNTIME_CONFIG_KEY_EVENT_TARGET: &'static str = "event_target"; +#[libpk::main] +async fn main() -> anyhow::Result<()> { let redis = libpk::db::init_redis().await?; - let shard_state = discord::shard_state::new(redis.clone()); + let runtime_config = Arc::new( + RuntimeConfig::new( + redis.clone(), + format!( + "{}:{}", + libpk::config.runtime_config_key.as_ref().unwrap(), + cluster_config().node_id + ), + ) + .await?, + ); + + // hacky, but needed for selfhost for now + if let Some(target) = libpk::config + .discord + .as_ref() + .unwrap() + .gateway_target + .clone() + { + runtime_config + .set(RUNTIME_CONFIG_KEY_EVENT_TARGET.to_string(), target) + .await?; + } + let cache = Arc::new(discord::cache::new()); + let awaiter = Arc::new(EventAwaiter::new()); + tokio::spawn({ + let awaiter = awaiter.clone(); + async move { awaiter.cleanup_loop().await } + }); let shards = discord::gateway::create_shards(redis.clone())?; - let (event_tx, _event_rx) = channel(); + // arbitrary + // todo: make sure this doesn't fill up + let (event_tx, mut event_rx) = channel::<(ShardId, twilight_gateway::Event, String)>(1000); + + // todo: make sure this doesn't fill up + let (state_tx, mut state_rx) = channel::<( + ShardId, + ShardStateEvent, + Option, + Option, + )>(1000); let mut senders = Vec::new(); let mut signal_senders = Vec::new(); @@ -45,61 +85,160 @@ async fn real_main() -> anyhow::Result<()> { set.spawn(tokio::spawn(discord::gateway::runner( shard, event_tx.clone(), - shard_state.clone(), + state_tx.clone(), cache.clone(), + runtime_config.clone(), ))); } + let shard_state = Arc::new(discord::shard_state::new(redis.clone())); + + set.spawn(tokio::spawn({ + let shard_state = shard_state.clone(); + + async move { + while let Some((shard_id, state_event, parsed_event, latency)) = state_rx.recv().await { + match state_event { + ShardStateEvent::Heartbeat => { + if !latency.is_none() + && let Err(error) = shard_state + .heartbeated(shard_id.number(), latency.unwrap()) + .await + { + error!("failed to update shard state for heartbeat: {error}") + }; + } + ShardStateEvent::Closed => { + if let Err(error) = shard_state.socket_closed(shard_id.number()).await { + error!("failed to update shard state for heartbeat: {error}") + }; + } + ShardStateEvent::Other => { + if let Err(error) = shard_state + .handle_event( + shard_id.number(), + parsed_event.expect("shard state event not provided!"), + ) + .await + { + error!("failed to update shard state for heartbeat: {error}") + }; + } + } + } + } + })); + + set.spawn(tokio::spawn({ + let runtime_config = runtime_config.clone(); + let awaiter = awaiter.clone(); + + async move { + let client = Arc::new( + ClientBuilder::new() + .connect_timeout(Duration::from_secs(1)) + .timeout(Duration::from_secs(1)) + .build() + .expect("error making client"), + ); + + while let Some((shard_id, parsed_event, raw_event)) = event_rx.recv().await { + let target = if let Some(target) = awaiter.target_for_event(parsed_event).await { + info!(target = ?target, "sending event to awaiter"); + Some(target) + } else if let Some(target) = + runtime_config.get(RUNTIME_CONFIG_KEY_EVENT_TARGET).await + { + Some(target) + } else { + None + }; + + if let Some(target) = target { + tokio::spawn({ + let client = client.clone(); + async move { + match client + .post(format!("{target}/{}", shard_id.number())) + .body(raw_event) + .send() + .await + { + Ok(res) => { + if res.status() != StatusCode::OK { + error!( + status = ?res.status(), + target = ?target, + "got non-200 from bot while sending event", + ); + } + } + Err(error) => { + error!(?error, "failed to request event target"); + } + } + } + }); + } + } + } + })); + set.spawn(tokio::spawn( async move { scheduled_task(redis, senders).await }, )); - // todo: probably don't do it this way - let api_shutdown_tx = shutdown_tx.clone(); set.spawn(tokio::spawn(async move { - match cache_api::run_server(cache).await { + match api::run_server(cache, shard_state, runtime_config, awaiter.clone()).await { Err(error) => { - tracing::error!(?error, "failed to serve cache api"); - let _ = api_shutdown_tx.send(()); + error!(?error, "failed to serve cache api"); } _ => unreachable!(), } })); - let mut signals = Signals::new(&[SIGINT, SIGTERM])?; - set.spawn(tokio::spawn(async move { - for sig in signals.forever() { - info!("received signal {:?}", sig); - - let presence = UpdatePresence { - op: twilight_model::gateway::OpCode::PresenceUpdate, - d: discord::gateway::presence("Restarting... (please wait)", true), - }; - - for sender in signal_senders.iter() { - let presence = presence.clone(); - let _ = sender.command(&presence); - } - - let _ = shutdown_tx.send(()); - break; - } + signal(SignalKind::interrupt()).unwrap().recv().await; + info!("got SIGINT"); })); - let _ = shutdown_rx.recv(); + set.spawn(tokio::spawn(async move { + signal(SignalKind::terminate()).unwrap().recv().await; + info!("got SIGTERM"); + })); - // sleep 500ms to allow everything to clean up properly - tokio::time::sleep(Duration::from_millis(500)).await; + set.join_next().await; + + info!("gateway exiting, have a nice day!"); + + let presence = UpdatePresence { + op: twilight_model::gateway::OpCode::PresenceUpdate, + d: discord::gateway::presence("Restarting... (please wait)", true), + }; + + for sender in signal_senders.iter() { + let presence = presence.clone(); + let _ = sender.command(&presence); + } set.abort_all(); - info!("gateway exiting, have a nice day!"); + // sleep 500ms to allow everything to clean up properly + tokio::time::sleep(Duration::from_millis(500)).await; Ok(()) } async fn scheduled_task(redis: RedisPool, senders: Vec<(ShardId, MessageSender)>) { + let prefix = libpk::config + .discord + .as_ref() + .expect("missing discord config") + .bot_prefix_for_gateway + .clone(); + + println!("{prefix}"); + loop { tokio::time::sleep(Duration::from_secs( (60 - chrono::offset::Utc::now().second()).into(), @@ -119,9 +258,9 @@ async fn scheduled_task(redis: RedisPool, senders: Vec<(ShardId, MessageSender)> op: twilight_model::gateway::OpCode::PresenceUpdate, d: discord::gateway::presence( if let Some(status) = status { - format!("pk;help | {}", status) + format!("{prefix}help | {status}") } else { - "pk;help".to_string() + format!("{prefix}help") } .as_str(), false, diff --git a/crates/gdpr_worker/Cargo.toml b/crates/gdpr_worker/Cargo.toml new file mode 100644 index 00000000..a30751f9 --- /dev/null +++ b/crates/gdpr_worker/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "gdpr_worker" +version = "0.1.0" +edition = "2021" + +[dependencies] +libpk = { path = "../libpk" } +anyhow = { workspace = true } +axum = { workspace = true } +futures = { workspace = true } +sqlx = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +twilight-http = { workspace = true } +twilight-model = { workspace = true } diff --git a/crates/gdpr_worker/src/main.rs b/crates/gdpr_worker/src/main.rs new file mode 100644 index 00000000..b40557c0 --- /dev/null +++ b/crates/gdpr_worker/src/main.rs @@ -0,0 +1,149 @@ +#![feature(let_chains)] + +use sqlx::prelude::FromRow; +use std::{sync::Arc, time::Duration}; +use tracing::{error, info, warn}; +use twilight_http::api_error::{ApiError, GeneralApiError}; +use twilight_model::id::{ + marker::{ChannelMarker, MessageMarker}, + Id, +}; + +// create table messages_gdpr_jobs (mid bigint not null references messages(mid) on delete cascade, channel bigint not null); + +#[libpk::main] +async fn main() -> anyhow::Result<()> { + let db = libpk::db::init_messages_db().await?; + + let mut client_builder = twilight_http::Client::builder() + .token( + libpk::config + .discord + .as_ref() + .expect("missing discord config") + .bot_token + .clone(), + ) + .timeout(Duration::from_secs(30)); + + if let Some(base_url) = libpk::config + .discord + .as_ref() + .expect("missing discord config") + .api_base_url + .clone() + { + client_builder = client_builder.proxy(base_url, true).ratelimiter(None); + } + + let client = Arc::new(client_builder.build()); + + loop { + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + match run_job(db.clone(), client.clone()).await { + Ok(()) => {} + Err(error) => { + error!(?error, "failed to run messages gdpr job"); + } + } + } +} + +#[derive(FromRow)] +struct GdprJobEntry { + mid: i64, + channel_id: i64, +} + +async fn run_job(pool: sqlx::PgPool, discord: Arc) -> anyhow::Result<()> { + let mut tx = pool.begin().await?; + + let message: Option = sqlx::query_as( + "select mid, channel_id from messages_gdpr_jobs for update skip locked limit 1;", + ) + .fetch_optional(&mut *tx) + .await?; + + let Some(message) = message else { + info!("no job to run, sleeping for 1 minute"); + tokio::time::sleep(tokio::time::Duration::from_secs(60)).await; + return Ok(()); + }; + + info!("got mid={}, cleaning up...", message.mid); + + // naively delete message on discord's end + let res = discord + .delete_message( + Id::::new(message.channel_id as u64), + Id::::new(message.mid as u64), + ) + .await; + + if res.is_ok() { + sqlx::query("delete from messages_gdpr_jobs where mid = $1") + .bind(message.mid) + .execute(&mut *tx) + .await?; + } + + if let Err(err) = res { + if let twilight_http::error::ErrorType::Response { error, status, .. } = err.kind() + && let ApiError::General(GeneralApiError { code, .. }) = error + { + match (status.get(), code) { + (403, _) => { + warn!( + "got 403 while deleting message in channel {}, failing fast", + message.channel_id + ); + sqlx::query("delete from messages_gdpr_jobs where channel_id = $1") + .bind(message.channel_id) + .execute(&mut *tx) + .await?; + } + (_, 10003) => { + warn!( + "deleting message in channel {}: channel not found, failing fast", + message.channel_id + ); + sqlx::query("delete from messages_gdpr_jobs where channel_id = $1") + .bind(message.channel_id) + .execute(&mut *tx) + .await?; + } + (_, 10008) => { + warn!("deleting message {}: message not found", message.mid); + sqlx::query("delete from messages_gdpr_jobs where mid = $1") + .bind(message.mid) + .execute(&mut *tx) + .await?; + } + (_, 50083) => { + warn!( + "could not delete message in thread {}: thread is archived, failing fast", + message.channel_id + ); + sqlx::query("delete from messages_gdpr_jobs where channel_id = $1") + .bind(message.channel_id) + .execute(&mut *tx) + .await?; + } + _ => { + error!( + ?status, + ?code, + message_id = message.mid, + "got unknown error deleting message", + ); + } + } + } else { + return Err(err.into()); + } + } + + tx.commit().await?; + + return Ok(()); +} diff --git a/crates/h b/crates/h new file mode 100644 index 00000000..e69de29b diff --git a/crates/libpk/Cargo.toml b/crates/libpk/Cargo.toml index 40d430c8..30d77ae0 100644 --- a/crates/libpk/Cargo.toml +++ b/crates/libpk/Cargo.toml @@ -8,6 +8,7 @@ anyhow = { workspace = true } fred = { workspace = true } lazy_static = { workspace = true } metrics = { workspace = true } +pk_macros = { path = "../macros" } sentry = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/libpk/src/_config.rs b/crates/libpk/src/_config.rs index b182f359..8358440b 100644 --- a/crates/libpk/src/_config.rs +++ b/crates/libpk/src/_config.rs @@ -12,10 +12,16 @@ pub struct ClusterSettings { pub total_nodes: u32, } +fn _default_bot_prefix() -> String { + "pk;".to_string() +} + #[derive(Deserialize, Debug)] pub struct DiscordConfig { pub client_id: Id, pub bot_token: String, + #[serde(default = "_default_bot_prefix")] + pub bot_prefix_for_gateway: String, pub client_secret: String, pub max_concurrency: u32, #[serde(default)] @@ -24,6 +30,9 @@ pub struct DiscordConfig { #[serde(default = "_default_api_addr")] pub cache_api_addr: String, + + #[serde(default)] + pub gateway_target: Option, } #[derive(Deserialize, Debug)] @@ -85,6 +94,7 @@ pub struct ScheduledTasksConfig { pub set_guild_count: bool, pub expected_gateway_count: usize, pub gateway_url: String, + pub prometheus_url: String, } fn _metrics_default() -> bool { @@ -113,6 +123,9 @@ pub struct PKConfig { #[serde(default = "_json_log_default")] pub(crate) json_log: bool, + #[serde(default)] + pub runtime_config_key: Option, + #[serde(default)] pub sentry_url: Option, } @@ -132,10 +145,15 @@ impl PKConfig { lazy_static! { #[derive(Debug)] pub static ref CONFIG: Arc = { + // hacks if let Ok(var) = std::env::var("NOMAD_ALLOC_INDEX") && std::env::var("pluralkit__discord__cluster__total_nodes").is_ok() { std::env::set_var("pluralkit__discord__cluster__node_id", var); } + if let Ok(var) = std::env::var("STATEFULSET_NAME_FOR_INDEX") + && std::env::var("pluralkit__discord__cluster__total_nodes").is_ok() { + std::env::set_var("pluralkit__discord__cluster__node_id", var.split("-").last().unwrap()); + } Arc::new(Config::builder() .add_source(config::Environment::with_prefix("pluralkit").separator("__")) diff --git a/crates/libpk/src/lib.rs b/crates/libpk/src/lib.rs index af967728..55031bf3 100644 --- a/crates/libpk/src/lib.rs +++ b/crates/libpk/src/lib.rs @@ -8,12 +8,15 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilte use sentry_tracing::event_from_event; pub mod db; +pub mod runtime_config; pub mod state; pub mod _config; pub use crate::_config::CONFIG as config; -// functions in this file are only used by the main function below +// functions in this file are only used by the main function in macros/entrypoint.rs + +pub use pk_macros::main; pub fn init_logging(component: &str) { let sentry_layer = @@ -42,6 +45,7 @@ pub fn init_logging(component: &str) { tracing_subscriber::registry() .with(sentry_layer) .with(tracing_subscriber::fmt::layer()) + .with(EnvFilter::from_default_env()) .init(); } } @@ -66,28 +70,3 @@ pub fn init_sentry() -> sentry::ClientInitGuard { ..Default::default() }) } - -#[macro_export] -macro_rules! main { - ($component:expr) => { - fn main() -> anyhow::Result<()> { - let _sentry_guard = libpk::init_sentry(); - // we might also be able to use env!("CARGO_CRATE_NAME") here - libpk::init_logging($component); - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(async { - if let Err(err) = libpk::init_metrics() { - tracing::error!("failed to init metrics collector: {err}"); - }; - tracing::info!("hello world"); - if let Err(err) = real_main().await { - tracing::error!("failed to run service: {err}"); - }; - }); - Ok(()) - } - }; -} diff --git a/crates/libpk/src/runtime_config.rs b/crates/libpk/src/runtime_config.rs new file mode 100644 index 00000000..e1fbea7a --- /dev/null +++ b/crates/libpk/src/runtime_config.rs @@ -0,0 +1,72 @@ +use fred::{clients::RedisPool, interfaces::HashesInterface}; +use std::collections::HashMap; +use tokio::sync::RwLock; +use tracing::info; + +pub struct RuntimeConfig { + redis: RedisPool, + settings: RwLock>, + redis_key: String, +} + +impl RuntimeConfig { + pub async fn new(redis: RedisPool, component_key: String) -> anyhow::Result { + let redis_key = format!("remote_config:{component_key}"); + + let mut c = RuntimeConfig { + redis, + settings: RwLock::new(HashMap::new()), + redis_key, + }; + + c.load().await?; + + Ok(c) + } + + pub async fn load(&mut self) -> anyhow::Result<()> { + let redis_config: HashMap = self.redis.hgetall(&self.redis_key).await?; + + let mut settings = self.settings.write().await; + + for (key, value) in redis_config { + settings.insert(key, value); + } + + info!("starting with runtime config: {:?}", settings); + Ok(()) + } + + pub async fn set(&self, key: String, value: String) -> anyhow::Result<()> { + self.redis + .hset::<(), &str, (String, String)>(&self.redis_key, (key.clone(), value.clone())) + .await?; + self.settings + .write() + .await + .insert(key.clone(), value.clone()); + info!("updated runtime config: {key}={value}"); + Ok(()) + } + + pub async fn delete(&self, key: String) -> anyhow::Result<()> { + self.redis + .hdel::<(), &str, String>(&self.redis_key, key.clone()) + .await?; + self.settings.write().await.remove(&key.clone()); + info!("updated runtime config: {key} removed"); + Ok(()) + } + + pub async fn get(&self, key: &str) -> Option { + self.settings.read().await.get(key).cloned() + } + + pub async fn exists(&self, key: &str) -> bool { + self.settings.read().await.contains_key(key) + } + + pub async fn get_all(&self) -> HashMap { + self.settings.read().await.clone() + } +} diff --git a/crates/libpk/src/state.rs b/crates/libpk/src/state.rs index 90a77c21..df44ea1d 100644 --- a/crates/libpk/src/state.rs +++ b/crates/libpk/src/state.rs @@ -1,4 +1,4 @@ -#[derive(serde::Serialize, serde::Deserialize, Clone, Default)] +#[derive(serde::Serialize, serde::Deserialize, Clone, Default, Debug)] pub struct ShardState { pub shard_id: i32, pub up: bool, @@ -10,3 +10,9 @@ pub struct ShardState { pub last_connection: i32, pub cluster_id: Option, } + +pub enum ShardStateEvent { + Closed, + Heartbeat, + Other, +} diff --git a/crates/model_macros/Cargo.toml b/crates/macros/Cargo.toml similarity index 85% rename from crates/model_macros/Cargo.toml rename to crates/macros/Cargo.toml index d2d0e009..8090798f 100644 --- a/crates/model_macros/Cargo.toml +++ b/crates/macros/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "model_macros" +name = "pk_macros" version = "0.1.0" edition = "2021" diff --git a/crates/macros/src/entrypoint.rs b/crates/macros/src/entrypoint.rs new file mode 100644 index 00000000..1e10012a --- /dev/null +++ b/crates/macros/src/entrypoint.rs @@ -0,0 +1,41 @@ +use proc_macro::{Delimiter, TokenTree}; +use quote::quote; + +pub fn macro_impl( + _args: proc_macro::TokenStream, + input: proc_macro::TokenStream, +) -> proc_macro::TokenStream { + // yes, this ignores everything except the codeblock + // it's fine. + let body = match input.into_iter().last().expect("empty") { + TokenTree::Group(group) if group.delimiter() == Delimiter::Brace => group.stream(), + _ => panic!("invalid function"), + }; + + let body = proc_macro2::TokenStream::from(body); + + return quote! { + fn main() { + let _sentry_guard = libpk::init_sentry(); + libpk::init_logging(env!("CARGO_CRATE_NAME")); + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(async { + if let Err(error) = libpk::init_metrics() { + tracing::error!(?error, "failed to init metrics collector"); + }; + + tracing::info!("hello world"); + + let result: anyhow::Result<()> = async { #body }.await; + + if let Err(error) = result { + tracing::error!(?error, "failed to run service"); + }; + }); + } + } + .into(); +} diff --git a/crates/macros/src/lib.rs b/crates/macros/src/lib.rs new file mode 100644 index 00000000..db5a55b7 --- /dev/null +++ b/crates/macros/src/lib.rs @@ -0,0 +1,14 @@ +use proc_macro::TokenStream; + +mod entrypoint; +mod model; + +#[proc_macro_attribute] +pub fn main(args: TokenStream, input: TokenStream) -> TokenStream { + entrypoint::macro_impl(args, input) +} + +#[proc_macro_attribute] +pub fn pk_model(args: TokenStream, input: TokenStream) -> TokenStream { + model::macro_impl(args, input) +} diff --git a/crates/model_macros/src/lib.rs b/crates/macros/src/model.rs similarity index 82% rename from crates/model_macros/src/lib.rs rename to crates/macros/src/model.rs index 77f286e2..924b5bcd 100644 --- a/crates/model_macros/src/lib.rs +++ b/crates/macros/src/model.rs @@ -16,6 +16,7 @@ struct ModelField { patch: ElemPatchability, json: Option, is_privacy: bool, + privacy: Option, default: Option, } @@ -26,6 +27,7 @@ fn parse_field(field: syn::Field) -> ModelField { patch: ElemPatchability::None, json: None, is_privacy: false, + privacy: None, default: None, }; @@ -61,6 +63,12 @@ fn parse_field(field: syn::Field) -> ModelField { } f.json = Some(nv.value.clone()); } + "privacy" => { + if f.privacy.is_some() { + panic!("cannot set privacy multiple times for same field"); + } + f.privacy = Some(nv.value.clone()); + } "default" => { if f.default.is_some() { panic!("cannot set default multiple times for same field"); @@ -84,8 +92,7 @@ fn parse_field(field: syn::Field) -> ModelField { f } -#[proc_macro_attribute] -pub fn pk_model( +pub fn macro_impl( _args: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { @@ -108,8 +115,6 @@ pub fn pk_model( panic!("fields of a struct must be named"); }; - // println!("{}: {:#?}", tname, fields); - let tfields = mk_tfields(fields.clone()); let from_json = mk_tfrom_json(fields.clone()); let _from_sql = mk_tfrom_sql(fields.clone()); @@ -138,9 +143,7 @@ pub fn pk_model( #from_json } - pub fn to_json(self) -> serde_json::Value { - #to_json - } + #to_json } #[derive(Debug, Clone)] @@ -189,19 +192,28 @@ fn mk_tfrom_sql(_fields: Vec) -> TokenStream { quote! { unimplemented!(); } } fn mk_tto_json(fields: Vec) -> TokenStream { - // todo: check privacy access + let has_privacy = fields.iter().any(|f| f.privacy.is_some()); let fielddefs: TokenStream = fields .iter() .filter_map(|f| { f.json.as_ref().map(|v| { let tname = f.name.clone(); - if let Some(default) = f.default.as_ref() { + let maybepriv = if let Some(privacy) = f.privacy.as_ref() { quote! { - #v: self.#tname.unwrap_or(#default), + #v: crate::_util::privacy_lookup!(self.#tname, self.#privacy, lookup_level) } } else { quote! { - #v: self.#tname, + #v: self.#tname + } + }; + if let Some(default) = f.default.as_ref() { + quote! { + #maybepriv.unwrap_or(#default), + } + } else { + quote! { + #maybepriv, } } }) @@ -223,13 +235,35 @@ fn mk_tto_json(fields: Vec) -> TokenStream { }) .collect(); - quote! { - serde_json::json!({ - #fielddefs - "privacy": { - #privacyfielddefs + let privdef = if has_privacy { + quote! { + , lookup_level: crate::PrivacyLevel + } + } else { + quote! {} + }; + + let privacy_fielddefs = if has_privacy { + quote! { + "privacy": if matches!(lookup_level, crate::PrivacyLevel::Private) { + Some(serde_json::json!({ + #privacyfielddefs + })) + } else { + None } - }) + } + } else { + quote! {} + }; + + quote! { + pub fn to_json(self #privdef) -> serde_json::Value { + serde_json::json!({ + #fielddefs + #privacy_fielddefs + }) + } } } diff --git a/crates/migrate/Cargo.toml b/crates/migrate/Cargo.toml new file mode 100644 index 00000000..cf4eff2d --- /dev/null +++ b/crates/migrate/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "migrate" +version = "0.1.0" +edition = "2021" + +[dependencies] +libpk = { path = "../libpk" } + +anyhow = { workspace = true } +sqlx = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } diff --git a/crates/migrate/build.rs b/crates/migrate/build.rs new file mode 100644 index 00000000..80829b0d --- /dev/null +++ b/crates/migrate/build.rs @@ -0,0 +1,55 @@ +use std::{ + env, + error::Error, + fs::{self, File}, + io::Write, + path::Path, +}; + +fn main() -> Result<(), Box> { + let out_dir = env::var("OUT_DIR")?; + let dest_path = Path::new(&out_dir).join("data.rs"); + let mut datafile = File::create(&dest_path)?; + + let prefix = "../../../../../../crates/migrate/data"; + + let ct = fs::read_dir("data/migrations")? + .filter(|p| { + p.as_ref() + .unwrap() + .file_name() + .into_string() + .unwrap() + .contains(".sql") + }) + .count(); + + writeln!(&mut datafile, "const MIGRATIONS: [&'static str; {ct}] = [")?; + for idx in 0..ct { + writeln!( + &mut datafile, + "\tinclude_str!(\"{prefix}/migrations/{idx}.sql\")," + )?; + } + writeln!(&mut datafile, "];\n")?; + + writeln!( + &mut datafile, + "const CLEAN: &'static str = include_str!(\"{prefix}/clean.sql\");" + )?; + writeln!( + &mut datafile, + "const VIEWS: &'static str = include_str!(\"{prefix}/views.sql\");" + )?; + writeln!( + &mut datafile, + "const FUNCTIONS: &'static str = include_str!(\"{prefix}/functions.sql\");" + )?; + + writeln!( + &mut datafile, + "const SEED: &'static str = include_str!(\"{prefix}/seed.sql\");" + )?; + + Ok(()) +} diff --git a/PluralKit.Core/Database/clean.sql b/crates/migrate/data/clean.sql similarity index 100% rename from PluralKit.Core/Database/clean.sql rename to crates/migrate/data/clean.sql diff --git a/PluralKit.Core/Database/Functions/functions.sql b/crates/migrate/data/functions.sql similarity index 100% rename from PluralKit.Core/Database/Functions/functions.sql rename to crates/migrate/data/functions.sql diff --git a/PluralKit.Core/Database/Migrations/0.sql b/crates/migrate/data/migrations/0.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/0.sql rename to crates/migrate/data/migrations/0.sql diff --git a/PluralKit.Core/Database/Migrations/1.sql b/crates/migrate/data/migrations/1.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/1.sql rename to crates/migrate/data/migrations/1.sql diff --git a/PluralKit.Core/Database/Migrations/10.sql b/crates/migrate/data/migrations/10.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/10.sql rename to crates/migrate/data/migrations/10.sql diff --git a/PluralKit.Core/Database/Migrations/11.sql b/crates/migrate/data/migrations/11.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/11.sql rename to crates/migrate/data/migrations/11.sql diff --git a/PluralKit.Core/Database/Migrations/12.sql b/crates/migrate/data/migrations/12.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/12.sql rename to crates/migrate/data/migrations/12.sql diff --git a/PluralKit.Core/Database/Migrations/13.sql b/crates/migrate/data/migrations/13.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/13.sql rename to crates/migrate/data/migrations/13.sql diff --git a/PluralKit.Core/Database/Migrations/14.sql b/crates/migrate/data/migrations/14.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/14.sql rename to crates/migrate/data/migrations/14.sql diff --git a/PluralKit.Core/Database/Migrations/15.sql b/crates/migrate/data/migrations/15.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/15.sql rename to crates/migrate/data/migrations/15.sql diff --git a/PluralKit.Core/Database/Migrations/16.sql b/crates/migrate/data/migrations/16.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/16.sql rename to crates/migrate/data/migrations/16.sql diff --git a/PluralKit.Core/Database/Migrations/17.sql b/crates/migrate/data/migrations/17.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/17.sql rename to crates/migrate/data/migrations/17.sql diff --git a/PluralKit.Core/Database/Migrations/18.sql b/crates/migrate/data/migrations/18.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/18.sql rename to crates/migrate/data/migrations/18.sql diff --git a/PluralKit.Core/Database/Migrations/19.sql b/crates/migrate/data/migrations/19.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/19.sql rename to crates/migrate/data/migrations/19.sql diff --git a/PluralKit.Core/Database/Migrations/2.sql b/crates/migrate/data/migrations/2.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/2.sql rename to crates/migrate/data/migrations/2.sql diff --git a/PluralKit.Core/Database/Migrations/20.sql b/crates/migrate/data/migrations/20.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/20.sql rename to crates/migrate/data/migrations/20.sql diff --git a/PluralKit.Core/Database/Migrations/21.sql b/crates/migrate/data/migrations/21.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/21.sql rename to crates/migrate/data/migrations/21.sql diff --git a/PluralKit.Core/Database/Migrations/22.sql b/crates/migrate/data/migrations/22.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/22.sql rename to crates/migrate/data/migrations/22.sql diff --git a/PluralKit.Core/Database/Migrations/23.sql b/crates/migrate/data/migrations/23.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/23.sql rename to crates/migrate/data/migrations/23.sql diff --git a/PluralKit.Core/Database/Migrations/24.sql b/crates/migrate/data/migrations/24.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/24.sql rename to crates/migrate/data/migrations/24.sql diff --git a/PluralKit.Core/Database/Migrations/25.sql b/crates/migrate/data/migrations/25.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/25.sql rename to crates/migrate/data/migrations/25.sql diff --git a/PluralKit.Core/Database/Migrations/26.sql b/crates/migrate/data/migrations/26.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/26.sql rename to crates/migrate/data/migrations/26.sql diff --git a/PluralKit.Core/Database/Migrations/27.sql b/crates/migrate/data/migrations/27.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/27.sql rename to crates/migrate/data/migrations/27.sql diff --git a/PluralKit.Core/Database/Migrations/28.sql b/crates/migrate/data/migrations/28.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/28.sql rename to crates/migrate/data/migrations/28.sql diff --git a/PluralKit.Core/Database/Migrations/29.sql b/crates/migrate/data/migrations/29.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/29.sql rename to crates/migrate/data/migrations/29.sql diff --git a/PluralKit.Core/Database/Migrations/3.sql b/crates/migrate/data/migrations/3.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/3.sql rename to crates/migrate/data/migrations/3.sql diff --git a/PluralKit.Core/Database/Migrations/30.sql b/crates/migrate/data/migrations/30.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/30.sql rename to crates/migrate/data/migrations/30.sql diff --git a/PluralKit.Core/Database/Migrations/31.sql b/crates/migrate/data/migrations/31.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/31.sql rename to crates/migrate/data/migrations/31.sql diff --git a/PluralKit.Core/Database/Migrations/32.sql b/crates/migrate/data/migrations/32.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/32.sql rename to crates/migrate/data/migrations/32.sql diff --git a/PluralKit.Core/Database/Migrations/33.sql b/crates/migrate/data/migrations/33.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/33.sql rename to crates/migrate/data/migrations/33.sql diff --git a/PluralKit.Core/Database/Migrations/34.sql b/crates/migrate/data/migrations/34.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/34.sql rename to crates/migrate/data/migrations/34.sql diff --git a/PluralKit.Core/Database/Migrations/35.sql b/crates/migrate/data/migrations/35.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/35.sql rename to crates/migrate/data/migrations/35.sql diff --git a/PluralKit.Core/Database/Migrations/36.sql b/crates/migrate/data/migrations/36.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/36.sql rename to crates/migrate/data/migrations/36.sql diff --git a/PluralKit.Core/Database/Migrations/37.sql b/crates/migrate/data/migrations/37.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/37.sql rename to crates/migrate/data/migrations/37.sql diff --git a/PluralKit.Core/Database/Migrations/38.sql b/crates/migrate/data/migrations/38.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/38.sql rename to crates/migrate/data/migrations/38.sql diff --git a/PluralKit.Core/Database/Migrations/39.sql b/crates/migrate/data/migrations/39.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/39.sql rename to crates/migrate/data/migrations/39.sql diff --git a/PluralKit.Core/Database/Migrations/4.sql b/crates/migrate/data/migrations/4.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/4.sql rename to crates/migrate/data/migrations/4.sql diff --git a/PluralKit.Core/Database/Migrations/40.sql b/crates/migrate/data/migrations/40.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/40.sql rename to crates/migrate/data/migrations/40.sql diff --git a/PluralKit.Core/Database/Migrations/41.sql b/crates/migrate/data/migrations/41.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/41.sql rename to crates/migrate/data/migrations/41.sql diff --git a/PluralKit.Core/Database/Migrations/42.sql b/crates/migrate/data/migrations/42.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/42.sql rename to crates/migrate/data/migrations/42.sql diff --git a/PluralKit.Core/Database/Migrations/43.sql b/crates/migrate/data/migrations/43.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/43.sql rename to crates/migrate/data/migrations/43.sql diff --git a/PluralKit.Core/Database/Migrations/44.sql b/crates/migrate/data/migrations/44.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/44.sql rename to crates/migrate/data/migrations/44.sql diff --git a/PluralKit.Core/Database/Migrations/45.sql b/crates/migrate/data/migrations/45.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/45.sql rename to crates/migrate/data/migrations/45.sql diff --git a/PluralKit.Core/Database/Migrations/46.sql b/crates/migrate/data/migrations/46.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/46.sql rename to crates/migrate/data/migrations/46.sql diff --git a/PluralKit.Core/Database/Migrations/47.sql b/crates/migrate/data/migrations/47.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/47.sql rename to crates/migrate/data/migrations/47.sql diff --git a/PluralKit.Core/Database/Migrations/48.sql b/crates/migrate/data/migrations/48.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/48.sql rename to crates/migrate/data/migrations/48.sql diff --git a/PluralKit.Core/Database/Migrations/49.sql b/crates/migrate/data/migrations/49.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/49.sql rename to crates/migrate/data/migrations/49.sql diff --git a/PluralKit.Core/Database/Migrations/5.sql b/crates/migrate/data/migrations/5.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/5.sql rename to crates/migrate/data/migrations/5.sql diff --git a/PluralKit.Core/Database/Migrations/50.sql b/crates/migrate/data/migrations/50.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/50.sql rename to crates/migrate/data/migrations/50.sql diff --git a/PluralKit.Core/Database/Migrations/51.sql b/crates/migrate/data/migrations/51.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/51.sql rename to crates/migrate/data/migrations/51.sql diff --git a/crates/migrate/data/migrations/52.sql b/crates/migrate/data/migrations/52.sql new file mode 100644 index 00000000..7c603021 --- /dev/null +++ b/crates/migrate/data/migrations/52.sql @@ -0,0 +1,21 @@ +-- database version 52 +-- messages db updates + +create index messages_by_original on messages(original_mid); +create index messages_by_sender on messages(sender); + +-- remove old table from database version 11 +alter table command_messages rename to command_messages_old; + +create table command_messages ( + mid bigint primary key, + channel bigint not null, + guild bigint not null, + sender bigint not null, + original_mid bigint not null +); + +create index command_messages_by_original on command_messages(original_mid); +create index command_messages_by_sender on command_messages(sender); + +update info set schema_version = 52; diff --git a/PluralKit.Core/Database/Migrations/6.sql b/crates/migrate/data/migrations/6.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/6.sql rename to crates/migrate/data/migrations/6.sql diff --git a/PluralKit.Core/Database/Migrations/7.sql b/crates/migrate/data/migrations/7.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/7.sql rename to crates/migrate/data/migrations/7.sql diff --git a/PluralKit.Core/Database/Migrations/8.sql b/crates/migrate/data/migrations/8.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/8.sql rename to crates/migrate/data/migrations/8.sql diff --git a/PluralKit.Core/Database/Migrations/9.sql b/crates/migrate/data/migrations/9.sql similarity index 100% rename from PluralKit.Core/Database/Migrations/9.sql rename to crates/migrate/data/migrations/9.sql diff --git a/crates/migrate/data/seed.sql b/crates/migrate/data/seed.sql new file mode 100644 index 00000000..27bca359 --- /dev/null +++ b/crates/migrate/data/seed.sql @@ -0,0 +1,8 @@ +-- example data (for integration tests or such) + +insert into systems (hid, token) values ( + 'exmpl', + 'vlPitT0tEgT++a450w1/afODy5NXdALcHDwryX6dOIZdGUGbZg+5IH3nrUsQihsw' +); +insert into system_config (system) values (1); +insert into system_guild (system, guild) values (1, 466707357099884544); diff --git a/PluralKit.Core/Database/Views/views.sql b/crates/migrate/data/views.sql similarity index 100% rename from PluralKit.Core/Database/Views/views.sql rename to crates/migrate/data/views.sql diff --git a/crates/migrate/src/main.rs b/crates/migrate/src/main.rs new file mode 100644 index 00000000..85b15e33 --- /dev/null +++ b/crates/migrate/src/main.rs @@ -0,0 +1,70 @@ +#![feature(let_chains)] + +use tracing::info; + +include!(concat!(env!("OUT_DIR"), "/data.rs")); + +#[libpk::main] +async fn main() -> anyhow::Result<()> { + let db = libpk::db::init_data_db().await?; + + // clean + // get current migration + // migrate to latest + // run views + // run functions + + #[derive(sqlx::FromRow)] + struct CurrentMigration { + schema_version: i32, + } + + let info = match sqlx::query_as("select schema_version from info") + .fetch_optional(&db) + .await + { + Ok(Some(result)) => result, + Ok(None) => CurrentMigration { schema_version: -1 }, + Err(e) if format!("{e}").contains("relation \"info\" does not exist") => { + CurrentMigration { schema_version: -1 } + } + Err(e) => return Err(e.into()), + }; + + info!("current migration: {}", info.schema_version); + + info!("running clean.sql"); + sqlx::raw_sql(fix_feff(CLEAN)).execute(&db).await?; + + for idx in (info.schema_version + 1) as usize..MIGRATIONS.len() { + info!("running migration {idx}"); + sqlx::raw_sql(fix_feff(MIGRATIONS[idx as usize])) + .execute(&db) + .await?; + } + + info!("running views.sql"); + sqlx::raw_sql(fix_feff(VIEWS)).execute(&db).await?; + + info!("running functions.sql"); + sqlx::raw_sql(fix_feff(FUNCTIONS)).execute(&db).await?; + + if let Ok(var) = std::env::var("SEED") + && var == "true" + { + info!("running seed.sql"); + sqlx::raw_sql(fix_feff(SEED)).execute(&db).await?; + info!( + "example system created with hid 'exmpl', token 'vlPitT0tEgT++a450w1/afODy5NXdALcHDwryX6dOIZdGUGbZg+5IH3nrUsQihsw', guild_id 466707357099884544" + ); + } + + info!("all done!"); + + Ok(()) +} + +// some migration scripts have \u{feff} at the start +fn fix_feff(sql: &str) -> &str { + sql.trim_start_matches("\u{feff}") +} diff --git a/crates/models/Cargo.toml b/crates/models/Cargo.toml index da90d79a..0fbc358c 100644 --- a/crates/models/Cargo.toml +++ b/crates/models/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] chrono = { workspace = true, features = ["serde"] } -model_macros = { path = "../model_macros" } +pk_macros = { path = "../macros" } sea-query = "0.32.1" serde = { workspace = true } serde_json = { workspace = true, features = ["preserve_order"] } diff --git a/crates/models/src/_util.rs b/crates/models/src/_util.rs index c13a0bf6..2d4921ad 100644 --- a/crates/models/src/_util.rs +++ b/crates/models/src/_util.rs @@ -33,3 +33,17 @@ macro_rules! fake_enum_impls { } pub(crate) use fake_enum_impls; + +macro_rules! privacy_lookup { + ($v:expr, $vprivacy:expr, $lookup_level:expr) => { + if matches!($vprivacy, crate::PrivacyLevel::Public) + || matches!($lookup_level, crate::PrivacyLevel::Private) + { + Some($v) + } else { + None + } + }; +} + +pub(crate) use privacy_lookup; diff --git a/crates/models/src/lib.rs b/crates/models/src/lib.rs index bb7bb08d..0bd1f92b 100644 --- a/crates/models/src/lib.rs +++ b/crates/models/src/lib.rs @@ -9,3 +9,25 @@ macro_rules! model { model!(system); model!(system_config); + +#[derive(serde::Serialize, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum PrivacyLevel { + Public, + Private, +} + +// this sucks, put it somewhere else +use sqlx::{postgres::PgTypeInfo, Database, Decode, Postgres, Type}; +use std::error::Error; +_util::fake_enum_impls!(PrivacyLevel); + +impl From for PrivacyLevel { + fn from(value: i32) -> Self { + match value { + 1 => PrivacyLevel::Public, + 2 => PrivacyLevel::Private, + _ => unreachable!(), + } + } +} diff --git a/crates/models/src/system.rs b/crates/models/src/system.rs index d59d5957..ddaf1980 100644 --- a/crates/models/src/system.rs +++ b/crates/models/src/system.rs @@ -1,36 +1,13 @@ -use std::error::Error; - -use model_macros::pk_model; +use pk_macros::pk_model; use chrono::NaiveDateTime; -use sqlx::{postgres::PgTypeInfo, Database, Decode, Postgres, Type}; use uuid::Uuid; -use crate::_util::fake_enum_impls; +use crate::PrivacyLevel; // todo: fix this pub type SystemId = i32; -// todo: move this -#[derive(serde::Serialize, Debug, Clone)] -#[serde(rename_all = "snake_case")] -pub enum PrivacyLevel { - Public, - Private, -} - -fake_enum_impls!(PrivacyLevel); - -impl From for PrivacyLevel { - fn from(value: i32) -> Self { - match value { - 1 => PrivacyLevel::Public, - 2 => PrivacyLevel::Private, - _ => unreachable!(), - } - } -} - #[pk_model] struct System { id: SystemId, @@ -40,21 +17,25 @@ struct System { #[json = "uuid"] uuid: Uuid, #[json = "name"] + #[privacy = name_privacy] name: Option, #[json = "description"] + #[privacy = description_privacy] description: Option, #[json = "tag"] tag: Option, #[json = "pronouns"] + #[privacy = pronoun_privacy] pronouns: Option, #[json = "avatar_url"] + #[privacy = avatar_privacy] avatar_url: Option, - #[json = "banner_image"] + #[json = "banner"] + #[privacy = banner_privacy] banner_image: Option, #[json = "color"] color: Option, token: Option, - #[json = "webhook_url"] webhook_url: Option, webhook_token: Option, #[json = "created"] diff --git a/crates/models/src/system_config.rs b/crates/models/src/system_config.rs index d6b58a58..772d231d 100644 --- a/crates/models/src/system_config.rs +++ b/crates/models/src/system_config.rs @@ -1,4 +1,4 @@ -use model_macros::pk_model; +use pk_macros::pk_model; use sqlx::{postgres::PgTypeInfo, Database, Decode, Postgres, Type}; use std::error::Error; @@ -10,7 +10,7 @@ pub const DEFAULT_GROUP_LIMIT: i32 = 250; #[derive(serde::Serialize, Debug, Clone)] #[serde(rename_all = "snake_case")] -enum HidPadFormat { +pub enum HidPadFormat { #[serde(rename = "off")] None, Left, @@ -31,7 +31,7 @@ impl From for HidPadFormat { #[derive(serde::Serialize, Debug, Clone)] #[serde(rename_all = "snake_case")] -enum ProxySwitchAction { +pub enum ProxySwitchAction { Off, New, Add, @@ -83,7 +83,8 @@ struct SystemConfig { #[json = "proxy_switch"] proxy_switch: ProxySwitchAction, #[json = "name_format"] - name_format: String, + #[default = "{name} {tag}".to_string()] + name_format: Option, #[json = "description_templates"] description_templates: Vec, } diff --git a/crates/scheduled_tasks/src/main.rs b/crates/scheduled_tasks/src/main.rs index cd0a182b..c689a99e 100644 --- a/crates/scheduled_tasks/src/main.rs +++ b/crates/scheduled_tasks/src/main.rs @@ -20,8 +20,8 @@ pub struct AppCtx { pub discord: Arc, } -libpk::main!("scheduled_tasks"); -async fn real_main() -> anyhow::Result<()> { +#[libpk::main] +async fn main() -> anyhow::Result<()> { let mut client_builder = twilight_http::Client::builder().token( libpk::config .discord @@ -74,8 +74,7 @@ async fn real_main() -> anyhow::Result<()> { info!("running {}", $desc); let before = std::time::Instant::now(); if let Err(error) = $fn(ctx).await { - error!("failed to run {}: {}", $desc, error); - // sentry + error!(?error, "failed to run {}", $desc); } let duration = before.elapsed(); info!("ran {} in {duration:?}", $desc); diff --git a/crates/scheduled_tasks/src/tasks.rs b/crates/scheduled_tasks/src/tasks.rs index e0247768..64246fc9 100644 --- a/crates/scheduled_tasks/src/tasks.rs +++ b/crates/scheduled_tasks/src/tasks.rs @@ -10,7 +10,6 @@ use metrics::gauge; use num_format::{Locale, ToFormattedString}; use reqwest::ClientBuilder; use sqlx::Executor; -use tracing::error; use crate::AppCtx; @@ -19,11 +18,18 @@ pub async fn update_prometheus(ctx: AppCtx) -> anyhow::Result<()> { struct Count { count: i64, } + + let pending_count: Count = sqlx::query_as("select count(*) from image_cleanup_pending_jobs") + .fetch_one(&ctx.data) + .await?; + let count: Count = sqlx::query_as("select count(*) from image_cleanup_jobs") .fetch_one(&ctx.data) .await?; - gauge!("pluralkit_image_cleanup_queue_length").set(count.count as f64); + gauge!("pluralkit_image_cleanup_queue_length", "pending" => "true") + .set(pending_count.count as f64); + gauge!("pluralkit_image_cleanup_queue_length", "pending" => "false").set(count.count as f64); let gateway = ctx.discord.gateway().authed().await?.model().await?; @@ -93,12 +99,14 @@ pub async fn update_discord_stats(ctx: AppCtx) -> anyhow::Result<()> { let mut guild_count = 0; let mut channel_count = 0; + let mut url = cfg.gateway_url.clone(); for idx in 0..cfg.expected_gateway_count { - let res = client - .get(format!("http://cluster{idx}.{}/stats", cfg.gateway_url)) - .send() - .await?; + if url.contains("{clusterid}") { + url = url.replace("{clusterid}", &idx.to_string()); + } + + let res = client.get(&url).send().await?; let stat: GatewayStatus = res.json().await?; @@ -132,14 +140,10 @@ pub async fn update_discord_stats(ctx: AppCtx) -> anyhow::Result<()> { } pub async fn queue_deleted_image_cleanup(ctx: AppCtx) -> anyhow::Result<()> { - // todo: we want to delete immediately when system is deleted, but after a - // delay if member is deleted - ctx.data - .execute( - r#" -insert into image_cleanup_jobs -select id, now() from images where - not exists (select from image_cleanup_jobs j where j.id = images.id) + // if an image is present on no member, add it to the pending deletion queue + // if it is still present on no member after 24h, actually delete it + + let usage_query = r#" and not exists (select from systems where avatar_url = images.url) and not exists (select from systems where banner_image = images.url) and not exists (select from system_guild where avatar_url = images.url) @@ -151,9 +155,42 @@ select id, now() from images where and not exists (select from groups where icon = images.url) and not exists (select from groups where banner_image = images.url); + "#; + + ctx.data + .execute( + format!( + r#" + insert into image_cleanup_pending_jobs + select id, now() from images where + not exists (select from image_cleanup_pending_jobs j where j.id = images.id) + and not exists (select from image_cleanup_jobs j where j.id = images.id) + {} "#, + usage_query + ) + .as_str(), ) .await?; + + ctx.data + .execute( + format!( + r#" + insert into image_cleanup_jobs + select image_cleanup_pending_jobs.id from image_cleanup_pending_jobs + left join images on images.id = image_cleanup_pending_jobs.id + where + ts < now() - '24 hours'::interval + and not exists (select from image_cleanup_jobs j where j.id = images.id) + {} + "#, + usage_query + ) + .as_str(), + ) + .await?; + Ok(()) } @@ -164,6 +201,11 @@ pub async fn update_stats_api(ctx: AppCtx) -> anyhow::Result<()> { .build() .expect("error making client"); + let cfg = config + .scheduled_tasks + .as_ref() + .expect("missing scheduled_tasks config"); + #[derive(serde::Deserialize, Debug)] struct PrometheusResult { data: PrometheusResultData, @@ -179,39 +221,32 @@ pub async fn update_stats_api(ctx: AppCtx) -> anyhow::Result<()> { macro_rules! prom_instant_query { ($t:ty, $q:expr) => {{ + tracing::info!("Query: {}", $q); let resp = client - .get(format!( - "http://vm.svc.pluralkit.net/select/0/prometheus/api/v1/query?query={}", - $q - )) + .get(format!("{}/api/v1/query?query={}", cfg.prometheus_url, $q)) .send() .await?; let data = resp.json::().await?; - let error_handler = || { - error!("missing data at {}", $q); - }; + let error_handler = || anyhow::anyhow!("missing data at {}", $q); data.data .result .get(0) - .ok_or_else(error_handler) - .unwrap() + .ok_or_else(error_handler)? .value .clone() .get(1) - .ok_or_else(error_handler) - .unwrap() + .ok_or_else(error_handler)? .as_str() - .ok_or_else(error_handler) - .unwrap() + .ok_or_else(error_handler)? .parse::<$t>()? }}; ($t:ty, $q:expr, $wrap:expr) => {{ let val = prom_instant_query!($t, $q); let val = (val * $wrap).round() / $wrap; - format!("{:.2}", val).parse::().unwrap() + format!("{:.2}", val).parse::()? }}; } diff --git a/dashboard/src/routes/Status/status.svelte b/dashboard/src/routes/Status/status.svelte index 9f93ecf2..c31c88a1 100644 --- a/dashboard/src/routes/Status/status.svelte +++ b/dashboard/src/routes/Status/status.svelte @@ -26,7 +26,7 @@ const get = async () => { const pkdata = await api().private.discord.shard_state.get(); - let data = pkdata.shards.sort((x, y) => (x.id > y.id) ? 1 : -1); + let data = pkdata.shards.sort((x, y) => (x.shard_id < y.shard_id) ? 1 : -1); let latencies = 0; data = data.map(shard => { latencies += shard.latency; @@ -71,7 +71,7 @@ var match = findShardInput.match(/https:\/\/(?:[\w]*\.)?discord(?:app)?\.com\/channels\/(\d+)\/\d+\/\d+/); if (match != null) { console.log("match", match) - foundShard = shards[Number(getShardID(match[1], shards.length))]; + foundShard = shards[(shards.length - 1) - (Number(getShardID(match[1], shards.length)))]; valid = true; shardInfoMsg = ""; return; @@ -84,7 +84,7 @@ shardInfoMsg = "Invalid server ID"; return; } - foundShard = shards[Number(shard)]; + foundShard = shards[(shards.length - 1) - Number(shard)]; valid = true; shardInfoMsg = ""; } catch(e) { diff --git a/BRANCHRENAME.md b/dev-docs/BRANCHRENAME.md similarity index 100% rename from BRANCHRENAME.md rename to dev-docs/BRANCHRENAME.md diff --git a/LEGACYMIGRATE.md b/dev-docs/LEGACYMIGRATE.md similarity index 100% rename from LEGACYMIGRATE.md rename to dev-docs/LEGACYMIGRATE.md diff --git a/dev-docs/README.md b/dev-docs/README.md new file mode 100644 index 00000000..76199e09 --- /dev/null +++ b/dev-docs/README.md @@ -0,0 +1,67 @@ +# PluralKit development documentation + +Most of PluralKit's code is written in C#, and is split into 3 projects: `PluralKit.Core` (supporting libraries), `PluralKit.Bot` (the Discord bot), and `PluralKit.API` (ASP.NET webserver with controllers for most API endpoints). + +There is an ongoing effort to port this code to Rust, and we have a handful of crates already. +Currently, the main Rust services are: +- `gateway` - connects to Discord to receive events +- `api` - handles authentication and rate-limiting for the public API, as well as a couple of private endpoints +- `scheduled_tasks` - background cron job runner, for statistics and miscellaneous cleanup. +- `avatars` - handles avatar storage and cleanup +- `dispatch` - dispatches webhook events + +Additionally, `libpk` handles runtime configuration and database functions. + +At the very least, `PluralKit.Bot` and `gateway` are required for the bot to run. While code still exists to connect to the Discord gateway directly from the C# bot, this is no longer a supported configuration and may break in the future. + +Service-specific documentation can be found for the C# services in [dotnet.md](./dotnet.md), and for the Rust services in [rust.md](./rust.md). + +## Building/running + +PluralKit uses a PostgreSQL database and a Redis database to store data. User-provided data is stored in Postgres; Redis is used for internal state and transient data such as the command execution cache. It's generally easy to run these in Docker or with the Nix `process-compose`, but any install method should work. + +The production instance of PluralKit uses Docker images built in CI. These take a long time to rebuild and aren't good for development (they're production builds, so it's not possible to hook up a debugger). Instead, it's preferable to install build dependencies locally. This is easy with the provided Nix flake: run `nix develop .#bot` to drop into a shell with all the C# build dependencies available, and `nix develop .#services` to get a shell with the Rust build dependencies. It's also okay to manually install build dependencies if you prefer. + +PluralKit services are configured with environment variables; see service-specific documentation for details. Generally, the configuration from the self-host `docker-compose.yml` should get you started. + +### process-compose basic steps + +Your .env should contain at least the following for the bot to run (see the C#/Rust service specific docs for more on configuration): +``` +pluralkit__discord__bot_token="" +PluralKit__Bot__Token="" +pluralkit__discord__client_id="" +PluralKit__Bot__Client="" + +RUST_LOG="info" +pluralkit__db__db_password="postgres" +pluralkit__db__data_db_uri="postgresql://postgres@localhost:5432/pluralkit" +pluralkit__db__data_redis_addr="redis://localhost:6379" +pluralkit__discord__client_secret=1 +pluralkit__discord__max_concurrency=1 +pluralkit__discord__gateway_target="http://localhost:5002/events" +pluralkit__runtime_config_key=gateway +PluralKit__Database="Host=localhost;Port=5432;Username=postgres;Password=postgres;Database=pluralkit" +PluralKit__RedisAddr="localhost:6379" +PluralKit__Bot__DisableGateway="true" +PluralKit__Bot__EventAwaiterTarget="http://localhost:5002/events" +PluralKit__Bot__HttpListenerAddr="127.0.0.1" +PluralKit__Bot__HttpCacheUrl="localhost:5000" +``` + +1. Clone the repository: `git clone --recurse-submodules https://github.com/PluralKit/PluralKit` +2. Create a `.env` configuration file in the `PluralKit` directory *(see above)* +3. Build and run: `nix run .#dev` + - This will download the dependencies, build, and run PluralKit + - If Nix is not setup to allow flakes, you may need to add `--extra-experimental-features nix-command --extra-experimental-features flakes` to the command + - If the `pluralkit-bot` process fails to run, you can restart it by selecting it and pressing `Ctrl-R` +``` +[nix-shell:~]$ git clone --recurse-submodules https://github.com/PluralKit/PluralKit +[nix-shell:~]$ cd PluralKit +[nix-shell:~/PluralKit]$ nano .env +[nix-shell:~/PluralKit]$ nix run .#dev +``` + +## Upgrading database from legacy version +If you have an instance of the Python version of the bot (from the `legacy` branch), you may need to take extra database migration steps. +For more information, see [LEGACYMIGRATE.md](./LEGACYMIGRATE.md). diff --git a/dev-docs/dotnet.md b/dev-docs/dotnet.md new file mode 100644 index 00000000..b4f99276 --- /dev/null +++ b/dev-docs/dotnet.md @@ -0,0 +1,39 @@ +## Configuration +Configuration was previously done through a JSON configuration file `pluralkit.conf` placed in the bot's working directory. To simplify things however and maintain consistency with the Rust services, it is now recommended to use environment variables. + +The minimum configuration needed for the dotnet part of the bot to function include the following: +- **`PluralKit__Bot__Token`**: the Discord bot token to connect with +- **`PluralKit__Bot__ClientId`**: the ID of the bot's user account, used for calculating the bot's own permissions and for the link in `pk;invite` +- **`PluralKit__Database`**: the URI of the PostgreSQL database to connect to (in [ADO.NET Npgsql format](https://www.connectionstrings.com/npgsql/)) +- **`PluralKit__RedisAddr`**: the `host:port` of the Redis database to connect to + +**When using Nix, the Database URI Username, Password, and Database fields must match what the database was setup with in the `flake.nix` file!** + + +### Available Configuration Values: + +| Name | Description | Rust Equivalent (if applicable) | +| ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------- | +| **`PluralKit__Api__ClientId`** | the ID of the bot's user account, used for OAuth with Discord | *`pluralkit__discord__client_id`* | +| **`PluralKit__Api__ClientSecret`** | the client secret of the application, used for OAuth with Discord | *`pluralkit__discord__client_secret`* | +| **`PluralKit__Api__TrustAuth`** | boolean used to determine if the API should trust upstream to provide it the system id of the authenticated user | | +| **`PluralKit__Bot__AdminRole`** | Discord role ID used to determine if a user can use `pk;admin` commands | | +| **`PluralKit__Bot__AvatarServiceUrl`** | the URL of the avatar service | | +| **`PluralKit__Bot__ClientId`** | the ID of the bot's user account, used for calculating the bot's own permissions and for the link in `pk;invite`. | *`pluralkit__discord__client_id`* | +| **`PluralKit__Bot__Cluster__TotalShards`** | the total number of shards | *`pluralkit__discord__cluster__total_shards`* | +| **`PluralKit__Bot__DisableGateway`** | (deprecated) boolean used to enable or disable the inbuilt gateway functions, should be true if using Rust `gateway` | | +| **`PluralKit__Bot__DiscordBaseUrl`** | the base Discord API url used for HTTP API requests | *`pluralkit__discord__api_base_url`* | +| **`PluralKit__Bot__EventAwaiterTarget`** | the target bind address used to receive bot-instance specific events (such as interactive prompts/menus) from `gateway` over http -- value should generally be `source-addr`. | | +| **`PluralKit__Bot__HttpCacheUrl`** | the URL of the http cache to use, as of now, the `gateway` service | | +| **`PluralKit__Bot__HttpListenerAddr`** | the base bind address (use `allv4v6` instead of `::` if you want to also bind to `0.0.0.0`) | | +| **`PluralKit__Bot__Token`** | the Discord bot token to connect with | *`pluralkit__discord__bot_token`* | +| **`PluralKit__Database`** | the URI of the database to connect to (in [ADO.NET Npgsql format](https://www.connectionstrings.com/npgsql/)) | *`pluralkit__db__data_db_uri`* (diff formatting) | +| **`PluralKit__MessagesDatabase`** | the URI of the database for message storage to connect to (in [ADO.NET Npgsql format](https://www.connectionstrings.com/npgsql/)) | *`pluralkit__db__messages_db_uri`* (diff formatting) | +| **`PluralKit__RedisAddr`** | the `host:port` of a Redis database to connect to | *`pluralkit__db__data_redis_addr`* (diff formatting) | +| **`PluralKit__DispatchProxyToken`** | the token used to authenticate with the dispatch proxy service | | +| **`PluralKit__DispatchProxyUrl`** | the URL of the dispatch proxy service | | +| **`PluralKit__ConsoleLogLevel`** | the minimum [log level](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=net-9.0-pp) used for logging | | +| **`PluralKit__InfluxUrl`** | the URL to an [InfluxDB](https://www.influxdata.com/products/influxdb-overview/) server to report aggregate statistics to. An example of these stats can be seen on [the public stats page](https://stats.pluralkit.me). | | +| **`PluralKit__InfluxDb`** | the name of an InfluxDB database to report statistics to. If either this field or `PluralKit.InfluxUrl` are absent, InfluxDB reporting will be disabled. | | +| **`PluralKit__SentryUrl`** | the [Sentry](https://sentry.io/welcome/) client key/DSN to report runtime errors to. If absent, disables Sentry integration. | | +| **`PluralKit__Bot__Prefixes__0`** | A custom prefix to use instead of `pk;`, add additional entries replacing 0 with n for more prefixes | *`pluralkit__discord__bot_prefix_for_gateway`* (only first) | diff --git a/dev-docs/rust.md b/dev-docs/rust.md new file mode 100644 index 00000000..4c504101 --- /dev/null +++ b/dev-docs/rust.md @@ -0,0 +1,48 @@ +## Services Overview +TODO: write more about what each service does (and quirks) + +## Configuration +Configuration is done through environment variables. A potentially uncompleted and/or outdated list is as follows: + +#### Key: +- G - gateway +- A - api +- ST - scheduled_tasks +- AV - avatars +- D - dispatch + +| Used by: | Name | Description | +| --------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| G, ST | **`pluralkit__discord__bot_token`** | the Discord bot token to connect with | +| G, A | **`pluralkit__discord__client_id`** | the ID of the bot's user account, used for calculating the bot's own permissions and for the link in `pk;invite`. | +| A | **`pluralkit__discord__client_secret`** | the client secret of the application, used for OAuth with Discord | +| G | **`pluralkit__discord__cluster__total_shards`** | the total number of shards | +| G | **`pluralkit__discord__cluster__total_nodes`** | the total number of clusters | +| G | **`pluralkit__discord__cluster__node_id`** | the ID of the cluster (overwritten at runtime when operating under managers that can't template the node id into this variable, such as kubernetes) | +| G | **`pluralkit__discord__max_concurrency`** | number of identify requests per 5 seconds -- see Discord docs | +| G | **`pluralkit__discord__gateway_target`** | the URL of a dotnet bot instance to send events to | +| G | **`pluralkit__discord__bot_prefix_for_gateway`** | the prefix to show in the bot's activity status. If not specified will use `pk;` | +| G, ST | **`pluralkit__discord__api_base_url`** | the base Discord API url used for HTTP API requests | +| G, A, ST, AV | **`pluralkit__db__data_db_uri`** | the URI of the PostgreSQL data database in [libpq format](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING) | +| G, A, ST, AV | **`pluralkit__db__data_redis_addr`** | the address of the Redis instance, in [standard Redis format](https://redis.io/docs/latest/develop/clients/nodejs/connect/) | +| G, A, ST, AV | **`pluralkit__db__db_password`** | the password to use for PostgreSQL database connections | +| G, A, ST, AV | **`pluralkit__db__messages_db_uri`** | the URI of the PostgreSQL messages database in [libpq format](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING) | +| G, A, ST, AV | **`pluralkit__db__stats_db_uri`** | the URI of the PostgreSQL statistics database in [libpq format](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING) | +| ST | **`pluralkit__scheduled_tasks__expected_gateway_count`** | the total number of expected running gateway instances | +| ST | **`pluralkit__scheduled_tasks__gateway_url`** | the base URL used for querying statistics from gateway instances | +| ST | **`pluralkit__scheduled_tasks__set_guild_count`** | boolean used to determine if the guild count should be updated in Redis for the bot status | +| A | **`pluralkit__api__addr`** | the bind address used for the Rust API | +| A | **`pluralkit__api__ratelimit_redis_addr`** | the address of a Redis instance to use for request ratelimiting | +| A | **`pluralkit__api__remote_url`** | the remote url of the dotnet API instance | +| A | **`pluralkit__api__temp_token2`** | the token used in the API for fetching app authorization | +| AV | **`pluralkit__avatars__cdn_url`** | the CDN address used for avatar storage | +| AV | **`pluralkit__avatars__cloudflare_token`** | the Cloudflare token to use for avatar cache cleanup | +| AV | **`pluralkit__avatars__cloudflare_zone_id`** | the Cloudflare zone id to use for avatar cache cleanup | +| AV | **`pluralkit__avatars__s3__application_id`** | the application id of the s3 instance to use for avatar storage | +| AV | **`pluralkit__avatars__s3__application_key`** | the application key of the s3 instance to use for avatar storage | +| AV | **`pluralkit__avatars__s3__bucket`** | the bucket to use for avatar storage | +| AV | **`pluralkit__avatars__s3__endpoint`** | the endpoint URL of the s3 instance to use for avatar storage | +| G, A, ST, AV, D | **`pluralkit__json_log`** | boolean used to enable or disable JSON log formatting | +| G | **`pluralkit__runtime_config_key`** | the instance identifier key used when fetching configuration from Redis at runtime to differentiate gateway instances (ex. 'gateway') | +| G, A, ST, AV, D | **`pluralkit__run_metrics_server`** | boolean used to enable or disable the inbuilt Prometheus format metrics server | +| G, A, ST, AV, D | **`pluralkit__sentry_url`** | the URL of a sentry instance to publish errors to | diff --git a/docker-compose.yml b/docker-compose.yml index 161fd133..e2caa46a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,52 +1,63 @@ -# API port: 2838, InfluxDB port: 2839, both listen on localhost only -# Reads `pluralkit.conf` from current directory, and logs to `/var/log/pluralkit`. - version: "3" services: + migrate: + build: + context: . + dockerfile: ci/Dockerfile.rust + environment: + - RUST_LOG=info + - pluralkit__db__data_db_uri=postgresql://postgres:postgres@db:5432/postgres + - pluralkit__db__data_redis_addr=1 + command: ["/migrate"] + depends_on: ["db"] + bot: - image: pluralkit # This image is reused in the other containers due to the - build: . # build instruction right here + build: + context: . + dockerfile: ci/Dockerfile.dotnet command: ["bin/PluralKit.Bot.dll"] environment: - - "PluralKit:Database=Host=db;Username=postgres;Password=postgres;Database=postgres;Maximum Pool Size=1000" - - "PluralKit:RedisAddr=redis" - - "PluralKit:InfluxUrl=http://influx:8086" - - "PluralKit:InfluxDb=pluralkit" - - "PluralKit:LogDir=/var/log/pluralkit" - volumes: - - "./pluralkit.conf:/app/pluralkit.conf:ro" - - "/var/log/pluralkit:/var/log/pluralkit" + - "PluralKit__Database=Host=db;Username=postgres;Password=postgres;Database=postgres" + - "PluralKit__RedisAddr=redis" + - "PluralKit__Bot__Token=${BOT_TOKEN}" + - "PluralKit__Bot__ClientId=${CLIENT_ID}" + - "PluralKit__Bot__AdminRole=${ADMIN_ROLE}" + - "PluralKit__Bot__HttpCacheUrl=gateway:5000" + - "PluralKit__Bot__HttpListenerAddr=0.0.0.0" + - "PluralKit__Bot__EventAwaiterTarget=http://bot:5002/events" + - "PluralKit__Bot__DisableGateway=true" restart: unless-stopped + depends_on: + migrate: + condition: service_completed_successfully - api: - image: pluralkit - command: ["bin/PluralKit.API.dll"] + gateway: + build: + context: . + dockerfile: ci/Dockerfile.rust + command: ["/gateway"] environment: - - "PluralKit:Database=Host=db;Username=postgres;Password=postgres;Database=postgres;Maximum Pool Size=1000" - - "PluralKit:RedisAddr=redis" - ports: - - "127.0.0.1:2838:5000" - restart: unless-stopped - - scheduled_tasks: - image: pluralkit - command: ["bin/PluralKit.ScheduledTasks.dll"] - environment: - - "PluralKit:Database=Host=db;Username=postgres;Password=postgres;Database=postgres;Maximum Pool Size=1000" + - RUST_LOG=info + - pluralkit__discord__client_id=${CLIENT_ID} + - pluralkit__discord__bot_token=${BOT_TOKEN} + - pluralkit__discord__max_concurrency=1 + - pluralkit__discord__gateway_target=http://bot:5002/events + - pluralkit__db__data_db_uri=postgresql://postgres:postgres@db:5432/postgres + - pluralkit__db__data_redis_addr=redis://redis:6379 + - pluralkit__api__temp_token2=1 + - pluralkit__api__remote_url=1 + - pluralkit__api__ratelimit_redis_addr=1 + - pluralkit__discord__client_secret=1 + - pluralkit__runtime_config_key=gateway + depends_on: + - redis restart: unless-stopped db: - image: postgres:12-alpine + image: postgres:17-alpine volumes: - "db_data:/var/lib/postgresql/data" - - "/var/run/postgresql:/var/run/postgresql" - command: ["postgres", - "-c", "max-connections=1000", - "-c", "timezone=Etc/UTC", - "-c", "max_wal_size=1GB", - "-c", "min_wal_size=80MB", - "-c", "shared_buffers=128MB"] environment: - "POSTGRES_PASSWORD=postgres" restart: unless-stopped @@ -55,14 +66,5 @@ services: image: redis:alpine restart: unless-stopped - influx: - image: influxdb:1.8 - volumes: - - "influx_data:/var/lib/influxdb" - ports: - - 127.0.0.1:2839:8086 - restart: unless-stopped - volumes: db_data: - influx_data: diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100644 index 00000000..a13a59fb --- /dev/null +++ b/docs/Dockerfile @@ -0,0 +1,18 @@ +FROM alpine:latest AS builder + +RUN apk add nodejs-current yarn + +WORKDIR /build +COPY . . +RUN yarn +ENV NODE_OPTIONS=--openssl-legacy-provider +RUN yarn build + +FROM alpine:latest + +RUN apk add caddy + +WORKDIR /app +COPY --from=builder /build/content/.vuepress/dist /app + +ENTRYPOINT [ "/usr/sbin/caddy", "file-server", "-l", "0.0.0.0:8000", "-r", "/app" ] diff --git a/docs/content/.vuepress/config.js b/docs/content/.vuepress/config.js index 25e14ac0..5bdc945e 100644 --- a/docs/content/.vuepress/config.js +++ b/docs/content/.vuepress/config.js @@ -82,7 +82,6 @@ module.exports = { }, ["https://discord.gg/PczBt78", "Join the support server"], ], - pkBannerContent: "PluralKit needs your help! Check out our latest funding update for details.", }, plugins: [ diff --git a/docs/content/api/endpoints.md b/docs/content/api/endpoints.md index 908af0bb..a9e48dbe 100644 --- a/docs/content/api/endpoints.md +++ b/docs/content/api/endpoints.md @@ -34,6 +34,8 @@ GET `/systems/{systemRef}/settings` Returns a [system settings object](/api/models#system-settings-model). +If not authenticated, or authenticated as a different system, returns a [public system settings object](/api/models#public-system-settings-model). + ### Update System Settings PATCH `/systems/{systemRef}/settings` @@ -137,6 +139,8 @@ Returns 204 No Content on success. GET `/members/{memberRef}/groups` +Returns an array of [group objects](/api/models/#group-model). + ### Add Member To Groups POST `/members/{memberRef}/groups/add` @@ -277,6 +281,8 @@ GET `/systems/{systemRef}/fronters` Returns a [switch object](/api/models#switch-model) containing a list of member objects. +If the target system has no registered switches, returns 204 status code with no content. + ### Create Switch POST `/systems/{systemRef}/switches` @@ -292,6 +298,8 @@ JSON Body Parameters ** Can be short IDs or UUIDs. +Returns a [switch object](/api/models#switch-model) containing a list of member objects. + ### Get Switch GET `/systems/{systemRef}/switches/{switchRef}` diff --git a/docs/content/api/models.md b/docs/content/api/models.md index ad81f837..65fba699 100644 --- a/docs/content/api/models.md +++ b/docs/content/api/models.md @@ -16,6 +16,8 @@ Privacy objects (`privacy` key in models) contain values "private" or "public". Every PluralKit entity has two IDs: a short (5 or 6 character) ID and a longer UUID. The short ID is unique across the resource (a member can have the same short ID as a system, for example), while the UUID is consistent for the lifetime of the entity and globally unique across the bot. +The PluralKit Discord bot can be configured to display short IDs in uppercase, or (in the case of 6-character IDs) as two parts of 3 characters separated by a dash (for example, `EXA-MPL`). For convenience, IDs are accepted by the API in any format displayable by the bot. + ### System model |key|type|notes| @@ -29,10 +31,10 @@ Every PluralKit entity has two IDs: a short (5 or 6 character) ID and a longer U |avatar_url|?string|256-character limit, must be a publicly-accessible URL| |banner|?string|256-character limit, must be a publicly-accessible URL| |color|?string|6-character hex code, no `#` at the beginning| -|created|?datetime|| +|created|datetime|| |privacy|?system privacy object|| -* System privacy keys: `description_privacy`, `pronoun_privacy`, `member_list_privacy`, `group_list_privacy`, `front_privacy`, `front_history_privacy` +* System privacy keys: `name_privacy`, `description_privacy`, `avatar_privacy`, `banner_privacy`, `pronoun_privacy`, `member_list_privacy`, `group_list_privacy`, `front_privacy`, `front_history_privacy` ### Member model @@ -59,7 +61,7 @@ Every PluralKit entity has two IDs: a short (5 or 6 character) ID and a longer U |last_message_timestamp|?datetime|| |privacy|?member privacy object|| -* Member privacy keys: `visibility`, `name_privacy`, `description_privacy`, `birthday_privacy`, `pronoun_privacy`, `avatar_privacy`, `metadata_privacy`, `proxy_privacy` +* Member privacy keys: `visibility`, `name_privacy`, `description_privacy`, `birthday_privacy`, `pronoun_privacy`, `avatar_privacy`, `banner_privacy`, `metadata_privacy`, `proxy_privacy` #### ProxyTag object @@ -80,12 +82,13 @@ Every PluralKit entity has two IDs: a short (5 or 6 character) ID and a longer U |name|string|100-character limit| |display_name|?string|100-character limit| |description|?string|1000-character limit| +|created|?datetime|| |icon|?string|256-character limit, must be a publicly-accessible URL| |banner|?string|256-character limit, must be a publicly-accessible URL| |color|?string|6-character hex code, no `#` at the beginning| |privacy|?group privacy object|| -* Group privacy keys: `name_privacy`, `description_privacy`, `icon_privacy`, `list_privacy`, `metadata_privacy`, `visibility` +* Group privacy keys: `name_privacy`, `description_privacy`, `banner_privacy`, `icon_privacy`, `list_privacy`, `metadata_privacy`, `visibility` ### Switch model @@ -113,16 +116,55 @@ Every PluralKit entity has two IDs: a short (5 or 6 character) ID and a longer U |key|type|notes| |---|---|---| |timezone|string|defaults to `UTC`| -|pings_enabled|boolean| -|latch_timeout|int?| +|pings_enabled|boolean|whether proxied messages can be pinged using the :bell: reaction| +|latch_timeout|int?|seconds after which latch autoproxy will timeout (null/default is 6 hours, 0 is "never")| |member_default_private*|boolean|whether members created through the bot have privacy settings set to private by default| |group_default_private*|boolean|whether groups created through the bot have privacy settings set to private by default| |show_private_info|boolean|whether the bot shows the system's own private information without a `-private` flag| |member_limit|int|read-only, defaults to 1000| |group_limit|int|read-only, defaults to 250| +|case_sensitive_proxy_tags|bool|whether the bot will match proxy tags matching only the case used in the trigger message| +|proxy_error_message_enabled|bool|whether the bot will show errors when proxying fails| +|hid_display_split|bool|whether 6-character ids will be shown by the bot as two 3-character parts separated by a `-`| +|hid_display_caps|bool|whether ids will be shown by the bot in uppercase| +|hid_list_padding|[ID padding format](#id-padding-format-enum)|whether the bot will pad 5-character ids in lists| +|proxy_switch|[proxy switch action](#proxy-switch-action-enum)|switch action the bot will take when proxying| +|name_format|string|format used for webhook names during proxying (defaults to `{name} {tag}`)| \* this *does not* affect members/groups created through the API - please specify privacy keys in the JSON payload instead +#### ID padding format enum + +|key|description| +|---|---| +|off|do not pad 5-character ids| +|left|add a padding space to the left of 5-character ids in lists| +|right|add a padding space to the right of 5-character ids in lists| + +#### Proxy switch action enum + +|key|description| +|---|---| +|off|do nothing| +|new|if the currently proxied member is not present in the current switch, log a new switch with this member| +|add|if the current switch has 0 members, log a new switch with the currently proxied member; otherwise, add the member to the current switch| + +### Public system settings model + +This model is used when querying system settings without authenticating, or for a system other than the one you are authenticated as. + +|key|type|notes| +|---|---|---| +|pings_enabled|boolean|whether proxied messages can be pinged using the :bell: reaction| +|latch_timeout|int?|seconds after which latch autoproxy will timeout (null/default is 6 hours, 0 is "never")| +|case_sensitive_proxy_tags|bool|whether the bot will match proxy tags matching only the case used in the trigger message| +|proxy_error_message_enabled|bool|whether the bot will show errors when proxying fails| +|hid_display_split|bool|whether 6-character ids will be shown by the bot as two 3-character parts separated by a `-`| +|hid_display_caps|bool|whether ids will be shown by the bot in uppercase| +|hid_list_padding|[ID padding format](#id-padding-format-enum)|whether the bot will pad 5-character ids in lists| +|proxy_switch|[proxy switch action](#proxy-switch-action-enum)|switch action the bot will take when proxying| +|name_format|string|format used for webhook names during proxying (defaults to `{name} {tag}`)| + ### System guild settings model |key|type|notes| @@ -131,6 +173,8 @@ Every PluralKit entity has two IDs: a short (5 or 6 character) ID and a longer U |proxying_enabled|boolean|| |tag|?string|79-character limit| |tag_enabled|boolean|| +|avatar_url|?string|256-character limit| +|display_name|?string|100-character limit| ### Autoproxy settings model @@ -155,7 +199,7 @@ Every PluralKit entity has two IDs: a short (5 or 6 character) ID and a longer U |key|type|notes| |---|---|---| -|guild_id|snowflake|only sent if the guild ID isn't already known (in dispatch payloads)| +|?guild_id|snowflake|only sent if the guild ID isn't already known (in dispatch payloads)| |display_name|?string|100-character limit| |avatar_url|?string|256-character limit, must be a publicly-accessible URL| |keep_proxy|?boolean|| diff --git a/docs/content/staff/compatibility.md b/docs/content/staff/compatibility.md index 77273daa..8d0c6fc5 100644 --- a/docs/content/staff/compatibility.md +++ b/docs/content/staff/compatibility.md @@ -42,6 +42,7 @@ At the moment, log cleanup works with the following bots: - [UnbelievaBoat](https://unbelievaboat.com/) (precise) - Vanessa (fuzzy) - [Vortex](https://github.com/jagrosh/Vortex/wiki) (fuzzy) +- [Zeppelin](https://zeppelin.gg/) (precise) ::: warning In most cases, PluralKit will match log messages by the ID of the deleted message itself. However, some bots (marked with *(fuzzy)* above) don't include this in their logs. In this case, PluralKit will attempt to match based on other parameters, but there may be false positives. diff --git a/docs/content/user-guide.md b/docs/content/user-guide.md index a4b7d5c8..51654841 100644 --- a/docs/content/user-guide.md +++ b/docs/content/user-guide.md @@ -212,9 +212,9 @@ a display name using the `pk;member displayname` command, like so: pk;member John displayname Jonathan pk;member Robert displayname Bob (he/him) -To remove a display name, just use the same command with no last parameter, eg: +To remove a display name, use the same command with `-clear` as the parameter, eg: - pk;member John displayname + pk;member John displayname -clear This will remove the display name, and thus the member will be proxied with their canonical name. diff --git a/docs/fly.toml b/docs/fly.toml new file mode 100644 index 00000000..c85d5812 --- /dev/null +++ b/docs/fly.toml @@ -0,0 +1,3 @@ +app = "pluralkit-docs" +primary_region = "arn" +http_service.internal_port = 8000 diff --git a/netlify.toml b/netlify.toml deleted file mode 100644 index 9a00fb7f..00000000 --- a/netlify.toml +++ /dev/null @@ -1,6 +0,0 @@ -# Configuration for Netlify (website deployment, etc) - -[build] -base = "docs/" -publish = "content/.vuepress/dist" -command = "npm run build" diff --git a/pluralkit.conf.example b/pluralkit.conf.example deleted file mode 100644 index 44ce1ef0..00000000 --- a/pluralkit.conf.example +++ /dev/null @@ -1,10 +0,0 @@ -{ - "PluralKit": { - "Bot": { - "Token": "BOT_TOKEN_GOES_HERE", - "ClientId": 466707357099884544, - }, - "Database": "Host=localhost;Port=5432;Username=myusername;Password=mypassword;Database=mydatabasename", - "RedisAddr": "host:port" - } -} \ No newline at end of file