diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index ca7d5f3..78333ea 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -4,14 +4,14 @@ on: push: tags: - "v*" - workflow_run: - workflows: ["Release"] - types: [completed] - workflow_dispatch: permissions: contents: read +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: false + env: CARGO_TERM_COLOR: always @@ -31,13 +31,33 @@ jobs: - name: Install dev dependencies run: uv sync --group dev --no-install-project - name: Ruff lint - run: uv run --no-project --with ruff==0.11.4 ruff check . + run: uv run --no-project ruff check . - name: Ruff format check - run: uv run --no-project --with ruff==0.11.4 ruff format --check . + run: uv run --no-project ruff format --check . + + tests: + name: Python Tests + needs: quality + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + - uses: astral-sh/setup-uv@v5 + - uses: dtolnay/rust-toolchain@stable + - name: Install dev dependencies + run: uv sync --group dev --no-install-project + - name: Build native extension + run: uv run maturin develop + - name: Run pytest + run: uv run pytest -q linux: name: Linux manylinux wheels - needs: quality + needs: [quality, tests] runs-on: ${{ matrix.platform.runner }} strategy: fail-fast: false @@ -82,7 +102,7 @@ jobs: android: name: Android wheels - needs: quality + needs: [quality, tests] runs-on: ubuntu-22.04 strategy: fail-fast: false @@ -118,7 +138,7 @@ jobs: windows: name: Windows wheels - needs: quality + needs: [quality, tests] runs-on: ${{ matrix.platform.runner }} strategy: fail-fast: false @@ -153,7 +173,7 @@ jobs: macos: name: macOS wheels - needs: quality + needs: [quality, tests] # Both targets use macos-latest (Apple Silicon) — x86_64 is cross-compiled. # macos-13 runners have very long queues (12h+); macos-latest is faster. runs-on: macos-latest @@ -185,7 +205,7 @@ jobs: sdist: name: Source distribution - needs: quality + needs: [quality, tests] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -205,11 +225,7 @@ jobs: release: name: Publish to PyPI - if: > - ${{ - startsWith(github.ref, 'refs/tags/v') || - (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success') - }} + if: startsWith(github.ref, 'refs/tags/v') needs: [linux, android, windows, macos, sdist] runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index e8c2dec..1f37eb6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,14 +2,14 @@ name: Documentation on: push: - branches: [master, main] + branches: [master, main, dev] paths: - "docs/**" - "mkdocs.yml" - "python/tryx/**" pull_request: - branches: [master, main] + branches: [master, main, dev] paths: - "docs/**" - "mkdocs.yml" @@ -21,10 +21,6 @@ on: description: "Version to deploy (e.g. 0.7, 0.8)" required: false type: string - delete_version: - description: "Version to delete" - required: false - type: string permissions: contents: write @@ -51,14 +47,14 @@ jobs: - uses: astral-sh/setup-uv@v5 + - uses: dtolnay/rust-toolchain@stable + - name: Install dependencies - run: | - uv sync --group docs --no-install-project - uv pip install pyright mypy --system + run: uv sync --group dev --group docs --no-install-project - name: Validate .pyi stubs with pyright run: | - pyright python/tryx/client.pyi \ + uv run --no-project pyright python/tryx/client.pyi \ python/tryx/events.pyi \ python/tryx/types.pyi \ python/tryx/wacore.pyi \ @@ -71,7 +67,7 @@ jobs: - name: Validate .pyi stubs with mypy run: | - mypy python/tryx/client.pyi \ + uv run --no-project mypy python/tryx/client.pyi \ python/tryx/events.pyi \ python/tryx/types.pyi \ python/tryx/wacore.pyi \ @@ -84,16 +80,14 @@ jobs: --ignore-missing-imports - name: Build docs (validation only) - run: uv run --no-project mkdocs build --strict - env: - PYTHONPATH: python + run: uv run --no-project zensical build -f mkdocs.yml -s - # ── Job 2: Deploy (runs on push to master or tag) ────────────────────────── + # ── Job 2: Deploy (runs on push or manual trigger) ───────────────────────── deploy: name: Deploy Documentation needs: validate runs-on: ubuntu-latest - if: github.event_name == 'push' + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' steps: - uses: actions/checkout@v4 with: @@ -107,9 +101,7 @@ jobs: - uses: astral-sh/setup-uv@v5 - name: Install dependencies - run: | - uv sync --group docs --no-install-project - uv pip install mike + run: uv sync --group dev --group docs --no-install-project - name: Configure git run: | @@ -124,83 +116,31 @@ jobs: echo "Deploying docs for version: $VERSION" uv run --no-project mike deploy --push --update-aliases "$VERSION" latest uv run --no-project mike set-default --push latest - env: - PYTHONPATH: python - # Auto-deploy from master → "latest" (without version number) - - name: Deploy latest docs (from master) - if: "!startsWith(github.ref, 'refs/tags/')" + # Auto-deploy from master → "dev" version + - name: Deploy dev docs (from master) + if: "!startsWith(github.ref, 'refs/tags/') && github.event_name == 'push'" run: | - echo "Deploying docs as latest" + echo "Deploying docs as dev" uv run --no-project mike deploy --push --update-aliases dev latest uv run --no-project mike set-default --push latest - env: - PYTHONPATH: python - - - name: List deployed versions - if: success() - run: uv run --no-project mike list - # ── Job 3: Manual deploy (workflow_dispatch) ──────────────────────────────── - manual_deploy: - name: Manual Deploy - runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - submodules: recursive - - - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - - uses: astral-sh/setup-uv@v5 - - - name: Install dependencies - run: | - uv sync --group docs --no-install-project - uv pip install mike - - - name: Configure git - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - - # Deploy custom version - - name: Deploy custom version - if: inputs.version != '' + # Manual deploy with custom version + - name: Deploy custom version (manual) + if: github.event_name == 'workflow_dispatch' && inputs.version != '' run: | echo "Deploying docs for version: ${{ inputs.version }}" uv run --no-project mike deploy --push --update-aliases "${{ inputs.version }}" latest uv run --no-project mike set-default --push latest - env: - PYTHONPATH: python - # Delete a version - - name: Delete docs version - if: inputs.delete_version != '' + # Manual deploy without version → deploy as dev + - name: Deploy dev docs (manual) + if: github.event_name == 'workflow_dispatch' && inputs.version == '' run: | - echo "Deleting docs version: ${{ inputs.delete_version }}" - uv run --no-project mike delete --push "${{ inputs.delete_version }}" - env: - PYTHONPATH: python + echo "Deploying docs as dev" + uv run --no-project mike deploy --push --update-aliases dev latest + uv run --no-project mike set-default --push latest - name: List deployed versions if: success() run: uv run --no-project mike list - - # ── Job 4: Deploy to GitHub Pages (only for tag releases) ────────────────── - deploy_pages: - name: Deploy to GitHub Pages - needs: deploy - runs-on: ubuntu-latest - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4efe70d..e3142e1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,8 +31,8 @@ jobs: - uses: dtolnay/rust-toolchain@stable - - name: Install python-semantic-release - run: pip install "python-semantic-release>=9.21.1" + - name: Install dependencies + run: uv sync --group dev --no-install-project - name: Python Semantic Release id: release diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 630f9c7..a2a7a84 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,19 +15,19 @@ repos: hooks: - id: ruff-check name: ruff check - entry: uv run ruff check --fix + entry: uv run --no-project ruff check --fix language: system types_or: [python, pyi] - id: ruff-format name: ruff format - entry: uv run ruff format + entry: uv run --no-project ruff format language: system types_or: [python, pyi] - id: pyright name: pyright type check - entry: uv run pyright + entry: uv run --no-project pyright language: system types: [python] pass_filenames: false @@ -35,13 +35,13 @@ repos: - id: stub-parity name: check stub parity - entry: uv run python scripts/check_stub_parity.py + entry: uv run --no-project python scripts/check_stub_parity.py language: system pass_filenames: false files: "^python/tryx/.*\\.(py|pyi)$" - id: conventional-commit-message name: conventional commit message - entry: uv run python scripts/check_commit_message.py + entry: uv run --no-project python scripts/check_commit_message.py language: system stages: [commit-msg] diff --git a/Cargo.lock b/Cargo.lock index b4c54e9..01a29a3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1973cfbc1a2daf9cf550e74e1f088c28e7f7d8c1e1418fb6c9dc5184b7e84c99" dependencies = [ - "crypto-common 0.2.1", + "crypto-common 0.2.2", "inout 0.2.2", ] @@ -39,71 +39,48 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8eb277bec05f56a0e0591f155a484cbd0f4f07ff2905051a48c72f004f7ed58" dependencies = [ - "cipher 0.5.1", + "cipher 0.5.2", "cpubits", "cpufeatures 0.3.0", ] [[package]] name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead 0.5.2", - "aes 0.8.4", - "cipher 0.4.4", - "ctr 0.9.2", - "ghash 0.5.1", - "subtle", -] - -[[package]] -name = "aes-gcm" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdf011db2e21ce0d575593d749db5554b47fed37aff429e4dc50bc91ac93a028" +checksum = "7f2b8006a0c83f52b62ba44a97b58bf76fe2f70a329e588f67f89691d93d498f" dependencies = [ "aead 0.6.1", "aes 0.9.2", - "cipher 0.5.1", - "ctr 0.10.0", - "ghash 0.6.0", - "subtle", + "cipher 0.5.2", + "ctr 0.10.1", + "ctutils", + "ghash", ] [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] [[package]] name = "android_system_properties" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" dependencies = [ "libc", ] [[package]] name = "anyhow" -version = "1.0.102" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" - -[[package]] -name = "arc-swap" -version = "1.9.2" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c049c0be4daef0b145cb3555416b3b8ef5b7888a38aea1a3a155801fe7b0810b" -dependencies = [ - "rustversion", -] +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "arrayvec" @@ -117,7 +94,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5493c3bedbacf7fd7382c6346bbd66687d12bbaad3a89a2d2c303ee6cf20b048" dependencies = [ - "asn1-rs-derive", + "asn1-rs-derive 0.5.1", "asn1-rs-impl", "displaydoc", "nom", @@ -127,6 +104,22 @@ dependencies = [ "time", ] +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +dependencies = [ + "asn1-rs-derive 0.6.0", + "asn1-rs-impl", + "displaydoc", + "nom", + "num-traits", + "rusticata-macros", + "thiserror 2.0.20", + "time", +] + [[package]] name = "asn1-rs-derive" version = "0.5.1" @@ -135,7 +128,19 @@ checksum = "965c2d33e53cb6b267e148a4cb0760bc01f4904c1cd4bb4002a085bb016d1490" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", + "synstructure", +] + +[[package]] +name = "asn1-rs-derive" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", "synstructure", ] @@ -147,7 +152,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -197,15 +202,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "base16ct" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "base64" @@ -214,10 +213,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "base64ct" -version = "1.8.3" +name = "base64" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "bincode" @@ -228,6 +227,15 @@ dependencies = [ "serde", ] +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -236,9 +244,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.11.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" [[package]] name = "block-buffer" @@ -251,22 +259,13 @@ dependencies = [ [[package]] name = "block-buffer" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" dependencies = [ "hybrid-array", ] -[[package]] -name = "block-padding" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" -dependencies = [ - "generic-array", -] - [[package]] name = "block-padding" version = "0.4.2" @@ -292,13 +291,13 @@ version = "3.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dee98b0db6a962de883bf5d20362dee4d7ca0d12fe39a7c6c73c844e1cd7c1f" dependencies = [ - "darling", + "darling 0.23.0", "ident_case", "prettyplease", "proc-macro2", "quote", "rustversion", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -307,7 +306,7 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9e6224bc4ee1f189ad257120c156fb05f95b826f5369d620b24984476c200a" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "foldhash 0.1.5", "hashbrown 0.15.5", @@ -341,7 +340,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "thiserror 2.0.20", ] @@ -359,9 +358,32 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytecheck" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26333eeac754f0ad8a6bcd0eb0ac012156302e4e16b852b72ee399aea4f12c29" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "rancor", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "46d07918caa9eeaaf06b7873925c53a61daac173539b4f7715090745e44e4e69" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] [[package]] name = "bytemuck" @@ -377,9 +399,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" [[package]] name = "castaway" @@ -392,27 +414,18 @@ dependencies = [ [[package]] name = "cbc" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" -dependencies = [ - "cipher 0.4.4", -] - -[[package]] -name = "cbc" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98db6aeaef0eeef2c1e3ce9a27b739218825dae116076352ac3777076aa22225" +checksum = "ce2dc9ee5f88d11e0beb842c88b33c8a5cf0d1329c4b19494af42b07dbfe8896" dependencies = [ - "cipher 0.5.1", + "cipher 0.5.2", ] [[package]] name = "cc" -version = "1.2.57" +version = "1.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a0dd1ca384932ff3641c8718a02769f1698e7563dc6974ffd03346116310423" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" dependencies = [ "find-msvc-tools", "shlex", @@ -432,12 +445,13 @@ dependencies = [ [[package]] name = "cfb" -version = "0.12.1" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb9b414dc579b1b0194d06e65472b6ee2e87f623f970a715e3bb04e105400ffa" +checksum = "a347dcabdae9c31b0825fd6a8bed285ec9c2acb89c47827126d52fa4f59cece3" dependencies = [ "fnv", "uuid", + "web-time", ] [[package]] @@ -446,22 +460,28 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + [[package]] name = "chacha20" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "rand_core 0.10.0", + "rand_core 0.10.1", ] [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "js-sys", @@ -483,12 +503,12 @@ dependencies = [ [[package]] name = "cipher" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34d8227fe1ba289043aeb13792056ff80fd6de1a9f49137a5f499de8e8c78ea" +checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ - "block-buffer 0.12.0", - "crypto-common 0.2.1", + "block-buffer 0.12.1", + "crypto-common 0.2.2", "inout 0.2.2", ] @@ -503,9 +523,9 @@ dependencies = [ [[package]] name = "cmov" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0758edba32d61d1fd9f4d69491b47604b91ee2f7e6b33de7e54ca4ebe55dc3" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" [[package]] name = "compact_str" @@ -530,12 +550,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -544,9 +558,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpubits" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ef0c543070d296ea414df2dd7625d1b24866ce206709d8a4a424f28377f5861" +checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" [[package]] name = "cpufeatures" @@ -567,37 +581,19 @@ dependencies = [ ] [[package]] -name = "crc" -version = "3.4.0" +name = "crc32c" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +checksum = "3a47af21622d091a8f0fb295b88bc886ac74efcc613efc19f5d0b21de5c89e47" dependencies = [ - "crc-catalog", + "rustc_version", ] -[[package]] -name = "crc-catalog" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" - [[package]] name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[package]] -name = "crypto-bigint" -version = "0.5.5" +version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" -dependencies = [ - "generic-array", - "rand_core 0.6.4", - "subtle", - "zeroize", -] +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" [[package]] name = "crypto-common" @@ -612,9 +608,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ "hybrid-array", ] @@ -630,37 +626,22 @@ dependencies = [ [[package]] name = "ctr" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17469f8eb9bdbfad10f71f4cfddfd38b01143520c0e717d8796ccb4d44d44e42" +checksum = "baaca1c4b237092596f64d571e9db6ce4109c4ef9742e27590f1709594461f21" dependencies = [ - "cipher 0.5.1", + "cipher 0.5.2", ] [[package]] name = "ctutils" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1005a6d4446f5120ef475ad3d2af2b30c49c2c9c6904258e3bb30219bebed5e4" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" dependencies = [ "cmov", ] -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "curve25519-dalek-derive", - "fiat-crypto 0.2.9", - "rustc_version", - "subtle", - "zeroize", -] - [[package]] name = "curve25519-dalek" version = "5.0.0" @@ -670,7 +651,7 @@ dependencies = [ "cfg-if", "cpufeatures 0.3.0", "curve25519-dalek-derive", - "fiat-crypto 0.3.0", + "fiat-crypto", "rustc_version", "subtle", "zeroize", @@ -684,7 +665,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -693,8 +674,18 @@ version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", ] [[package]] @@ -708,7 +699,20 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.117", + "syn 2.0.119", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", ] [[package]] @@ -717,9 +721,20 @@ version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "darling_core", + "darling_core 0.21.3", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -729,23 +744,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" [[package]] -name = "der" -version = "0.7.10" +name = "der-parser" +version = "9.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", + "asn1-rs 0.6.2", + "displaydoc", + "nom", + "num-bigint", + "num-traits", + "rusticata-macros", ] [[package]] name = "der-parser" -version = "9.0.0" +version = "10.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cd0a5c643689626bec213c4d8bd4d96acc8ffdb4ad4bb6bc16abf27d5f4b553" +checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" dependencies = [ - "asn1-rs", + "asn1-rs 0.7.2", "displaydoc", "nom", "num-bigint", @@ -758,9 +776,6 @@ name = "deranged" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" -dependencies = [ - "powerfmt", -] [[package]] name = "diesel" @@ -778,15 +793,15 @@ dependencies = [ [[package]] name = "diesel_derives" -version = "2.3.7" +version = "2.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47618bf0fac06bb670c036e48404c26a865e6a71af4114dfd97dfe89936e404e" +checksum = "d1817b7f4279b947fc4cafddec12b0e5f8727141706561ce3ac94a60bddd1cf5" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -806,7 +821,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe2444076b48641147115697648dc743c2c00b61adade0f01ce67133c7babe8c" dependencies = [ - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -816,19 +831,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid", "crypto-common 0.1.7", "subtle", ] [[package]] name = "digest" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" dependencies = [ - "block-buffer 0.12.0", - "crypto-common 0.2.1", + "block-buffer 0.12.1", + "crypto-common 0.2.2", "ctutils", ] @@ -855,54 +869,19 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd122633e4bef06db27737f21d3738fb89c8f6d5360d6d9d7635dda142a7757e" dependencies = [ - "darling", + "darling 0.21.3", "either", "heck", "proc-macro2", "quote", - "syn 2.0.117", -] - -[[package]] -name = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest 0.10.7", - "elliptic-curve", - "rfc6979", - "signature", - "spki", + "syn 2.0.119", ] [[package]] name = "either" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" - -[[package]] -name = "elliptic-curve" -version = "0.13.8" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" -dependencies = [ - "base16ct", - "crypto-bigint", - "digest 0.10.7", - "ff", - "generic-array", - "group", - "hkdf 0.12.4", - "pem-rfc7468", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] name = "encoding_rs" @@ -931,11 +910,10 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.4.1" +version = "5.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" dependencies = [ - "concurrent-queue", "parking", "pin-project-lite", ] @@ -958,25 +936,9 @@ checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" [[package]] name = "fastrand" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" - -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.2.9" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" [[package]] name = "fiat-crypto" @@ -986,9 +948,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "fnv" @@ -1008,11 +970,20 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -1024,9 +995,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -1034,44 +1005,44 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -1092,7 +1063,6 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", - "zeroize", ] [[package]] @@ -1108,60 +1078,25 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi 5.3.0", - "wasip2", -] - -[[package]] -name = "getrandom" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi 6.0.0", - "rand_core 0.10.0", - "wasip2", - "wasip3", + "r-efi", + "rand_core 0.10.1", "wasm-bindgen", ] -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval 0.6.2", -] - [[package]] name = "ghash" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eecf2d5dc9b66b732b97707a0210906b1d30523eb773193ab777c0c84b3e8d5" dependencies = [ - "polyval 0.7.1", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", + "polyval", ] [[package]] @@ -1201,15 +1136,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac 0.12.1", -] - [[package]] name = "hkdf" version = "0.13.0" @@ -1234,7 +1160,7 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" dependencies = [ - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -1255,9 +1181,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hybrid-array" -version = "0.4.8" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" dependencies = [ "typenum", ] @@ -1287,10 +1213,87 @@ dependencies = [ ] [[package]] -name = "id-arena" +name = "icu_collections" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] [[package]] name = "ident_case" @@ -1298,22 +1301,41 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" -version = "2.13.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.16.1", - "serde", - "serde_core", + "hashbrown 0.17.1", ] [[package]] name = "infer" -version = "0.21.0" -source = "git+https://github.com/bojand/infer?branch=master#7e6b1d00762cbfbef480333cd137582b3e4aba32" +version = "0.22.0" +source = "git+https://github.com/bojand/infer?branch=master#cb05400c5f43e07200fbc1c5753e15946e9b1de8" dependencies = [ "cfb", ] @@ -1324,7 +1346,6 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ - "block-padding 0.3.3", "generic-array", ] @@ -1334,29 +1355,24 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4250ce6452e92010fdf7268ccc5d14faa80bb12fc741938534c58f16804e03c7" dependencies = [ - "block-padding 0.4.2", + "block-padding", "hybrid-array", ] -[[package]] -name = "ipnet" -version = "2.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" - [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" dependencies = [ - "once_cell", + "cfg-if", + "futures-util", "wasm-bindgen", ] @@ -1366,17 +1382,11 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - [[package]] name = "libc" -version = "0.2.183" +version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" [[package]] name = "libloading" @@ -1405,6 +1415,12 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + [[package]] name = "lock_api" version = "0.4.14" @@ -1416,9 +1432,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "matchers" @@ -1429,6 +1445,16 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest 0.10.7", +] + [[package]] name = "md5" version = "0.8.1" @@ -1437,15 +1463,15 @@ checksum = "7ebb8d8732c6a6df3d8f032a82911cfc747e00efb95cc46e8d0acd5b5b88570c" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memoffset" -version = "0.7.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] @@ -1479,26 +1505,46 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" dependencies = [ "libc", "wasi", "windows-sys 0.61.2", ] +[[package]] +name = "munge" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e17401f259eba956ca16491461b6e8f72913a0a114e39736ce404410f915a0c" +dependencies = [ + "munge_macro", +] + +[[package]] +name = "munge_macro" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4568f25ccbd45ab5d5603dc34318c1ec56b117531781260002151b8530a9f931" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "nix" -version = "0.26.4" +version = "0.31.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.13.1", "cfg-if", + "cfg_aliases", "libc", "memoffset", - "pin-utils", ] [[package]] @@ -1532,15 +1578,15 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] @@ -1560,20 +1606,23 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d8034d9489cdaf79228eb9f6a3b8d7bb32ba00d6645ebd48eef4077ceb5bd9" dependencies = [ - "asn1-rs", + "asn1-rs 0.6.2", ] [[package]] -name = "once_cell" -version = "1.21.4" +name = "oid-registry" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" +dependencies = [ + "asn1-rs 0.7.2", +] [[package]] -name = "opaque-debug" -version = "0.3.1" +name = "once_cell" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "opus" @@ -1584,30 +1633,6 @@ dependencies = [ "audiopus_sys", ] -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2 0.10.9", -] - -[[package]] -name = "p384" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" -dependencies = [ - "ecdsa", - "elliptic-curve", - "primeorder", - "sha2 0.10.9", -] - [[package]] name = "parking" version = "2.2.1" @@ -1643,19 +1668,10 @@ version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" dependencies = [ - "base64", + "base64 0.22.1", "serde_core", ] -[[package]] -name = "pem-rfc7468" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" -dependencies = [ - "base64ct", -] - [[package]] name = "percent-encoding" version = "2.3.2" @@ -1668,56 +1684,37 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - [[package]] name = "pkg-config" -version = "0.3.32" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "polyval" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash 0.5.1", -] - -[[package]] -name = "polyval" -version = "0.7.1" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dfc63250416fea14f5749b90725916a6c903f599d51cb635aa7a52bfd03eede" +checksum = "f0fa31d631f2b2cb2a544d0aa321ce847a94764d701ca2becc411138b93d49cd" dependencies = [ "cpubits", "cpufeatures 0.3.0", - "universal-hash 0.6.1", + "universal-hash", ] [[package]] name = "portable-atomic" -version = "1.13.1" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] [[package]] name = "powerfmt" @@ -1726,47 +1723,49 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "prettyplease" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ - "zerocopy", + "proc-macro2", + "syn 2.0.119", ] [[package]] -name = "prettyplease" -version = "0.2.37" +name = "proc-macro2" +version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" dependencies = [ - "proc-macro2", - "syn 2.0.117", + "unicode-ident", ] [[package]] -name = "primeorder" -version = "0.13.6" +name = "ptr_meta" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +checksum = "743da816b98c921cdbe8628ef7381b76f25ecf4da599fc80aca90eae7ef70cc0" dependencies = [ - "elliptic-curve", + "ptr_meta_derive", ] [[package]] -name = "proc-macro2" -version = "1.0.106" +name = "ptr_meta_derive" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +checksum = "1c8d9ca532f185d5d4db7a7c9d51420b452168ea1c2b913953281bd6fe1fcbd0" dependencies = [ - "unicode-ident", + "proc-macro2", + "quote", + "syn 3.0.3", ] [[package]] name = "pyo3" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf85e27e86080aafd5a22eae58a162e133a589551542b3e5cee4beb27e54f8e1" +checksum = "91fd8e38a3b50ed1167fb981cd6fd60147e091784c427b8f7183a7ee32c31c12" dependencies = [ "libc", "once_cell", @@ -1799,23 +1798,23 @@ checksum = "c23399970eea9c31d0ac84cee4a9d8dd05f89b1da2f4dd5bb44b32a3f66db4f8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "pyo3-build-config" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf94ee265674bf76c09fa430b0e99c26e319c945d96ca0d5a8215f31bf81cf7" +checksum = "e368e7ddfdeb98c9bca7f8383be1648fd84ab466bf2bc015e94008db6d35611e" dependencies = [ "target-lexicon", ] [[package]] name = "pyo3-ffi" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "491aa5fc66d8059dd44a75f4580a2962c1862a1c2945359db36f6c2818b748dc" +checksum = "7f29e10af80b1f7ccaf7f69eace800a03ecd13e883acfacc1e5d0988605f651e" dependencies = [ "libc", "pyo3-build-config", @@ -1823,44 +1822,38 @@ dependencies = [ [[package]] name = "pyo3-macros" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5d671734e9d7a43449f8480f8b38115df67bef8d21f76837fa75ee7aaa5e52e" +checksum = "df6e520eff47c45997d2fc7dd8214b25dd1310918bbb2642156ef66a67f29813" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "pyo3-macros-backend" -version = "0.28.2" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22faaa1ce6c430a1f71658760497291065e6450d7b5dc2bcf254d49f66ee700a" +checksum = "c4cdc218d835738f81c2338f822078af45b4afdf8b2e33cbb5916f108b813acb" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] name = "quote" -version = "1.0.45" +version = "1.0.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +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 = "r-efi" version = "6.0.0" @@ -1879,55 +1872,23 @@ dependencies = [ ] [[package]] -name = "rand" -version = "0.8.7" +name = "rancor" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +checksum = "9b534442d0fcdb55d66f373d9cac6d33b6293a2335bc2136dbd06ce0e87d2572" dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", + "ptr_meta", ] [[package]] name = "rand" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.5", -] - -[[package]] -name = "rand" -version = "0.10.0" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" dependencies = [ "chacha20", - "getrandom 0.4.2", - "rand_core 0.10.0", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] @@ -1941,29 +1902,21 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rand_core" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" [[package]] name = "rcgen" -version = "0.13.2" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" +checksum = "091e7a8e7d86e6feb87a27ce8e2cba29d49eff9507afeebefab7eeb2ca667fb4" dependencies = [ "pem", "ring", "rustls-pki-types", "time", + "x509-parser 0.18.1", "yasna", ] @@ -1973,14 +1926,14 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", ] [[package]] name = "regex-automata" -version = "0.4.14" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -1989,18 +1942,17 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] -name = "rfc6979" -version = "0.4.0" +name = "rend" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "663ba70707f96e871406fe10d68128412e619b06d1d47cb91c3a4c6501176240" dependencies = [ - "hmac 0.12.1", - "subtle", + "bytecheck", ] [[package]] @@ -2017,16 +1969,137 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rkyv" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9776093b7ca170454ab1406954f7b7d97a57c51dc6c0642957fb2ef25c2d399" +dependencies = [ + "bytecheck", + "bytes", + "hashbrown 0.17.1", + "indexmap", + "munge", + "ptr_meta", + "rancor", + "rend", + "rkyv_derive", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c25ef604ac7dd839d44d64648952ea23c97866f124ff671b0ed2cf3ad9bb06e" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + [[package]] name = "rsqlite-vfs" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" +checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" dependencies = [ "hashbrown 0.16.1", "thiserror 2.0.20", ] +[[package]] +name = "rtc-crypto" +version = "0.21.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d47521f43ea9a6e16feafa78ba06633ca85a51f0ad66b906a5f01ccfe08ba90" +dependencies = [ + "aes 0.8.4", + "ccm", + "ctr 0.9.2", + "hmac 0.12.1", + "md-5", + "rand", + "ring", + "sha1 0.10.7", + "subtle", + "thiserror 2.0.20", + "zeroize", +] + +[[package]] +name = "rtc-datachannel" +version = "0.21.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374adcfc6f138839109d84c521ccedae9410b11f62c1ae577cb786ed91494f50" +dependencies = [ + "bytes", + "log", + "rtc-sctp", + "rtc-shared", + "sansio", +] + +[[package]] +name = "rtc-dtls" +version = "0.21.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3baf3ee5b4d481d57fdee828ed4abd281ba45ad98594502cd4e866d14316b48f" +dependencies = [ + "bytecheck", + "byteorder", + "bytes", + "der-parser 9.0.0", + "log", + "pem", + "rcgen", + "rkyv", + "rtc-crypto", + "rtc-shared", + "rustls", + "x509-parser 0.16.0", +] + +[[package]] +name = "rtc-sctp" +version = "0.21.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db38ed06af7bea634b210c3e8cf0e78e3bb36669e120968f5961a3c12a209b67" +dependencies = [ + "bytes", + "crc32c", + "log", + "rand", + "rtc-shared", + "rustc-hash", + "slab", + "thiserror 2.0.20", +] + +[[package]] +name = "rtc-shared" +version = "0.21.0-beta.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "108063844e58fa8470139c2d6bea3a28362e07d43194acef7e0d57d829d27cd6" +dependencies = [ + "bitflags 1.3.2", + "bytes", + "nix", + "rand", + "serde", + "substring", + "thiserror 2.0.20", + "url", + "winapi", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2051,7 +2124,7 @@ version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.11.0", + "bitflags 2.13.1", "errno", "libc", "linux-raw-sys", @@ -2060,9 +2133,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.37" +version = "0.23.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" +checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06" dependencies = [ "log", "once_cell", @@ -2075,18 +2148,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "zeroize", ] [[package]] name = "rustls-webpki" -version = "0.103.9" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "ring", "rustls-pki-types", @@ -2095,9 +2168,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.22" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "sansio" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +checksum = "c62751faa8bc286982334a082fe125184a29fc89d17775766e4f891b7d726980" [[package]] name = "scheduled-thread-pool" @@ -2114,31 +2193,17 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" -[[package]] -name = "sec1" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" -dependencies = [ - "base16ct", - "der", - "generic-array", - "pkcs8", - "subtle", - "zeroize", -] - [[package]] name = "semver" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" dependencies = [ "serde_core", "serde_derive", @@ -2155,29 +2220,29 @@ dependencies = [ [[package]] name = "serde_core" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.228" +version = "1.0.229" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.151" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" dependencies = [ "itoa", "memchr", @@ -2188,9 +2253,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "1.0.4" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] @@ -2214,18 +2279,7 @@ checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", -] - -[[package]] -name = "sha2" -version = "0.10.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" -dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", + "digest 0.11.3", ] [[package]] @@ -2236,7 +2290,7 @@ checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" dependencies = [ "cfg-if", "cpufeatures 0.3.0", - "digest 0.11.2", + "digest 0.11.3", ] [[package]] @@ -2250,9 +2304,9 @@ 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" @@ -2264,16 +2318,6 @@ dependencies = [ "libc", ] -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest 0.10.7", - "rand_core 0.6.4", -] - [[package]] name = "simdutf8" version = "0.1.5" @@ -2288,9 +2332,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "smallvec" -version = "1.15.1" +version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" dependencies = [ "serde", ] @@ -2306,29 +2350,19 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys 0.61.2", ] -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - [[package]] name = "sqlite-wasm-rs" -version = "0.5.2" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4206ed3a67690b9c29b77d728f6acc3ce78f16bf846d83c94f76400320181b" +checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" dependencies = [ "cc", "js-sys", @@ -2352,7 +2386,16 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "substring" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ee6433ecef213b2e72f587ef64a2f5943e7cd16fbd82dbe8bc07486c534c86" +dependencies = [ + "autocfg", +] [[package]] name = "subtle" @@ -2470,9 +2513,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.117" +version = "2.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" dependencies = [ "proc-macro2", "quote", @@ -2498,7 +2541,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2514,7 +2557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.2", + "getrandom 0.4.3", "once_cell", "rustix", "windows-sys 0.61.2", @@ -2546,7 +2589,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2562,21 +2605,20 @@ dependencies = [ [[package]] name = "thread_local" -version = "1.1.9" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" dependencies = [ "cfg-if", ] [[package]] name = "time" -version = "0.3.47" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", - "itoa", "num-conv", "powerfmt", "serde_core", @@ -2586,20 +2628,45 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" [[package]] name = "time-macros" -version = "0.2.27" +version = "0.2.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" dependencies = [ "num-conv", "time-core", ] +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +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.53.1" @@ -2609,7 +2676,6 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -2619,13 +2685,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] @@ -2640,13 +2706,14 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.18" +version = "0.7.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" dependencies = [ "bytes", "futures-core", "futures-sink", + "libc", "pin-project-lite", "tokio", ] @@ -2657,13 +2724,13 @@ version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d52efb639344a7c6adb8e62c6f3d2c19c001ff1b79a5041ba1c6ed42e19c6aa5" dependencies = [ - "base64", + "base64 0.22.1", "bytes", "futures-core", "futures-sink", "http", "httparse", - "rand 0.10.0", + "rand", "ring", "rustls-pki-types", "simdutf8", @@ -2696,11 +2763,11 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.10+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df25b4befd31c4816df190124375d5a20c6b6921e2cad937316de3fccd63420" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ - "winnow 1.0.0", + "winnow 1.0.4", ] [[package]] @@ -2722,7 +2789,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -2766,7 +2833,7 @@ dependencies = [ [[package]] name = "tryx" -version = "0.6.4" +version = "1.3.1" dependencies = [ "async-channel", "async-trait", @@ -2796,9 +2863,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.19.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "unicode-ident" @@ -2806,29 +2873,13 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common 0.1.7", - "subtle", -] - [[package]] name = "universal-hash" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4987bdc12753382e0bec4a65c50738ffaabc998b9cdd1f952fb5f39b0048a96" dependencies = [ - "crypto-common 0.2.1", + "crypto-common 0.2.2", "ctutils", ] @@ -2840,11 +2891,11 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.3.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +checksum = "972d7902c8735f2695410b8aed7df6ed12a47394aa1c8d7af49f0497b731a94d" dependencies = [ - "base64", + "base64 0.23.1", "log", "percent-encoding", "rustls", @@ -2856,27 +2907,45 @@ dependencies = [ [[package]] name = "ureq-proto" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +checksum = "da5f78b09e6941e1a0f2e30e695e4b120377b54d5e0aec11b594bb57b3971613" dependencies = [ - "base64", + "base64 0.23.1", "http", "httparse", "log", ] +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + [[package]] name = "utf8-zero" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + [[package]] name = "uuid" -version = "1.22.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a68d3c8f01c0cfa54a75291d83601161799e4a89a39e0929f4b0354d88757a37" +checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc" dependencies = [ "js-sys", "wasm-bindgen", @@ -2905,35 +2974,35 @@ name = "wacore" version = "0.7.0" dependencies = [ "aes 0.9.2", - "aes-gcm 0.11.0", + "aes-gcm", "anyhow", "async-channel", "async-lock", "async-trait", - "base64", + "base64 0.23.1", "bon", "buffa", "buffa-build", "bytes", "chrono", "compact_str", - "ctr 0.10.0", + "ctr 0.10.1", "event-listener", "futures", "hashbrown 0.17.1", "hex", - "hkdf 0.13.0", + "hkdf", "hmac 0.13.0", "itoa", "log", "md5", "portable-atomic", - "rand 0.10.0", + "rand", "serde", "serde-big-array", "serde_json", "sha1 0.11.0", - "sha2 0.11.0", + "sha2", "smallvec", "smoothutf8", "subtle", @@ -2955,13 +3024,13 @@ dependencies = [ "anyhow", "buffa", "hex", - "hkdf 0.13.0", + "hkdf", "hmac 0.13.0", "log", "serde", "serde-big-array", "serde_json", - "sha2 0.11.0", + "sha2", "thiserror 2.0.20", "wacore-binary", "wacore-libsignal", @@ -3002,25 +3071,25 @@ dependencies = [ "async-trait", "buffa", "bytes", - "cbc 0.2.0", + "cbc", "chrono", - "ctr 0.10.0", - "curve25519-dalek 5.0.0", - "ghash 0.6.0", + "ctr 0.10.1", + "curve25519-dalek", + "ghash", "hex", - "hkdf 0.13.0", + "hkdf", "hmac 0.13.0", "log", "portable-atomic", - "rand 0.10.0", + "rand", "serde", "sha1 0.11.0", - "sha2 0.11.0", + "sha2", "subtle", "thiserror 2.0.20", "wacore-derive", "waproto", - "x25519-dalek 3.0.0", + "x25519-dalek", ] [[package]] @@ -3030,10 +3099,10 @@ dependencies = [ "anyhow", "buffa", "bytes", - "hkdf 0.13.0", + "hkdf", "log", - "rand 0.10.0", - "sha2 0.11.0", + "rand", + "sha2", "thiserror 2.0.20", "wacore-binary", "wacore-libsignal", @@ -3050,7 +3119,7 @@ dependencies = [ "bytes", "heck", "serde", - "sha2 0.11.0", + "sha2", ] [[package]] @@ -3059,29 +3128,11 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.2+wasi-0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wasip3" -version = "0.4.0+wasi-0.3.0-rc-2026-01-06" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" -dependencies = [ - "wit-bindgen", -] - [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" dependencies = [ "cfg-if", "once_cell", @@ -3092,9 +3143,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3102,58 +3153,34 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.127" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" dependencies = [ "unicode-ident", ] [[package]] -name = "wasm-encoder" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.244.0" +name = "web-time" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ - "bitflags 2.11.0", - "hashbrown 0.15.5", - "indexmap", - "semver", + "js-sys", + "wasm-bindgen", ] [[package]] @@ -3165,117 +3192,6 @@ dependencies = [ "rustls-pki-types", ] -[[package]] -name = "webrtc-data" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd470286275809f2fcfcdb1e73ef5f1500be82eff7fe98150ce81b20aad5a2a4" -dependencies = [ - "bytes", - "log", - "portable-atomic", - "thiserror 1.0.69", - "tokio", - "webrtc-sctp", - "webrtc-util 0.17.2", -] - -[[package]] -name = "webrtc-dtls" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ccbe4d9049390ab52695c3646c1395c877e16c15fb05d3bda8eee0c7351711c" -dependencies = [ - "aes 0.8.4", - "aes-gcm 0.10.3", - "async-trait", - "bincode", - "byteorder", - "cbc 0.1.2", - "ccm", - "der-parser", - "hkdf 0.12.4", - "hmac 0.12.1", - "log", - "p256", - "p384", - "portable-atomic", - "rand 0.8.7", - "rand_core 0.6.4", - "rcgen", - "ring", - "rustls", - "sec1", - "serde", - "sha1 0.10.7", - "sha2 0.10.9", - "subtle", - "thiserror 1.0.69", - "tokio", - "webrtc-util 0.11.0", - "x25519-dalek 2.0.1", - "x509-parser", -] - -[[package]] -name = "webrtc-sctp" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c4b637f0d8eb96d900ac0f79b3060ddd21ca88dfefa467e96e67ab0016f2574" -dependencies = [ - "arc-swap", - "async-trait", - "bytes", - "crc", - "log", - "portable-atomic", - "rand 0.9.5", - "thiserror 1.0.69", - "tokio", - "webrtc-util 0.17.2", -] - -[[package]] -name = "webrtc-util" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64bfb10dbe6d762f80169ae07cf252bafa1f764b9594d140008a0231c0cdce58" -dependencies = [ - "async-trait", - "bitflags 1.3.2", - "bytes", - "ipnet", - "lazy_static", - "libc", - "log", - "nix", - "portable-atomic", - "rand 0.8.7", - "thiserror 1.0.69", - "tokio", - "winapi", -] - -[[package]] -name = "webrtc-util" -version = "0.17.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1beae0b4f24969741c26ca282a257dc5823bd9cbe608f7e3ca3775102d323ad9" -dependencies = [ - "async-trait", - "bitflags 1.3.2", - "bytes", - "ipnet", - "lazy_static", - "log", - "nix", - "portable-atomic", - "rand 0.9.5", - "thiserror 1.0.69", - "tokio", - "winapi", -] - [[package]] name = "whatsapp-rust" version = "0.7.0" @@ -3284,21 +3200,24 @@ dependencies = [ "async-channel", "async-lock", "async-trait", - "base64", + "base64 0.23.1", "buffa", "bytes", "chrono", "event-listener", "futures", - "getrandom 0.4.2", + "getrandom 0.4.3", "hashbrown 0.17.1", "hex", "itoa", "log", "opus", "portable-atomic", - "rand 0.10.0", - "rustls", + "rand", + "rtc-datachannel", + "rtc-dtls", + "rtc-sctp", + "rtc-shared", "scopeguard", "serde", "serde_json", @@ -3308,11 +3227,6 @@ dependencies = [ "wacore", "wacore-binary", "waproto", - "webrtc-data", - "webrtc-dtls", - "webrtc-sctp", - "webrtc-util 0.11.0", - "webrtc-util 0.17.2", "whatsapp-rust-sqlite-storage", "whatsapp-rust-tokio-transport", "whatsapp-rust-ureq-http-client", @@ -3333,7 +3247,7 @@ dependencies = [ "log", "scheduled-thread-pool", "serde_json", - "sha2 0.11.0", + "sha2", "tokio", "wacore", ] @@ -3411,7 +3325,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3422,7 +3336,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[package]] @@ -3539,109 +3453,15 @@ checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" [[package]] name = "winnow" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e88e4667264a994d34e6d1ab2d26d398dcdca8b7f52bec8668957517fc7d8" - -[[package]] -name = "wit-bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" -dependencies = [ - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" -dependencies = [ - "anyhow", - "heck", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" -dependencies = [ - "anyhow", - "heck", - "indexmap", - "prettyplease", - "syn 2.0.117", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn 2.0.117", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.244.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" -dependencies = [ - "anyhow", - "bitflags 2.11.0", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.244.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" [[package]] -name = "x25519-dalek" -version = "2.0.1" +name = "writeable" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" -dependencies = [ - "curve25519-dalek 4.1.3", - "rand_core 0.6.4", - "serde", - "zeroize", -] +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "x25519-dalek" @@ -3649,8 +3469,8 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7e8131a03190127fb2263afc72b322ecadae46b6ff8c6f399ff5d02f5559af6" dependencies = [ - "curve25519-dalek 5.0.0", - "rand_core 0.10.0", + "curve25519-dalek", + "rand_core 0.10.1", "zeroize", ] @@ -3660,31 +3480,50 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcbc162f30700d6f3f82a24bf7cc62ffe7caea42c0b2cba8bf7f3ae50cf51f69" dependencies = [ - "asn1-rs", + "asn1-rs 0.6.2", "data-encoding", - "der-parser", + "der-parser 9.0.0", "lazy_static", "nom", - "oid-registry", + "oid-registry 0.7.1", "rusticata-macros", "thiserror 1.0.69", "time", ] +[[package]] +name = "x509-parser" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202" +dependencies = [ + "asn1-rs 0.7.2", + "data-encoding", + "der-parser 10.0.0", + "lazy_static", + "nom", + "oid-registry 0.8.1", + "ring", + "rusticata-macros", + "thiserror 2.0.20", + "time", +] + [[package]] name = "yasna" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +checksum = "b5f6765e852b9b4dc8e2a76843e4d64d1cea8e79bcde0b6901aea8e7c7f08282" dependencies = [ + "bit-vec", "time", ] [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -3699,7 +3538,7 @@ checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] @@ -3720,27 +3559,27 @@ checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", ] [[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.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 2.0.119", "synstructure", ] @@ -3749,19 +3588,38 @@ name = "zeroize" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ - "zeroize_derive", + "displaydoc", + "yoke", + "zerofrom", ] [[package]] -name = "zeroize_derive" -version = "1.5.0" +name = "zerovec" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.117", + "syn 3.0.3", ] [[package]] @@ -3772,6 +3630,6 @@ checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12" [[package]] name = "zmij" -version = "1.0.21" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 49c40da..45c1e69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,6 @@ crate-type = ["cdylib"] [dependencies] pyo3 = { version = "0.28.0", features = ["extension-module", "abi3-py38"] } -# NOTE: intentionally no `simd` feature — it turns on `#![feature(portable_simd)]` -# in wacore-binary, which is nightly-only and breaks stable builds (CI + sdist). # NOTE: `voip` is excluded from the hardcoded feature set; it is controlled by # crate-level features below so CI can toggle voip-libopus per target. whatsapp-rust = { path = "libs/whatsapp-rust", default-features = false, features = ["sqlite-storage", "tokio-transport", "tokio-runtime", "ureq-client", "tokio-native", "signal"] } @@ -40,8 +38,7 @@ async-channel = "2.5" # the PyO3 player and receives lifecycle results. symphonia = { version = "0.5.4", default-features = false, features = ["mp3", "wav", "ogg", "vorbis", "pcm"] } serde = { version = "1", features = ["derive"] } -# default-features = false disables wacore-appstate's default `simd` feature, -# which would otherwise pull in nightly-only portable_simd via wacore-binary. +# default-features = false avoids pulling unnecessary default features. wacore-appstate = { path = "libs/whatsapp-rust/wacore/appstate", default-features = false } # ── Feature flags ────────────────────────────────────────────────────── @@ -55,14 +52,17 @@ voip = ["whatsapp-rust/voip"] voip-mlow = ["whatsapp-rust/voip-mlow"] # ── Release profile ──────────────────────────────────────────────────── -# Aggressively optimize for binary size. Without this, debug symbols -# alone can bloat the .so to ~1 GB. +# Optimised for maximum runtime performance. opt-level=3 enables full +# inlining and loop-unrolling; lto=fat + codegen-units=1 give the +# optimizer a single, whole-program view. panic="unwind" is mandatory +# because PyO3 and whatsapp-rust rely on catch_unwind() to convert +# Rust panics into Python exceptions instead of crashing the host. [profile.release] -strip = true # strip debug symbols (~900 MB saved) -lto = true # link-time optimization (20-30% smaller code) -opt-level = "z" # optimize for size over speed -codegen-units = 1 # single codegen unit for better optimization -panic = "abort" # skip unwind tables (~5% smaller) +opt-level = 3 # maximum runtime performance +lto = "fat" # full cross-crate link-time optimization +codegen-units = 1 # single codegen unit for whole-program opts +strip = "symbols" # strip debug symbols, keep minimal info +panic = "unwind" # REQUIRED: PyO3 catch_unwind → Python exceptions [profile.dev] # Keep dev builds fast. diff --git a/README.id.md b/README.id.md new file mode 100644 index 0000000..e923495 --- /dev/null +++ b/README.id.md @@ -0,0 +1,248 @@ +# Tryx + +[![PyPI version](https://img.shields.io/pypi/v/tryx?color=blue)](https://pypi.org/project/tryx/) +[![Python](https://img.shields.io/pypi/pyversions/tryx.svg)](https://pypi.org/project/tryx/) +[![License](https://img.shields.io/github/license/krypton-byte/tryx)](LICENSE) +[![Docs](https://img.shields.io/badge/docs-online-brightgreen)](https://krypton-byte.github.io/tryx/) + +**Bahasa:** [English](README.md) | [简体中文](README.zh.md) | Bahasa Indonesia + +Tryx adalah library otomasi WhatsApp untuk Python yang dibangun dengan Rust dan PyO3. API publiknya dibuat agar nyaman digunakan dari Python, dengan dukungan async, sesi persisten, dan akses ke fitur protokol WhatsApp Web tingkat lanjut. + +> Tryx adalah proyek independen. Tidak berafiliasi dengan WhatsApp, Meta, atau produk resmi mereka. + +## Apa Itu Tryx? + +Tryx membantu pengembang Python membangun tools otomasi WhatsApp tanpa harus menulis Rust secara langsung. Logika protokol berjalan di Rust untuk performa, sedangkan API publiknya tetap nyaman untuk aplikasi Python. + +Tryx cocok untuk: + +- Otomasi WhatsApp Web dari Python. +- Sesi login persisten dengan SQLite, PostgreSQL, atau MySQL. +- API async untuk bot, worker, dashboard, dan tool internal. +- Akses ke kontak, grup, newsletter, status, privasi, profil, label, komentar, event, dan helper protokol tingkat lanjut. + +## Fitur Utama + +- **Core Rust, API Python** — logika protokol diimplementasikan dalam Rust dan diekspos melalui PyO3. +- **Async first** — dirancang untuk aplikasi `asyncio`. +- **Sesi persisten** — sesi perangkat dapat digunakan ulang melalui database. +- **API luas** — namespace umum dan advanced sudah diexpose. +- **Type stub Python** — tersedia `.pyi` untuk autocomplete dan type checking yang lebih baik. + +## Instalasi + +```bash +pip install tryx +``` + +Untuk development lokal: + +```bash +git clone https://github.com/krypton-byte/tryx.git +cd tryx +git submodule update --init --recursive +uv sync --group dev +uv run maturin develop +``` + +## Mulai Cepat + +```python +import asyncio + +from tryx.backend import SqliteStore +from tryx.client import Tryx, TryxClient +from tryx.events import EvMessage, EvPairingQrCode + +backend = SqliteStore("whatsapp.db") +app = Tryx(backend) + + +@app.on(EvPairingQrCode) +async def on_pairing_qr(_client: TryxClient, event: EvPairingQrCode) -> None: + print("Scan kode QR ini dengan WhatsApp:") + print(event.code) + + +@app.on(EvMessage) +async def on_message(client: TryxClient, event: EvMessage) -> None: + text = (event.data.get_text() or "").strip() + if text.lower() == "ping": + await client.send_text( + event.data.message_info.source.chat, "pong", quoted=event + ) + + +async def main() -> None: + await app.run() + + +asyncio.run(main()) +``` + +## Namespace Client + +Objek runtime `TryxClient` dibagi menjadi beberapa namespace agar API lebih rapi: + +| Namespace | Fungsi | +| --- | --- | +| `contact` | Helper untuk kontak dan pencarian kontak. | +| `chat_actions` | Operasi chat seperti mute, archive, pin, mark read, clear chat, dan save contact. | +| `groups` | Pembuatan grup, metadata, member, invite, request membership, dan pengaturan admin. | +| `community` | Operasi terkait komunitas. | +| `newsletter` | Info newsletter, pesan, mute, edit, dan revoke. | +| `status` | Posting status, lookup status, dan helper terkait status. | +| `chatstate` | State chat seperti typing, recording, dan paused. | +| `blocking` | Block dan unblock kontak. | +| `polls` | Membuat poll dan mengirim vote. | +| `presence` | Subscribe presence dan update availability. | +| `privacy` | Pengaturan privasi. | +| `profile` | Nama profil, status teks, dan foto profil. | +| `labels` | Manajemen label untuk chat dan pesan. | +| `comments` | Helper protokol terkait komentar. | +| `events` | Helper response dan operasi berbasis event. | +| `advanced` | Wait lifecycle, diagnostik, dan akses protokol tingkat lanjut. | +| `voip` | Manajemen panggilan audio dan video. | + +## Storage Backend + +Tryx mendukung tiga tipe storage untuk menyimpan sesi WhatsApp: + +| Tipe | Backend | Kegunaan | +| --- | --- | --- | +| Built-in | `SqliteStore("whatsapp.db")` | Development lokal dan deployment sederhana. | +| Native FFI | `FfiStoreProtocol` | Store eksternal throughput tinggi seperti PostgreSQL. | +| Pure Python | subclass `StoreBase` | Store async custom seperti Redis, MongoDB, atau DynamoDB. | + +SQLite cukup untuk development lokal. Native FFI atau backend Python custom lebih cocok jika sesi perlu dipakai bersama oleh beberapa worker atau environment deployment. + +## VoIP: Panggilan Audio dan Video + +Tryx menyediakan bridge VoIP berbasis Rust untuk panggilan audio dan video WhatsApp. Protocol, RTP/WebRTC, enkripsi, codec, dan orkestrasi call berjalan di whatsapp-rust; Python menyediakan media melalui adapter source dan sink asynchronous. + +### Kontrak Media + +| Media | Kontrak | +| --- | --- | +| Audio | Mono signed PCM16 little-endian, 16.000 Hz, 960 sample / 1.920 byte per frame, 60 ms | +| Video | H.264 Annex-B access unit melalui `VideoFrame` | + +### Adapter Audio Minimal + +```python +from collections.abc import AsyncIterator +from tryx.media import AudioSink, AudioSource, validate_audio_frame + + +class Microphone(AudioSource): + async def frames(self) -> AsyncIterator[bytes]: + while True: + frame = await read_microphone_frame() + yield validate_audio_frame(frame) + + +class Speaker(AudioSink): + async def write(self, frame: bytes) -> None: + validate_audio_frame(frame) + await play_speaker_frame(frame) +``` + +### Panggilan Audio 1:1 + +```python +from tryx.types import JID + + +async def start_audio_call(client, phone_number: str): + peer = JID(phone_number + "@s.whatsapp.net") + call = await client.voip.call(peer, Microphone(), Speaker()) + print("panggilan dimulai:", call.call_id) + call.set_muted(True) + call.set_muted(False) + await call.wait_ended() +``` + +### Panggilan Video + +```python +from tryx.media import VideoPlayer + + +async def start_video_call(client, peer, video_sink): + video_source = VideoPlayer(fps=15) + video_source.play("sample.mp4") + call = await client.voip.video_call( + peer, Microphone(), Speaker(), video_source, video_sink + ) + await call.wait_ended() + video_source.stop() +``` + +### Group Call dan Call Link + +```python +call = await client.voip.group_call( + peers=[peer_a, peer_b], + audio_source=Microphone(), + audio_sink=Speaker(), +) +await call.invite_participant(peer_c) +await call.ring_participant(peer_c) + +linked = await client.voip.join_call_link( + "https://call.whatsapp.com/your-token", + "audio", + Microphone(), + Speaker(), +) +``` + +### AudioPlayer Native + +```python +from tryx.media import AudioPlayer + +player = AudioPlayer(buffer_frames=3) +player.play("intro.mp3", mode="replace") +call = await client.voip.call(peer, player, Speaker()) +player.pause() +player.resume() +player.enqueue("next.wav") +player.skip() +player.clear_queue() +player.stop() +``` + +## Development + +```bash +cargo check +cargo test --lib +uv run pytest -q +uv run maturin develop +uv run maturin build --release +``` + +## Struktur Project + +```text +. +├── libs/whatsapp-rust/ # Submodule crate protokol Rust +├── src/ # Binding Rust dan PyO3 +├── python/tryx/ # Package Python dan type stub +├── examples/ # Contoh script otomasi +├── docs/ # Situs dokumentasi +└── tests/ # Test suite Python +``` + +## Link Penting + +- Dokumentasi: +- Contoh: [`examples/`](examples/) +- Type stub Python: [`python/tryx/`](python/tryx/) +- Binding Rust: [`src/`](src/) + +## Lisensi + +Proyek ini dilisensikan di bawah [MIT License](LICENSE). diff --git a/README.md b/README.md index e7f2fcb..95a24c2 100644 --- a/README.md +++ b/README.md @@ -3,19 +3,15 @@ [![PyPI version](https://img.shields.io/pypi/v/tryx?color=blue)](https://pypi.org/project/tryx/) [![Python](https://img.shields.io/pypi/pyversions/tryx.svg)](https://pypi.org/project/tryx/) [![License](https://img.shields.io/github/license/krypton-byte/tryx)](LICENSE) -[![Docs](https://img.shields.io/badge/docs-online-brightgreen)](http://krypton-byte.tech/tryx/) +[![Docs](https://img.shields.io/badge/docs-online-brightgreen)](https://krypton-byte.github.io/tryx/) -**Language:** [English](#english) | [简体中文](#简体中文) | [Bahasa Indonesia](#bahasa-indonesia) +**Language:** English | [简体中文](README.zh.md) | [Bahasa Indonesia](README.id.md) Tryx is a Python automation library powered by Rust and PyO3. It provides a Python-friendly API on top of the `whatsapp-rust` crate, with async support, persistent sessions, and access to advanced WhatsApp Web protocol features. > Tryx is an independent project. It is not affiliated with WhatsApp, Meta, or their official products. ---- - -## English - -### What Is Tryx? +## What Is Tryx? Tryx helps Python developers build WhatsApp automation tools without writing Rust directly. The core protocol logic runs in Rust for performance, while the public interface stays ergonomic for Python applications. @@ -26,15 +22,15 @@ Use Tryx when you need: - Async APIs for bots, workers, dashboards, and internal tools. - Access to contacts, groups, newsletters, status, privacy, profile, labels, comments, events, and lower-level protocol helpers. -### Highlights +## Highlights -- **Rust core, Python API** - protocol logic is implemented in Rust and exposed through PyO3. -- **Async first** - designed for `asyncio` applications. -- **Persistent sessions** - reuse device sessions through a database-backed store. -- **Broad API surface** - exposes common and advanced client namespaces. -- **Typed Python stubs** - includes `.pyi` files for better editor support. +- **Rust core, Python API** — protocol logic is implemented in Rust and exposed through PyO3. +- **Async first** — designed for `asyncio` applications. +- **Persistent sessions** — reuse device sessions through a database-backed store. +- **Broad API surface** — exposes common and advanced client namespaces. +- **Typed Python stubs** — includes `.pyi` files for better editor support. -### Installation +## Installation ```bash pip install tryx @@ -50,7 +46,7 @@ uv sync --group dev uv run maturin develop ``` -### Quick Start +## Quick Start ```python import asyncio @@ -85,7 +81,7 @@ async def main() -> None: asyncio.run(main()) ``` -### Runtime Client Namespaces +## Client Namespaces The runtime `TryxClient` object exposes several namespaces so the API stays organized: @@ -107,8 +103,9 @@ The runtime `TryxClient` object exposes several namespaces so the API stays orga | `comments` | Comment-related protocol helpers. | | `events` | Event response helpers and event-oriented operations. | | `advanced` | Lifecycle waits, diagnostics, and lower-level protocol escape hatches. | +| `voip` | Audio and video call management. | -### Storage Backends +## Storage Backends Tryx supports three storage tiers: @@ -120,31 +117,18 @@ Tryx supports three storage tiers: SQLite is usually enough for local development. Native FFI or a custom Python backend is better when several workers or deployment environments need shared session state. -### VoIP: Audio and Video Calls - -Tryx provides a Rust-backed bridge for WhatsApp audio and video calls. After the client is connected, use client.voip. Protocol handling, RTP/WebRTC, encryption, codecs, and call orchestration run in whatsapp-rust; Python supplies media through asynchronous source and sink adapters. +## VoIP: Audio and Video Calls -#### Features +Tryx provides a Rust-backed bridge for WhatsApp audio and video calls. Protocol handling, RTP/WebRTC, encryption, codecs, and call orchestration run in whatsapp-rust; Python supplies media through asynchronous source and sink adapters. -- One-to-one audio calls through voip.call(). -- One-to-one video calls through voip.video_call(). -- Group calls with optional video through voip.group_call(). -- Call-link joining through voip.join_call_link() with audio or video media. -- Hangup, wait, mute/unmute, video, participant, approval, and screen-sharing controls. -- Native Rust playback for WAV, MP3, OGG, Vorbis, and PCM audio files. -- Native FFmpeg playback producing H.264 Annex-B video access units. -- Python adapters for microphones, speakers, cameras, codecs, TTS, DSP, and custom pipelines. +### Media Contract -#### Media Contract - -| Media | Kontrak | +| Media | Contract | | --- | --- | | Audio | Mono signed PCM16 little-endian, 16,000 Hz, 960 samples / 1,920 bytes per frame, 60 ms | -| Video | H.264 Annex-B access unit represented by VideoFrame | - -AudioSource.frames() must yield exactly 1,920 bytes per frame. AudioSink.write() receives the same format. VideoFrame carries data, timestamp_us, duration_us, keyframe, optional dimensions, and orientation. +| Video | H.264 Annex-B access unit represented by `VideoFrame` | -#### Minimal Audio Adapters +### Minimal Audio Adapters ```python from collections.abc import AsyncIterator @@ -164,9 +148,7 @@ class Speaker(AudioSink): await play_speaker_frame(frame) ``` -Replace the two backend functions with PyAudio, sounddevice, ALSA, CoreAudio, or another device library. - -#### One-to-One Audio Call +### One-to-One Audio Call ```python from tryx.types import JID @@ -180,9 +162,9 @@ async def start_audio_call(client, phone_number: str): call.set_muted(False) await call.wait_ended() ``` -Use await call.hangup() to end a call explicitly. Keep the CallHandle alive and await wait_ended() for deterministic cleanup. -#### Video Call +### Video Call + ```python from tryx.media import VideoPlayer @@ -196,9 +178,9 @@ async def start_video_call(client, peer, video_sink): await call.wait_ended() video_source.stop() ``` -VideoPlayer accepts 1–60 FPS and defaults to 15. FFmpeg must be in PATH; missing files and missing FFmpeg produce explicit errors. -#### Group Calls and Call Links +### Group Calls and Call Links + ```python call = await client.voip.group_call( peers=[peer_a, peer_b], @@ -207,7 +189,7 @@ call = await client.voip.group_call( ) await call.invite_participant(peer_c) await call.ring_participant(peer_c) -await call.set_approval_required(True) + linked = await client.voip.join_call_link( "https://call.whatsapp.com/your-token", "audio", @@ -215,25 +197,9 @@ linked = await client.voip.join_call_link( Speaker(), ) ``` -group_call() accepts optional video source/sink pairs. join_call_link() accepts a token or URL and media audio or video. - -#### Incoming Calls - -IncomingCallEvent exposes call_id, peer, is_video, accept(audio_source, audio_sink), and reject(). Accept or reject it only once because the invitation is consumed. -```python -@app.on(EvIncomingCall) -async def on_incoming_call(_client, event): - if event.is_video: - await event.reject() - return - call = await event.accept(Microphone(), Speaker()) - await call.wait_ended() -``` -Use the incoming-call event exported by the installed package version. -#### Native AudioPlayer +### Native AudioPlayer -AudioPlayer decodes files in Rust and normalizes them to mono PCM16 at 16 kHz. ```python from tryx.media import AudioPlayer @@ -247,42 +213,18 @@ player.skip() player.clear_queue() player.stop() ``` -Modes are replace, queue, and interrupt. buffer_frames defaults to 3 and is capped at 30. States are idle, playing, and paused; non-16 kHz audio uses linear interpolation. - -#### Internal Data Flow and Backpressure - - Python source/sink -> PyO3 bridge + bounded async channel - -> whatsapp-rust VoIP facade - -> WaCore call engine -> RTP/SRTP/WebRTC - -> WhatsApp call network - -Inbound media follows the reverse path: transport decrypts and decodes, the bridge creates PCM16 or VideoFrame, and the Python sink receives it. Bounded channels prevent unbounded queues; audio defaults to three frames, about 180 ms before network and codec overhead. Slow sinks cause asynchronous backpressure instead of CPU spin-waiting. -Audio commands wait for the manager result and propagate errors to Python. VideoPlayer.stop() cancels its FFmpeg task, which kills and reaps the child process instead of leaving an orphan. - -#### Lifecycle and Troubleshooting - -- Start calls only after Tryx is connected; use one native player per active call. -- Stop players when cancelling a call and release them after wait_ended(). -- Send only mono PCM16 16 kHz frames of exactly 1,920 bytes. -- Video sources must emit H.264 Annex-B, not an MP4 container. -- For invalid audio sizes, chunk or resample into 960 samples. -- For high latency, use buffer_frames=2 or 3. -- Install FFmpeg and verify it with ffmpeg -version when video playback fails. - -### Development - -Useful commands: +## Development ```bash cargo check cargo test --lib -env UV_CACHE_DIR=/tmp/uv-cache uv run pytest -q +uv run pytest -q uv run maturin develop uv run maturin build --release ``` -Project layout: +## Project Layout ```text . @@ -294,579 +236,13 @@ Project layout: └── tests/ # Python test suite ``` -### Links +## Links -- Documentation: +- Documentation: - Examples: [`examples/`](examples/) - Python type stubs: [`python/tryx/`](python/tryx/) - Rust bindings: [`src/`](src/) ---- - -## 简体中文 - -### Tryx 是什么? - -Tryx 是一个基于 Rust 和 PyO3 的 Python WhatsApp 自动化库。底层协议能力来自 `whatsapp-rust` crate,Python 侧提供更易用的异步 API,适合构建机器人、后台任务、内部工具和自动化服务。 - -Tryx 适合以下场景: - -- 使用 Python 操作 WhatsApp Web。 -- 通过 SQLite、PostgreSQL 或 MySQL 持久化登录会话。 -- 在 `asyncio` 项目中构建机器人或自动化流程。 -- 使用联系人、群组、频道、状态、隐私、资料、标签、评论、事件和高级协议能力。 - -### 主要特点 - -- **Rust 核心,Python 调用** - 协议逻辑在 Rust 中运行,Python 通过 PyO3 调用。 -- **异步优先** - 适合 `asyncio` 应用。 -- **会话持久化** - 可以复用已登录的 WhatsApp 设备会话。 -- **API 覆盖面更广** - 暴露常用功能和高级功能命名空间。 -- **类型提示支持** - 提供 `.pyi` 文件,提升编辑器补全体验。 - -### 安装 - -```bash -pip install tryx -``` - -本地开发: - -```bash -git clone https://github.com/krypton-byte/tryx.git -cd tryx -git submodule update --init --recursive -uv sync --group dev -uv run maturin develop -``` - -### 快速开始 - -```python -import asyncio - -from tryx.backend import SqliteStore -from tryx.client import Tryx, TryxClient -from tryx.events import EvMessage, EvPairingQrCode - -backend = SqliteStore("whatsapp.db") -app = Tryx(backend) - - -@app.on(EvPairingQrCode) -async def on_pairing_qr(_client: TryxClient, event: EvPairingQrCode) -> None: - print("Scan this QR code with WhatsApp:") - print(event.code) - - -@app.on(EvMessage) -async def on_message(client: TryxClient, event: EvMessage) -> None: - text = (event.data.get_text() or "").strip() - if text.lower() == "ping": - await client.send_text( - event.data.message_info.source.chat, "pong", quoted=event - ) - - -async def main() -> None: - await app.run() - - -asyncio.run(main()) -``` - -### Client 命名空间 - -运行时 `TryxClient` 对象按功能拆分为多个命名空间: - -| 命名空间 | 用途 | -| --- | --- | -| `contact` | 联系人查询和联系人相关工具。 | -| `chat_actions` | 聊天操作,例如静音、归档、置顶、标记已读、清空聊天和保存联系人。 | -| `groups` | 群组创建、信息、成员、邀请、入群请求和管理员设置。 | -| `community` | 社区相关操作。 | -| `newsletter` | 频道信息、消息、静音设置、编辑和撤回。 | -| `status` | 状态发布、查询和相关工具。 | -| `chatstate` | 输入中、录音中、暂停等聊天状态。 | -| `blocking` | 拉黑和取消拉黑联系人。 | -| `polls` | 投票创建和投票操作。 | -| `presence` | 在线状态订阅和可用性更新。 | -| `privacy` | 隐私设置和控制。 | -| `profile` | 资料名称、状态文本和头像操作。 | -| `labels` | 聊天和消息标签管理。 | -| `comments` | 评论相关协议工具。 | -| `events` | 事件响应和事件相关操作。 | -| `advanced` | 生命周期等待、诊断和更底层的协议能力。 | - -### 存储后端 - -Tryx 支持三种会话存储方式: - -| 类型 | 后端 | 使用场景 | -| --- | --- | --- | -| 内置 | `SqliteStore("whatsapp.db")` | 本地开发和简单部署。 | -| Native FFI | `FfiStoreProtocol` | 高吞吐外部存储,例如 PostgreSQL。 | -| 纯 Python | `StoreBase` 子类 | 自定义异步存储,例如 Redis、MongoDB 或 DynamoDB。 | - -本地开发通常使用 SQLite 即可。如果多个 worker 或部署环境需要共享会话状态,建议使用 Native FFI 或自定义 Python 后端。 - -### VoIP:音频和视频通话 - -Tryx 提供基于 Rust 的 WhatsApp 音频和视频通话桥接层。客户端连接后,可以通过 client.voip 使用 API。协议处理、RTP/WebRTC、加密、编解码器和通话编排由 whatsapp-rust 执行;Python 通过异步 source 和 sink adapter 提供媒体数据。 - -#### 功能 - -- 使用 voip.call() 发起一对一音频通话。 -- 使用 voip.video_call() 发起一对一视频通话。 -- 使用 voip.group_call() 发起带可选视频的群组通话。 -- 使用 voip.join_call_link() 加入音频或视频 call link。 -- 支持挂断、等待结束、静音/取消静音、开始/停止视频。 -- 支持邀请、响铃、参与者审批,以及允许或拒绝等待中的用户。 -- 支持 start_screen_share() 和 stop_screen_share() 屏幕共享。 -- Rust 原生播放 WAV、MP3、OGG、Vorbis 和 PCM 音频文件。 -- 通过 FFmpeg 原生播放视频并输出 H.264 Annex-B access unit。 -- 支持用于麦克风、扬声器、摄像头、编解码器、TTS、DSP 和自定义媒体管线的 Python adapter。 - -#### 媒体契约 - -| 媒体 | 契约 | -| --- | --- | -| 音频 | 单声道 signed PCM16 little-endian,16,000 Hz,每帧 960 个 sample / 1,920 字节,60 ms | -| 视频 | 通过 VideoFrame 表示的 H.264 Annex-B access unit | - -AudioSource.frames() 必须每次产生恰好 1,920 字节。AudioSink.write() 接收相同的 PCM 格式。VideoFrame 包含 data、timestamp_us、duration_us、keyframe、可选的 width 和 height,以及 orientation。 - -#### 最小音频 Adapter -```python -from collections.abc import AsyncIterator -from tryx.media import AudioSink, AudioSource, validate_audio_frame - - -class Microphone(AudioSource): - async def frames(self) -> AsyncIterator[bytes]: - while True: - frame = await read_microphone_frame() - yield validate_audio_frame(frame) - - -class Speaker(AudioSink): - async def write(self, frame: bytes) -> None: - validate_audio_frame(frame) - await play_speaker_frame(frame) -``` -请将两个设备函数替换为 PyAudio、sounddevice、ALSA、CoreAudio 或其他音频设备库的实现。 - -#### 一对一音频通话 - -```python -from tryx.types import JID - - -async def start_audio_call(client, phone_number: str): - peer = JID(phone_number + "@s.whatsapp.net") - call = await client.voip.call(peer, Microphone(), Speaker()) - print("call started:", call.call_id) - call.set_muted(True) - call.set_muted(False) - await call.wait_ended() -``` -使用 await call.hangup() 主动结束通话。通话期间请保持 CallHandle,并等待 wait_ended(),以便媒体资源能够稳定清理。 - -#### 视频通话 -```python -from tryx.media import VideoPlayer - - -async def start_video_call(client, peer, video_sink): - video_source = VideoPlayer(fps=15) - video_source.play("sample.mp4") - call = await client.voip.video_call( - peer, Microphone(), Speaker(), video_source, video_sink - ) - await call.wait_ended() - video_source.stop() -``` -VideoPlayer 支持 1–60 FPS,默认值为 15。系统必须在 PATH 中提供 FFmpeg;文件不存在或 FFmpeg 不可用时,play() 会返回明确错误。 - -#### 群组通话和 Call Link -```python -call = await client.voip.group_call( - peers=[peer_a, peer_b], - audio_source=Microphone(), - audio_sink=Speaker(), -) -await call.invite_participant(peer_c) -await call.ring_participant(peer_c) -await call.set_approval_required(True) -linked = await client.voip.join_call_link( - "https://call.whatsapp.com/your-token", - "audio", - Microphone(), - Speaker(), -) -``` -group_call() 可以接收可选的 video source/sink。join_call_link() 可以接收 token 或 URL,并使用 media="audio" 或 media="video"。 - -#### 来电 - -IncomingCallEvent 提供 call_id、peer、is_video、accept(audio_source, audio_sink) 和 reject()。一个 event 只能 accept 或 reject 一次,因为底层来电邀请在操作后会被消费。 -```python -@app.on(EvIncomingCall) -async def on_incoming_call(_client, event): - if event.is_video: - await event.reject() - return - call = await event.accept(Microphone(), Speaker()) - await call.wait_ended() -``` -请使用当前安装版本导出的来电 event 名称。 - -#### Rust 原生 AudioPlayer - -AudioPlayer 在 Rust 中解码文件,并将其标准化为 16 kHz 单声道 PCM16。 -```python -from tryx.media import AudioPlayer - -player = AudioPlayer(buffer_frames=3) -player.play("intro.mp3", mode="replace") -call = await client.voip.call(peer, player, Speaker()) -player.pause() -player.resume() -player.enqueue("next.wav") -player.skip() -player.clear_queue() -player.stop() -``` -播放模式为 replace、queue 和 interrupt。buffer_frames 默认 3,最大限制为 30。播放器状态为 idle、playing 和 paused。非 16 kHz 音频使用线性插值转换。 - -#### 内部数据流和背压 - - Python source/sink -> PyO3 bridge + bounded async channel - -> whatsapp-rust VoIP facade - -> WaCore call engine -> RTP/SRTP/WebRTC - -> WhatsApp call network - -接收媒体沿反方向流动:transport 解密并解码,bridge 将数据转换为 PCM16 或 VideoFrame,再交给 Python sink。bounded channel 防止队列无限增长;音频默认缓存 3 帧,即在网络和 codec 开销之前约 180 ms。sink 较慢时 producer 会异步等待,而不是消耗 CPU 自旋等待。 - -AudioPlayer command 会等待 manager 的结果,并将错误传递给 Python。VideoPlayer.stop() 会取消 FFmpeg task,由该 task kill 并回收 child process,避免留下孤儿进程。 - -#### 生命周期和故障排查 - -- 只有在 Tryx 连接成功后才开始通话;每个活动通话使用一个 native player。 -- 取消通话时停止 player,并在 wait_ended() 后释放资源。 -- 只能发送恰好 1,920 字节的单声道 PCM16 16 kHz 音频帧。 -- 视频 source 必须输出 H.264 Annex-B,不能直接输出 MP4 container。 -- 音频尺寸错误时,将音频切分或重采样为 960 samples。 -- 延迟过高时使用 buffer_frames=2 或 3。 -- 视频播放失败时安装 FFmpeg,并使用 ffmpeg -version 验证 PATH。 - -### 开发命令 - -```bash -cargo check -cargo test --lib -env UV_CACHE_DIR=/tmp/uv-cache uv run pytest -q -uv run maturin develop -uv run maturin build --release -``` - -### 相关链接 - -- 文档: -- 示例:[`examples/`](examples/) -- Python 类型文件:[`python/tryx/`](python/tryx/) -- Rust 绑定代码:[`src/`](src/) - ---- - -## Bahasa Indonesia - -### Apa Itu Tryx? - -Tryx adalah library otomasi WhatsApp untuk Python yang dibangun dengan Rust dan PyO3. Logika protokol berjalan di Rust melalui `whatsapp-rust`, sedangkan API publiknya dibuat agar nyaman digunakan dari Python. - -Tryx cocok untuk: - -- Membuat otomasi WhatsApp Web dari Python. -- Menyimpan sesi login dengan SQLite, PostgreSQL, atau MySQL. -- Membangun bot, worker, dashboard, dan tool internal berbasis `asyncio`. -- Mengakses fitur kontak, grup, newsletter, status, privasi, profil, label, komentar, event, dan helper protokol tingkat lanjut. - -### Fitur Utama - -- **Core Rust, API Python** - performa dan logika protokol ditangani Rust, pemakaian tetap sederhana dari Python. -- **Async first** - dirancang untuk aplikasi `asyncio`. -- **Sesi persisten** - sesi WhatsApp dapat dipakai ulang melalui database. -- **API luas** - namespace umum dan advanced sudah diexpose. -- **Type stub Python** - tersedia `.pyi` untuk autocomplete dan type checking yang lebih baik. - -### Instalasi - -```bash -pip install tryx -``` - -Untuk development lokal: - -```bash -git clone https://github.com/krypton-byte/tryx.git -cd tryx -git submodule update --init --recursive -uv sync --group dev -uv run maturin develop -``` - -### Mulai Cepat - -```python -import asyncio - -from tryx.backend import SqliteStore -from tryx.client import Tryx, TryxClient -from tryx.events import EvMessage, EvPairingQrCode - -backend = SqliteStore("whatsapp.db") -app = Tryx(backend) - - -@app.on(EvPairingQrCode) -async def on_pairing_qr(_client: TryxClient, event: EvPairingQrCode) -> None: - print("Scan this QR code with WhatsApp:") - print(event.code) - - -@app.on(EvMessage) -async def on_message(client: TryxClient, event: EvMessage) -> None: - text = (event.data.get_text() or "").strip() - if text.lower() == "ping": - await client.send_text( - event.data.message_info.source.chat, "pong", quoted=event - ) - - -async def main() -> None: - await app.run() - - -asyncio.run(main()) -``` - -### Namespace Client - -Objek runtime `TryxClient` dibagi menjadi beberapa namespace agar API lebih rapi: - -| Namespace | Fungsi | -| --- | --- | -| `contact` | Helper untuk kontak dan pencarian kontak. | -| `chat_actions` | Operasi chat seperti mute, archive, pin, mark read, clear chat, dan save contact. | -| `groups` | Pembuatan grup, metadata, member, invite, request membership, dan pengaturan admin. | -| `community` | Operasi terkait komunitas. | -| `newsletter` | Info newsletter, pesan, mute, edit, dan revoke. | -| `status` | Posting status, lookup status, dan helper terkait status. | -| `chatstate` | State chat seperti typing, recording, dan paused. | -| `blocking` | Block dan unblock kontak. | -| `polls` | Membuat poll dan mengirim vote. | -| `presence` | Subscribe presence dan update availability. | -| `privacy` | Pengaturan privasi. | -| `profile` | Nama profil, status teks, dan foto profil. | -| `labels` | Manajemen label untuk chat dan pesan. | -| `comments` | Helper protokol terkait komentar. | -| `events` | Helper response dan operasi berbasis event. | -| `advanced` | Wait lifecycle, diagnostik, dan akses protokol tingkat lanjut. | - -### Storage Backend - -Tryx mendukung tiga tipe storage untuk menyimpan sesi WhatsApp: - -| Tipe | Backend | Kegunaan | -| --- | --- | --- | -| Built-in | `SqliteStore("whatsapp.db")` | Development lokal dan deployment sederhana. | -| Native FFI | `FfiStoreProtocol` | Store eksternal throughput tinggi seperti PostgreSQL. | -| Pure Python | subclass `StoreBase` | Store async custom seperti Redis, MongoDB, atau DynamoDB. | - -SQLite cukup untuk development lokal. Native FFI atau backend Python custom lebih cocok jika sesi perlu dipakai bersama oleh beberapa worker atau environment deployment. - -### VoIP: Panggilan Audio dan Video - -Tryx menyediakan bridge VoIP berbasis Rust untuk panggilan audio dan video WhatsApp. Setelah client tersambung, API tersedia melalui client.voip. Protocol, RTP/WebRTC, enkripsi, codec, dan orkestrasi call berjalan di whatsapp-rust; Python menyediakan media melalui adapter source dan sink asynchronous. - -#### Fitur - -- Panggilan audio 1:1 melalui voip.call(). -- Panggilan video 1:1 melalui voip.video_call(). -- Group call dengan video opsional melalui voip.group_call(). -- Bergabung ke call link melalui voip.join_call_link() dengan media audio atau video. -- Kontrol hangup, wait, mute/unmute, video, peserta, approval, dan screen sharing. -- Playback file audio native Rust untuk WAV, MP3, OGG, Vorbis, dan PCM. -- Playback video native melalui FFmpeg dengan output H.264 Annex-B access unit. -- Adapter Python untuk microphone, speaker, camera, codec, TTS, DSP, dan pipeline media custom. - -#### Kontrak Media - -| Media | Kontrak | -| --- | --- | -| Audio | Mono signed PCM16 little-endian, 16.000 Hz, 960 sample / 1.920 byte per frame, 60 ms | -| Video | H.264 Annex-B access unit melalui VideoFrame | - -AudioSource.frames() harus menghasilkan async iterator dengan tepat 1.920 byte untuk setiap frame. AudioSink.write() menerima format PCM yang sama. VideoFrame berisi data, timestamp_us, duration_us, keyframe, dimensi opsional, dan orientation. - -#### Adapter Audio Minimal -```python -from collections.abc import AsyncIterator -from tryx.media import AudioSink, AudioSource, validate_audio_frame - - -class Microphone(AudioSource): - async def frames(self) -> AsyncIterator[bytes]: - while True: - frame = await read_microphone_frame() - yield validate_audio_frame(frame) - - -class Speaker(AudioSink): - async def write(self, frame: bytes) -> None: - validate_audio_frame(frame) - await play_speaker_frame(frame) -``` -Ganti fungsi perangkat tersebut dengan implementasi PyAudio, sounddevice, ALSA, CoreAudio, atau library perangkat lain. - -#### Panggilan Audio 1:1 -```python -from tryx.types import JID - - -async def start_audio_call(client, phone_number: str): - peer = JID(phone_number + "@s.whatsapp.net") - call = await client.voip.call(peer, Microphone(), Speaker()) - print("call started:", call.call_id) - call.set_muted(True) - call.set_muted(False) - await call.wait_ended() -``` -Gunakan await call.hangup() untuk mengakhiri call secara eksplisit. Pertahankan CallHandle selama call aktif dan tunggu wait_ended() agar cleanup media berjalan deterministik. - -#### Panggilan Video -```python -from tryx.media import VideoPlayer - - -async def start_video_call(client, peer, video_sink): - video_source = VideoPlayer(fps=15) - video_source.play("sample.mp4") - call = await client.voip.video_call( - peer, Microphone(), Speaker(), video_source, video_sink - ) - await call.wait_ended() - video_source.stop() -``` -VideoPlayer mendukung 1–60 FPS dengan default 15. FFmpeg harus tersedia di PATH. File yang tidak ditemukan atau FFmpeg yang tidak tersedia akan menghasilkan error yang jelas. - -#### Group Call dan Call Link -```python - call = await client.voip.group_call( - peers=[peer_a, peer_b], - audio_source=Microphone(), - audio_sink=Speaker(), - ) - await call.invite_participant(peer_c) - await call.ring_participant(peer_c) - await call.set_approval_required(True) - - linked = await client.voip.join_call_link( - "https://call.whatsapp.com/your-token", - "audio", - Microphone(), - Speaker(), - ) -``` -group_call() menerima video source dan sink secara opsional. join_call_link() menerima token atau URL dengan media audio atau video. - -#### Panggilan Masuk - -IncomingCallEvent menyediakan call_id, peer, is_video, accept(audio_source, audio_sink), dan reject(). Event hanya boleh di-accept atau reject satu kali karena invitation akan dikonsumsi. -```python - @app.on(EvIncomingCall) - async def on_incoming_call(_client, event): - if event.is_video: - await event.reject() - return - call = await event.accept(Microphone(), Speaker()) - await call.wait_ended() -``` -Gunakan nama event incoming-call yang diexport oleh versi package yang terpasang. - -#### AudioPlayer Native - -AudioPlayer mendecode file di Rust dan menormalisasikannya menjadi mono PCM16 16 kHz. -```python - from tryx.media import AudioPlayer - - player = AudioPlayer(buffer_frames=3) - player.play("intro.mp3", mode="replace") - call = await client.voip.call(peer, player, Speaker()) - player.pause() - player.resume() - player.enqueue("next.wav") - player.skip() - player.clear_queue() - player.stop() -``` -Mode playback adalah replace, queue, dan interrupt. buffer_frames default 3 dan dibatasi maksimal 30 frame. State player adalah idle, playing, atau paused. Audio non-16 kHz diproses dengan interpolasi linear. - -#### Alur Internal dan Backpressure - - Python source/sink -> PyO3 bridge + bounded async channel - -> whatsapp-rust VoIP facade - -> WaCore call engine -> RTP/SRTP/WebRTC - -> WhatsApp call network - -Media masuk berjalan terbalik: transport mendekripsi dan mendecode, bridge mengubahnya menjadi PCM16 atau VideoFrame, kemudian sink Python menerimanya. Bounded channel mencegah queue tumbuh tanpa batas. Buffer audio default 3 frame, sekitar 180 ms sebelum overhead jaringan dan codec. Jika sink lambat, producer menunggu secara asynchronous tanpa spin-wait CPU. - -Command AudioPlayer menunggu hasil manager dan meneruskan error ke Python. VideoPlayer.stop() membatalkan task FFmpeg; task tersebut membunuh dan mereap child process agar tidak meninggalkan proses yatim. - -#### Lifecycle dan Troubleshooting - -- Mulai call setelah Tryx tersambung dan gunakan satu native player untuk setiap call aktif. -- Hentikan player saat call dibatalkan dan lepaskan setelah wait_ended(). -- Kirim hanya frame mono PCM16 16 kHz dengan ukuran tepat 1.920 byte. -- Video source harus menghasilkan H.264 Annex-B, bukan MP4 container. -- Jika ukuran audio salah, lakukan chunk atau resample menjadi 960 sample. -- Jika latency tinggi, gunakan buffer_frames=2 atau 3. -- Jika video gagal, instal FFmpeg dan verifikasi dengan ffmpeg -version. - -### Development - -Command yang umum dipakai: - -```bash -cargo check -cargo test --lib -env UV_CACHE_DIR=/tmp/uv-cache uv run pytest -q -uv run maturin develop -uv run maturin build --release -``` - -Struktur project: - -```text -. -├── libs/whatsapp-rust/ # Submodule crate protokol Rust -├── src/ # Binding Rust dan PyO3 -├── python/tryx/ # Package Python dan type stub -├── examples/ # Contoh script otomasi -├── docs/ # Situs dokumentasi -└── tests/ # Test suite Python -``` - -### Link Penting - -- Dokumentasi: -- Contoh: [`examples/`](examples/) -- Type stub Python: [`python/tryx/`](python/tryx/) -- Binding Rust: [`src/`](src/) - ---- - ## License This project is licensed under the terms of the [MIT License](LICENSE). diff --git a/README.zh.md b/README.zh.md new file mode 100644 index 0000000..bc51c1e --- /dev/null +++ b/README.zh.md @@ -0,0 +1,248 @@ +# Tryx + +[![PyPI version](https://img.shields.io/pypi/v/tryx?color=blue)](https://pypi.org/project/tryx/) +[![Python](https://img.shields.io/pypi/pyversions/tryx.svg)](https://pypi.org/project/tryx/) +[![License](https://img.shields.io/github/license/krypton-byte/tryx)](LICENSE) +[![Docs](https://img.shields.io/badge/docs-online-brightgreen)](https://krypton-byte.github.io/tryx/) + +**语言:** [English](README.md) | 简体中文 | [Bahasa Indonesia](README.id.md) + +Tryx 是一个基于 Rust 和 PyO3 的 Python WhatsApp 自动化库。底层协议能力来自 `whatsapp-rust` crate,Python 侧提供更易用的异步 API,适合构建机器人、后台任务、内部工具和自动化服务。 + +> Tryx 是一个独立项目,与 WhatsApp、Meta 或其官方产品无关。 + +## Tryx 是什么? + +Tryx 帮助 Python 开发者构建 WhatsApp 自动化工具,无需直接编写 Rust 代码。核心协议逻辑在 Rust 中运行以保证性能,而公共接口则保持对 Python 应用的友好性。 + +Tryx 适合以下场景: + +- 使用 Python 操作 WhatsApp Web。 +- 通过 SQLite、PostgreSQL 或 MySQL 持久化登录会话。 +- 在 `asyncio` 项目中构建机器人或自动化流程。 +- 使用联系人、群组、频道、状态、隐私、资料、标签、评论、事件和高级协议能力。 + +## 主要特点 + +- **Rust 核心,Python 调用** — 协议逻辑在 Rust 中运行,Python 通过 PyO3 调用。 +- **异步优先** — 适合 `asyncio` 应用。 +- **会话持久化** — 可以复用已登录的 WhatsApp 设备会话。 +- **API 覆盖面更广** — 暴露常用功能和高级功能命名空间。 +- **类型提示支持** — 提供 `.pyi` 文件,提升编辑器补全体验。 + +## 安装 + +```bash +pip install tryx +``` + +本地开发: + +```bash +git clone https://github.com/krypton-byte/tryx.git +cd tryx +git submodule update --init --recursive +uv sync --group dev +uv run maturin develop +``` + +## 快速开始 + +```python +import asyncio + +from tryx.backend import SqliteStore +from tryx.client import Tryx, TryxClient +from tryx.events import EvMessage, EvPairingQrCode + +backend = SqliteStore("whatsapp.db") +app = Tryx(backend) + + +@app.on(EvPairingQrCode) +async def on_pairing_qr(_client: TryxClient, event: EvPairingQrCode) -> None: + print("请用 WhatsApp 扫描此二维码:") + print(event.code) + + +@app.on(EvMessage) +async def on_message(client: TryxClient, event: EvMessage) -> None: + text = (event.data.get_text() or "").strip() + if text.lower() == "ping": + await client.send_text( + event.data.message_info.source.chat, "pong", quoted=event + ) + + +async def main() -> None: + await app.run() + + +asyncio.run(main()) +``` + +## Client 命名空间 + +运行时 `TryxClient` 对象按功能拆分为多个命名空间: + +| 命名空间 | 用途 | +| --- | --- | +| `contact` | 联系人查询和联系人相关工具。 | +| `chat_actions` | 聊天操作,例如静音、归档、置顶、标记已读、清空聊天和保存联系人。 | +| `groups` | 群组创建、信息、成员、邀请、入群请求和管理员设置。 | +| `community` | 社区相关操作。 | +| `newsletter` | 频道信息、消息、静音设置、编辑和撤回。 | +| `status` | 状态发布、查询和相关工具。 | +| `chatstate` | 输入中、录音中、暂停等聊天状态。 | +| `blocking` | 拉黑和取消拉黑联系人。 | +| `polls` | 投票创建和投票操作。 | +| `presence` | 在线状态订阅和可用性更新。 | +| `privacy` | 隐私设置和控制。 | +| `profile` | 资料名称、状态文本和头像操作。 | +| `labels` | 聊天和消息标签管理。 | +| `comments` | 评论相关协议工具。 | +| `events` | 事件响应和事件相关操作。 | +| `advanced` | 生命周期等待、诊断和更底层的协议能力。 | +| `voip` | 音频和视频通话管理。 | + +## 存储后端 + +Tryx 支持三种会话存储方式: + +| 类型 | 后端 | 使用场景 | +| --- | --- | --- | +| 内置 | `SqliteStore("whatsapp.db")` | 本地开发和简单部署。 | +| Native FFI | `FfiStoreProtocol` | 高吞吐外部存储,例如 PostgreSQL。 | +| 纯 Python | `StoreBase` 子类 | 自定义异步存储,例如 Redis、MongoDB 或 DynamoDB。 | + +本地开发通常使用 SQLite 即可。如果多个 worker 或部署环境需要共享会话状态,建议使用 Native FFI 或自定义 Python 后端。 + +## VoIP:音频和视频通话 + +Tryx 提供基于 Rust 的 WhatsApp 音频和视频通话桥接层。协议处理、RTP/WebRTC、加密、编解码器和通话编排由 whatsapp-rust 执行;Python 通过异步 source 和 sink adapter 提供媒体数据。 + +### 媒体契约 + +| 媒体 | 契约 | +| --- | --- | +| 音频 | 单声道 signed PCM16 little-endian,16,000 Hz,每帧 960 个 sample / 1,920 字节,60 ms | +| 视频 | 通过 `VideoFrame` 表示的 H.264 Annex-B access unit | + +### 最小音频 Adapter + +```python +from collections.abc import AsyncIterator +from tryx.media import AudioSink, AudioSource, validate_audio_frame + + +class Microphone(AudioSource): + async def frames(self) -> AsyncIterator[bytes]: + while True: + frame = await read_microphone_frame() + yield validate_audio_frame(frame) + + +class Speaker(AudioSink): + async def write(self, frame: bytes) -> None: + validate_audio_frame(frame) + await play_speaker_frame(frame) +``` + +### 一对一音频通话 + +```python +from tryx.types import JID + + +async def start_audio_call(client, phone_number: str): + peer = JID(phone_number + "@s.whatsapp.net") + call = await client.voip.call(peer, Microphone(), Speaker()) + print("通话已开始:", call.call_id) + call.set_muted(True) + call.set_muted(False) + await call.wait_ended() +``` + +### 视频通话 + +```python +from tryx.media import VideoPlayer + + +async def start_video_call(client, peer, video_sink): + video_source = VideoPlayer(fps=15) + video_source.play("sample.mp4") + call = await client.voip.video_call( + peer, Microphone(), Speaker(), video_source, video_sink + ) + await call.wait_ended() + video_source.stop() +``` + +### 群组通话和 Call Link + +```python +call = await client.voip.group_call( + peers=[peer_a, peer_b], + audio_source=Microphone(), + audio_sink=Speaker(), +) +await call.invite_participant(peer_c) +await call.ring_participant(peer_c) + +linked = await client.voip.join_call_link( + "https://call.whatsapp.com/your-token", + "audio", + Microphone(), + Speaker(), +) +``` + +### Rust 原生 AudioPlayer + +```python +from tryx.media import AudioPlayer + +player = AudioPlayer(buffer_frames=3) +player.play("intro.mp3", mode="replace") +call = await client.voip.call(peer, player, Speaker()) +player.pause() +player.resume() +player.enqueue("next.wav") +player.skip() +player.clear_queue() +player.stop() +``` + +## 开发命令 + +```bash +cargo check +cargo test --lib +uv run pytest -q +uv run maturin develop +uv run maturin build --release +``` + +## 项目结构 + +```text +. +├── libs/whatsapp-rust/ # Rust 协议 crate 子模块 +├── src/ # Rust 和 PyO3 绑定 +├── python/tryx/ # Python 包和类型存根 +├── examples/ # 示例自动化脚本 +├── docs/ # 文档站点 +└── tests/ # Python 测试套件 +``` + +## 相关链接 + +- 文档: +- 示例:[`examples/`](examples/) +- Python 类型文件:[`python/tryx/`](python/tryx/) +- Rust 绑定代码:[`src/`](src/) + +## 许可证 + +本项目基于 [MIT 许可证](LICENSE) 授权。 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..121947c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,63 @@ +# Security Policy + +## Reporting a Vulnerability + +If you discover a security vulnerability in Tryx, please report it +responsibly. **Do not open a public GitHub issue for security vulnerabilities.** + +### How to Report + +1. **Email**: Send a detailed report to + [krypton-byte](https://github.com/krypton-byte) via GitHub's private + vulnerability reporting feature, or open a + [Security Advisory](https://github.com/krypton-byte/tryx/security/advisories/new). + +2. **Include**: + - Description of the vulnerability + - Steps to reproduce + - Potential impact assessment + - Any suggested fix (if available) + +3. **Response time**: We aim to acknowledge reports within **72 hours**. + +## Scope + +The following are considered in-scope for security reports: + +- **Protocol handling** — Rust core logic for Signal protocol, encryption, + and key exchange +- **PyO3 bridge** — Type conversion, GIL management, and error marshaling + between Rust and Python +- **Session storage** — SQLite, FFI, and Python store backends +- **Media processing** — Upload, download, and transcoding paths +- **Authentication** — Pairing, session persistence, and credential handling + +## Out of Scope + +- Issues in third-party dependencies (report upstream) +- Denial of service against WhatsApp servers +- Social engineering attacks +- Issues requiring physical access to the target device + +## Disclosure Policy + +- We follow a **coordinated disclosure** process. +- We will work with you to understand and address the issue before any + public disclosure. +- Once a fix is released, we will publicly acknowledge the vulnerability + and credit the reporter (unless anonymity is requested). + +## Supported Versions + +| Version | Supported | +| --- | --- | +| 1.x | ✅ Active | +| 0.x | ❌ End of life | + +## Best Practices for Users + +- Keep Tryx updated to the latest version. +- Use environment variables or a secrets manager for sensitive configuration. +- Restrict admin-level bot commands to authorized users. +- Monitor `EvTemporaryBan` events for rate-limit violations. +- Use a dedicated WhatsApp account for automation (not your personal account). diff --git a/docs/api/backend.md b/docs/api/backend.md index 6b9abd4..91a5645 100644 --- a/docs/api/backend.md +++ b/docs/api/backend.md @@ -42,17 +42,19 @@ app = Tryx(backend) ## FfiStoreProtocol Structural typing protocol for native FFI-based storage backends. Any object -exposing `lib_path` and `connect_string` attributes satisfies this protocol. +exposing `lib_path` and `config_json` attributes satisfies this protocol. ```python +import json + class PostgresStore: lib_path: str # path to compiled .so - connect_string: str + config_json: str backend = PostgresStore( lib_path="./libtryx_pg.so", - connect_string="host=localhost dbname=tryx", + config_json=json.dumps({"host": "localhost", "dbname": "tryx"}), ) ``` diff --git a/docs/api/client.md b/docs/api/client.md index f7a9eb3..39c5d16 100644 --- a/docs/api/client.md +++ b/docs/api/client.md @@ -7,131 +7,134 @@ --- -`TryxClient` is the runtime facade passed to every handler, and it exposes a root messaging surface plus 12 namespace clients. +`TryxClient` is the runtime facade passed to every handler, and it exposes +a root messaging surface plus 12 namespace clients. -!!! tip "How To Read This Section" - 1. Start with this gateway page. - 2. Open the namespace page that matches your task. - 3. Jump to [Events API](events.md) for event contracts and [Types API](types.md) for enum/value-object constraints. +!!! tip "How to read this section" + 1. Start with this gateway page. + 2. Open the namespace page that matches your task. + 3. Jump to [Events API](events.md) for event contracts and + [Types API](types.md) for enum/value-object constraints. ## Client Topology ```mermaid flowchart TD - A[TryxClient] --> B[Root send/download/upload methods] - A --> C[contact] - A --> D[chat_actions] - A --> E[community] - A --> F[newsletter] - A --> G[groups] - A --> H[status] - A --> I[chatstate] - A --> J[blocking] - A --> K[polls] - A --> L[presence] - A --> M[privacy] - A --> N[profile] + A[TryxClient] --> B[Root send/download/upload methods] + A --> C[contact] + A --> D[chat_actions] + A --> E[community] + A --> F[newsletter] + A --> G[groups] + A --> H[status] + A --> I[chatstate] + A --> J[blocking] + A --> K[polls] + A --> L[presence] + A --> M[privacy] + A --> N[profile] ``` ## Namespace Router - + Use root send methods + `chat_actions` + `chatstate`. -## Root Transport Methods + 1. Parse incoming event. + 2. Signal typing with `client.chatstate.send_composing(chat)`. + 3. Send reply with `client.send_text(...)`. + 4. Optional message edit/revoke via `client.chat_actions`. -These methods stay on `TryxClient` directly because they are cross-domain primitives. - -| Method | Purpose | Typical usage | -| --- | --- | --- | -| `is_connected() -> bool` | Connection health check | Guard before sends in long-running jobs | -| `download_media(message) -> bytes` | Download media blob from message proto media node | Save image/audio/document payloads | -| `upload_file(path, media_type) -> UploadResponse` | Upload file path for later message/status usage | Status media workflows | -| `upload(data, media_type) -> UploadResponse` | Upload in-memory bytes | Transform pipelines | -| `send_message(to, message) -> SendResult` | Raw protobuf message send | Advanced custom payloads | -| `send_text(...) -> SendResult` | Text helper | Most command handlers | -| `send_photo(...) -> SendResult` | Image helper | Client replies with screenshots/posters | -| `send_document(...) -> SendResult` | File helper | Reports, exports, invoices | -| `send_audio(...) -> SendResult` | Audio helper | Voice notes / TTS | -| `send_video(...) -> SendResult` | Video helper | Clips, demos | -| `send_gif(...) -> SendResult` | GIF helper | Motion responses | -| `send_sticker(...) -> SendResult` | Sticker helper | Lightweight reactions | -| `request_media_reupload(...) -> MediaReuploadResult` | Recover stale media references | Retry failed media downloads | - -!!! warning "Reconnect-safe Pattern" - Avoid caching `TryxClient` on global module state across runtime restarts. - Always use the `client` object injected in the current handler call. - -## Practical Flow By Goal +=== "Moderation Client" -=== "Message Client" - Use root send methods + `chat_actions` + `chatstate`. + Use `groups`, `blocking`, `privacy`. - 1. Parse incoming event. - 2. Signal typing with `client.chatstate.send_composing(chat)`. - 3. Send reply with `client.send_text(...)`. - 4. Optional message edit/revoke via `client.chat_actions`. + 1. Resolve sender via [Types API](types.md). + 2. Apply participant actions (`promote`, `remove`, `approve request`). + 3. Enforce policy with blocklist/privacy settings. -=== "Moderation Client" - Use `groups`, `blocking`, `privacy`. +=== "Broadcast/Channel Client" - 1. Resolve sender via [Types API](types.md). - 2. Apply participant actions (`promote`, `remove`, `approve request`). - 3. Enforce policy with blocklist/privacy settings. + Use `status`, `newsletter`, `polls`. -=== "Broadcast/Channel Client" - Use `status`, `newsletter`, `polls`. + 1. Upload content or build text payload. + 2. Publish status/newsletter message. + 3. Track engagement using polls and reactions. - 1. Upload content or build text payload. - 2. Publish status/newsletter message. - 3. Track engagement using polls and reactions. +--- ## Cross-References diff --git a/docs/assets/stylesheets/extra.css b/docs/assets/stylesheets/extra.css index bfceb63..b2b7c75 100644 --- a/docs/assets/stylesheets/extra.css +++ b/docs/assets/stylesheets/extra.css @@ -1,302 +1,255 @@ -@import url("https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap"); +/* ═══════════════════════════════════════════════════════════════════════════ + Tryx Documentation — Zensical Modern Theme + Clean, professional design with teal accent and solid typography. + ═══════════════════════════════════════════════════════════════════════════ */ -/* ── Theme Variables ─────────────────────────────────────────────────────── */ +/* ── Root Variables ──────────────────────────────────────────────────── */ :root { - --md-text-font: "Space Grotesk"; - --md-code-font: "JetBrains Mono"; - --tryx-bg-a: rgba(255, 193, 90, 0.18); - --tryx-bg-b: rgba(39, 189, 168, 0.14); - --tryx-hero-bg: radial-gradient(circle at 12% 18%, #c7f4e2 0%, #f6fcfa 35%, #fef6e8 100%); - --tryx-card-bg: rgba(255, 255, 255, 0.72); - --tryx-card-border: rgba(18, 127, 112, 0.16); - --tryx-card-shadow: 0 10px 28px rgba(20, 35, 40, 0.08); - --tryx-muted-border: rgba(0, 0, 0, 0.15); - --tryx-code-bg: #f6f8fa; - --tryx-code-border: rgba(0, 0, 0, 0.06); -} - -[data-md-color-scheme="slate"] { - --tryx-bg-a: rgba(255, 184, 76, 0.08); - --tryx-bg-b: rgba(39, 189, 168, 0.08); - --tryx-hero-bg: linear-gradient(135deg, rgba(40, 53, 57, 0.9), rgba(24, 37, 39, 0.9)); - --tryx-card-bg: rgba(28, 41, 45, 0.74); - --tryx-card-border: rgba(88, 132, 125, 0.44); - --tryx-card-shadow: 0 12px 34px rgba(0, 0, 0, 0.35); - --tryx-muted-border: rgba(255, 255, 255, 0.24); - --tryx-code-bg: #1e292b; - --tryx-code-border: rgba(255, 255, 255, 0.08); -} - -/* ── Global Typography ───────────────────────────────────────────────────── */ + --tryx-radius: 8px; + --tryx-gap: 1rem; + --tryx-border: 1px solid var(--md-default-fg-color--lightest); +} + +/* ── Typography ──────────────────────────────────────────────────────── */ .md-typeset h1, .md-typeset h2, .md-typeset h3 { - letter-spacing: -0.02em; + letter-spacing: -0.025em; + font-weight: 600; } .md-typeset h2 { - margin-top: 2rem; - padding-bottom: 0.3rem; - border-bottom: 1px solid var(--tryx-muted-border); + margin-top: 2.5rem; + padding-bottom: 0.35rem; + border-bottom: var(--tryx-border); } -/* ── Main Background ─────────────────────────────────────────────────────── */ -.md-main { - background: - radial-gradient(circle at 85% 12%, var(--tryx-bg-a), transparent 32%), - radial-gradient(circle at 6% 80%, var(--tryx-bg-b), transparent 28%); +.md-typeset h3 { + margin-top: 1.5rem; + font-weight: 500; } -/* ── Hero Section ────────────────────────────────────────────────────────── */ +/* ── Hero Section ────────────────────────────────────────────────────── */ .tryx-hero { - border: 1px solid var(--tryx-card-border); - border-radius: 18px; + border: var(--tryx-border); + border-radius: var(--tryx-radius); + padding: 2rem; + margin-bottom: 2rem; background: linear-gradient( 135deg, - rgba(13, 148, 136, 0.12) 0%, - rgba(245, 158, 11, 0.08) 50%, - rgba(13, 148, 136, 0.12) 100% + rgba(13, 148, 136, 0.04) 0%, + rgba(13, 148, 136, 0.01) 100% ); - background-size: 200% 200%; - animation: heroGradient 8s ease infinite; - box-shadow: var(--tryx-card-shadow); - padding: 1.5rem 1.6rem; - margin-bottom: 1.5rem; - position: relative; - overflow: hidden; } -.tryx-hero::before { - content: ""; - position: absolute; - top: -50%; - right: -20%; - width: 300px; - height: 300px; - background: radial-gradient(circle, rgba(13, 148, 136, 0.15), transparent 70%); - border-radius: 50%; - pointer-events: none; +[data-md-color-scheme="slate"] .tryx-hero { + background: linear-gradient( + 135deg, + rgba(13, 148, 136, 0.08) 0%, + rgba(13, 148, 136, 0.02) 100% + ); } -@keyframes heroGradient { - 0%, 100% { background-position: 0% 50%; } - 50% { background-position: 100% 50%; } +.tryx-hero h1 { + font-size: 1.8rem; + margin-bottom: 0.5rem; + font-weight: 700; +} + +.tryx-hero p { + color: var(--md-default-fg-color--light); + font-size: 0.95rem; + max-width: 640px; } .tryx-pill-row { display: flex; flex-wrap: wrap; - gap: 0.55rem; - margin-top: 0.9rem; + gap: 0.4rem; + margin-top: 1rem; } .tryx-pill { - border: 1px solid var(--tryx-muted-border); + border: var(--tryx-border); border-radius: 999px; - padding: 0.25rem 0.7rem; - font-size: 0.78rem; + padding: 0.15rem 0.6rem; + font-size: 0.75rem; font-weight: 500; - background: rgba(255, 255, 255, 0.58); - transition: all 0.2s ease; + letter-spacing: 0.02em; + color: var(--md-accent-fg-color); + background: rgba(13, 148, 136, 0.06); } -.tryx-pill:hover { +[data-md-color-scheme="slate"] .tryx-pill { background: rgba(13, 148, 136, 0.12); - border-color: rgba(13, 148, 136, 0.3); } -[data-md-color-scheme="slate"] .tryx-pill { - background: rgba(255, 255, 255, 0.06); +/* ── Feature Grid ────────────────────────────────────────────────────── */ +.tryx-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: var(--tryx-gap); + margin-top: 1rem; } -[data-md-color-scheme="slate"] .tryx-pill:hover { - background: rgba(13, 148, 136, 0.15); +.tryx-card { + border: var(--tryx-border); + border-radius: var(--tryx-radius); + padding: 1.25rem; + transition: border-color 0.2s, box-shadow 0.2s; } -/* ── Feature Grid ────────────────────────────────────────────────────────── */ -.tryx-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); - gap: 0.8rem; - margin-top: 0.8rem; +.tryx-card:hover { + border-color: var(--md-accent-fg-color); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); } -.tryx-card { - border: 1px solid var(--tryx-card-border); - border-radius: 14px; - padding: 1rem; - background: var(--tryx-card-bg); - box-shadow: var(--tryx-card-shadow); - transition: all 0.25s ease; - position: relative; - overflow: hidden; +[data-md-color-scheme="slate"] .tryx-card:hover { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); } -.tryx-card::after { - content: ""; - position: absolute; - top: 0; - left: 0; - right: 0; - height: 3px; - background: linear-gradient(90deg, var(--md-accent-fg-color), transparent); - opacity: 0; - transition: opacity 0.25s ease; +.tryx-card h3 { + margin-top: 0; + font-size: 0.95rem; + font-weight: 600; } -.tryx-card:hover { - transform: translateY(-3px); - box-shadow: 0 14px 36px rgba(20, 35, 40, 0.12); +.tryx-card p { + margin-top: 0.3rem; + font-size: 0.85rem; + color: var(--md-default-fg-color--light); + line-height: 1.5; } -.tryx-card:hover::after { - opacity: 1; +/* ── Architecture Diagram ────────────────────────────────────────────── */ +.tryx-arch { + border: var(--tryx-border); + border-radius: var(--tryx-radius); + padding: 1rem 1.5rem; + margin: 1.5rem 0; + background: var(--md-code-bg-color); + overflow-x: auto; } -[data-md-color-scheme="slate"] .tryx-card:hover { - box-shadow: 0 14px 36px rgba(0, 0, 0, 0.4); +.tryx-arch pre { + margin: 0; + font-size: 0.78rem; + line-height: 1.6; } -/* ── Link Grid ───────────────────────────────────────────────────────────── */ +/* ── Link Grid ───────────────────────────────────────────────────────── */ .tryx-link-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 0.9rem; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: var(--tryx-gap); } .tryx-link-grid a { display: block; - border: 1px solid var(--tryx-card-border); - border-radius: 14px; - padding: 1rem; + border: var(--tryx-border); + border-radius: var(--tryx-radius); + padding: 0.8rem 1rem; text-decoration: none; - background: var(--tryx-card-bg); - box-shadow: var(--tryx-card-shadow); - transition: all 0.25s ease; + transition: border-color 0.2s, box-shadow 0.2s; } .tryx-link-grid a:hover { - transform: translateY(-3px); border-color: var(--md-accent-fg-color); - box-shadow: 0 12px 32px rgba(13, 148, 136, 0.15); + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); } -[data-md-color-scheme="slate"] .tryx-link-grid a:hover { - box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4); +.tryx-link-grid strong { + display: block; + font-size: 0.9rem; + margin-bottom: 0.2rem; } -/* ── Keyboard Shortcuts ──────────────────────────────────────────────────── */ +.tryx-link-grid span { + font-size: 0.8rem; + color: var(--md-default-fg-color--light); +} + +/* ── Keyboard Shortcuts ──────────────────────────────────────────────── */ .tryx-kbd { - border: 1px solid var(--tryx-muted-border); + border: 1px solid var(--md-default-fg-color--lightest); border-bottom-width: 2px; - border-radius: 6px; + border-radius: 4px; padding: 0.1rem 0.35rem; font-family: var(--md-code-font); - font-size: 0.76rem; + font-size: 0.78rem; } -/* ── Tables ──────────────────────────────────────────────────────────────── */ +/* ── Tables ──────────────────────────────────────────────────────────── */ .md-typeset table:not([class]) { - border-radius: 10px; + border-radius: var(--tryx-radius); overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); + font-size: 0.85rem; } .md-typeset table:not([class]) th { - background: linear-gradient(135deg, rgba(13, 148, 136, 0.08), rgba(13, 148, 136, 0.04)); font-weight: 600; + background: var(--md-code-bg-color); } -[data-md-color-scheme="slate"] .md-typeset table:not([class]) th { - background: linear-gradient(135deg, rgba(13, 148, 136, 0.15), rgba(13, 148, 136, 0.08)); -} - -.md-typeset table:not([class]) tr:hover { - background: rgba(13, 148, 136, 0.03); -} - -[data-md-color-scheme="slate"] .md-typeset table:not([class]) tr:hover { - background: rgba(13, 148, 136, 0.08); -} - -/* ── Admonitions ─────────────────────────────────────────────────────────── */ -.md-typeset .admonition { - border-radius: 12px; - border-left-width: 4px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); +.md-typeset table:not([class]) td { + vertical-align: top; } +/* ── Admonitions ─────────────────────────────────────────────────────── */ +.md-typeset .admonition, .md-typeset details.admonition { - border-radius: 12px; + border-radius: var(--tryx-radius); + border-left-width: 3px; } .md-typeset .admonition-title { - border-radius: 12px 12px 0 0; + border-radius: var(--tryx-radius) var(--tryx-radius) 0 0; font-weight: 600; + font-size: 0.88rem; } -/* ── Code Blocks ─────────────────────────────────────────────────────────── */ +/* ── Code Blocks ─────────────────────────────────────────────────────── */ .md-typeset pre { - border-radius: 12px; - border: 1px solid var(--tryx-code-border); - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04); -} - -.md-typeset pre > code { - border-radius: 12px; + border-radius: var(--tryx-radius); } .md-typeset code:not(pre code) { - border-radius: 6px; - padding: 0.15rem 0.4rem; - font-size: 0.85em; - border: 1px solid var(--tryx-code-border); - background: var(--tryx-code-bg); + border-radius: 4px; + padding: 0.12rem 0.35rem; + font-size: 0.84em; } -/* Code block line highlighting */ .md-typeset .highlight .hll { - background: rgba(13, 148, 136, 0.12); - border-left: 3px solid var(--md-accent-fg-color); + background: rgba(13, 148, 136, 0.06); + border-left: 2px solid var(--md-accent-fg-color); margin-left: -0.4rem; padding-left: 0.4rem; } -/* ── Navigation Enhancements ─────────────────────────────────────────────── */ -.md-header { - backdrop-filter: blur(12px); - -webkit-backdrop-filter: blur(12px); -} - +/* ── Navigation ──────────────────────────────────────────────────────── */ .md-nav__link { - transition: color 0.15s ease, padding-left 0.15s ease; -} - -.md-nav__link:hover { - padding-left: 0.2rem; + transition: color 0.15s ease; } -/* Active nav item indicator */ .md-nav__item--active > .md-nav__link { font-weight: 600; - color: var(--md-accent-fg-color); } -/* ── Search Enhancement ──────────────────────────────────────────────────── */ +/* ── Search ──────────────────────────────────────────────────────────── */ .md-search-result__link { - border-radius: 10px; + border-radius: var(--tryx-radius); transition: background 0.15s ease; } .md-search-result__link:hover { - background: rgba(13, 148, 136, 0.06); + background: rgba(13, 148, 136, 0.04); } .md-search-result__teaser { font-size: 0.82rem; } -/* ── Focus States ────────────────────────────────────────────────────────── */ +/* ── Focus States ────────────────────────────────────────────────────── */ a:focus-visible, button:focus-visible, input:focus-visible, @@ -306,69 +259,40 @@ summary:focus-visible { border-radius: 4px; } -/* ── Animations ──────────────────────────────────────────────────────────── */ -.tryx-fade-up { - animation: tryxFadeUp 520ms ease-out both; -} - -@keyframes tryxFadeUp { - from { - opacity: 0; - transform: translateY(12px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.tryx-fade-in { - animation: tryxFadeIn 600ms ease-out both; -} - -@keyframes tryxFadeIn { - from { opacity: 0; } - to { opacity: 1; } -} - -/* Staggered card animations */ -.tryx-grid .tryx-card:nth-child(1) { animation-delay: 0ms; } -.tryx-grid .tryx-card:nth-child(2) { animation-delay: 80ms; } -.tryx-grid .tryx-card:nth-child(3) { animation-delay: 160ms; } -.tryx-grid .tryx-card:nth-child(4) { animation-delay: 240ms; } -.tryx-grid .tryx-card { animation: tryxFadeUp 520ms ease-out both; } - -/* ── Progress Bar (instant navigation) ───────────────────────────────────── */ +/* ── Progress Bar ────────────────────────────────────────────────────── */ .md-vestigial__progress { - height: 3px; - background: linear-gradient(90deg, var(--md-accent-fg-color), rgba(13, 148, 136, 0.6)); + height: 2px; } -/* ── Footer Navigation ───────────────────────────────────────────────────── */ +/* ── Footer ──────────────────────────────────────────────────────────── */ .md-footer { - border-top: 1px solid var(--tryx-muted-border); -} - -.md-footer__link { - transition: color 0.15s ease; + border-top: var(--tryx-border); } -/* ── Scrollbar Styling ───────────────────────────────────────────────────── */ +/* ── Scrollbar ───────────────────────────────────────────────────────── */ .md-sidebar--primary .md-nav__scrollwrap { scrollbar-width: thin; - scrollbar-color: var(--tryx-muted-border) transparent; } -.md-sidebar--primary .md-nav__scrollwrap::-webkit-scrollbar { - width: 4px; +/* ── Content Tabs ────────────────────────────────────────────────────── */ +.md-typeset .tabbed-set > .tabbed-labels { + border-bottom: var(--tryx-border); } -.md-sidebar--primary .md-nav__scrollwrap::-webkit-scrollbar-thumb { - background: var(--tryx-muted-border); - border-radius: 4px; +.md-typeset .tabbed-set > .tabbed-labels > label { + font-weight: 500; +} + +/* ── Inline Code ─────────────────────────────────────────────────────── */ +.md-typeset code { + border-color: rgba(13, 148, 136, 0.15); } -/* ── Print Styles ────────────────────────────────────────────────────────── */ +[data-md-color-scheme="slate"] .md-typeset code { + border-color: rgba(13, 148, 136, 0.25); +} + +/* ── Print ───────────────────────────────────────────────────────────── */ @media print { .md-sidebar, .md-header, @@ -380,9 +304,4 @@ summary:focus-visible { margin: 0; max-width: 100%; } - - .md-typeset .admonition { - border: 1px solid #ccc; - box-shadow: none; - } } diff --git a/docs/core-concepts/architecture.md b/docs/core-concepts/architecture.md index d5b516c..8e262bc 100644 --- a/docs/core-concepts/architecture.md +++ b/docs/core-concepts/architecture.md @@ -1,83 +1,208 @@ # Architecture -Tryx intentionally splits protocol-heavy runtime responsibilities and Python-facing ergonomics. +Tryx splits performance-sensitive protocol work (Rust) from application +ergonomics (Python). This gives you WhatsApp protocol handling at native +speed while keeping your bot logic simple and typed. ## Layered Design -Tryx uses a two-layer model: - -1. Rust core layer -2. Python API layer +``` +┌──────────────────────────────────────────────────────┐ +│ Python Application │ +│ (handlers, business logic, bots) │ +├──────────────────────────────────────────────────────┤ +│ Python API Layer │ +│ TryxClient, namespace clients, @app.on() │ +│ (async methods, typed stubs) │ +├──────────────────────────────────────────────────────┤ +│ PyO3 Bridge │ +│ GIL management, type conversion, error marshal │ +├──────────────────────────────────────────────────────┤ +│ Rust Core │ +│ Protocol parsing, Signal crypto, media, transport │ +│ (Noise, protobuf, tokio) │ +└──────────────────────────────────────────────────────┘ +``` ### Rust Core Layer -The Rust side handles: +The Rust side handles everything performance-sensitive: -- protocol parsing -- transport/runtime state -- heavy event transformations -- media and protobuf conversions +| Module | Responsibility | Source | +|--------|---------------|--------| +| `src/clients/` | Client method implementations | `src/clients/tryx.rs`, `src/clients/groups.rs`, etc. | +| `src/events/` | Event dispatcher and event class definitions | `src/events/dispatcher.rs` | +| `src/types.rs` | Shared data classes (`JID`, `MessageInfo`, etc.) | `src/types.rs` | +| `src/backend/` | Storage backend bridge (SQLite, FFI, Python) | `src/backend/` | -Additional responsibilities: +Key Rust capabilities: -- connection lifecycle and stream state -- event normalization and serialization boundaries -- low-level protocol node handling +- **Protocol parsing** — WhatsApp binary protocol, protobuf messages, Noise handshake +- **Transport** — WebSocket connection, stream management, reconnection +- **Crypto** — Signal protocol (Double Ratchet, X3DH), end-to-end encryption +- **Media** — Upload/download, transcoding, thumbnail generation +- **Event normalization** — Raw protocol events → typed Python objects ### Python API Layer -The Python side provides: +The Python side provides ergonomics: -- ergonomic async API -- namespace-based clients (`contact`, `groups`, `privacy`, etc.) -- typed stubs for IDE and static analysis -- callback registration via decorators +- **Namespace clients** — `client.groups`, `client.privacy`, `client.newsletter`, etc. +- **Event handlers** — Decorator-based callback registration via `@app.on(EventClass)` +- **Typed stubs** — `.pyi` files for IDE intelligence and static analysis +- **Storage backends** — SQLite (built-in), FFI (Postgres, etc.), pure Python -Additional responsibilities: +### PyO3 Bridge -- namespace-driven domain APIs (`client.groups`, `client.privacy`, etc.) -- handler orchestration and business logic composition -- integration with third-party systems (DB, queues, APIs) +The bridge layer handles type conversion between Python and Rust: -## Why This Design Works +- **GIL management** — Uses `Python::attach()` (PyO3 0.28+) for lightweight GIL acquisition +- **Error marshaling** — Rust panics → Python `RuntimeError` / `panic::PanicException` +- **Async bridging** — `pyo3-async-runtimes` connects tokio to Python asyncio +- **Type conversion** — `JID`, `MessageProto`, `Node`, etc. converted via PyO3 extractors -- Performance-sensitive logic stays in Rust. -- Product logic stays simple in Python. -- Event payloads are structured classes, not ad-hoc dicts. +--- -## Runtime Boundary Principle +## Data Flow -!!! tip - Keep protocol assumptions in Rust-backed typed models and keep product/business policy in Python handlers. +### Sending a Message -## Data Flow +```mermaid +sequenceDiagram + participant User as Python Code + participant Client as TryxClient + participant Bridge as PyO3 Bridge + participant Rust as Rust Core + participant WA as WhatsApp + + User->>Client: await client.send_text(jid, "hello") + Client->>Bridge: Call Rust method + Bridge->>Rust: Build protobuf message + Rust->>Rust: Encrypt with Signal protocol + Rust->>WA: Send over WebSocket + WA-->>Rust: Server ACK + Rust-->>Bridge: SendResult + Bridge-->>User: SendResult +``` + +### Receiving an Event + +```mermaid +sequenceDiagram + participant WA as WhatsApp + participant Rust as Rust Core + participant Bridge as PyO3 Bridge + participant Dispatcher as Event Dispatcher + participant Handler as Python Handler + + WA->>Rust: Raw protocol event + Rust->>Rust: Parse & normalize + Rust->>Bridge: Typed event object + Bridge->>Dispatcher: Route to handlers + Dispatcher->>Handler: Call registered handler + Handler-->>Dispatcher: Process event +``` + +--- + +## Storage Architecture + +Tryx uses a pluggable storage backend for Signal protocol state: ```mermaid -flowchart LR - A[WhatsApp Stream] --> B[Rust Runtime] - B --> C[Event Conversion] - C --> D[PyO3 Classes] - D --> E[Python Handler] - E --> F[TryxClient API Calls] - F --> B +graph TD + A[TryxClient] --> B{Backend Type} + B -->|Default| C[SqliteStore] + B -->|FFI| D[FfiStore] + B -->|Custom| E[StoreBase] + D --> F[.so/.dylib] + F --> G[Postgres, Redis, etc.] + C --> H[whatsapp.db] ``` +### Backend Selection + +| Backend | Use Case | How It Works | +|---------|----------|--------------| +| `SqliteStore` | Default, zero-config | Built-in SQLite with WAL mode | +| `FfiStore` | High-performance | C ABI shared library loaded via `libloading` | +| `StoreBase` | Custom async | Pure Python, implement abstract methods | + +The FFI backend is unique: Python never loads the `.so` directly. Python +only resolves the path and stores connection config — Tryx's Rust runtime +does the actual FFI loading. + +--- + ## Module Map -- `src/lib.rs`: submodule registration and class exports -- `src/clients/*`: client methods exposed to Python -- `src/events/*`: event classes and dispatcher -- `src/types.rs`: shared data classes (`JID`, `MessageInfo`, etc.) -- `src/wacore/*`: low-level node and stanza models -- `python/tryx/*.py`: runtime re-export wrappers -- `python/tryx/*.pyi`: typed API contracts +| Path | Purpose | +|------|---------| +| `src/lib.rs` | Submodule registration and class exports | +| `src/clients/*.rs` | Client method implementations | +| `src/events/*` | Event classes and dispatcher | +| `src/types.rs` | Shared data classes | +| `src/wacore/*` | Low-level protocol models | +| `python/tryx/*.py` | Runtime re-export wrappers | +| `python/tryx/*.pyi` | Typed API contracts | + +--- + +## Design Principles + +### Protocol in Rust, Logic in Python + +Keep protocol assumptions in Rust-backed typed models. Keep business policy +in Python handlers. This separation means: + +- Protocol changes don't break your Python code +- Business logic changes don't require recompilation +- Performance-sensitive paths are always native + +### Typed Contracts + +Every Python-facing API has a `.pyi` stub file. This gives you: + +- Editor autocomplete and type hints +- Static analysis with mypy/pyright +- Clear API documentation +- Runtime compatibility checking + +### Event-Driven Architecture + +All WhatsApp interactions flow through typed events: + +```python +from tryx.events import EvMessage + +@app.on(EvMessage) +async def handle_message(client, event: EvMessage): + # event.sender, event.text, event.media, etc. + pass +``` + +Events are structured classes, not ad-hoc dicts. This means: + +- Guaranteed fields with correct types +- IDE support for event properties +- Clear documentation of what data is available + +### Pluggable Storage + +Storage is decoupled from the protocol engine: + +- SQLite for development and single-instance deployments +- FFI shared libraries for high-throughput production +- Pure Python for exotic backends or rapid prototyping -## Practical Implication +All three tiers implement the same underlying Rust trait, so switching +backends requires only a change in the `Tryx()` constructor. -You can safely treat Python classes as stable contracts while trusting Rust internals for throughput and protocol-heavy work. +--- -## Related Docs +## Related -- [Event Model](event-model.md) -- [Type System](type-system.md) -- [Client API Gateway](../api/client.md) +- [Event Model](event-model.md) — detailed event type reference +- [Type System](type-system.md) — shared data classes +- [Storage Backends](storage-backends.md) — backend comparison +- [Client API Gateway](../api/client.md) — namespace client reference diff --git a/docs/core-concepts/event-model.md b/docs/core-concepts/event-model.md index ed7bc08..9b2ea53 100644 --- a/docs/core-concepts/event-model.md +++ b/docs/core-concepts/event-model.md @@ -1,67 +1,265 @@ # Event Model -Tryx emits typed event classes from `tryx.events`. Every event has a known payload shape. +Tryx uses an event-driven architecture. All WhatsApp interactions — messages, +presence changes, group updates, contact syncs — flow through typed event +classes. You register handlers for specific event types, and Tryx dispatches +them as they arrive. -## Dispatch Contract +## How Events Work -Handlers are registered by event class and receive `(client, event)`. +``` +WhatsApp Stream → Rust Parser → Event Dispatcher → Your Handlers +``` -!!! note - Event flow is asynchronous and stateful. Design handlers for retries and replay-like conditions. +1. **Raw protocol data** arrives over WebSocket +2. **Rust parser** normalizes it into typed event objects +3. **Event dispatcher** routes events to registered handlers +4. **Your handlers** process the event and respond ## Handler Registration +Handlers are registered using the `@app.on()` decorator: + ```python +from tryx.client import Tryx +from tryx.events import EvMessage, EvConnected, EvDisconnected + +app = Tryx(store) + +@app.on(EvConnected) +async def on_connected(client): + print("Connected to WhatsApp!") + @app.on(EvMessage) -async def on_message(client: TryxClient, event: EvMessage) -> None: ... +async def on_message(client, event): + print(f"Message from {event.sender}: {event.text}") + +@app.on(EvDisconnected) +async def on_disconnected(client): + print("Disconnected, will reconnect...") +``` + +### Handler Signature + +Every handler receives two arguments: + +```python +async def handler(client: TryxClient, event: EventType) -> None: + ... ``` -## Event Categories +- `client` — The `TryxClient` instance, ready to send messages and query state +- `event` — The typed event object with all relevant data + +### Multiple Handlers + +You can register multiple handlers for the same event type. Both will be +called for each event: + +```python +@app.on(EvMessage) +async def log_message(client, event): + logger.info(f"Received: {event.message_id}") + +@app.on(EvMessage) +async def process_message(client, event): + if event.text: + await handle_text(client, event) +``` + +--- + +## Event Taxonomy + +### Lifecycle Events + +Track the client connection lifecycle: + +| Event | When | Use Case | +|-------|------|----------| +| `EvConnected` | WebSocket connected | Start sending messages | +| `EvDisconnected` | WebSocket lost | Pause non-critical operations | +| `EvLoggedOut` | Session invalidated | Re-authenticate | +| `EvStreamReplaced` | Another login replaced you | Check for duplicate sessions | +| `EvClientOutDated` | Client needs update | Prompt for upgrade | +| `EvReady` | Fully initialized | Safe to query state | + +### Pairing Events + +Track the phone pairing process: + +| Event | When | Use Case | +|-------|------|----------| +| `EvPairingQrCode` | QR code generated | Display QR to user | +| `EvPairingCode` | Pairing code generated | Display code to user | +| `EvPairSuccess` | Pairing completed | Proceed with bot logic | +| `EvPairError` | Pairing failed | Show error, retry | + +### Messaging Events + +Track message flow: + +| Event | When | Use Case | +|-------|------|----------| +| `EvMessage` | Message received | Process incoming messages | +| `EvReceipt` | Delivery/read receipt | Track message status | +| `EvUndecryptableMessage` | Decryption failed | Log and request re-send | +| `EvNotification` | Server notification | Handle system messages | + +### Chat Action Sync Events + +Track chat state changes (synced from other devices): + +| Event | When | Use Case | +|-------|------|----------| +| `EvPinUpdate` | Chat pinned/unpinned | Update local state | +| `EvMuteUpdate` | Chat muted/unmuted | Adjust notifications | +| `EvArchiveUpdate` | Chat archived/unarchived | Update UI | +| `EvMarkChatAsReadUpdate` | Chat read status changed | Update unread count | +| `EvDeleteChatUpdate` | Chat deleted | Clean up local data | +| `EvDeleteMessageForMeUpdate` | Message deleted locally | Remove from cache | +| `EvStarUpdate` | Message starred/unstarred | Update star state | +| `EvChatArchive` | Chat archived | Update UI | + +### Presence and Profile Events -- Lifecycle: `EvConnected`, `EvDisconnected`, `EvLoggedOut` -- Pairing: `EvPairingQrCode`, `EvPairingCode`, `EvPairSuccess`, `EvPairError` -- Messaging: `EvMessage`, `EvReceipt`, `EvUndecryptableMessage` -- Chat actions sync: archive, mute, mark-read, delete-chat, delete-for-me -- Presence and profile: chat presence, availability, picture, push-name, about -- Contact and device sync: contact update, device list update -- Group and newsletter updates +Track user presence and profile changes: -For full taxonomy, see [Events API](../api/events.md). +| Event | When | Use Case | +|-------|------|----------| +| `EvPresence` | User typing/recording | Show typing indicator | +| `EvChatPresence` | Chat presence changed | Update UI | +| `EvAvailability` | Online/offline status | Track availability | +| `EvPicture` | Profile picture changed | Update avatar cache | +| `EvPushName` | Display name changed | Update contact info | +| `EvAbout` | Status text changed | Update status display | + +### Contact and Device Sync Events + +Track contact and device state: + +| Event | When | Use Case | +|-------|------|----------| +| `EvContactUpdate` | Contact info changed | Update contact list | +| `EvContactNumberChanged` | Phone number changed | Update contact mapping | +| `EvDeviceListUpdate` | Device list changed | Re-key encryption | +| `EvGroupUpdate` | Group metadata changed | Update group info | +| `EvGroupInfoUpdate` | Group info refreshed | Sync group state | +| `EvJoinedGroup` | Bot joined a group | Initialize group state | +| `EvNewsletterUpdate` | Newsletter changed | Update channel info | +| `EvNewsletterLiveUpdate` | Live update received | Process live content | + +### Business Events + +| Event | When | Use Case | +|-------|------|----------| +| `EvBusinessStatusUpdate` | Business profile changed | Update business info | + +--- ## Event Payload Pattern Many events expose a lazy `data` property: -- `event.data` returns a rich typed object -- conversion from Rust internals happens on demand -- repeated access often reuses cached object instances +```python +@app.on(EvMessage) +async def handle(client, event): + # Access raw data (lazy-loaded from Rust) + data = event.data + + # Repeated access reuses cached object + same_data = event.data # No re-parsing +``` -## Important Reliability Notes +The `data` property: -- Callback execution order is event-driven; do not assume strict timing between different event classes. -- Keep handlers short and non-blocking. -- For expensive work, queue to background tasks. +- Returns a rich typed object +- Converts from Rust internals on demand +- Caches the result for subsequent access -!!! warning "Ordering assumptions" - Do not assume strict global ordering between all event types. Build idempotent handlers using message identifiers. +--- -## Best Practices +## Reliability Considerations + +### Handler Execution + +- Handlers are **async** and run on the tokio runtime +- Execution order is **event-driven**, not sequential +- Do not assume strict timing between different event types + +### Error Handling + +```python +@app.on(EvMessage) +async def safe_handler(client, event): + try: + await process_message(client, event) + except Exception as e: + logger.error(f"Handler failed: {e}") + # Don't crash — Tryx will continue dispatching +``` -1. Validate optional fields before use. -2. Prefer exact event classes over broad dynamic checks. -3. Log enough metadata (`jid`, `message_id`, timestamps) for debugging. -4. Treat undecryptable and sync events as normal runtime states, not always errors. +!!! warning "Handler crashes" + If a handler raises an exception, Tryx catches it and continues + dispatching other events. However, the error is logged and may affect + your bot's reliability. + +### Idempotency + +Build idempotent handlers using message identifiers: + +```python +processed = set() + +@app.on(EvMessage) +async def idempotent_handler(client, event): + message_id = event.data.message_info.id + if message_id in processed: + return + processed.add(message_id) + await process_message(client, event) +``` + +### Ordering + +Do not assume strict global ordering between all event types. For example: + +- An `EvMessage` may arrive before its `EvReceipt` +- Group updates may arrive out of order +- Presence events may be stale + +Design handlers to handle these cases gracefully. + +--- ## Event-to-Action Mapping -| Event Example | Typical Namespace Follow-up | -| --- | --- | -| `EvMessage` | root send methods, [Chat Actions](../api/chat-actions.md) | -| `EvGroupUpdate` | [Groups](../api/groups.md), [Community](../api/community.md) | -| `EvPresence` | [Presence](../api/presence.md), [Chatstate](../api/chatstate.md) | -| `EvNewsletterLiveUpdate` | [Newsletter](../api/newsletter.md), [Polls](../api/polls.md) | +| Event | Typical Follow-up | Namespace | +|-------|-------------------|-----------| +| `EvMessage` | Reply, forward, react | Root send methods | +| `EvGroupUpdate` | Update local group state | [Groups](../api/groups.md) | +| `EvPresence` | Show typing indicator | [Presence](../api/presence.md) | +| `EvNewsletterLiveUpdate` | Process live updates | [Newsletter](../api/newsletter.md) | +| `EvContactUpdate` | Update contact list | [Contact](../api/contact.md) | +| `EvChatArchive` | Update UI state | [Chat Actions](../api/chat-actions.md) | +| `EvReceipt` | Track delivery status | Root send methods | +| `EvPicture` | Update avatar cache | [Contact](../api/contact.md) | + +--- + +## Best Practices + +1. **Keep handlers short** — Queue expensive work to background tasks +2. **Validate optional fields** — Check `event.text`, `event.media`, etc. before use +3. **Log metadata** — Include `jid`, `message_id`, timestamps for debugging +4. **Handle errors gracefully** — Don't let one bad event crash your bot +5. **Use typed events** — Prefer specific event classes over broad dynamic checks +6. **Treat undecryptable events as normal** — They happen during key rotation + +--- -## Related Docs +## Related -- [Client API Gateway](../api/client.md) -- [Reliability](../operations/reliability.md) +- [Client API Gateway](../api/client.md) — namespace client reference +- [Reliability](../operations/reliability.md) — production error handling +- [Events API](../api/events.md) — full event type reference diff --git a/docs/core-concepts/storage-backends.md b/docs/core-concepts/storage-backends.md index afdce72..efc2f9a 100644 --- a/docs/core-concepts/storage-backends.md +++ b/docs/core-concepts/storage-backends.md @@ -69,16 +69,18 @@ sequenceDiagram ### Usage -Any object with `lib_path` and `connect_string` attributes is detected as an FFI backend: +Any object with `lib_path` and `config_json` attributes is detected as an FFI backend: ```python +import json + from tryx.client import Tryx # tryx-store-postgres exposes this interface class PostgresStore: lib_path = "./libtryx_pg.so" - connect_string = "host=localhost dbname=tryx" + config_json = json.dumps({"host": "localhost", "dbname": "tryx"}) app = Tryx(PostgresStore()) diff --git a/docs/core-concepts/type-system.md b/docs/core-concepts/type-system.md index 650f040..5c8749b 100644 --- a/docs/core-concepts/type-system.md +++ b/docs/core-concepts/type-system.md @@ -1,73 +1,320 @@ # Type System -Tryx ships with `.pyi` stubs and `py.typed`, enabling full editor and type-checker support. +Tryx ships with complete `.pyi` stub files and a `py.typed` marker, +enabling full editor intelligence, static analysis, and API discoverability. !!! tip "Why this matters" - Typed contracts make event handling safer, API discovery faster, and refactors less risky. + Typed contracts make event handling safer, API discovery faster, and + refactors less risky. You get autocomplete for every method, parameter, + and return type. + +## How Typing Works + +Tryx uses a two-layer typing approach: + +1. **Runtime** — PyO3 generates Python classes from Rust structs +2. **Static** — `.pyi` stub files describe the exact API surface + +```python +from tryx.client import Tryx, TryxClient +from tryx.types import JID, SendResult + +# IDE shows: JID.whatsapp(phone: str) -> JID +jid = JID.whatsapp("5599800001") + +# IDE shows: async def send_text(to: JID, text: str, ...) -> SendResult +result = await client.send_text(jid, "Hello!") + +# IDE shows: result.message_id: str +print(result.message_id) +``` + +--- ## Core Types -- `JID`: canonical address object -- `MessageSource`: message origin and routing context -- `MessageInfo`: metadata for message identity and attributes -- `UploadResponse`: media upload output -- `SendResult`: send operation result -- `MediaReuploadResult`: media retry result -- `ProfilePicture`: profile picture metadata +### JID — WhatsApp Address + +The fundamental address type for all WhatsApp entities: + +```python +from tryx.types import JID + +# Personal account +jid = JID.whatsapp("5599800001") + +# Group +group = JID.whatsapp_group("120363000000000000@g.us") + +# Newsletter +newsletter = JID.whatsapp_newsletter("120363000000000000@newsletter") +``` + +### MessageInfo — Message Metadata + +Contains identity, routing, and attribute information: + +```python +from tryx.events import EvMessage + +@app.on(EvMessage) +async def handle(client, event): + info = event.data.message_info + + # Message identity + message_id = info.message_id + timestamp = info.timestamp + + # Routing + sender = info.source.sender # JID + chat = info.source.chat # JID + participant = info.source.participant # JID | None + + # Attributes + msg_type = info.message_type # "text", "image", etc. + is_from_me = info.is_from_me +``` + +### SendResult — Send Operation Output + +Returned by all send methods: + +```python +result = await client.send_text(jid, "Hello") + +result.message_id # str: unique message ID +result.timestamp # int: server timestamp +result.key # MessageKey: message key for tracking +``` + +### MediaReuploadResult — Media Retry Output + +Returned by `request_media_reupload`: + +```python +result = await client.request_media_reupload( + message_id="3EB0...", + chat_jid=jid, + media_key=b"...", +) + +result.url # str: re-uploaded media URL +result.direct_path # str: direct download path +``` + +### UploadResponse — Media Upload Output + +Returned by media upload methods: + +```python +upload = await client.upload_photo(photo_bytes, jid) + +upload.url # str: media URL +upload.direct_path # str: direct path +upload.media_key # bytes: encryption key +upload.file_length # int: file size +``` + +### ProfilePicture — Profile Image Metadata + +```python +picture = await client.contact.get_profile_picture(jid, preview=False) + +picture.url # str: image URL +picture.direct_path # str: direct download path +picture.file_length # int: file size +picture.mimetype # str: image MIME type +``` + +--- ## Event Types -Event classes define explicit payload contracts: +Every event class has a defined payload contract: -- no guessing with nested dict keys -- discoverable through IDE autocomplete -- easier static checks in large projects +```python +from tryx.events import ( + EvMessage, # Incoming message + EvConnected, # WebSocket connected + EvDisconnected, # WebSocket lost + EvLoggedOut, # Session invalidated + EvPresence, # User typing/recording + EvGroupUpdate, # Group metadata changed + EvReceipt, # Delivery/read receipt +) -## Enum-heavy Domains +@app.on(EvMessage) +async def handle(client, event: EvMessage): + # event.sender: JID + # event.text: str | None + # event.media: MediaInfo | None + # event.message_id: str + # event.timestamp: int + ... +``` -Key domains with enum-like constraints: +No guessing with nested dict keys — everything is discoverable through +IDE autocomplete. -- privacy and disallowed-list management -- status audience control -- chatstate and presence signaling -- group/community policy modes +--- ## Enum-Style Classes -Several Rust enums are exposed as Python classes with fixed attributes (for example status/privacy and event reason classes). +Several Rust enums are exposed as Python classes with fixed attributes: -## Suggested Typing Workflow +### Privacy -1. Keep handler function signatures explicit. -2. Annotate helper functions returning event-derived data. -3. Run static analysis in CI (mypy or pyright). +```python +from tryx.types import PrivacyCategory, PrivacyValue + +# Categories +PrivacyCategory.LastSeen +PrivacyCategory.ProfilePhoto +PrivacyCategory.Status +PrivacyCategory.ReadReceipts +PrivacyCategory.Groups + +# Values +PrivacyValue.Everyone +PrivacyValue.Contacts +PrivacyValue.Nobody +``` + +### Status + +```python +from tryx.types import StatusPrivacySetting + +StatusPrivacySetting.Contacts +StatusPrivacySetting.AllowList +StatusPrivacySetting.DenyList +``` + +### Chatstate + +```python +from tryx.types import ChatStateType + +ChatStateType.Composing # User is typing +ChatStateType.Recording # User is recording audio +ChatStateType.Paused # User stopped typing +``` + +### Presence + +```python +from tryx.types import PresenceStatus + +PresenceStatus.Available +PresenceStatus.Unavailable +``` + +### Group Policies + +```python +from tryx.types import MembershipApprovalMode, MemberAddMode, MemberLinkMode + +MembershipApprovalMode.On +MembershipApprovalMode.Off + +MemberAddMode.AdminOnly +MemberAddMode.AllMembers + +MemberLinkMode.Admin +MemberLinkMode.AllMembers +``` + +### Newsletter + +```python +from tryx.types import NewsletterVerification, NewsletterState, NewsletterRole + +NewsletterVerification.Verified +NewsletterVerification.Unverified + +NewsletterState.Active +NewsletterState.Suspended +NewsletterState.Geosuspended + +NewsletterRole.Owner +NewsletterRole.Admin +NewsletterRole.Subscriber +NewsletterRole.Guest +``` + +### Events + +```python +from tryx.types import EventResponse + +EventResponse.Going +EventResponse.NotGoing +EventResponse.Maybe +``` + +--- ## Type Boundary Pattern +Keep type boundaries clean between layers: + ```python from tryx.events import EvMessage from tryx.types import JID - +# Input boundary: accept typed events def extract_sender(event: EvMessage) -> JID: return event.data.message_info.source.sender + +# Output boundary: return typed results +async def forward_message(client, event: EvMessage, target: JID) -> SendResult: + text = event.data.text or "" + return await client.send_text(target, text) ``` -Then keep your service layer function signatures strictly typed as well. +--- -## Example +## Static Analysis Workflow -```python -from tryx.events import EvMessage -from tryx.types import JID +### With Mypy +```bash +uv add mypy +uv run mypy your_project/ +``` + +### With Pyright -def extract_chat(event: EvMessage) -> JID: - return event.data.message_info.source.chat +```bash +uv add pyright +uv run pyright your_project/ ``` -## Related Docs +### In CI + +```yaml +# .github/workflows/ci.yml +- name: Type check + run: | + uv run mypy your_project/ --strict + uv run pyright your_project/ +``` + +--- + +## Best Practices + +1. **Keep handler signatures explicit** — Always type-annotate `client` and `event` +2. **Annotate helper functions** — Especially those returning event-derived data +3. **Use specific event types** — `EvMessage` instead of generic `Event` +4. **Run static analysis in CI** — Catch type errors before deployment +5. **Leverage IDE autocomplete** — Let the stubs guide your API usage + +--- + +## Related -- [Types API](../api/types.md) -- [Privacy Namespace](../api/privacy.md) -- [Status Namespace](../api/status.md) +- [Types API](../api/types.md) — full type reference +- [Events API](../api/events.md) — event type reference +- [Privacy Namespace](../api/privacy.md) — privacy type usage +- [Status Namespace](../api/status.md) — status type usage diff --git a/docs/faq/qna.md b/docs/faq/qna.md index fb330dc..cebc82e 100644 --- a/docs/faq/qna.md +++ b/docs/faq/qna.md @@ -1,105 +1,206 @@ -# QnA +# FAQ -Use this page for quick decisions, then jump to linked technical pages for implementation details. +Quick answers to common questions. Jump to the linked pages for implementation +details. + +--- ## General ### What is Tryx? -Tryx is a Rust-powered Python SDK for event-driven WhatsApp automation. + +Tryx is a Rust-powered Python SDK for event-driven WhatsApp automation. It +pairs a Rust runtime core with a typed Python API, giving you WhatsApp +protocol handling at native speed while keeping application logic in clean +async Python. ### Why not pure Python? -Rust handles protocol-heavy runtime work for better throughput and lower overhead, while Python keeps app logic easy to write. + +Rust handles protocol-heavy runtime work for better throughput and lower +overhead: + +- **Signal protocol** — Double Ratchet, X3DH key exchange +- **Noise handshake** — WebSocket transport encryption +- **Protobuf** — Message serialization/deserialization +- **Media processing** — Upload/download, transcoding + +Python keeps app logic easy to write and maintain. ### Is Tryx synchronous or asynchronous? -Both: async-first (`await app.run()`), plus blocking convenience (`app.run_blocking()`). + +Both: + +- **Async-first**: `await app.run()` with full asyncio support +- **Blocking**: `app.run_blocking()` for quick scripts + +```python +# Async +asyncio.run(app.run()) + +# Blocking +app.run_blocking() +``` See [Quick Start](../getting-started/quickstart.md). +### What Python versions are supported? + +Python 3.8 and newer. We recommend 3.10+ for the best typing experience. + +### Does Tryx support Linux/macOS/Windows? + +Yes, with proper Rust toolchain and platform build dependencies. See the +[Installation](../getting-started/installation.md) page for platform-specific +setup. + +--- + ## Pairing and Session ### Do I need to pair every time? -No. If backend storage is preserved, session data is reused. -### What does EvStreamReplaced mean? -Another session replaced your active stream. Re-check device/session ownership. +No. If backend storage is preserved (`whatsapp.db` or equivalent), session +data is reused across restarts. + +### What does `EvStreamReplaced` mean? + +Another session replaced your active stream. This typically happens when: + +- Another device logged into the same account +- A deployment is using the same backend path -### What should I do on EvLoggedOut? -Treat it as session invalidation. Re-pair and refresh persisted state. +Check device/session ownership and ensure single-writer backend access. -See [Authentication Flow](../getting-started/authentication.md). +### What should I do on `EvLoggedOut`? + +Treat it as session invalidation. Re-pair and refresh persisted state. See +[Authentication Flow](../getting-started/authentication.md). + +### Can I use the same backend path across multiple instances? + +**No.** Avoid multiple runtime instances writing to the same backend path. +This can cause stream replacement, data corruption, or forced re-pairing. + +--- ## Event Handling ### Can I register multiple handlers for one event? -Yes. Dispatcher stores callbacks per event class. -### Why does an event have `data` property instead of direct fields? -Many event payloads are lazily materialized for efficiency. +Yes. The dispatcher stores callbacks per event class and calls all of them +for each event. + +### Why does an event have a `data` property instead of direct fields? + +Many event payloads are lazily materialized for efficiency. The `data` +property returns a rich typed object that's cached for subsequent access. ### Should I process heavy logic directly in handlers? -Prefer short handlers that delegate expensive work to background tasks. -See [Reliability](../operations/reliability.md) and [Performance](../operations/performance.md). +**No.** Keep handlers short and non-blocking. Delegate expensive work to +background tasks. See [Reliability](../operations/reliability.md) and +[Performance](../operations/performance.md). + +### How do I handle undecryptable messages? + +Treat them as normal — they happen during key rotation. Log them and move +on. Tryx continues dispatching other events. + +--- ## Messaging and Media ### Which media types can Tryx send? -Text, photo, document, audio, video, GIF, sticker, and protobuf-raw messages. -### When should I call request_media_reupload? +| Type | Method | Notes | +|------|--------|-------| +| Text | `send_text()` | Basic text messages | +| Photo | `send_photo()` | Images with optional caption | +| Document | `send_document()` | Files with optional name | +| Audio | `send_audio()` | Voice notes (ptt=True) or clips | +| Video | `send_video()` | Video clips with optional caption | +| GIF | `send_gif()` | Animated GIFs | +| Sticker | `send_sticker()` | Static WEBP or animated | +| Raw | `send_message()` | Custom protobuf messages | + +### When should I call `request_media_reupload`? + When media direct path is stale or unavailable and normal download fails. +This is common for older messages where the media CDN link has expired. ### Can I quote a message in replies? -Yes, pass the original `EvMessage` to send helpers that support `quoted`. + +Yes, pass the original `EvMessage` to send helpers that support `quoted`: + +```python +await client.send_text(chat, "reply text", quoted=event) +``` See [Media Workflows](../tutorials/media-workflows.md). +--- + ## Groups and Privacy ### Can I automate group moderation? -Yes, use `client.groups.*` and handle `EvGroupUpdate` for state feedback. + +Yes, use `client.groups.*` for participant management and handle +`EvGroupUpdate` for state feedback. See +[Group Automation](../tutorials/group-automation.md). ### Can I modify privacy settings? -Yes, use `client.privacy.fetch_settings()` and `set_setting(...)`. -See [Privacy Namespace](../api/privacy.md) and [Profile and Privacy Tutorial](../tutorials/profile-privacy.md). +Yes, use `client.privacy.fetch_settings()` and `set_setting(...)`. See +[Privacy Namespace](../api/privacy.md). + +--- ## Deployment and Operations ### What is the minimum production checklist? -1. durable backend/session storage -2. bounded retry strategy -3. idempotent message processing -4. basic security controls (admin-only commands, secret management) +1. Durable backend/session storage +2. Bounded retry strategy +3. Idempotent message processing +4. Basic security controls (admin-only commands, secret management) +5. Structured logging See [Deployment Guide](../operations/deployment.md). -### How do I troubleshoot reconnect loops quickly? -Use the connection decision tree in [Troubleshooting](../operations/troubleshooting.md) and verify single-writer backend ownership. +### How do I troubleshoot reconnect loops? + +Use the connection decision tree in +[Troubleshooting](../operations/troubleshooting.md) and verify single-writer +backend ownership. + +--- ## Typing and Tooling ### Are stubs complete? -Tryx ships `.pyi` stubs for public modules including events and low-level wacore types. + +Tryx ships `.pyi` stubs for all public modules including events, types, +client namespaces, and low-level wacore types. ### Can I use mypy or pyright? + Yes, the package includes `py.typed` for static analysis integration. -## Reliability +```bash +uv run mypy your_project/ +uv run pyright your_project/ +``` -### How should I handle temporary bans? -Listen to `EvTemporaryBan`, pause high-frequency operations, and avoid aggressive retries. +--- -### How can I make my client idempotent? -Store processed message IDs and guard side effects before calling external systems. +## Reliability -See [Reliability](../operations/reliability.md). +### How should I handle temporary bans? -## Compatibility +Listen to `EvTemporaryBan`, pause high-frequency operations, and avoid +aggressive retries. Resume gradually after the ban period. -### Which Python versions are supported? -Python 3.8 and newer. +### How can I make my client idempotent? -### Does Tryx support Linux/macOS/Windows? -Yes, with proper Rust toolchain and platform build dependencies. +Store processed message IDs and guard side effects before calling external +systems. See [Reliability](../operations/reliability.md). diff --git a/docs/getting-started/authentication.md b/docs/getting-started/authentication.md index 17cae58..f9d7555 100644 --- a/docs/getting-started/authentication.md +++ b/docs/getting-started/authentication.md @@ -1,33 +1,98 @@ # Authentication Flow -Tryx follows the WhatsApp multi-device pairing flow. The first run links a session, and later runs reuse stored state. +Tryx follows the WhatsApp multi-device pairing flow. The first run links +a session, and later runs reuse stored state. -!!! note - Authentication stability is mostly a storage and ownership problem. Treat backend/session files as critical runtime state. +!!! note "Key principle" + Authentication stability is mostly a storage and ownership problem. + Treat backend/session files as critical runtime state. + +--- ## Pairing Modes -- QR pairing event: `EvPairingQrCode` -- Numeric pairing event: `EvPairingCode` -- Success event: `EvPairSuccess` -- Failure event: `EvPairError` +| Mode | Event | How to Use | +|------|-------|------------| +| **QR Code** | `EvPairingQrCode` | Scan with your phone camera | +| **Numeric Code** | `EvPairingCode` | Enter 8-digit code on your phone | + +--- + +## First-Run Sequence + +```mermaid +sequenceDiagram + participant Bot as Tryx Bot + participant WA as WhatsApp + participant Phone as Your Phone + + Bot->>WA: Connect to WebSocket + WA-->>Bot: Pairing challenge + Bot-->>Bot: Emit EvPairingQrCode or EvPairingCode + Note over Phone: User scans QR or enters code + Phone->>WA: Confirm pairing + WA-->>Bot: Session established + Bot-->>Bot: Emit EvPairSuccess + Bot-->>Bot: Persist session in backend +``` + +1. Start client runtime +2. Wait for `EvPairingQrCode` or `EvPairingCode` +3. Complete pairing from your WhatsApp mobile app +4. Receive `EvPairSuccess` +5. Session credentials are persisted in your backend + +--- + +## Event Reference -## Typical First-Run Sequence +| Event | Meaning | Action | +|-------|---------|--------| +| `EvPairingQrCode` | QR challenge issued | Display QR to user | +| `EvPairingCode` | Code-based pairing challenge | Display code to user | +| `EvPairSuccess` | Session linked and persisted | Continue normal operations | +| `EvPairError` | Pairing rejected/failed | Inspect logs and retry | + +--- + +## Code Example: Pairing Handler + +```python +from tryx.client import Tryx +from tryx.backend import SqliteStore +from tryx.events import ( + EvPairingQrCode, + EvPairingCode, + EvPairSuccess, + EvPairError, +) -1. Start client runtime. -2. Wait for `EvPairingQrCode` or `EvPairingCode`. -3. Complete pairing from your WhatsApp mobile app. -4. Receive `EvPairSuccess`. -5. Session credentials are persisted in your backend. +backend = SqliteStore("whatsapp.db") +app = Tryx(backend) -## Event-level Interpretation -| Event | Meaning | Operator action | -| --- | --- | --- | -| `EvPairingQrCode` | QR challenge issued | scan with mobile app | -| `EvPairingCode` | code-based pairing challenge | enter code in paired device flow | -| `EvPairSuccess` | session linked and persisted | continue normal operations | -| `EvPairError` | pairing rejected/failed | inspect logs and retry pairing | +@app.on(EvPairingQrCode) +async def on_qr(client, event): + print(f"Scan this QR code: {event.qr_data}") + # In a real bot, you might send this to a web interface + + +@app.on(EvPairingCode) +async def on_code(client, event): + print(f"Enter this code: {event.pairing_code}") + + +@app.on(EvPairSuccess) +async def on_success(client, event): + print("Successfully paired!") + + +@app.on(EvPairError) +async def on_error(client, event): + print(f"Pairing failed: {event.error}") +``` + +--- ## Persistence @@ -42,27 +107,67 @@ backend = SqliteStore("/srv/tryx/session.db") If the same backend path is reused, you usually do not need to pair again. !!! warning "Single writer rule" - Avoid multiple runtime instances writing to the same backend path unless you explicitly control ownership. + Avoid multiple runtime instances writing to the same backend path + unless you explicitly control ownership. -## Operational Guidance - -- Keep one active session owner for a backend path. -- Avoid deleting backend files unless resetting account link is intentional. -- Back up backend data before infrastructure migration. +--- ## Recovery Signals -- `EvLoggedOut`: account session is no longer valid. -- `EvStreamReplaced`: another login/session replaced your current stream. -- `EvTemporaryBan`: temporary restrictions detected; pause high-volume operations. +| Event | Meaning | Action | +|-------|---------|--------| +| `EvLoggedOut` | Session invalidated | Re-pair and rotate session artifacts | +| `EvStreamReplaced` | Another login replaced your stream | Check for duplicate sessions | +| `EvTemporaryBan` | Temporary restrictions detected | Pause high-volume operations | + +--- ## Recovery Playbook -1. if `EvLoggedOut`: re-pair and rotate session artifacts. -2. if `EvStreamReplaced`: check if another deployment is using the same account/backend. -3. if temporary-ban signal: stop automation burst traffic and re-enable gradually. +### `EvLoggedOut` + +```python +@app.on(EvLoggedOut) +async def on_logged_out(client): + logger.warning("Session invalidated, re-pairing required") + # Stop bot operations + # Notify operator + # Trigger re-pairing flow +``` + +### `EvStreamReplaced` + +```python +@app.on(EvStreamReplaced) +async def on_replaced(client): + logger.warning("Stream replaced by another session") + # Check if another deployment is using the same account + # Ensure single-writer backend ownership +``` + +### `EvTemporaryBan` + +```python +@app.on(EvTemporaryBan) +async def on_ban(client, event): + logger.warning(f"Temporary ban: {event.reason}") + # Stop automation burst traffic + # Re-enable gradually after ban period +``` + +--- + +## Operational Guidance + +- Keep one active session owner for a backend path +- Avoid deleting backend files unless resetting account link is intentional +- Back up backend data before infrastructure migration +- Monitor for `EvStreamReplaced` to detect duplicate sessions + +--- -## Related Docs +## Related -- [Deployment Guide](../operations/deployment.md) -- [Troubleshooting](../operations/troubleshooting.md) +- [Deployment Guide](../operations/deployment.md) — production setup +- [Troubleshooting](../operations/troubleshooting.md) — connection issues +- [Reliability](../operations/reliability.md) — error handling patterns diff --git a/docs/getting-started/contributing.md b/docs/getting-started/contributing.md index 192b458..e92d8d8 100644 --- a/docs/getting-started/contributing.md +++ b/docs/getting-started/contributing.md @@ -1,80 +1,148 @@ -# Contributing Guide +# Contributing -This page describes how to contribute to Tryx using the project standards for tooling, commits, and pull requests. +This guide explains how to contribute to Tryx development. -## Local Setup +--- + +## Development Setup ```bash -uv sync --group dev --group docs +# Clone +git clone https://github.com/krypton-byte/tryx.git +cd tryx + +# Install dependencies +uv sync --group dev + +# Build Rust extension uv run maturin develop -uv run pre-commit install --hook-type pre-commit --hook-type commit-msg + +# Verify +uv run python -c "from tryx.client import Tryx; print('OK')" +``` + +--- + +## Code Structure + +``` +tryx/ +├── src/ # Rust source +│ ├── lib.rs # Module entry point +│ ├── clients/ # Client method implementations +│ ├── events/ # Event dispatcher and types +│ ├── types.rs # Shared data classes +│ └── backend/ # Storage backend bridge +├── python/tryx/ # Python package +│ ├── *.pyi # Type stubs (edit these for API changes) +│ └── waproto/ # Protobuf definitions +├── tests/ # Test suite +└── docs/ # Documentation +``` + +--- + +## Development Workflow + +### 1. Create a Branch + +```bash +git checkout -b feature/my-feature ``` -## Required Checks +### 2. Make Changes + +- **Rust changes**: Edit files in `src/` +- **Python API changes**: Edit `.pyi` stubs in `python/tryx/` +- **Documentation**: Edit files in `docs/` + +### 3. Build and Test ```bash -uv run --no-project --with ruff==0.11.4 ruff check . -uv run --no-project --with ruff==0.11.4 ruff format --check . -uv run python scripts/check_stub_parity.py +# Rebuild Rust extension +uv run maturin develop + +# Run tests +uv run pytest + +# Type check +uv run mypy your_changes/ + +# Lint +uv run ruff check . ``` -## Commit Message Standard +### 4. Commit -Tryx uses Conventional Commits. +Use [Conventional Commits](https://www.conventionalcommits.org/): -Pattern: +```bash +# Feature +git commit -m "feat: add new group action" + +# Bug fix +git commit -m "fix: handle edge case in media download" -```text -type(scope): summary -type(scope)!: summary +# Documentation +git commit -m "docs: improve quickstart guide" ``` -Scope is optional. - -Allowed `type`: -- `feat` -- `fix` -- `perf` -- `refactor` -- `docs` -- `test` -- `build` -- `ci` -- `chore` -- `style` - -Release impact: -- `feat` -> minor bump -- `fix` / `perf` -> patch bump -- `!` or `BREAKING CHANGE:` -> major bump - -Examples: -- `feat(groups): add participant count helper` -- `fix(profile): validate image bytes before upload` -- `feat(status)!: change default privacy behavior` - -## Pull Requests - -1. Use the PR template. -2. Keep title in Conventional Commit format. -3. Link relevant issue(s). -4. Add tests/docs updates when behavior changes. -5. Ensure CI is green before requesting review. - -## Issues - -Use issue templates for: -- Bug reports -- Feature requests - -High-quality issues include: -- clear reproduction steps -- expected and actual results -- logs or traceback snippets -- OS / Python / Tryx version details - -## Additional Recommendations - -- Prefer squash merge for consistent release history. -- Keep PR size under review-friendly scope. -- Update docs and stubs together with API changes. +### 5. Push and Create PR + +```bash +git push origin feature/my-feature +``` + +--- + +## Code Style + +### Rust + +- Follow standard `rustfmt` formatting +- Use `clippy` for linting +- Keep functions focused and well-documented + +### Python + +- Follow `ruff` formatting (line length 88) +- Use Google-style docstrings in `.pyi` files +- Keep type stubs in sync with Rust implementations + +### Documentation + +- Use Markdown with Material for MkDocs / Zensical conventions +- Include code examples for all API methods +- Keep explanations concise and actionable + +--- + +## Testing + +```bash +# Run all tests +uv run pytest + +# Run specific test file +uv run pytest tests/test_types.py + +# Run with verbose output +uv run pytest -v +``` + +--- + +## Pull Request Guidelines + +1. **One feature per PR** — keep changes focused +2. **Include tests** — for new functionality +3. **Update documentation** — if API surface changes +4. **Run CI checks** — before requesting review +5. **Write clear commit messages** — describe what and why + +--- + +## Getting Help + +- **Issues**: [GitHub Issues](https://github.com/krypton-byte/tryx/issues) +- **Discussions**: [GitHub Discussions](https://github.com/krypton-byte/tryx/discussions) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 5493847..76e3c9c 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -1,81 +1,172 @@ # Installation -This page sets up a local development environment for the Tryx Python bindings backed by Rust. +This guide sets up a working development environment for building with Tryx. -!!! note "Recommended shell flow" - Use `uv` to manage the project environment and dependencies consistently. +!!! tip "One command setup" + ```bash + uv sync --group dev && uv run maturin develop + ``` ## Prerequisites -- Python 3.8+ -- Rust toolchain (stable) -- `uv` +| Tool | Version | Purpose | +|------|---------|---------| +| **Python** | 3.8+ | Runtime | +| **Rust** | stable | Native extension compilation | +| **uv** | latest | Package management | +| **OpenSSL** | 1.1+ | TLS for WebSocket connections | -## Environment Bootstrap +### Install Rust ```bash -uv sync --group dev +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source ~/.cargo/env +``` + +### Install uv + +```bash +curl -LsSf https://astral.sh/uv/install.sh | sh ``` -## Local Development Install +## Quick Setup ```bash +# Clone the repository +git clone https://github.com/krypton-byte/tryx.git +cd tryx + +# Install all dependencies +uv sync --group dev + +# Build the Rust extension into your environment uv run maturin develop ``` -This installs the Rust extension module into your active environment in editable mode. +!!! success "Verify installation" + ```bash + uv run python -c "from tryx.client import Tryx; print('Tryx loaded successfully')" + ``` + If this prints `Tryx loaded successfully`, you're ready to go. -!!! tip "Fast rebuild loop" - Re-run `uv run maturin develop` after Rust binding changes to keep Python runtime artifacts in sync. +## Build Options -## Build Wheel +### Development Build (fast compilation) + +```bash +uv run maturin develop +``` + +This installs the extension module in editable mode. Re-run after Rust source changes. + +### Release Build (optimized binary) ```bash uv run maturin build --release ``` -Typical wheel output appears under `target/wheels/`. +The wheel is output to `target/wheels/`. -## Verify Installation +### With Specific Features -```python -from tryx.client import Tryx, TryxClient -from tryx.backend import SqliteStore +```bash +# Verbose build for debugging +uv run maturin develop -v -backend = SqliteStore("whatsapp.db") -app = Tryx(backend) -client = app.get_client() -print(type(client).__name__) +# Release with debug symbols +uv run maturin develop --release --cargo-extra-args="--profile dev" ``` -If output shows `TryxClient`, extension loading is successful. +## Project Layout -## Optional Tools - -- `uv run mypy ...` or `pyright` for static type checks -- `uv run --no-project --with ruff==0.11.4 ruff check .` for linting (no project build) -- `uv run pytest` for integration test harnesses -- `uv run pre-commit run --all-files` for local gate parity with CI +``` +tryx/ +├── Cargo.toml # Rust dependencies and build config +├── pyproject.toml # Python dependencies and tool config +├── src/ # Rust source code +│ ├── lib.rs # Module entry point +│ ├── clients/ # Client implementations +│ ├── events/ # Event dispatcher +│ └── types.rs # Shared data types +├── python/tryx/ # Python package +│ ├── __init__.py # Re-exports +│ ├── *.pyi # Type stubs for IDE support +│ └── waproto/ # Protobuf definitions +├── tests/ # Test suite +└── examples/ # Usage examples +``` -## Common Install Issues +## Common Issues ### Rust compiler not found -Install Rust with `rustup` and reopen your shell. +```bash +# Install Rust toolchain +rustup default stable +``` ### Build fails with linker errors -Ensure your platform build tools are installed: +=== "Linux" + + ```bash + sudo apt install build-essential libssl-dev pkg-config + ``` + +=== "macOS" -- Linux: `build-essential` and OpenSSL dev headers -- macOS: Xcode command line tools -- Windows: MSVC Build Tools + ```bash + xcode-select --install + ``` + +=== "Windows" + + Install [MSVC Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/). ### ImportError for extension module -Re-run `uv run maturin develop` in the same project environment where you run Python. +Make sure you're using the same Python environment where `maturin develop` was run: + +```bash +# Check which Python your venv uses +which python + +# Rebuild if needed +uv run maturin develop +``` + +### Protobuf version mismatch + +Tryx uses protobuf 5.28+ for code generation. If you see version warnings: + +```bash +uv add "protobuf>=5.28.3,<7" +uv run maturin develop +``` + +## Optional Tools + +| Tool | Install | Purpose | +|------|---------|---------| +| **mypy** | `uv add mypy` | Static type checking | +| **pyright** | `uv add pyright` | Type checking alternative | +| **ruff** | `uv add ruff` | Linting and formatting | +| **pytest** | `uv add pytest` | Test runner | + +```bash +# Type check +uv run mypy your_project/ + +# Lint +uv run ruff check . + +# Format +uv run ruff format . + +# Test +uv run pytest +``` ## Next Step -- Continue to [Quick Start](quickstart.md) -- Then configure pairing in [Authentication Flow](authentication.md) +→ [Quick Start](quickstart.md) — build your first bot in 5 minutes diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index e8600cd..38b64f9 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -1,69 +1,255 @@ # Quick Start -Build and run a minimal echo client, then expand it safely. +Build a working WhatsApp bot in 5 minutes. This guide walks through the +minimal setup, then expands with real functionality. -!!! tip "Expected outcome" - You should receive incoming text and reply with an echo message in the same chat. +## Step 1: Create Your Bot -## Minimal Client +Create a file called `bot.py`: ```python import asyncio - +from tryx.client import Tryx from tryx.backend import SqliteStore -from tryx.client import Tryx, TryxClient from tryx.events import EvMessage -from tryx.waproto.whatsapp_pb2 import Message +# Create storage backend (persists session and protocol state) backend = SqliteStore("whatsapp.db") -app = Tryx(backend) +# Initialize the Tryx runtime +app = Tryx(backend) +# Register an event handler @app.on(EvMessage) -async def on_message(client: TryxClient, event: EvMessage) -> None: - text = event.data.get_text() or "" +async def on_message(client, event): + text = event.data.get_text() or "" chat = event.data.message_info.source.chat - await client.send_message(chat, Message(conversation=f"Echo: {text}")) + if text.lower() == "ping": + await client.send_text(chat, "pong") -async def main() -> None: - await app.run() +# Start the bot +asyncio.run(app.run()) +``` +## Step 2: Run and Pair -if __name__ == "__main__": - asyncio.run(main()) +```bash +python bot.py ``` +On first run, Tryx will emit a pairing event. Scan the QR code with your +WhatsApp mobile app (Linked Devices → Link a Device). + +!!! info "Pairing modes" + - **QR code**: Scan with your phone camera + - **Numeric code**: Enter the 8-digit code on your phone + +After pairing, the session is stored in `whatsapp.db` and you won't need +to pair again. + +## Step 3: Test It + +Send "ping" to your WhatsApp number from another device. The bot should +reply with "pong". + +--- + ## How It Works -1. backend persists pairing/session state -2. `Tryx` runtime wires event dispatcher -3. `@app.on(EvMessage)` registers handler -4. `TryxClient` executes namespace/root API calls +``` +┌──────────────┐ ┌──────────────┐ ┌──────────────┐ +│ SqliteStore │────▶│ Tryx │────▶│ WebSocket │ +│ (persist) │ │ (dispatcher)│ │ (WhatsApp) │ +└──────────────┘ └──────┬───────┘ └──────────────┘ + │ + ┌──────▼───────┐ + │ Your Handler│ + │ @app.on() │ + └──────────────┘ +``` + +1. **`SqliteStore`** creates and manages a local database for session + persistence and Signal protocol state. +2. **`Tryx`** initializes the Rust runtime and event dispatcher. +3. **`@app.on(EvMessage)`** registers your handler to fire on every + incoming message. +4. **`await app.run()`** opens the WebSocket connection and begins + processing events. -## Runtime Flow +--- -1. Create backend storage. -2. Create `Tryx` client instance. -3. Register handlers with `@app.on(EventClass)`. -4. Start runtime with `await app.run()`. -5. Use `TryxClient` inside handlers for API calls. +## Sending Messages -## First Production Hardening +### Text -=== "Reliability" - - deduplicate with message id - - bound retries for network operations +```python +from tryx.types import JID -=== "Safety" - - validate command input - - keep admin-only commands restricted +jid = JID.whatsapp("5599800001") +await client.send_text(jid, "Hello from Tryx!") +``` -=== "Performance" - - keep handlers short - - offload heavy work to worker queue +### Photo with Caption + +```python +with open("photo.jpg", "rb") as f: + photo_data = f.read() + +result = await client.send_photo( + to=jid, + photo_data=photo_data, + caption="Check this out!", +) +print(f"Sent: {result.message_id}") +``` + +### Voice Message + +```python +with open("voice.ogg", "rb") as f: + audio_data = f.read() + +await client.send_audio( + to=jid, + audio_data=audio_data, + ptt=True, # Push-to-talk (voice note) + seconds=15, # Duration hint +) +``` + +### Document + +```python +with open("report.pdf", "rb") as f: + doc_data = f.read() + +await client.send_document( + to=jid, + document_data=doc_data, + file_name="report.pdf", + caption="Monthly report", +) +``` + +### Video + +```python +with open("clip.mp4", "rb") as f: + video_data = f.read() + +await client.send_video( + to=jid, + video_data=video_data, + caption="Check this clip", +) +``` + +### Sticker + +```python +with open("sticker.webp", "rb") as f: + sticker_data = f.read() + +await client.send_sticker(to=jid, sticker_data=sticker_data) +``` + +--- + +## Handling Events + +### Multiple Event Types + +```python +from tryx.events import EvMessage, EvConnected, EvDisconnected + +@app.on(EvConnected) +async def on_connected(client): + print("Connected to WhatsApp!") + +@app.on(EvMessage) +async def on_message(client, event): + text = event.data.get_text() or "" + chat = event.data.message_info.source.chat + + if text == "!ping": + await client.send_text(chat, "Pong!") + elif text == "!info": + stats = await client.advanced.stats() + await client.send_text(chat, f"Stats: {stats}") + elif text == "!groups": + groups = await client.groups.get_participating() + names = [m.subject for m in groups.values()] + await client.send_text(chat, f"Groups: {', '.join(names)}") + +@app.on(EvDisconnected) +async def on_disconnected(client): + print("Disconnected, will reconnect...") +``` + +### Processing Media + +```python +@app.on(EvMessage) +async def handle_media(client, event): + chat = event.data.message_info.source.chat + + if event.data.media: + media_type = event.data.media.media_type + + if media_type == "image": + await client.send_text(chat, "Nice photo!") + elif media_type == "video": + await client.send_text(chat, "Cool video!") + elif media_type == "audio": + await client.send_text(chat, "Got your audio!") +``` + +### Quoting Messages + +```python +@app.on(EvMessage) +async def handle_reply(client, event): + text = event.data.get_text() or "" + chat = event.data.message_info.source.chat + + if text.lower() == "ping": + # Quote the original message + await client.send_text(chat, "pong", quoted=event) +``` + +--- -## Blocking Script Mode +## Error Handling + +Always wrap handler logic to prevent one bad event from crashing your bot: + +```python +import logging + +logger = logging.getLogger(__name__) + +@app.on(EvMessage) +async def safe_handler(client, event): + try: + text = event.data.get_text() or "" + chat = event.data.message_info.source.chat + + if text: + await client.send_text(chat, f"Echo: {text}") + except Exception as e: + logger.error(f"Handler failed: {e}") + # Don't crash — Tryx continues dispatching +``` + +!!! warning "Handler exceptions" + If a handler raises an exception, Tryx catches it and continues + dispatching other events. However, the error is logged and may affect + your bot's reliability. Always handle errors explicitly. + +--- + +## Blocking Mode For quick scripts without manual event loop management: @@ -75,12 +261,37 @@ app = Tryx(SqliteStore("whatsapp.db")) app.run_blocking() ``` -!!! warning - `run_blocking()` is convenient for small scripts. Prefer explicit async runtime control for larger systems. +!!! tip "When to use" + `run_blocking()` is convenient for small scripts and prototyping. + For larger systems, prefer explicit `asyncio.run(app.run())`. + +--- + +## Production Tips + +=== "Reliability" + + - Deduplicate with `event.data.message_info.id` + - Bound retries for network operations + - Handle undecryptable messages gracefully + +=== "Safety" + + - Validate command input + - Restrict admin-only commands + - Rate-limit expensive operations + +=== "Performance" + + - Keep handlers short and async + - Offload heavy work to background tasks + - Use connection pooling for databases + +--- ## Next Steps -- Read [Authentication Flow](authentication.md) to understand pairing and session persistence. -- Explore [Client API Gateway](../api/client.md) for all namespace methods. -- Review [Event Model](../core-concepts/event-model.md) before building complex logic. -- Continue with [Tutorial: Command Automation](../tutorials/command-bot.md). +- [Authentication Flow](authentication.md) — understand pairing and session persistence +- [Architecture](../core-concepts/architecture.md) — how Tryx works internally +- [Client API Gateway](../api/client.md) — all namespace methods +- [Tutorial: Command Bot](../tutorials/command-bot.md) — build a real bot diff --git a/docs/index.md b/docs/index.md index 65e93b0..d634ed0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,99 +1,200 @@ -# Tryx Documentation - -
-

