diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bd1c70f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + +jobs: + check: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + steps: + - uses: actions/checkout@v7 + - uses: dtolnay/rust-toolchain@stable + with: + components: clippy, rustfmt + - run: cargo fmt --check + - run: cargo clippy --locked --all-targets -- -D warnings + - run: cargo test --locked diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8d57add..9fa7814 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,7 +22,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable @@ -30,9 +30,8 @@ jobs: toolchain: stable target: ${{ matrix.target }} - - name: Install dependencies (Linux) - if: matrix.os == 'ubuntu-latest' - run: sudo apt-get update && sudo apt-get install -y libssl-dev pkg-config + - name: Run tests + run: cargo test --locked - name: Build application run: cargo build --release --target ${{ matrix.target }} --locked @@ -53,7 +52,7 @@ jobs: shell: pwsh - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ matrix.artifact_name }} path: ${{ matrix.asset_name }} @@ -68,19 +67,24 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 + - name: Check tag matches Cargo.toml version + run: | + v=$(grep -m1 '^version' Cargo.toml | cut -d'"' -f2) + [ "v$v" = "${{ github.event.release.tag_name }}" ] || [ "$v" = "${{ github.event.release.tag_name }}" ] || { echo "Tag ${{ github.event.release.tag_name }} != Cargo.toml version $v"; exit 1; } + - name: Download all artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: artifacts - name: Attach artifacts to release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: files: artifacts/**/* - name: Publish to crates.io - run: cargo publish + run: cargo publish --locked env: CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 55ab292..a0dd83c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,26 +2,11 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - [[package]] name = "anstream" -version = "0.6.20" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae563653d1938f79b1ab1b5e668c87c76a9930414574a6583a7b7e11a8e6192" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -34,44 +19,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.11" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e231f6134f61b71076a3eab506c379d4f36122f2af15a9ff04415ea4c3339e2" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anstyle-wincon" -version = "3.0.10" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e0633414522a32ffaac8ac6cc8f748e090c5717661fddeea04219e2344f5f2a" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "atomic-waker" @@ -79,77 +64,51 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "backtrace" -version = "0.3.75" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base64" -version = "0.22.1" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "bitflags" -version = "2.9.2" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a65b545ab31d687cff52899d4890855fec459eb6afe0da6417b8a18da87aa29" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytes" -version = "1.10.1" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "cc" -version = "1.2.33" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee0f8803222ba5a7e2777dd72ca451868909b1ac410621b676adf07280e9b5f" +checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" dependencies = [ + "find-msvc-tools", "shlex", ] [[package]] name = "cfg-if" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fd1289c04a9ea8cb22300a459a72a385d7c73d3259e2ed7dcb2af674838cfa9" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" [[package]] name = "clap" -version = "4.5.45" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" dependencies = [ "clap_builder", "clap_derive", @@ -157,9 +116,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.44" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" dependencies = [ "anstream", "anstyle", @@ -169,33 +128,69 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.45" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] name = "clap_lex" -version = "0.7.5" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "combine" +version = "4.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4356eba6976435498d05a311ee9fc486da3bc81c200d3f0d7dd9703b41913f92" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "dirs" -version = "6.0.0" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +checksum = "8d57d423b3c82e89b9a24ca3091fee61f456a26edbd28d26c65906f4bc1dcd8f" dependencies = [ "dirs-sys", ] @@ -209,25 +204,25 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] -name = "fnv" -version = "1.0.7" +name = "find-msvc-tools" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" [[package]] name = "form_urlencoded" @@ -240,75 +235,80 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", ] [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] -name = "futures-task" -version = "0.3.31" +name = "futures-io" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] -name = "futures-util" -version = "0.3.31" +name = "futures-macro" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ - "futures-core", - "futures-task", - "pin-project-lite", - "pin-utils", + "proc-macro2", + "quote", + "syn 3.0.9", ] [[package]] -name = "getrandom" -version = "0.2.16" +name = "futures-sink" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "wasm-bindgen", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", ] [[package]] name = "getrandom" -version = "0.3.3" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", - "js-sys", "libc", - "r-efi", - "wasi 0.14.2+wasi-0.2.4", - "wasm-bindgen", + "wasi", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "glob" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" [[package]] name = "heck" @@ -318,20 +318,19 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "http" -version = "1.3.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", - "fnv", "itoa", ] [[package]] name = "http-body" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" dependencies = [ "bytes", "http", @@ -339,9 +338,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", @@ -358,20 +357,18 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hyper" -version = "1.7.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb3aa54a13a0dfe7fbe3a59e0c76093041720fdc77b110cc0fc260fafb4dc51e" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" dependencies = [ "atomic-waker", "bytes", - "futures-channel", "futures-core", "http", "http-body", "httparse", "itoa", "pin-project-lite", - "pin-utils", "smallvec", "tokio", "want", @@ -379,53 +376,54 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.7" +version = "0.27.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +checksum = "dfa8e654703247911e29c23fbeaa261834bd9bb74efba2f9acddc37bfb127f53" dependencies = [ "http", "hyper", "hyper-util", "rustls", - "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", - "webpki-roots", ] [[package]] name = "hyper-util" -version = "0.1.16" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" dependencies = [ "base64", "bytes", "futures-channel", - "futures-core", "futures-util", "http", "http-body", + "httparse", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.0", + "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] name = "icu_collections" -version = "2.0.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", + "utf8_iter", "yoke", "zerofrom", "zerovec", @@ -433,9 +431,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.0.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -446,11 +444,10 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.0.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ - "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", @@ -461,42 +458,39 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.0.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", - "potential_utf", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.0.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", - "stable_deref_trait", - "tinystr", "writeable", "yoke", "zerofrom", @@ -517,112 +511,124 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] -name = "io-uring" -version = "0.7.9" +name = "ipnet" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" -dependencies = [ - "bitflags", - "cfg-if", - "libc", -] +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] -name = "ipnet" -version = "2.11.0" +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] -name = "iri-string" -version = "0.7.8" +name = "jni" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" dependencies = [ - "memchr", - "serde", + "cfg-if", + "combine", + "jni-macros", + "jni-sys", + "log", + "simd_cesu8", + "thiserror", + "walkdir", + "windows-link", ] [[package]] -name = "is_terminal_polyfill" -version = "1.70.1" +name = "jni-macros" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] [[package]] -name = "itoa" -version = "1.0.15" +name = "jni-sys" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +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.119", +] [[package]] name = "js-sys" -version = "0.3.77" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.175" +version = "0.2.191" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" [[package]] name = "libredox" -version = "0.1.9" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" dependencies = [ - "bitflags", "libc", ] [[package]] name = "litemap" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" - -[[package]] -name = "lock_api" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" -dependencies = [ - "autocfg", - "scopeguard", -] +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "lru-slab" -version = "0.1.2" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "memchr" -version = "2.7.5" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "mime" @@ -640,46 +646,34 @@ dependencies = [ "unicase", ] -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", -] - [[package]] name = "mio" -version = "1.0.4" +version = "1.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" dependencies = [ "libc", - "wasi 0.11.1+wasi-snapshot-preview1", - "windows-sys 0.59.0", + "wasi", + "windows-sys 0.61.2", ] [[package]] -name = "object" -version = "0.36.7" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" -dependencies = [ - "memchr", -] +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "once_cell" -version = "1.21.3" +name = "once_cell_polyfill" +version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] -name = "once_cell_polyfill" -version = "1.70.1" +name = "openssl-probe" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "option-ext" @@ -687,29 +681,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" -[[package]] -name = "parking_lot" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -718,167 +689,52 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] name = "potential_utf" -version = "0.1.2" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - [[package]] name = "proc-macro2" -version = "1.0.101" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ "unicode-ident", ] -[[package]] -name = "quinn" -version = "0.11.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2 0.5.10", - "thiserror", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" -dependencies = [ - "bytes", - "getrandom 0.3.3", - "lru-slab", - "rand", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2 0.5.10", - "tracing", - "windows-sys 0.52.0", -] - [[package]] name = "quote" -version = "1.0.40" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" 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 = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.3", -] - -[[package]] -name = "redox_syscall" -version = "0.5.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" -dependencies = [ - "bitflags", -] - [[package]] name = "redox_users" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" dependencies = [ - "getrandom 0.2.16", "libredox", "thiserror", ] [[package]] name = "reqwest" -version = "0.12.23" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d429f34c8092b2d42c7c93cec323bb4adeb7c67698f70839adec842ec10c7ceb" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ "base64", "bytes", @@ -895,23 +751,23 @@ dependencies = [ "mime_guess", "percent-encoding", "pin-project-lite", - "quinn", "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", - "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", "tokio-rustls", + "tokio-util", "tower", "tower-http", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", - "webpki-roots", ] [[package]] @@ -922,29 +778,26 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] -name = "rustc-demangle" -version = "0.1.26" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" - -[[package]] -name = "rustc-hash" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] [[package]] name = "rustls" -version = "0.23.31" +version = "0.23.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc" +checksum = "48e13bd8c0e9365c43cfa5c9e8f9ad49d3c8444926c9aac819e0e4dc503c8fdf" dependencies = [ "once_cell", "ring", @@ -954,21 +807,59 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a073f5dc7469f984c52ad2752b63b0807745133b6de880b7b64c1ac4c48aec4" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" -version = "1.12.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ - "web-time", "zeroize", ] +[[package]] +name = "rustls-platform-verifier" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1167586491e2b18b8bfbb293e8180ec17c201c4f076d7cb3070ca964e7598f98" +dependencies = [ + "core-foundation 0.10.1", + "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-platform-verifier-android" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eec689c0bc40ff2458a5977b6619cb718087084a18e02a131c599b62d05e1a5f" + [[package]] name = "rustls-webpki" -version = "0.103.4" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "ring", "rustls-pki-types", @@ -977,52 +868,104 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] -name = "scopeguard" -version = "1.2.0" +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cb23c424d8ee841bb4d813677ca237e2a610067d594fec130f87d6093495ab5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "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 = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.219" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] name = "serde_json" -version = "1.0.143" +version = "1.0.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d401abef1d108fbd9cbaebc3e46611f4b1021f714a0597a71f41ee463f5f4a5a" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" dependencies = [ "itoa", "memchr", - "ryu", "serde", + "serde_core", + "zmij", ] [[package]] @@ -1039,56 +982,53 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] -name = "signal-hook-registry" -version = "1.4.6" +name = "simd_cesu8" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" dependencies = [ - "libc", + "rustc_version", + "simdutf8", ] [[package]] -name = "slab" -version = "0.4.11" +name = "simdutf8" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" [[package]] -name = "smallvec" -version = "1.15.1" +name = "slab" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] -name = "socket2" -version = "0.5.10" +name = "smallvec" +version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" [[package]] name = "socket2" -version = "0.6.0" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "strsim" @@ -1104,9 +1044,20 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.106" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ede7c438028d4436d71104916910f5bb611972c5cfd7f89b8300a8186e6fada6" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" dependencies = [ "proc-macro2", "quote", @@ -1124,24 +1075,46 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.13.2" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] name = "telegram-cli" -version = "0.1.1" +version = "0.2.0" dependencies = [ "anyhow", "clap", "dirs", "glob", "reqwest", + "rustls", "serde", "serde_json", "tokio", @@ -1149,95 +1122,88 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "09e52cb86a36cede5cb101bf8908837b3e4c6e5e59fe7fd85c23fb56200d189e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "fe5197923287db20a58125f0bc85c062f7f2c892de97b18c356f9efb14b28524" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", ] -[[package]] -name = "tinyvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - [[package]] name = "tokio" -version = "1.47.1" +version = "1.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038" +checksum = "ce3335fa71841cda333a58d7615b03901380ecf09d59b3296d21f8bbac0dde4e" dependencies = [ - "backtrace", "bytes", - "io-uring", "libc", "mio", - "parking_lot", "pin-project-lite", - "signal-hook-registry", - "slab", - "socket2 0.6.0", + "socket2", "tokio-macros", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.5.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "c9cc2678c2cdd569ef8215e2afd7954ada2ae20b4fdd2c5fe6139a3b02d105db" dependencies = [ "rustls", "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e464cf451ba96ebfc6f9b6542f17ee8b8956e33f1e40d9690624e59d7a7f8a4b" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -1250,20 +1216,20 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.6" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "bitflags", "bytes", "futures-util", "http", "http-body", - "iri-string", "pin-project-lite", "tower", "tower-layer", "tower-service", + "url", ] [[package]] @@ -1280,9 +1246,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", "tracing-core", @@ -1290,9 +1256,9 @@ dependencies = [ [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", ] @@ -1305,15 +1271,15 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicase" -version = "2.8.1" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "357cc3acc6a036009fd6c973ed009037c732d60d0b4f6c673e9041497482a28f" [[package]] name = "unicode-ident" -version = "1.0.18" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" [[package]] name = "untrusted" @@ -1323,13 +1289,14 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.6" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "137a3c834eaf7139b73688502f3f1141a0337c5d8e4d9b536f9b8c796e26a7c4" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -1344,11 +1311,21 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" dependencies = [ "try-lock", ] @@ -1359,59 +1336,35 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasi" -version = "0.14.2+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" -dependencies = [ - "wit-bindgen-rt", -] - [[package]] name = "wasm-bindgen" -version = "0.2.100" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.100" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" -dependencies = [ - "bumpalo", - "log", - "proc-macro2", - "quote", - "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.50" +version = "0.4.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +checksum = "3cbab34de2d982e9b48e18d216d04c4a6f641066ff19ffb699980f591ee3610e" dependencies = [ - "cfg-if", "js-sys", - "once_cell", + "tokio", "wasm-bindgen", - "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.100" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1419,132 +1372,141 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.100" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn", - "wasm-bindgen-backend", + "syn 3.0.9", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.100" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" dependencies = [ "unicode-ident", ] [[package]] -name = "web-sys" -version = "0.3.77" +name = "wasm-streams" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" dependencies = [ + "futures-util", "js-sys", "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] -name = "web-time" -version = "1.1.0" +name = "web-sys" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "webpki-roots" -version = "1.0.2" +name = "webpki-root-certs" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" +checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" dependencies = [ "rustls-pki-types", ] +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "windows-link" -version = "0.1.3" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-sys" -version = "0.52.0" +name = "windows-registry" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" dependencies = [ - "windows-targets 0.52.6", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "windows-sys" -version = "0.59.0" +name = "windows-result" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ - "windows-targets 0.52.6", + "windows-link", ] [[package]] -name = "windows-sys" -version = "0.60.2" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-targets 0.53.3", + "windows-link", ] [[package]] -name = "windows-targets" -version = "0.52.6" +name = "windows-sys" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "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", + "windows-targets", ] [[package]] -name = "windows-targets" -version = "0.53.3" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", - "windows_aarch64_gnullvm 0.53.0", - "windows_aarch64_msvc 0.53.0", - "windows_i686_gnu 0.53.0", - "windows_i686_gnullvm 0.53.0", - "windows_i686_msvc 0.53.0", - "windows_x86_64_gnu 0.53.0", - "windows_x86_64_gnullvm 0.53.0", - "windows_x86_64_msvc 0.53.0", ] [[package]] -name = "windows_aarch64_gnullvm" +name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] [[package]] name = "windows_aarch64_gnullvm" -version = "0.53.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -1552,106 +1514,54 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_aarch64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" - [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" -[[package]] -name = "windows_i686_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" - [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_i686_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnu" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" -[[package]] -name = "windows_x86_64_msvc" -version = "0.53.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" - -[[package]] -name = "wit-bindgen-rt" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" -dependencies = [ - "bitflags", -] - [[package]] name = "writeable" -version = "0.6.1" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "yoke" -version = "0.8.0" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ - "serde", "stable_deref_trait", "yoke-derive", "zerofrom", @@ -1659,68 +1569,48 @@ dependencies = [ [[package]] name = "yoke-derive" -version = "0.8.0" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +checksum = "ec8ebde2db3681e8c9980cc27822030e68752690ddfa9473e739aeb4dbde6d71" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", "synstructure", ] -[[package]] -name = "zerocopy" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "zerofrom" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", "synstructure", ] [[package]] name = "zeroize" -version = "1.8.1" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" [[package]] name = "zerotrie" -version = "0.2.2" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -1729,9 +1619,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.4" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke", "zerofrom", @@ -1740,11 +1630,17 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.1" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 3.0.9", ] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 27f213a..c28bf86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "telegram-cli" -version = "0.1.1" +version = "0.2.0" edition = "2024" +rust-version = "1.88" description = "A simple and powerful CLI tool to interact with the Telegram Bot API." authors = ["1ffycat <1ffycatx@gmail.com>"] license = "MPL-2.0" @@ -11,11 +12,17 @@ keywords = ["telegram", "cli", "bot", "api", "tool"] categories = ["command-line-utilities", "api-bindings"] [dependencies] -clap = { version = "4.5.4", features = ["derive"] } -serde = { version = "1.0.199", features = ["derive"] } -serde_json = "1.0.116" -tokio = { version = "1.37.0", features = ["full"] } -reqwest = { version = "0.12.4", features = ["json", "multipart", "rustls-tls"], default-features = false } -anyhow = "1.0.82" -dirs = "6.0.0" -glob = "0.3.3" \ No newline at end of file +clap = { version = "4.6", features = ["derive", "env"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +tokio = { version = "1.53", features = ["rt", "macros"] } +reqwest = { version = "0.13", default-features = false, features = ["rustls-no-provider", "form", "multipart", "stream", "socks", "system-proxy"] } +anyhow = "1.0" +rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] } +dirs = "7.0" +glob = "0.3" + +[profile.release] +lto = true +codegen-units = 1 +strip = true diff --git a/README.md b/README.md index 9289270..85705a0 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,9 @@ A simple, fast, and powerful command-line tool written in Rust to interact with * Read message content from standard input. * Format messages with Markdown or HTML. * Wrap messages in code blocks with syntax highlighting. -* Configure default bot token and chat ID in a config.json file under `~/.config/telegram-cli/`. +* Works with a config file, environment variables, CLI arguments, or any mix of them. The config file is optional. +* Secrets can be read from files, so [agenix](https://github.com/ryantm/agenix) / [sops-nix](https://github.com/Mic92/sops-nix) work out of the box. +* Honours `HTTPS_PROXY` / `HTTP_PROXY` / `ALL_PROXY` / `NO_PROXY`, with explicit HTTP and SOCKS5 proxy options. * Configure the prefix and postfix to automatically add to your text messages. * Overrides for bot token and chat ID via command-line options. * Available as a Nix package and flake for easy installation on NixOS and other systems. @@ -77,14 +79,77 @@ The flake also exposes a `home-manager` module for more granular, user-level con ```nix programs.telegram-cli = { enable = true; - botToken = ""; # Optional: Default bot token - defaultChatId = ""; # Optional: Default chat ID + botTokenFile = config.age.secrets.telegram-bot-token.path; # Recommended: runtime path of a secret (agenix / sops-nix), see below + defaultChatId = ""; # Optional: Default chat ID (or defaultChatIdFile) + # botToken = ""; # Works, but ends up in the world-readable Nix store + # proxy = "socks5h://127.0.0.1:1080"; # Optional: see "Proxy" below (or proxyFile) prefix = "NixOS Alert:\n"; # Optional: Prefix for text messages postfix = "\n-- Sent from my NixOS machine"; # Optional: Postfix for text messages defaultFormat = "MarkdownV2"; # Optional: "Html", "MarkdownV2", or "No". Defaults to "MarkdownV2". }; ``` +**Secrets with agenix / sops-nix:** + +Point `botTokenFile` (and, if you like, `defaultChatIdFile` / `proxyFile`) at the *runtime path* of the decrypted secret. Use a string, not a Nix path literal, so nothing is copied into the store. The file is read every time `telegram-cli` runs. + +```nix +# agenix (home-manager) +age.secrets.telegram-bot-token.file = ./secrets/telegram-bot-token.age; +programs.telegram-cli.botTokenFile = config.age.secrets.telegram-bot-token.path; + +# sops-nix (home-manager) +sops.secrets.telegram-bot-token = { }; +programs.telegram-cli.botTokenFile = config.sops.secrets.telegram-bot-token.path; +``` + +## Configuration + +Every setting can come from three places. When several are present, the first one wins: + +1. **CLI arguments** +2. **Environment variables** +3. **Config file** (`~/.config/telegram-cli/config.json`, or the file given by `--config` / `TELEGRAM_CLI_CONFIG`) + +The config file is **optional**: `telegram-cli` works with nothing but environment variables or flags (handy for containers and CI), and never creates files or directories on its own. + +| Setting | CLI | Environment | Config file key | +| :----------- | :----------------- | :----------------------------------- | :------------------ | +| Bot token | `--bot-token` | `TELEGRAM_BOT_TOKEN` | `BotToken` | +| Token file | `--bot-token-file` | `TELEGRAM_BOT_TOKEN_FILE` | `BotTokenFile` | +| Chat ID | `--chat-id` | `TELEGRAM_CHAT_ID` | `DefaultChatId` | +| Chat ID file | `--chat-id-file` | `TELEGRAM_CHAT_ID_FILE` | `DefaultChatIdFile` | +| Proxy | `--proxy` | `TELEGRAM_PROXY` (or `HTTPS_PROXY`) | `Proxy` | +| Proxy file | | | `ProxyFile` | +| Config path | `--config` | `TELEGRAM_CLI_CONFIG` | | + +Within one source, a direct value beats a `...File` value. Files are trimmed, so a trailing newline is fine. Empty values are ignored. + +Other config keys: `Prefix`, `Postfix` and `DefaultFormat` (`Html`, `MarkdownV2` or `No`). + +Example `config.json`: + +```json +{ + "BotTokenFile": "/run/agenix/telegram-bot-token", + "DefaultChatId": "-1001234567890", + "Prefix": "🚀 ", + "DefaultFormat": "MarkdownV2" +} +``` + +> **Tip:** command-line arguments are visible to other users via `ps`. Prefer `--bot-token-file`, the environment, or the config file for the bot token. + +### Proxy + +`telegram-cli` respects the standard proxy environment variables (`HTTPS_PROXY`, `HTTP_PROXY`, `ALL_PROXY`, `NO_PROXY`). Telegram is reached over HTTPS, so `HTTPS_PROXY` is the relevant one: + +```bash +HTTPS_PROXY=http://proxy.example:3128 telegram-cli "Hello" +``` + +To set it explicitly, use `--proxy`, `TELEGRAM_PROXY` or the `Proxy` / `ProxyFile` config keys. An explicit proxy takes priority over the standard variables. `http://`, `https://`, `socks5://` and `socks5h://` (proxy-side DNS) URLs are supported, and credentials can be embedded (`http://user:pass@host:3128`; keep those in `ProxyFile`). Use `--no-proxy` to ignore every proxy setting and connect directly. + ## Usage ``` @@ -100,7 +165,12 @@ telegram-cli [OPTIONS] [MESSAGE] | Flag | Long Flag | Description | | :--- | :------------------------ | :----------------------------------------------------------------------------- | | | `--chat-id ` | Override the default chat ID from the config file. | +| | `--chat-id-file ` | Read the chat ID from a file. | | | `--bot-token ` | Override the default bot token from the config file. | +| | `--bot-token-file ` | Read the bot token from a file (agenix / sops-nix). | +| | `--proxy ` | Use this HTTP(S) / SOCKS5 proxy. | +| | `--no-proxy` | Ignore all proxy settings. | +| | `--config ` | Use a different config file. | | | `--format ` | Select message format. Possible values: `html`, `md`, `no`. | | `-c` | `--code ` | Wrap the message in a code block for a given language (e.g., `rust`). | | `-f` | `--file ` | Send file(s) as documents. Can be used multiple times. Supports glob patterns. | @@ -155,6 +225,16 @@ telegram-cli -c rust 'fn main() { println!("Hello from Rust!"); }' telegram-cli --bot-token "YOUR_OTHER_BOT_TOKEN" --chat-id "ANOTHER_CHAT_ID" "Message to a different chat." ``` +**Using only environment variables (no config file):** + +```bash +TELEGRAM_BOT_TOKEN="..." TELEGRAM_CHAT_ID="..." telegram-cli "Deploy finished" +``` + +## Building + +Requires Rust 1.88 or newer: `cargo build --release`. + ## License This project is licensed under the **Mozilla Public License 2.0 (MPL-2.0)**. diff --git a/default.nix b/default.nix index 24f0d00..8ddeb92 100644 --- a/default.nix +++ b/default.nix @@ -1,12 +1,16 @@ { pkgs ? import { } }: let manifest = (pkgs.lib.importTOML ./Cargo.toml).package; in -pkgs.rustPlatform.buildRustPackage rec { +pkgs.rustPlatform.buildRustPackage { pname = manifest.name; version = manifest.version; - nativeBuildInputs = with pkgs; [ pkg-config openssl ]; - PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig"; - cargoLock.lockFile = ./Cargo.lock; src = pkgs.lib.cleanSource ./.; -} \ No newline at end of file + + meta = { + inherit (manifest) description; + homepage = manifest.repository; + license = pkgs.lib.licenses.mpl20; + mainProgram = "telegram-cli"; + }; +} diff --git a/flake.lock b/flake.lock index a535be7..7ddd94b 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "lastModified": 1791914995, + "narHash": "sha256-Zf/xfQEAasnwnmEfHqW/4hjDE/s44yn2hD4RuVIZRjA=", "owner": "numtide", "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "rev": "b7a79d3fea1e214f6d668d521cecec0c01e26544", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1755615617, - "narHash": "sha256-HMwfAJBdrr8wXAkbGhtcby1zGFvs+StOp19xNsbqdOg=", + "lastModified": 1792094642, + "narHash": "sha256-zp8ToDMnqZcu4o4IH9pdwXFXGp7yLm/M0dEnNWnyd7I=", "owner": "nixos", "repo": "nixpkgs", - "rev": "20075955deac2583bb12f07151c2df830ef346b4", + "rev": "fe13242281a67641c0102cdb1087f0ebe586c7df", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 6ca759d..4753a24 100644 --- a/flake.nix +++ b/flake.nix @@ -12,9 +12,10 @@ in { packages.telegram-cli = telegramCli; + packages.default = telegramCli; defaultPackage = telegramCli; }) // { homeManagerModules.telegram-cli = import ./home-manager/module.nix; homeManagerModules.default = self.homeManagerModules.telegram-cli; }; -} \ No newline at end of file +} \ No newline at end of file diff --git a/home-manager/module.nix b/home-manager/module.nix index ee23a24..4dd3d9e 100644 --- a/home-manager/module.nix +++ b/home-manager/module.nix @@ -4,13 +4,18 @@ let cfg = config.programs.telegram-cli; telegramCli = pkgs.callPackage ../default.nix { }; - configFile = pkgs.writeText "telegram-cli-config.json" (builtins.toJSON { + # Unset (null) options are left out so that the CLI falls back to the environment. + configFile = pkgs.writeText "telegram-cli-config.json" (builtins.toJSON (lib.filterAttrs (_: v: v != null) { Prefix = cfg.prefix; Postfix = cfg.postfix; DefaultChatId = cfg.defaultChatId; + DefaultChatIdFile = cfg.defaultChatIdFile; BotToken = cfg.botToken; + BotTokenFile = cfg.botTokenFile; + Proxy = cfg.proxy; + ProxyFile = cfg.proxyFile; DefaultFormat = cfg.defaultFormat; - }); + })); in { options.programs.telegram-cli = { @@ -40,10 +45,54 @@ in description = "The default chat ID for telegram-cli"; }; + defaultChatIdFile = mkOption { + type = types.nullOr types.str; + default = null; + example = "/run/agenix/telegram-chat-id"; + description = '' + Runtime path of a file containing the default chat ID, e.g. + `config.sops.secrets.telegram-chat-id.path` or `config.age.secrets.telegram-chat-id.path`. + Use a string, not a Nix path, so the file is never copied to the store. + ''; + }; + botToken = mkOption { type = types.nullOr types.str; default = null; - description = "The Telegram bot token to send messages with"; + description = '' + The Telegram bot token to send messages with. + Warning: this ends up in the world-readable Nix store. Prefer `botTokenFile`. + ''; + }; + + botTokenFile = mkOption { + type = types.nullOr types.str; + default = null; + example = "/run/agenix/telegram-bot-token"; + description = '' + Runtime path of a file containing the bot token, e.g. + `config.sops.secrets.telegram-bot-token.path` (sops-nix) or + `config.age.secrets.telegram-bot-token.path` (agenix). + Use a string, not a Nix path, so the file is never copied to the store. + ''; + }; + + proxy = mkOption { + type = types.nullOr types.str; + default = null; + example = "socks5h://127.0.0.1:1080"; + description = '' + Proxy URL (http, https, socks5 or socks5h). When null, telegram-cli honours the + HTTPS_PROXY / HTTP_PROXY / ALL_PROXY / NO_PROXY environment variables. + Do not put credentials here (Nix store); use `proxyFile`. + ''; + }; + + proxyFile = mkOption { + type = types.nullOr types.str; + default = null; + example = "/run/agenix/telegram-proxy"; + description = "Runtime path of a file containing the proxy URL (may include credentials)."; }; defaultFormat = mkOption { @@ -54,6 +103,21 @@ in }; config = mkIf cfg.enable { + assertions = [ + { + assertion = cfg.botToken == null || cfg.botTokenFile == null; + message = "programs.telegram-cli: set only one of botToken and botTokenFile."; + } + { + assertion = cfg.defaultChatId == null || cfg.defaultChatIdFile == null; + message = "programs.telegram-cli: set only one of defaultChatId and defaultChatIdFile."; + } + { + assertion = cfg.proxy == null || cfg.proxyFile == null; + message = "programs.telegram-cli: set only one of proxy and proxyFile."; + } + ]; + home.packages = [ cfg.package ]; home.file.".config/telegram-cli/config.json".source = configFile; }; diff --git a/src/cli.rs b/src/cli.rs index 437dc0a..385fcbc 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -11,13 +11,40 @@ pub struct Cli { pub message: Option, /// Override the default chat ID from the config file. - #[arg(long)] + #[arg(long, env = "TELEGRAM_CHAT_ID")] pub chat_id: Option, + /// Read the chat ID from a file (e.g. an agenix/sops-nix secret). + #[arg(long, env = "TELEGRAM_CHAT_ID_FILE", value_name = "PATH")] + pub chat_id_file: Option, + /// Override the default bot token from the config file. - #[arg(long)] + /// Prefer --bot-token-file or the environment: command-line arguments are visible to other users. + #[arg(long, env = "TELEGRAM_BOT_TOKEN", hide_env_values = true)] pub bot_token: Option, + /// Read the bot token from a file (e.g. an agenix/sops-nix secret). + #[arg(long, env = "TELEGRAM_BOT_TOKEN_FILE", value_name = "PATH")] + pub bot_token_file: Option, + + /// Route requests through this proxy (http://, https://, socks5:// or socks5h://). + /// Overrides HTTPS_PROXY / HTTP_PROXY / ALL_PROXY and the config file. + #[arg( + long, + env = "TELEGRAM_PROXY", + hide_env_values = true, + value_name = "URL" + )] + pub proxy: Option, + + /// Ignore every proxy setting (CLI, config file and environment) and connect directly. + #[arg(long, conflicts_with = "proxy")] + pub no_proxy: bool, + + /// Use this config file instead of ~/.config/telegram-cli/config.json. + #[arg(long, env = "TELEGRAM_CLI_CONFIG", value_name = "PATH")] + pub config: Option, + /// Select message format. #[arg(long, value_enum)] pub format: Option, diff --git a/src/config.rs b/src/config.rs index bf0858b..f7c974f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,14 +1,22 @@ use anyhow::{Context, Result}; use clap::ValueEnum; use serde::Deserialize; -use std::path::PathBuf; -use std::process; +use std::path::{Path, PathBuf}; -#[derive(Debug, Deserialize)] +/// Contents of `config.json`. Every field is optional, and so is the file itself. +#[derive(Debug, Default, Deserialize)] #[serde(rename_all = "PascalCase")] pub struct Config { pub bot_token: Option, + /// Path to a file containing the bot token (agenix, sops-nix, systemd credentials, ...). + pub bot_token_file: Option, pub default_chat_id: Option, + /// Path to a file containing the default chat ID. + pub default_chat_id_file: Option, + /// Proxy URL. Overrides HTTPS_PROXY / HTTP_PROXY / ALL_PROXY. + pub proxy: Option, + /// Path to a file containing the proxy URL (useful when it embeds credentials). + pub proxy_file: Option, pub prefix: Option, pub postfix: Option, pub default_format: Option, @@ -33,55 +41,105 @@ impl std::fmt::Display for Format { } } -pub fn load_config() -> Result { - let config_path = get_config_path().context("Could not determine config path")?; - if !config_path.exists() { - anyhow::bail!( - "Config file not found. Please create it at: {:?}\n\ - Example:\n\ - {{\n\ - \t\"BotToken\": \"123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11\",\n\ - \t\"DefaultChatId\": \"-1001234567890\",\n\ - \t\"Prefix\": \"🚀 \",\n\ - \t\"DefaultFormat\": \"MarkdownV2\"\n\ - }}", - config_path - ); - } - let config_str = std::fs::read_to_string(&config_path) - .with_context(|| format!("Failed to read config file at {:?}", config_path))?; - let config: Config = serde_json::from_str(&config_str) - .with_context(|| format!("Failed to parse config file at {:?}", config_path))?; +/// Loads the config file. A missing *default* file is not an error: the tool works +/// with CLI arguments and environment variables alone. An explicitly requested +/// file (`--config` / `TELEGRAM_CLI_CONFIG`) must exist. +pub fn load_config(explicit: Option<&Path>) -> Result { + let (path, required) = match explicit { + Some(p) => (p.to_path_buf(), true), + None => match default_config_path() { + Some(p) => (p, false), + None => return Ok(Config::default()), + }, + }; - // Check for empty BotToken or DefaultChatId - if let Some(bot_token) = &config.bot_token { - if bot_token.is_empty() { - eprintln!( - "Warning: BotToken is empty in config file at {:?}. \ - Please provide a valid default bot token or remove the empty BotToken field if intentional.", - config_path - ); - process::exit(1); + let config_str = match std::fs::read_to_string(&path) { + Ok(s) => s, + Err(e) if e.kind() == std::io::ErrorKind::NotFound && !required => { + return Ok(Config::default()); } - } - if let Some(default_chat_id) = &config.default_chat_id { - if default_chat_id.is_empty() { - eprintln!( - "Warning: DefaultChatId is empty in config file at {:?}. \ - Please provide a valid default chat ID or remove the empty DefaultChatId field if intentional.", - config_path - ); - process::exit(1); + Err(e) => { + return Err(e).with_context(|| format!("Failed to read config file at {path:?}")); } - } + }; + serde_json::from_str(&config_str) + .with_context(|| format!("Failed to parse config file at {path:?}")) +} - Ok(config) +fn default_config_path() -> Option { + Some( + dirs::home_dir()? + .join(".config") + .join("telegram-cli") + .join("config.json"), + ) +} + +/// One place a setting can come from, in precedence order. +pub enum Source<'a> { + Value(Option<&'a str>), + File(Option<&'a Path>), +} + +/// Returns the first non-empty source. Files are read at call time and trimmed, so +/// secrets managed by agenix/sops-nix never need to be stored in the config itself. +pub fn resolve(name: &str, sources: &[Source]) -> Result> { + for source in sources { + match source { + Source::Value(Some(v)) if !v.trim().is_empty() => { + return Ok(Some(v.trim().to_string())); + } + Source::File(Some(path)) if !path.as_os_str().is_empty() => { + let content = std::fs::read_to_string(path) + .with_context(|| format!("Failed to read the {name} file at {path:?}"))?; + let content = content.trim(); + if content.is_empty() { + anyhow::bail!("The {name} file at {path:?} is empty"); + } + return Ok(Some(content.to_string())); + } + _ => {} + } + } + Ok(None) } -fn get_config_path() -> Result { - let home_dir = dirs::home_dir().context("Failed to get home directory")?; - let config_dir = home_dir.join(".config").join("telegram-cli"); - std::fs::create_dir_all(&config_dir) - .with_context(|| format!("Failed to create config directory at {:?}", config_dir))?; - Ok(config_dir.join("config.json")) +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn legacy_config_still_parses() { + let c: Config = serde_json::from_str( + r#"{"BotToken":"1:A","DefaultChatId":"-100","Prefix":"x","Postfix":null,"DefaultFormat":"MarkdownV2"}"#, + ) + .unwrap(); + assert_eq!(c.bot_token.as_deref(), Some("1:A")); + assert_eq!(c.default_format, Some(Format::MarkdownV2)); + assert!(c.bot_token_file.is_none() && c.proxy.is_none()); + } + + #[test] + fn empty_values_fall_through_to_next_source() { + let got = resolve("t", &[Source::Value(Some("")), Source::Value(Some(" b "))]).unwrap(); + assert_eq!(got.as_deref(), Some("b")); + } + + #[test] + fn file_secrets_are_trimmed_and_missing_files_error() { + let path = std::env::temp_dir().join("telegram-cli-test-secret"); + std::fs::write(&path, "123:ABC\n").unwrap(); + let got = resolve("t", &[Source::File(Some(&path))]).unwrap(); + assert_eq!(got.as_deref(), Some("123:ABC")); + std::fs::remove_file(&path).unwrap(); + assert!(resolve("t", &[Source::File(Some(&path))]).is_err()); + } + + #[test] + fn nothing_configured_resolves_to_none() { + assert_eq!( + resolve("t", &[Source::Value(None), Source::File(None)]).unwrap(), + None + ); + } } diff --git a/src/main.rs b/src/main.rs index 33dd327..a2e9597 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,42 +5,88 @@ mod telegram; use anyhow::{Context, Result}; use clap::Parser; use cli::Cli; -use config::load_config; +use config::{Format, Source, load_config, resolve}; use glob::glob; use std::io::{self, Read}; -use std::process; -use telegram::TelegramClient; +use std::path::{Path, PathBuf}; +use telegram::{Media, ProxyMode, TelegramClient}; -#[tokio::main] +/// Expands a user-supplied path: an existing path is used as-is (so names containing +/// glob characters like `[` still work), anything else is treated as a glob pattern. +fn expand(path: &Path) -> Result> { + if path.exists() { + return Ok(vec![path.to_path_buf()]); + } + let pattern = path.to_string_lossy(); + let mut found = Vec::new(); + for entry in + glob(&pattern).with_context(|| format!("Failed to read glob pattern: {pattern}"))? + { + match entry { + Ok(p) => found.push(p), + Err(e) => eprintln!("Warning: Skipping invalid path from glob: {e}"), + } + } + if found.is_empty() { + eprintln!("Warning: No files match {pattern}"); + } + Ok(found) +} + +#[tokio::main(flavor = "current_thread")] async fn main() -> Result<()> { + rustls::crypto::ring::default_provider() + .install_default() + .map_err(|_| anyhow::anyhow!("Failed to install the TLS crypto provider"))?; + let cli = Cli::parse(); - let config = load_config()?; + let config = load_config(cli.config.as_deref())?; - // Check for valid chat_id, exit if both CLI and config default are missing or empty - let bot_token = match (cli.bot_token.as_deref(), &config.bot_token) { - (Some(bot_token), _) if !bot_token.is_empty() => bot_token, - (_, Some(default_id)) if !default_id.is_empty() => default_id, - _ => { - eprintln!( - "Error: No valid bot token provided. Please specify a bot token via CLI arguments or set a non-empty default in the config file." - ); - process::exit(1); - } - }; + // Precedence: CLI argument > environment variable (both handled by clap) > config file. + let bot_token = resolve( + "bot token", + &[ + Source::Value(cli.bot_token.as_deref()), + Source::File(cli.bot_token_file.as_deref()), + Source::Value(config.bot_token.as_deref()), + Source::File(config.bot_token_file.as_deref()), + ], + )? + .context( + "No bot token provided. Use --bot-token, --bot-token-file, TELEGRAM_BOT_TOKEN, \ + TELEGRAM_BOT_TOKEN_FILE, or set BotToken/BotTokenFile in the config file.", + )?; - let client = TelegramClient::new(bot_token.to_string()); + let chat_id = resolve( + "chat ID", + &[ + Source::Value(cli.chat_id.as_deref()), + Source::File(cli.chat_id_file.as_deref()), + Source::Value(config.default_chat_id.as_deref()), + Source::File(config.default_chat_id_file.as_deref()), + ], + )? + .context( + "No chat ID provided. Use --chat-id, --chat-id-file, TELEGRAM_CHAT_ID, \ + TELEGRAM_CHAT_ID_FILE, or set DefaultChatId/DefaultChatIdFile in the config file.", + )?; - // Check for valid chat_id, exit if both CLI and config default are missing or empty - let chat_id = match (cli.chat_id.as_deref(), &config.default_chat_id) { - (Some(chat_id), _) if !chat_id.is_empty() => chat_id, - (_, Some(default_id)) if !default_id.is_empty() => default_id, - _ => { - eprintln!( - "Error: No valid chat ID provided. Please specify a chat ID via CLI arguments or set a non-empty default in the config file." - ); - process::exit(1); - } + let proxy = if cli.no_proxy { + ProxyMode::Disabled + } else { + let explicit = resolve( + "proxy", + &[ + Source::Value(cli.proxy.as_deref()), + Source::Value(config.proxy.as_deref()), + Source::File(config.proxy_file.as_deref()), + ], + )?; + explicit.map_or(ProxyMode::System, ProxyMode::Url) }; + + let client = TelegramClient::new(bot_token, proxy)?; + let chat_id = chat_id.as_str(); let format = cli.format.or(config.default_format); let message_from_cli = if cli.stdin { @@ -48,50 +94,42 @@ async fn main() -> Result<()> { io::stdin() .read_to_string(&mut buffer) .context("Failed to read from stdin")?; - if buffer.trim().is_empty() { - None - } else { - Some(buffer) - } + (!buffer.trim().is_empty()).then_some(buffer) } else { - // Collect all positional arguments into one message. - // This allows `telegram-cli hello world` without quotes. cli.message }; // Process the message content if it exists - let (final_text, final_parse_mode) = if let Some(mut content) = message_from_cli { - let mut processed_content = String::new(); - if let Some(prefix) = &config.prefix { - processed_content.push_str(prefix); - } - processed_content.push_str(&content); - if let Some(postfix) = &config.postfix { - processed_content.push_str(postfix); - } - content = processed_content; + let (final_text, final_parse_mode) = if let Some(content) = message_from_cli { + let content = format!( + "{}{}{}", + config.prefix.as_deref().unwrap_or_default(), + content, + config.postfix.as_deref().unwrap_or_default() + ); if let Some(lang) = cli.code { - let (formatted_content, parse_mode) = if format == Some(config::Format::Html) { + if format == Some(Format::Html) { let escaped_content = content .replace('&', "&") .replace('<', "<") .replace('>', ">"); ( - format!( - "
{}
", - lang, escaped_content - ), + Some(format!( + "
{escaped_content}
" + )), Some("HTML"), ) } else { - (format!("```{}\n{}\n```", lang, content), Some("MarkdownV2")) - }; - (Some(formatted_content), parse_mode) + ( + Some(format!("```{lang}\n{content}\n```")), + Some("MarkdownV2"), + ) + } } else { let parse_mode = match format { - Some(config::Format::Html) => Some("HTML"), - Some(config::Format::MarkdownV2) => Some("MarkdownV2"), + Some(Format::Html) => Some("HTML"), + Some(Format::MarkdownV2) => Some("MarkdownV2"), _ => None, }; (Some(content), parse_mode) @@ -103,39 +141,29 @@ async fn main() -> Result<()> { let caption = final_text.as_deref(); let mut files_sent_count = 0; - macro_rules! send_files { - ($paths:expr, $sender:ident) => { - for path in $paths { - let path_str = &path.to_string_lossy(); - for entry in glob(path_str) - .with_context(|| format!("Failed to read glob pattern: {}", path_str))? - { - match entry { - Ok(p) => { - client - .$sender(chat_id, &p, caption, final_parse_mode) - .await?; - files_sent_count += 1; - } - Err(e) => eprintln!("Warning: Skipping invalid path from glob: {}", e), - } - } + for (media, paths) in [ + (Media::Document, &cli.file), + (Media::Photo, &cli.photo), + (Media::Video, &cli.video), + (Media::Audio, &cli.audio), + ] { + for path in paths { + for file in expand(path)? { + client + .send_media(media, chat_id, &file, caption, final_parse_mode) + .await?; + files_sent_count += 1; } - }; + } } - send_files!(&cli.file, send_document); - send_files!(&cli.photo, send_photo); - send_files!(&cli.video, send_video); - send_files!(&cli.audio, send_audio); - // If NO files were sent and we have text, send it as a standard message. - if files_sent_count == 0 { - if let Some(text_to_send) = final_text { - client - .send_message(chat_id, &text_to_send, final_parse_mode) - .await?; - } + if files_sent_count == 0 + && let Some(text_to_send) = final_text + { + client + .send_message(chat_id, &text_to_send, final_parse_mode) + .await?; } Ok(()) diff --git a/src/telegram.rs b/src/telegram.rs index 6c64aa6..b465833 100644 --- a/src/telegram.rs +++ b/src/telegram.rs @@ -1,6 +1,46 @@ use anyhow::{Context, Result}; -use reqwest::multipart; +use reqwest::{Proxy, Response, multipart}; +use serde::Deserialize; use std::path::Path; +use std::time::Duration; + +/// How outgoing requests reach the network. +pub enum ProxyMode { + /// Honour HTTPS_PROXY / HTTP_PROXY / ALL_PROXY / NO_PROXY. + System, + /// Always use this proxy. + Url(String), + /// Never use a proxy. + Disabled, +} + +#[derive(Clone, Copy)] +pub enum Media { + Document, + Photo, + Video, + Audio, +} + +impl Media { + fn method(self) -> &'static str { + match self { + Media::Document => "sendDocument", + Media::Photo => "sendPhoto", + Media::Video => "sendVideo", + Media::Audio => "sendAudio", + } + } + + fn field(self) -> &'static str { + match self { + Media::Document => "document", + Media::Photo => "photo", + Media::Video => "video", + Media::Audio => "audio", + } + } +} pub struct TelegramClient { client: reqwest::Client, @@ -8,11 +48,26 @@ pub struct TelegramClient { } impl TelegramClient { - pub fn new(bot_token: String) -> Self { - Self { - client: reqwest::Client::new(), + pub fn new(bot_token: String, proxy: ProxyMode) -> Result { + let builder = reqwest::Client::builder().connect_timeout(Duration::from_secs(15)); + let builder = match proxy { + ProxyMode::System => builder, + ProxyMode::Disabled => builder.no_proxy(), + // The URL may carry credentials, so it is deliberately left out of the error. + ProxyMode::Url(url) => builder.proxy( + Proxy::all(url) + .map_err(|e| e.without_url()) + .context("Invalid proxy URL")?, + ), + }; + Ok(Self { + client: builder.build().context("Failed to build HTTP client")?, bot_token, - } + }) + } + + fn url(&self, method: &str) -> String { + format!("https://api.telegram.org/bot{}/{}", self.bot_token, method) } pub async fn send_message( @@ -21,121 +76,38 @@ impl TelegramClient { text: &str, parse_mode: Option<&str>, ) -> Result<()> { - let url = format!("https://api.telegram.org/bot{}/sendMessage", self.bot_token); - - let mut params = std::collections::HashMap::new(); - params.insert("chat_id", chat_id); - params.insert("text", text); - + let mut params = vec![("chat_id", chat_id), ("text", text)]; if let Some(pm) = parse_mode { - params.insert("parse_mode", pm); + params.push(("parse_mode", pm)); } - self.client - .post(&url) + let response = self + .client + .post(self.url("sendMessage")) .form(¶ms) .send() - .await? - .error_for_status() - .context("Failed to send message")?; - Ok(()) - } - - pub async fn send_document( - &self, - chat_id: &str, - file_path: &Path, - caption: Option<&str>, - parse_mode: Option<&str>, - ) -> Result<()> { - self.send_file( - "sendDocument", - "document", - chat_id, - file_path, - caption, - parse_mode, - ) - .await - } - - pub async fn send_photo( - &self, - chat_id: &str, - file_path: &Path, - caption: Option<&str>, - parse_mode: Option<&str>, - ) -> Result<()> { - self.send_file( - "sendPhoto", - "photo", - chat_id, - file_path, - caption, - parse_mode, - ) - .await - } - - pub async fn send_video( - &self, - chat_id: &str, - file_path: &Path, - caption: Option<&str>, - parse_mode: Option<&str>, - ) -> Result<()> { - self.send_file( - "sendVideo", - "video", - chat_id, - file_path, - caption, - parse_mode, - ) - .await - } - - pub async fn send_audio( - &self, - chat_id: &str, - file_path: &Path, - caption: Option<&str>, - parse_mode: Option<&str>, - ) -> Result<()> { - self.send_file( - "sendAudio", - "audio", - chat_id, - file_path, - caption, - parse_mode, - ) - .await + .await + .map_err(|e| e.without_url()) + .context("Failed to reach Telegram")?; + check(response, "Failed to send message").await } - async fn send_file( + pub async fn send_media( &self, - method: &str, - file_type: &str, + media: Media, chat_id: &str, file_path: &Path, caption: Option<&str>, parse_mode: Option<&str>, ) -> Result<()> { - let url = format!("https://api.telegram.org/bot{}/{}", self.bot_token, method); - let file_name = file_path - .file_name() - .context("Invalid file path")? - .to_string_lossy() - .to_string(); - let file_bytes = tokio::fs::read(file_path) + // Streamed from disk, so large files are not held in memory. + let part = multipart::Part::file(file_path) .await - .with_context(|| format!("Failed to read file: {:?}", file_path))?; - let part = multipart::Part::bytes(file_bytes).file_name(file_name); + .with_context(|| format!("Failed to read file: {file_path:?}"))?; let mut form = multipart::Form::new() .text("chat_id", chat_id.to_string()) - .part(file_type.to_string(), part); + .part(media.field(), part); if let Some(cap) = caption { form = form.text("caption", cap.to_string()); @@ -144,13 +116,42 @@ impl TelegramClient { } } - self.client - .post(&url) + let response = self + .client + .post(self.url(media.method())) .multipart(form) .send() - .await? - .error_for_status() - .with_context(|| format!("Failed to send {} from path {:?}", file_type, file_path))?; - Ok(()) + .await + .map_err(|e| e.without_url()) + .context("Failed to reach Telegram")?; + check( + response, + &format!("Failed to send {} {file_path:?}", media.field()), + ) + .await + } +} + +#[derive(Deserialize)] +struct ApiError { + description: Option, +} + +/// Turns a non-2xx response into an error carrying Telegram's own explanation. +/// (`error_for_status` would put the request URL, and thus the bot token, in the message.) +async fn check(response: Response, what: &str) -> Result<()> { + let status = response.status(); + if status.is_success() { + return Ok(()); + } + let description = response + .text() + .await + .ok() + .and_then(|body| serde_json::from_str::(&body).ok()) + .and_then(|e| e.description); + match description { + Some(d) => anyhow::bail!("{what}: {status}: {d}"), + None => anyhow::bail!("{what}: {status}"), } }