diff --git a/.env.example b/.env.example deleted file mode 100644 index 17fda83..0000000 --- a/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -TOKEN="TOKEN" - -CHANNEL_ID=100000000000000000000 -LOG_CHANNEL_ID=100000000000000000000 -ROLE_ID=100000000000000000000 -TRIGGER_REGEX="[おオオ][のノノ][れレレ][GgGg][RrRr][EeEe][GgGg]" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6e2fadd --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,98 @@ +name: Build + +permissions: + contents: write + packages: write + +on: + push: + tags-ignore: + - "**" + branches: + - "**" + pull_request: + workflow_dispatch: + +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + build: + name: Release binary + strategy: + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + glibc: 2.17 + - os: ubuntu-latest + target: aarch64-unknown-linux-gnu + glibc: 2.17 + + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + # Rustのpackage名を取得して環境変数に入れておく。(後のステップで使用) + - name: Extract crate information + shell: bash + run: | + echo "PROJECT_NAME=$(sed -n 's/^name = "\(.*\)"/\1/p' Cargo.toml | head -n1)" >> $GITHUB_ENV + + # zigをインストール + - name: Install Zig + uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 + + # rustをインストール + - name: Install Rust toolchain + uses: moonrepo/setup-rust@abb2d32350334249b178c401e5ec5836e0cd88d3 # v1.3.0 + with: + cache-target: release + cache-extra-identifier: ${{ matrix.target }} + targets: ${{ matrix.target }} + bins: cargo-zigbuild,cargo-about + env: + GITHUB_TOKEN: ${{ github.token }} + + # moldをインストール + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + + # ビルド + - name: Build with glibc version + if: matrix.glibc != -1 + run: | + cargo zigbuild --release --target ${{ matrix.target }}.${{ matrix.glibc }} + + - name: Build without glibc version + if: matrix.glibc == -1 + run: | + cargo build --release --target ${{ matrix.target }} + + # ビルド済みバイナリをコピーしてリネーム + - name: Copy and rename artifacts + shell: bash + run: | + mkdir -p dist + cp target/${{ matrix.target }}/release/${{ env.PROJECT_NAME }} dist/${{ matrix.target }} + cp LICENSE dist/LICENSE + + - name: Generate third-party license notices + run: | + cargo about generate about.hbs \ + --locked \ + --fail \ + --target ${{ matrix.target }} \ + -o dist/THIRD_PARTY_LICENSES-${{ matrix.target }} + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ matrix.target }} + path: dist/* diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9ae26bf..6a6203b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,11 +2,12 @@ name: Release permissions: contents: write + packages: write on: - push: - tags: - - v* + release: + types: + - published jobs: build: @@ -17,16 +18,16 @@ jobs: - os: ubuntu-latest target: x86_64-unknown-linux-gnu glibc: 2.17 - extension: "" - os: ubuntu-latest target: aarch64-unknown-linux-gnu glibc: 2.17 - extension: "" runs-on: ${{ matrix.os }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 # Rustのpackage名を取得して環境変数に入れておく。(後のステップで使用) - name: Extract crate information @@ -36,14 +37,21 @@ jobs: # zigをインストール - name: Install Zig - uses: goto-bus-stop/setup-zig@v2 + uses: mlugg/setup-zig@d1434d08867e3ee9daa34448df10607b98908d29 # v2.2.1 # rustをインストール - name: Install Rust toolchain - uses: moonrepo/setup-rust@v1 + uses: moonrepo/setup-rust@abb2d32350334249b178c401e5ec5836e0cd88d3 # v1.3.0 with: + cache-target: release + cache-extra-identifier: ${{ matrix.target }} targets: ${{ matrix.target }} - bins: cargo-zigbuild + bins: cargo-zigbuild,cargo-about + env: + GITHUB_TOKEN: ${{ github.token }} + + # moldをインストール + - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 # ビルド - name: Build with glibc version @@ -56,17 +64,87 @@ jobs: run: | cargo build --release --target ${{ matrix.target }} - # ビルド済みバイナリをリネーム - - name: Rename artifacts + # ビルド済みバイナリをコピーしてリネーム + - name: Copy and rename artifacts shell: bash run: | - mv target/${{ matrix.target }}/release/${{ env.PROJECT_NAME }}{,-${{ github.ref_name }}-${{ matrix.target }}${{ matrix.extension }}} + mkdir -p dist + cp target/${{ matrix.target }}/release/${{ env.PROJECT_NAME }} dist/${{ env.PROJECT_NAME }}-${{ github.ref_name }}-${{ matrix.target }} + cp LICENSE dist/LICENSE + + - name: Generate third-party license notices + run: | + cargo about generate about.hbs \ + --locked \ + --fail \ + --target ${{ matrix.target }} \ + -o dist/THIRD_PARTY_LICENSES-${{ matrix.target }} + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: ${{ matrix.target }} + path: dist/* - # ビルド済みバイナリをReleasesに配置 - - name: Release - uses: softprops/action-gh-release@v2 + # ビルド済みバイナリとライセンス通知をReleasesに配置 + - name: Release binary and target license notices + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: files: | - target/${{ matrix.target }}/release/${{ env.PROJECT_NAME }}-${{ github.ref_name }}-${{ matrix.target }}${{ matrix.extension }} + dist/${{ env.PROJECT_NAME }}-${{ github.ref_name }}-${{ matrix.target }} + dist/THIRD_PARTY_LICENSES-${{ matrix.target }} + + - name: Release project license + if: matrix.target == 'x86_64-unknown-linux-gnu' + uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: dist/LICENSE + + docker: + runs-on: ubuntu-latest + needs: build + name: Build and push Docker image + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0 + + - name: Download artifacts + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: dist + merge-multiple: true + + - name: Login to GitHub Container Registry + uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata for Docker + id: meta + uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0 + with: + images: ghcr.io/${{ github.repository }} + tags: | + type=sha,enable=false + type=semver,pattern={{version}} + + - name: Build and push Docker image + id: push + uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0 + with: + context: . + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + platforms: linux/amd64,linux/arm64 diff --git a/.gitignore b/.gitignore index 83a00ee..58dbb17 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /target +/dist +/THIRD_PARTY_LICENSES* .env +config.toml* diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5dec177 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "editor.formatOnSave": true, + "[rust]": { + "editor.defaultFormatter": "rust-lang.rust-analyzer" + }, + "[toml]": { + "editor.defaultFormatter": "tamasfe.even-better-toml", + }, + "evenBetterToml.formatter.compactArrays": false, + "evenBetterToml.formatter.compactInlineTables": false, + "evenBetterToml.formatter.compactEntries": false +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a445066..b817e4a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,37 +1,46 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] -name = "addr2line" -version = "0.22.0" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aformat" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f387c59d52324934bdd3586fe904051338ce4583a9bb921982a3dbb060a26e6f" dependencies = [ - "gimli", + "aformat-macros", + "to-arraystring", + "typenum", + "typenum_mappings", ] [[package]] -name = "adler" -version = "1.0.2" +name = "aformat-macros" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "254adeba6d67e7e6706f01ffdf1787cdad41e361be5b7c1e3265bba54dca7d8f" +dependencies = [ + "bytestring", + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -41,52 +50,87 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" dependencies = [ "serde", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "async-trait" -version = "0.1.80" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.117", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" -version = "1.3.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] -name = "backtrace" -version = "0.3.73" +name = "aws-lc-rs" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00" dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", + "aws-lc-sys", + "zeroize", ] [[package]] -name = "base64" -version = "0.21.7" +name = "aws-lc-sys" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] [[package]] name = "base64" @@ -96,15 +140,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.5.0" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block-buffer" @@ -116,101 +154,151 @@ dependencies = [ ] [[package]] -name = "bumpalo" -version = "3.16.0" +name = "bool_to_bitflags" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "c7c039d9bc676b768f6d59556e99f95f5e47c811b672f8b2b2b606eb28527a2f" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", + "to-arraystring", +] [[package]] -name = "bytecount" -version = "0.6.8" +name = "bpaf" +version = "0.9.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" +checksum = "0b86829876e7e200161a5aa6ea688d46c32d64d70ee3100127790dde84688d6e" +dependencies = [ + "bpaf_derive", +] [[package]] -name = "byteorder" -version = "1.5.0" +name = "bpaf_derive" +version = "0.5.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "2f7e98cee839b19076cb3ce1afdb62bb182e04ff5f71f70188827002fae91094" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] [[package]] -name = "bytes" -version = "1.6.0" +name = "bs58" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] [[package]] -name = "camino" -version = "1.1.7" +name = "bstr" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ + "memchr", "serde", ] [[package]] -name = "cargo-platform" -version = "0.1.8" +name = "bumpalo" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" -dependencies = [ - "serde", -] +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] -name = "cargo_metadata" -version = "0.14.2" +name = "bytestring" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +checksum = "86566c496f2f47d9b8147a4c8b02ffdb69c919fe0c2b2e7195d22cbba0e635c9" dependencies = [ - "camino", - "cargo-platform", - "semver", - "serde", - "serde_json", + "bytes", ] [[package]] name = "cc" -version = "1.0.99" +version = "1.2.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chacha20" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ - "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", "serde", - "windows-targets 0.52.5", + "wasm-bindgen", + "windows-link", ] [[package]] -name = "command_attr" -version = "0.5.2" +name = "cmake" +version = "0.1.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88da8d7e9fe6f30d8e3fcf72d0f84102b49de70fece952633e8439e89bdc7631" +checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", + "cc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", ] [[package]] name = "core-foundation" -version = "0.9.4" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" dependencies = [ "core-foundation-sys", "libc", @@ -218,61 +306,131 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] -name = "crc32fast" -version = "1.4.2" +name = "cpufeatures" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" dependencies = [ - "cfg-if", + "libc", ] [[package]] -name = "crossbeam-channel" -version = "0.5.13" +name = "crc32fast" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "crossbeam-utils", + "cfg-if", ] [[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 = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "typenum", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.117", +] + [[package]] name = "dashmap" -version = "5.5.3" +version = "6.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "e6361d5c062261c78a176addb82d4c821ae42bed6089de0e12603cd25de2059c" dependencies = [ "cfg-if", - "hashbrown", + "crossbeam-utils", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -281,18 +439,29 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" [[package]] name = "deranged" -version = "0.3.11" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" dependencies = [ "powerfmt", - "serde", + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", ] [[package]] @@ -305,71 +474,100 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "objc2", +] + [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.117", ] [[package]] -name = "dotenvy" -version = "0.15.7" +name = "dunce" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" [[package]] -name = "encoding_rs" -version = "0.8.34" +name = "duration-str" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "027cd1402a609c71a9ac333c7e3d90ee042e7a131da1a83da8c60df323f12f61" dependencies = [ - "cfg-if", + "chrono", + "rust_decimal", + "serde", + "thiserror", + "time", + "winnow 0.7.15", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + [[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.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] -name = "error-chain" -version = "0.12.4" +name = "extract_map" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +checksum = "a8855baff5d450715f5d34c1d291a8c77363bd5a20ddacf560d7d6ea2a07f2c3" dependencies = [ - "version_check", + "hashbrown 0.15.5", + "serde", ] [[package]] -name = "fastrand" -version = "2.1.0" +name = "find-msvc-tools" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flate2" -version = "1.0.30" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", + "zlib-rs", ] [[package]] @@ -378,23 +576,42 @@ 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 = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -403,9 +620,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -413,44 +630,55 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -460,19 +688,9 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -485,45 +703,50 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] -name = "gimli" -version = "0.29.0" +name = "getrandom" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] [[package]] -name = "glob" -version = "0.3.1" +name = "getrandom" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] [[package]] -name = "h2" -version = "0.3.26" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" @@ -532,104 +755,140 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] -name = "hermit-abi" -version = "0.3.9" +name = "hashbrown" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "http" -version = "0.2.12" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", - "fnv", "itoa", ] [[package]] -name = "http" -version = "1.1.0" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "fnv", - "itoa", + "http", ] [[package]] -name = "http-body" -version = "0.4.6" +name = "http-body-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "http 0.2.12", + "futures-core", + "http", + "http-body", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" - -[[package]] -name = "httpdate" -version = "1.0.3" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "0.14.29" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ + "atomic-waker", "bytes", "futures-channel", "futures-core", - "futures-util", - "h2", - "http 0.2.12", + "http", "http-body", "httparse", - "httpdate", "itoa", "pin-project-lite", - "socket2", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] name = "hyper-rustls" -version = "0.24.2" +version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", "futures-util", - "http 0.2.12", + "http", + "http-body", "hyper", - "rustls 0.21.12", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", "tokio", - "tokio-rustls 0.24.1", + "tower-service", + "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -645,21 +904,23 @@ dependencies = [ [[package]] name = "icu_collections" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" dependencies = [ "displaydoc", + "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", ] [[package]] -name = "icu_locid" -version = "1.5.0" +name = "icu_locale_core" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" dependencies = [ "displaydoc", "litemap", @@ -668,193 +929,258 @@ dependencies = [ "zerovec", ] -[[package]] -name = "icu_locid_transform" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" -dependencies = [ - "displaydoc", - "icu_locid", - "icu_locid_transform_data", - "icu_provider", - "tinystr", - "zerovec", -] - -[[package]] -name = "icu_locid_transform_data" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" - [[package]] name = "icu_normalizer" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", - "utf16_iter", - "utf8_iter", - "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" [[package]] name = "icu_properties" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f8ac670d7422d7f76b32e17a5db556510825b29ec9154f235977c9caba61036" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" dependencies = [ - "displaydoc", "icu_collections", - "icu_locid_transform", + "icu_locale_core", "icu_properties_data", "icu_provider", - "tinystr", + "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" [[package]] name = "icu_provider" -version = "1.5.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" dependencies = [ "displaydoc", - "icu_locid", - "icu_provider_macros", - "stable_deref_trait", - "tinystr", + "icu_locale_core", "writeable", "yoke", "zerofrom", + "zerotrie", "zerovec", ] [[package]] -name = "icu_provider_macros" -version = "1.5.0" +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", + "idna_adapter", + "smallvec", + "utf8_iter", ] [[package]] -name = "idna" -version = "1.0.0" +name = "idna_adapter" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4716a3a0933a1d01c2f72450e89596eb51dd34ef3c211ccd875acdf1f8fe47ed" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", - "smallvec", - "utf8_iter", ] [[package]] name = "indexmap" -version = "2.2.6" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.17.1", + "serde", + "serde_core", ] [[package]] name = "ipnet" -version = "2.9.0" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "itertools" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.117", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" dependencies = [ + "cfg-if", + "futures-util", "wasm-bindgen", ] [[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" [[package]] -name = "levenshtein" -version = "1.0.5" +name = "leb128fmt" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db13adb97ab515a3691f56e4dbab09283d0b86cb45abd991d8634a9d6f501760" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "litemap" -version = "0.7.3" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.21" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "lru-slab" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" [[package]] name = "mime" @@ -864,64 +1190,67 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", ] [[package]] -name = "mini-moka" -version = "0.10.3" +name = "miniz_oxide" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "crossbeam-channel", - "crossbeam-utils", - "dashmap", - "skeptic", - "smallvec", - "tagptr", - "triomphe", + "adler2", + "simd-adler32", ] [[package]] -name = "miniz_oxide" -version = "0.7.3" +name = "mio" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ - "adler", + "libc", + "wasi", + "windows-sys 0.61.2", ] [[package]] -name = "mio" -version = "0.8.11" +name = "nonmax" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" dependencies = [ - "libc", - "wasi", - "windows-sys 0.48.0", + "serde", ] [[package]] -name = "nu-ansi-term" -version = "0.46.0" +name = "ntapi" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ - "overload", "winapi", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-traits" @@ -933,41 +1262,79 @@ dependencies = [ ] [[package]] -name = "num_cpus" -version = "1.16.0" +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "objc2", +] + +[[package]] +name = "objc2-io-kit" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15" dependencies = [ - "hermit-abi", "libc", + "objc2-core-foundation", ] [[package]] -name = "object" -version = "0.36.0" +name = "objc2-open-directory" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" +checksum = "bb82bed227edf5201dfedf072bba4015a33d3d4a98519837295a90f0a23f676d" dependencies = [ - "memchr", + "objc2", + "objc2-core-foundation", + "objc2-foundation", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "overload" -version = "0.1.1" +name = "openssl-probe" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -975,34 +1342,72 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.52.5", + "windows-link", ] [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "poise" +version = "0.6.1" +source = "git+https://github.com/serenity-rs/poise?rev=de8bd6fdd54de8ee5daf0242ffb6dc6eba58df69#de8bd6fdd54de8ee5daf0242ffb6dc6eba58df69" +dependencies = [ + "async-trait", + "derivative", + "futures-util", + "indexmap 2.14.0", + "parking_lot", + "poise_macros", + "regex", + "serenity 0.12.5 (git+https://github.com/serenity-rs/serenity?branch=next)", + "tokio", + "tracing", + "trim-in-place", +] + +[[package]] +name = "poise_macros" +version = "0.6.1" +source = "git+https://github.com/serenity-rs/poise?rev=de8bd6fdd54de8ee5daf0242ffb6dc6eba58df69#de8bd6fdd54de8ee5daf0242ffb6dc6eba58df69" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] [[package]] name = "powerfmt" @@ -1012,83 +1417,189 @@ 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.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] [[package]] name = "proc-macro2" -version = "1.0.85" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] -name = "pulldown-cmark" -version = "0.9.6" +name = "quinn" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57206b407293d2bcd3af849ce869d52068623f19e1b5ff8e8778e3309439682b" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ - "bitflags 2.5.0", - "memchr", - "unicase", + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.4", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" 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 = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "rand" -version = "0.8.5" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "libc", "rand_chacha", - "rand_core", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.5", ] [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ - "getrandom", + "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + [[package]] name = "redox_syscall" -version = "0.5.2" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ - "bitflags 2.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] name = "regex" -version = "1.10.5" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -1098,9 +1609,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -1109,155 +1620,176 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" -version = "0.11.27" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ - "base64 0.21.7", + "base64", "bytes", - "encoding_rs", "futures-core", "futures-util", - "h2", - "http 0.2.12", + "http", "http-body", + "http-body-util", "hyper", "hyper-rustls", - "ipnet", + "hyper-util", "js-sys", "log", - "mime", "mime_guess", - "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.12", - "rustls-pemfile", + "quinn", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", "sync_wrapper", - "system-configuration", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", "tokio-util", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 0.25.4", - "winreg", ] [[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.17", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "rust_decimal" +version = "1.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be2a24f50780bc85f09cc6ac299bdf1424302742d77221106859c9d8b102126a" +dependencies = [ + "arrayvec", + "num-traits", +] + +[[package]] +name = "rustc-hash" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] -name = "rustix" -version = "0.38.34" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "bitflags 2.5.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", + "semver", ] [[package]] name = "rustls" -version = "0.21.12" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ - "log", - "ring", - "rustls-webpki 0.101.7", - "sct", + "aws-lc-rs", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", ] [[package]] -name = "rustls" -version = "0.22.4" +name = "rustls-native-certs" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ - "log", - "ring", + "openssl-probe", "rustls-pki-types", - "rustls-webpki 0.102.4", - "subtle", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" +dependencies = [ + "web-time", "zeroize", ] [[package]] -name = "rustls-pemfile" -version = "1.0.4" +name = "rustls-platform-verifier" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" dependencies = [ - "base64 0.21.7", + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", ] [[package]] -name = "rustls-pki-types" -version = "1.7.0" +name = "rustls-platform-verifier-android" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.101.7" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ + "aws-lc-rs", "ring", + "rustls-pki-types", "untrusted", ] [[package]] -name = "rustls-webpki" -version = "0.102.4" +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" -dependencies = [ - "ring", - "rustls-pki-types", - "untrusted", -] +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -1269,127 +1801,220 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "schannel" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] [[package]] -name = "sct" -version = "0.7.1" +name = "schemars" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" dependencies = [ - "ring", - "untrusted", + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "secrecy" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" dependencies = [ - "serde", "zeroize", ] [[package]] -name = "semver" -version = "1.0.23" +name = "security-framework" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ - "serde", + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" -version = "1.0.203" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_cow" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e84ce5596a72f0c4c60759a10ff8c22d5eaf227b0dc2789c8746193309058b" +checksum = "1e7bbbec7196bfde255ab54b65e34087c0849629280028238e67ee25d6a4b7da" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.117", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", - "ryu", + "memchr", "serde", + "serde_core", + "zmij", ] [[package]] -name = "serde_urlencoded" -version = "0.7.1" +name = "serde_spanned" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", + "serde_core", ] [[package]] -name = "serenity" -version = "0.12.2" +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "880a04106592d0a8f5bdacb1d935889bfbccb4a14f7074984d9cd857235d34ac" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serenity" +version = "0.12.5" +source = "git+https://github.com/serenity-rs/serenity?branch=next#30a293f0a02c63908b68a9f4fa687e964b7ff5c8" +replace = "serenity 0.12.5 (git+https://github.com/serenity-rs/serenity?rev=30a293f0a02c63908b68a9f4fa687e964b7ff5c8)" + +[[package]] +name = "serenity" +version = "0.12.5" +source = "git+https://github.com/serenity-rs/serenity?rev=30a293f0a02c63908b68a9f4fa687e964b7ff5c8#30a293f0a02c63908b68a9f4fa687e964b7ff5c8" dependencies = [ + "aformat", "arrayvec", "async-trait", - "base64 0.22.1", - "bitflags 2.5.0", + "base64", + "bitflags", + "bool_to_bitflags", "bytes", "chrono", - "command_attr", "dashmap", + "extract_map", "flate2", + "foldhash 0.2.0", "futures", - "fxhash", - "levenshtein", + "mime", "mime_guess", + "nonmax", "parking_lot", "percent-encoding", + "ref-cast", "reqwest", - "secrecy", "serde", "serde_cow", "serde_json", - "static_assertions", + "small-fixed-array", + "strum", "time", + "to-arraystring", "tokio", "tokio-tungstenite", "tracing", - "typemap_rev", "typesize", "url", - "uwl", + "zeroize", + "zstd", ] [[package]] @@ -1399,7 +2024,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] @@ -1412,90 +2037,122 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] [[package]] -name = "simple_auth_bot" -version = "0.1.0" +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "simd_cesu8" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" dependencies = [ - "dotenvy", - "regex", - "serenity", - "tokio", - "tracing", - "tracing-subscriber", + "rustc_version", + "simdutf8", ] [[package]] -name = "skeptic" -version = "0.13.7" +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "similar" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +checksum = "e6505efef05804732ed8a3f2d4f279429eb485bd69d5b0cc6b19cc02005cda16" dependencies = [ - "bytecount", - "cargo_metadata", - "error-chain", - "glob", - "pulldown-cmark", - "tempfile", - "walkdir", + "bstr", ] [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "small-fixed-array" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "f47eb472ef0994fb63d68ce4851eef89fa0faaf0dc4088c941b4015ce32c083f" dependencies = [ - "autocfg", + "serde", ] [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" -version = "0.5.7" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] -name = "spin" -version = "0.9.8" +name = "stable_deref_trait" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] -name = "stable_deref_trait" -version = "1.2.0" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "static_assertions" -version = "1.1.0" +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] [[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" @@ -1510,9 +2167,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1521,116 +2178,94 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.117", ] [[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" +name = "sysinfo" +version = "0.39.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "21d0d938c10fcda3e897e28aaddf4ab462375d411f4378cd63b1c945f69aba96" dependencies = [ - "core-foundation-sys", "libc", -] - -[[package]] -name = "tagptr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" - -[[package]] -name = "tempfile" -version = "3.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" -dependencies = [ - "cfg-if", - "fastrand", - "rustix", - "windows-sys 0.52.0", + "memchr", + "ntapi", + "objc2-core-foundation", + "objc2-io-kit", + "objc2-open-directory", + "windows", ] [[package]] name = "thiserror" -version = "1.0.61" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.117", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] name = "time" -version = "0.3.36" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -1638,85 +2273,98 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.7.6" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", ] +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "to-arraystring" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fafaa22f176928fb926345e78eb2ec404603c878b274e6ab1f76de1f6dde1b1" +dependencies = [ + "arrayvec", + "itoa", + "ryu", +] + [[package]] name = "tokio" -version = "1.38.0" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ - "backtrace", "bytes", "libc", "mio", - "num_cpus", "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.3.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", -] - -[[package]] -name = "tokio-rustls" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" -dependencies = [ - "rustls 0.21.12", - "tokio", + "syn 2.0.117", ] [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.22.4", - "rustls-pki-types", + "rustls", "tokio", ] [[package]] name = "tokio-tungstenite" -version = "0.21.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" dependencies = [ "futures-util", "log", - "rustls 0.22.4", + "rustls", "rustls-pki-types", "tokio", - "tokio-rustls 0.25.0", + "tokio-rustls", "tungstenite", - "webpki-roots 0.26.2", + "webpki-roots 0.26.11", ] [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -1725,17 +2373,95 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow 1.0.3", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.3", +] + +[[package]] +name = "toml_writer" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[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.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -1745,20 +2471,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.117", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -1777,9 +2503,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "nu-ansi-term", "sharded-slab", @@ -1790,10 +2516,10 @@ dependencies = [ ] [[package]] -name = "triomphe" -version = "0.1.12" +name = "trim-in-place" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b2cb4fbb9995eeb36ac86fadf24031ccd58f99d6b4b2d7b911db70bddb80d90" +checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" [[package]] name = "try-lock" @@ -1803,18 +2529,17 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.21.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ - "byteorder", "bytes", "data-encoding", - "http 1.1.0", + "http", "httparse", "log", - "rand", - "rustls 0.22.4", + "rand 0.9.4", + "rustls", "rustls-pki-types", "sha1", "thiserror", @@ -1823,27 +2548,31 @@ dependencies = [ ] [[package]] -name = "typemap_rev" -version = "0.3.0" +name = "typenum" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74b08b0c1257381af16a5c3605254d529d3e7e109f3c62befc5d168968192998" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] -name = "typenum" -version = "1.17.0" +name = "typenum_mappings" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "0cbc2d8952dd1e08b0164a5b51549e80631ac9da4107669d26c8ea89cb0b5545" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "to-arraystring", +] [[package]] name = "typesize" -version = "0.1.7" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb704842c709bc76f63e99e704cb208beeccca2abbabd0d9aec02e48ca1cee0f" +checksum = "a313188364f7e10138257cb288c64e9b0e282510abefc91c796799559fa14762" dependencies = [ "chrono", - "dashmap", - "hashbrown", - "mini-moka", + "nonmax", "parking_lot", "secrecy", "serde_json", @@ -1854,29 +2583,32 @@ dependencies = [ [[package]] name = "typesize-derive" -version = "0.1.7" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "905e88c2a4cc27686bd57e495121d451f027e441388a67f773be729ad4be1ea8" +checksum = "536b6812192bda8551cfa0e52524e328c6a951b48e66529ee4522d6c721243d6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.117", ] [[package]] name = "unicase" -version = "2.7.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-xid" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "untrusted" @@ -1886,14 +2618,15 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.1" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c25da092f0a868cdf09e8674cd3b7ef3a7d92a24253e663a2fb85e2496de56" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", + "serde_derive", ] [[package]] @@ -1902,12 +2635,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "utf16_iter" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -1915,22 +2642,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] -name = "uwl" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4bf03e0ca70d626ecc4ba6b0763b934b6f2976e8c744088bb3c1d646fbb1ad0" +name = "valine_bot" +version = "2.7.0" +dependencies = [ + "async-stream", + "bpaf", + "chrono", + "dashmap", + "duration-str", + "futures", + "itertools", + "poise", + "rand 0.10.1", + "regex", + "serde", + "serde_with", + "serenity 0.12.5 (git+https://github.com/serenity-rs/serenity?rev=30a293f0a02c63908b68a9f4fa687e964b7ff5c8)", + "similar", + "sysinfo", + "thiserror", + "time", + "tokio", + "toml", + "tracing", + "tracing-subscriber", + "valine_bot_macros", +] [[package]] -name = "valuable" +name = "valine_bot_macros" version = "0.1.0" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "valuable" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[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" @@ -1953,52 +2710,56 @@ dependencies = [ [[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 = "wasm-bindgen" -version = "0.2.92" +name = "wasip2" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", + "wit-bindgen 0.57.1", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "bumpalo", - "log", + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" +dependencies = [ + "cfg-if", "once_cell", - "proc-macro2", - "quote", - "syn 2.0.66", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" dependencies = [ - "cfg-if", "js-sys", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2006,28 +2767,53 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.66", - "wasm-bindgen-backend", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.123" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder", + "wasmparser", +] [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ "futures-util", "js-sys", @@ -2036,27 +2822,61 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver", +] + [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", ] +[[package]] +name = "webpki-root-certs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "webpki-roots" -version = "0.25.4" +version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.7", +] [[package]] name = "webpki-roots" -version = "0.26.2" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c452ad30530b54a4d8e71952716a212b08efd0f3562baa66c29a618b07da7c3" +checksum = "52f5ee44c96cf55f1b349600768e3ece3a8f26010c05265ab73f945bb1a2eb9d" dependencies = [ "rustls-pki-types", ] @@ -2079,11 +2899,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2092,22 +2912,105 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + [[package]] name = "windows-core" -version = "0.52.0" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ - "windows-targets 0.52.5", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" dependencies = [ - "windows-targets 0.48.5", + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", ] [[package]] @@ -2116,159 +3019,286 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "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", + "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] -name = "winreg" -version = "0.50.0" +name = "winnow" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.14.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", ] [[package]] -name = "write16" -version = "1.0.0" +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.14.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" -version = "0.5.5" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "yoke" -version = "0.7.4" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -2276,48 +3306,79 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.7.4" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.117", "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "zerofrom" -version = "0.1.4" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.117", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] [[package]] name = "zerovec" -version = "0.10.2" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", @@ -2326,11 +3387,51 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.10.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.117", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[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.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index b1cc88f..2ff1169 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,17 +1,45 @@ [package] -name = "simple_auth_bot" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +name = "valine_bot" +version = "2.7.0" +edition = "2024" +license = "MIT" +publish = false [dependencies] -dotenvy = "0.15.7" -regex = "1.10.5" -serenity = "0.12.2" -tokio = { version = "1.38.0", features = ["rt-multi-thread", "macros", "signal"] } -tracing = "0.1.40" -tracing-subscriber = "0.3.18" +async-stream = "0.3" +bpaf = { version = "0.9", features = [ "derive" ] } +chrono = "0.4" +dashmap = "6.1" +duration-str = "0.21" +futures = "0.3" +itertools = "0.14" +rand = "0.10" +regex = "1.0" +serde = { version = "1.0", features = [ "derive" ] } +serde_with = "3" +similar = "3.1" +sysinfo = "0.39" +thiserror = "2" +# 最新の Serenity と time 0.3.48 に互換性がない +time = "=0.3.47" +tokio = { version = "1.0", features = [ "rt-multi-thread", "macros", "signal" ] } +toml = "1.0" +tracing = "0.1" +tracing-subscriber = "0.3" +valine_bot_macros = { path = "macros" } + +[dependencies.serenity] +git = "https://github.com/serenity-rs/serenity" +rev = "30a293f0a02c63908b68a9f4fa687e964b7ff5c8" + +[dependencies.poise] +git = "https://github.com/serenity-rs/poise" +rev = "de8bd6fdd54de8ee5daf0242ffb6dc6eba58df69" + +# Poise が next ブランチを参照しているので、直接の依存関係に合わせる +[replace."https://github.com/serenity-rs/serenity#serenity:0.12.5"] +git = "https://github.com/serenity-rs/serenity" +rev = "30a293f0a02c63908b68a9f4fa687e964b7ff5c8" [profile.release] panic = "abort" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..7b2ba90 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +FROM debian:bookworm-slim AS builder + +ARG TARGETPLATFORM + +WORKDIR /builder +COPY dist dist + +RUN set -eu; \ + case "$TARGETPLATFORM" in \ + "linux/amd64") target="x86_64-unknown-linux-gnu" ;; \ + "linux/arm64") target="aarch64-unknown-linux-gnu" ;; \ + *) echo "Unsupported TARGETPLATFORM=$TARGETPLATFORM" >&2; exit 1 ;; \ + esac; \ + cp dist/valine_bot-*-"$target" ./valine_bot; \ + cp "dist/THIRD_PARTY_LICENSES-$target" ./THIRD_PARTY_LICENSES; \ + cp dist/LICENSE ./LICENSE; \ + chmod +x ./valine_bot + +FROM gcr.io/distroless/cc-debian12:nonroot +WORKDIR /app +COPY --from=builder /builder/valine_bot /app/valine_bot +COPY --from=builder /builder/LICENSE /app/LICENSE +COPY --from=builder /builder/THIRD_PARTY_LICENSES /app/THIRD_PARTY_LICENSES +ENTRYPOINT ["./valine_bot"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1102a3c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Lapis256 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/about.hbs b/about.hbs new file mode 100644 index 0000000..ac0d51c --- /dev/null +++ b/about.hbs @@ -0,0 +1,35 @@ +Third-party license notices +=========================== + +This file lists license notices for third-party Rust crates used by this +project. Distribute it with raw binaries published for this release. + +The license for this project itself is provided separately in LICENSE. + +Generated with: + + cargo about generate about.hbs --locked --fail --target -o THIRD_PARTY_LICENSES- + +License overview +---------------- + +{{#each overview}} +- {{{name}}} ({{count}}) +{{/each}} + +License texts +------------- + +{{#each licenses}} +================================================================================ +{{{name}}} +================================================================================ + +Used by: +{{#each used_by}} +- {{{crate.name}}} {{{crate.version}}}{{#if crate.repository}} ({{{crate.repository}}}){{else}} (https://crates.io/crates/{{{crate.name}}}){{/if}} +{{/each}} + +{{{text}}} + +{{/each}} diff --git a/about.toml b/about.toml new file mode 100644 index 0000000..00a047c --- /dev/null +++ b/about.toml @@ -0,0 +1,14 @@ +accepted = [ + "0BSD", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "BSL-1.0", + "CDLA-Permissive-2.0", + "ISC", + "MIT", + "Unicode-3.0", + "Unlicense", + "Zlib", +] diff --git a/config.sample.toml b/config.sample.toml new file mode 100644 index 0000000..c780266 --- /dev/null +++ b/config.sample.toml @@ -0,0 +1,77 @@ +[bot] +token = "TOKEN" +owners = [ "000000000000000000" ] + + +[auth] +# 認証の結果を通知するチャンネルID +log_channel_id = "000000000000000000" +# 認証後に付与するロールID +role_id = "000000000000000000" +# マッチしたら認証成功とする正規表現 +keyword = "あいことば" +# モーダルのデフォルト値にランダムに選択させるダミーのキーワード郡 +dummy_keywords = [ "ダミー", "dummy" ] + + +[auto_kick] +# キック対象のギルドID +guild_id = "000000000000000000" +# 参加してからキックされるまでの猶予時間 (例: "12h" なら12時間) (1時間に一度チェックされる) +grace_period = "12h" +# キックされた際にDMへ送信するメッセージ +kick_message = """てすとサーバー にて合言葉の入力が確認できなかったため、自動的にキックされました。 +再度参加する場合は、以下のリンクから再度参加してください。 +https://example.com/ +""" + + +[honeypot] +# ハニーポットのチャンネルID +channel_id = "000000000000000000" +# 削除対象にする同一内容の過去メッセージの送信された期間 (例: "24h" なら24時間以内) +message_lookback = "24h" +# キックされた際にDMへ送信するメッセージ +kick_message = """アカウントが乗っ取られたとみられる行動を確認したため、「てすとサーバー」から自動的にキックされました。""" +# ログを残すチャンネルID +log_channel_id = "000000000000000000" + + +[message_logging] +# メッセージの削除・編集のログを残すチャンネルID +channel_id = "000000000000000000" + + +[message_cache] +# 過去メッセージのキャッシュを無効化するかどうか +disabled = false +# 過去のメッセージのキャッシュを取るギルドIDのリスト +# target_guild_ids = ["651286346861641735"] +target_guild_ids = [ "000000000000000000" ] + + +[pin] +# チャンネルのオーナーを指定する +channels = [ + { id = "1318964564095930419", owner = "422735871410700308" }, # テスト +] + + +[thread_auto_invite] +# ロールの追加削除のトリガーとなるロールID +display_role_id = "000000000000000000" +# それぞれのロールを付与できるメンバーの数 (2500人のサーバでのテストの結果、250人以上にすると招待されない) +min_member_count = 250 +# 実際に呼び出されるロールのID +role_ids = [ "000000000000000000", "000000000000000000" ] + + +[question] +# 質問の投稿先フォーラムのID +forum_id = "000000000000000000" +# 解決済みのタグID +solved_tag = "000000000000000000" +# 解決済みスレッド名の先頭につく文字 +solved_name_prefix = "✅️ " +# 除外するタグIDのリスト +exclude_tags = [ "000000000000000000" ] diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..32998e8 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,125 @@ +# Valine Bot デプロイ + +## 専用ユーザー + +専用ユーザー `valine-bot` を作り、パスワードをロックします。 + +```sh +if ! id valine-bot >/dev/null 2>&1; then + sudo useradd -m -s /bin/bash valine-bot +fi + +sudo passwd -l valine-bot +sudo loginctl enable-linger valine-bot +``` + +以降の作業は専用ユーザーに切り替えて行います。 + +```sh +sudo -iu valine-bot +``` + +## インストール + +`main` ではなく、release tag または commit SHA に固定した raw URL を使ってください。 + +```sh +podman quadlet install -r \ + https://raw.githubusercontent.com/Valine3gDev/valine_bot/vX.Y.Z/deploy/valine-bot.quadlets +``` + +このリポジトリの GHCR image は release workflow で `ghcr.io/valine3gdev/valine_bot:2.4.2` のような `v` なしの semver tag として発行されます。別バージョンを使う場合は、インストール後に `$HOME/.config/containers/systemd/valine-bot.container` の `Image=` を更新してください。 + +## 更新 + +イメージタグなどを変更する場合: + +```sh +nano $HOME/.config/containers/systemd/valine-bot.container + +systemctl --user daemon-reload +systemctl --user restart valine-bot.service +``` + +ユーザーを切り替えずに更新する場合: + +```sh +sudo -u valine-bot -H sh -lc '${EDITOR:-nano} "$HOME/.config/containers/systemd/valine-bot.container"' +sudo systemctl --user --machine=valine-bot@.host daemon-reload +sudo systemctl --user --machine=valine-bot@.host restart valine-bot.service +``` + +## env file + +サンプルをダウンロードして、実際のパスワードに書き換えてからインストールします。 + +```sh +curl -fsSLo valine-bot.env \ + https://raw.githubusercontent.com/Valine3gDev/valine_bot/vX.Y.Z/deploy/valine-bot.env.sample + +${EDITOR:-nano} valine-bot.env + +install -D -m 600 valine-bot.env "$HOME/.config/containers/systemd/valine-bot.env" +rm -f valine-bot.env +``` + +## config file + +`$HOME/valine-bot/config.toml` を直接参照します。このファイルはコンテナ内の `/app/config.toml` として read-only mount されます。 + +```sh +mkdir -p "$HOME/valine-bot" +chmod 700 "$HOME/valine-bot" + +curl -fsSLo "$HOME/valine-bot/config.toml" \ + https://raw.githubusercontent.com/Valine3gDev/valine_bot/vX.Y.Z/config.sample.toml + +${EDITOR:-nano} "$HOME/valine-bot/config.toml" +chmod 600 "$HOME/valine-bot/config.toml" +``` + +## 起動 + +```sh +systemctl --user daemon-reload +systemctl --user start valine-bot-db.service valine-bot.service +``` + +## 状態確認 + +```sh +systemctl --user status valine-bot-db.service +systemctl --user status valine-bot.service +``` + +## ログ確認 + +```sh +journalctl --user -axu valine-bot.service -f +journalctl --user -axu valine-bot-db.service -f +``` + +ユーザーを切り替えずに +```sh +sudo -u valine-bot journalctl --user -axu valine-bot.service -f +sudo -u valine-bot journalctl --user -axu valine-bot-db.service -f +``` + +## 停止 + +```sh +systemctl --user stop valine-bot.service valine-bot-db.service +``` + +## コンフィグ検証 + +```sh +podman run --rm \ + --read-only \ + --cap-drop=all \ + --userns=keep-id \ + --user "$(id -u):$(id -g)" \ + --volume "$HOME/valine-bot/config.toml:/app/config.toml:ro" \ + ghcr.io/valine3gdev/valine_bot:2.4.2 \ + --check-config +``` diff --git a/deploy/valine-bot.env.sample b/deploy/valine-bot.env.sample new file mode 100644 index 0000000..ace69e8 --- /dev/null +++ b/deploy/valine-bot.env.sample @@ -0,0 +1 @@ +POSTGRES_PASSWORD=change-me diff --git a/deploy/valine-bot.quadlets b/deploy/valine-bot.quadlets new file mode 100644 index 0000000..c1f1e8d --- /dev/null +++ b/deploy/valine-bot.quadlets @@ -0,0 +1,58 @@ +# FileName=valine-bot +[Unit] +Description=Valine Bot Podman network + +[Network] +NetworkName=valine-bot-net + +--- +# FileName=valine-bot-db +[Unit] +Description=Valine Bot PostgreSQL database +Requires=valine-bot.network +After=valine-bot.network + +[Container] +Image=docker.io/library/postgres:18.3 +ContainerName=valine-bot-db +Network=valine-bot.network +Volume=valine-bot-db-data:/var/lib/postgresql +Environment=POSTGRES_DB=valine_bot +Environment=POSTGRES_USER=valine_bot +EnvironmentFile=./valine-bot.env + +[Service] +Restart=always + +[Install] +WantedBy=default.target + +--- +# FileName=valine-bot +[Unit] +Description=Valine Bot application +Requires=valine-bot.network +Requires=valine-bot-db.container +After=valine-bot.network +After=valine-bot-db.container + +[Container] +Image=ghcr.io/valine3gdev/valine_bot:2.4.2 +ContainerName=valine-bot +Network=valine-bot.network +Volume=%h/valine-bot/config.toml:/app/config.toml:ro +User=%U:%G +UserNS=keep-id +Environment=DB_HOST=valine-bot-db +Environment=DB_PORT=5432 +Environment=DB_NAME=valine_bot +Environment=DB_USER=valine_bot +EnvironmentFile=./valine-bot.env +ReadOnly=true +DropCapability=all + +[Service] +Restart=always + +[Install] +WantedBy=default.target diff --git a/macros/Cargo.toml b/macros/Cargo.toml new file mode 100644 index 0000000..7b73013 --- /dev/null +++ b/macros/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "valine_bot_macros" +version = "0.1.0" +edition = "2024" +publish = false +license = "MIT" + +[lib] +proc-macro = true + +[dependencies] +quote = "1" +syn = { version = "2", features = [ "full" ] } diff --git a/macros/src/lib.rs b/macros/src/lib.rs new file mode 100644 index 0000000..2c7660b --- /dev/null +++ b/macros/src/lib.rs @@ -0,0 +1,68 @@ +use proc_macro::TokenStream; +use quote::quote; +use syn::{FnArg, ItemFn, ReturnType, parse_macro_input}; + +#[proc_macro_attribute] +pub fn event_handler(_attr: TokenStream, item: TokenStream) -> TokenStream { + let function = parse_macro_input!(item as ItemFn); + + if function.sig.asyncness.is_none() { + return syn::Error::new_spanned( + &function.sig.fn_token, + "`#[simple_event_handler]` can only be used on async functions", + ) + .into_compile_error() + .into(); + } + + if !matches!(function.sig.output, ReturnType::Default) { + return syn::Error::new_spanned( + &function.sig.output, + "`#[simple_event_handler]` functions must return `()`", + ) + .into_compile_error() + .into(); + } + + if !function.sig.generics.params.is_empty() || function.sig.generics.where_clause.is_some() { + return syn::Error::new_spanned( + &function.sig.generics, + "`#[simple_event_handler]` functions cannot have generics", + ) + .into_compile_error() + .into(); + } + + if function.sig.inputs.len() != 2 + || function + .sig + .inputs + .iter() + .any(|input| !matches!(input, FnArg::Typed(_))) + { + return syn::Error::new_spanned( + &function.sig.inputs, + "`#[simple_event_handler]` functions must take `&Context` and `&FullEvent`", + ) + .into_compile_error() + .into(); + } + + let attributes = function.attrs; + let visibility = function.vis; + let handler_name = function.sig.ident; + let inputs = function.sig.inputs; + let body = function.block; + + quote! { + #(#attributes)* + #[allow(non_camel_case_types)] + #visibility struct #handler_name; + + #[::serenity::async_trait] + impl crate::core::BotEventHandler for #handler_name { + async fn dispatch(&self, #inputs) #body + } + } + .into() +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 8cca5be..4fedb95 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.80" +channel = "1.96" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..53f860a --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,2 @@ +max_width = 120 +use_field_init_shorthand = true diff --git a/src/app/config.rs b/src/app/config.rs new file mode 100644 index 0000000..08c6055 --- /dev/null +++ b/src/app/config.rs @@ -0,0 +1,114 @@ +use std::{ + collections::{HashMap, HashSet}, + path::Path, +}; + +use chrono::Duration; +use duration_str::deserialize_duration_chrono; +use regex::Regex; +use serde::{Deserialize, Deserializer}; +use serde_with::{DisplayFromStr, serde_as}; +use serenity::all::{ChannelId, ForumTagId, GuildId, RoleId, Token, UserId}; +use tokio::fs::read_to_string; + +use crate::app::AppError; + +#[derive(Debug, Deserialize)] +pub struct AppConfig { + pub bot: BotConfig, + pub auth: AuthConfig, + pub auto_kick: AutoKickConfig, + pub honeypot: HoneypotConfig, + pub message_logging: MessageLoggingConfig, + pub message_cache: MessageCacheConfig, + pub pin: PinConfig, + pub thread_auto_invite: ThreadAutoInviteConfig, + pub question: QuestionConfig, +} + +impl AppConfig { + pub async fn from_file(path: &str) -> Result { + let text = read_to_string(path).await?; + Ok(toml::from_str(&text)?) + } +} + +#[derive(Debug, Deserialize)] +pub struct BotConfig { + pub token: Token, + pub owners: HashSet, +} + +#[serde_as] +#[derive(Debug, Deserialize)] +pub struct AuthConfig { + pub log_channel_id: ChannelId, + pub role_id: RoleId, + pub keyword: String, + pub dummy_keywords: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct AutoKickConfig { + pub guild_id: GuildId, + #[serde(deserialize_with = "deserialize_duration_chrono")] + pub grace_period: Duration, + pub kick_message: String, +} + +#[derive(Debug, Deserialize)] +pub struct HoneypotConfig { + pub channel_id: ChannelId, + #[serde(deserialize_with = "deserialize_duration_chrono")] + pub message_lookback: Duration, + pub kick_message: String, + pub log_channel_id: ChannelId, +} + +#[derive(Debug, Deserialize)] +pub struct MessageLoggingConfig { + pub channel_id: ChannelId, +} + +#[derive(Debug, Deserialize)] +pub struct MessageCacheConfig { + pub disabled: bool, + pub target_guild_ids: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct PinConfig { + #[serde(deserialize_with = "to_pin_channels")] + pub channels: HashMap, +} + +fn to_pin_channels<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Debug, Deserialize)] + struct Temp { + id: ChannelId, + owner: UserId, + } + + Ok(Vec::deserialize(deserializer)? + .into_iter() + .map(|i: Temp| (i.id, i.owner)) + .collect()) +} + +#[derive(Debug, Deserialize)] +pub struct ThreadAutoInviteConfig { + pub display_role_id: RoleId, + pub role_ids: Vec, + pub min_member_count: u32, +} + +#[derive(Debug, Deserialize)] +pub struct QuestionConfig { + pub forum_id: ChannelId, + pub exclude_tags: Vec, + pub solved_tag: ForumTagId, + pub solved_name_prefix: String, +} diff --git a/src/app/data.rs b/src/app/data.rs new file mode 100644 index 0000000..4bffdca --- /dev/null +++ b/src/app/data.rs @@ -0,0 +1,50 @@ +use std::sync::Arc; + +use poise::ApplicationContext; +use serenity::all::prelude::Context; +use tokio::sync::RwLock; + +use crate::app::{AppApplicationContext, AppContext, AppError, config::AppConfig}; + +pub struct BotData { + config: RwLock>, +} + +impl BotData { + pub fn new(config: AppConfig) -> Self { + Self { + config: RwLock::new(Arc::new(config)), + } + } +} + +pub trait BotDataExt { + fn bot_data(&self) -> Arc; + + async fn app_config(&self) -> Arc { + self.bot_data().config.read().await.clone() + } + + async fn replace_app_config(&self, config: AppConfig) { + let data = self.bot_data(); + *data.config.write().await = Arc::new(config); + } +} + +impl BotDataExt for Context { + fn bot_data(&self) -> Arc { + self.data() + } +} + +impl<'a> BotDataExt for AppContext<'a> { + fn bot_data(&self) -> Arc { + self.data() + } +} + +impl<'a> BotDataExt for AppApplicationContext<'a> { + fn bot_data(&self) -> Arc { + self.data() + } +} diff --git a/src/app/error.rs b/src/app/error.rs new file mode 100644 index 0000000..f405ccf --- /dev/null +++ b/src/app/error.rs @@ -0,0 +1,79 @@ +use poise::{FrameworkError, say_reply}; +use thiserror::Error; +use tracing::error; + +use crate::{ + app::{AppError, BotData}, + utils::format_duration, +}; + +#[derive(Error, Debug)] +pub enum BotError { + #[error("このコマンドの実行に必要なロールがありません。")] + HasNoRole, + #[error("スレッドでのみ実行できるコマンドです。")] + IsNotInThread, + #[error("プライベートスレッドでは実行できません。")] + IsPrivateThread, +} + +pub async fn on_error(error: FrameworkError<'_, BotData, AppError>) { + match error { + FrameworkError::Command { error, ctx, .. } => { + let _ = say_reply(ctx, "コマンド実行中にエラーが発生しました。").await; + error!("Command error: Command: {:#?}, Error: {error:#?}", ctx.command()); + } + FrameworkError::ArgumentParse { ctx, input, error, .. } => { + let Some(input) = input else { + return error!("Error parsing input: {error:#?}"); + }; + + // let error = match error.downcast_ref::() { + // Some(MessageParseError::Malformed) => { + // "メッセージとして解析できませんでした。\nメッセージID、メッセージURL形式で入力してください。" + // } + // Some(MessageParseError::Http(_)) => "メッセージを取得できませんでした。", + // _ => &error.to_string(), + // }; + + let _ = say_reply(ctx, format!("入力 `{input}` の解析に失敗しました。\n{error:#?}")).await; + } + FrameworkError::MissingBotPermissions { + missing_permissions, + ctx, + .. + } => { + let msg = format!("ボットに権限が無いためコマンドを実行できません: {missing_permissions}",); + let _ = say_reply(ctx, msg).await; + } + FrameworkError::NotAnOwner { ctx, .. } => { + let _ = say_reply(ctx, "このコマンドはボットのオーナーのみ実行できます。").await; + } + FrameworkError::CooldownHit { + remaining_cooldown, + ctx, + .. + } => { + let _ = say_reply( + ctx, + format!( + "このコマンドはクールダウン中です。残り時間: {}", + format_duration(remaining_cooldown, 2), + ), + ) + .await; + } + FrameworkError::CommandCheckFailed { ctx, error, .. } => { + let error = match error { + Some(error) => error.to_string(), + None => "コマンドの実行条件を満たしていません。".to_string(), + }; + let _ = say_reply(ctx, error).await; + } + error => { + if let Err(e) = poise::builtins::on_error(error).await { + println!("Error while handling error: {e:#?}") + } + } + } +} diff --git a/src/app/event_handler.rs b/src/app/event_handler.rs new file mode 100644 index 0000000..da5f86a --- /dev/null +++ b/src/app/event_handler.rs @@ -0,0 +1,88 @@ +use std::time::Duration; + +use futures::lock::Mutex; +use serenity::{ + all::prelude::Context, + async_trait, + gateway::{ActivityData, ChunkGuildFilter}, + http::RatelimitInfo, + model::{event::FullEvent, gateway::Ready, guild::Guild}, +}; +use sysinfo::{Pid, System}; +use tokio::task::JoinHandle; +use tracing::{error, info, warn}; + +use crate::core::BotEventHandler; + +pub struct MainEventHandler { + activity_task: Mutex>>, +} + +impl MainEventHandler { + pub fn new() -> Self { + Self { + activity_task: Mutex::new(None), + } + } + + async fn handle_ready(&self, ready: &Ready) { + info!("{} is connected!", ready.user.name); + } + + async fn handle_cache_ready(&self, ctx: &Context) { + let mut task = self.activity_task.lock().await; + + if let Some(handle) = task.take() { + handle.abort(); + } + + let ctx = ctx.clone(); + + *task = Some(tokio::spawn(async move { + let mut system = System::new_all(); + let pid = Pid::from_u32(std::process::id()); + + loop { + system.refresh_all(); + + if let Some(memory) = system.process(pid).map(|p| p.memory() as f64 / 1024.0 / 1024.0) { + ctx.set_activity(Some(ActivityData::custom(format!("メモリ使用量: {:.1}MB", memory)))); + } else { + error!("Failed to get process info"); + } + + tokio::time::sleep(Duration::from_secs(60)).await; + } + })); + } + + async fn handle_guild_create(&self, ctx: &Context, guild: &Guild) { + // 全てのメンバーを取得する。結果は Serenity によって自動でキャッシュされる。 + ctx.chunk_guild(guild.id, Some(0), false, ChunkGuildFilter::None, None); + } +} + +#[async_trait] +impl BotEventHandler for MainEventHandler { + async fn dispatch(&self, ctx: &Context, event: &FullEvent) { + if let FullEvent::CacheReady { .. } = event { + self.handle_cache_ready(ctx).await; + } + + match event { + FullEvent::CacheReady { .. } => self.handle_cache_ready(ctx).await, + FullEvent::Ready { data_about_bot, .. } => self.handle_ready(data_about_bot).await, + FullEvent::GuildCreate { guild, .. } => self.handle_guild_create(ctx, guild).await, + _ => {} + } + } + + async fn ratelimit(&self, data: &RatelimitInfo) { + warn!( + "Ratelimited {} {}: {}s", + data.method.reqwest_method(), + data.path, + data.timeout.as_secs() + ); + } +} diff --git a/src/app/mod.rs b/src/app/mod.rs new file mode 100644 index 0000000..000eab8 --- /dev/null +++ b/src/app/mod.rs @@ -0,0 +1,13 @@ +#![allow(unused_imports)] + +pub mod config; +mod data; +mod error; +mod event_handler; +pub mod types; +pub mod utils; + +pub use data::{BotData, BotDataExt}; +pub use error::{BotError, on_error}; +pub use event_handler::MainEventHandler; +pub use types::{AppApplicationContext, AppCommand, AppContext, AppError}; diff --git a/src/app/types.rs b/src/app/types.rs new file mode 100644 index 0000000..7844279 --- /dev/null +++ b/src/app/types.rs @@ -0,0 +1,6 @@ +use crate::app::BotData; + +pub type AppError = Box; +pub type AppContext<'a> = poise::Context<'a, BotData, AppError>; +pub type AppApplicationContext<'a> = poise::ApplicationContext<'a, BotData, AppError>; +pub type AppCommand = poise::Command; diff --git a/src/app/utils/mod.rs b/src/app/utils/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/app/utils/mod.rs @@ -0,0 +1 @@ + diff --git a/src/core/client.rs b/src/core/client.rs new file mode 100644 index 0000000..0f62b8f --- /dev/null +++ b/src/core/client.rs @@ -0,0 +1,34 @@ +use std::sync::Arc; + +use serenity::{ + Client, + all::prelude::{Context, EventHandler, GatewayIntents}, + async_trait, + gateway::client::ClientBuilder, + http::RatelimitInfo, + model::event::FullEvent, + secrets::Token, +}; + +use crate::core::event_handler::BotEventHandlers; + +struct ClientEventHandler(BotEventHandlers); + +#[async_trait] +impl EventHandler for ClientEventHandler { + async fn dispatch(&self, ctx: &Context, event: &FullEvent) { + self.0.dispatch(ctx, event).await; + } + + async fn ratelimit(&self, data: RatelimitInfo) { + self.0.ratelimit(&data).await; + } +} + +pub fn create_client( + token: impl Into, + intents: GatewayIntents, + event_handlers: BotEventHandlers, +) -> ClientBuilder { + Client::builder(token.into(), intents).event_handler(Arc::new(ClientEventHandler(event_handlers))) +} diff --git a/src/core/event_handler.rs b/src/core/event_handler.rs new file mode 100644 index 0000000..43e2887 --- /dev/null +++ b/src/core/event_handler.rs @@ -0,0 +1,34 @@ +use serenity::{all::prelude::Context, async_trait, http::RatelimitInfo, model::event::FullEvent}; + +#[async_trait] +pub trait BotEventHandler: Send + Sync { + async fn dispatch(&self, ctx: &Context, event: &FullEvent); + + #[allow(unused_variables)] + async fn ratelimit(&self, data: &RatelimitInfo) {} +} + +pub struct BotEventHandlers(Vec>); + +impl BotEventHandlers { + pub fn new() -> Self { + Self(vec![]) + } + + pub async fn dispatch(&self, ctx: &Context, event: &FullEvent) { + for handler in &self.0 { + handler.dispatch(ctx, event).await + } + } + + pub async fn ratelimit(&self, data: &RatelimitInfo) { + for handler in &self.0 { + handler.ratelimit(data).await + } + } + + pub fn add(mut self, handler: B) -> Self { + self.0.push(Box::new(handler)); + self + } +} diff --git a/src/core/mod.rs b/src/core/mod.rs new file mode 100644 index 0000000..e7bc732 --- /dev/null +++ b/src/core/mod.rs @@ -0,0 +1,5 @@ +mod client; +mod event_handler; + +pub use client::create_client; +pub use event_handler::{BotEventHandler, BotEventHandlers}; diff --git a/src/extensions/message_builder.rs b/src/extensions/message_builder.rs new file mode 100644 index 0000000..fa665e2 --- /dev/null +++ b/src/extensions/message_builder.rs @@ -0,0 +1,116 @@ +use serenity::{ + model::Timestamp, + utils::{FormattedTimestamp, FormattedTimestampStyle, MessageBuilder}, +}; + +#[allow(dead_code)] +pub trait MessageBuilderTimestampExt { + fn push_timestamp(self, timestamp: Timestamp, style: Option) -> Self; + fn push_timestamp_line(self, timestamp: Timestamp, style: Option) -> Self; + + fn push_short_timestamp(self, timestamp: Timestamp) -> Self; + fn push_short_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_medium_timestamp(self, timestamp: Timestamp) -> Self; + fn push_medium_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_short_date_timestamp(self, timestamp: Timestamp) -> Self; + fn push_short_date_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_long_date_timestamp(self, timestamp: Timestamp) -> Self; + fn push_long_date_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_long_date_short_timestamp(self, timestamp: Timestamp) -> Self; + fn push_long_date_short_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_full_date_short_timestamp(self, timestamp: Timestamp) -> Self; + fn push_full_date_short_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_short_date_short_timestamp(self, timestamp: Timestamp) -> Self; + fn push_short_date_short_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_short_date_medium_timestamp(self, timestamp: Timestamp) -> Self; + fn push_short_date_medium_timestamp_line(self, timestamp: Timestamp) -> Self; + + fn push_relative_timestamp(self, timestamp: Timestamp) -> Self; + fn push_timestamp_relative_time_line(self, timestamp: Timestamp) -> Self; +} + +fn _push_linebreak(mut builder: MessageBuilder) -> MessageBuilder { + builder.0.push('\n'); + builder +} + +impl MessageBuilderTimestampExt for MessageBuilder { + fn push_timestamp(mut self, timestamp: Timestamp, style: Option) -> Self { + let formatted = FormattedTimestamp::new(timestamp, style).to_string(); + self.0.push_str(&formatted); + self + } + fn push_timestamp_line(self, timestamp: Timestamp, style: Option) -> Self { + _push_linebreak(self.push_timestamp(timestamp, style)) + } + + fn push_short_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::ShortTime)) + } + fn push_short_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_short_timestamp(timestamp)) + } + + fn push_medium_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::MediumTime)) + } + fn push_medium_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_medium_timestamp(timestamp)) + } + + fn push_short_date_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::ShortDate)) + } + fn push_short_date_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_short_date_timestamp(timestamp)) + } + + fn push_long_date_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::LongDate)) + } + fn push_long_date_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_long_date_timestamp(timestamp)) + } + + fn push_long_date_short_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::LongDateShortTime)) + } + fn push_long_date_short_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_long_date_short_timestamp(timestamp)) + } + + fn push_full_date_short_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::FullDateShortTime)) + } + fn push_full_date_short_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_full_date_short_timestamp(timestamp)) + } + + fn push_short_date_short_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::ShortDateShortTime)) + } + fn push_short_date_short_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_short_date_short_timestamp(timestamp)) + } + + fn push_short_date_medium_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::ShortDateMediumTime)) + } + fn push_short_date_medium_timestamp_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_short_date_medium_timestamp(timestamp)) + } + + fn push_relative_timestamp(self, timestamp: Timestamp) -> Self { + self.push_timestamp(timestamp, Some(FormattedTimestampStyle::RelativeTime)) + } + fn push_timestamp_relative_time_line(self, timestamp: Timestamp) -> Self { + _push_linebreak(self.push_relative_timestamp(timestamp)) + } +} diff --git a/src/extensions/mod.rs b/src/extensions/mod.rs new file mode 100644 index 0000000..4a14c70 --- /dev/null +++ b/src/extensions/mod.rs @@ -0,0 +1,3 @@ +mod message_builder; + +pub use message_builder::MessageBuilderTimestampExt; diff --git a/src/features/admin.rs b/src/features/admin.rs new file mode 100644 index 0000000..665ecc7 --- /dev/null +++ b/src/features/admin.rs @@ -0,0 +1,12 @@ +use poise::say_reply; + +use crate::app::{AppContext, AppError, BotDataExt, config::AppConfig}; + +/// コンフィグを再読み込み +#[poise::command(slash_command, ephemeral, owners_only, dm_only)] +pub async fn reload_config(ctx: AppContext<'_>) -> Result<(), AppError> { + let config = AppConfig::from_file("config.toml").await?; + ctx.replace_app_config(config).await; + say_reply(ctx, "Config reloaded").await?; + Ok(()) +} diff --git a/src/features/auth/auto_kick.rs b/src/features/auth/auto_kick.rs new file mode 100644 index 0000000..2af02d1 --- /dev/null +++ b/src/features/auth/auto_kick.rs @@ -0,0 +1,103 @@ +use std::{ + sync::atomic::{AtomicBool, Ordering}, + time::Duration, +}; + +use futures::StreamExt; +use serenity::{ + all::{Context, prelude::CacheHttp}, + async_trait, + model::{Color, event::FullEvent}, +}; +use tracing::error; + +use crate::{ + app::BotDataExt, + core::BotEventHandler, + features::auth::utils::create_auth_log_message, + utils::{create_message, send_message, stream_members}, +}; + +pub struct AutoKickEventHandler { + task_started: AtomicBool, +} + +impl AutoKickEventHandler { + pub fn new() -> Self { + Self { + task_started: AtomicBool::new(false), + } + } + + async fn run_kick_loop(ctx: Context) { + loop { + let config = ctx.app_config().await; + + let mut member_stream = stream_members(&ctx, config.auto_kick.guild_id); + while let Some(member) = member_stream.next().await { + if member.user.bot() { + continue; + } + + if member.roles.contains(&config.auth.role_id) { + continue; + } + + let joined_at = match member.joined_at { + Some(time) => *time, + None => continue, + }; + + if chrono::Utc::now().signed_duration_since(joined_at) < config.auto_kick.grace_period { + continue; + } + + let dm_result = member + .user + .id + .direct_message(&ctx, create_message(&config.auto_kick.kick_message)) + .await; + + if let Err(error) = member + .kick(ctx.http(), Some("一定期間のうちに認証ロールが付与されていないため")) + .await + { + error!("Failed to kick user: {error:#?}"); + // continue; + }; + + let _ = send_message( + &ctx, + &config.auth.log_channel_id, + create_auth_log_message( + "認証期限切れのため Kick", + Color::ORANGE, + &member, + Some(dm_result.is_ok()), + ), + ) + .await; + } + + tokio::time::sleep(Duration::from_secs(3600)).await; + } + } + + async fn handle_cache_ready(&self, ctx: &Context) { + if self.task_started.swap(true, Ordering::Relaxed) { + return; + } + + let ctx = ctx.clone(); + tokio::spawn(Self::run_kick_loop(ctx)); + } +} + +#[async_trait] +impl BotEventHandler for AutoKickEventHandler { + async fn dispatch(&self, ctx: &Context, event: &FullEvent) { + if let FullEvent::CacheReady { .. } = event { + self.handle_cache_ready(ctx).await; + } + } +} diff --git a/src/features/auth/keyword.rs b/src/features/auth/keyword.rs new file mode 100644 index 0000000..abaed55 --- /dev/null +++ b/src/features/auth/keyword.rs @@ -0,0 +1,211 @@ +use std::{ + str::FromStr, + sync::Arc, + time::{Duration, Instant}, +}; + +use dashmap::DashMap; +use poise::say_reply; +use rand::seq::IndexedRandom; +use serenity::all::prelude::CacheHttp; +use serenity::all::{ + ComponentInteractionDataKind, Context, CreateActionRow, CreateButton, CreateInputText, + CreateInteractionResponseFollowup, InputTextStyle, Interaction, Mentionable, ModalInteractionCollector, UserId, +}; +use serenity::async_trait; +use serenity::builder::{CreateComponent, CreateLabel, CreateModalComponent}; +use serenity::model::application::{ButtonStyle, LabelComponent, ModalComponent}; +use serenity::model::colour::colours::branding; +use serenity::model::event::FullEvent; +use serenity::small_fixed_array::FixedString; +use tracing::error; + +use crate::app::{AppContext, AppError, BotDataExt}; +use crate::core::BotEventHandler; +use crate::features::auth::utils::create_auth_log_message; +use crate::utils::{create_ephemeral_message, create_interaction_message, create_message, create_model, send_message}; + +const KEYWORD_INPUT_BUTTON_CUSTOM_ID: &str = "keyword_input:button"; +const FAILED_ATTEMPT_COOLDOWN: Duration = Duration::from_secs(60); + +pub struct KeywordAuthEventHandler { + cooldown_started_at: Arc>, +} + +impl KeywordAuthEventHandler { + pub fn new() -> Self { + Self { + cooldown_started_at: Arc::new(DashMap::new()), + } + } + + fn remove_expired_cooldowns(&self) { + self.cooldown_started_at + .retain(|_, started_at| started_at.elapsed() < FAILED_ATTEMPT_COOLDOWN); + } + + fn remaining_cooldown_seconds(&self, user_id: UserId) -> Option { + self.remove_expired_cooldowns(); + + if let Some(started_at) = self.cooldown_started_at.get(&user_id) { + let remaining_seconds = FAILED_ATTEMPT_COOLDOWN.checked_sub(started_at.elapsed())?.as_secs(); + if remaining_seconds > 0 { + return Some(remaining_seconds); + } + } + None + } + + fn start_cooldown_for(&self, user_id: UserId) { + self.cooldown_started_at.insert(user_id, Instant::now()); + } + + async fn handle_interaction_create(&self, ctx: &Context, interaction: &Interaction) { + let Interaction::Component(interaction) = interaction else { + return; + }; + let ComponentInteractionDataKind::Button = interaction.data.kind else { + return; + }; + if interaction.data.custom_id != KEYWORD_INPUT_BUTTON_CUSTOM_ID { + return; + } + + let config = &ctx.app_config().await.auth; + let member = interaction.member.as_ref().unwrap(); + + if member.roles.contains(&config.role_id) { + let _ = interaction + .create_response(ctx.http(), create_ephemeral_message("既に認証済みです。", None)) + .await; + return; + } + + if let Some(remaining_seconds) = self.remaining_cooldown_seconds(interaction.user.id) { + let _ = interaction + .create_response( + ctx.http(), + create_ephemeral_message( + format!("クールダウン中です。\n{remaining_seconds}秒後に再度お試しください。"), + None, + ), + ) + .await; + return; + } + + let mut keyword_input = CreateInputText::new(InputTextStyle::Short, "keyword") + .required(true) + .placeholder("合言葉を入力してください。"); + + if let Some(value) = config.dummy_keywords.choose(&mut rand::rng()) { + keyword_input = keyword_input.value(value); + } + + let modal_custom_id = FixedString::from_str(&interaction.id.to_string()).unwrap(); + + let _ = interaction + .create_response( + ctx.http(), + create_model( + &modal_custom_id, + "合言葉を入力してください。", + &[CreateModalComponent::Label(CreateLabel::input_text( + "合言葉", + keyword_input, + ))], + ), + ) + .await; + + let Some(interaction) = ModalInteractionCollector::new(ctx) + .custom_ids([modal_custom_id].to_vec()) + .timeout(Duration::from_secs(60)) + .await + else { + let _ = interaction + .create_followup( + ctx.http(), + CreateInteractionResponseFollowup::new() + .content("時間切れです。もう一度お試しください。") + .ephemeral(true), + ) + .await; + return; + }; + + let submitted_keyword = if let ModalComponent::Label(label) = interaction.data.components.first().unwrap() + && let LabelComponent::InputText(text) = label.component.clone() + { + text.value + } else { + return error!("Invalid modal interaction: {interaction:#?}"); + }; + let submitted_keyword = submitted_keyword.trim(); + + if config.keyword != submitted_keyword { + let _ = interaction + .create_response( + ctx.http(), + create_interaction_message("合言葉が間違っています。", true, None), + ) + .await; + self.start_cooldown_for(interaction.user.id); + return; + } + + if let Err(error) = member.add_role(ctx.http(), config.role_id, Some("認証成功")).await { + let log = create_message(format!( + "{} にロールを追加できませんでした。\n```\n{error:#?}```", + member.mention() + )); + let _ = send_message(ctx, &config.log_channel_id, log).await; + return error!("Failed to add role: {error:#?}"); + } + + let _ = send_message( + ctx, + &config.log_channel_id, + create_auth_log_message("認証成功", branding::GREEN, member, None), + ) + .await; + + const AUTH_SUCCESS_MESSAGE: &str = "合言葉を確認しました。\nチャンネルが表示されない場合、アプリの再起動や再読み込み(Ctrl + R)をお試しください。"; + let _ = interaction + .create_response(ctx.http(), create_interaction_message(AUTH_SUCCESS_MESSAGE, true, None)) + .await; + } +} + +#[async_trait] +impl BotEventHandler for KeywordAuthEventHandler { + async fn dispatch(&self, ctx: &Context, event: &FullEvent) { + if let FullEvent::InteractionCreate { interaction, .. } = event { + self.handle_interaction_create(ctx, interaction).await + } + } +} + +/// 合言葉を入力するボタンを作成します。 +#[poise::command(slash_command, ephemeral, guild_only, default_member_permissions = "ADMINISTRATOR")] +pub async fn create_keyword_button( + ctx: AppContext<'_>, + #[description = "ボタンの表示名"] button: String, + #[description = "メッセージ内容"] content: String, +) -> Result<(), AppError> { + say_reply(ctx, "ボタンを作成しました。").await?; + + let _ = ctx + .channel_id() + .send_message( + ctx.http(), + create_message(content).components(&[CreateComponent::ActionRow(CreateActionRow::buttons(&[ + CreateButton::new(KEYWORD_INPUT_BUTTON_CUSTOM_ID) + .label(button) + .style(ButtonStyle::Primary), + ]))]), + ) + .await?; + + Ok(()) +} diff --git a/src/features/auth/mod.rs b/src/features/auth/mod.rs new file mode 100644 index 0000000..454d548 --- /dev/null +++ b/src/features/auth/mod.rs @@ -0,0 +1,6 @@ +mod auto_kick; +mod keyword; +mod utils; + +pub use auto_kick::AutoKickEventHandler; +pub use keyword::{KeywordAuthEventHandler, create_keyword_button}; diff --git a/src/features/auth/utils.rs b/src/features/auth/utils.rs new file mode 100644 index 0000000..c15bdab --- /dev/null +++ b/src/features/auth/utils.rs @@ -0,0 +1,56 @@ +use std::borrow::Cow; + +use serenity::all::{Mentionable, MessageBuilder}; +use serenity::builder::{CreateEmbed, CreateMessage}; +use serenity::model::Color; +use serenity::model::guild::Member; +use serenity::utils::EmbedMessageBuilding; + +use crate::utils::create_safe_message; + +pub(in crate::features::auth) fn create_auth_log_message<'a>( + title: impl Into>, + color: impl Into, + member: &Member, + dm_delivery_succeeded: Option, +) -> CreateMessage<'a> { + let mut description = MessageBuilder::new() + .push("- ") + .push_bold_line("ユーザー") + .push(" - ") + .push_bold("表示名: ") + .push_line_safe(member.display_name()) + .push(" - ") + .push_bold("メンション: ") + .push_safe(&*member.mention().to_string()) + .push(" ") + .push_named_link("リンク", &*format!("", member.user.id)) + .push_line("") + .push(" - ") + .push_bold("ユーザー名: ") + .push_line_safe(&*member.user.name) + .push(" - ") + .push_bold("ID: ") + .push_line_safe(&*member.user.id.to_string()); + + if let Some(delivery_succeeded) = dm_delivery_succeeded { + description = description + .push("- ") + .push_bold("DM送信可否: ") + .push_line(if delivery_succeeded { "YES" } else { "NO" }); + } + + let embed = CreateEmbed::new() + .title(title) + .description(description.build()) + .color(color) + .thumbnail( + member + .user + .avatar_url() + .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), + Some("ユーザーアイコン".into()), + ); + + create_safe_message().embed(embed) +} diff --git a/src/features/honeypot.rs b/src/features/honeypot.rs new file mode 100644 index 0000000..a623052 --- /dev/null +++ b/src/features/honeypot.rs @@ -0,0 +1,207 @@ +use std::collections::HashMap; + +use chrono::Duration; +use serenity::{ + all::prelude::{CacheHttp, Context}, + builder::CreateEmbed, + model::{ + Timestamp, + channel::Message, + event::FullEvent, + id::{ChannelId, GuildId, MessageId, UserId}, + }, + utils::MessageBuilder, +}; +use tracing::error; +use valine_bot_macros::event_handler; + +use crate::{ + app::BotDataExt, + utils::{create_message, create_safe_message, send_message}, +}; + +struct MessageFingerprint { + author_id: UserId, + content: String, + attachments: Vec, +} + +impl MessageFingerprint { + fn matches(&self, other: &MessageFingerprint) -> bool { + if self.author_id != other.author_id { + return false; + } + + if self.content != other.content { + return false; + } + + let mut self_attachments = self.attachments.to_vec(); + let mut other_attachments = other.attachments.to_vec(); + self_attachments.sort(); + other_attachments.sort(); + + self_attachments == other_attachments + } + + fn matches_message(&self, message: &Message) -> bool { + self.matches(&message.into()) + } +} + +impl From for MessageFingerprint { + fn from(message: Message) -> Self { + (&message).into() + } +} + +impl From<&Message> for MessageFingerprint { + fn from(message: &Message) -> Self { + Self { + author_id: message.author.id, + content: message.content.clone().into_string(), + attachments: message + .attachments + .iter() + .map(|a| a.filename.clone().into_string()) + .collect(), + } + } +} + +/** +指定されたメッセージと同一の内容を持ち、指定された期間内に送信されたメッセージのID一覧を、 Serenity のキャッシュ内から収集する + +スパム対策の性質上 Bot起動以前のメッセージが必要になる可能性が低いため、Serenityのキャッシュからのみ収集する実装とした + +また、同様にスレッドにメッセージが送信されないというスパムの傾向を踏まえ、スレッド内のメッセージは収集対象から除外する +*/ +fn collect_message_ids( + ctx: &Context, + guild_id: GuildId, + target_message: impl Into, + message_lookback: Duration, +) -> HashMap> { + let Some(guild) = guild_id.to_guild_cached(&ctx.cache) else { + error!("guild {guild_id} not found in cache"); + return HashMap::new(); + }; + + let mut ids = HashMap::new(); + let cutoff = Timestamp::now().unix_timestamp() - message_lookback.num_seconds(); + let target_message = target_message.into(); + + for channel in &guild.channels { + let id = channel.id; + if let Some(messages) = ctx.cache.channel_messages(id.into()) { + let message_ids = messages + .iter() + .filter(|m| m.timestamp.unix_timestamp() >= cutoff && target_message.matches_message(m)) + .map(|m| m.id) + .collect::>(); + + if !message_ids.is_empty() { + ids.insert(id, message_ids); + } + } + } + + ids +} + +async fn delete_messages(ctx: &Context, messages: &HashMap>) { + static DELETE_REASON: Option<&str> = Some("ハニーポットに送信されたメッセージと同一のため"); + + for (channel_id, message_ids) in messages { + let channel_id = channel_id.widen(); + if message_ids.len() > 2 { + for chunk in message_ids.chunks(100) { + if let Err(e) = channel_id.delete_messages(ctx.http(), chunk, DELETE_REASON).await { + error!("Failed to delete messages in channel {channel_id}: {e:#?}"); + } + } + continue; + } + + if let Some(id) = message_ids.first() + && let Err(e) = channel_id.delete_message(ctx.http(), *id, DELETE_REASON).await + { + error!("Failed to delete message {id} in channel {channel_id}: {e:#?}"); + } + } +} + +#[event_handler] +pub async fn handle_honeypot_event(ctx: &Context, event: &FullEvent) { + if let FullEvent::Message { new_message, .. } = event { + let author = &new_message.author; + + if author.bot() { + return; + } + + let config = ctx.app_config().await; + + if config.honeypot.channel_id != new_message.channel_id.expect_channel() { + return; + } + + let dm_message = author + .id + .direct_message(&ctx, create_message(&config.honeypot.kick_message)) + .await; + + let Ok(member) = new_message.member(&ctx).await else { + error!("user {} not found", author.id); + return; + }; + + let _ = member + .kick(ctx.http(), Some("ハニーポットにメッセージを送信したため")) + .await; + + let delete_message_ids = collect_message_ids( + ctx, + new_message.guild_id.unwrap(), + new_message, + config.honeypot.message_lookback, + ); + delete_messages(ctx, &delete_message_ids).await; + + let mut log_builder = MessageBuilder::new() + .push_bold("ユーザー: ") + .push_safe(member.display_name()) + .push(" ") + .push_mono_line(&*author.id.to_string()) + .push_line(dm_message.map_or("DMの送信に失敗しました。", |_| "")) + .push_bold_line("削除したメッセージID:"); + + for (channel_id, message_ids) in &delete_message_ids { + for message_id in message_ids { + log_builder = log_builder + .push("- ") + .push(&*message_id.link(channel_id.widen(), new_message.guild_id).to_string()) + .push(" ") + .push_mono_line(&*message_id.to_string()); + } + } + + let embed = CreateEmbed::new() + .title("ハニーポット検知") + .description(log_builder.build()) + .color(0xf00000) + .thumbnail( + author + .avatar_url() + .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), + Some("ユーザーアイコン".into()), + ); + + let _ = send_message( + ctx, + &config.honeypot.log_channel_id, + create_safe_message().add_embed(embed), + ) + .await; + } +} diff --git a/src/features/message_cache_handler.rs b/src/features/message_cache_handler.rs new file mode 100644 index 0000000..8b798af --- /dev/null +++ b/src/features/message_cache_handler.rs @@ -0,0 +1,161 @@ +use std::sync::atomic::{AtomicBool, Ordering}; + +use async_stream::stream; +use futures::{StreamExt, future}; +use serenity::{ + all::{Context, Guild, GuildChannel, Member, prelude::CacheHttp}, + async_trait, + model::{ + Permissions, + channel::{GenericGuildChannelRef, GuildThread}, + event::FullEvent, + id::GenericChannelId, + }, + small_fixed_array::FixedString, +}; +use tracing::{error, info}; + +use crate::{app::BotDataExt, core::BotEventHandler, utils::fetch_all_archived_public_thread}; + +enum ChannelWrapper { + Channel(GuildChannel), + Thread(GuildThread), +} + +impl ChannelWrapper { + fn is_text_based(&self) -> bool { + match self { + Self::Channel(c) => c.is_text_based(), + Self::Thread(_) => true, + } + } + + fn id(&self) -> GenericChannelId { + match self { + Self::Channel(c) => c.id.widen(), + Self::Thread(t) => t.id.widen(), + } + } + + fn name(&self) -> FixedString { + (match self { + Self::Channel(c) => &c.base, + Self::Thread(t) => &t.base, + }) + .clone() + .name + } + + fn user_permission(&self, guild: &Guild, member: &Member) -> Option { + match self { + Self::Channel(c) => Some(guild.user_permissions_in(c, member)), + Self::Thread(t) => guild.channel(t.parent_id.into()).and_then(|c| match c { + GenericGuildChannelRef::Channel(gc) => Some(guild.user_permissions_in(gc, member)), + _ => None, + }), + } + } +} + +pub struct MessageCacheHandler { + disabled: bool, + collected: AtomicBool, +} + +impl MessageCacheHandler { + pub fn new(disabled: bool) -> Self { + Self { + disabled, + collected: AtomicBool::new(false), + } + } + + async fn cache_channel_message(&self, ctx: &Context, channel: ChannelWrapper, guild: &Guild, bot_member: &Member) { + if !channel.is_text_based() { + return; + } + + let has_read_message_history_permission = channel + .user_permission(guild, bot_member) + .map(|p| p.read_message_history()) + .unwrap_or(false); + if !has_read_message_history_permission { + return; + } + + // Context を渡すと Serenity 標準キャッシュに取得結果が載るようになる + let collected_count = channel + .id() + .messages_iter(&ctx) + .take_while(|x| future::ready(x.is_ok())) + .filter_map(|x| future::ready(x.ok())) + .count() + .await; + + info!( + "Cached {collected_count} messages for channel: {} ({})", + channel.name(), + channel.id() + ); + } + + async fn handle_cache_ready(&self, ctx: &Context) { + if self.disabled || self.collected.swap(true, Ordering::Relaxed) { + return; + } + + let config = ctx.app_config().await; + + for guild_id in &config.message_cache.target_guild_ids { + let guild = match guild_id.to_guild_cached(&ctx.cache) { + Some(guild) => guild.clone(), + None => { + error!("Failed to get guild: {}", guild_id); + continue; + } + }; + + let bot_id = ctx.cache.current_user().id; + + let Ok(bot_member) = guild.member(ctx.http(), bot_id).await else { + error!("Failed to get bot member for guild: {}", guild_id); + continue; + }; + + let Ok(channels) = guild_id.channels(ctx.http()).await else { + error!("Failed to get channels for guild: {}", guild_id); + continue; + }; + + let active_threads = guild.threads.clone(); + let _ = stream! { + for thread in active_threads { + yield ChannelWrapper::Thread(thread); + } + + for channel in channels { + let id = channel.id; + yield ChannelWrapper::Channel(channel); + + for await thread in fetch_all_archived_public_thread(ctx, id, None).await { + yield ChannelWrapper::Thread(thread); + } + } + } + .map(|c| self.cache_channel_message(ctx, c, &guild, &bot_member)) + .buffer_unordered(20) + .collect::>() + .await; + } + info!("Cache ready!"); + } +} + +#[async_trait] +impl BotEventHandler for MessageCacheHandler { + async fn dispatch(&self, ctx: &Context, event: &FullEvent) { + if let FullEvent::CacheReady { .. } = event { + self.handle_cache_ready(ctx).await + } + } +} diff --git a/src/features/message_logging/embed_builder.rs b/src/features/message_logging/embed_builder.rs new file mode 100644 index 0000000..a6b78fb --- /dev/null +++ b/src/features/message_logging/embed_builder.rs @@ -0,0 +1,128 @@ +use std::ops::{Deref, Not}; + +use itertools::{Itertools, enumerate}; +use serenity::all::{CreateEmbed, EmbedMessageBuilding, Message, MessageBuilder, MessageId, MessageReferenceKind}; +use tracing::error; + +use crate::{extensions::MessageBuilderTimestampExt, utils::create_diff_lines_text}; + +pub(in crate::features::message_logging) fn build_reply_field<'a>( + embed: CreateEmbed<'a>, + message: &Message, +) -> CreateEmbed<'a> { + let Some(m_ref) = &message.message_reference else { + return embed; + }; + let id = m_ref.message_id.unwrap_or(MessageId::default()); + let (name, content) = match m_ref.kind { + MessageReferenceKind::Default => ("__**返信**__", "返信先: "), + MessageReferenceKind::Forward => ("__**転送**__", "転送元: "), + _ => ("__**不明**__", "不明な対象メッセージ: "), + }; + embed.field( + name, + MessageBuilder::new() + .push_bold_safe(content) + .push_safe(&*id.link(m_ref.channel_id, m_ref.guild_id).to_string()) + .push_safe(" ") + .push_mono_line_safe(&*id.to_string()) + .build(), + false, + ) +} + +pub(in crate::features::message_logging) fn build_poll_field<'a>( + embed: CreateEmbed<'a>, + message: &Message, +) -> CreateEmbed<'a> { + let Some(poll) = &message.poll else { + return embed; + }; + + let mut builder = MessageBuilder::new(); + builder = builder + .push_bold_safe("タイトル: ") + .push_line_safe(poll.question.text.as_deref().unwrap_or("<不明なタイトル>")) + .push_bold_line_safe("回答:"); + + for (i, answer) in enumerate(&poll.answers) { + let Ok(i) = i.try_into() else { + error!("poll answer index must fit in u8"); + break; + }; + + let answer_text = answer.poll_media.text.as_deref().unwrap_or("<不明な回答>"); + builder = builder.push_safe(format!("- {answer_text}").as_str()); + + builder = if let Some(results) = &poll.results { + builder.push_line_safe(format!(": {}票", results.answer_counts[i].count).as_str()) + } else { + builder.push_safe("\n") + }; + } + + if let Some(expiry) = poll.expiry { + builder = builder + .push_bold_safe("有効期限: ") + .push_short_date_medium_timestamp(expiry); + } + + embed.field("__**投票**__", builder.build(), false) +} + +pub(in crate::features::message_logging) fn build_diff_field<'a>( + mut embed: CreateEmbed<'a>, + old_content: &str, + new_content: &str, +) -> CreateEmbed<'a> { + if old_content.is_empty() { + return embed; + } + + let diff = create_diff_lines_text(old_content, new_content); + let chunks = diff.lines().peekable().batching(|lines| { + let mut str = String::new(); + while let Some(line) = lines.next_if(|&l| str.len() + l.len() <= 1000) { + str.push_str(line); + str.push('\n'); + } + str.is_empty().not().then_some(str) + }); + + for (i, chunk) in enumerate(chunks) { + let changed = MessageBuilder::new() + .push_codeblock_safe(chunk.as_str(), Some("diff")) + .build(); + + embed = embed.field(if i == 0 { "__**テキスト差分**__" } else { "" }, changed, false) + } + embed +} + +pub(in crate::features::message_logging) fn build_attachments_field<'a>( + embed: CreateEmbed<'a>, + message: &Message, +) -> CreateEmbed<'a> { + if message.attachments.is_empty() { + return embed; + } + let mut builder = MessageBuilder::new(); + for attachment in &message.attachments { + builder = builder + .push_safe("- ") + .push_named_link_safe(attachment.filename.deref(), attachment.url.deref()) + .push_safe("\n"); + } + embed.field("__**添付ファイル**__", builder.build(), false) +} + +pub(in crate::features::message_logging) fn build_embed<'a>( + message: &Message, + new_content: &str, + mut embed: CreateEmbed<'a>, +) -> CreateEmbed<'a> { + embed = build_reply_field(embed, message); + embed = build_poll_field(embed, message); + embed = build_diff_field(embed, &message.content, new_content); + build_attachments_field(embed, message) +} diff --git a/src/features/message_logging/handler.rs b/src/features/message_logging/handler.rs new file mode 100644 index 0000000..b9c3130 --- /dev/null +++ b/src/features/message_logging/handler.rs @@ -0,0 +1,122 @@ +use serenity::{ + all::{Context, CreateEmbed, Mentionable, Message, MessageBuilder, MessageId, Timestamp}, + model::{event::FullEvent, id::GenericChannelId}, +}; +use tracing::error; +use valine_bot_macros::event_handler; + +use crate::{ + app::BotDataExt, + extensions::MessageBuilderTimestampExt, + features::message_logging::{embed_builder::build_embed, log_type::LogType}, + utils::{create_safe_message, send_message}, +}; + +async fn create_and_send_log(ctx: &Context, message: &Message, log_type: LogType) { + if message.author.bot() { + return; + } + + let description = MessageBuilder::new() + .push_bold_safe("メンバー: ") + .mention(&message.author.mention()) + .push_safe(" ") + .push_mono_line_safe(&*message.author.id.to_string()) + .push_bold_safe("メッセージ: ") + .push_safe(&*message.id.link(message.channel_id, message.guild_id).to_string()) + .push_safe(" ") + .push_mono_line_safe(&*message.id.to_string()) + .push_bold_safe("メッセージ送信日時: ") + .push_short_date_medium_timestamp_line(message.timestamp) + .push_bold_safe(format!("{}日時: ", log_type.name()).as_str()) + .push_short_date_medium_timestamp(Timestamp::now()) + .build(); + + let mut embed = CreateEmbed::new() + .title(log_type.title()) + .description(description) + .color(log_type.color()) + .thumbnail( + message + .author + .avatar_url() + .unwrap_or("https://cdn.discordapp.com/embed/avatars/0.png".to_string()), + Some("ユーザーアイコン".into()), + ); + + let new_content = log_type.new_content().unwrap_or(""); + embed = build_embed(message, new_content, embed); + send_log(ctx, embed).await; +} + +async fn send_log<'a>(ctx: &Context, embed: CreateEmbed<'a>) { + let config = ctx.app_config().await; + let log = create_safe_message().add_embed(embed); + let _ = send_message(ctx, &config.message_logging.channel_id, log).await; +} + +async fn handle_message_update(ctx: &Context, old_if_available: &Option, new_message: &Message) { + let Some(message) = old_if_available else { + return error!("Failed to get message: {}", new_message.id); + }; + + if message.content == new_message.content { + return; + } + + create_and_send_log( + ctx, + message, + LogType::Edit { + new_content: new_message.content.to_string(), + }, + ) + .await; +} + +async fn handle_message_delete(ctx: &Context, channel_id: &GenericChannelId, deleted_message_id: &MessageId) { + let message = match ctx.cache.message(*channel_id, *deleted_message_id) { + Some(message) => message.clone(), + None => return error!("Failed to get message: {deleted_message_id}"), + }; + + create_and_send_log(ctx, &message, LogType::Delete).await; +} + +async fn handle_message_delete_bulk(ctx: &Context, channel_id: &GenericChannelId, deleted_message_ids: &[MessageId]) { + for message_id in deleted_message_ids { + let message = match ctx.cache.message(*channel_id, *message_id) { + Some(message) => message.clone(), + None => { + error!("Failed to get message: {message_id}"); + continue; + } + }; + create_and_send_log(ctx, &message, LogType::Delete).await; + } +} + +#[event_handler] +pub async fn handle_message_logging_event(ctx: &Context, event: &FullEvent) { + match event { + FullEvent::MessageUpdate { + old_if_available, + event, + .. + } => handle_message_update(ctx, old_if_available, &event.message).await, + + FullEvent::MessageDelete { + channel_id, + deleted_message_id, + .. + } => handle_message_delete(ctx, channel_id, deleted_message_id).await, + + FullEvent::MessageDeleteBulk { + channel_id, + multiple_deleted_messages_ids, + .. + } => handle_message_delete_bulk(ctx, channel_id, multiple_deleted_messages_ids).await, + + _ => {} + } +} diff --git a/src/features/message_logging/log_type.rs b/src/features/message_logging/log_type.rs new file mode 100644 index 0000000..9999913 --- /dev/null +++ b/src/features/message_logging/log_type.rs @@ -0,0 +1,33 @@ +use serenity::model::Color; + +pub(in crate::features::message_logging) enum LogType { + Edit { new_content: String }, + Delete, +} + +impl LogType { + pub fn name(&self) -> &'static str { + match self { + LogType::Edit { .. } => "編集", + LogType::Delete => "削除", + } + } + + pub fn title(&self) -> String { + format!("メッセージ{}ログ", self.name()) + } + + pub fn color(&self) -> Color { + match self { + LogType::Edit { .. } => Color::ORANGE, + LogType::Delete => Color::RED, + } + } + + pub fn new_content(&self) -> Option<&str> { + match self { + LogType::Edit { new_content } => Some(new_content), + LogType::Delete => None, + } + } +} diff --git a/src/features/message_logging/mod.rs b/src/features/message_logging/mod.rs new file mode 100644 index 0000000..29a736c --- /dev/null +++ b/src/features/message_logging/mod.rs @@ -0,0 +1,5 @@ +mod embed_builder; +mod handler; +mod log_type; + +pub use handler::handle_message_logging_event; diff --git a/src/features/mod.rs b/src/features/mod.rs new file mode 100644 index 0000000..48e5944 --- /dev/null +++ b/src/features/mod.rs @@ -0,0 +1,71 @@ +mod admin; +mod auth; +mod honeypot; +mod message_cache_handler; +mod message_logging; +mod pin; +mod question; +mod thread_auto_invite; + +use std::borrow::Cow; + +use crate::{ + app::{AppCommand, config::AppConfig}, + core::BotEventHandlers, + features::{ + auth::{AutoKickEventHandler, KeywordAuthEventHandler}, + honeypot::handle_honeypot_event, + message_cache_handler::MessageCacheHandler, + message_logging::handle_message_logging_event, + question::handle_question_event, + thread_auto_invite::handle_thread_auto_invite_event, + }, +}; + +pub fn event_handlers(config: &AppConfig) -> BotEventHandlers { + BotEventHandlers::new() + .add(handle_honeypot_event) + .add(handle_message_logging_event) + .add(handle_thread_auto_invite_event) + .add(handle_question_event) + .add(KeywordAuthEventHandler::new()) + .add(AutoKickEventHandler::new()) + .add(MessageCacheHandler::new(config.message_cache.disabled)) +} + +pub fn commands() -> Vec { + build_commands( + [ + auth::create_keyword_button, + question::question, + pin::pin, + admin::reload_config, + thread_auto_invite::invite_thread, + thread_auto_invite::add_invite_role, + thread_auto_invite::remove_invite_role, + ] + .to_vec(), + ) +} + +fn alias_command(base: fn() -> AppCommand, name: Cow<'static, str>) -> AppCommand { + let mut command = base(); + command.name = name; + command.aliases = (&[]).into(); + command.context_menu_action = None; + command.context_menu_name = None; + command +} + +fn build_commands(commands: Vec AppCommand>) -> Vec { + commands + .into_iter() + .flat_map(|cmd| { + let base = cmd(); + let aliases = base.aliases.clone(); + std::iter::once(base) + .chain(aliases.iter().map(move |a| alias_command(cmd, a.clone()))) + .collect::>() + }) + .collect() +} diff --git a/src/features/pin.rs b/src/features/pin.rs new file mode 100644 index 0000000..de8eff7 --- /dev/null +++ b/src/features/pin.rs @@ -0,0 +1,93 @@ +use std::time::Duration; + +use futures::StreamExt; +use poise::say_reply; +use serenity::{ + all::{Message, MessageType}, + collector::CollectMessages, + model::{channel::Channel, id::MessageId}, +}; + +use crate::{ + app::{AppContext, AppError, BotDataExt, config::AppConfig}, + utils::has_authed_role, +}; + +async fn check_owner(ctx: AppContext<'_>, config: &AppConfig, channel: &Channel) -> bool { + let author_id = ctx.author().id; + + // コンフィグで設定されたオーナーかどうか + if config.pin.channels.get(&channel.id().expect_channel()) == Some(&author_id) { + return true; + } + + let Channel::GuildThread(channel) = channel else { + return false; + }; + + if channel.owner_id == author_id { + return true; + } + + // 質問フォーラムの場合、初期メッセージのメンションからスレッド主を取得 + if channel.parent_id == config.question.forum_id { + // スレッドの初期メッセージのIDはスレッドのIDと同じ + let Ok(msg) = channel.id.widen().message(ctx, MessageId::new(channel.id.get())).await else { + // メッセージが取得できない場合はスレッドオーナーではない判定 + return false; + }; + + if msg.mentions.iter().any(|m| m.id == author_id) { + return true; + } + } + + false +} + +/// スレッド主限定でメッセージをピン留めします。 +#[poise::command( + context_menu_command = "ピン留め", + slash_command, + ephemeral, + guild_only, + aliases("ピン留め"), + required_bot_permissions = "MANAGE_MESSAGES", + check = "has_authed_role" +)] +pub async fn pin( + ctx: AppContext<'_>, + #[description = "ピン留めするメッセージ (リンクかID)"] msg: Message, +) -> Result<(), AppError> { + let config = ctx.app_config().await; + let channel = ctx.channel().await.unwrap(); + + if !check_owner(ctx, &config, &channel).await { + say_reply(ctx, "あなたはこのチャンネルでピン留めできません。").await?; + return Ok(()); + } + + let mut stream = channel + .id() + .collect_messages(ctx.serenity_context()) + .timeout(Duration::from_secs(5)) + .channel_id(msg.channel_id) + .author_id(ctx.serenity_context().cache.current_user().id) + .filter(|r| r.kind == MessageType::PinsAdd) + .stream(); + + static PIN_REASON: Option<&str> = Some("/pin コマンドによる操作"); + if msg.pinned() { + msg.unpin(ctx.http(), PIN_REASON).await?; + say_reply(ctx, "ピン留めを解除しました。").await?; + } else { + msg.pin(ctx.http(), PIN_REASON).await?; + say_reply(ctx, "ピン留めしました。").await?; + } + + if let Some(msg) = stream.next().await { + let _ = msg.delete(ctx.http(), None).await; + } + + Ok(()) +} diff --git a/src/features/question/command.rs b/src/features/question/command.rs new file mode 100644 index 0000000..f038ec9 --- /dev/null +++ b/src/features/question/command.rs @@ -0,0 +1,205 @@ +use poise::CreateReply; +use serenity::all::{ + ButtonStyle, CreateActionRow, CreateButton, CreateForumPost, CreateMessage, CreateSelectMenu, CreateSelectMenuKind, + CreateSelectMenuOption, ForumEmoji, ForumTag, ForumTagId, MessageBuilder, ReactionType, +}; +use serenity::builder::CreateComponent; +use tokio::sync::{RwLock, mpsc}; +use tracing::debug; + +use std::borrow::Cow; +use std::ops::Deref; +use std::sync::Arc; +use std::vec; + +use crate::app::{AppApplicationContext, AppError, BotDataExt}; +use crate::features::question::QUESTION_CLOSE_PREFIX; +use crate::features::question::modal::{BasicQuestionData, DetailedQuestionData}; +use crate::features::question::question_creation_handler::{CustomIds, QuestionCreationHandler}; +use crate::utils::has_authed_role; + +fn reaction_from_forum_emoji(emoji: &ForumEmoji) -> Option { + match emoji.clone() { + ForumEmoji::Id(emoji) => Some(emoji.into()), + ForumEmoji::Name(emoji) => Some(emoji.deref().try_into().unwrap()), + _ => None, + } +} + +fn create_select_menu<'a>( + custom_id: impl Into>, + available_tags: &[ForumTag], + exclude_tags: &[ForumTagId], + selected_tags: &[ForumTagId], +) -> CreateComponent<'a> { + let options = available_tags + .iter() + .filter(|x| !exclude_tags.contains(&x.id)) + .map(|x| { + let opt = CreateSelectMenuOption::new(x.name.clone(), x.id.to_string()) + .default_selection(selected_tags.contains(&x.id)); + match &x.emoji { + Some(emoji) => opt.emoji(reaction_from_forum_emoji(emoji).unwrap()), + None => opt, + } + }) + .collect::>(); + + let length = options.len(); + let select_menu = CreateSelectMenu::new( + custom_id, + CreateSelectMenuKind::String { + options: options.into(), + }, + ) + .min_values(1) + .max_values(length.try_into().unwrap()) + .placeholder("タグを選択してください"); + + CreateComponent::ActionRow(CreateActionRow::select_menu(select_menu)) +} + +/// Modに関する質問を行うためのフォーラムを作成します。 +#[poise::command( + slash_command, + ephemeral, + guild_only, + aliases("質問開始"), + member_cooldown = 60, + required_bot_permissions = "CREATE_PUBLIC_THREADS", + check = "has_authed_role" +)] +pub async fn question(ctx: AppApplicationContext<'_>) -> Result<(), AppError> { + let custom_ids = Arc::new(CustomIds::new(ctx.id())); + + ctx.defer_ephemeral().await?; + + let submit_button = CreateButton::new(&custom_ids.submit) + .label("質問を送信") + .style(ButtonStyle::Success); + + let config = &ctx.app_config().await.question; + let Ok(channel) = config.forum_id.to_guild_channel(&ctx, ctx.guild_id()).await else { + return Err("Failed to create forum channel".into()); + }; + + let buttons = &[ + CreateButton::new(&custom_ids.basic) + .label("質問の基本情報を入力") + .style(ButtonStyle::Primary), + CreateButton::new(&custom_ids.detailed) + .label("質問の詳細情報を入力") + .style(ButtonStyle::Primary), + submit_button.clone().disabled(true), + ]; + + const PROMPT: &str = "ボタンをクリックしてすべての情報を入力してください。\nセレクトボックスからタグを設定してください。\nまた、再度ボタンをクリックすると入力内容を編集することができます。"; + let message = ctx + .send(CreateReply::default().content(PROMPT).components(&[ + create_select_menu( + &custom_ids.select_tag, + &channel.available_tags, + &config.exclude_tags, + &[], + ), + CreateComponent::ActionRow(CreateActionRow::buttons(&buttons.clone())), + ])) + .await?; + + let basic_data = Arc::new(RwLock::new(None::)); + let detailed_data = Arc::new(RwLock::new(None::)); + let forum_tag_ids = Arc::new(RwLock::new(Vec::::new())); + + let (submit_tx, mut submit_rx) = mpsc::channel::<()>(1); + let (inputted_tx, mut inputted_rx) = mpsc::channel::<()>(1); + + { + let handler = QuestionCreationHandler { + ctx: ctx.serenity_context().clone(), + interaction: ctx.interaction.clone(), + custom_ids: custom_ids.clone(), + basic_data: basic_data.clone(), + detailed_data: detailed_data.clone(), + forum_tag_ids: forum_tag_ids.clone(), + submit_tx, + inputted_tx, + }; + + handler.handle_component_interaction(); + handler.handle_modal_interaction(); + } + + if inputted_rx.recv().await.is_none() { + debug!("inputted_rx closed"); + return Ok(()); + } + inputted_rx.close(); + + const CONFIRM: &str = "情報が入力されました、内容を確認し問題なければ「質問を送信」ボタンをクリックしてください。\n### この機能で作成されるフォームは編集出来ません、間違いが無いように気をつけてください。"; + message + .edit( + ctx.into(), + CreateReply::default() + .content(format!("{PROMPT}\n{CONFIRM}")) + .components(&[ + create_select_menu( + &custom_ids.select_tag, + &channel.available_tags, + &config.exclude_tags, + &forum_tag_ids.read().await, + ), + CreateComponent::ActionRow(CreateActionRow::buttons(&{ + let mut c = buttons.clone(); + c[2] = submit_button.clone(); + c + })), + ]), + ) + .await?; + + if submit_rx.recv().await.is_none() { + debug!("submit_rx closed"); + return Ok(()); + } + submit_rx.close(); + + let basic_data = basic_data.read().await.clone().unwrap(); + let detailed_data = detailed_data.read().await.clone().unwrap(); + let forum_tag_ids = forum_tag_ids.read().await; + + let msg = MessageBuilder::new() + .push_line(&*basic_data.to_string()) + .push_line(&*detailed_data.to_string()) + .push("\n質問者: ") + .mention(&ctx.interaction.user) + .build(); + + let forum_channel = channel + .id + .create_forum_post( + ctx.http(), + CreateForumPost::new( + &basic_data.title, + CreateMessage::default() + .content(msg) + .components(&[CreateComponent::ActionRow(CreateActionRow::buttons(&[ + CreateButton::new(&*format!("{QUESTION_CLOSE_PREFIX}:{}", ctx.interaction.user.id)) + .label("質問を解決済みにする") + .style(ButtonStyle::Danger), + ]))]), + ) + .set_applied_tags(&*forum_tag_ids), + ) + .await?; + + let msg = MessageBuilder::new() + .push_line_safe("質問フォーラムを開始しました。") + .mention(&forum_channel) + .build(); + + message + .edit(ctx.into(), CreateReply::default().content(msg).components(vec![])) + .await?; + + Ok(()) +} diff --git a/src/features/question/mod.rs b/src/features/question/mod.rs new file mode 100644 index 0000000..957c80f --- /dev/null +++ b/src/features/question/mod.rs @@ -0,0 +1,124 @@ +mod command; +mod modal; +mod question_creation_handler; + +use std::{str::FromStr, time::Duration}; + +pub use command::question; + +use serenity::{ + all::{ + ButtonStyle, CacheHttp, ComponentInteractionCollector, ComponentInteractionDataKind, Context, CreateActionRow, + CreateButton, EditInteractionResponse, EditThread, Interaction, UserId, + }, + builder::CreateComponent, + model::event::FullEvent, +}; +use tracing::error; +use valine_bot_macros::event_handler; + +use crate::{app::BotDataExt, utils::create_interaction_message}; + +pub static QUESTION_CLOSE_PREFIX: &str = "close_question_forum"; + +async fn handle_interaction_create(ctx: &Context, interaction: &Interaction) { + let Interaction::Component(interaction) = interaction else { + return; + }; + let ComponentInteractionDataKind::Button = interaction.data.kind else { + return; + }; + let custom_id = &interaction.data.custom_id; + if !custom_id.starts_with(QUESTION_CLOSE_PREFIX) { + return; + } + + let (_, author_id) = custom_id.split_at(QUESTION_CLOSE_PREFIX.len() + 1); + let author_id = UserId::from_str(author_id).unwrap(); + if author_id != interaction.user.id { + return; + } + + let config = &ctx.app_config().await.question; + let Ok(thread) = interaction + .channel_id + .expect_thread() + .to_thread(&ctx, interaction.guild_id) + .await + else { + return error!("Failed to get channel: {:#?}", interaction.channel_id); + }; + if thread.applied_tags.contains(&config.solved_tag) { + let _ = interaction + .create_response(ctx.http(), create_interaction_message("既に解決済みです。", true, None)) + .await; + } + + let confirm_custom_id = format!("close_question_confirm:{}", interaction.id); + let cancel_custom_id = format!("close_question_cancel:{}", interaction.id); + + let _ = interaction + .create_response( + ctx.http(), + create_interaction_message( + "本当に質問を終了しますか?", + true, + Some(&[CreateComponent::ActionRow(CreateActionRow::buttons(&[ + CreateButton::new(&confirm_custom_id) + .label("はい") + .emoji('✅') + .style(ButtonStyle::Danger), + CreateButton::new(&cancel_custom_id) + .label("いいえ") + .emoji('❎') + .style(ButtonStyle::Success), + ]))]), + ), + ) + .await; + + let res = ComponentInteractionCollector::new(ctx) + .custom_ids( + [confirm_custom_id.clone(), cancel_custom_id] + .map(|id| id.try_into().unwrap()) + .into(), + ) + .timeout(Duration::from_secs(60)) + .await; + + let (confirmed, text) = match res { + Some(i) if i.data.custom_id == confirm_custom_id => (true, "質問を解決済みにしました。"), + _ => (false, "キャンセルしました。"), + }; + + if confirmed { + let mut applied_tags = thread.applied_tags.to_vec(); + applied_tags.push(config.solved_tag); + + interaction + .channel_id + .expect_thread() + .edit( + ctx.http(), + EditThread::new() + .name(format!("{}{}", config.solved_name_prefix, thread.base.name)) + .applied_tags(applied_tags), + ) + .await + .unwrap(); + } + + let _ = interaction + .edit_response( + ctx.http(), + EditInteractionResponse::new().content(text).components(vec![]), + ) + .await; +} + +#[event_handler] +pub async fn handle_question_event(ctx: &Context, event: &FullEvent) { + if let FullEvent::InteractionCreate { interaction, .. } = event { + handle_interaction_create(ctx, interaction).await; + } +} diff --git a/src/features/question/modal.rs b/src/features/question/modal.rs new file mode 100644 index 0000000..d7b2814 --- /dev/null +++ b/src/features/question/modal.rs @@ -0,0 +1,85 @@ +use std::fmt::Display; + +#[derive(Debug, Clone, poise::Modal)] +#[name = "質問の基本情報を入力"] +pub struct BasicQuestionData { + #[name = "質問のタイトル (わかりやすいように質問内容を要約してください)"] + #[placeholder = "質問のタイトルを入力してください"] + #[min_length = 10] + #[max_length = 100] + pub title: String, + #[name = "Minecraftのバージョン"] + #[placeholder = "Minecraftのバージョンを入力してください"] + #[min_length = 3] + #[max_length = 20] + pub mc_version: String, + #[name = "Modローダー (Forge, Fabric, NeoForge, Quilt, その他)"] + #[placeholder = "使用しているModローダーを選択してください"] + #[min_length = 3] + #[max_length = 20] + pub loader: String, +} + +impl Display for BasicQuestionData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "### 基本情報\n- Minecraftバージョン: {}\n- Modローダー: {}", + self.mc_version, self.loader + ) + } +} + +#[derive(Debug, Clone, poise::Modal)] +#[name = "質問の詳細情報を入力"] +pub struct DetailedQuestionData { + #[name = "質問の内容 (詳細な質問内容を入力してください)"] + #[placeholder = "質問の内容を入力してください"] + #[min_length = 20] + #[max_length = 1000] + #[paragraph] + pub content: String, + #[name = "問題解決の達成基準"] + #[placeholder = "問題解決の達成基準を入力してください"] + #[min_length = 20] + #[max_length = 1000] + #[paragraph] + pub content2: String, + #[name = "試したこと・調べたこと"] + #[placeholder = "質問を行う前に試したことや調べたことを入力してください"] + #[min_length = 20] + #[max_length = 1000] + #[paragraph] + pub content3: String, + #[name = "mclo.gs にアップロードしたログやクラッシュレポートなどのリンク (任意)"] + #[placeholder = "mclo.gs にアップロードしたログやクラッシュレポートなどのリンクを入力してください"] + #[max_length = 1000] + #[paragraph] + pub logs: Option, +} + +impl Display for DetailedQuestionData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "### 質問内容\n- 質問内容:\n{}\n- 問題解決の達成基準:\n{}\n- 試したこと・調べたこと:\n{}\n- ログやクラッシュレポートのリンク:\n{}", + self.content, + self.content2, + self.content3, + self.logs.as_deref().unwrap_or("なし") + ) + } +} + +impl Default for DetailedQuestionData { + fn default() -> Self { + Self { + content: "例:クラッシュした, 変な挙動をする, modの扱い方がわからない".to_string(), + content2: "例: クラッシュから抜け出したい, このような挙動にしたい, このmodでこのようなことがしたい" + .to_string(), + content3: "例:○○というサイトに掲載されてた対処法を試した\nAIに聞いてみてこのような回答を得られた\nなど" + .to_string(), + logs: None, + } + } +} diff --git a/src/features/question/question_creation_handler.rs b/src/features/question/question_creation_handler.rs new file mode 100644 index 0000000..19c7021 --- /dev/null +++ b/src/features/question/question_creation_handler.rs @@ -0,0 +1,176 @@ +use std::{str::FromStr, sync::Arc, time::Duration}; + +use poise::Modal; +use serenity::{ + all::{ + CacheHttp, CommandInteraction, ComponentInteraction, ComponentInteractionCollector, + ComponentInteractionDataKind, Context, ForumTagId, ModalInteractionCollector, ModalInteractionData, + }, + futures::StreamExt, + small_fixed_array::{FixedArray, FixedString}, +}; +use tokio::sync::{RwLock, mpsc}; +use tracing::error; + +use super::modal::{BasicQuestionData, DetailedQuestionData}; + +pub struct CustomIds { + pub basic: FixedString, + pub detailed: FixedString, + pub select_tag: FixedString, + pub submit: FixedString, +} + +impl CustomIds { + pub fn new(id: u64) -> Self { + Self { + basic: format!("open_basic_question_modal:{id}").try_into().unwrap(), + detailed: format!("open_detailed_question_modal:{id}").try_into().unwrap(), + select_tag: format!("question_select_tag:{id}").try_into().unwrap(), + submit: format!("question_submit:{id}").try_into().unwrap(), + } + } + + pub fn to_vec(&self) -> Vec { + [ + self.basic.clone(), + self.detailed.clone(), + self.select_tag.clone(), + self.submit.clone(), + ] + .to_vec() + } + + pub fn to_fixed_array(&self) -> FixedArray { + self.to_vec().try_into().unwrap() + } +} + +static TIMEOUT: Duration = Duration::from_secs(3600); + +#[derive(Clone)] +pub struct QuestionCreationHandler { + pub ctx: Context, + pub interaction: CommandInteraction, + pub custom_ids: Arc, + pub basic_data: Arc>>, + pub detailed_data: Arc>>, + pub forum_tag_ids: Arc>>, + pub submit_tx: mpsc::Sender<()>, + pub inputted_tx: mpsc::Sender<()>, +} + +impl QuestionCreationHandler { + async fn enable_button(&self) { + if self.inputted_tx.is_closed() { + return; + } + + let has_basic_data = self.basic_data.read().await.is_some(); + let has_detailed_data = self.detailed_data.read().await.is_some(); + let has_forum_tag_ids = !self.forum_tag_ids.try_read().unwrap().is_empty(); + + if has_basic_data && has_detailed_data && has_forum_tag_ids { + self.inputted_tx.send(()).await.unwrap(); + } + } + + async fn send_modal(&self, interaction: &ComponentInteraction, default: Option, custom_id: &str) { + let modal = M::create(default, custom_id.to_owned()); + let Ok(_) = interaction.create_response(self.ctx.http(), modal.clone()).await else { + error!("Failed to create response: {modal:#?}"); + return; + }; + } + + async fn parse_response(&self, data: &ModalInteractionData) -> Option { + Some(M::parse(data.clone())) + } + + pub fn handle_component_interaction(&self) { + let self_clone = self.clone(); + tokio::spawn(async move { + self_clone._handle_component_interaction().await; + }); + } + + async fn _handle_component_interaction(self) { + let mut stream = ComponentInteractionCollector::new(&self.ctx) + .custom_ids(self.custom_ids.to_fixed_array()) + .timeout(TIMEOUT) + .stream(); + let http = self.ctx.http(); + while let Some(interaction) = stream.next().await { + match interaction.data.custom_id { + ref x if x == &self.custom_ids.basic => { + self.send_modal::( + &interaction, + self.basic_data.read().await.clone(), + &self.custom_ids.basic, + ) + .await + } + ref x if x == &self.custom_ids.detailed => { + self.send_modal::( + &interaction, + Some(self.detailed_data.read().await.clone().unwrap_or_default()), + &self.custom_ids.detailed, + ) + .await; + } + ref x if x == &self.custom_ids.select_tag => { + if let ComponentInteractionDataKind::StringSelect { ref values } = interaction.data.kind { + let mut tags = self.forum_tag_ids.write().await; + *tags = values.iter().map(|x| ForumTagId::from_str(x).unwrap()).collect(); + interaction.defer(http).await.unwrap(); + }; + } + ref x if x == &self.custom_ids.submit => { + self.submit_tx.send(()).await.unwrap(); + interaction.defer(http).await.unwrap(); + return; + } + _ => {} + } + + // データが入力されたら送信ボタンを有効化する + self.enable_button().await; + } + + let _ = self.interaction.delete_response(http).await; + } + + pub fn handle_modal_interaction(&self) { + let self_clone = self.clone(); + tokio::spawn(async move { + self_clone._handle_modal_interaction().await; + }); + } + + async fn _handle_modal_interaction(self) { + let mut stream = ModalInteractionCollector::new(&self.ctx) + .custom_ids(self.custom_ids.to_vec()) + .timeout(TIMEOUT) + .stream(); + while let Some(res) = tokio::select! { + res = stream.next() => res, + _ = self.submit_tx.closed() => None, + } { + match res.data.custom_id { + ref x if x == &self.custom_ids.basic => { + let mut data = self.basic_data.write().await; + *data = self.parse_response::(&res.data).await; + } + ref x if x == &self.custom_ids.detailed => { + let mut data = self.detailed_data.write().await; + *data = self.parse_response::(&res.data).await; + } + _ => {} + } + + let _ = res.defer(self.ctx.http()).await; + + self.enable_button().await; + } + } +} diff --git a/src/features/thread_auto_invite/command.rs b/src/features/thread_auto_invite/command.rs new file mode 100644 index 0000000..d52e589 --- /dev/null +++ b/src/features/thread_auto_invite/command.rs @@ -0,0 +1,71 @@ +use futures::StreamExt; +use poise::say_reply; + +use crate::{ + app::{AppContext, AppError, BotDataExt}, + features::thread_auto_invite::handler::{assign_role, invite_thread_by_roles, remove_role}, + utils::{has_authed_role, is_in_public_thread, stream_members}, +}; + +/// 招待用ロールを持ったメンバーを実行したスレッドに招待します。 +#[poise::command( + slash_command, + ephemeral, + guild_only, + aliases("スレッドに招待"), + channel_cooldown = 86400, // 24 時間 + check = "has_authed_role", + check = "is_in_public_thread" +)] +pub async fn invite_thread(ctx: AppContext<'_>) -> Result<(), AppError> { + let config = &ctx.app_config().await.thread_auto_invite; + ctx.defer_ephemeral().await?; + invite_thread_by_roles(ctx.serenity_context(), ctx.channel_id(), &config.role_ids).await; + say_reply(ctx, "スレッドに招待しました。").await?; + Ok(()) +} + +/// 表示用のロールを持ったメンバーに呼び出し用のロールを付与します +#[poise::command(slash_command, guild_only, default_member_permissions = "MANAGE_ROLES")] +pub async fn add_invite_role(ctx: AppContext<'_>) -> Result<(), AppError> { + ctx.defer().await?; + + let config = &ctx.app_config().await.thread_auto_invite; + let mut members = stream_members(ctx.serenity_context(), ctx.guild_id().unwrap()); + let mut added_count = 0; + while let Some(member) = members.next().await { + if config.role_ids.iter().any(|r| member.roles.contains(r)) { + remove_role(ctx.serenity_context(), &member, config).await; + } + + if member.roles.contains(&config.display_role_id) { + assign_role(ctx.serenity_context(), &member, config).await; + added_count += 1; + continue; + } + } + + say_reply(ctx, format!("{added_count} 人に招待用ロールを付与しました。")).await?; + Ok(()) +} + +/// 表示用のロールを持ったメンバーに呼び出し用のロールを削除 +#[poise::command(slash_command, guild_only, default_member_permissions = "MANAGE_ROLES")] +pub async fn remove_invite_role(ctx: AppContext<'_>) -> Result<(), AppError> { + ctx.defer().await?; + + let config = &ctx.app_config().await.thread_auto_invite; + let mut members = stream_members(ctx.serenity_context(), ctx.guild_id().unwrap()); + let mut role_count = 0; + while let Some(member) = members.next().await { + if !config.role_ids.iter().any(|r| member.roles.contains(r)) { + continue; + } + + remove_role(ctx.serenity_context(), &member, config).await; + role_count += 1; + } + + say_reply(ctx, format!("{role_count} 人から招待用ロールを全て削除しました。")).await?; + Ok(()) +} diff --git a/src/features/thread_auto_invite/handler.rs b/src/features/thread_auto_invite/handler.rs new file mode 100644 index 0000000..0914491 --- /dev/null +++ b/src/features/thread_auto_invite/handler.rs @@ -0,0 +1,133 @@ +use itertools::Itertools; +use serenity::{ + all::{ChannelType, Context, EditMessage, GuildId, Member, Mentionable, RoleId, prelude::CacheHttp}, + model::{channel::GuildThread, event::FullEvent, id::GenericChannelId}, +}; +use tracing::{error, info}; +use valine_bot_macros::event_handler; + +use crate::{ + app::{BotDataExt, config::ThreadAutoInviteConfig}, + utils::create_message, +}; + +async fn find_role(ctx: &Context, guild_id: GuildId, config: &ThreadAutoInviteConfig) -> Option { + let role_member_counts = ctx + .http + .get_guild_role_member_counts(guild_id) + .await + .map_err(|e| error!("Failed to get guild role member counts: {e:#?}")) + .ok()?; + + config.role_ids.iter().find_map(|&role_id| { + let count = *role_member_counts.get(&role_id)?; + (count < config.min_member_count).then_some(role_id) + }) +} + +pub(in crate::features::thread_auto_invite) async fn assign_role( + ctx: &Context, + new: &Member, + config: &ThreadAutoInviteConfig, +) { + let Some(role) = find_role(ctx, new.guild_id, config).await else { + error!("No role found with count less than {}", config.min_member_count); + return; + }; + + if let Err(e) = new.add_role(ctx.http(), role, None).await { + error!("Failed to add role {role} to member {}: {e:#?}", new.user.id); + } else { + info!("Added role {role} to member {}", new.user.id); + } +} + +pub(in crate::features::thread_auto_invite) async fn remove_role( + ctx: &Context, + old: &Member, + config: &ThreadAutoInviteConfig, +) { + let roles = old + .roles + .iter() + .filter(|r| config.role_ids.contains(r)) + .collect::>(); + + for role_id in roles { + if let Err(e) = old.remove_role(ctx.http(), *role_id, None).await { + error!("Failed to remove role {role_id} from member {}: {e:#?}", old.user.id); + } else { + info!("Removed role {role_id} from member {}", old.user.id); + break; + } + } +} + +pub(in crate::features::thread_auto_invite) async fn invite_thread_by_roles( + ctx: &Context, + thread_id: GenericChannelId, + role_ids: &[RoleId], +) { + let mut message = { + let msg = create_message("スレッド自動招待用メッセージ"); + match thread_id.send_message(ctx.http(), msg).await { + Ok(m) => m, + Err(why) => return error!("Error sending message: {why:#?}"), + } + }; + + let content = role_ids.iter().map(|r| r.mention().to_string()).join(" "); + let _ = message.edit(&ctx, EditMessage::new().content(content)).await; + + let _ = message.delete(ctx.http(), None).await; +} + +async fn handle_thread_create(ctx: &Context, thread: &GuildThread, newly_created: &Option) { + if !newly_created.unwrap_or(false) { + return; + } + + if thread.base.kind == ChannelType::PrivateThread { + return; + } + + let config = &ctx.app_config().await.thread_auto_invite; + invite_thread_by_roles(ctx, thread.id.widen(), &config.role_ids).await; +} + +async fn handle_guild_member_update(ctx: &Context, old: &Option, new: &Option) { + let Some(new) = new else { + error!("Member update event with no new member"); + return; + }; + let Some(old) = old else { + error!("Member update event with no old member"); + return; + }; + + let config = &ctx.app_config().await.thread_auto_invite; + + let has_new_role = new.roles.contains(&config.display_role_id); + let has_old_role = old.roles.contains(&config.display_role_id); + + if has_new_role && !has_old_role { + assign_role(ctx, new, config).await; + } else if has_old_role && !has_new_role { + remove_role(ctx, old, config).await; + } +} + +#[event_handler] +pub async fn handle_thread_auto_invite_event(ctx: &Context, event: &FullEvent) { + match event { + FullEvent::ThreadCreate { + thread, newly_created, .. + } => handle_thread_create(ctx, thread, newly_created).await, + + FullEvent::GuildMemberUpdate { + old_if_available, new, .. + } => handle_guild_member_update(ctx, old_if_available, new).await, + + _ => {} + } +} diff --git a/src/features/thread_auto_invite/mod.rs b/src/features/thread_auto_invite/mod.rs new file mode 100644 index 0000000..6cb7d37 --- /dev/null +++ b/src/features/thread_auto_invite/mod.rs @@ -0,0 +1,5 @@ +mod command; +mod handler; + +pub use command::{add_invite_role, invite_thread, remove_invite_role}; +pub use handler::handle_thread_auto_invite_event; diff --git a/src/main.rs b/src/main.rs index 3a567d0..70b9bbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,123 +1,87 @@ -use std::env; -use std::sync::LazyLock; - -use regex::Regex; -use serenity::all::{ - ChannelId, CreateAllowedMentions, CreateMessage, GuildId, MessageUpdateEvent, Ready, RoleId, +mod app; +mod core; +mod extensions; +mod features; +mod utils; + +use std::sync::Arc; + +use bpaf::Bpaf; +use poise::{Framework, FrameworkOptions, PrefixFrameworkOptions}; +use serenity::{cache::Settings as CacheSettings, prelude::*}; +use tracing::error; + +use crate::{ + app::{AppError, BotData, MainEventHandler, config::AppConfig, on_error}, + core::create_client, + features::{commands, event_handlers}, }; -use serenity::async_trait; -use serenity::model::channel::Message; -use serenity::prelude::*; -use tracing::{error, info}; - -static TRIGGER_REGEX: LazyLock = - LazyLock::new(|| Regex::new(&env::var("TRIGGER_REGEX").unwrap()).unwrap()); -static CHANNEL_ID: LazyLock = - LazyLock::new(|| ChannelId::new(env::var("CHANNEL_ID").unwrap().parse().unwrap())); -static LOG_CHANNEL_ID: LazyLock = - LazyLock::new(|| ChannelId::new(env::var("LOG_CHANNEL_ID").unwrap().parse().unwrap())); -static ROLE_ID: LazyLock = - LazyLock::new(|| RoleId::new(env::var("ROLE_ID").unwrap().parse().unwrap())); - -fn create_message(content: String) -> CreateMessage { - CreateMessage::new() - .content(content) - .allowed_mentions(CreateAllowedMentions::new().all_users(false)) -} - -struct Handler; - -impl Handler { - async fn handle_message(&self, ctx: &Context, guild_id: GuildId, msg: Message) { - if !TRIGGER_REGEX.is_match(&msg.content) { - return; - } - if msg.channel_id != *CHANNEL_ID { - return; - } - - let member = match guild_id.member(&ctx.http, msg.author.id).await { - Ok(member) => member, - Err(why) => return error!("Failed to get member: {:?}", why), - }; - - if member.roles.contains(&ROLE_ID) { - error!("{} already has the role", member.user.name); - return; - } - - if let Err(why) = member.add_role(&ctx.http, *ROLE_ID).await { - let log = create_message(format!( - "{} にロールを追加できませんでした。\n```\n{}```", - member.mention(), - why - )); - if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { - error!("Error sending message: {:?}", why) - } - return error!("Failed to add role: {:?}", why); - } - - let log = create_message(format!("{} にロールを追加しました。", member.mention())); - if let Err(why) = LOG_CHANNEL_ID.send_message(&ctx.http, log).await { - error!("Error sending message: {:?}", why) - } - } -} - -#[async_trait] -impl EventHandler for Handler { - async fn message(&self, ctx: Context, msg: Message) { - let Some(guild_id) = msg.guild_id else { - return error!("Failed to get guild id: {:?}", msg); - }; - self.handle_message(&ctx, guild_id, msg).await; - } - - async fn message_update( - &self, - ctx: Context, - _: Option, - _: Option, - event: MessageUpdateEvent, - ) { - let Some(guild_id) = event.guild_id else { - return error!("Failed to get guild id: {:?}", event); - }; - match event.channel_id.message(&ctx.http, event.id).await { - Ok(msg) => self.handle_message(&ctx, guild_id, msg).await, - Err(why) => error!("Failed to get message: {:?}", why), - } - } - - async fn ready(&self, _: Context, ready: Ready) { - info!("{} is connected!", ready.user.name); - } +#[derive(Clone, Debug, Bpaf)] +#[bpaf(options, version)] +struct Options { + #[bpaf(short, long)] + check_config: bool, } #[tokio::main] -async fn main() { +async fn main() -> Result<(), AppError> { tracing_subscriber::fmt::init(); - let _ = dotenvy::dotenv(); + let config = AppConfig::from_file("config.toml").await?; - let token = env::var("TOKEN").expect("Expected a TOKEN in the environment"); - let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT; - let mut client = Client::builder(&token, intents) - .event_handler(Handler) - .await - .expect("Err creating client"); + let options = options().run(); - let shard_manager = client.shard_manager.clone(); + if options.check_config { + println!("Config is valid"); + return Ok(()); + } + + let framework = Framework::builder() + .options(FrameworkOptions { + prefix_options: PrefixFrameworkOptions { + prefix: None, + mention_as_prefix: false, + ..Default::default() + }, + commands: commands(), + on_error: |error| Box::pin(on_error(error)), + skip_checks_for_owners: false, + owners: config.bot.owners.clone(), + ..Default::default() + }) + .build(); + + let intents = GatewayIntents::GUILD_MESSAGES + | GatewayIntents::GUILDS + | GatewayIntents::GUILD_MEMBERS + | GatewayIntents::MESSAGE_CONTENT; + + let mut settings = CacheSettings::default(); + settings.max_messages = usize::MAX; + + let mut client = create_client( + config.bot.token.clone(), + intents, + event_handlers(&config).add(MainEventHandler::new()), + ) + .framework(Box::new(framework)) + .cache_settings(settings) + .data(Arc::new(BotData::new(config))) + .await + .expect("Err creating client"); + + let shutdown = client.shard_manager.get_shutdown_trigger(); tokio::spawn(async move { tokio::signal::ctrl_c() .await .expect("Could not register ctrl+c handler"); - shard_manager.shutdown_all().await; + shutdown() }); if let Err(why) = client.start().await { - error!("Client error: {:?}", why); + error!("Client error: {:#?}", why); } + + Ok(()) } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..d2476a7 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,194 @@ +use std::{borrow::Cow, time::Duration}; + +use async_stream::stream; +use futures::{ + Stream, StreamExt, + stream::{self, BoxStream}, +}; +use itertools::Itertools; +use serenity::{ + Result, + all::{ChannelId, Context, CreateAllowedMentions, CreateMessage, Message, prelude::CacheHttp}, + builder::{ + CreateComponent, CreateInteractionResponse, CreateInteractionResponseMessage, CreateModal, CreateModalComponent, + }, + model::{ + channel::{ChannelType, GuildThread}, + guild::Member, + id::GuildId, + }, +}; +use similar::{Algorithm, ChangeTag, TextDiff}; +use tracing::error; + +use crate::app::{AppContext, AppError, BotDataExt, BotError}; + +pub fn create_safe_message<'a>() -> CreateMessage<'a> { + CreateMessage::new().allowed_mentions(CreateAllowedMentions::new().all_users(false)) +} + +pub fn create_message<'a>(content: impl Into>) -> CreateMessage<'a> { + create_safe_message().content(content) +} + +pub fn create_interaction_message<'a>( + content: impl Into>, + ephemeral: bool, + components: Option<&'a [CreateComponent<'a>]>, +) -> CreateInteractionResponse<'a> { + let mut msg = CreateInteractionResponseMessage::new() + .content(content) + .ephemeral(ephemeral); + + if let Some(components) = components { + msg = msg.components(components); + } + + CreateInteractionResponse::Message(msg) +} + +pub fn create_ephemeral_message<'a>( + content: impl Into>, + components: Option<&'a [CreateComponent<'a>]>, +) -> CreateInteractionResponse<'a> { + create_interaction_message(content, true, components) +} + +pub fn create_model<'a>( + custom_id: impl Into>, + title: impl Into>, + components: impl Into]>>, +) -> CreateInteractionResponse<'a> { + CreateInteractionResponse::Modal(CreateModal::new(custom_id, title).components(components)) +} + +/* +認証済みロールを持っているかどうかを確認します。 +*/ +pub async fn has_authed_role(ctx: AppContext<'_>) -> Result { + let Some(member) = ctx.author_member().await else { + return Ok(false); + }; + + let config = ctx.app_config().await; + if !member.roles.contains(&config.auth.role_id) { + Err(BotError::HasNoRole.into()) + } else { + Ok(true) + } +} + +/* +実行した場所がパブリックスレッドであるかどうかを確認します。 +*/ +pub async fn is_in_public_thread(ctx: AppContext<'_>) -> Result { + let thread = ctx + .channel() + .await + .and_then(|t| t.thread()) + .ok_or(BotError::IsNotInThread)?; + match thread.base.kind { + ChannelType::PublicThread | ChannelType::NewsThread => Ok(true), + ChannelType::PrivateThread => Err(BotError::IsPrivateThread.into()), + _ => Err(BotError::IsNotInThread.into()), + } +} + +const UNITS: [(u64, &str); 4] = [(86400, "日"), (3600, "時間"), (60, "分"), (1, "秒")]; + +pub fn format_duration(duration: Duration, mut count: usize) -> String { + let mut remaining = duration.as_secs(); + let mut parts = Vec::new(); + + for (unit, label) in UNITS { + if remaining >= unit && count > 0 { + let value = remaining / unit; + if value > 0 { + parts.push(format!("{value}{label}")); + remaining %= unit; + count -= 1; + } + } + } + + parts.join(" ") +} + +pub async fn send_message<'a>(ctx: &Context, channel_id: &ChannelId, builder: CreateMessage<'a>) -> Result { + match channel_id.widen().send_message(ctx.http(), builder).await { + Ok(m) => Ok(m), + Err(why) => { + error!("Error sending message: {:#?}", why); + Err(why) + } + } +} + +/** +指定されたチャンネルのアーカイブされたパブリックスレッドをすべて取得します。 + */ +pub async fn fetch_all_archived_public_thread( + ctx: &Context, + channel_id: ChannelId, + max_retries: Option, +) -> impl Stream { + let max_retries = max_retries.unwrap_or(5); + Box::pin(stream! { + let mut retries_left = max_retries; + let mut before = None; + loop { + let thread_data = match ctx.http.get_channel_archived_public_threads(channel_id, before, Some(100)).await { + Ok(data) => data, + Err(_) => { + if retries_left == 0 { + break; + } else { + retries_left -= 1; + continue; + } + } + }; + + before = thread_data.threads + .last() + .and_then(|last| last.thread_metadata.archive_timestamp); + + for channel in thread_data.threads { + yield channel; + } + + if !thread_data.has_more || before.is_none() { + break; + } + } + }) +} + +pub fn create_diff_lines_text(old: &str, new: &str) -> String { + let diff = TextDiff::configure().algorithm(Algorithm::Myers).diff_lines(old, new); + diff.iter_all_changes() + .map(|c| match c.tag() { + ChangeTag::Delete => format!("- {c}"), + ChangeTag::Insert => format!("+ {c}"), + ChangeTag::Equal => format!(" {c}"), + }) + .join("") +} + +pub fn stream_members(ctx: &Context, guild_id: GuildId) -> BoxStream<'_, Member> { + let cached_members = guild_id + .to_guild_cached(&ctx.cache) + .filter(|guild| guild.members.len() as u32 >= guild.member_count.get()) + .map(|guild| guild.members.clone()); + + let stream = if let Some(members) = cached_members { + stream::iter(members).left_stream() + } else { + guild_id + .members_iter(ctx.http()) + .filter_map(|result| async { result.ok() }) + .right_stream() + }; + + stream.boxed() +} diff --git a/taplo.toml b/taplo.toml new file mode 100644 index 0000000..4332056 --- /dev/null +++ b/taplo.toml @@ -0,0 +1,15 @@ +[formatting] +column_width = 120 +compact_arrays = false +compact_entries = false +compact_inline_tables = false +reorder_keys = false + + +[[rule]] +include = [ "**/Cargo.toml" ] +keys = [ "dependencies" ] + + +[rule.formatting] +reorder_keys = true