Build WhatsApp Automations With Rust Speed and Python Ergonomics

-

Tryx combines a Rust runtime core with a typed Python API so you can ship robust bots, integrations, and event-driven workflows without sacrificing performance.

-
- Async-first - Typed stubs (.pyi) - PyO3 bindings - Event-driven architecture -
+# Tryx + +**Rust-powered Python SDK for event-driven WhatsApp automation.** + +Tryx pairs a Rust runtime core with a typed Python API. Protocol handling, +media processing, and Signal encryption run at native speed in Rust — your +application logic stays in clean async Python. + +
+ Async-first + PyO3 Native + Typed Stubs (.pyi) + Event-Driven + Signal E2E + Pluggable Storage
-!!! tip "Documentation Mode" - This site is designed as a full path: setup -> architecture -> API namespaces -> tutorials -> production operations. +--- -## What You Can Do +## What You Can Build

Messaging

-

Send text, photo, audio, document, video, GIF, and sticker content with a clean Python API.

+

Send text, photo, audio, document, video, GIF, and stickers through a clean async API with full type safety.

Realtime Events

-

Subscribe to rich event classes for messages, contact updates, sync actions, and lifecycle changes.

+

Subscribe to messages, contact updates, sync actions, and lifecycle changes with typed event classes and decorator-based handlers.

Namespace Clients

-

Use dedicated namespaces for contacts, groups, newsletter, status, privacy, polls, presence, and more.

+

Dedicated clients for contacts, groups, newsletters, status, privacy, polls, presence, communities, and chat actions.

-

Typed Development

-

Use complete Python stubs for editor intelligence, static checks, and better API discoverability.

+

Pluggable Storage

+

SQLite (built-in), FFI shared libraries (Postgres, Redis), or pure Python backends — all with the same Signal protocol store API.

-## Choose Your Path - -=== "I am New" - 1. [Installation](getting-started/installation.md) - 2. [Quick Start](getting-started/quickstart.md) - 3. [Authentication Flow](getting-started/authentication.md) - 4. [Client API Gateway](api/client.md) +--- -=== "I am Building Features" - 1. [Client Namespaces](api/client.md) - 2. [Events API](api/events.md) - 3. [Types API](api/types.md) - 4. [Tutorials](tutorials/command-bot.md) +## Quick Start -=== "I am Deploying" - 1. [Deployment Guide](operations/deployment.md) - 2. [Reliability](operations/reliability.md) - 3. [Troubleshooting](operations/troubleshooting.md) - 4. [Security](operations/security.md) +```python +from tryx.client import Tryx +from tryx.backend import SqliteStore -## Recommended Reading Path +app = Tryx(SqliteStore("whatsapp.db")) +client = app.get_client() -1. Start with [Installation](getting-started/installation.md). -2. Follow [Quick Start](getting-started/quickstart.md) to build your first running client. -3. Understand pairing in [Authentication Flow](getting-started/authentication.md). -4. Learn internals in [Architecture](core-concepts/architecture.md) and [Event Model](core-concepts/event-model.md). -5. Jump into [Client API Gateway](api/client.md) and namespace deep dives. -6. Use [Tutorials](tutorials/command-bot.md) for implementation patterns. -7. Use [QnA](faq/qna.md) and [Troubleshooting](operations/troubleshooting.md) when debugging. -8. Finish with [Deployment](operations/deployment.md) and [Reliability](operations/reliability.md) before production. +@app.on(EvMessage) +async def on_message(client, event): + text = event.data.get_text() + chat = event.data.message_info.source.chat + if text: + await client.send_text(chat, f"Echo: {text}") -## Project Scope +app.run_blocking() +``` -This documentation set focuses on the Python SDK experience first: +