diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
new file mode 100644
index 000000000..867978dd8
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -0,0 +1,147 @@
+name: Bug Report
+description: Report a bug in Stoat — please fill out every required field to help us triage quickly.
+title: "[Bug]: "
+labels:
+ - bug
+ - unconfirmed
+
+body:
+ # ── Thank you section ────────────────────────────────────────────────────────
+ - type: markdown
+ attributes:
+ value: |
+ **Thank you for taking the time to report a bug for Stoat!**
+
+ Please follow the instructions below and provide as much detail as possible to help us understand and reproduce the issue.
+
+ # ── Pre-flight checks ────────────────────────────────────────────────────────
+ - type: checkboxes
+ id: preflight
+ attributes:
+ label: Pre-flight checklist
+ description: Please confirm all of the following before submitting.
+ options:
+ - label: I am on the latest available version of Stoat.
+ required: true
+ - label: I searched existing issues and did not find a duplicate.
+ required: true
+ - label: This is a bug report, not a support question or feature request.
+ required: true
+
+ # ── Bug description ──────────────────────────────────────────────────────────
+ - type: textarea
+ id: description
+ attributes:
+ label: Bug description
+ description: >
+ What happened? What did you expect to happen instead?
+ Keep this brief — detailed steps go in the next field.
+ placeholder: "Example: When I open a DM thread, new messages from the other person do not appear unless I reload the page."
+ validations:
+ required: true
+
+ # ── Steps to reproduce ───────────────────────────────────────────────────────
+ - type: textarea
+ id: steps
+ attributes:
+ label: Steps to reproduce
+ description: >
+ Provide a numbered list of every step needed to trigger the bug.
+ The clearer this is, the faster we can fix it.
+ placeholder: |
+ 1. Log in to Stoat on the web client.
+ 2. Open a direct message conversation.
+ 3. Ask the other person to send a message.
+ 4. Observe that the message does not appear without reloading.
+ validations:
+ required: true
+
+ # ── Expected behavior ────────────────────────────────────────────────────────
+ - type: textarea
+ id: expected
+ attributes:
+ label: Expected behavior
+ description: What should have happened?
+ placeholder: "New messages should appear in real time without a page reload."
+ validations:
+ required: true
+
+ # ── Client / platform ────────────────────────────────────────────────────────
+ - type: dropdown
+ id: platform
+ attributes:
+ label: Client / platform
+ description: Which Stoat client are you using?
+ options:
+ - Web (browser)
+ - Stoat for Desktop
+ - Android
+ - iOS
+ - Self-hosted server
+ validations:
+ required: true
+
+ # ── Client version ───────────────────────────────────────────────────────────
+ - type: input
+ id: version
+ attributes:
+ label: Client version
+ description: >
+ Find this in **Settings**. For the web client, include the build
+ number shown in the footer or About page.
+ placeholder: "e.g. Stoat for Desktop 1.3.0 or 0.2.1 (2025-10-10)"
+ validations:
+ required: true
+
+ # ── OS / browser details ─────────────────────────────────────────────────────
+ - type: input
+ id: os
+ attributes:
+ label: OS / browser details
+ description: Your operating system and, for web bugs, your browser and its version.
+ placeholder: "e.g. Windows 11, Chrome 121 or macOS 14.3, Stoat for Desktop 1.3.0 or Android 14"
+ validations:
+ required: true
+
+ # ── Reproducibility ──────────────────────────────────────────────────────────
+ - type: dropdown
+ id: reproducibility
+ attributes:
+ label: Reproducibility
+ description: How consistently does this bug occur?
+ options:
+ - Always
+ - Usually
+ - Rarely
+ - Only once
+ - Unknown
+ validations:
+ required: true
+
+ # ── Screenshots or video ─────────────────────────────────────────────────────
+ - type: textarea
+ id: screenshots
+ attributes:
+ label: Screenshots or video
+ description: Paste images or drag-and-drop a screen recording here. GitHub accepts common image and video formats.
+ validations:
+ required: false
+
+ # ── Additional context ───────────────────────────────────────────────────────
+ - type: textarea
+ id: additional
+ attributes:
+ label: Additional context
+ description: >
+ Anything else that might help: workarounds you found, links to related issues,
+ self-hosted configuration details (Redis, MongoDB, LiveKit, SMTP), network environment, etc.
+ validations:
+ required: false
+
+ # ── Closing note ─────────────────────────────────────────────────────────────
+ - type: markdown
+ attributes:
+ value: |
+ ---
+ A maintainer will review your report. Please watch for follow-up questions — issues
+ that go unanswered are harder to resolve and may be closed after 30 days of inactivity.
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 000000000..da918526e
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,24 @@
+
+
+Fixes # (issue)
+
+## How was this PR tested?
+
+
+
+- [ ] Test A
+- [ ] Test B
+
+## Checklist:
+
+- [ ] I have carefully read [the contributing guidelines](https://developers.stoat.chat/developing/contrib/)
+- [ ] I have performed a self-review of my own code
+- [ ] I have made corresponding changes to the documentation if applicable
+- [ ] I have no unrelated changes in the PR
+- [ ] I have confirmed that any new dependencies are strictly necessary
+- [ ] I have written tests for new code (if applicable)
+- [ ] I have followed naming conventions/patterns in the surrounding code
+
+## Please declare, if any, LLM usage involved in creating this PR
+
+...
diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml
deleted file mode 100644
index 00f7ef712..000000000
--- a/.github/workflows/book.yml
+++ /dev/null
@@ -1,37 +0,0 @@
-name: Build documentation
-on:
- push:
- branches:
- - main
-
-jobs:
- deploy:
- runs-on: ubuntu-latest
- permissions:
- contents: write # To push a branch
- pages: write # To push to a GitHub Pages site
- id-token: write # To update the deployment status
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
- - name: Install latest mdbook
- run: |
- tag=$(curl 'https://api.github.com/repos/rust-lang/mdbook/releases/latest' | jq -r '.tag_name')
- url="https://github.com/rust-lang/mdbook/releases/download/${tag}/mdbook-${tag}-x86_64-unknown-linux-gnu.tar.gz"
- mkdir mdbook
- curl -sSL $url | tar -xz --directory=./mdbook
- echo `pwd`/mdbook >> $GITHUB_PATH
- - name: Build Book
- run: |
- cd doc
- mdbook build
- - name: Setup Pages
- uses: actions/configure-pages@v4
- - name: Upload artifact
- uses: actions/upload-pages-artifact@v3
- with:
- path: "doc/book"
- - name: Deploy to GitHub Pages
- id: deployment
- uses: actions/deploy-pages@v4
diff --git a/.github/workflows/docker-cleanup.yaml b/.github/workflows/docker-cleanup.yaml
new file mode 100644
index 000000000..830619495
--- /dev/null
+++ b/.github/workflows/docker-cleanup.yaml
@@ -0,0 +1,46 @@
+name: Docker PR Image Cleanup
+
+on:
+ pull_request:
+ types:
+ - closed
+
+permissions:
+ contents: read
+ packages: write
+
+concurrency:
+ group: docker-cleanup-${{ github.event.pull_request.number }}
+ cancel-in-progress: false
+
+jobs:
+ cleanup:
+ runs-on: ubuntu-latest
+ if: ${{ !github.event.pull_request.head.repo.fork }}
+ strategy:
+ fail-fast: false
+ matrix:
+ package:
+ - base
+ - api
+ - events
+ - file-server
+ - proxy
+ - gifbox
+ - crond
+ - pushd
+ - voice-ingress
+ steps:
+ - env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ ORG: stoatchat
+ PACKAGE: ${{ matrix.package }}
+ TAG: pr-${{ github.event.pull_request.number }}
+ run: |
+ set -euo pipefail
+ gh api --paginate \
+ "/orgs/${ORG}/packages/container/${PACKAGE}/versions" \
+ --jq ".[] | select(.metadata.container.tags | index(\"${TAG}\")) | .id" \
+ | while read -r id; do
+ gh api -X DELETE "/orgs/${ORG}/packages/container/${PACKAGE}/versions/${id}"
+ done
diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml
index fccaf6341..75aefeb63 100644
--- a/.github/workflows/docker.yaml
+++ b/.github/workflows/docker.yaml
@@ -5,20 +5,21 @@ on:
tags:
- "*"
pull_request:
- branches:
- - "main"
- paths:
- - "Dockerfile"
+ workflow_dispatch:
permissions:
contents: read
packages: write
+concurrency:
+ group: ${{ github.head_ref || github.ref }}
+ cancel-in-progress: true
+
jobs:
base:
- name: Test base image build
- runs-on: ubuntu-latest
- if: github.event_name == 'pull_request'
+ name: Test base image build (fork)
+ runs-on: arc-runner-set
+ if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork
steps:
# Configure build environment
- name: Checkout
@@ -38,8 +39,8 @@ jobs:
cache-to: type=gha,scope=buildx-base-multi-arch,mode=max
publish:
- runs-on: self-hosted
- if: github.event_name != 'pull_request'
+ runs-on: arc-runner-set
+ if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork }}
name: Publish Docker images
steps:
# Configure build environment
@@ -49,13 +50,6 @@ jobs:
uses: docker/setup-buildx-action@v2
# Authenticate with Docker Hub and GHCR
- - name: Login to DockerHub
- uses: docker/login-action@v2
- with:
- registry: docker.io
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
-
- name: Login to Github Container Registry
uses: docker/login-action@v2
with:
@@ -63,6 +57,15 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
+ - name: Determine base image tag
+ id: base
+ run: |
+ if [ "${{ github.event_name }}" = "pull_request" ]; then
+ echo "tag=pr-${{ github.event.number }}" >> "$GITHUB_OUTPUT"
+ else
+ echo "tag=latest" >> "$GITHUB_OUTPUT"
+ fi
+
# Build the image
- name: Build base image
uses: docker/build-push-action@v4
@@ -70,16 +73,17 @@ jobs:
context: .
push: true
platforms: linux/amd64,linux/arm64
- tags: ghcr.io/${{ github.repository_owner }}/base:latest
+ tags: ghcr.io/${{ github.repository_owner }}/base:${{ steps.base.outputs.tag }}
+ cache-from: type=gha,scope=buildx-base-multi-arch
+ cache-to: type=gha,scope=buildx-base-multi-arch,mode=max
- # revoltchat/server
+ # stoatchat/api
- name: Docker meta
id: meta-delta
uses: docker/metadata-action@v4
with:
images: |
- docker.io/revoltchat/server
- ghcr.io/revoltchat/server
+ ghcr.io/stoatchat/api
- name: Publish
uses: docker/build-push-action@v4
with:
@@ -89,17 +93,16 @@ jobs:
file: crates/delta/Dockerfile
tags: ${{ steps.meta-delta.outputs.tags }}
build-args: |
- BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
+ BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:${{ steps.base.outputs.tag }}
labels: ${{ steps.meta-delta.outputs.labels }}
- # revoltchat/bonfire
+ # stoatchat/events
- name: Docker meta
id: meta-bonfire
uses: docker/metadata-action@v4
with:
images: |
- docker.io/revoltchat/bonfire
- ghcr.io/revoltchat/bonfire
+ ghcr.io/stoatchat/events
- name: Publish
uses: docker/build-push-action@v4
with:
@@ -109,17 +112,16 @@ jobs:
file: crates/bonfire/Dockerfile
tags: ${{ steps.meta-bonfire.outputs.tags }}
build-args: |
- BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
+ BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:${{ steps.base.outputs.tag }}
labels: ${{ steps.meta-bonfire.outputs.labels }}
- # revoltchat/autumn
+ # stoatchat/file-server
- name: Docker meta
id: meta-autumn
uses: docker/metadata-action@v4
with:
images: |
- docker.io/revoltchat/autumn
- ghcr.io/revoltchat/autumn
+ ghcr.io/stoatchat/file-server
- name: Publish
uses: docker/build-push-action@v4
with:
@@ -129,17 +131,16 @@ jobs:
file: crates/services/autumn/Dockerfile
tags: ${{ steps.meta-autumn.outputs.tags }}
build-args: |
- BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
+ BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:${{ steps.base.outputs.tag }}
labels: ${{ steps.meta-autumn.outputs.labels }}
- # revoltchat/january
+ # stoatchat/proxy
- name: Docker meta
id: meta-january
uses: docker/metadata-action@v4
with:
images: |
- docker.io/revoltchat/january
- ghcr.io/revoltchat/january
+ ghcr.io/stoatchat/proxy
- name: Publish
uses: docker/build-push-action@v4
with:
@@ -149,17 +150,35 @@ jobs:
file: crates/services/january/Dockerfile
tags: ${{ steps.meta-january.outputs.tags }}
build-args: |
- BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
+ BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:${{ steps.base.outputs.tag }}
labels: ${{ steps.meta-january.outputs.labels }}
- # revoltchat/crond
+ # stoatchat/gifbox
+ - name: Docker meta
+ id: meta-gifbox
+ uses: docker/metadata-action@v4
+ with:
+ images: |
+ ghcr.io/stoatchat/gifbox
+ - name: Publish
+ uses: docker/build-push-action@v4
+ with:
+ context: .
+ push: true
+ platforms: linux/amd64,linux/arm64
+ file: crates/services/gifbox/Dockerfile
+ tags: ${{ steps.meta-gifbox.outputs.tags }}
+ build-args: |
+ BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:${{ steps.base.outputs.tag }}
+ labels: ${{ steps.meta-gifbox.outputs.labels }}
+
+ # stoatchat/crond
- name: Docker meta
id: meta-crond
uses: docker/metadata-action@v4
with:
images: |
- docker.io/revoltchat/crond
- ghcr.io/revoltchat/crond
+ ghcr.io/stoatchat/crond
- name: Publish
uses: docker/build-push-action@v4
with:
@@ -169,17 +188,16 @@ jobs:
file: crates/daemons/crond/Dockerfile
tags: ${{ steps.meta-crond.outputs.tags }}
build-args: |
- BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
+ BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:${{ steps.base.outputs.tag }}
labels: ${{ steps.meta-crond.outputs.labels }}
- # revoltchat/pushd
+ # stoatchat/pushd
- name: Docker meta
id: meta-pushd
uses: docker/metadata-action@v4
with:
images: |
- docker.io/revoltchat/pushd
- ghcr.io/revoltchat/pushd
+ ghcr.io/stoatchat/pushd
- name: Publish
uses: docker/build-push-action@v4
with:
@@ -189,5 +207,24 @@ jobs:
file: crates/daemons/pushd/Dockerfile
tags: ${{ steps.meta-pushd.outputs.tags }}
build-args: |
- BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
+ BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:${{ steps.base.outputs.tag }}
labels: ${{ steps.meta-pushd.outputs.labels }}
+
+ # stoatchat/voice-ingress
+ - name: Docker meta
+ id: meta-voice-ingress
+ uses: docker/metadata-action@v4
+ with:
+ images: |
+ ghcr.io/stoatchat/voice-ingress
+ - name: Publish
+ uses: docker/build-push-action@v4
+ with:
+ context: .
+ push: true
+ platforms: linux/amd64,linux/arm64
+ file: crates/daemons/voice-ingress/Dockerfile
+ tags: ${{ steps.meta-voice-ingress.outputs.tags }}
+ build-args: |
+ BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:${{ steps.base.outputs.tag }}
+ labels: ${{ steps.meta-voice-ingress.outputs.labels }}
diff --git a/.github/workflows/docs-test.yml b/.github/workflows/docs-test.yml
new file mode 100644
index 000000000..26a065fc1
--- /dev/null
+++ b/.github/workflows/docs-test.yml
@@ -0,0 +1,23 @@
+name: Documentation (test)
+
+on:
+ pull_request:
+
+jobs:
+ test-deploy:
+ name: Test deployment
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./docs
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Mise
+ uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+
+ - run: mise docs:build
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
new file mode 100644
index 000000000..451deda74
--- /dev/null
+++ b/.github/workflows/docs.yml
@@ -0,0 +1,48 @@
+name: Documentation
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ build:
+ name: Build Docusaurus
+ runs-on: ubuntu-latest
+ defaults:
+ run:
+ working-directory: ./docs
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Setup Mise
+ uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+
+ - run: mise docs:build
+
+ - name: Upload Build Artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: ./docs/build
+
+ deploy:
+ name: Deploy to GitHub Pages
+ needs: build
+
+ permissions:
+ pages: write # to deploy to Pages
+ id-token: write # to verify the deployment originates from an appropriate source
+
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+
+ runs-on: ubuntu-latest
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.github/workflows/git-town.yml b/.github/workflows/git-town.yml
new file mode 100644
index 000000000..678b60c1f
--- /dev/null
+++ b/.github/workflows/git-town.yml
@@ -0,0 +1,23 @@
+# DO NOT EDIT DIRECTLY IN REPOSITORY
+# Managed in Terraform templates
+
+name: Git Town
+
+on:
+ pull_request:
+
+jobs:
+ git-town:
+ name: Display the branch stack
+ runs-on: ubuntu-slim
+
+ if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !startsWith(github.head_ref, 'release-please--') }}
+
+ permissions:
+ contents: read
+ pull-requests: write
+
+ steps:
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ - uses: stoatchat/action-git-town@4bc5c942e4603bffa0806b51d5fe5f0bc5deb0ac
+ continue-on-error: true
\ No newline at end of file
diff --git a/.github/workflows/publish-crates.yml b/.github/workflows/publish-crates.yml
new file mode 100644
index 000000000..5e35d3580
--- /dev/null
+++ b/.github/workflows/publish-crates.yml
@@ -0,0 +1,26 @@
+name: Publish Crates
+
+on:
+ workflow_dispatch:
+ release:
+ types: [published]
+
+jobs:
+ publish:
+ name: Publish Crates
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ with:
+ persist-credentials: false
+
+ - name: Setup Mise
+ uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Publish
+ env:
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
+ run: mise publish --workspace
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
new file mode 100644
index 000000000..63d9a88e3
--- /dev/null
+++ b/.github/workflows/release-please.yml
@@ -0,0 +1,63 @@
+name: Release Please
+
+on:
+ push:
+ branches: [main] # updates/opens the release PR when commits land on main
+ workflow_dispatch:
+
+permissions:
+ contents: write
+ pull-requests: write
+ id-token: write
+
+concurrency:
+ group: release-please
+ cancel-in-progress: true
+
+jobs:
+ release-please:
+ name: Release Please
+ runs-on: ubuntu-latest
+ outputs:
+ release_created: ${{ steps.rp.outputs.release_created }}
+ tag_name: ${{ steps.rp.outputs.tag_name }}
+ steps:
+ - id: app-token
+ uses: actions/create-github-app-token@v2
+ with:
+ app-id: ${{ secrets.GH_STOAT_RELEASE_APP_ID }}
+ private-key: ${{ secrets.GH_STOAT_RELEASE_APP_PRIVATE_KEY }}
+ - id: rp
+ uses: googleapis/release-please-action@v4
+ with:
+ token: ${{ steps.app-token.outputs.token }}
+ config-file: release-please-config.json
+
+ - name: Install latest stable
+ uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # v1
+ with:
+ toolchain: stable
+
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+ with:
+ token: ${{ steps.app-token.outputs.token }}
+ - name: Update Cargo.lock
+ if: ${{ steps.rp.outputs.prs_created == 'true' || steps.rp.outputs.prs_updated == 'true' }}
+ env:
+ GH_TOKEN: ${{ steps.app-token.outputs.token }}
+ RP_PR_OUTPUT_JSON: ${{ steps.rp.outputs.prs }}
+ run: |
+ PR_NUMBER=$(echo "$RP_PR_OUTPUT_JSON" | jq -r '.[0].number')
+ gh pr checkout "$PR_NUMBER"
+
+ cargo update -w
+
+ if git diff --quiet Cargo.lock; then
+ echo "No changes to Cargo.lock"
+ else
+ git config user.name "github-actions[bot]"
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git add Cargo.lock
+ git commit -s -m "chore: update Cargo.lock"
+ git push
+ fi
diff --git a/.github/workflows/release-webhook.yml b/.github/workflows/release-webhook.yml
new file mode 100644
index 000000000..3996b1f87
--- /dev/null
+++ b/.github/workflows/release-webhook.yml
@@ -0,0 +1,26 @@
+# DO NOT EDIT DIRECTLY IN REPOSITORY
+# Managed in Terraform templates
+
+name: Release Webhook
+
+on:
+ workflow_dispatch:
+ release:
+ types: [published]
+
+jobs:
+ release-webhook:
+ name: Send Release Webhook
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Send release notification webhook
+ env:
+ TAG_NAME: ${{ github.event.release.tag_name }}
+ REPOSITORY: ${{ github.repository }}
+ WEBHOOK_URL: ${{ secrets.STOAT_WEBHOOK_UPDATES_URL }}
+ run: |
+ RELEASE_URL="https://github.com/${REPOSITORY}/releases/tag/${TAG_NAME}"
+ curl -X POST "$WEBHOOK_URL" \
+ -H "Content-Type: application/json" \
+ -d "{\"content\": \"$RELEASE_URL\"}"
\ No newline at end of file
diff --git a/.github/workflows/renovate.yml b/.github/workflows/renovate.yml
new file mode 100644
index 000000000..7ce0d5ba0
--- /dev/null
+++ b/.github/workflows/renovate.yml
@@ -0,0 +1,30 @@
+# DO NOT EDIT DIRECTLY IN REPOSITORY
+# Managed in Terraform templates
+
+name: Renovate
+on:
+ workflow_dispatch:
+ schedule:
+ - cron: '0/15 * * * *'
+jobs:
+ renovate:
+ runs-on: ubuntu-latest
+ steps:
+ - id: app-token
+ uses: actions/create-github-app-token@v2
+ with:
+ app-id: ${{ secrets.GH_STOAT_RELEASE_APP_ID }}
+ private-key: ${{ secrets.GH_STOAT_RELEASE_APP_PRIVATE_KEY }}
+
+ - name: Setup Mise
+ uses: immich-app/devtools/actions/use-mise@7b8610a904d57da241e4ddba17fa62b62b15aed4 # use-mise-action-v2.0.2
+ with:
+ github_token: ${{ steps.app-token.outputs.token }}
+
+ - name: Self-hosted Renovate
+ uses: renovatebot/github-action@v46.1.14
+ with:
+ token: '${{ steps.app-token.outputs.token }}'
+ env:
+ RENOVATE_PLATFORM_COMMIT: 'enabled'
+ RENOVATE_REPOSITORIES: '${{ github.repository }}'
\ No newline at end of file
diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml
index f0bd33894..3b7a78d22 100644
--- a/.github/workflows/rust.yaml
+++ b/.github/workflows/rust.yaml
@@ -2,16 +2,12 @@ name: Rust build, test, and generate specification
on:
push:
- paths-ignore:
- - ".github/**"
- - "!.github/workflows/rust.yaml"
- - ".vscode/**"
- - "doc/**"
- - ".gitignore"
- - "LICENSE"
- - "README"
- pull_request:
branches: [main]
+ pull_request:
+
+concurrency:
+ group: ${{ github.head_ref || github.ref }}
+ cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
@@ -19,63 +15,60 @@ env:
jobs:
check:
name: Rust project
- runs-on: ubuntu-latest
+ runs-on: arc-runner-set
steps:
- - uses: actions/checkout@v2
- - name: Install latest stable
- uses: actions-rs/toolchain@v1
- with:
- toolchain: stable
- override: true
- components: rustfmt, clippy
- - name: Install cargo-nextest
- uses: baptiste0928/cargo-install@v1
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
- crate: cargo-nextest
- locked: true
+ persist-credentials: false
- - name: Run cargo build
- uses: actions-rs/cargo@v1
- with:
- command: build
+ # Using our own runners for now:
+ # - name: Free up disk space
+ # run: |
+ # sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc
- - name: Run services in background
- run: |
- docker compose -f compose.yml up -d
+ - name: Setup Mise
+ uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
- - name: Run cargo test
+ - run: mise build
+ - run: mise docker:start
+
+ - name: Reference Test
env:
TEST_DB: REFERENCE
+ continue-on-error: ${{ github.ref_name == 'main' }}
run: |
- cargo nextest run
+ mise test
- - name: Run cargo test (with MongoDB)
+ - name: MongoDB Test
env:
TEST_DB: MONGODB
MONGODB: mongodb://localhost
+ continue-on-error: ${{ github.ref_name == 'main' }}
run: |
- cargo nextest run
+ mise test
- name: Start API in background
if: github.event_name != 'pull_request' && github.ref_name == 'main'
env:
TEST_DB: REFERENCE
run: |
- cargo build --bin revolt-delta && (cargo run --bin revolt-delta &)
+ mise build --bin revolt-delta && (mise service:api &)
- name: Wait for API to go up
if: github.event_name != 'pull_request' && github.ref_name == 'main'
- uses: nev7n/wait_for_response@v1
+ uses: nev7n/wait_for_response@7fef3c1a6e8939d0b09062f14fec50d3c5d15fa1 # v1.0.1
with:
url: "http://localhost:14702/"
- name: Checkout API repository
if: github.event_name != 'pull_request' && github.ref_name == 'main'
- uses: actions/checkout@v3
+ uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
- repository: revoltchat/api
+ repository: stoatchat/javascript-client-api
path: api
- token: ${{ secrets.PAT }}
+ ssh-key: ${{ secrets.DEPLOY_KEY_JAVASCRIPT_CLIENT_API }}
- name: Download OpenAPI specification
if: github.event_name != 'pull_request' && github.ref_name == 'main'
@@ -83,10 +76,10 @@ jobs:
- name: Commit changes
if: github.event_name != 'pull_request' && github.ref_name == 'main'
- uses: EndBug/add-and-commit@v4
+ uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
with:
cwd: "api"
add: "*.json"
- author_name: Revolt CI
- author_email: revolt-ci@users.noreply.github.com
+ author_name: Stoat CI
+ author_email: stoat-ci@users.noreply.github.com
message: "chore: generate OpenAPI specification"
diff --git a/.github/workflows/triage_issue.yml b/.github/workflows/triage_issue.yml
deleted file mode 100644
index ecc69f590..000000000
--- a/.github/workflows/triage_issue.yml
+++ /dev/null
@@ -1,54 +0,0 @@
-name: Add Issue to Board
-
-on:
- issues:
- types: [opened]
-
-jobs:
- track_issue:
- runs-on: ubuntu-latest
- steps:
- - name: Get project data
- env:
- GITHUB_TOKEN: ${{ secrets.PAT }}
- run: |
- gh api graphql -f query='
- query {
- organization(login: "revoltchat"){
- projectV2(number: 3) {
- id
- fields(first:20) {
- nodes {
- ... on ProjectV2SingleSelectField {
- id
- name
- options {
- id
- name
- }
- }
- }
- }
- }
- }
- }' > project_data.json
-
- echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
- echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
- echo 'TODO_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV
-
- - name: Add issue to project
- env:
- GITHUB_TOKEN: ${{ secrets.PAT }}
- ISSUE_ID: ${{ github.event.issue.node_id }}
- run: |
- item_id="$( gh api graphql -f query='
- mutation($project:ID!, $issue:ID!) {
- addProjectV2ItemById(input: {projectId: $project, contentId: $issue}) {
- item {
- id
- }
- }
- }' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectV2ItemById.item.id')"
-
- echo 'ITEM_ID='$item_id >> $GITHUB_ENV
diff --git a/.github/workflows/triage_pr.yml b/.github/workflows/triage_pr.yml
deleted file mode 100644
index 3010d2e5e..000000000
--- a/.github/workflows/triage_pr.yml
+++ /dev/null
@@ -1,79 +0,0 @@
-name: Add PR to Board
-
-on:
- pull_request_target:
- types: [opened, synchronize, ready_for_review, review_requested]
-
-jobs:
- track_pr:
- runs-on: ubuntu-latest
- steps:
- - name: Get project data
- env:
- GITHUB_TOKEN: ${{ secrets.PAT }}
- run: |
- gh api graphql -f query='
- query {
- organization(login: "revoltchat"){
- projectV2(number: 5) {
- id
- fields(first:20) {
- nodes {
- ... on ProjectV2SingleSelectField {
- id
- name
- options {
- id
- name
- }
- }
- }
- }
- }
- }
- }' > project_data.json
-
- echo 'PROJECT_ID='$(jq '.data.organization.projectV2.id' project_data.json) >> $GITHUB_ENV
- echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
- echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectV2.fields.nodes[] | select(.name== "Status") | .options[] | select(.name=="🆕 Untriaged") |.id' project_data.json) >> $GITHUB_ENV
-
- - name: Add PR to project
- env:
- GITHUB_TOKEN: ${{ secrets.PAT }}
- PR_ID: ${{ github.event.pull_request.node_id }}
- run: |
- item_id="$( gh api graphql -f query='
- mutation($project:ID!, $pr:ID!) {
- addProjectV2ItemById(input: {projectId: $project, contentId: $pr}) {
- item {
- id
- }
- }
- }' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectV2ItemById.item.id')"
-
- echo 'ITEM_ID='$item_id >> $GITHUB_ENV
-
- - name: Set fields
- env:
- GITHUB_TOKEN: ${{ secrets.PAT }}
- run: |
- gh api graphql -f query='
- mutation (
- $project: ID!
- $item: ID!
- $status_field: ID!
- $status_value: String!
- ) {
- set_status: updateProjectV2ItemFieldValue(input: {
- projectId: $project
- itemId: $item
- fieldId: $status_field
- value: {
- singleSelectOptionId: $status_value
- }
- }) {
- projectV2Item {
- id
- }
- }
- }' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.INCOMING_OPTION_ID }} --silent
diff --git a/.github/workflows/validate-pr-title.yml b/.github/workflows/validate-pr-title.yml
new file mode 100644
index 000000000..1374d58cc
--- /dev/null
+++ b/.github/workflows/validate-pr-title.yml
@@ -0,0 +1,23 @@
+# DO NOT EDIT DIRECTLY IN REPOSITORY
+# Managed in Terraform templates
+
+name: "Lint PR"
+
+on:
+ pull_request_target:
+ types:
+ - opened
+ - reopened
+ - edited
+ - synchronize
+
+jobs:
+ main:
+ name: Validate PR title
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: read
+ steps:
+ - uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 8483bc435..ae3bfadb5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,4 +11,5 @@ venv/
.vercel
.DS_Store
+livekit.yml
.idea
\ No newline at end of file
diff --git a/.mise/config.toml b/.mise/config.toml
new file mode 100644
index 000000000..f5238e777
--- /dev/null
+++ b/.mise/config.toml
@@ -0,0 +1,28 @@
+[tools]
+node = "25.4.0"
+pnpm = "10.28.1"
+
+gh = "2.25.0"
+
+rust = "1.92.0"
+"cargo:cargo-nextest" = "0.9.122"
+
+"github:git-town/git-town" = "22.4.0"
+
+[settings]
+experimental = true
+idiomatic_version_file_enable_tools = ["rust"]
+
+[tasks.start]
+description = "Run all services"
+depends = ["docker:start", "build"]
+wait_for = ["docker:start", "build"]
+run = [{ task = "service:*" }]
+
+[env]
+BUILDER = "cargo"
+DOCKER_NETWORK_NAME = "stoatchat_default"
+DATABASE_PORT = "27017"
+RABBIT_PORT = "5672"
+REDIS_PORT = "6379"
+_.file = { path = ".env", tools = true }
diff --git a/.mise/tasks/build b/.mise/tasks/build
new file mode 100755
index 000000000..2e584eaf4
--- /dev/null
+++ b/.mise/tasks/build
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+#MISE description="Build project"
+set -e
+
+${BUILDER} build "$@"
diff --git a/.mise/tasks/check b/.mise/tasks/check
new file mode 100755
index 000000000..116f10166
--- /dev/null
+++ b/.mise/tasks/check
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+#MISE description="Check project with clippy"
+set -e
+
+cargo clippy
diff --git a/.mise/tasks/docker/start b/.mise/tasks/docker/start
new file mode 100755
index 000000000..c045a4bd0
--- /dev/null
+++ b/.mise/tasks/docker/start
@@ -0,0 +1,13 @@
+#!/usr/bin/env bash
+#MISE description="Start Docker containers"
+set -e
+
+docker compose up -d
+
+docker run \
+--network=${DOCKER_NETWORK_NAME} \
+--name wait \
+--rm dokku/wait -c \
+rabbit:${RABBIT_PORT},\
+database:${DATABASE_PORT},\
+redis:${REDIS_PORT}
diff --git a/.mise/tasks/docker/stop b/.mise/tasks/docker/stop
new file mode 100755
index 000000000..51d39d613
--- /dev/null
+++ b/.mise/tasks/docker/stop
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+#MISE description="Stop Docker containers"
+set -e
+
+docker compose down
diff --git a/.mise/tasks/docs/_default b/.mise/tasks/docs/_default
new file mode 100755
index 000000000..742d586fd
--- /dev/null
+++ b/.mise/tasks/docs/_default
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+#MISE description="Start the Stoat Developers website"
+#MISE depends=["docs:install"]
+#MISE dir="{{config_root}}/docs"
+set -e
+
+pnpm build
diff --git a/.mise/tasks/docs/build b/.mise/tasks/docs/build
new file mode 100755
index 000000000..ae3cdb65f
--- /dev/null
+++ b/.mise/tasks/docs/build
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+#MISE description="Build the Stoat Developers website"
+#MISE depends=["docs:install"]
+#MISE dir="{{config_root}}/docs"
+set -e
+
+pnpm build
diff --git a/.mise/tasks/docs/install b/.mise/tasks/docs/install
new file mode 100755
index 000000000..753779c1d
--- /dev/null
+++ b/.mise/tasks/docs/install
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+#MISE description="Install dependencies for docs site"
+#MISE dir="{{config_root}}/docs"
+set -e
+
+pnpm i --frozen-lockfile
diff --git a/.mise/tasks/publish b/.mise/tasks/publish
new file mode 100755
index 000000000..27df8f9a7
--- /dev/null
+++ b/.mise/tasks/publish
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+#MISE description="Publish project"
+set -e
+
+cargo publish "$@"
diff --git a/.mise/tasks/service/api b/.mise/tasks/service/api
new file mode 100755
index 000000000..07915c17c
--- /dev/null
+++ b/.mise/tasks/service/api
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+#MISE description="Run API server"
+set -e
+
+cargo run --bin revolt-delta
diff --git a/.mise/tasks/service/crond b/.mise/tasks/service/crond
new file mode 100755
index 000000000..ce4bc4918
--- /dev/null
+++ b/.mise/tasks/service/crond
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+#MISE description="Run cron daemon"
+set -e
+
+cargo run --bin revolt-crond
diff --git a/.mise/tasks/service/events b/.mise/tasks/service/events
new file mode 100755
index 000000000..85bea49a6
--- /dev/null
+++ b/.mise/tasks/service/events
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+#MISE description="Run events server"
+set -e
+
+cargo run --bin revolt-bonfire
diff --git a/.mise/tasks/service/files b/.mise/tasks/service/files
new file mode 100755
index 000000000..431c5a521
--- /dev/null
+++ b/.mise/tasks/service/files
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+#MISE description="Run file server"
+set -e
+
+cargo run --bin revolt-autumn
diff --git a/.mise/tasks/service/gifbox b/.mise/tasks/service/gifbox
new file mode 100755
index 000000000..bc72192be
--- /dev/null
+++ b/.mise/tasks/service/gifbox
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+#MISE description="Run GIF proxy server"
+set -e
+
+cargo run --bin revolt-gifbox
diff --git a/.mise/tasks/service/proxy b/.mise/tasks/service/proxy
new file mode 100755
index 000000000..a16634fc4
--- /dev/null
+++ b/.mise/tasks/service/proxy
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+#MISE description="Run proxy server"
+set -e
+
+cargo run --bin revolt-january
diff --git a/.mise/tasks/service/pushd b/.mise/tasks/service/pushd
new file mode 100755
index 000000000..1cbb96bab
--- /dev/null
+++ b/.mise/tasks/service/pushd
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+#MISE description="Run push daemon"
+set -e
+
+cargo run --bin revolt-pushd
diff --git a/.mise/tasks/test b/.mise/tasks/test
new file mode 100755
index 000000000..848ad35db
--- /dev/null
+++ b/.mise/tasks/test
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+#MISE description="Test project"
+set -e
+
+: "${TEST_DB:=REFERENCE}"
+export TEST_DB
+
+cargo nextest run
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
new file mode 100644
index 000000000..297819281
--- /dev/null
+++ b/.release-please-manifest.json
@@ -0,0 +1,3 @@
+{
+ ".": "0.13.7"
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 000000000..9912f5b65
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,288 @@
+# Changelog
+
+## [0.13.7](https://github.com/stoatchat/stoatchat/compare/v0.13.6...v0.13.7) (2026-05-21)
+
+
+### Bug Fixes
+
+* sanitize emoji input to handle variation selectors ([#774](https://github.com/stoatchat/stoatchat/issues/774)) ([2d308e0](https://github.com/stoatchat/stoatchat/commit/2d308e03d58c19f27b5b4d65dc2a15ef20b56190))
+* update mention count badge for channel acks ([#769](https://github.com/stoatchat/stoatchat/issues/769)) ([0d9ae50](https://github.com/stoatchat/stoatchat/commit/0d9ae508d9d2199f0e408b8ca634d20489be6f61))
+
+## [0.13.6](https://github.com/stoatchat/stoatchat/compare/v0.13.5...v0.13.6) (2026-05-18)
+
+
+### Features
+
+* Update FCM payload for android notifications ([#766](https://github.com/stoatchat/stoatchat/issues/766)) ([acbc087](https://github.com/stoatchat/stoatchat/commit/acbc087982e9aeb05cabc5ab4c9b1291f67490ad))
+* user slowmode events ([#760](https://github.com/stoatchat/stoatchat/issues/760)) ([af0d8aa](https://github.com/stoatchat/stoatchat/commit/af0d8aad14dc68d88159d0e1c714077d362e21e4))
+
+
+### Bug Fixes
+
+* include `minio` region as tests need it ([#761](https://github.com/stoatchat/stoatchat/issues/761)) ([298742d](https://github.com/stoatchat/stoatchat/commit/298742dbad4eafae356f976c56b9db23904b0c3a))
+* set env var for publishing crates ([#768](https://github.com/stoatchat/stoatchat/issues/768)) ([018afaf](https://github.com/stoatchat/stoatchat/commit/018afaf38f6330d92dad2a68b640c0cb3f6b639a))
+* Use proper headers to determine IP when not behind cloudflare ([#764](https://github.com/stoatchat/stoatchat/issues/764)) ([494c8b7](https://github.com/stoatchat/stoatchat/commit/494c8b7cabaae2a51039a7a5b559d5e2e5279554))
+* voice ingress crashing due to new Result in AMQP::new_auto() ([#765](https://github.com/stoatchat/stoatchat/issues/765)) ([2871632](https://github.com/stoatchat/stoatchat/commit/2871632382395cb20cbe0047c542d3ac31ff3f03))
+
+
+### Miscellaneous Chores
+
+* switch to lapin ([#767](https://github.com/stoatchat/stoatchat/issues/767)) ([5b19853](https://github.com/stoatchat/stoatchat/commit/5b1985381ae829a92c80a19e91a414cd9dc4de93))
+
+## [0.13.5](https://github.com/stoatchat/stoatchat/compare/v0.13.4...v0.13.5) (2026-05-17)
+
+
+### Bug Fixes
+
+* dont panic on hash missing when deleting files ([#755](https://github.com/stoatchat/stoatchat/issues/755)) ([c902077](https://github.com/stoatchat/stoatchat/commit/c902077cf51076fee11712eb732dc8a8f786fc4b))
+
+## [0.13.4](https://github.com/stoatchat/stoatchat/compare/v0.13.3...v0.13.4) (2026-05-16)
+
+
+### Bug Fixes
+
+* add TLS feature to livekit-api crate ([#753](https://github.com/stoatchat/stoatchat/issues/753)) ([6cfee1f](https://github.com/stoatchat/stoatchat/commit/6cfee1f601c1e084df7c8f1e7a5e8a560d1dd514))
+
+## [0.13.3](https://github.com/stoatchat/stoatchat/compare/v0.13.2...v0.13.3) (2026-05-15)
+
+
+### Bug Fixes
+
+* don't automatically set up rabbitmq in delta ([#749](https://github.com/stoatchat/stoatchat/issues/749)) ([7647cfc](https://github.com/stoatchat/stoatchat/commit/7647cfc8d93aba99f5faef13eb3d970097540d76))
+* don't declare queues which seem to cause the backend to crash in prod ([7647cfc](https://github.com/stoatchat/stoatchat/commit/7647cfc8d93aba99f5faef13eb3d970097540d76))
+
+## [0.13.2](https://github.com/stoatchat/stoatchat/compare/v0.13.1...v0.13.2) (2026-05-11)
+
+
+### Bug Fixes
+
+* update default exchange to `revolt.default` ([#746](https://github.com/stoatchat/stoatchat/issues/746)) ([fcb8091](https://github.com/stoatchat/stoatchat/commit/fcb8091cd7a00d7f26c798daa33aae4b923b2a8b))
+
+## [0.13.1](https://github.com/stoatchat/stoatchat/compare/v0.13.0...v0.13.1) (2026-05-10)
+
+
+### Bug Fixes
+
+* amqprs startup bug ([#744](https://github.com/stoatchat/stoatchat/issues/744)) ([1100eaf](https://github.com/stoatchat/stoatchat/commit/1100eaf46f849f2509ae01ac497556ca33bde778))
+
+## [0.13.0](https://github.com/stoatchat/stoatchat/compare/v0.12.1...v0.13.0) (2026-05-08)
+
+
+### Features
+
+* add embed support for YouTube Shorts ([#734](https://github.com/stoatchat/stoatchat/issues/734)) ([d46c7f7](https://github.com/stoatchat/stoatchat/commit/d46c7f7f3c04524c0639c3e0a122626f8e0b3bf7))
+* add emoji rename endpoint ([#714](https://github.com/stoatchat/stoatchat/issues/714)) ([23ad135](https://github.com/stoatchat/stoatchat/commit/23ad1359834bb7d07a460b8678d6a6ebffc73eb0))
+* add legal links to root payload ([#733](https://github.com/stoatchat/stoatchat/issues/733)) ([21d8201](https://github.com/stoatchat/stoatchat/commit/21d82018cf84ab0fdd10613d254b9562aea8eea3))
+* add role icon support ([#724](https://github.com/stoatchat/stoatchat/issues/724)) ([841985d](https://github.com/stoatchat/stoatchat/commit/841985d3b994df1c6eefab2fc7ecbd77ab22c493))
+* Add webhook endpoints for editing and deleting messages ([#682](https://github.com/stoatchat/stoatchat/issues/682)) ([6f3441c](https://github.com/stoatchat/stoatchat/commit/6f3441cf4acac2a8e6e1bf07a279a153b80f7956))
+* automatically sanitise usernames on create/update ([#689](https://github.com/stoatchat/stoatchat/issues/689)) ([e937697](https://github.com/stoatchat/stoatchat/commit/e93769786c7669485a659ee471630740d3cea702))
+* blacklist private ip ranges and add january domain blocklist ([#731](https://github.com/stoatchat/stoatchat/issues/731)) ([6b41db9](https://github.com/stoatchat/stoatchat/commit/6b41db984bb491b2e58324309cc70d8c14e0b814))
+* Rewrite acks ([#741](https://github.com/stoatchat/stoatchat/issues/741)) ([ab5bd47](https://github.com/stoatchat/stoatchat/commit/ab5bd47a39ee889de0b5ae6e7b560620853daead))
+
+
+### Bug Fixes
+
+* add new_user_hours to configuration limits ([#729](https://github.com/stoatchat/stoatchat/issues/729)) ([279f5d5](https://github.com/stoatchat/stoatchat/commit/279f5d5fd7af2df55902c706859ec07f569cdb1e))
+* add reconnection policy to Redis subscriber to prevent ghost state ([#708](https://github.com/stoatchat/stoatchat/issues/708)) ([057f2bb](https://github.com/stoatchat/stoatchat/commit/057f2bb8b359f8b942741a30ff54eeb8fbe3e0b1))
+* docker compose file had personal url in it ([#742](https://github.com/stoatchat/stoatchat/issues/742)) ([0719985](https://github.com/stoatchat/stoatchat/commit/0719985ac5636590f91e6f9ec4b68f3eded70c13))
+* don't strip ICC from exif ([#735](https://github.com/stoatchat/stoatchat/issues/735)) ([d76a711](https://github.com/stoatchat/stoatchat/commit/d76a71141f3e508f6308ba52fa28eaeb56fb3438))
+* dont send notification in fcm ([#721](https://github.com/stoatchat/stoatchat/issues/721)) ([89171e9](https://github.com/stoatchat/stoatchat/commit/89171e9bd0f15711157e78c6eec0fe7b480de93a))
+* encode filenames in redirects ([#737](https://github.com/stoatchat/stoatchat/issues/737)) ([9fd7128](https://github.com/stoatchat/stoatchat/commit/9fd7128f800badbd184baf943d4f799e601201e4))
+* january ip redirects & domain resolver ([#738](https://github.com/stoatchat/stoatchat/issues/738)) ([356491e](https://github.com/stoatchat/stoatchat/commit/356491e934b274f9e895df883dd63ef0b3123510))
+* update message length validation to remove upper limit ([#723](https://github.com/stoatchat/stoatchat/issues/723)) ([ed4fd5e](https://github.com/stoatchat/stoatchat/commit/ed4fd5ebfe6d0ea534a0898da4afdc1f4e2cd6c5))
+* use correct response for NoEffect errors ([#732](https://github.com/stoatchat/stoatchat/issues/732)) ([5378cd2](https://github.com/stoatchat/stoatchat/commit/5378cd22b4c7d85f44c31a6af0dda00941b80d5c))
+
+## [0.12.1](https://github.com/stoatchat/stoatchat/compare/v0.12.0...v0.12.1) (2026-04-10)
+
+
+### Bug Fixes
+
+* add migration to update existing files to be animated ([#705](https://github.com/stoatchat/stoatchat/issues/705)) ([f2c056a](https://github.com/stoatchat/stoatchat/commit/f2c056a1515be493b195f3f5db5886c2ddf36700))
+* don't send self dm notifications ([#706](https://github.com/stoatchat/stoatchat/issues/706)) ([f30b729](https://github.com/stoatchat/stoatchat/commit/f30b729ca90d0be6853c57ab4935694e5e59ae56))
+* mise start + missing docker image ([#564](https://github.com/stoatchat/stoatchat/issues/564)) ([fb8fe16](https://github.com/stoatchat/stoatchat/commit/fb8fe1655776791421284a6a093e86f0320c258a))
+* test failure due to wrong assertion ([#707](https://github.com/stoatchat/stoatchat/issues/707)) ([f81e329](https://github.com/stoatchat/stoatchat/commit/f81e3291bdd57af9ceedb2987b111acc7051d69c))
+
+## [0.12.0](https://github.com/stoatchat/stoatchat/compare/v0.11.5...v0.12.0) (2026-03-28)
+
+
+### Features
+
+* add bug report template for issue tracking ([#627](https://github.com/stoatchat/stoatchat/issues/627)) ([f777e28](https://github.com/stoatchat/stoatchat/commit/f777e2863c6ca50057c8b5d0a5be14915d287724))
+* Add slowmode functionality to text channels ([#680](https://github.com/stoatchat/stoatchat/issues/680)) ([6107f24](https://github.com/stoatchat/stoatchat/commit/6107f242fd3ebaff71a15f9a16330ffbcb4f2d7b))
+* Allow restricting server creation to specific users ([#685](https://github.com/stoatchat/stoatchat/issues/685)) ([edfa97d](https://github.com/stoatchat/stoatchat/commit/edfa97db108c9c81828547f98a1db5315cb5ba4a))
+* compute thumbhash for images ([#596](https://github.com/stoatchat/stoatchat/issues/596)) ([c2d4369](https://github.com/stoatchat/stoatchat/commit/c2d4369e160f32d79bce0a0b0f14677f89de3669))
+* Detect animation in image files for fetch_preview ([#574](https://github.com/stoatchat/stoatchat/issues/574)) ([3fa0abf](https://github.com/stoatchat/stoatchat/commit/3fa0abf47f5f42ddd8ee041fe4c44fbc5ba800c1))
+* expose global and user limits in root API response ([#644](https://github.com/stoatchat/stoatchat/issues/644)) ([0b522eb](https://github.com/stoatchat/stoatchat/commit/0b522ebddc17f2e3f792ff5e2347793e9849fa23))
+* implement time based message sweep on user ban ([#670](https://github.com/stoatchat/stoatchat/issues/670)) ([98c7b1b](https://github.com/stoatchat/stoatchat/commit/98c7b1b5a5b9fdac5c0ab83be10f0e23114dbfc9))
+* load config from env vars ([#576](https://github.com/stoatchat/stoatchat/issues/576)) ([5191bd1](https://github.com/stoatchat/stoatchat/commit/5191bd16b2a905b8409838e34eb0baca96f08580))
+* parse message push notification content and replace internal formatting ([#693](https://github.com/stoatchat/stoatchat/issues/693)) ([d1e72ce](https://github.com/stoatchat/stoatchat/commit/d1e72cee42c54e16f4e49af569897528b10a28ca))
+* Transfer ownership ([#396](https://github.com/stoatchat/stoatchat/issues/396)) ([735d644](https://github.com/stoatchat/stoatchat/commit/735d644e043793cb86e74aab5b88bb4b8bc17ba2))
+* update livekit ([#698](https://github.com/stoatchat/stoatchat/issues/698)) ([f181edc](https://github.com/stoatchat/stoatchat/commit/f181edc8f2ff3ce4b6d48938dfc73931ecfa2279))
+
+
+### Bug Fixes
+
+* add flag for disabling events instead of commenting them out ([#695](https://github.com/stoatchat/stoatchat/issues/695)) ([a5cd08a](https://github.com/stoatchat/stoatchat/commit/a5cd08a655dece4269f3ac84fa2387ae356709a5))
+* add masquerade permission to default direct message settings ([#665](https://github.com/stoatchat/stoatchat/issues/665)) ([ab52569](https://github.com/stoatchat/stoatchat/commit/ab525699bd6663333f0e9fed6d2455e482e6a09f))
+* Check for appropriate permission for removing other users avatar ([#657](https://github.com/stoatchat/stoatchat/issues/657)) ([d56135e](https://github.com/stoatchat/stoatchat/commit/d56135e0cbc713884c9378832952f7ad490fa315))
+* default video resolution is a non-existent size ([#601](https://github.com/stoatchat/stoatchat/issues/601)) ([0698e11](https://github.com/stoatchat/stoatchat/commit/0698e115e8d003d615e468c4fb9654e6bbc9107f)), closes [#588](https://github.com/stoatchat/stoatchat/issues/588)
+* **docs:** Update GitHub links ([#647](https://github.com/stoatchat/stoatchat/issues/647)) ([b830631](https://github.com/stoatchat/stoatchat/commit/b830631bd25a546844b7bdd30386084bb365e4de))
+* don't use a bitop for OR ([#676](https://github.com/stoatchat/stoatchat/issues/676)) ([5701b5c](https://github.com/stoatchat/stoatchat/commit/5701b5c18c513f796af365169ceaea372a22638c))
+* Fix typo for p256dh in vapid notification flow ([#622](https://github.com/stoatchat/stoatchat/issues/622)) ([a80ad1c](https://github.com/stoatchat/stoatchat/commit/a80ad1cbe58b8af5e45751e51d94d93c1cea1c9f))
+* improve generated openapi.json ([#584](https://github.com/stoatchat/stoatchat/issues/584)) ([52ed510](https://github.com/stoatchat/stoatchat/commit/52ed5100c2446e0b261085639e123e7e124cab2c))
+* no node state set on channel creation ([#653](https://github.com/stoatchat/stoatchat/issues/653)) ([24d0d2b](https://github.com/stoatchat/stoatchat/commit/24d0d2b7266f6f8a692d0a52704acfecf517674c))
+* only show first line on commit messages ([#696](https://github.com/stoatchat/stoatchat/issues/696)) ([91783b9](https://github.com/stoatchat/stoatchat/commit/91783b906697fc85305dee683f7c15dda55f0c50))
+* pass &str to Reference ([#697](https://github.com/stoatchat/stoatchat/issues/697)) ([ccda6f5](https://github.com/stoatchat/stoatchat/commit/ccda6f5c53ee043705f7ff6b5f6c393f020781de))
+* redis_url vs redis_uri in config ([#666](https://github.com/stoatchat/stoatchat/issues/666)) ([b0b728f](https://github.com/stoatchat/stoatchat/commit/b0b728fb0dbc9ee28360301de1c3ea501bbbff1d))
+* replace some links and Revolt mentions to current Stoat ([#515](https://github.com/stoatchat/stoatchat/issues/515)) ([d629e89](https://github.com/stoatchat/stoatchat/commit/d629e89304be2f0011e189293b278f07d346aa7d))
+* send push notifications for DM and group messages ([#660](https://github.com/stoatchat/stoatchat/issues/660)) ([52c0d2f](https://github.com/stoatchat/stoatchat/commit/52c0d2f266b76d8975bba2d5e75c62bb30149c45))
+* store server id in redis and in room metadata to be able to delete voice state in all scenarios ([#656](https://github.com/stoatchat/stoatchat/issues/656)) ([49c6289](https://github.com/stoatchat/stoatchat/commit/49c628958070e4f0a5edc764d3b48158589219d9))
+* uname is missing from crond ([#675](https://github.com/stoatchat/stoatchat/issues/675)) ([dc4438b](https://github.com/stoatchat/stoatchat/commit/dc4438bc3c7b2cad8d442b3cd438afb9ed566a5e))
+
+## [0.11.5](https://github.com/stoatchat/stoatchat/compare/v0.11.4...v0.11.5) (2026-02-17)
+
+
+### Reverts
+
+* disable user update events ([#593](https://github.com/stoatchat/stoatchat/issues/593)) ([1c98ead](https://github.com/stoatchat/stoatchat/commit/1c98ead69579b4700be0b51c9020bb8402336cc6))
+
+## [0.11.4](https://github.com/stoatchat/stoatchat/compare/v0.11.3...v0.11.4) (2026-02-16)
+
+
+### Bug Fixes
+
+* add separate config option for redis events replica url ([#590](https://github.com/stoatchat/stoatchat/issues/590)) ([a75e4ea](https://github.com/stoatchat/stoatchat/commit/a75e4eabfc4b34aba7620c82ba77558a32d9e10a))
+
+## [0.11.3](https://github.com/stoatchat/stoatchat/compare/v0.11.2...v0.11.3) (2026-02-13)
+
+
+### Bug Fixes
+
+* cut presence traffic too while we engineer a new events architecture ([#561](https://github.com/stoatchat/stoatchat/issues/561)) ([1f8ea96](https://github.com/stoatchat/stoatchat/commit/1f8ea963ad742f693f405e6438f1c343c81e6579))
+
+## [0.11.2](https://github.com/stoatchat/stoatchat/compare/v0.11.1...v0.11.2) (2026-02-13)
+
+
+### Bug Fixes
+
+* cut events traffic while we engineer a new events architecture ([#559](https://github.com/stoatchat/stoatchat/issues/559)) ([a11986b](https://github.com/stoatchat/stoatchat/commit/a11986ba1ad16b672ff1080913a684567d88adbb))
+
+## [0.11.1](https://github.com/stoatchat/stoatchat/compare/v0.11.0...v0.11.1) (2026-02-13)
+
+
+### Bug Fixes
+
+* bots in multiple voice channel logic ([#544](https://github.com/stoatchat/stoatchat/issues/544)) ([94cb916](https://github.com/stoatchat/stoatchat/commit/94cb916231b9b8befb2e94065917ff40815bec52))
+
+## [0.11.0](https://github.com/stoatchat/stoatchat/compare/v0.10.3...v0.11.0) (2026-02-10)
+
+
+### Features
+
+* appeal to the almighty Spamhaus ([#524](https://github.com/stoatchat/stoatchat/issues/524)) ([5132270](https://github.com/stoatchat/stoatchat/commit/5132270f2edd6df25ce414daa42ed1b2aa6fa7a9))
+
+## [0.10.3](https://github.com/stoatchat/stoatchat/compare/v0.10.2...v0.10.3) (2026-02-07)
+
+
+### Bug Fixes
+
+* update `Revolt` -> `Stoat` in email titles/desc. ([#508](https://github.com/stoatchat/stoatchat/issues/508)) ([84483ce](https://github.com/stoatchat/stoatchat/commit/84483cee7af3e5dfa16f7fe13e334c4d9f5abd60))
+
+## [0.10.2](https://github.com/stoatchat/stoatchat/compare/v0.10.1...v0.10.2) (2026-01-25)
+
+
+### Bug Fixes
+
+* thumbnailification requires rgb8/rgba8 ([#505](https://github.com/stoatchat/stoatchat/issues/505)) ([413aa04](https://github.com/stoatchat/stoatchat/commit/413aa04dcaf8bff3935ed1e5f31432e11a03ce6f))
+
+## [0.10.1](https://github.com/stoatchat/stoatchat/compare/v0.10.0...v0.10.1) (2026-01-25)
+
+
+### Bug Fixes
+
+* use Rust 1.92.0 for Docker build ([#503](https://github.com/stoatchat/stoatchat/issues/503)) ([98da8a2](https://github.com/stoatchat/stoatchat/commit/98da8a28a0aa2fee4e8eee1d86bd7c49e3187477))
+
+## [0.10.0](https://github.com/stoatchat/stoatchat/compare/v0.9.4...v0.10.0) (2026-01-25)
+
+
+### Features
+
+* allow kicking members from voice channels ([#495](https://github.com/stoatchat/stoatchat/issues/495)) ([0dc5442](https://github.com/stoatchat/stoatchat/commit/0dc544249825a49c793309edee5ec1838458a6da))
+* repository architecture for files crate w. added tests ([#498](https://github.com/stoatchat/stoatchat/issues/498)) ([01ded20](https://github.com/stoatchat/stoatchat/commit/01ded209c62208fc906d6aab9b08c04e860e10ef))
+
+
+### Bug Fixes
+
+* expose ratelimit headers via cors ([#496](https://github.com/stoatchat/stoatchat/issues/496)) ([a1a2125](https://github.com/stoatchat/stoatchat/commit/a1a21252d0ad58937e41f16e5fb86f96bebd2a51))
+
+## [0.9.4](https://github.com/stoatchat/stoatchat/compare/v0.9.3...v0.9.4) (2026-01-10)
+
+
+### Bug Fixes
+
+* checkout repo. before bumping lock ([#490](https://github.com/stoatchat/stoatchat/issues/490)) ([b2da2a8](https://github.com/stoatchat/stoatchat/commit/b2da2a858787853be43136fd526a0bd72baf78ef))
+* persist credentials for git repo ([#492](https://github.com/stoatchat/stoatchat/issues/492)) ([c674a9f](https://github.com/stoatchat/stoatchat/commit/c674a9fd4e0abbd51569870e4b38074d4a1de03c))
+
+## [0.9.3](https://github.com/stoatchat/stoatchat/compare/v0.9.2...v0.9.3) (2026-01-10)
+
+
+### Bug Fixes
+
+* pipeline fixes ([#487](https://github.com/stoatchat/stoatchat/issues/487)) ([aeeafeb](https://github.com/stoatchat/stoatchat/commit/aeeafebefc36a43a656cf797c9251ca50292733c))
+
+## [0.9.2](https://github.com/stoatchat/stoatchat/compare/v0.9.1...v0.9.2) (2026-01-10)
+
+
+### Bug Fixes
+
+* disable publish for services ([#485](https://github.com/stoatchat/stoatchat/issues/485)) ([d13609c](https://github.com/stoatchat/stoatchat/commit/d13609c37279d6a40445dcd99564e5c3dd03bac1))
+
+## [0.9.1](https://github.com/stoatchat/stoatchat/compare/v0.9.0...v0.9.1) (2026-01-10)
+
+
+### Bug Fixes
+
+* **ci:** pipeline fixes (marked as fix to force release) ([#483](https://github.com/stoatchat/stoatchat/issues/483)) ([303e52b](https://github.com/stoatchat/stoatchat/commit/303e52b476585eea81c33837f1b01506ce387684))
+
+## [0.9.0](https://github.com/stoatchat/stoatchat/compare/v0.8.8...v0.9.0) (2026-01-10)
+
+
+### Features
+
+* add id field to role ([#470](https://github.com/stoatchat/stoatchat/issues/470)) ([2afea56](https://github.com/stoatchat/stoatchat/commit/2afea56e56017f02de98e67316b4457568ad5b26))
+* add ratelimits to gifbox ([1542047](https://github.com/stoatchat/stoatchat/commit/154204742d21cbeff6e2577b00f50b495ea44631))
+* include groups and dms in fetch mutuals ([caa8607](https://github.com/stoatchat/stoatchat/commit/caa86074680d46223cebc20f41e9c91c41ec825d))
+* include member payload in ServerMemberJoin event ([480f210](https://github.com/stoatchat/stoatchat/commit/480f210ce85271e13d1dac58a5dae08de108579d))
+* initial work on tenor gif searching ([b0c977b](https://github.com/stoatchat/stoatchat/commit/b0c977b324b8144c1152589546eb8fec5954c3e7))
+* make message lexer use unowned string ([1561481](https://github.com/stoatchat/stoatchat/commit/1561481eb4cdc0f385fbf0a81e4950408050e11f))
+* ready payload field customisation ([db57706](https://github.com/stoatchat/stoatchat/commit/db577067948f13e830b5fb773034e9713a1abaff))
+* require auth for search ([b5cd5e3](https://github.com/stoatchat/stoatchat/commit/b5cd5e30ef7d5e56e8964fb7c543965fa6bf5a4a))
+* trending and categories routes ([5885e06](https://github.com/stoatchat/stoatchat/commit/5885e067a627b8fff1c8ce2bf9e852ff8cf3f07a))
+* voice chats v2 ([#414](https://github.com/stoatchat/stoatchat/issues/414)) ([d567155](https://github.com/stoatchat/stoatchat/commit/d567155f124e4da74115b1a8f810062f7c6559d9))
+
+
+### Bug Fixes
+
+* add license to revolt-parser ([5335124](https://github.com/stoatchat/stoatchat/commit/53351243064cac8d499dd74284be73928fa78a43))
+* allow for disabling default features ([65fbd36](https://github.com/stoatchat/stoatchat/commit/65fbd3662462aed1333b79e59155fa6377e83fcc))
+* apple music to use original url instead of metadata url ([bfe4018](https://github.com/stoatchat/stoatchat/commit/bfe4018e436a4075bae780dd4d35a9b58315e12f))
+* apply uname fix to january and autumn ([8f9015a](https://github.com/stoatchat/stoatchat/commit/8f9015a6ff181d208d9269ab8691bd417d39811a))
+* **ci:** publish images under stoatchat and remove docker hub ([d65c1a1](https://github.com/stoatchat/stoatchat/commit/d65c1a1ab3bdc7e5684b03f280af77d881661a3d))
+* correct miniz_oxide in lockfile ([#478](https://github.com/stoatchat/stoatchat/issues/478)) ([5d27a91](https://github.com/stoatchat/stoatchat/commit/5d27a91e901dd2ea3e860aeaed8468db6c5f3214))
+* correct shebang for try-tag-and-release ([050ba16](https://github.com/stoatchat/stoatchat/commit/050ba16d4adad5d0fb247867aa3e94e3d42bd12d))
+* correct string_cache in lockfile ([#479](https://github.com/stoatchat/stoatchat/issues/479)) ([0b178fc](https://github.com/stoatchat/stoatchat/commit/0b178fc791583064bf9ca94b1d39b42d021e1d79))
+* don't remove timeouts when a member leaves a server ([#409](https://github.com/stoatchat/stoatchat/issues/409)) ([e635bc2](https://github.com/stoatchat/stoatchat/commit/e635bc23ec857d648d5705e1a3875d7bc3402b0d))
+* don't update the same field while trying to remove it ([f4ee35f](https://github.com/stoatchat/stoatchat/commit/f4ee35fb093ca49f0a64ff4b17fd61587df28145)), closes [#392](https://github.com/stoatchat/stoatchat/issues/392)
+* github webhook incorrect payload and formatting ([#468](https://github.com/stoatchat/stoatchat/issues/468)) ([dc9c82a](https://github.com/stoatchat/stoatchat/commit/dc9c82aa4e9667ea6639256c65ac8de37a24d1f7))
+* implement Serialize to ClientMessage ([dea0f67](https://github.com/stoatchat/stoatchat/commit/dea0f675dde7a63c7a59b38d469f878b7a8a3af4))
+* newly created roles should be ranked the lowest ([947eb15](https://github.com/stoatchat/stoatchat/commit/947eb15771ed6785b3dcd16c354c03ded5e4cbe0))
+* permit empty `remove` array in edit requests ([6ad3da5](https://github.com/stoatchat/stoatchat/commit/6ad3da5f35f989a2e7d8e29718b98374248e76af))
+* preserve order of replies in message ([#447](https://github.com/stoatchat/stoatchat/issues/447)) ([657a3f0](https://github.com/stoatchat/stoatchat/commit/657a3f08e5d652814bbf0647e089ed9ebb139bbf))
+* prevent timing out members which have TimeoutMembers permission ([e36fc97](https://github.com/stoatchat/stoatchat/commit/e36fc9738bac0de4f3fcbccba521f1e3754f7ae7))
+* relax settings name regex ([3a34159](https://github.com/stoatchat/stoatchat/commit/3a3415915f0d0fdce1499d47a2b7fa097f5946ea))
+* remove authentication tag bytes from attachment download ([32e6600](https://github.com/stoatchat/stoatchat/commit/32e6600272b885c595c094f0bc69459250220dcb))
+* rename openapi operation ids ([6048587](https://github.com/stoatchat/stoatchat/commit/6048587d348fbca0dc3a9b47690c56df8fece576)), closes [#406](https://github.com/stoatchat/stoatchat/issues/406)
+* respond with 201 if no body in requests ([#465](https://github.com/stoatchat/stoatchat/issues/465)) ([24fedf8](https://github.com/stoatchat/stoatchat/commit/24fedf8c4d9cd3160bdec97aa451520f8beaa739))
+* swap to using reqwest for query building ([38dd4d1](https://github.com/stoatchat/stoatchat/commit/38dd4d10797b3e6e397fc219e818f379bdff19f2))
+* use `trust_cloudflare` config value instead of env var ([cc7a796](https://github.com/stoatchat/stoatchat/commit/cc7a7962a882e1627fcd0bc75858a017415e8cfc))
+* use our own result types instead of tenors types ([a92152d](https://github.com/stoatchat/stoatchat/commit/a92152d86da136997817e797c7af8e38731cdde8))
diff --git a/Cargo.lock b/Cargo.lock
index 4d310e03f..bcd1c5acb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,9 +4,9 @@ version = 4
[[package]]
name = "addr2line"
-version = "0.24.2"
+version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
+checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b"
dependencies = [
"gimli",
]
@@ -35,7 +35,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
- "cpufeatures",
+ "cpufeatures 0.2.17",
]
[[package]]
@@ -58,7 +58,7 @@ version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9"
dependencies = [
- "getrandom 0.2.16",
+ "getrandom 0.2.17",
"once_cell",
"version_check",
]
@@ -70,7 +70,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75"
dependencies = [
"cfg-if",
- "getrandom 0.3.3",
+ "getrandom 0.3.4",
"once_cell",
"version_check",
"zerocopy",
@@ -78,13 +78,22 @@ dependencies = [
[[package]]
name = "aho-corasick"
-version = "1.1.3"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
dependencies = [
"memchr",
]
+[[package]]
+name = "aligned"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee4508988c62edf04abd8d92897fca0c2995d907ce1dfeaf369dac3716a40685"
+dependencies = [
+ "as-slice",
+]
+
[[package]]
name = "aligned-vec"
version = "0.6.4"
@@ -94,6 +103,21 @@ dependencies = [
"equator",
]
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
[[package]]
name = "allocator-api2"
version = "0.2.21"
@@ -101,35 +125,54 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
-name = "amqp_serde"
-version = "0.4.2"
+name = "amq-protocol"
+version = "10.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46af430da4849b906416235798be5d0b8ff07e478d9809f05a00f81eb9527781"
+checksum = "c5b2e8843d88d935a75cbdb1c5b9ad80987da6e6472c2703914e41dc14560990"
dependencies = [
- "bytes 1.10.1",
+ "amq-protocol-tcp",
+ "amq-protocol-types",
+ "amq-protocol-uri",
+ "cookie-factory",
+ "nom 8.0.0",
"serde",
- "serde_bytes",
]
[[package]]
-name = "amqprs"
-version = "1.8.0"
+name = "amq-protocol-tcp"
+version = "10.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f1b4afcbd862e16c272b7625b6b057930b052d63c720bc90f6afab0d9abe8a8"
+checksum = "998fb81655e11de5a336bb609042c11633678fc01f90b0772fb9a7886b6cc4c2"
dependencies = [
- "amqp_serde",
- "async-trait",
- "bytes 1.10.1",
+ "amq-protocol-uri",
+ "async-rs",
+ "cfg-if",
+ "tcp-stream",
+ "tracing",
+]
+
+[[package]]
+name = "amq-protocol-types"
+version = "10.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee5b3a9e458bd2e452536995c8cf861b01c17283f55c4bbe0a1b9626b8253add"
+dependencies = [
+ "cookie-factory",
+ "nom 8.0.0",
"serde",
- "serde_bytes_ng",
- "tokio 1.47.1",
+ "serde_json",
]
[[package]]
-name = "android-tzdata"
-version = "0.1.1"
+name = "amq-protocol-uri"
+version = "10.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+checksum = "dca3316970d20cdcca9123f4e8feb7a2c1c8fdca572a9692fc10002db35407aa"
+dependencies = [
+ "amq-protocol-types",
+ "percent-encoding",
+ "url",
+]
[[package]]
name = "android_system_properties"
@@ -142,24 +185,24 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.98"
+version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
-dependencies = [
- "backtrace",
-]
+checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "arbitrary"
-version = "1.4.1"
+version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
+checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1"
[[package]]
name = "arc-swap"
-version = "1.7.1"
+version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
+checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207"
+dependencies = [
+ "rustversion",
+]
[[package]]
name = "arg_enum_proc_macro"
@@ -168,8 +211,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -184,13 +227,61 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+[[package]]
+name = "as-slice"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
+dependencies = [
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "asn1-rs"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60"
+dependencies = [
+ "asn1-rs-derive",
+ "asn1-rs-impl",
+ "displaydoc",
+ "nom 7.1.3",
+ "num-traits",
+ "rusticata-macros",
+ "thiserror 2.0.18",
+ "time",
+]
+
+[[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 1.0.45",
+ "syn 2.0.117",
+ "synstructure 0.13.2",
+]
+
+[[package]]
+name = "asn1-rs-impl"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7"
+dependencies = [
+ "proc-macro2",
+ "quote 1.0.45",
+ "syn 2.0.117",
+]
+
[[package]]
name = "async-attributes"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5"
dependencies = [
- "quote 1.0.40",
+ "quote 1.0.45",
"syn 1.0.109",
]
@@ -214,20 +305,33 @@ dependencies = [
"concurrent-queue",
"event-listener-strategy",
"futures-core",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
+]
+
+[[package]]
+name = "async-compat"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1ba85bc55464dcbf728b56d97e119d673f4cf9062be330a9a26f3acf504a590"
+dependencies = [
+ "futures-core",
+ "futures-io",
+ "once_cell",
+ "pin-project-lite 0.2.17",
+ "tokio 1.51.0",
]
[[package]]
name = "async-executor"
-version = "1.13.2"
+version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb812ffb58524bdd10860d7d974e2f01cc0950c2438a74ee5ec2e2280c6c4ffa"
+checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
dependencies = [
"async-task",
"concurrent-queue",
- "fastrand 2.3.0",
- "futures-lite 2.6.1",
- "pin-project-lite 0.2.16",
+ "fastrand 2.4.0",
+ "futures-lite",
+ "pin-project-lite 0.2.17",
"slab",
]
@@ -240,30 +344,44 @@ dependencies = [
"async-channel 2.5.0",
"async-executor",
"async-io",
- "async-lock 3.4.1",
+ "async-lock 3.4.2",
"blocking",
- "futures-lite 2.6.1",
+ "futures-lite",
"once_cell",
"tokio 0.2.25",
- "tokio 1.47.1",
+ "tokio 1.51.0",
+]
+
+[[package]]
+name = "async-global-executor"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13f937e26114b93193065fd44f507aa2e9169ad0cdabbb996920b1fe1ddea7ba"
+dependencies = [
+ "async-channel 2.5.0",
+ "async-executor",
+ "async-lock 3.4.2",
+ "blocking",
+ "futures-lite",
+ "tokio 1.51.0",
]
[[package]]
name = "async-io"
-version = "2.5.0"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca"
+checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
dependencies = [
- "async-lock 3.4.1",
+ "autocfg",
"cfg-if",
"concurrent-queue",
"futures-io",
- "futures-lite 2.6.1",
+ "futures-lite",
"parking",
- "polling 3.10.0",
- "rustix 1.0.8",
+ "polling",
+ "rustix 1.1.4",
"slab",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -277,31 +395,31 @@ dependencies = [
[[package]]
name = "async-lock"
-version = "3.4.1"
+version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc"
+checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
dependencies = [
"event-listener 5.4.1",
"event-listener-strategy",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
]
[[package]]
name = "async-process"
-version = "2.4.0"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65daa13722ad51e6ab1a1b9c01299142bc75135b337923cfa10e79bbbd669f00"
+checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
dependencies = [
"async-channel 2.5.0",
"async-io",
- "async-lock 3.4.1",
+ "async-lock 3.4.2",
"async-signal",
"async-task",
"blocking",
"cfg-if",
"event-listener 5.4.1",
- "futures-lite 2.6.1",
- "rustix 1.0.8",
+ "futures-lite",
+ "rustix 1.1.4",
]
[[package]]
@@ -311,51 +429,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "async-rs"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e32bd31386d41d0c06bd79b0397ec96e544d69d9dbd6db0236c7ceefe1ad61b"
+dependencies = [
+ "async-compat",
+ "async-global-executor 3.1.0",
+ "async-trait",
+ "futures-core",
+ "futures-io",
+ "hickory-resolver 0.26.1",
+ "tokio 1.51.0",
+ "tokio-stream",
]
[[package]]
name = "async-signal"
-version = "0.2.12"
+version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f567af260ef69e1d52c2b560ce0ea230763e6fbb9214a85d768760a920e3e3c1"
+checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c"
dependencies = [
"async-io",
- "async-lock 3.4.1",
+ "async-lock 3.4.2",
"atomic-waker",
"cfg-if",
"futures-core",
"futures-io",
- "rustix 1.0.8",
+ "rustix 1.1.4",
"signal-hook-registry",
"slab",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
name = "async-std"
-version = "1.13.1"
+version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "730294c1c08c2e0f85759590518f6333f0d5a0a766a27d519c1b244c3dfd8a24"
+checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b"
dependencies = [
"async-attributes",
"async-channel 1.9.0",
- "async-global-executor",
+ "async-global-executor 2.4.1",
"async-io",
- "async-lock 3.4.1",
+ "async-lock 3.4.2",
"async-process",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
- "futures-lite 2.6.1",
+ "futures-lite",
"gloo-timers",
"kv-log-macro",
"log",
"memchr",
"once_cell",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"pin-utils",
"slab",
"wasm-bindgen-futures",
@@ -369,7 +503,7 @@ checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
dependencies = [
"async-stream-impl",
"futures-core",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
]
[[package]]
@@ -379,8 +513,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -391,13 +525,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]]
name = "async-trait"
-version = "0.1.88"
+version = "0.1.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
+checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -410,8 +544,8 @@ dependencies = [
"futures-io",
"futures-util",
"log",
- "pin-project-lite 0.2.16",
- "tungstenite",
+ "pin-project-lite 0.2.17",
+ "tungstenite 0.17.3",
]
[[package]]
@@ -448,9 +582,9 @@ dependencies = [
[[package]]
name = "authifier"
-version = "1.0.15"
+version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1a03b810b342b4c584cdc1cfc40d1ec763b1d653ef4086f7494740f35d9e1f0"
+checksum = "1d6163caecbb985d91406c2c6dc7710c46b38e6461d93d5e843b2f0eac43910c"
dependencies = [
"async-std",
"async-trait",
@@ -472,7 +606,7 @@ dependencies = [
"revolt_rocket_okapi",
"rocket",
"rust-argon2",
- "schemars 0.8.22",
+ "schemars",
"serde",
"serde_json",
"sha1",
@@ -489,47 +623,58 @@ checksum = "7460f7dd8e100147b82a63afca1a20eb6c231ee36b90ba7272e14951cb58af59"
[[package]]
name = "autocfg"
-version = "0.1.8"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0dde43e75fd43e8a1bf86103336bc699aa8d17ad1be60c76c0bdfd4828e19b78"
-dependencies = [
- "autocfg 1.5.0",
-]
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
-name = "autocfg"
-version = "1.5.0"
+name = "av-scenechange"
+version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+checksum = "0f321d77c20e19b92c39e7471cf986812cbb46659d2af674adc4331ef3f18394"
+dependencies = [
+ "aligned",
+ "anyhow",
+ "arg_enum_proc_macro",
+ "arrayvec",
+ "log",
+ "num-rational",
+ "num-traits",
+ "pastey",
+ "rayon",
+ "thiserror 2.0.18",
+ "v_frame",
+ "y4m",
+]
[[package]]
name = "av1-grain"
-version = "0.2.4"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4f3efb2ca85bc610acfa917b5aaa36f3fcbebed5b3182d7f877b02531c4b80c8"
+checksum = "8cfddb07216410377231960af4fcab838eaa12e013417781b78bd95ee22077f8"
dependencies = [
"anyhow",
"arrayvec",
"log",
- "nom",
+ "nom 8.0.0",
"num-rational",
"v_frame",
]
[[package]]
name = "avif-serialize"
-version = "0.8.5"
+version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ea8ef51aced2b9191c08197f55450d830876d9933f8f48a429b354f1d496b42"
+checksum = "375082f007bd67184fb9c0374614b29f9aaa604ec301635f72338bb65386a53d"
dependencies = [
"arrayvec",
]
[[package]]
name = "aws-config"
-version = "1.8.4"
+version = "1.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "483020b893cdef3d89637e428d588650c71cfae7ea2e6ecbaee4de4ff99fb2dd"
+checksum = "11493b0bad143270fb8ad284a096dd529ba91924c5409adeac856cc1bf047dbc"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -543,13 +688,13 @@ dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
- "bytes 1.10.1",
- "fastrand 2.3.0",
+ "bytes 1.11.1",
+ "fastrand 2.4.0",
"hex",
- "http 1.3.1",
- "ring",
+ "http 1.4.0",
+ "sha1",
"time",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tracing",
"url",
"zeroize",
@@ -557,9 +702,9 @@ dependencies = [
[[package]]
name = "aws-credential-types"
-version = "1.2.5"
+version = "1.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1541072f81945fa1251f8795ef6c92c4282d74d59f88498ae7d4bf00f0ebdad9"
+checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7"
dependencies = [
"aws-smithy-async",
"aws-smithy-runtime-api",
@@ -569,9 +714,9 @@ dependencies = [
[[package]]
name = "aws-lc-rs"
-version = "1.13.3"
+version = "1.16.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c953fe1ba023e6b7730c0d4b031d06f267f23a46167dcbd40316644b10a17ba"
+checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc"
dependencies = [
"aws-lc-sys",
"zeroize",
@@ -579,11 +724,10 @@ dependencies = [
[[package]]
name = "aws-lc-sys"
-version = "0.30.0"
+version = "0.39.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbfd150b5dbdb988bcc8fb1fe787eb6b7ee6180ca24da683b61ea5405f3d43ff"
+checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399"
dependencies = [
- "bindgen",
"cc",
"cmake",
"dunce",
@@ -592,9 +736,9 @@ dependencies = [
[[package]]
name = "aws-runtime"
-version = "1.5.10"
+version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c034a1bc1d70e16e7f4e4caf7e9f7693e4c9c24cd91cf17c2a0b21abaebc7c8b"
+checksum = "5fc0651c57e384202e47153c1260b84a9936e19803d747615edf199dc3b98d17"
dependencies = [
"aws-credential-types",
"aws-sigv4",
@@ -605,21 +749,24 @@ dependencies = [
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
- "bytes 1.10.1",
- "fastrand 2.3.0",
+ "bytes 1.11.1",
+ "bytes-utils",
+ "fastrand 2.4.0",
"http 0.2.12",
+ "http 1.4.0",
"http-body 0.4.6",
+ "http-body 1.0.1",
"percent-encoding",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"tracing",
"uuid",
]
[[package]]
name = "aws-sdk-s3"
-version = "1.101.0"
+version = "1.128.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b16efa59a199f5271bf21ab3e570c5297d819ce4f240e6cf0096d1dc0049c44"
+checksum = "99304b64672e0d81a3c100a589b93d9ef5e9c0ce12e21c848fd39e50f493c2a1"
dependencies = [
"aws-credential-types",
"aws-runtime",
@@ -629,19 +776,20 @@ dependencies = [
"aws-smithy-eventstream",
"aws-smithy-http",
"aws-smithy-json",
+ "aws-smithy-observability",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-smithy-xml",
"aws-types",
- "bytes 1.10.1",
- "fastrand 2.3.0",
+ "bytes 1.11.1",
+ "fastrand 2.4.0",
"hex",
"hmac",
"http 0.2.12",
- "http 1.3.1",
- "http-body 0.4.6",
- "lru 0.12.5",
+ "http 1.4.0",
+ "http-body 1.0.1",
+ "lru",
"percent-encoding",
"regex-lite",
"sha2",
@@ -651,89 +799,95 @@ dependencies = [
[[package]]
name = "aws-sdk-sso"
-version = "1.79.0"
+version = "1.97.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a847168f15b46329fa32c7aca4e4f1a2e072f9b422f0adb19756f2e1457f111"
+checksum = "9aadc669e184501caaa6beafb28c6267fc1baef0810fb58f9b205485ca3f2567"
dependencies = [
"aws-credential-types",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-json",
+ "aws-smithy-observability",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
- "bytes 1.10.1",
- "fastrand 2.3.0",
+ "bytes 1.11.1",
+ "fastrand 2.4.0",
"http 0.2.12",
+ "http 1.4.0",
"regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-ssooidc"
-version = "1.80.0"
+version = "1.99.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b654dd24d65568738593e8239aef279a86a15374ec926ae8714e2d7245f34149"
+checksum = "1342a7db8f358d3de0aed2007a0b54e875458e39848d54cc1d46700b2bfcb0a8"
dependencies = [
"aws-credential-types",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-json",
+ "aws-smithy-observability",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-types",
- "bytes 1.10.1",
- "fastrand 2.3.0",
+ "bytes 1.11.1",
+ "fastrand 2.4.0",
"http 0.2.12",
+ "http 1.4.0",
"regex-lite",
"tracing",
]
[[package]]
name = "aws-sdk-sts"
-version = "1.81.0"
+version = "1.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c92ea8a7602321c83615c82b408820ad54280fb026e92de0eeea937342fafa24"
+checksum = "ab41ad64e4051ecabeea802d6a17845a91e83287e1dd249e6963ea1ba78c428a"
dependencies = [
"aws-credential-types",
"aws-runtime",
"aws-smithy-async",
"aws-smithy-http",
"aws-smithy-json",
+ "aws-smithy-observability",
"aws-smithy-query",
"aws-smithy-runtime",
"aws-smithy-runtime-api",
"aws-smithy-types",
"aws-smithy-xml",
"aws-types",
- "fastrand 2.3.0",
+ "fastrand 2.4.0",
"http 0.2.12",
+ "http 1.4.0",
"regex-lite",
"tracing",
]
[[package]]
name = "aws-sigv4"
-version = "1.3.4"
+version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "084c34162187d39e3740cb635acd73c4e3a551a36146ad6fe8883c929c9f876c"
+checksum = "b0b660013a6683ab23797778e21f1f854744fdf05f68204b4cca4c8c04b5d1f4"
dependencies = [
"aws-credential-types",
"aws-smithy-eventstream",
"aws-smithy-http",
"aws-smithy-runtime-api",
"aws-smithy-types",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"crypto-bigint 0.5.5",
"form_urlencoded",
"hex",
"hmac",
"http 0.2.12",
- "http 1.3.1",
+ "http 1.4.0",
"p256 0.11.1",
"percent-encoding",
"ring",
@@ -746,30 +900,31 @@ dependencies = [
[[package]]
name = "aws-smithy-async"
-version = "1.2.5"
+version = "1.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c"
+checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc"
dependencies = [
"futures-util",
- "pin-project-lite 0.2.16",
- "tokio 1.47.1",
+ "pin-project-lite 0.2.17",
+ "tokio 1.51.0",
]
[[package]]
name = "aws-smithy-checksums"
-version = "0.63.6"
+version = "0.64.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9054b4cc5eda331cde3096b1576dec45365c5cbbca61d1fffa5f236e251dfce7"
+checksum = "6750f3dd509b0694a4377f0293ed2f9630d710b1cebe281fa8bac8f099f88bc6"
dependencies = [
"aws-smithy-http",
"aws-smithy-types",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"crc-fast",
"hex",
- "http 0.2.12",
- "http-body 0.4.6",
+ "http 1.4.0",
+ "http-body 1.0.1",
+ "http-body-util",
"md-5",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"sha1",
"sha2",
"tracing",
@@ -777,88 +932,90 @@ dependencies = [
[[package]]
name = "aws-smithy-eventstream"
-version = "0.60.10"
+version = "0.60.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "604c7aec361252b8f1c871a7641d5e0ba3a7f5a586e51b66bc9510a5519594d9"
+checksum = "faf09d74e5e32f76b8762da505a3cd59303e367a664ca67295387baa8c1d7548"
dependencies = [
"aws-smithy-types",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"crc32fast",
]
[[package]]
name = "aws-smithy-http"
-version = "0.62.3"
+version = "0.63.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c4dacf2d38996cf729f55e7a762b30918229917eca115de45dfa8dfb97796c9"
+checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231"
dependencies = [
"aws-smithy-eventstream",
"aws-smithy-runtime-api",
"aws-smithy-types",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"bytes-utils",
"futures-core",
- "http 0.2.12",
- "http 1.3.1",
- "http-body 0.4.6",
+ "futures-util",
+ "http 1.4.0",
+ "http-body 1.0.1",
+ "http-body-util",
"percent-encoding",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"pin-utils",
"tracing",
]
[[package]]
name = "aws-smithy-http-client"
-version = "1.0.6"
+version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f108f1ca850f3feef3009bdcc977be201bca9a91058864d9de0684e64514bee0"
+checksum = "6a2f165a7feee6f263028b899d0a181987f4fa7179a6411a32a439fba7c5f769"
dependencies = [
"aws-smithy-async",
"aws-smithy-runtime-api",
"aws-smithy-types",
"h2 0.3.27",
- "h2 0.4.11",
+ "h2 0.4.13",
"http 0.2.12",
- "http 1.3.1",
+ "http 1.4.0",
"http-body 0.4.6",
"hyper 0.14.32",
- "hyper 1.6.0",
+ "hyper 1.9.0",
"hyper-rustls 0.24.2",
"hyper-rustls 0.27.7",
"hyper-util",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"rustls 0.21.12",
- "rustls 0.23.31",
- "rustls-native-certs 0.8.1",
+ "rustls 0.23.37",
+ "rustls-native-certs 0.8.3",
"rustls-pki-types",
- "tokio 1.47.1",
+ "tokio 1.51.0",
+ "tokio-rustls 0.26.4",
"tower",
"tracing",
]
[[package]]
name = "aws-smithy-json"
-version = "0.61.4"
+version = "0.62.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a16e040799d29c17412943bdbf488fd75db04112d0c0d4b9290bacf5ae0014b9"
+checksum = "9648b0bb82a2eedd844052c6ad2a1a822d1f8e3adee5fbf668366717e428856a"
dependencies = [
"aws-smithy-types",
]
[[package]]
name = "aws-smithy-observability"
-version = "0.1.3"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9364d5989ac4dd918e5cc4c4bdcc61c9be17dcd2586ea7f69e348fc7c6cab393"
+checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c"
dependencies = [
"aws-smithy-runtime-api",
]
[[package]]
name = "aws-smithy-query"
-version = "0.60.7"
+version = "0.60.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2fbd61ceb3fe8a1cb7352e42689cec5335833cd9f94103a61e98f9bb61c64bb"
+checksum = "1a56d79744fb3edb5d722ef79d86081e121d3b9422cb209eb03aea6aa4f21ebd"
dependencies = [
"aws-smithy-types",
"urlencoding",
@@ -866,9 +1023,9 @@ dependencies = [
[[package]]
name = "aws-smithy-runtime"
-version = "1.8.6"
+version = "1.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e107ce0783019dbff59b3a244aa0c114e4a8c9d93498af9162608cd5474e796"
+checksum = "028999056d2d2fd58a697232f9eec4a643cf73a71cf327690a7edad1d2af2110"
dependencies = [
"aws-smithy-async",
"aws-smithy-http",
@@ -876,75 +1033,76 @@ dependencies = [
"aws-smithy-observability",
"aws-smithy-runtime-api",
"aws-smithy-types",
- "bytes 1.10.1",
- "fastrand 2.3.0",
+ "bytes 1.11.1",
+ "fastrand 2.4.0",
"http 0.2.12",
- "http 1.3.1",
+ "http 1.4.0",
"http-body 0.4.6",
"http-body 1.0.1",
- "pin-project-lite 0.2.16",
+ "http-body-util",
+ "pin-project-lite 0.2.17",
"pin-utils",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tracing",
]
[[package]]
name = "aws-smithy-runtime-api"
-version = "1.8.7"
+version = "1.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75d52251ed4b9776a3e8487b2a01ac915f73b2da3af8fc1e77e0fce697a550d4"
+checksum = "876ab3c9c29791ba4ba02b780a3049e21ec63dabda09268b175272c3733a79e6"
dependencies = [
"aws-smithy-async",
"aws-smithy-types",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"http 0.2.12",
- "http 1.3.1",
- "pin-project-lite 0.2.16",
- "tokio 1.47.1",
+ "http 1.4.0",
+ "pin-project-lite 0.2.17",
+ "tokio 1.51.0",
"tracing",
"zeroize",
]
[[package]]
name = "aws-smithy-types"
-version = "1.3.2"
+version = "1.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d498595448e43de7f4296b7b7a18a8a02c61ec9349128c80a368f7c3b4ab11a8"
+checksum = "9d73dbfbaa8e4bc57b9045137680b958d274823509a360abfd8e1d514d40c95c"
dependencies = [
"base64-simd",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"bytes-utils",
"futures-core",
"http 0.2.12",
- "http 1.3.1",
+ "http 1.4.0",
"http-body 0.4.6",
"http-body 1.0.1",
"http-body-util",
"itoa",
"num-integer",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"pin-utils",
"ryu",
"serde",
"time",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tokio-util",
]
[[package]]
name = "aws-smithy-xml"
-version = "0.60.10"
+version = "0.60.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3db87b96cb1b16c024980f133968d52882ca0daaee3a086c6decc500f6c99728"
+checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3"
dependencies = [
"xmlparser",
]
[[package]]
name = "aws-types"
-version = "1.3.8"
+version = "1.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b069d19bf01e46298eaedd7c6f283fe565a59263e53eebec945f3e6398f42390"
+checksum = "47c8323699dd9b3c8d5b3c13051ae9cdef58fd179957c882f8374dd8725962d9"
dependencies = [
"aws-credential-types",
"aws-smithy-async",
@@ -962,12 +1120,13 @@ checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
dependencies = [
"async-trait",
"axum-core",
- "bytes 1.10.1",
+ "axum-macros",
+ "bytes 1.11.1",
"futures-util",
- "http 1.3.1",
+ "http 1.4.0",
"http-body 1.0.1",
"http-body-util",
- "hyper 1.6.0",
+ "hyper 1.9.0",
"hyper-util",
"itoa",
"matchit",
@@ -975,14 +1134,14 @@ dependencies = [
"mime",
"multer",
"percent-encoding",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"rustversion",
"serde",
"serde_json",
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper 1.0.2",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tower",
"tower-layer",
"tower-service",
@@ -996,13 +1155,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199"
dependencies = [
"async-trait",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"futures-util",
- "http 1.3.1",
+ "http 1.4.0",
"http-body 1.0.1",
"http-body-util",
"mime",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"rustversion",
"sync_wrapper 1.0.2",
"tower-layer",
@@ -1018,16 +1177,16 @@ checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04"
dependencies = [
"axum",
"axum-core",
- "bytes 1.10.1",
- "fastrand 2.3.0",
+ "bytes 1.11.1",
+ "fastrand 2.4.0",
"futures-util",
"headers",
- "http 1.3.1",
+ "http 1.4.0",
"http-body 1.0.1",
"http-body-util",
"mime",
"multer",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"serde",
"tower",
"tower-layer",
@@ -1041,8 +1200,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -1054,13 +1213,13 @@ dependencies = [
"anyhow",
"axum",
"axum_typed_multipart_macros",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"chrono",
"futures-core",
"futures-util",
"tempfile",
"thiserror 1.0.69",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"uuid",
]
@@ -1071,18 +1230,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11eeb275b20a4c750c9fe7bf5a750e97e7944563003efd1c82e70c229a612ca1"
dependencies = [
"darling 0.20.11",
- "heck",
+ "heck 0.5.0",
"proc-macro-error",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
"ubyte",
]
+[[package]]
+name = "backon"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef"
+dependencies = [
+ "fastrand 2.4.0",
+]
+
[[package]]
name = "backtrace"
-version = "0.3.75"
+version = "0.3.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002"
+checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6"
dependencies = [
"addr2line",
"cfg-if",
@@ -1090,7 +1258,7 @@ dependencies = [
"miniz_oxide",
"object",
"rustc-demangle",
- "windows-targets 0.52.6",
+ "windows-link",
]
[[package]]
@@ -1141,9 +1309,9 @@ dependencies = [
[[package]]
name = "base64ct"
-version = "1.8.0"
+version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba"
+checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "beef"
@@ -1166,29 +1334,6 @@ dependencies = [
"serde",
]
-[[package]]
-name = "bindgen"
-version = "0.69.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088"
-dependencies = [
- "bitflags 2.9.1",
- "cexpr",
- "clang-sys",
- "itertools",
- "lazy_static",
- "lazycell",
- "log",
- "prettyplease",
- "proc-macro2",
- "quote 1.0.40",
- "regex",
- "rustc-hash",
- "shlex",
- "syn 2.0.104",
- "which",
-]
-
[[package]]
name = "binstring"
version = "0.1.7"
@@ -1197,9 +1342,9 @@ checksum = "0669d5a35b64fdb5ab7fb19cae13148b6b5cbdf4b8247faf54ece47f699c8cef"
[[package]]
name = "bit_field"
-version = "0.10.2"
+version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
+checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6"
[[package]]
name = "bitfield"
@@ -1215,15 +1360,18 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
-version = "2.9.1"
+version = "2.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
+checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af"
[[package]]
name = "bitstream-io"
-version = "2.6.0"
+version = "4.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
+checksum = "60d4bd9d1db2c6bdf285e223a7fa369d5ce98ec767dec949c6ca62863ce61757"
+dependencies = [
+ "core2",
+]
[[package]]
name = "bitvec"
@@ -1239,13 +1387,13 @@ dependencies = [
[[package]]
name = "blake2b_simd"
-version = "1.0.3"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "06e903a20b159e944f91ec8499fe1e55651480c541ea0a584f5d967c49ad9d99"
+checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3"
dependencies = [
"arrayref",
"arrayvec",
- "constant_time_eq",
+ "constant_time_eq 0.4.2",
]
[[package]]
@@ -1257,6 +1405,24 @@ dependencies = [
"generic-array 0.14.7",
]
+[[package]]
+name = "block-padding"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93"
+dependencies = [
+ "generic-array 0.14.7",
+]
+
+[[package]]
+name = "block2"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5"
+dependencies = [
+ "objc2",
+]
+
[[package]]
name = "blocking"
version = "1.6.2"
@@ -1266,10 +1432,20 @@ dependencies = [
"async-channel 2.5.0",
"async-task",
"futures-io",
- "futures-lite 2.6.1",
+ "futures-lite",
"piper",
]
+[[package]]
+name = "brotli-decompressor"
+version = "5.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
[[package]]
name = "bson"
version = "2.15.0"
@@ -1279,10 +1455,10 @@ dependencies = [
"ahash 0.8.12",
"base64 0.22.1",
"bitvec",
- "getrandom 0.2.16",
- "getrandom 0.3.3",
+ "getrandom 0.2.17",
+ "getrandom 0.3.4",
"hex",
- "indexmap 2.10.0",
+ "indexmap 2.13.1",
"js-sys",
"once_cell",
"rand 0.9.2",
@@ -1295,21 +1471,21 @@ dependencies = [
[[package]]
name = "built"
-version = "0.7.7"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b"
+checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64"
[[package]]
name = "bumpalo"
-version = "3.19.0"
+version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
+checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytemuck"
-version = "1.23.1"
+version = "1.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422"
+checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
[[package]]
name = "byteorder"
@@ -1331,9 +1507,9 @@ checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
[[package]]
name = "bytes"
-version = "1.10.1"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "bytes-utils"
@@ -1341,7 +1517,7 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35"
dependencies = [
- "bytes 1.10.1",
+ "bytes 1.11.1",
"either",
]
@@ -1359,7 +1535,7 @@ dependencies = [
"instant",
"once_cell",
"thiserror 1.0.69",
- "tokio 1.47.1",
+ "tokio 1.51.0",
]
[[package]]
@@ -1371,7 +1547,7 @@ dependencies = [
"cached_proc_macro_types",
"darling 0.14.4",
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"syn 1.0.109",
]
@@ -1383,29 +1559,39 @@ checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
[[package]]
name = "castaway"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dec551ab6e7578819132c713a93c022a05d60159dc86e7a7050223577484c55a"
+dependencies = [
+ "rustversion",
+]
+
+[[package]]
+name = "cbc"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
+checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6"
+dependencies = [
+ "cipher",
+]
[[package]]
name = "cc"
-version = "1.2.31"
+version = "1.2.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2"
+checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283"
dependencies = [
+ "find-msvc-tools",
"jobserver",
"libc",
"shlex",
]
[[package]]
-name = "cexpr"
-version = "0.6.0"
+name = "cesu8"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
-dependencies = [
- "nom",
-]
+checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
[[package]]
name = "cfb"
@@ -1419,32 +1605,37 @@ dependencies = [
]
[[package]]
-name = "cfg-expr"
-version = "0.15.8"
+name = "cfg-if"
+version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
-dependencies = [
- "smallvec",
- "target-lexicon",
-]
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
-name = "cfg-if"
-version = "1.0.1"
+name = "cfg_aliases"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+
+[[package]]
+name = "chacha20"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
+checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
+dependencies = [
+ "cfg-if",
+ "cpufeatures 0.3.0",
+ "rand_core 0.10.1",
+]
[[package]]
name = "chrono"
-version = "0.4.41"
+version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
+checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
- "android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
- "serde",
"wasm-bindgen",
"windows-link",
]
@@ -1460,39 +1651,31 @@ dependencies = [
]
[[package]]
-name = "clang-sys"
-version = "1.8.1"
+name = "cmake"
+version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
+checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
- "glob",
- "libc",
- "libloading",
+ "cc",
]
[[package]]
-name = "cloudabi"
-version = "0.0.3"
+name = "cms"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
+checksum = "7b77c319abfd5219629c45c34c89ba945ed3c5e49fcde9d16b6c3885f118a730"
dependencies = [
- "bitflags 1.3.2",
-]
-
-[[package]]
-name = "cmake"
-version = "0.1.54"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0"
-dependencies = [
- "cc",
+ "const-oid 0.9.6",
+ "der 0.7.10",
+ "spki 0.7.3",
+ "x509-cert",
]
[[package]]
name = "coarsetime"
-version = "0.1.36"
+version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91849686042de1b41cd81490edc83afbcb0abe5a9b6f2c4114f23ce8cca1bcf4"
+checksum = "e58eb270476aa4fc7843849f8a35063e8743b4dbcdf6dd0f8ea0886980c204c2"
dependencies = [
"libc",
"wasix",
@@ -1511,11 +1694,11 @@ version = "4.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
dependencies = [
- "bytes 1.10.1",
+ "bytes 1.11.1",
"futures-core",
"memchr",
- "pin-project-lite 0.2.16",
- "tokio 1.47.1",
+ "pin-project-lite 0.2.17",
+ "tokio 1.51.0",
"tokio-util",
]
@@ -1537,7 +1720,7 @@ dependencies = [
"async-trait",
"json5",
"lazy_static",
- "nom",
+ "nom 7.1.3",
"pathdiff",
"ron",
"rust-ini",
@@ -1574,7 +1757,7 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
dependencies = [
- "getrandom 0.2.16",
+ "getrandom 0.2.17",
"once_cell",
"tiny-keccak",
]
@@ -1585,11 +1768,20 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
+[[package]]
+name = "constant_time_eq"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b"
+
[[package]]
name = "convert_case"
-version = "0.4.0"
+version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
+dependencies = [
+ "unicode-segmentation",
+]
[[package]]
name = "cookie"
@@ -1637,6 +1829,15 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+[[package]]
+name = "core2"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b49ba7ef1ad6107f8824dbe97de947cbaac53c44e7f9756a1fba0d37c1eec505"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "core_maths"
version = "0.1.1"
@@ -1655,6 +1856,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "cpufeatures"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "crc"
version = "3.3.0"
@@ -1672,15 +1882,14 @@ checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[package]]
name = "crc-fast"
-version = "1.3.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6bf62af4cc77d8fe1c22dde4e721d87f2f54056139d8c412e1366b740305f56f"
+checksum = "2fd92aca2c6001b1bf5ba0ff84ee74ec8501b52bbef0cac80bf25a6c1d87a83d"
dependencies = [
"crc",
"digest",
- "libc",
- "rand 0.9.2",
- "regex",
+ "rustversion",
+ "spin 0.10.0",
]
[[package]]
@@ -1698,6 +1907,12 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "critical-section"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
+
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
@@ -1773,9 +1988,9 @@ dependencies = [
[[package]]
name = "crypto-common"
-version = "0.1.6"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array 0.14.7",
"rand_core 0.6.4",
@@ -1801,8 +2016,8 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -1822,24 +2037,24 @@ dependencies = [
[[package]]
name = "curl"
-version = "0.4.48"
+version = "0.4.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e2d5c8f48d9c0c23250e52b55e82a6ab4fdba6650c931f5a0a57a43abda812b"
+checksum = "79fc3b6dd0b87ba36e565715bf9a2ced221311db47bd18011676f24a6066edbc"
dependencies = [
"curl-sys",
"libc",
- "openssl-probe",
+ "openssl-probe 0.1.6",
"openssl-sys",
"schannel",
- "socket2 0.5.10",
+ "socket2 0.6.3",
"windows-sys 0.59.0",
]
[[package]]
name = "curl-sys"
-version = "0.4.82+curl-8.14.1"
+version = "0.4.87+curl-8.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4d63638b5ec65f1a4ae945287b3fd035be4554bbaf211901159c9a2a74fb5be"
+checksum = "61a460380f0ef783703dcbe909107f39c162adeac050d73c850055118b5b6327"
dependencies = [
"cc",
"libc",
@@ -1851,6 +2066,33 @@ dependencies = [
"windows-sys 0.59.0",
]
+[[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",
+ "digest",
+ "fiat-crypto",
+ "rustc_version",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "curve25519-dalek-derive"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
+dependencies = [
+ "proc-macro2",
+ "quote 1.0.45",
+ "syn 2.0.117",
+]
+
[[package]]
name = "darling"
version = "0.13.4"
@@ -1881,6 +2123,16 @@ dependencies = [
"darling_macro 0.20.11",
]
+[[package]]
+name = "darling"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
+dependencies = [
+ "darling_core 0.23.0",
+ "darling_macro 0.23.0",
+]
+
[[package]]
name = "darling_core"
version = "0.13.4"
@@ -1890,7 +2142,7 @@ dependencies = [
"fnv",
"ident_case",
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"strsim 0.10.0",
"syn 1.0.109",
]
@@ -1904,7 +2156,7 @@ dependencies = [
"fnv",
"ident_case",
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"strsim 0.10.0",
"syn 1.0.109",
]
@@ -1918,9 +2170,22 @@ dependencies = [
"fnv",
"ident_case",
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
+ "strsim 0.11.1",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0"
+dependencies = [
+ "ident_case",
+ "proc-macro2",
+ "quote 1.0.45",
"strsim 0.11.1",
- "syn 2.0.104",
+ "syn 2.0.117",
]
[[package]]
@@ -1930,7 +2195,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835"
dependencies = [
"darling_core 0.13.4",
- "quote 1.0.40",
+ "quote 1.0.45",
"syn 1.0.109",
]
@@ -1941,7 +2206,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e"
dependencies = [
"darling_core 0.14.4",
- "quote 1.0.40",
+ "quote 1.0.45",
"syn 1.0.109",
]
@@ -1952,8 +2217,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core 0.20.11",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
+dependencies = [
+ "darling_core 0.23.0",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -1971,15 +2247,15 @@ dependencies = [
[[package]]
name = "data-encoding"
-version = "2.9.0"
+version = "2.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476"
+checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea"
[[package]]
name = "data-url"
-version = "0.3.1"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a"
+checksum = "be1e0bca6c3637f992fc1cc7cbc52a78c1ef6db076dbf1059c4323d6a2048376"
[[package]]
name = "deadqueue"
@@ -1988,7 +2264,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ff1266be84e4e04a81e2d1cbb998cb271b374fb73ce780245ef96c037c50cd"
dependencies = [
"crossbeam-queue",
- "tokio 1.47.1",
+ "tokio 1.51.0",
]
[[package]]
@@ -2003,9 +2279,13 @@ dependencies = [
[[package]]
name = "decancer"
-version = "1.6.5"
+version = "3.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "080b09f6adad25c23d8c47c54e52e59b0dc09d079c4b23e0f147dac440359d0d"
+checksum = "a9244323129647178bf41ac861a2cdb9d9c81b9b09d3d0d1de9cd302b33b8a1d"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
[[package]]
name = "der"
@@ -2014,7 +2294,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4"
dependencies = [
"const-oid 0.6.2",
- "der_derive",
+ "der_derive 0.4.1",
]
[[package]]
@@ -2035,10 +2315,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
dependencies = [
"const-oid 0.9.6",
+ "der_derive 0.7.3",
+ "flagset",
"pem-rfc7468 0.7.0",
"zeroize",
]
+[[package]]
+name = "der-parser"
+version = "10.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6"
+dependencies = [
+ "asn1-rs",
+ "displaydoc",
+ "nom 7.1.3",
+ "num-bigint",
+ "num-traits",
+ "rusticata-macros",
+]
+
[[package]]
name = "der_derive"
version = "0.4.1"
@@ -2046,19 +2342,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aed3b3c608dc56cf36c45fe979d04eda51242e6703d8d0bb03426ef7c41db6a"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"syn 1.0.109",
"synstructure 0.12.6",
]
+[[package]]
+name = "der_derive"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8034092389675178f570469e6c3b0465d3d30b4505c294a6550db47f3c17ad18"
+dependencies = [
+ "proc-macro2",
+ "quote 1.0.45",
+ "syn 2.0.117",
+]
+
[[package]]
name = "deranged"
-version = "0.4.0"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
+checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c"
dependencies = [
"powerfmt",
- "serde",
+ "serde_core",
]
[[package]]
@@ -2068,19 +2375,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
name = "derive-where"
-version = "1.5.0"
+version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b"
+checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -2088,12 +2395,42 @@ name = "derive_more"
version = "0.99.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
+dependencies = [
+ "proc-macro2",
+ "quote 1.0.45",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
dependencies = [
"convert_case",
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"rustc_version",
- "syn 2.0.104",
+ "syn 2.0.117",
+ "unicode-xid 0.2.6",
+]
+
+[[package]]
+name = "des"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e"
+dependencies = [
+ "cipher",
]
[[package]]
@@ -2113,7 +2450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71b28680d8be17a570a2334922518be6adc3f58ecc880cbb404eaeb8624fd867"
dependencies = [
"devise_core",
- "quote 1.0.40",
+ "quote 1.0.45",
]
[[package]]
@@ -2122,11 +2459,11 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7"
dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.11.0",
"proc-macro2",
"proc-macro2-diagnostics",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -2141,6 +2478,16 @@ dependencies = [
"subtle",
]
+[[package]]
+name = "dispatch2"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38"
+dependencies = [
+ "bitflags 2.11.0",
+ "objc2",
+]
+
[[package]]
name = "displaydoc"
version = "0.2.5"
@@ -2148,8 +2495,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -2160,9 +2507,9 @@ checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
[[package]]
name = "dtoa"
-version = "1.0.10"
+version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04"
+checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590"
[[package]]
name = "dtoa-short"
@@ -2229,14 +2576,38 @@ dependencies = [
"thiserror 1.0.69",
]
+[[package]]
+name = "ed25519"
+version = "2.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
+dependencies = [
+ "pkcs8 0.10.2",
+ "signature 2.2.0",
+]
+
[[package]]
name = "ed25519-compact"
-version = "2.1.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e9b3460f44bea8cd47f45a0c70892f1eff856d97cd55358b2f73f663789f6190"
+checksum = "33ce99a9e19c84beb4cc35ece85374335ccc398240712114c85038319ed709bd"
dependencies = [
"ct-codecs",
- "getrandom 0.2.16",
+ "getrandom 0.3.4",
+]
+
+[[package]]
+name = "ed25519-dalek"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
+dependencies = [
+ "curve25519-dalek",
+ "ed25519",
+ "serde",
+ "sha2",
+ "subtle",
+ "zeroize",
]
[[package]]
@@ -2323,10 +2694,10 @@ version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc"
dependencies = [
- "heck",
+ "heck 0.5.0",
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -2340,13 +2711,13 @@ dependencies = [
[[package]]
name = "enum-iterator-derive"
-version = "1.4.0"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b"
+checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -2378,8 +2749,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -2399,19 +2770,19 @@ dependencies = [
[[package]]
name = "errno"
-version = "0.3.13"
+version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys 0.60.2",
+ "windows-sys 0.61.2",
]
[[package]]
name = "euclid"
-version = "0.22.11"
+version = "0.22.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48"
+checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06"
dependencies = [
"num-traits",
]
@@ -2430,7 +2801,7 @@ checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
dependencies = [
"concurrent-queue",
"parking",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
]
[[package]]
@@ -2440,14 +2811,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
dependencies = [
"event-listener 5.4.1",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
]
[[package]]
name = "exr"
-version = "1.73.0"
+version = "1.74.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f83197f59927b46c04a183a619b7c29df34e63e63c7869320862268c0ef687e0"
+checksum = "4300e043a56aa2cb633c01af81ca8f699a321879a7854d3896a0ba89056363be"
dependencies = [
"bit_field",
"half",
@@ -2469,9 +2840,29 @@ dependencies = [
[[package]]
name = "fastrand"
-version = "2.3.0"
+version = "2.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a043dc74da1e37d6afe657061213aa6f425f855399a11d3463c6ecccc4dfda1f"
+
+[[package]]
+name = "fax"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+checksum = "f05de7d48f37cd6730705cbca900770cab77a89f413d23e100ad7fad7795a0ab"
+dependencies = [
+ "fax_derive",
+]
+
+[[package]]
+name = "fax_derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0aca10fb742cb43f9e7bb8467c91aa9bcb8e3ffbc6a6f7389bb93ffc920577d"
+dependencies = [
+ "proc-macro2",
+ "quote 1.0.45",
+ "syn 2.0.117",
+]
[[package]]
name = "fcm_v1"
@@ -2525,6 +2916,12 @@ dependencies = [
"serde_json",
]
+[[package]]
+name = "fiat-crypto"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
+
[[package]]
name = "figment"
version = "0.10.19"
@@ -2539,6 +2936,12 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+
[[package]]
name = "findshlibs"
version = "0.10.2"
@@ -2551,11 +2954,23 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
+[[package]]
+name = "flagset"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7ac824320a75a52197e8f2d787f6a38b6718bb6897a35142d749af3c0e8f4fe"
+
[[package]]
name = "flate2"
-version = "1.1.2"
+version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d"
+checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -2570,6 +2985,17 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "flume"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e139bc46ca777eb5efaf62df0ab8cc5fd400866427e56c68b22e414e53bd3be"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+ "spin 0.9.8",
+]
+
[[package]]
name = "fnv"
version = "1.0.7"
@@ -2582,6 +3008,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
+[[package]]
+name = "foldhash"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
+
[[package]]
name = "fontconfig-parser"
version = "0.5.8"
@@ -2611,7 +3043,28 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
- "foreign-types-shared",
+ "foreign-types-shared 0.1.1",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
+dependencies = [
+ "foreign-types-macros",
+ "foreign-types-shared 0.3.1",
+]
+
+[[package]]
+name = "foreign-types-macros"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
+dependencies = [
+ "proc-macro2",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -2620,11 +3073,17 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+[[package]]
+name = "foreign-types-shared"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b"
+
[[package]]
name = "form_urlencoded"
-version = "1.2.1"
+version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
"percent-encoding",
]
@@ -2637,7 +3096,7 @@ checksum = "4b8e3a1339ed45ad8fde94530c4bdcbd5f371a3c6bd3bf57682923792830aa37"
dependencies = [
"arc-swap",
"async-trait",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"bytes-utils",
"crossbeam-queue",
"float-cmp",
@@ -2648,7 +3107,7 @@ dependencies = [
"redis-protocol",
"semver",
"socket2 0.5.10",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tokio-stream",
"tokio-util",
"url",
@@ -2661,12 +3120,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
-[[package]]
-name = "fuchsia-cprng"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
-
[[package]]
name = "funty"
version = "2.0.0"
@@ -2685,9 +3138,9 @@ dependencies = [
[[package]]
name = "futures"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
"futures-channel",
"futures-core",
@@ -2700,9 +3153,9 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
"futures-core",
"futures-sink",
@@ -2710,15 +3163,15 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-executor"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
dependencies = [
"futures-core",
"futures-task",
@@ -2727,24 +3180,9 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
-
-[[package]]
-name = "futures-lite"
-version = "1.13.0"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce"
-dependencies = [
- "fastrand 1.9.0",
- "futures-core",
- "futures-io",
- "memchr",
- "parking",
- "pin-project-lite 0.2.16",
- "waker-fn",
-]
+checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
[[package]]
name = "futures-lite"
@@ -2752,11 +3190,11 @@ version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
dependencies = [
- "fastrand 2.3.0",
+ "fastrand 2.4.0",
"futures-core",
"futures-io",
"parking",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
]
[[package]]
@@ -2767,31 +3205,42 @@ checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06"
dependencies = [
"futures-channel",
"futures-task",
- "tokio 1.47.1",
+ "tokio 1.51.0",
]
[[package]]
name = "futures-macro"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
-name = "futures-sink"
-version = "0.3.31"
+name = "futures-rustls"
+version = "0.26.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb"
+dependencies = [
+ "futures-io",
+ "rustls 0.23.37",
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
[[package]]
name = "futures-task"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-timer"
@@ -2801,9 +3250,9 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24"
[[package]]
name = "futures-util"
-version = "0.3.31"
+version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-channel",
"futures-core",
@@ -2812,8 +3261,7 @@ dependencies = [
"futures-sink",
"futures-task",
"memchr",
- "pin-project-lite 0.2.16",
- "pin-utils",
+ "pin-project-lite 0.2.17",
"slab",
]
@@ -2836,21 +3284,7 @@ dependencies = [
"libc",
"log",
"rustversion",
- "windows 0.48.0",
-]
-
-[[package]]
-name = "generator"
-version = "0.8.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d18470a76cb7f8ff746cf1f7470914f900252ec36bbc40b569d74b1258446827"
-dependencies = [
- "cc",
- "cfg-if",
- "libc",
- "log",
- "rustversion",
- "windows 0.61.3",
+ "windows",
]
[[package]]
@@ -2866,49 +3300,64 @@ dependencies = [
[[package]]
name = "generic-array"
-version = "1.2.0"
+version = "1.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8c8444bc9d71b935156cc0ccab7f622180808af7867b1daae6547d773591703"
+checksum = "eaf57c49a95fd1fe24b90b3033bee6dc7e8f1288d51494cb44e627c295e38542"
dependencies = [
+ "rustversion",
"typenum",
]
[[package]]
name = "getopts"
-version = "0.2.23"
+version = "0.2.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1"
+checksum = "cfe4fbac503b8d1f88e6676011885f34b7174f46e59956bba534ba83abded4df"
dependencies = [
"unicode-width",
]
[[package]]
name = "getrandom"
-version = "0.2.16"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"js-sys",
"libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasi",
"wasm-bindgen",
]
[[package]]
name = "getrandom"
-version = "0.3.3"
+version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [
"cfg-if",
"js-sys",
"libc",
- "r-efi",
- "wasi 0.14.2+wasi-0.2.4",
+ "r-efi 5.3.0",
+ "wasip2",
"wasm-bindgen",
]
+[[package]]
+name = "getrandom"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi 6.0.0",
+ "rand_core 0.10.1",
+ "wasip2",
+ "wasip3",
+]
+
[[package]]
name = "getset"
version = "0.1.6"
@@ -2917,8 +3366,8 @@ checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912"
dependencies = [
"proc-macro-error2",
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -2941,11 +3390,21 @@ dependencies = [
"weezl",
]
+[[package]]
+name = "gif"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f5df2ba84018d80c213569363bdcd0c64e6933c67fe4c1d60ecf822971a3c35e"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
[[package]]
name = "gimli"
-version = "0.31.1"
+version = "0.32.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7"
[[package]]
name = "git2"
@@ -2962,9 +3421,9 @@ dependencies = [
[[package]]
name = "glob"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
+checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "gloo-timers"
@@ -3006,46 +3465,47 @@ version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d"
dependencies = [
- "bytes 1.10.1",
+ "bytes 1.11.1",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http 0.2.12",
- "indexmap 2.10.0",
+ "indexmap 2.13.1",
"slab",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tokio-util",
"tracing",
]
[[package]]
name = "h2"
-version = "0.4.11"
+version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785"
+checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
dependencies = [
"atomic-waker",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"fnv",
"futures-core",
"futures-sink",
- "http 1.3.1",
- "indexmap 2.10.0",
+ "http 1.4.0",
+ "indexmap 2.13.1",
"slab",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tokio-util",
"tracing",
]
[[package]]
name = "half"
-version = "2.6.0"
+version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
+checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [
"cfg-if",
"crunchy",
+ "zerocopy",
]
[[package]]
@@ -3082,20 +3542,25 @@ name = "hashbrown"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
- "ahash 0.8.12",
- "allocator-api2",
+ "foldhash 0.1.5",
]
[[package]]
name = "hashbrown"
-version = "0.15.4"
+version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
- "foldhash",
+ "foldhash 0.2.0",
]
[[package]]
@@ -3105,9 +3570,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb"
dependencies = [
"base64 0.22.1",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"headers-core",
- "http 1.3.1",
+ "http 1.4.0",
"httpdate",
"mime",
"sha1",
@@ -3119,9 +3584,15 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4"
dependencies = [
- "http 1.3.1",
+ "http 1.4.0",
]
+[[package]]
+name = "heck"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
+
[[package]]
name = "heck"
version = "0.5.0"
@@ -3149,11 +3620,35 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+[[package]]
+name = "hickory-net"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2295ed2f9c31e471e1428a8f88a3f0e1f4b27c15049592138d1eebe9c35b183"
+dependencies = [
+ "async-trait",
+ "cfg-if",
+ "data-encoding",
+ "futures-channel",
+ "futures-io",
+ "futures-util",
+ "hickory-proto 0.26.1",
+ "idna 1.1.0",
+ "ipnet",
+ "jni 0.22.4",
+ "rand 0.10.1",
+ "thiserror 2.0.18",
+ "tinyvec",
+ "tokio 1.51.0",
+ "tracing",
+ "url",
+]
+
[[package]]
name = "hickory-proto"
-version = "0.24.4"
+version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248"
+checksum = "f8a6fe56c0038198998a6f217ca4e7ef3a5e51f46163bd6dd60b5c71ca6c6502"
dependencies = [
"async-trait",
"cfg-if",
@@ -3162,35 +3657,82 @@ dependencies = [
"futures-channel",
"futures-io",
"futures-util",
- "idna 1.0.3",
+ "idna 1.1.0",
"ipnet",
"once_cell",
- "rand 0.8.5",
- "thiserror 1.0.69",
+ "rand 0.9.2",
+ "ring",
+ "thiserror 2.0.18",
+ "tinyvec",
+ "tokio 1.51.0",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "hickory-proto"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bab31817bfb44672a252e97fe81cd0c18d1b2cf892108922f6818820df8c643"
+dependencies = [
+ "data-encoding",
+ "idna 1.1.0",
+ "ipnet",
+ "jni 0.22.4",
+ "once_cell",
+ "prefix-trie",
+ "rand 0.10.1",
+ "ring",
+ "thiserror 2.0.18",
"tinyvec",
- "tokio 1.47.1",
"tracing",
"url",
]
[[package]]
name = "hickory-resolver"
-version = "0.24.4"
+version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e"
+checksum = "dc62a9a99b0bfb44d2ab95a7208ac952d31060efc16241c87eaf36406fecf87a"
dependencies = [
"cfg-if",
"futures-util",
- "hickory-proto",
+ "hickory-proto 0.25.2",
"ipconfig",
- "lru-cache",
+ "moka",
"once_cell",
"parking_lot",
- "rand 0.8.5",
+ "rand 0.9.2",
"resolv-conf",
"smallvec",
- "thiserror 1.0.69",
- "tokio 1.47.1",
+ "thiserror 2.0.18",
+ "tokio 1.51.0",
+ "tracing",
+]
+
+[[package]]
+name = "hickory-resolver"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0d58d28879ceecde6607729660c2667a081ccdc082e082675042793960f178c"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "hickory-net",
+ "hickory-proto 0.26.1",
+ "ipconfig",
+ "ipnet",
+ "jni 0.22.4",
+ "moka",
+ "ndk-context",
+ "once_cell",
+ "parking_lot",
+ "rand 0.10.1",
+ "resolv-conf",
+ "smallvec",
+ "system-configuration 0.7.0",
+ "thiserror 2.0.18",
+ "tokio 1.51.0",
"tracing",
]
@@ -3214,35 +3756,35 @@ dependencies = [
[[package]]
name = "hmac-sha1-compact"
-version = "1.1.5"
+version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18492c9f6f9a560e0d346369b665ad2bdbc89fa9bceca75796584e79042694c3"
+checksum = "b0b3ba31f6dc772cc8221ce81dbbbd64fa1e668255a6737d95eeace59b5a8823"
[[package]]
name = "hmac-sha256"
-version = "1.1.12"
+version = "1.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425"
+checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f"
dependencies = [
"digest",
]
[[package]]
name = "hmac-sha512"
-version = "1.1.7"
+version = "1.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e89e8d20b3799fa526152a5301a771eaaad80857f83e01b23216ceaafb2d9280"
+checksum = "019ece39bbefc17f13f677a690328cb978dbf6790e141a3c24e66372cb38588b"
dependencies = [
"digest",
]
[[package]]
name = "home"
-version = "0.5.11"
+version = "0.5.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
+checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d"
dependencies = [
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -3266,8 +3808,8 @@ dependencies = [
"mac",
"markup5ever",
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -3276,19 +3818,18 @@ version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
- "bytes 1.10.1",
+ "bytes 1.11.1",
"fnv",
"itoa",
]
[[package]]
name = "http"
-version = "1.3.1"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
+checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
dependencies = [
- "bytes 1.10.1",
- "fnv",
+ "bytes 1.11.1",
"itoa",
]
@@ -3298,9 +3839,9 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
- "bytes 1.10.1",
+ "bytes 1.11.1",
"http 0.2.12",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
]
[[package]]
@@ -3309,8 +3850,8 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
- "bytes 1.10.1",
- "http 1.3.1",
+ "bytes 1.11.1",
+ "http 1.4.0",
]
[[package]]
@@ -3319,11 +3860,11 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
- "bytes 1.10.1",
+ "bytes 1.11.1",
"futures-core",
- "http 1.3.1",
+ "http 1.4.0",
"http-body 1.0.1",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
]
[[package]]
@@ -3353,7 +3894,7 @@ version = "0.14.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7"
dependencies = [
- "bytes 1.10.1",
+ "bytes 1.11.1",
"futures-channel",
"futures-core",
"futures-util",
@@ -3363,9 +3904,9 @@ dependencies = [
"httparse",
"httpdate",
"itoa",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"socket2 0.5.10",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tower-service",
"tracing",
"want",
@@ -3373,22 +3914,23 @@ dependencies = [
[[package]]
name = "hyper"
-version = "1.6.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80"
+checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
dependencies = [
- "bytes 1.10.1",
+ "atomic-waker",
+ "bytes 1.11.1",
"futures-channel",
- "futures-util",
- "h2 0.4.11",
- "http 1.3.1",
+ "futures-core",
+ "h2 0.4.13",
+ "http 1.4.0",
"http-body 1.0.1",
"httparse",
"httpdate",
"itoa",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"smallvec",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"want",
]
@@ -3404,7 +3946,7 @@ dependencies = [
"log",
"rustls 0.21.12",
"rustls-native-certs 0.6.3",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tokio-rustls 0.24.1",
]
@@ -3415,12 +3957,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c"
dependencies = [
"futures-util",
- "http 1.3.1",
- "hyper 1.6.0",
+ "http 1.4.0",
+ "hyper 1.9.0",
"hyper-util",
"rustls 0.22.4",
"rustls-pki-types",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tokio-rustls 0.25.0",
"tower-service",
"webpki-roots 0.26.11",
@@ -3432,14 +3974,14 @@ version = "0.27.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58"
dependencies = [
- "http 1.3.1",
- "hyper 1.6.0",
+ "http 1.4.0",
+ "hyper 1.9.0",
"hyper-util",
- "rustls 0.23.31",
- "rustls-native-certs 0.8.1",
+ "rustls 0.23.37",
+ "rustls-native-certs 0.8.3",
"rustls-pki-types",
- "tokio 1.47.1",
- "tokio-rustls 0.26.2",
+ "tokio 1.51.0",
+ "tokio-rustls 0.26.4",
"tower-service",
]
@@ -3449,50 +3991,33 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
- "bytes 1.10.1",
+ "bytes 1.11.1",
"hyper 0.14.32",
"native-tls",
- "tokio 1.47.1",
- "tokio-native-tls",
-]
-
-[[package]]
-name = "hyper-tls"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
-dependencies = [
- "bytes 1.10.1",
- "http-body-util",
- "hyper 1.6.0",
- "hyper-util",
- "native-tls",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tokio-native-tls",
- "tower-service",
]
[[package]]
name = "hyper-util"
-version = "0.1.16"
+version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
+checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
"base64 0.22.1",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"futures-channel",
- "futures-core",
"futures-util",
- "http 1.3.1",
+ "http 1.4.0",
"http-body 1.0.1",
- "hyper 1.6.0",
+ "hyper 1.9.0",
"ipnet",
"libc",
"percent-encoding",
- "pin-project-lite 0.2.16",
- "socket2 0.6.0",
- "system-configuration 0.6.1",
- "tokio 1.47.1",
+ "pin-project-lite 0.2.17",
+ "socket2 0.6.3",
+ "system-configuration 0.7.0",
+ "tokio 1.51.0",
"tower-service",
"tracing",
"windows-registry",
@@ -3500,9 +4025,9 @@ dependencies = [
[[package]]
name = "iana-time-zone"
-version = "0.1.63"
+version = "0.1.65"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
dependencies = [
"android_system_properties",
"core-foundation-sys",
@@ -3524,12 +4049,13 @@ dependencies = [
[[package]]
name = "icu_collections"
-version = "2.0.0"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47"
+checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
dependencies = [
"displaydoc",
"potential_utf",
+ "utf8_iter",
"yoke",
"zerofrom",
"zerovec",
@@ -3537,9 +4063,9 @@ dependencies = [
[[package]]
name = "icu_locale_core"
-version = "2.0.0"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a"
+checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
dependencies = [
"displaydoc",
"litemap",
@@ -3550,11 +4076,10 @@ dependencies = [
[[package]]
name = "icu_normalizer"
-version = "2.0.0"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979"
+checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
dependencies = [
- "displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
@@ -3565,42 +4090,38 @@ dependencies = [
[[package]]
name = "icu_normalizer_data"
-version = "2.0.0"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3"
+checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
[[package]]
name = "icu_properties"
-version = "2.0.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b"
+checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
dependencies = [
- "displaydoc",
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
- "potential_utf",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
-version = "2.0.1"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632"
+checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
[[package]]
name = "icu_provider"
-version = "2.0.0"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af"
+checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
dependencies = [
"displaydoc",
"icu_locale_core",
- "stable_deref_trait",
- "tinystr",
"writeable",
"yoke",
"zerofrom",
@@ -3608,6 +4129,12 @@ dependencies = [
"zerovec",
]
+[[package]]
+name = "id-arena"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
+
[[package]]
name = "ident_case"
version = "1.0.1"
@@ -3647,9 +4174,9 @@ dependencies = [
[[package]]
name = "idna"
-version = "1.0.3"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec",
@@ -3668,31 +4195,32 @@ dependencies = [
[[package]]
name = "if_chain"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
+checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb"
[[package]]
name = "image"
-version = "0.25.6"
+version = "0.25.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a"
+checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
dependencies = [
"bytemuck",
"byteorder-lite",
"color_quant",
"exr",
- "gif",
- "image-webp 0.2.3",
+ "gif 0.14.1",
+ "image-webp 0.2.4",
+ "moxcms",
"num-traits",
- "png",
+ "png 0.18.1",
"qoi",
"ravif",
"rayon",
"rgb",
"tiff",
- "zune-core",
- "zune-jpeg",
+ "zune-core 0.5.1",
+ "zune-jpeg 0.5.15",
]
[[package]]
@@ -3707,9 +4235,9 @@ dependencies = [
[[package]]
name = "image-webp"
-version = "0.2.3"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6970fe7a5300b4b42e62c52efa0187540a5bef546c60edaf554ef595d2e6f0b"
+checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
dependencies = [
"byteorder-lite",
"quick-error 2.0.1",
@@ -3723,9 +4251,9 @@ checksum = "edcd27d72f2f071c64249075f42e205ff93c9a4c5f6c6da53e79ed9f9832c285"
[[package]]
name = "imgref"
-version = "1.11.0"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
+checksum = "e7c5cedc30da3a610cac6b4ba17597bdf7152cf974e8aab3afb3d54455e371c8"
[[package]]
name = "impl_ops"
@@ -3739,20 +4267,21 @@ version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
- "autocfg 1.5.0",
+ "autocfg",
"hashbrown 0.12.3",
"serde",
]
[[package]]
name = "indexmap"
-version = "2.10.0"
+version = "2.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661"
+checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff"
dependencies = [
"equivalent",
- "hashbrown 0.15.4",
+ "hashbrown 0.16.1",
"serde",
+ "serde_core",
]
[[package]]
@@ -3776,6 +4305,7 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
+ "block-padding",
"generic-array 0.14.7",
]
@@ -3795,44 +4325,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
-]
-
-[[package]]
-name = "io-uring"
-version = "0.7.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4"
-dependencies = [
- "bitflags 2.9.1",
- "cfg-if",
- "libc",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
name = "ipconfig"
-version = "0.3.2"
+version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f"
+checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222"
dependencies = [
- "socket2 0.5.10",
+ "socket2 0.6.3",
"widestring",
- "windows-sys 0.48.0",
- "winreg",
+ "windows-registry",
+ "windows-result",
+ "windows-sys 0.61.2",
]
[[package]]
name = "ipnet"
-version = "2.11.0"
+version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
+checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
+dependencies = [
+ "serde",
+]
[[package]]
name = "iri-string"
-version = "0.7.8"
+version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2"
+checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
dependencies = [
"memchr",
"serde",
@@ -3840,34 +4363,33 @@ dependencies = [
[[package]]
name = "is-terminal"
-version = "0.4.16"
+version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9"
+checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi 0.5.2",
"libc",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
name = "isahc"
-version = "1.7.2"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9"
+checksum = "49980b382c0e59635b99bb2d9ef4c039a90aefa1b821d5d7a8526d4504e14594"
dependencies = [
- "async-channel 1.9.0",
+ "async-channel 2.5.0",
"castaway",
"crossbeam-utils",
"curl",
"curl-sys",
"encoding_rs",
- "event-listener 2.5.3",
- "futures-lite 1.13.0",
+ "event-listener 5.4.1",
+ "futures-lite",
"http 0.2.12",
"log",
"mime",
- "once_cell",
- "polling 2.8.0",
+ "polling",
"serde",
"serde_json",
"slab",
@@ -3884,12 +4406,21 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d4e5d712dd664b11e778d1cfc06c79ba2700d6bc1771e44fb7b6a4656b487d"
dependencies = [
- "generic-array 1.2.0",
- "schemars 0.8.22",
+ "generic-array 1.3.5",
+ "schemars",
"serde",
"time",
]
+[[package]]
+name = "itertools"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
+dependencies = [
+ "either",
+]
+
[[package]]
name = "itertools"
version = "0.12.1"
@@ -3899,52 +4430,152 @@ dependencies = [
"either",
]
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
[[package]]
name = "itoa"
-version = "1.0.15"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
-name = "jobserver"
-version = "0.1.33"
+name = "jni"
+version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
+checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
dependencies = [
- "getrandom 0.3.3",
- "libc",
+ "cesu8",
+ "cfg-if",
+ "combine",
+ "jni-sys 0.3.1",
+ "log",
+ "thiserror 1.0.69",
+ "walkdir",
+ "windows-sys 0.45.0",
]
[[package]]
-name = "jpeg-decoder"
-version = "0.3.2"
+name = "jni"
+version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "00810f1d8b74be64b13dbf3db89ac67740615d6c891f0e7b6179326533011a07"
+checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498"
+dependencies = [
+ "cfg-if",
+ "combine",
+ "jni-macros",
+ "jni-sys 0.4.1",
+ "log",
+ "simd_cesu8",
+ "thiserror 2.0.18",
+ "walkdir",
+ "windows-link",
+]
[[package]]
-name = "js-sys"
-version = "0.3.77"
+name = "jni-macros"
+version = "0.22.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3"
dependencies = [
- "once_cell",
- "wasm-bindgen",
+ "proc-macro2",
+ "quote 1.0.45",
+ "rustc_version",
+ "simd_cesu8",
+ "syn 2.0.117",
]
[[package]]
-name = "json5"
-version = "0.4.1"
+name = "jni-sys"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
+checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258"
dependencies = [
- "pest",
- "pest_derive",
- "serde",
+ "jni-sys 0.4.1",
]
[[package]]
-name = "jwt-simple"
-version = "0.11.9"
+name = "jni-sys"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2"
+dependencies = [
+ "jni-sys-macros",
+]
+
+[[package]]
+name = "jni-sys-macros"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264"
+dependencies = [
+ "quote 1.0.45",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "jobserver"
+version = "0.1.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
+dependencies = [
+ "getrandom 0.3.4",
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "json5"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1"
+dependencies = [
+ "pest",
+ "pest_derive",
+ "serde",
+]
+
+[[package]]
+name = "jsonwebtoken"
+version = "10.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0529410abe238729a60b108898784df8984c87f6054c9c4fcacc47e4803c1ce1"
+dependencies = [
+ "base64 0.22.1",
+ "ed25519-dalek",
+ "getrandom 0.2.17",
+ "hmac",
+ "js-sys",
+ "p256 0.13.2",
+ "p384",
+ "rand 0.8.5",
+ "rsa 0.9.10",
+ "serde",
+ "serde_json",
+ "sha2",
+ "signature 2.2.0",
+]
+
+[[package]]
+name = "jwt-simple"
+version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357892bb32159d763abdea50733fadcb9a8e1c319a9aa77592db8555d05af83e"
dependencies = [
@@ -3960,7 +4591,7 @@ dependencies = [
"p256 0.13.2",
"p384",
"rand 0.8.5",
- "rsa",
+ "rsa 0.7.2",
"serde",
"serde_json",
"spki 0.6.0",
@@ -3970,18 +4601,18 @@ dependencies = [
[[package]]
name = "jxl-bitstream"
-version = "0.4.1"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5855ff16398ffbcf81fee52c41ca65326499c8764b21bb9952c367ace98995fb"
+checksum = "b480e752277e29eb4054f69546887a9b84656fe78c08f54ba5850ced98a378fe"
dependencies = [
"tracing",
]
[[package]]
name = "jxl-coding"
-version = "0.4.1"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da5b5093904e940bc11ef50e872c7bdf7b6e88653f012b925f8479daf212b5c9"
+checksum = "cd972bcd125e776f1eb241ac50e39f956095a1c2770c64736c968f8946bd9a3c"
dependencies = [
"jxl-bitstream",
"tracing",
@@ -3989,28 +4620,31 @@ dependencies = [
[[package]]
name = "jxl-color"
-version = "0.7.1"
+version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0cb1c31e10054079df585633fc14fb9e4c96565c58c05b983c502e2472b57fa0"
+checksum = "f316b1358c1711755b3ee8e8cb5c4a1dad12e796233088a7a513440782de80b2"
dependencies = [
"jxl-bitstream",
"jxl-coding",
"jxl-grid",
+ "jxl-image",
+ "jxl-oxide-common",
"jxl-threadpool",
"tracing",
]
[[package]]
name = "jxl-frame"
-version = "0.9.0"
+version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e35b289aa0f24044167d83a1c29ae2b99210c5082ab7e3e90dacbcae818aa0a2"
+checksum = "2d967c6fd669c7c01060b5022d8835fa82fd46b06ffc98b549f17600a097c2b3"
dependencies = [
"jxl-bitstream",
"jxl-coding",
"jxl-grid",
"jxl-image",
"jxl-modular",
+ "jxl-oxide-common",
"jxl-threadpool",
"jxl-vardct",
"tracing",
@@ -4018,60 +4652,94 @@ dependencies = [
[[package]]
name = "jxl-grid"
-version = "0.4.2"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70b96735a85299a6bce8664643fcb759f29ea73ce344a90b6f7de9b92a8e9b2d"
+checksum = "a0e0ef92d5d60e76bf41098e57e985f523185e08fad54268da448637feca6989"
dependencies = [
"tracing",
]
[[package]]
name = "jxl-image"
-version = "0.9.0"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b31b17ed3bd0e3b65e7b06628f5930e009dda6cd17638cf5159a20a3feedec6"
+checksum = "c5f752d62577c702a94dbbce4045caf08cb58639e8a4d56464b40ecf33ffe565"
dependencies = [
"jxl-bitstream",
- "jxl-color",
"jxl-grid",
+ "jxl-oxide-common",
+ "tracing",
+]
+
+[[package]]
+name = "jxl-jbr"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e35d032bcec660647828527ff42c6f5776d2fd44b8357f9f6d9ac6dc07218e46"
+dependencies = [
+ "brotli-decompressor",
+ "jxl-bitstream",
+ "jxl-frame",
+ "jxl-grid",
+ "jxl-image",
+ "jxl-modular",
+ "jxl-oxide-common",
+ "jxl-threadpool",
+ "jxl-vardct",
"tracing",
]
[[package]]
name = "jxl-modular"
-version = "0.7.1"
+version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da3b9fb8f46e63a14ecedefbd0f873b04162aaf8a09676b630c31bc8dadc4638"
+checksum = "da758b2f989aafd9eeb39489fe43d7be5a3a0d2ad61cf1bad705eb6990a6053c"
dependencies = [
"jxl-bitstream",
"jxl-coding",
"jxl-grid",
+ "jxl-oxide-common",
"jxl-threadpool",
"tracing",
]
[[package]]
name = "jxl-oxide"
-version = "0.8.1"
+version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ba1ee3895e6c62b131994807b1ee6d179013613a86c01c203369af8d1e8d2f0"
+checksum = "ee8ecd2678ed70c1eda42b811ccb2e25ab836edeb18e7f1178c1f917ed36b772"
dependencies = [
+ "brotli-decompressor",
+ "bytemuck",
+ "image",
"jxl-bitstream",
"jxl-color",
"jxl-frame",
"jxl-grid",
"jxl-image",
+ "jxl-jbr",
+ "jxl-oxide-common",
"jxl-render",
"jxl-threadpool",
"tracing",
]
+[[package]]
+name = "jxl-oxide-common"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b62394c5021b3a9e7e0dbb2d639d555d019090c9946c39f6d3b09d390db4157b"
+dependencies = [
+ "jxl-bitstream",
+]
+
[[package]]
name = "jxl-render"
-version = "0.8.2"
+version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "203a79b3025b86f875cc97a6f39e1fcc6314f915b2f6b69f767fca45fd482a11"
+checksum = "aa0c3100918bd3c41bb0f8ce1f4f1664e48f3032ff8eeab0d6a2cfc3276f462d"
dependencies = [
+ "bytemuck",
"jxl-bitstream",
"jxl-coding",
"jxl-color",
@@ -4079,6 +4747,7 @@ dependencies = [
"jxl-grid",
"jxl-image",
"jxl-modular",
+ "jxl-oxide-common",
"jxl-threadpool",
"jxl-vardct",
"tracing",
@@ -4086,9 +4755,9 @@ dependencies = [
[[package]]
name = "jxl-threadpool"
-version = "0.1.2"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad9c78eaf899cce165e266300f9963d8d376d4ed95cf4d12dd7066f05542cd88"
+checksum = "25f15eb830aa77a7f21148d72e153562a26bfe570139bd4922eab1908dd499d3"
dependencies = [
"rayon",
"rayon-core",
@@ -4097,14 +4766,15 @@ dependencies = [
[[package]]
name = "jxl-vardct"
-version = "0.7.0"
+version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16af82a1ad770887cad720bfd3cc6a6d023faf377036989a24cf2c6538b649e0"
+checksum = "ce72a18c6d3a47172ab6c479be2bdb56f22066b5d7092663f03b4490820b4511"
dependencies = [
"jxl-bitstream",
"jxl-coding",
"jxl-grid",
"jxl-modular",
+ "jxl-oxide-common",
"jxl-threadpool",
"tracing",
]
@@ -4152,26 +4822,67 @@ dependencies = [
"log",
]
+[[package]]
+name = "lapin"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "478790661081f7434e111a31953acc1cf23654cf2a2d815d0e12cef9a2aed720"
+dependencies = [
+ "amq-protocol",
+ "async-rs",
+ "async-trait",
+ "atomic-waker",
+ "backon",
+ "cfg-if",
+ "flume",
+ "futures-core",
+ "futures-io",
+ "tracing",
+]
+
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
dependencies = [
- "spin",
+ "spin 0.9.8",
]
[[package]]
-name = "lazycell"
-version = "1.3.0"
+name = "lcms2"
+version = "6.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b75877b724685dd49310bdbadbf973fc69b1d01992a6d4a861b928fc3943f87b"
+dependencies = [
+ "bytemuck",
+ "foreign-types 0.5.0",
+ "lcms2-sys",
+]
+
+[[package]]
+name = "lcms2-sys"
+version = "4.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c2604b23848ca80b2add60f0fb2270fd980e622c25029b6597fa01cfd5f8d5f"
+dependencies = [
+ "cc",
+ "dunce",
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "leb128fmt"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "lebe"
-version = "0.5.2"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
+checksum = "7a79a3332a6609480d7d0c9eab957bca6b455b91bb84e66d19f5ff66294b85b8"
[[package]]
name = "lettre"
@@ -4189,24 +4900,24 @@ dependencies = [
"idna 0.3.0",
"mime",
"native-tls",
- "nom",
+ "nom 7.1.3",
"once_cell",
"quoted_printable",
"socket2 0.4.10",
- "tokio 1.47.1",
+ "tokio 1.51.0",
]
[[package]]
name = "libc"
-version = "0.2.174"
+version = "0.2.184"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
+checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af"
[[package]]
name = "libfuzzer-sys"
-version = "0.4.10"
+version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5037190e1f70cbeef565bd267599242926f724d3b8a9f510fd7e0b540cfa4404"
+checksum = "f12a681b7dd8ce12bff52488013ba614b869148d54dd79836ab85aafdd53f08d"
dependencies = [
"arbitrary",
"cc",
@@ -4224,27 +4935,17 @@ dependencies = [
"pkg-config",
]
-[[package]]
-name = "libloading"
-version = "0.8.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
-dependencies = [
- "cfg-if",
- "windows-targets 0.53.3",
-]
-
[[package]]
name = "libm"
-version = "0.2.15"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
name = "libnghttp2-sys"
-version = "0.1.11+1.64.0"
+version = "0.1.13+1.68.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b6c24e48a7167cffa7119da39d577fa482e66c688a4aac016bee862e1a713c4"
+checksum = "492e00167f1418c15648144f42bbfc63099806ecee9bf8d09a6353d6b4856b3c"
dependencies = [
"cc",
"libc",
@@ -4262,9 +4963,9 @@ dependencies = [
[[package]]
name = "libz-sys"
-version = "1.1.22"
+version = "1.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b70e7a7df205e92a1a4cd9aaae7898dac0aa555503cc0a649494d0d60e7651d"
+checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22"
dependencies = [
"cc",
"libc",
@@ -4278,15 +4979,6 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
-[[package]]
-name = "linkify"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1986921c3c13e81df623c66a298d4b130c061bcb98a01f5b2d3ac402b1649a7f"
-dependencies = [
- "memchr",
-]
-
[[package]]
name = "linkify"
version = "0.8.1"
@@ -4304,65 +4996,119 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
-version = "0.9.4"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "litemap"
-version = "0.8.0"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
+
+[[package]]
+name = "livekit-api"
+version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+checksum = "e2247e3127fc52f9cc30f2763726f0508120d0424bd5159464d731cb58e2bea1"
+dependencies = [
+ "base64 0.21.7",
+ "http 1.4.0",
+ "jsonwebtoken",
+ "livekit-protocol",
+ "log",
+ "os_info",
+ "parking_lot",
+ "pbjson-types",
+ "prost",
+ "rand 0.9.2",
+ "reqwest 0.12.28",
+ "rustls-native-certs 0.6.3",
+ "scopeguard",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror 2.0.18",
+ "tokio-rustls 0.24.1",
+ "tokio-tungstenite",
+ "url",
+]
+
+[[package]]
+name = "livekit-protocol"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d080ff1e4806c4b57427db696dc093e1a6817de9500a262167259cf7bab646e"
+dependencies = [
+ "futures-util",
+ "livekit-runtime",
+ "parking_lot",
+ "pbjson",
+ "pbjson-types",
+ "prost",
+ "serde",
+ "thiserror 2.0.18",
+ "tokio 1.51.0",
+]
+
+[[package]]
+name = "livekit-runtime"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "532e84c6cdc5fe774f2b5d9912597b5f3bea561927a48296d03e24549d21c3f6"
+dependencies = [
+ "tokio 1.51.0",
+ "tokio-stream",
+]
[[package]]
name = "lock_api"
-version = "0.4.13"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
- "autocfg 1.5.0",
"scopeguard",
]
[[package]]
name = "log"
-version = "0.4.27"
+version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
dependencies = [
"value-bag",
]
[[package]]
name = "logos"
-version = "0.15.0"
+version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab6f536c1af4c7cc81edf73da1f8029896e7e1e16a219ef09b184e76a296f3db"
+checksum = "ff472f899b4ec2d99161c51f60ff7075eeb3097069a36050d8037a6325eb8154"
dependencies = [
"logos-derive",
]
[[package]]
name = "logos-codegen"
-version = "0.15.0"
+version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "189bbfd0b61330abea797e5e9276408f2edbe4f822d7ad08685d67419aafb34e"
+checksum = "192a3a2b90b0c05b27a0b2c43eecdb7c415e29243acc3f89cc8247a5b693045c"
dependencies = [
"beef",
"fnv",
"lazy_static",
"proc-macro2",
- "quote 1.0.40",
- "regex-syntax 0.8.5",
+ "quote 1.0.45",
+ "regex-syntax",
"rustc_version",
- "syn 2.0.104",
+ "syn 2.0.117",
]
[[package]]
name = "logos-derive"
-version = "0.15.0"
+version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebfe8e1a19049ddbfccbd14ac834b215e11b85b90bab0c2dba7c7b92fb5d5cba"
+checksum = "605d9697bcd5ef3a42d38efc51541aa3d6a4a25f7ab6d1ed0da5ac632a26b470"
dependencies = [
"logos-codegen",
]
@@ -4374,7 +5120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5"
dependencies = [
"cfg-if",
- "generator 0.7.5",
+ "generator",
"scoped-tls",
"serde",
"serde_json",
@@ -4382,19 +5128,6 @@ dependencies = [
"tracing-subscriber",
]
-[[package]]
-name = "loom"
-version = "0.7.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca"
-dependencies = [
- "cfg-if",
- "generator 0.8.5",
- "scoped-tls",
- "tracing",
- "tracing-subscriber",
-]
-
[[package]]
name = "loop9"
version = "0.1.5"
@@ -4406,39 +5139,18 @@ dependencies = [
[[package]]
name = "lru"
-version = "0.7.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
-dependencies = [
- "hashbrown 0.12.3",
-]
-
-[[package]]
-name = "lru"
-version = "0.11.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4a83fb7698b3643a0e34f9ae6f2e8f0178c0fd42f8b59d493aa271ff3a5bf21"
-dependencies = [
- "hashbrown 0.14.5",
-]
-
-[[package]]
-name = "lru"
-version = "0.12.5"
+version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
+checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
dependencies = [
- "hashbrown 0.15.4",
+ "hashbrown 0.16.1",
]
[[package]]
-name = "lru-cache"
+name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
-dependencies = [
- "linked-hash-map",
-]
+checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]]
name = "lru_time_cache"
@@ -4460,8 +5172,8 @@ checksum = "cc33f9f0351468d26fbc53d9ce00a096c8522ecb42f19b50f34f2c422f76d21d"
dependencies = [
"macro_magic_core",
"macro_magic_macros",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -4474,8 +5186,8 @@ dependencies = [
"derive-syn-parse",
"macro_magic_core_macros",
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -4485,8 +5197,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -4496,8 +5208,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869"
dependencies = [
"macro_magic_core",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -4522,11 +5234,11 @@ checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
name = "matchers"
-version = "0.1.0"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9"
dependencies = [
- "regex-automata 0.1.10",
+ "regex-automata",
]
[[package]]
@@ -4563,15 +5275,15 @@ dependencies = [
[[package]]
name = "memchr"
-version = "2.7.5"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "memmap2"
-version = "0.9.7"
+version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "483758ad303d734cec05e5c12b41d7e93e6a6390c5e9dae6bdeb7c1259012d28"
+checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3"
dependencies = [
"libc",
]
@@ -4610,13 +5322,13 @@ dependencies = [
[[package]]
name = "mio"
-version = "1.0.4"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
+checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [
"libc",
- "wasi 0.11.1+wasi-snapshot-preview1",
- "windows-sys 0.59.0",
+ "wasi",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -4634,7 +5346,7 @@ dependencies = [
"log",
"metrics",
"thiserror 1.0.69",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tracing",
"tracing-subscriber",
]
@@ -4651,85 +5363,108 @@ dependencies = [
[[package]]
name = "moka"
-version = "0.12.10"
+version = "0.12.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a9321642ca94a4282428e6ea4af8cc2ca4eac48ac7a6a4ea8f33f76d0ce70926"
+checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046"
dependencies = [
- "async-lock 3.4.1",
+ "async-lock 3.4.2",
"crossbeam-channel",
"crossbeam-epoch",
"crossbeam-utils",
+ "equivalent",
"event-listener 5.4.1",
"futures-util",
- "loom 0.7.2",
"parking_lot",
"portable-atomic",
- "rustc_version",
"smallvec",
"tagptr",
- "thiserror 1.0.69",
"uuid",
]
+[[package]]
+name = "mongocrypt"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8da0cd419a51a5fb44819e290fbdb0665a54f21dead8923446a799c7f4d26ad9"
+dependencies = [
+ "bson",
+ "mongocrypt-sys",
+ "once_cell",
+ "serde",
+]
+
+[[package]]
+name = "mongocrypt-sys"
+version = "0.1.5+1.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224484c5d09285a7b8cb0a0c117e847ebd14cb6e4470ecf68cdb89c503b0edb9"
+
[[package]]
name = "mongodb"
-version = "3.2.4"
+version = "3.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0f8c69f13acf07eae386a2974f48ffd9187ea2aba8defbea9aa34e7e272c5f3"
+checksum = "2c5941683db2ab2697f71e58dc0319024e808d3b28e7cf20f4bfb445fe54a30b"
dependencies = [
- "async-trait",
- "base64 0.13.1",
- "bitflags 1.3.2",
+ "base64 0.22.1",
+ "bitflags 2.11.0",
"bson",
- "chrono",
"derive-where",
- "derive_more",
+ "derive_more 2.1.1",
"futures-core",
- "futures-executor",
"futures-io",
"futures-util",
"hex",
- "hickory-proto",
- "hickory-resolver",
+ "hickory-proto 0.25.2",
+ "hickory-resolver 0.25.2",
"hmac",
"macro_magic",
"md-5",
+ "mongocrypt",
"mongodb-internal-macros",
- "once_cell",
"pbkdf2",
"percent-encoding",
- "rand 0.8.5",
+ "rand 0.9.2",
"rustc_version_runtime",
- "rustls 0.21.12",
- "rustls-pemfile 1.0.4",
+ "rustls 0.23.37",
+ "rustversion",
"serde",
"serde_bytes",
"serde_with",
"sha1",
"sha2",
- "socket2 0.5.10",
+ "socket2 0.6.3",
"stringprep",
"strsim 0.11.1",
"take_mut",
- "thiserror 1.0.69",
- "tokio 1.47.1",
- "tokio-rustls 0.24.1",
+ "thiserror 2.0.18",
+ "tokio 1.51.0",
+ "tokio-rustls 0.26.4",
"tokio-util",
"typed-builder",
"uuid",
- "webpki-roots 0.25.4",
+ "webpki-roots 1.0.6",
]
[[package]]
name = "mongodb-internal-macros"
-version = "3.2.4"
+version = "3.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9202de265a3a8bbb43f9fe56db27c93137d4f9fb04c093f47e9c7de0c61ac7d"
+checksum = "47021a12bbf0dffde9c890fa2d36ff6ae342c532016226b04a42301b2b912660"
dependencies = [
"macro_magic",
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "moxcms"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
+dependencies = [
+ "num-traits",
+ "pxfm",
]
[[package]]
@@ -4738,24 +5473,30 @@ version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b"
dependencies = [
- "bytes 1.10.1",
+ "bytes 1.11.1",
"encoding_rs",
"futures-util",
- "http 1.3.1",
+ "http 1.4.0",
"httparse",
"memchr",
"mime",
- "spin",
- "tokio 1.47.1",
+ "spin 0.9.8",
+ "tokio 1.51.0",
"tokio-util",
"version_check",
]
+[[package]]
+name = "multimap"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084"
+
[[package]]
name = "mutate_once"
-version = "0.1.1"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "16cf681a23b4d0a43fc35024c176437f9dcd818db34e0f42ab456a0ee5ad497b"
+checksum = "13d2233c9842d08cfe13f9eac96e207ca6a2ea10b80259ebe8ad0268be27d2af"
[[package]]
name = "nanoid"
@@ -4768,27 +5509,45 @@ dependencies = [
[[package]]
name = "native-tls"
-version = "0.2.14"
+version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
+checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
dependencies = [
"libc",
"log",
"openssl",
- "openssl-probe",
+ "openssl-probe 0.2.1",
"openssl-sys",
"schannel",
- "security-framework 2.11.1",
+ "security-framework 3.7.0",
"security-framework-sys",
"tempfile",
]
+[[package]]
+name = "ndk-context"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
+
[[package]]
name = "new_debug_unreachable"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
+[[package]]
+name = "nix"
+version = "0.30.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
+dependencies = [
+ "bitflags 2.11.0",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
[[package]]
name = "nom"
version = "7.1.3"
@@ -4799,6 +5558,15 @@ dependencies = [
"minimal-lexical",
]
+[[package]]
+name = "nom"
+version = "8.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "noop_proc_macro"
version = "0.3.0"
@@ -4807,21 +5575,20 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]]
name = "ntapi"
-version = "0.4.1"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
+checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae"
dependencies = [
"winapi",
]
[[package]]
name = "nu-ansi-term"
-version = "0.46.0"
+version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
- "overload",
- "winapi",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -4836,11 +5603,10 @@ dependencies = [
[[package]]
name = "num-bigint-dig"
-version = "0.8.4"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151"
+checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7"
dependencies = [
- "byteorder",
"lazy_static",
"libm",
"num-integer",
@@ -4853,9 +5619,9 @@ dependencies = [
[[package]]
name = "num-conv"
-version = "0.1.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
+checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967"
[[package]]
name = "num-derive"
@@ -4864,8 +5630,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -4883,7 +5649,7 @@ version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
dependencies = [
- "autocfg 1.5.0",
+ "autocfg",
"num-integer",
"num-traits",
]
@@ -4905,7 +5671,7 @@ version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
- "autocfg 1.5.0",
+ "autocfg",
"libm",
]
@@ -4921,69 +5687,226 @@ dependencies = [
[[package]]
name = "num_enum"
-version = "0.5.11"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
+checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1"
dependencies = [
- "num_enum_derive 0.5.11",
+ "num_enum_derive",
]
[[package]]
-name = "num_enum"
+name = "num_enum_derive"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a015b430d3c108a207fd776d2e2196aaf8b1cf8cf93253e3a097ff3085076a1"
+checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote 1.0.45",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "num_threads"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
dependencies = [
- "num_enum_derive 0.6.1",
+ "libc",
]
[[package]]
-name = "num_enum_derive"
-version = "0.5.11"
+name = "objc2"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
+checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
dependencies = [
- "proc-macro-crate",
- "proc-macro2",
- "quote 1.0.40",
- "syn 1.0.109",
+ "objc2-encode",
]
[[package]]
-name = "num_enum_derive"
-version = "0.6.1"
+name = "objc2-cloud-kit"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6"
+checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c"
dependencies = [
- "proc-macro-crate",
- "proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "bitflags 2.11.0",
+ "objc2",
+ "objc2-foundation",
]
[[package]]
-name = "num_threads"
-version = "0.1.7"
+name = "objc2-core-data"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
+checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
+dependencies = [
+ "bitflags 2.11.0",
+ "dispatch2",
+ "objc2",
+]
+
+[[package]]
+name = "objc2-core-graphics"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807"
+dependencies = [
+ "bitflags 2.11.0",
+ "dispatch2",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-io-surface",
+]
+
+[[package]]
+name = "objc2-core-image"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-location"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-core-text"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d"
+dependencies = [
+ "bitflags 2.11.0",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+]
+
+[[package]]
+name = "objc2-encode"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33"
+
+[[package]]
+name = "objc2-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
dependencies = [
+ "bitflags 2.11.0",
+ "block2",
"libc",
+ "objc2",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-io-surface"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d"
+dependencies = [
+ "bitflags 2.11.0",
+ "objc2",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "objc2-quartz-core"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f"
+dependencies = [
+ "bitflags 2.11.0",
+ "objc2",
+ "objc2-core-foundation",
+ "objc2-foundation",
+]
+
+[[package]]
+name = "objc2-ui-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22"
+dependencies = [
+ "bitflags 2.11.0",
+ "block2",
+ "objc2",
+ "objc2-cloud-kit",
+ "objc2-core-data",
+ "objc2-core-foundation",
+ "objc2-core-graphics",
+ "objc2-core-image",
+ "objc2-core-location",
+ "objc2-core-text",
+ "objc2-foundation",
+ "objc2-quartz-core",
+ "objc2-user-notifications",
+]
+
+[[package]]
+name = "objc2-user-notifications"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e"
+dependencies = [
+ "objc2",
+ "objc2-foundation",
]
[[package]]
name = "object"
-version = "0.36.7"
+version = "0.37.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
+checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe"
dependencies = [
"memchr",
]
+[[package]]
+name = "oid-registry"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7"
+dependencies = [
+ "asn1-rs",
+]
+
[[package]]
name = "once_cell"
-version = "1.21.3"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+dependencies = [
+ "critical-section",
+ "portable-atomic",
+]
+
+[[package]]
+name = "oorandom"
+version = "11.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
+checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
[[package]]
name = "opaque-debug"
@@ -4993,13 +5916,13 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381"
[[package]]
name = "openssl"
-version = "0.10.73"
+version = "0.10.76"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
+checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf"
dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.11.0",
"cfg-if",
- "foreign-types",
+ "foreign-types 0.3.2",
"libc",
"once_cell",
"openssl-macros",
@@ -5013,8 +5936,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -5023,11 +5946,17 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+[[package]]
+name = "openssl-probe"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
+
[[package]]
name = "openssl-sys"
-version = "0.9.109"
+version = "0.9.112"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
+checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb"
dependencies = [
"cc",
"libc",
@@ -5047,14 +5976,18 @@ dependencies = [
[[package]]
name = "os_info"
-version = "3.12.0"
+version = "3.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3"
+checksum = "e4022a17595a00d6a369236fdae483f0de7f0a339960a53118b818238e132224"
dependencies = [
+ "android_system_properties",
"log",
- "plist",
+ "nix",
+ "objc2",
+ "objc2-foundation",
+ "objc2-ui-kit",
"serde",
- "windows-sys 0.52.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -5064,10 +5997,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e"
[[package]]
-name = "overload"
-version = "0.1.1"
+name = "p12-keystore"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+checksum = "ffb9bf5222606eb712d3bb30e01bc9420545b00859970897e70c682353a034f2"
+dependencies = [
+ "base64 0.22.1",
+ "cbc",
+ "cms",
+ "der 0.7.10",
+ "des",
+ "hex",
+ "hmac",
+ "pkcs12",
+ "pkcs5",
+ "rand 0.10.1",
+ "rc2",
+ "sha1",
+ "sha2",
+ "thiserror 2.0.18",
+ "x509-parser",
+]
[[package]]
name = "p256"
@@ -5112,9 +6062,9 @@ checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot"
-version = "0.12.4"
+version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
@@ -5122,15 +6072,15 @@ dependencies = [
[[package]]
name = "parking_lot_core"
-version = "0.9.11"
+version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
- "windows-targets 0.52.6",
+ "windows-link",
]
[[package]]
@@ -5139,19 +6089,163 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+[[package]]
+name = "pastey"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec"
+
[[package]]
name = "pathdiff"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3"
+[[package]]
+name = "pbjson"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90"
+dependencies = [
+ "base64 0.21.7",
+ "serde",
+]
+
+[[package]]
+name = "pbjson-build"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735"
+dependencies = [
+ "heck 0.4.1",
+ "itertools 0.11.0",
+ "prost",
+ "prost-types",
+]
+
+[[package]]
+name = "pbjson-types"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12"
+dependencies = [
+ "bytes 1.11.1",
+ "chrono",
+ "pbjson",
+ "pbjson-build",
+ "prost",
+ "prost-build",
+ "serde",
+]
+
[[package]]
name = "pbkdf2"
-version = "0.11.0"
+version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
+checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
dependencies = [
"digest",
+ "hmac",
+]
+
+[[package]]
+name = "pdk-classy"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fa3e632c61a7f8ad1a77c4f52d9a85a89c577881f44e89b33e28163af38e515"
+dependencies = [
+ "bincode",
+ "futures",
+ "getrandom 0.2.17",
+ "http 0.2.12",
+ "log",
+ "pdk-proxy-wasm-stub",
+ "protobuf",
+ "proxy-wasm",
+ "serde",
+ "serde_derive",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "pdk-core"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60f2b1d1c8876b54d03d35a18fba1e5172ed8d7f1c379001c11b62c909fdf654"
+dependencies = [
+ "anyhow",
+ "log",
+ "pdk-classy",
+ "pdk-macros",
+ "pdk-script",
+ "protobuf",
+ "protobuf-codegen",
+ "rmp-serde",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "sha2",
+ "url",
+]
+
+[[package]]
+name = "pdk-ip-filter-lib"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dab00d1dfe7b232fcb5424c3af23721993b439a51960f1f3152760228f492ec"
+dependencies = [
+ "anyhow",
+ "ipnet",
+ "pdk-core",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "pdk-macros"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da346bb3e02aad6b6bf31e6e38798c0f3cdf8bd0319fe97d44addc5a0ea5de92"
+dependencies = [
+ "proc-macro2",
+ "quote 1.0.45",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "pdk-pel"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c60996708c43b91581ea9e6fa324af7eae5a9b8146dd96cacf2868e9260b654"
+dependencies = [
+ "base64 0.22.1",
+ "getrandom 0.2.17",
+ "serde_json",
+ "thiserror 1.0.69",
+ "uuid",
+]
+
+[[package]]
+name = "pdk-proxy-wasm-stub"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "469571b12631b71dce33890917bec59f58c53f9d3b05b748d5aa873c950e1702"
+
+[[package]]
+name = "pdk-script"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8842ea2908eb9b94ed7daa522cc75fcd5fe3738a20d7ecde67cb7829dbf129e"
+dependencies = [
+ "log",
+ "num-traits",
+ "oorandom",
+ "pdk-classy",
+ "pdk-pel",
+ "roxmltree",
+ "serde",
+ "serde_json",
+ "thiserror 1.0.69",
+ "url",
]
[[package]]
@@ -5173,8 +6267,8 @@ checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147"
dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -5190,12 +6284,12 @@ dependencies = [
[[package]]
name = "pem"
-version = "3.0.5"
+version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3"
+checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
dependencies = [
"base64 0.22.1",
- "serde",
+ "serde_core",
]
[[package]]
@@ -5218,26 +6312,25 @@ dependencies = [
[[package]]
name = "percent-encoding"
-version = "2.3.1"
+version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pest"
-version = "2.8.1"
+version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1db05f56d34358a8b1066f67cbb203ee3e7ed2ba674a6263a1d5ec6db2204323"
+checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662"
dependencies = [
"memchr",
- "thiserror 2.0.12",
"ucd-trie",
]
[[package]]
name = "pest_derive"
-version = "2.8.1"
+version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb056d9e8ea77922845ec74a1c4e8fb17e7c218cc4fc11a15c5d25e189aa40bc"
+checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77"
dependencies = [
"pest",
"pest_generator",
@@ -5245,27 +6338,37 @@ dependencies = [
[[package]]
name = "pest_generator"
-version = "2.8.1"
+version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "87e404e638f781eb3202dc82db6760c8ae8a1eeef7fb3fa8264b2ef280504966"
+checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
name = "pest_meta"
-version = "2.8.1"
+version = "2.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edd1101f170f5903fde0914f899bb503d9ff5271d7ba76bbb70bea63690cc0d5"
+checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220"
dependencies = [
"pest",
"sha2",
]
+[[package]]
+name = "petgraph"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
+dependencies = [
+ "fixedbitset",
+ "indexmap 2.13.1",
+]
+
[[package]]
name = "phf"
version = "0.10.1"
@@ -5334,8 +6437,8 @@ dependencies = [
"phf_generator 0.11.3",
"phf_shared 0.11.3",
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -5353,7 +6456,7 @@ version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
dependencies = [
- "siphasher 1.0.1",
+ "siphasher 1.0.2",
]
[[package]]
@@ -5364,22 +6467,22 @@ checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315"
[[package]]
name = "pin-project"
-version = "1.1.10"
+version = "1.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a"
+checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
-version = "1.1.10"
+version = "1.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861"
+checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -5390,9 +6493,9 @@ checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777"
[[package]]
name = "pin-project-lite"
-version = "0.2.16"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pin-utils"
@@ -5402,12 +6505,12 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "piper"
-version = "0.2.4"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96c8c490f422ef9a4efd2cb5b42b76c8613d7e7dfc1caf667b8a3350a5acc066"
+checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
dependencies = [
"atomic-waker",
- "fastrand 2.3.0",
+ "fastrand 2.4.0",
"futures-io",
]
@@ -5423,6 +6526,47 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "pkcs1"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f"
+dependencies = [
+ "der 0.7.10",
+ "pkcs8 0.10.2",
+ "spki 0.7.3",
+]
+
+[[package]]
+name = "pkcs12"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "695b3df3d3cc1015f12d70235e35b6b79befc5fa7a9b95b951eab1dd07c9efc2"
+dependencies = [
+ "cms",
+ "const-oid 0.9.6",
+ "der 0.7.10",
+ "digest",
+ "spki 0.7.3",
+ "x509-cert",
+ "zeroize",
+]
+
+[[package]]
+name = "pkcs5"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e847e2c91a18bfa887dd028ec33f2fe6f25db77db3619024764914affe8b69a6"
+dependencies = [
+ "aes",
+ "cbc",
+ "der 0.7.10",
+ "pbkdf2",
+ "scrypt",
+ "sha2",
+ "spki 0.7.3",
+]
+
[[package]]
name = "pkcs8"
version = "0.9.0"
@@ -5449,19 +6593,6 @@ version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
-[[package]]
-name = "plist"
-version = "1.7.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1"
-dependencies = [
- "base64 0.22.1",
- "indexmap 2.10.0",
- "quick-xml",
- "serde",
- "time",
-]
-
[[package]]
name = "png"
version = "0.17.16"
@@ -5476,33 +6607,30 @@ dependencies = [
]
[[package]]
-name = "polling"
-version = "2.8.0"
+name = "png"
+version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
+checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
dependencies = [
- "autocfg 1.5.0",
- "bitflags 1.3.2",
- "cfg-if",
- "concurrent-queue",
- "libc",
- "log",
- "pin-project-lite 0.2.16",
- "windows-sys 0.48.0",
+ "bitflags 2.11.0",
+ "crc32fast",
+ "fdeflate",
+ "flate2",
+ "miniz_oxide",
]
[[package]]
name = "polling"
-version = "3.10.0"
+version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5bd19146350fe804f7cb2669c851c03d69da628803dab0d98018142aaa5d829"
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi 0.5.2",
- "pin-project-lite 0.2.16",
- "rustix 1.0.8",
- "windows-sys 0.60.2",
+ "pin-project-lite 0.2.17",
+ "rustix 1.1.4",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -5512,22 +6640,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "portable-atomic"
-version = "1.11.1"
+version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
+checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
[[package]]
name = "potential_utf"
-version = "0.1.2"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585"
+checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
dependencies = [
"zerovec",
]
@@ -5553,6 +6681,17 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
+[[package]]
+name = "prefix-trie"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90f561214012d3fc240a1f9c817cc4d57f5310910d066069c1b093f766bb5966"
+dependencies = [
+ "either",
+ "ipnet",
+ "num-traits",
+]
+
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
@@ -5565,12 +6704,12 @@ dependencies = [
[[package]]
name = "prettyplease"
-version = "0.2.36"
+version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff24dfcda44452b9816fff4cd4227e1bb73ff5a2f1bc1105aa92fb8565ce44d2"
+checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
- "syn 2.0.104",
+ "syn 2.0.117",
]
[[package]]
@@ -5600,7 +6739,7 @@ checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"syn 1.0.109",
"version_check",
]
@@ -5612,7 +6751,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"version_check",
]
@@ -5623,7 +6762,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
]
[[package]]
@@ -5634,15 +6773,15 @@ checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
dependencies = [
"proc-macro-error-attr2",
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
name = "proc-macro2"
-version = "1.0.95"
+version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
@@ -5654,8 +6793,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
"version_check",
"yansi",
]
@@ -5675,8 +6814,8 @@ version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52717f9a02b6965224f95ca2a81e2e0c5c43baacd28ca057577988930b6c3d5b"
dependencies = [
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -5693,6 +6832,126 @@ dependencies = [
"thiserror 1.0.69",
]
+[[package]]
+name = "prost"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29"
+dependencies = [
+ "bytes 1.11.1",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
+dependencies = [
+ "bytes 1.11.1",
+ "heck 0.5.0",
+ "itertools 0.12.1",
+ "log",
+ "multimap",
+ "once_cell",
+ "petgraph",
+ "prettyplease",
+ "prost",
+ "prost-types",
+ "regex",
+ "syn 2.0.117",
+ "tempfile",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
+dependencies = [
+ "anyhow",
+ "itertools 0.12.1",
+ "proc-macro2",
+ "quote 1.0.45",
+ "syn 2.0.117",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0"
+dependencies = [
+ "prost",
+]
+
+[[package]]
+name = "protobuf"
+version = "3.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4"
+dependencies = [
+ "once_cell",
+ "protobuf-support",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "protobuf-codegen"
+version = "3.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d3976825c0014bbd2f3b34f0001876604fe87e0c86cd8fa54251530f1544ace"
+dependencies = [
+ "anyhow",
+ "once_cell",
+ "protobuf",
+ "protobuf-parse",
+ "regex",
+ "tempfile",
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "protobuf-parse"
+version = "3.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4aeaa1f2460f1d348eeaeed86aea999ce98c1bded6f089ff8514c9d9dbdc973"
+dependencies = [
+ "anyhow",
+ "indexmap 2.13.1",
+ "log",
+ "protobuf",
+ "protobuf-support",
+ "tempfile",
+ "thiserror 1.0.69",
+ "which",
+]
+
+[[package]]
+name = "protobuf-support"
+version = "3.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6"
+dependencies = [
+ "thiserror 1.0.69",
+]
+
+[[package]]
+name = "proxy-wasm"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8d35d9e2bc5104e2e954b149aa1d5f9fa3bb27f73b45b2706020fed101db685"
+dependencies = [
+ "hashbrown 0.16.1",
+ "log",
+]
+
+[[package]]
+name = "pxfm"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a041e753da8b807c9255f28de81879c78c876392ff2469cde94799b2896b9d"
+
[[package]]
name = "qoi"
version = "0.4.1"
@@ -5715,18 +6974,65 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
-name = "quick-error"
-version = "2.0.1"
+name = "quick-error"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+
+[[package]]
+name = "quinn"
+version = "0.11.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
+dependencies = [
+ "bytes 1.11.1",
+ "cfg_aliases",
+ "pin-project-lite 0.2.17",
+ "quinn-proto",
+ "quinn-udp",
+ "rustc-hash",
+ "rustls 0.23.37",
+ "socket2 0.6.3",
+ "thiserror 2.0.18",
+ "tokio 1.51.0",
+ "tracing",
+ "web-time",
+]
+
+[[package]]
+name = "quinn-proto"
+version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
+checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
+dependencies = [
+ "aws-lc-rs",
+ "bytes 1.11.1",
+ "getrandom 0.3.4",
+ "lru-slab",
+ "rand 0.9.2",
+ "ring",
+ "rustc-hash",
+ "rustls 0.23.37",
+ "rustls-pki-types",
+ "slab",
+ "thiserror 2.0.18",
+ "tinyvec",
+ "tracing",
+ "web-time",
+]
[[package]]
-name = "quick-xml"
-version = "0.38.1"
+name = "quinn-udp"
+version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9845d9dccf565065824e69f9f235fafba1587031eda353c1f1561cd6a6be78f4"
+checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [
- "memchr",
+ "cfg_aliases",
+ "libc",
+ "once_cell",
+ "socket2 0.6.3",
+ "tracing",
+ "windows-sys 0.60.2",
]
[[package]]
@@ -5737,9 +7043,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
[[package]]
name = "quote"
-version = "1.0.40"
+version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
@@ -5757,29 +7063,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]]
-name = "radium"
-version = "0.7.0"
+name = "r-efi"
+version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
-name = "rand"
-version = "0.6.5"
+name = "radium"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
-dependencies = [
- "autocfg 0.1.8",
- "libc",
- "rand_chacha 0.1.1",
- "rand_core 0.4.2",
- "rand_hc",
- "rand_isaac",
- "rand_jitter",
- "rand_os",
- "rand_pcg",
- "rand_xorshift",
- "winapi",
-]
+checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
@@ -5799,17 +7092,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
dependencies = [
"rand_chacha 0.9.0",
- "rand_core 0.9.3",
+ "rand_core 0.9.5",
]
[[package]]
-name = "rand_chacha"
-version = "0.1.1"
+name = "rand"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
+checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
dependencies = [
- "autocfg 0.1.8",
- "rand_core 0.3.1",
+ "chacha20",
+ "getrandom 0.4.2",
+ "rand_core 0.10.1",
]
[[package]]
@@ -5829,120 +7123,50 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
dependencies = [
"ppv-lite86",
- "rand_core 0.9.3",
-]
-
-[[package]]
-name = "rand_core"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
-dependencies = [
- "rand_core 0.4.2",
+ "rand_core 0.9.5",
]
-[[package]]
-name = "rand_core"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
-
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
- "getrandom 0.2.16",
+ "getrandom 0.2.17",
]
[[package]]
name = "rand_core"
-version = "0.9.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
-dependencies = [
- "getrandom 0.3.3",
-]
-
-[[package]]
-name = "rand_hc"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rand_isaac"
-version = "0.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
-dependencies = [
- "rand_core 0.3.1",
-]
-
-[[package]]
-name = "rand_jitter"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
-dependencies = [
- "libc",
- "rand_core 0.4.2",
- "winapi",
-]
-
-[[package]]
-name = "rand_os"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
-dependencies = [
- "cloudabi",
- "fuchsia-cprng",
- "libc",
- "rand_core 0.4.2",
- "rdrand",
- "wasm-bindgen",
- "winapi",
-]
-
-[[package]]
-name = "rand_pcg"
-version = "0.1.2"
+version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
+checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
dependencies = [
- "autocfg 0.1.8",
- "rand_core 0.4.2",
+ "getrandom 0.3.4",
]
[[package]]
-name = "rand_xorshift"
-version = "0.1.1"
+name = "rand_core"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
-dependencies = [
- "rand_core 0.3.1",
-]
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
name = "rav1e"
-version = "0.7.1"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cd87ce80a7665b1cce111f8a16c1f3929f6547ce91ade6addf4ec86a8dda5ce9"
+checksum = "43b6dd56e85d9483277cde964fd1bdb0428de4fec5ebba7540995639a21cb32b"
dependencies = [
+ "aligned-vec",
"arbitrary",
"arg_enum_proc_macro",
"arrayvec",
+ "av-scenechange",
"av1-grain",
"bitstream-io",
"built",
"cfg-if",
"interpolate_name",
- "itertools",
+ "itertools 0.14.0",
"libc",
"libfuzzer-sys",
"log",
@@ -5951,23 +7175,21 @@ dependencies = [
"noop_proc_macro",
"num-derive",
"num-traits",
- "once_cell",
"paste",
"profiling",
- "rand 0.8.5",
- "rand_chacha 0.3.1",
+ "rand 0.9.2",
+ "rand_chacha 0.9.0",
"simd_helpers",
- "system-deps",
- "thiserror 1.0.69",
+ "thiserror 2.0.18",
"v_frame",
"wasm-bindgen",
]
[[package]]
name = "ravif"
-version = "0.11.20"
+version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b"
+checksum = "e52310197d971b0f5be7fe6b57530dcd27beb35c1b013f29d66c1ad73fbbcc45"
dependencies = [
"avif-serialize",
"imgref",
@@ -5980,9 +7202,9 @@ dependencies = [
[[package]]
name = "rayon"
-version = "1.10.0"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
+checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f"
dependencies = [
"either",
"rayon-core",
@@ -5990,21 +7212,21 @@ dependencies = [
[[package]]
name = "rayon-core"
-version = "1.12.1"
+version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
-name = "rdrand"
-version = "0.4.0"
+name = "rc2"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
+checksum = "62c64daa8e9438b84aaae55010a93f396f8e60e3911590fcba770d04643fc1dd"
dependencies = [
- "rand_core 0.3.1",
+ "cipher",
]
[[package]]
@@ -6014,16 +7236,16 @@ source = "git+https://github.com/revoltchat/redis-rs?rev=523b2937367e17bd0073722
dependencies = [
"async-std",
"async-trait",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"combine",
"futures-util",
"itoa",
"percent-encoding",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"ryu",
"sha1_smol",
"socket2 0.4.10",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tokio-util",
"url",
]
@@ -6050,92 +7272,77 @@ version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c31deddf734dc0a39d3112e73490e88b61a05e83e074d211f348404cee4d2c6"
dependencies = [
- "bytes 1.10.1",
+ "bytes 1.11.1",
"bytes-utils",
"cookie-factory",
"crc16",
"log",
- "nom",
+ "nom 7.1.3",
]
[[package]]
name = "redox_syscall"
-version = "0.5.17"
+version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.11.0",
]
[[package]]
name = "ref-cast"
-version = "1.0.24"
+version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf"
+checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
-version = "1.0.24"
+version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
+checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
name = "regex"
-version = "1.11.1"
+version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
- "regex-automata 0.4.9",
- "regex-syntax 0.8.5",
-]
-
-[[package]]
-name = "regex-automata"
-version = "0.1.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
-dependencies = [
- "regex-syntax 0.6.29",
+ "regex-automata",
+ "regex-syntax",
]
[[package]]
name = "regex-automata"
-version = "0.4.9"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
- "regex-syntax 0.8.5",
+ "regex-syntax",
]
[[package]]
name = "regex-lite"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53a49587ad06b26609c52e423de037e7f57f20d53535d66e08c695f347df952a"
-
-[[package]]
-name = "regex-syntax"
-version = "0.6.29"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973"
[[package]]
name = "regex-syntax"
-version = "0.8.5"
+version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
[[package]]
name = "reqwest"
@@ -6144,7 +7351,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
"base64 0.21.7",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"encoding_rs",
"futures-core",
"futures-util",
@@ -6152,7 +7359,7 @@ dependencies = [
"http 0.2.12",
"http-body 0.4.6",
"hyper 0.14.32",
- "hyper-tls 0.5.0",
+ "hyper-tls",
"ipnet",
"js-sys",
"log",
@@ -6160,14 +7367,14 @@ dependencies = [
"native-tls",
"once_cell",
"percent-encoding",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"rustls-pemfile 1.0.4",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper 0.1.2",
"system-configuration 0.5.1",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tokio-native-tls",
"tower-service",
"url",
@@ -6179,37 +7386,76 @@ dependencies = [
[[package]]
name = "reqwest"
-version = "0.12.22"
+version = "0.12.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
+dependencies = [
+ "base64 0.22.1",
+ "bytes 1.11.1",
+ "futures-core",
+ "http 1.4.0",
+ "http-body 1.0.1",
+ "http-body-util",
+ "hyper 1.9.0",
+ "hyper-rustls 0.27.7",
+ "hyper-util",
+ "js-sys",
+ "log",
+ "percent-encoding",
+ "pin-project-lite 0.2.17",
+ "quinn",
+ "rustls 0.23.37",
+ "rustls-native-certs 0.8.3",
+ "rustls-pki-types",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper 1.0.2",
+ "tokio 1.51.0",
+ "tokio-rustls 0.26.4",
+ "tower",
+ "tower-http 0.6.8",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "reqwest"
+version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
+checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801"
dependencies = [
"base64 0.22.1",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"encoding_rs",
"futures-core",
- "h2 0.4.11",
- "http 1.3.1",
+ "h2 0.4.13",
+ "http 1.4.0",
"http-body 1.0.1",
"http-body-util",
- "hyper 1.6.0",
+ "hyper 1.9.0",
"hyper-rustls 0.27.7",
- "hyper-tls 0.6.0",
"hyper-util",
"js-sys",
"log",
"mime",
- "native-tls",
"percent-encoding",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
+ "quinn",
+ "rustls 0.23.37",
"rustls-pki-types",
+ "rustls-platform-verifier 0.6.2",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper 1.0.2",
- "tokio 1.47.1",
- "tokio-native-tls",
+ "tokio 1.51.0",
+ "tokio-rustls 0.26.4",
"tower",
- "tower-http 0.6.6",
+ "tower-http 0.6.8",
"tower-service",
"url",
"wasm-bindgen",
@@ -6219,9 +7465,9 @@ dependencies = [
[[package]]
name = "resolv-conf"
-version = "0.7.4"
+version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95325155c684b1c89f7765e30bc1c42e4a6da51ca513615660cb8a62ef9a88e3"
+checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7"
[[package]]
name = "resvg"
@@ -6229,7 +7475,7 @@ version = "0.44.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a325d5e8d1cebddd070b13f44cec8071594ab67d1012797c121f27a669b7958"
dependencies = [
- "gif",
+ "gif 0.13.3",
"image-webp 0.1.3",
"log",
"pico-args",
@@ -6237,12 +7483,12 @@ dependencies = [
"svgtypes",
"tiny-skia",
"usvg",
- "zune-jpeg",
+ "zune-jpeg 0.4.21",
]
[[package]]
name = "revolt-autumn"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
"axum",
"axum-macros",
@@ -6254,11 +7500,13 @@ dependencies = [
"jxl-oxide",
"kamadak-exif",
"lazy_static",
+ "lcms2",
"moka",
"nanoid",
"revolt-config",
"revolt-database",
"revolt-files",
+ "revolt-ratelimits",
"revolt-result",
"revolt_clamav-client",
"serde",
@@ -6267,11 +7515,13 @@ dependencies = [
"simdutf8",
"strum_macros",
"tempfile",
- "tokio 1.47.1",
+ "thumbhash",
+ "tokio 1.51.0",
"tower-http 0.5.2",
"tracing",
"tracing-subscriber",
"ulid 1.2.1",
+ "url-escape",
"utoipa",
"utoipa-scalar",
"webp",
@@ -6279,7 +7529,7 @@ dependencies = [
[[package]]
name = "revolt-bonfire"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
"async-channel 2.5.0",
"async-std",
@@ -6289,11 +7539,12 @@ dependencies = [
"fred",
"futures",
"log",
- "lru 0.7.8",
+ "lru",
"lru_time_cache",
"once_cell",
"querystring",
"redis-kiss",
+ "regex",
"revolt-config",
"revolt-database",
"revolt-models",
@@ -6304,12 +7555,21 @@ dependencies = [
"sentry",
"serde",
"serde_json",
- "ulid 0.5.0",
+ "ulid 1.2.1",
+]
+
+[[package]]
+name = "revolt-coalesced"
+version = "0.13.7"
+dependencies = [
+ "indexmap 2.13.1",
+ "lru",
+ "tokio 1.51.0",
]
[[package]]
name = "revolt-config"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
"async-std",
"cached",
@@ -6326,21 +7586,28 @@ dependencies = [
[[package]]
name = "revolt-crond"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
+ "futures-lite",
+ "iso8601-timestamp",
+ "lapin",
"log",
+ "redis-kiss",
"revolt-config",
"revolt-database",
"revolt-files",
+ "revolt-permissions",
"revolt-result",
- "tokio 1.47.1",
+ "revolt_optional_struct",
+ "serde",
+ "serde_json",
+ "tokio 1.51.0",
]
[[package]]
name = "revolt-database"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
- "amqprs",
"async-lock 2.8.0",
"async-recursion",
"async-std",
@@ -6352,18 +7619,23 @@ dependencies = [
"deadqueue",
"decancer",
"futures",
- "indexmap 1.9.3",
+ "indexmap 2.13.1",
"isahc",
"iso8601-timestamp",
- "linkify 0.8.1",
+ "lapin",
+ "linkify",
+ "livekit-api",
+ "livekit-protocol",
+ "livekit-runtime",
"log",
- "lru 0.11.1",
+ "lru",
"mongodb",
"nanoid",
"once_cell",
"rand 0.8.5",
"redis-kiss",
"regex",
+ "revolt-coalesced",
"revolt-config",
"revolt-models",
"revolt-parser",
@@ -6374,7 +7646,7 @@ dependencies = [
"revolt_optional_struct",
"revolt_rocket_okapi",
"rocket",
- "schemars 0.8.22",
+ "schemars",
"serde",
"serde_json",
"ulid 1.2.1",
@@ -6385,35 +7657,37 @@ dependencies = [
[[package]]
name = "revolt-delta"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
- "amqprs",
- "async-channel 1.9.0",
+ "async-channel 2.5.0",
"async-std",
"authifier",
"bitfield",
"chrono",
"dashmap",
- "env_logger",
"futures",
"impl_ops",
"iso8601-timestamp",
+ "lapin",
"lettre",
- "linkify 0.6.0",
+ "linkify",
+ "livekit-api",
+ "livekit-protocol",
"log",
- "lru 0.7.8",
+ "lru",
"nanoid",
- "num_enum 0.5.11",
+ "num_enum",
"once_cell",
"rand 0.8.5",
"redis-kiss",
"regex",
- "reqwest 0.11.27",
+ "reqwest 0.13.2",
"revolt-config",
"revolt-database",
"revolt-models",
"revolt-permissions",
"revolt-presence",
+ "revolt-ratelimits",
"revolt-result",
"revolt_rocket_okapi",
"rocket",
@@ -6421,10 +7695,10 @@ dependencies = [
"rocket_cors",
"rocket_empty",
"rocket_prometheus",
- "schemars 0.8.22",
+ "schemars",
"serde",
"serde_json",
- "ulid 0.4.1",
+ "ulid 1.2.1",
"url",
"validator 0.16.1",
"vergen",
@@ -6432,12 +7706,14 @@ dependencies = [
[[package]]
name = "revolt-files"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
"aes-gcm",
+ "anyhow",
+ "async-trait",
"aws-config",
"aws-sdk-s3",
- "base64 0.22.1",
+ "base64 0.21.7",
"ffprobe",
"image",
"imagesize",
@@ -6446,16 +7722,42 @@ dependencies = [
"revolt-config",
"revolt-result",
"tempfile",
+ "thiserror 2.0.18",
"tiny-skia",
+ "tokio 1.51.0",
"tracing",
"typenum",
"usvg",
+ "uuid",
"webp",
]
+[[package]]
+name = "revolt-gifbox"
+version = "0.13.7"
+dependencies = [
+ "axum",
+ "axum-extra",
+ "lru_time_cache",
+ "reqwest 0.13.2",
+ "revolt-coalesced",
+ "revolt-config",
+ "revolt-database",
+ "revolt-models",
+ "revolt-ratelimits",
+ "revolt-result",
+ "serde",
+ "serde_json",
+ "tokio 1.51.0",
+ "tower-http 0.5.2",
+ "tracing",
+ "utoipa",
+ "utoipa-scalar",
+]
+
[[package]]
name = "revolt-january"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
"async-recursion",
"axum",
@@ -6464,8 +7766,9 @@ dependencies = [
"lazy_static",
"mime",
"moka",
+ "pdk-ip-filter-lib",
"regex",
- "reqwest 0.12.22",
+ "reqwest 0.13.2",
"revolt-config",
"revolt-files",
"revolt-models",
@@ -6474,27 +7777,28 @@ dependencies = [
"serde",
"serde_json",
"tempfile",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tracing",
"tracing-subscriber",
+ "url",
"utoipa",
"utoipa-scalar",
]
[[package]]
name = "revolt-models"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
- "indexmap 1.9.3",
+ "indexmap 2.13.1",
"iso8601-timestamp",
- "num_enum 0.6.1",
+ "num_enum",
"once_cell",
"regex",
"revolt-config",
"revolt-permissions",
"revolt_optional_struct",
"rocket",
- "schemars 0.8.22",
+ "schemars",
"serde",
"utoipa",
"validator 0.16.1",
@@ -6502,29 +7806,29 @@ dependencies = [
[[package]]
name = "revolt-parser"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
"logos",
]
[[package]]
name = "revolt-permissions"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
"async-std",
"async-trait",
"auto_ops",
"bson",
- "num_enum 0.6.1",
+ "num_enum",
"once_cell",
"revolt-result",
- "schemars 0.8.22",
+ "schemars",
"serde",
]
[[package]]
name = "revolt-presence"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
"async-std",
"log",
@@ -6536,46 +7840,95 @@ dependencies = [
[[package]]
name = "revolt-pushd"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
- "amqprs",
"anyhow",
"async-trait",
"authifier",
- "base64 0.22.1",
+ "base64 0.21.7",
"fcm_v1",
"isahc",
"iso8601-timestamp",
+ "lapin",
"log",
"pretty_env_logger",
+ "redis-kiss",
+ "regex",
"revolt-config",
"revolt-database",
"revolt-models",
+ "revolt-parser",
"revolt-presence",
"revolt-result",
"revolt_a2",
"revolt_optional_struct",
"serde",
"serde_json",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"ulid 1.2.1",
"web-push",
]
+[[package]]
+name = "revolt-ratelimits"
+version = "0.13.7"
+dependencies = [
+ "async-trait",
+ "authifier",
+ "axum",
+ "dashmap",
+ "log",
+ "revolt-config",
+ "revolt-database",
+ "revolt-result",
+ "revolt_rocket_okapi",
+ "rocket",
+ "serde",
+]
+
[[package]]
name = "revolt-result"
-version = "0.8.8"
+version = "0.13.7"
dependencies = [
"axum",
+ "log",
"revolt_okapi",
"revolt_rocket_okapi",
"rocket",
- "schemars 0.8.22",
+ "schemars",
+ "sentry",
"serde",
"serde_json",
"utoipa",
]
+[[package]]
+name = "revolt-voice-ingress"
+version = "0.13.7"
+dependencies = [
+ "async-std",
+ "chrono",
+ "futures",
+ "livekit-api",
+ "livekit-protocol",
+ "livekit-runtime",
+ "log",
+ "lru",
+ "redis-kiss",
+ "revolt-config",
+ "revolt-database",
+ "revolt-models",
+ "revolt-permissions",
+ "revolt-result",
+ "rmp-serde",
+ "rocket",
+ "rocket_empty",
+ "sentry",
+ "serde",
+ "serde_json",
+ "ulid 1.2.1",
+]
+
[[package]]
name = "revolt_a2"
version = "0.10.1"
@@ -6584,20 +7937,21 @@ checksum = "edbe1f79cb41271d3cd8f932d75dddeba963c19dc93d1ee6cbe0391b495ab2f5"
dependencies = [
"base64 0.21.7",
"erased-serde",
- "http 1.3.1",
+ "http 1.4.0",
"http-body-util",
- "hyper 1.6.0",
+ "hyper 1.9.0",
"hyper-rustls 0.26.0",
"hyper-util",
+ "openssl",
"parking_lot",
- "pem 3.0.5",
+ "pem 3.0.6",
"ring",
"rustls 0.22.4",
"rustls-pemfile 2.2.0",
"serde",
"serde_json",
"thiserror 1.0.69",
- "tokio 1.47.1",
+ "tokio 1.51.0",
]
[[package]]
@@ -6613,7 +7967,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23bfdf7ae769c3042fe727f6e5c17363b02a64b4b33ad60c3e5f73b26df7835b"
dependencies = [
"log",
- "schemars 0.8.22",
+ "schemars",
"serde",
"serde_json",
]
@@ -6639,7 +7993,7 @@ dependencies = [
"revolt_okapi",
"revolt_rocket_okapi_codegen",
"rocket",
- "schemars 0.8.22",
+ "schemars",
"serde",
"serde_json",
]
@@ -6652,7 +8006,7 @@ checksum = "cc6620569d8ac8f0a1690fcca13f488503807a60e96ebf729749b59aca1dbef9"
dependencies = [
"darling 0.13.4",
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"rocket_http",
"syn 1.0.109",
]
@@ -6680,9 +8034,9 @@ dependencies = [
[[package]]
name = "rgb"
-version = "0.8.52"
+version = "0.8.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c6a884d2998352bb4daf0183589aec883f16a6da1f4dde84d8e2e9a5409a1ce"
+checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4"
dependencies = [
"bytemuck",
]
@@ -6695,7 +8049,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
- "getrandom 0.2.16",
+ "getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
@@ -6703,22 +8057,19 @@ dependencies = [
[[package]]
name = "rmp"
-version = "0.8.14"
+version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
+checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c"
dependencies = [
- "byteorder",
"num-traits",
- "paste",
]
[[package]]
name = "rmp-serde"
-version = "1.3.0"
+version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
+checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155"
dependencies = [
- "byteorder",
"rmp",
"serde",
]
@@ -6733,17 +8084,17 @@ dependencies = [
"async-trait",
"atomic 0.5.3",
"binascii",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"either",
"figment",
"futures",
- "indexmap 2.10.0",
+ "indexmap 2.13.1",
"log",
"memchr",
"multer",
"num_cpus",
"parking_lot",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"rand 0.8.5",
"ref-cast",
"rocket_codegen",
@@ -6753,7 +8104,7 @@ dependencies = [
"state",
"tempfile",
"time",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tokio-stream",
"tokio-util",
"ubyte",
@@ -6763,9 +8114,9 @@ dependencies = [
[[package]]
name = "rocket_authifier"
-version = "1.0.15"
+version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1f98a5be96ac4144ee1adc809c1594e8212032b8a08d9bc4cef70735dfd976e"
+checksum = "4a4e9660d198ea0165dc2694e7620d8fe199e923b39bec4d12b7159da5a4a6cd"
dependencies = [
"authifier",
"iso8601-timestamp",
@@ -6773,7 +8124,7 @@ dependencies = [
"revolt_rocket_okapi",
"rocket",
"rocket_empty",
- "schemars 0.8.22",
+ "schemars",
"serde",
]
@@ -6785,11 +8136,11 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46"
dependencies = [
"devise",
"glob",
- "indexmap 2.10.0",
+ "indexmap 2.13.1",
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"rocket_http",
- "syn 2.0.104",
+ "syn 2.0.117",
"unicode-xid 0.2.6",
"version_check",
]
@@ -6832,19 +8183,19 @@ dependencies = [
"futures",
"http 0.2.12",
"hyper 0.14.32",
- "indexmap 2.10.0",
+ "indexmap 2.13.1",
"log",
"memchr",
"pear",
"percent-encoding",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"ref-cast",
"serde",
"smallvec",
"stable-pattern",
"state",
"time",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"uncased",
]
@@ -6887,7 +8238,7 @@ dependencies = [
"num-integer",
"num-iter",
"num-traits",
- "pkcs1",
+ "pkcs1 0.4.1",
"pkcs8 0.9.0",
"rand_core 0.6.4",
"signature 1.6.4",
@@ -6896,6 +8247,26 @@ dependencies = [
"zeroize",
]
+[[package]]
+name = "rsa"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d"
+dependencies = [
+ "const-oid 0.9.6",
+ "digest",
+ "num-bigint-dig",
+ "num-integer",
+ "num-traits",
+ "pkcs1 0.7.5",
+ "pkcs8 0.10.2",
+ "rand_core 0.6.4",
+ "signature 2.2.0",
+ "spki 0.7.3",
+ "subtle",
+ "zeroize",
+]
+
[[package]]
name = "rust-argon2"
version = "1.0.1"
@@ -6904,7 +8275,7 @@ checksum = "a5885493fdf0be6cdff808d1533ce878d21cfa49c7086fa00c66355cd9141bfc"
dependencies = [
"base64 0.21.7",
"blake2b_simd",
- "constant_time_eq",
+ "constant_time_eq 0.3.1",
"crossbeam-utils",
]
@@ -6920,15 +8291,15 @@ dependencies = [
[[package]]
name = "rustc-demangle"
-version = "0.1.26"
+version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace"
+checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
[[package]]
name = "rustc-hash"
-version = "1.1.0"
+version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
[[package]]
name = "rustc_version"
@@ -6949,13 +8320,22 @@ dependencies = [
"semver",
]
+[[package]]
+name = "rusticata-macros"
+version = "4.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
+dependencies = [
+ "nom 7.1.3",
+]
+
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.11.0",
"errno",
"libc",
"linux-raw-sys 0.4.15",
@@ -6964,15 +8344,15 @@ dependencies = [
[[package]]
name = "rustix"
-version = "1.0.8"
+version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.11.0",
"errno",
"libc",
- "linux-raw-sys 0.9.4",
- "windows-sys 0.60.2",
+ "linux-raw-sys 0.12.1",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -7003,25 +8383,42 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.23.31"
+version = "0.23.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
+checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
dependencies = [
"aws-lc-rs",
+ "log",
"once_cell",
+ "ring",
"rustls-pki-types",
- "rustls-webpki 0.103.4",
+ "rustls-webpki 0.103.10",
"subtle",
"zeroize",
]
+[[package]]
+name = "rustls-connector"
+version = "0.23.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26bcb6901a3319d57589047c0da93a0f3228f13abf8dd949deef024749cb5e2"
+dependencies = [
+ "futures-io",
+ "futures-rustls",
+ "log",
+ "rustls 0.23.37",
+ "rustls-pki-types",
+ "rustls-platform-verifier 0.7.0",
+ "rustls-webpki 0.103.10",
+]
+
[[package]]
name = "rustls-native-certs"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00"
dependencies = [
- "openssl-probe",
+ "openssl-probe 0.1.6",
"rustls-pemfile 1.0.4",
"schannel",
"security-framework 2.11.1",
@@ -7029,14 +8426,14 @@ dependencies = [
[[package]]
name = "rustls-native-certs"
-version = "0.8.1"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3"
+checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63"
dependencies = [
- "openssl-probe",
+ "openssl-probe 0.2.1",
"rustls-pki-types",
"schannel",
- "security-framework 3.2.0",
+ "security-framework 3.7.0",
]
[[package]]
@@ -7059,13 +8456,62 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
-version = "1.12.0"
+version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79"
+checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
dependencies = [
+ "web-time",
"zeroize",
]
+[[package]]
+name = "rustls-platform-verifier"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784"
+dependencies = [
+ "core-foundation 0.10.1",
+ "core-foundation-sys",
+ "jni 0.21.1",
+ "log",
+ "once_cell",
+ "rustls 0.23.37",
+ "rustls-native-certs 0.8.3",
+ "rustls-platform-verifier-android",
+ "rustls-webpki 0.103.10",
+ "security-framework 3.7.0",
+ "security-framework-sys",
+ "webpki-root-certs",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls-platform-verifier"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0"
+dependencies = [
+ "core-foundation 0.10.1",
+ "core-foundation-sys",
+ "jni 0.22.4",
+ "log",
+ "once_cell",
+ "rustls 0.23.37",
+ "rustls-native-certs 0.8.3",
+ "rustls-platform-verifier-android",
+ "rustls-webpki 0.103.10",
+ "security-framework 3.7.0",
+ "security-framework-sys",
+ "webpki-root-certs",
+ "windows-sys 0.61.2",
+]
+
+[[package]]
+name = "rustls-platform-verifier-android"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f"
+
[[package]]
name = "rustls-webpki"
version = "0.101.7"
@@ -7089,9 +8535,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
-version = "0.103.4"
+version = "0.103.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc"
+checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef"
dependencies = [
"aws-lc-rs",
"ring",
@@ -7101,9 +8547,9 @@ dependencies = [
[[package]]
name = "rustversion"
-version = "1.0.21"
+version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d"
+checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "rustybuzz"
@@ -7111,7 +8557,7 @@ version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85d1ccd519e61834798eb52c4e886e8c2d7d698dd3d6ce0b1b47eb8557f1181"
dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.11.0",
"bytemuck",
"core_maths",
"log",
@@ -7125,52 +8571,47 @@ dependencies = [
[[package]]
name = "ryu"
-version = "1.0.20"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
-name = "schannel"
-version = "0.1.27"
+name = "salsa20"
+version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+checksum = "97a22f5af31f73a954c10289c93e8a50cc23d971e80ee446f1f6f7137a088213"
dependencies = [
- "windows-sys 0.59.0",
+ "cipher",
]
[[package]]
-name = "schemars"
-version = "0.8.22"
+name = "same-file"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
- "dyn-clone",
- "indexmap 1.9.3",
- "schemars_derive",
- "serde",
- "serde_json",
+ "winapi-util",
]
[[package]]
-name = "schemars"
-version = "0.9.0"
+name = "schannel"
+version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f"
+checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
dependencies = [
- "dyn-clone",
- "ref-cast",
- "serde",
- "serde_json",
+ "windows-sys 0.61.2",
]
[[package]]
name = "schemars"
-version = "1.0.4"
+version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0"
+checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615"
dependencies = [
"dyn-clone",
- "ref-cast",
+ "indexmap 1.9.3",
+ "indexmap 2.13.1",
+ "schemars_derive",
"serde",
"serde_json",
]
@@ -7182,9 +8623,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"serde_derive_internals",
- "syn 2.0.104",
+ "syn 2.0.117",
]
[[package]]
@@ -7215,6 +8656,17 @@ dependencies = [
"tendril",
]
+[[package]]
+name = "scrypt"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0516a385866c09368f0b5bcd1caff3366aace790fcd46e2bb032697bb172fd1f"
+dependencies = [
+ "pbkdf2",
+ "salsa20",
+ "sha2",
+]
+
[[package]]
name = "sct"
version = "0.7.1"
@@ -7276,7 +8728,7 @@ version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.11.0",
"core-foundation 0.9.4",
"core-foundation-sys",
"libc",
@@ -7285,11 +8737,11 @@ dependencies = [
[[package]]
name = "security-framework"
-version = "3.2.0"
+version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
+checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.11.0",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
@@ -7298,9 +8750,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
-version = "2.14.0"
+version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
+checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
"core-foundation-sys",
"libc",
@@ -7312,9 +8764,9 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb30575f3638fc8f6815f448d50cb1a2e255b0897985c8c59f4d37b72a07b06"
dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.11.0",
"cssparser",
- "derive_more",
+ "derive_more 0.99.20",
"fxhash",
"log",
"new_debug_unreachable",
@@ -7327,9 +8779,9 @@ dependencies = [
[[package]]
name = "semver"
-version = "1.0.26"
+version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "sentry"
@@ -7346,7 +8798,7 @@ dependencies = [
"sentry-debug-images",
"sentry-panic",
"sentry-tracing",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"ureq",
]
@@ -7483,7 +8935,7 @@ dependencies = [
"rand 0.9.2",
"serde",
"serde_json",
- "thiserror 2.0.12",
+ "thiserror 2.0.18",
"time",
"url",
"uuid",
@@ -7491,40 +8943,42 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.219"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
+ "serde_core",
"serde_derive",
]
[[package]]
name = "serde_bytes"
-version = "0.11.17"
+version = "0.11.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96"
+checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
dependencies = [
"serde",
+ "serde_core",
]
[[package]]
-name = "serde_bytes_ng"
-version = "0.1.2"
+name = "serde_core"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdb0ebce8684e2253f964e8b6ce51f0ccc6666bbb448fb4a6788088bda6544b6"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
- "serde",
+ "serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.219"
+version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -7534,31 +8988,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
name = "serde_json"
-version = "1.0.142"
+version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "030fedb782600dcbd6f02d479bf0d817ac3bb40d644745b769d6a96bc3afc5a7"
+checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
- "indexmap 2.10.0",
+ "indexmap 2.13.1",
"itoa",
"memchr",
- "ryu",
"serde",
+ "serde_core",
+ "zmij",
]
[[package]]
name = "serde_path_to_error"
-version = "0.1.17"
+version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a"
+checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
dependencies = [
"itoa",
"serde",
+ "serde_core",
]
[[package]]
@@ -7584,34 +9040,24 @@ dependencies = [
[[package]]
name = "serde_with"
-version = "3.14.0"
+version = "3.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5"
+checksum = "dd5414fad8e6907dbdd5bc441a50ae8d6e26151a03b1de04d89a5576de61d01f"
dependencies = [
- "base64 0.22.1",
- "chrono",
- "hex",
- "indexmap 1.9.3",
- "indexmap 2.10.0",
- "schemars 0.9.0",
- "schemars 1.0.4",
- "serde",
- "serde_derive",
- "serde_json",
+ "serde_core",
"serde_with_macros",
- "time",
]
[[package]]
name = "serde_with_macros"
-version = "3.14.0"
+version = "3.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f"
+checksum = "d3db8978e608f1fe7357e211969fd9abdcae80bac1ba7a3369bb7eb6b404eb65"
dependencies = [
- "darling 0.20.11",
+ "darling 0.23.0",
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -7630,7 +9076,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"digest",
]
@@ -7641,7 +9087,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"digest",
]
@@ -7658,7 +9104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
- "cpufeatures",
+ "cpufeatures 0.2.17",
"digest",
]
@@ -7679,10 +9125,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
-version = "1.4.6"
+version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
+ "errno",
"libc",
]
@@ -7708,9 +9155,19 @@ dependencies = [
[[package]]
name = "simd-adler32"
-version = "0.3.7"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
+
+[[package]]
+name = "simd_cesu8"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
+checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33"
+dependencies = [
+ "rustc_version",
+ "simdutf8",
+]
[[package]]
name = "simd_helpers"
@@ -7718,7 +9175,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95890f873bec569a0362c235787f3aca6e1e887302ba4840839bcc6459c42da6"
dependencies = [
- "quote 1.0.40",
+ "quote 1.0.45",
]
[[package]]
@@ -7744,32 +9201,32 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
[[package]]
name = "siphasher"
-version = "1.0.1"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
+checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]]
name = "slab"
-version = "0.4.10"
+version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "slotmap"
-version = "1.0.7"
+version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a"
+checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038"
dependencies = [
"version_check",
]
[[package]]
name = "sluice"
-version = "0.5.5"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5"
+checksum = "160b744a45e8261307bcfe03c98e2f8274502207d534c9a64b675c4db1b6bd58"
dependencies = [
- "async-channel 1.9.0",
+ "async-channel 2.5.0",
"futures-core",
"futures-io",
]
@@ -7802,12 +9259,12 @@ dependencies = [
[[package]]
name = "socket2"
-version = "0.6.0"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807"
+checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
"libc",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -7815,6 +9272,15 @@ name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "spin"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591"
[[package]]
name = "spki"
@@ -7847,9 +9313,9 @@ dependencies = [
[[package]]
name = "stable_deref_trait"
-version = "1.2.0"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "state"
@@ -7857,7 +9323,7 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8"
dependencies = [
- "loom 0.5.6",
+ "loom",
]
[[package]]
@@ -7891,7 +9357,7 @@ dependencies = [
"phf_generator 0.11.3",
"phf_shared 0.11.3",
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
]
[[package]]
@@ -7923,11 +9389,11 @@ version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
dependencies = [
- "heck",
+ "heck 0.5.0",
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"rustversion",
- "syn 2.0.104",
+ "syn 2.0.117",
]
[[package]]
@@ -7943,7 +9409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc"
dependencies = [
"kurbo",
- "siphasher 1.0.1",
+ "siphasher 1.0.2",
]
[[package]]
@@ -7964,18 +9430,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"unicode-ident",
]
[[package]]
name = "syn"
-version = "2.0.104"
+version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"unicode-ident",
]
@@ -8010,7 +9476,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"syn 1.0.109",
"unicode-xid 0.2.6",
]
@@ -8022,8 +9488,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -8053,11 +9519,11 @@ dependencies = [
[[package]]
name = "system-configuration"
-version = "0.6.1"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
+checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [
- "bitflags 2.9.1",
+ "bitflags 2.11.0",
"core-foundation 0.9.4",
"system-configuration-sys 0.6.0",
]
@@ -8082,19 +9548,6 @@ dependencies = [
"libc",
]
-[[package]]
-name = "system-deps"
-version = "6.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349"
-dependencies = [
- "cfg-expr",
- "heck",
- "pkg-config",
- "toml 0.8.23",
- "version-compare",
-]
-
[[package]]
name = "tagptr"
version = "0.2.0"
@@ -8114,22 +9567,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
-name = "target-lexicon"
-version = "0.12.16"
+name = "tcp-stream"
+version = "0.34.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
+checksum = "fd7219422d3348cddeaf9073772997c452085c33e22d0b08cbd19652e1b16da5"
+dependencies = [
+ "async-rs",
+ "cfg-if",
+ "futures-io",
+ "p12-keystore",
+ "rustls-connector",
+]
[[package]]
name = "tempfile"
-version = "3.20.0"
+version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
+checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
- "fastrand 2.3.0",
- "getrandom 0.3.3",
+ "fastrand 2.4.0",
+ "getrandom 0.4.2",
"once_cell",
- "rustix 1.0.8",
- "windows-sys 0.59.0",
+ "rustix 1.1.4",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -8163,11 +9623,11 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.12"
+version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
- "thiserror-impl 2.0.12",
+ "thiserror-impl 2.0.18",
]
[[package]]
@@ -8177,19 +9637,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.12"
+version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -8201,22 +9661,31 @@ dependencies = [
"cfg-if",
]
+[[package]]
+name = "thumbhash"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b7726e0245a7331bd0c9a1fb4fd99fd695bcd478ca569f0eda2ff2cb14e7a00"
+
[[package]]
name = "tiff"
-version = "0.9.1"
+version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba1310fcea54c6a9a4fd1aad794ecc02c31682f6bfbecdf460bf19533eed1e3e"
+checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52"
dependencies = [
+ "fax",
"flate2",
- "jpeg-decoder",
+ "half",
+ "quick-error 2.0.1",
"weezl",
+ "zune-jpeg 0.5.15",
]
[[package]]
name = "time"
-version = "0.3.41"
+version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
+checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c"
dependencies = [
"deranged",
"itoa",
@@ -8224,22 +9693,22 @@ dependencies = [
"num-conv",
"num_threads",
"powerfmt",
- "serde",
+ "serde_core",
"time-core",
"time-macros",
]
[[package]]
name = "time-core"
-version = "0.1.4"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
+checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca"
[[package]]
name = "time-macros"
-version = "0.2.22"
+version = "0.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
+checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215"
dependencies = [
"num-conv",
"time-core",
@@ -8265,7 +9734,7 @@ dependencies = [
"bytemuck",
"cfg-if",
"log",
- "png",
+ "png 0.17.16",
"tiny-skia-path",
]
@@ -8282,9 +9751,9 @@ dependencies = [
[[package]]
name = "tinystr"
-version = "0.8.1"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b"
+checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
dependencies = [
"displaydoc",
"zerovec",
@@ -8292,9 +9761,9 @@ dependencies = [
[[package]]
name = "tinyvec"
-version = "1.9.0"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71"
+checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [
"tinyvec_macros",
]
@@ -8318,33 +9787,30 @@ dependencies = [
[[package]]
name = "tokio"
-version = "1.47.1"
+version = "1.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
+checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd"
dependencies = [
- "backtrace",
- "bytes 1.10.1",
- "io-uring",
+ "bytes 1.11.1",
"libc",
"mio",
"parking_lot",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"signal-hook-registry",
- "slab",
- "socket2 0.6.0",
+ "socket2 0.6.3",
"tokio-macros",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
-version = "2.5.0"
+version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
+checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
@@ -8354,7 +9820,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
- "tokio 1.47.1",
+ "tokio 1.51.0",
]
[[package]]
@@ -8364,7 +9830,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls 0.21.12",
- "tokio 1.47.1",
+ "tokio 1.51.0",
]
[[package]]
@@ -8375,42 +9841,57 @@ checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
dependencies = [
"rustls 0.22.4",
"rustls-pki-types",
- "tokio 1.47.1",
+ "tokio 1.51.0",
]
[[package]]
name = "tokio-rustls"
-version = "0.26.2"
+version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
+checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
- "rustls 0.23.31",
- "tokio 1.47.1",
+ "rustls 0.23.37",
+ "tokio 1.51.0",
]
[[package]]
name = "tokio-stream"
-version = "0.1.17"
+version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
+checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70"
dependencies = [
"futures-core",
- "pin-project-lite 0.2.16",
- "tokio 1.47.1",
+ "pin-project-lite 0.2.17",
+ "tokio 1.51.0",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c"
+dependencies = [
+ "futures-util",
+ "log",
+ "rustls 0.21.12",
+ "rustls-native-certs 0.6.3",
+ "tokio 1.51.0",
+ "tokio-rustls 0.24.1",
+ "tungstenite 0.20.1",
]
[[package]]
name = "tokio-util"
-version = "0.7.16"
+version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
+checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [
- "bytes 1.10.1",
+ "bytes 1.11.1",
"futures-core",
"futures-io",
"futures-sink",
- "pin-project-lite 0.2.16",
- "tokio 1.47.1",
+ "pin-project-lite 0.2.17",
+ "tokio 1.51.0",
]
[[package]]
@@ -8449,7 +9930,7 @@ version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
- "indexmap 2.10.0",
+ "indexmap 2.13.1",
"toml_datetime",
"winnow 0.5.40",
]
@@ -8460,12 +9941,12 @@ version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
- "indexmap 2.10.0",
+ "indexmap 2.13.1",
"serde",
"serde_spanned",
"toml_datetime",
"toml_write",
- "winnow 0.7.12",
+ "winnow 0.7.15",
]
[[package]]
@@ -8488,15 +9969,15 @@ dependencies = [
[[package]]
name = "tower"
-version = "0.5.2"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
+checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
dependencies = [
"futures-core",
"futures-util",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"sync_wrapper 1.0.2",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tower-layer",
"tower-service",
"tracing",
@@ -8508,29 +9989,29 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5"
dependencies = [
- "bitflags 2.9.1",
- "bytes 1.10.1",
- "http 1.3.1",
+ "bitflags 2.11.0",
+ "bytes 1.11.1",
+ "http 1.4.0",
"http-body 1.0.1",
"http-body-util",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-http"
-version = "0.6.6"
+version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
+checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [
- "bitflags 2.9.1",
- "bytes 1.10.1",
+ "bitflags 2.11.0",
+ "bytes 1.11.1",
"futures-util",
- "http 1.3.1",
+ "http 1.4.0",
"http-body 1.0.1",
"iri-string",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"tower",
"tower-layer",
"tower-service",
@@ -8550,32 +10031,32 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
-version = "0.1.41"
+version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"log",
- "pin-project-lite 0.2.16",
+ "pin-project-lite 0.2.17",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
-version = "0.1.30"
+version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903"
+checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
name = "tracing-core"
-version = "0.1.34"
+version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
"valuable",
@@ -8604,14 +10085,14 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
-version = "0.3.19"
+version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
+checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
- "regex",
+ "regex-automata",
"sharded-slab",
"smallvec",
"thread_local",
@@ -8643,7 +10124,7 @@ checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0"
dependencies = [
"base64 0.13.1",
"byteorder",
- "bytes 1.10.1",
+ "bytes 1.11.1",
"http 0.2.12",
"httparse",
"log",
@@ -8654,22 +10135,51 @@ dependencies = [
"utf-8",
]
+[[package]]
+name = "tungstenite"
+version = "0.20.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9"
+dependencies = [
+ "byteorder",
+ "bytes 1.11.1",
+ "data-encoding",
+ "http 0.2.12",
+ "httparse",
+ "log",
+ "rand 0.8.5",
+ "rustls 0.21.12",
+ "sha1",
+ "thiserror 1.0.69",
+ "url",
+ "utf-8",
+]
+
[[package]]
name = "typed-builder"
-version = "0.10.0"
+version = "0.22.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "398a3a3c918c96de527dc11e6e846cd549d4508030b8a33e1da12789c856b81a"
+dependencies = [
+ "typed-builder-macro",
+]
+
+[[package]]
+name = "typed-builder-macro"
+version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89851716b67b937e393b3daa8423e67ddfc4bbbf1654bcf05488e95e0828db0c"
+checksum = "0e48cea23f68d1f78eb7bc092881b6bb88d3d6b5b7e6234f6f9c911da1ffb221"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 1.0.109",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
name = "typenum"
-version = "1.18.0"
+version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
+checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb"
[[package]]
name = "ubyte"
@@ -8686,17 +10196,6 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
-[[package]]
-name = "ulid"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7e95a59b292ca0cf9b45be2e52294d1ca6cb24eb11b08ef4376f73f1a00c549"
-dependencies = [
- "chrono",
- "lazy_static",
- "rand 0.6.5",
-]
-
[[package]]
name = "ulid"
version = "0.5.0"
@@ -8739,9 +10238,9 @@ dependencies = [
[[package]]
name = "unicase"
-version = "2.8.1"
+version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
+checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicase_serde"
@@ -8773,36 +10272,36 @@ checksum = "260bc6647b3893a9a90668360803a15f96b85a5257b1c3a0c3daf6ae2496de42"
[[package]]
name = "unicode-ident"
-version = "1.0.18"
+version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-normalization"
-version = "0.1.24"
+version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
+checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-properties"
-version = "0.1.3"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
+checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
[[package]]
name = "unicode-script"
-version = "0.5.7"
+version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fb421b350c9aff471779e262955939f565ec18b86c15364e6bdf0d662ca7c1f"
+checksum = "383ad40bb927465ec0ce7720e033cb4ca06912855fc35db31b5755d0de75b1ee"
[[package]]
name = "unicode-segmentation"
-version = "1.12.0"
+version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
+checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c"
[[package]]
name = "unicode-vo"
@@ -8812,9 +10311,9 @@ checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
[[package]]
name = "unicode-width"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
+checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "unicode-xid"
@@ -8859,14 +10358,15 @@ dependencies = [
[[package]]
name = "url"
-version = "2.5.4"
+version = "2.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [
"form_urlencoded",
- "idna 1.0.3",
+ "idna 1.1.0",
"percent-encoding",
"serde",
+ "serde_derive",
]
[[package]]
@@ -8901,7 +10401,7 @@ dependencies = [
"roxmltree",
"rustybuzz",
"simplecss",
- "siphasher 1.0.1",
+ "siphasher 1.0.2",
"strict-num",
"svgtypes",
"tiny-skia-path",
@@ -8929,7 +10429,7 @@ version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23"
dependencies = [
- "indexmap 2.10.0",
+ "indexmap 2.13.1",
"serde",
"serde_json",
"utoipa-gen",
@@ -8943,9 +10443,9 @@ checksum = "20c24e8ab68ff9ee746aad22d39b5535601e6416d1b0feeabf78be986a5c4392"
dependencies = [
"proc-macro-error",
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"regex",
- "syn 2.0.104",
+ "syn 2.0.117",
"ulid 1.2.1",
]
@@ -8963,13 +10463,13 @@ dependencies = [
[[package]]
name = "uuid"
-version = "1.17.0"
+version = "1.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3cf4199d1e5d15ddd86a694e4d0dffa9c323ce759fea589f00fef9d81cc1931d"
+checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9"
dependencies = [
- "getrandom 0.3.3",
+ "getrandom 0.4.2",
"js-sys",
- "serde",
+ "serde_core",
"wasm-bindgen",
]
@@ -9025,7 +10525,7 @@ dependencies = [
"lazy_static",
"proc-macro-error",
"proc-macro2",
- "quote 1.0.40",
+ "quote 1.0.45",
"regex",
"syn 1.0.109",
"validator_types",
@@ -9049,9 +10549,9 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "value-bag"
-version = "1.11.1"
+version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
+checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0"
[[package]]
name = "vcpkg"
@@ -9077,12 +10577,6 @@ dependencies = [
"time",
]
-[[package]]
-name = "version-compare"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b"
-
[[package]]
name = "version_check"
version = "0.9.5"
@@ -9101,6 +10595,16 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317211a0dc0ceedd78fb2ca9a44aed3d7b9b26f81870d485c07122b4350673b7"
+[[package]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "winapi-util",
+]
+
[[package]]
name = "want"
version = "0.3.1"
@@ -9117,94 +10621,121 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
-name = "wasi"
-version = "0.14.2+wasi-0.2.4"
+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 = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
+checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
- "wit-bindgen-rt",
+ "wit-bindgen",
]
[[package]]
name = "wasix"
-version = "0.12.21"
+version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d"
+checksum = "1757e0d1f8456693c7e5c6c629bdb54884e032aa0bb53c155f6a39f94440d332"
dependencies = [
- "wasi 0.11.1+wasi-snapshot-preview1",
+ "wasi",
]
[[package]]
name = "wasm-bindgen"
-version = "0.2.100"
+version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
-]
-
-[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.100"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
-dependencies = [
- "bumpalo",
- "log",
- "proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.50"
+version = "0.4.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
+checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e"
dependencies = [
- "cfg-if",
"js-sys",
- "once_cell",
"wasm-bindgen",
- "web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.100"
+version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be"
dependencies = [
- "quote 1.0.40",
+ "quote 1.0.45",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.100"
+version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2"
dependencies = [
+ "bumpalo",
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
- "wasm-bindgen-backend",
+ "quote 1.0.45",
+ "syn 2.0.117",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.100"
+version = "0.2.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b"
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 2.13.1",
+ "wasm-encoder",
+ "wasmparser",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.244.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
+dependencies = [
+ "bitflags 2.11.0",
+ "hashbrown 0.15.5",
+ "indexmap 2.13.1",
+ "semver",
+]
+
[[package]]
name = "web-push"
version = "0.10.4"
@@ -9215,12 +10746,12 @@ dependencies = [
"base64 0.13.1",
"chrono",
"ece",
- "futures-lite 2.6.1",
+ "futures-lite",
"http 0.2.12",
"isahc",
"jwt-simple",
"log",
- "pem 3.0.5",
+ "pem 3.0.6",
"sec1_decode",
"serde",
"serde_derive",
@@ -9229,9 +10760,9 @@ dependencies = [
[[package]]
name = "web-sys"
-version = "0.3.77"
+version = "0.3.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
+checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -9249,19 +10780,22 @@ dependencies = [
[[package]]
name = "webp"
-version = "0.3.0"
+version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f53152f51fb5af0c08484c33d16cca96175881d1f3dec068c23b31a158c2d99"
+checksum = "c071456adef4aca59bf6a583c46b90ff5eb0b4f758fc347cea81290288f37ce1"
dependencies = [
"image",
"libwebp-sys",
]
[[package]]
-name = "webpki-roots"
-version = "0.25.4"
+name = "webpki-root-certs"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1"
+checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca"
+dependencies = [
+ "rustls-pki-types",
+]
[[package]]
name = "webpki-roots"
@@ -9269,23 +10803,23 @@ version = "0.26.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
dependencies = [
- "webpki-roots 1.0.2",
+ "webpki-roots 1.0.6",
]
[[package]]
name = "webpki-roots"
-version = "1.0.2"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2"
+checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
dependencies = [
"rustls-pki-types",
]
[[package]]
name = "weezl"
-version = "0.1.10"
+version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3"
+checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "which"
@@ -9301,9 +10835,9 @@ dependencies = [
[[package]]
name = "widestring"
-version = "1.2.0"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
+checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471"
[[package]]
name = "winapi"
@@ -9323,11 +10857,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
-version = "0.1.9"
+version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -9345,33 +10879,11 @@ dependencies = [
"windows-targets 0.48.5",
]
-[[package]]
-name = "windows"
-version = "0.61.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
-dependencies = [
- "windows-collections",
- "windows-core",
- "windows-future",
- "windows-link",
- "windows-numerics",
-]
-
-[[package]]
-name = "windows-collections"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
-dependencies = [
- "windows-core",
-]
-
[[package]]
name = "windows-core"
-version = "0.61.2"
+version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
@@ -9380,60 +10892,39 @@ dependencies = [
"windows-strings",
]
-[[package]]
-name = "windows-future"
-version = "0.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
-dependencies = [
- "windows-core",
- "windows-link",
- "windows-threading",
-]
-
[[package]]
name = "windows-implement"
-version = "0.60.0"
+version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
name = "windows-interface"
-version = "0.59.1"
+version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
name = "windows-link"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
-
-[[package]]
-name = "windows-numerics"
-version = "0.2.0"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
-dependencies = [
- "windows-core",
- "windows-link",
-]
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-registry"
-version = "0.5.3"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e"
+checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link",
"windows-result",
@@ -9442,22 +10933,31 @@ dependencies = [
[[package]]
name = "windows-result"
-version = "0.3.4"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
-version = "0.4.2"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets 0.42.2",
+]
+
[[package]]
name = "windows-sys"
version = "0.48.0"
@@ -9491,7 +10991,31 @@ version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
- "windows-targets 0.53.3",
+ "windows-targets 0.53.5",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
+dependencies = [
+ "windows_aarch64_gnullvm 0.42.2",
+ "windows_aarch64_msvc 0.42.2",
+ "windows_i686_gnu 0.42.2",
+ "windows_i686_msvc 0.42.2",
+ "windows_x86_64_gnu 0.42.2",
+ "windows_x86_64_gnullvm 0.42.2",
+ "windows_x86_64_msvc 0.42.2",
]
[[package]]
@@ -9527,29 +11051,26 @@ dependencies = [
[[package]]
name = "windows-targets"
-version = "0.53.3"
+version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91"
+checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
- "windows_aarch64_gnullvm 0.53.0",
- "windows_aarch64_msvc 0.53.0",
- "windows_i686_gnu 0.53.0",
- "windows_i686_gnullvm 0.53.0",
- "windows_i686_msvc 0.53.0",
- "windows_x86_64_gnu 0.53.0",
- "windows_x86_64_gnullvm 0.53.0",
- "windows_x86_64_msvc 0.53.0",
+ "windows_aarch64_gnullvm 0.53.1",
+ "windows_aarch64_msvc 0.53.1",
+ "windows_i686_gnu 0.53.1",
+ "windows_i686_gnullvm 0.53.1",
+ "windows_i686_msvc 0.53.1",
+ "windows_x86_64_gnu 0.53.1",
+ "windows_x86_64_gnullvm 0.53.1",
+ "windows_x86_64_msvc 0.53.1",
]
[[package]]
-name = "windows-threading"
-version = "0.1.0"
+name = "windows_aarch64_gnullvm"
+version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
-dependencies = [
- "windows-link",
-]
+checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
@@ -9565,9 +11086,15 @@ checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.53.0"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
+checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
@@ -9583,9 +11110,15 @@ checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.53.0"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
+checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
@@ -9601,9 +11134,9 @@ checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
+checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
@@ -9613,9 +11146,15 @@ checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
-version = "0.53.0"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
+checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
@@ -9631,9 +11170,15 @@ checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
-version = "0.53.0"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
+checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
@@ -9649,9 +11194,15 @@ checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.53.0"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
+checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
@@ -9667,9 +11218,15 @@ checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.53.0"
+version = "0.53.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
+checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
@@ -9685,9 +11242,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.53.0"
+version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winnow"
@@ -9700,9 +11257,9 @@ dependencies = [
[[package]]
name = "winnow"
-version = "0.7.12"
+version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3edebf492c8125044983378ecb5766203ad3b4c2f7a922bd7dd207f6d443e95"
+checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
dependencies = [
"memchr",
]
@@ -9718,19 +11275,98 @@ dependencies = [
]
[[package]]
-name = "wit-bindgen-rt"
-version = "0.39.0"
+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 0.5.0",
+ "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 0.5.0",
+ "indexmap 2.13.1",
+ "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 1.0.45",
+ "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 2.13.1",
+ "log",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-encoder",
+ "wasm-metadata",
+ "wasmparser",
+ "wit-parser",
+]
+
+[[package]]
+name = "wit-parser"
+version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
+checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
- "bitflags 2.9.1",
+ "anyhow",
+ "id-arena",
+ "indexmap 2.13.1",
+ "log",
+ "semver",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "unicode-xid 0.2.6",
+ "wasmparser",
]
[[package]]
name = "writeable"
-version = "0.6.1"
+version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb"
+checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "wyz"
@@ -9741,6 +11377,34 @@ dependencies = [
"tap",
]
+[[package]]
+name = "x509-cert"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1301e935010a701ae5f8655edc0ad17c44bad3ac5ce8c39185f75453b720ae94"
+dependencies = [
+ "const-oid 0.9.6",
+ "der 0.7.10",
+ "spki 0.7.3",
+]
+
+[[package]]
+name = "x509-parser"
+version = "0.18.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d43b0f71ce057da06bc0851b23ee24f3f86190b07203dd8f567d0b706a185202"
+dependencies = [
+ "asn1-rs",
+ "data-encoding",
+ "der-parser",
+ "lazy_static",
+ "nom 7.1.3",
+ "oid-registry",
+ "rusticata-macros",
+ "thiserror 2.0.18",
+ "time",
+]
+
[[package]]
name = "xmlparser"
version = "0.13.6"
@@ -9753,6 +11417,12 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7a2a501ed189703dba8b08142f057e887dfc4b2cc4db2d343ac6376ba3e0b9"
+[[package]]
+name = "y4m"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5a4b21e1a62b67a2970e6831bc091d7b87e119e7f9791aef9702e3bef04448"
+
[[package]]
name = "yaml-rust"
version = "0.4.5"
@@ -9773,11 +11443,10 @@ dependencies = [
[[package]]
name = "yoke"
-version = "0.8.0"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc"
+checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
dependencies = [
- "serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
@@ -9785,13 +11454,13 @@ dependencies = [
[[package]]
name = "yoke-derive"
-version = "0.8.0"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6"
+checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
"synstructure 0.13.2",
]
@@ -9808,7 +11477,7 @@ dependencies = [
"http 0.2.12",
"hyper 0.14.32",
"hyper-rustls 0.24.2",
- "itertools",
+ "itertools 0.12.1",
"log",
"percent-encoding",
"rustls 0.22.4",
@@ -9817,63 +11486,63 @@ dependencies = [
"serde",
"serde_json",
"time",
- "tokio 1.47.1",
+ "tokio 1.51.0",
"tower-service",
"url",
]
[[package]]
name = "zerocopy"
-version = "0.8.26"
+version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
+checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.26"
+version = "0.8.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
+checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
[[package]]
name = "zerofrom"
-version = "0.1.6"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
+checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
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 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
"synstructure 0.13.2",
]
[[package]]
name = "zeroize"
-version = "1.8.1"
+version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zerotrie"
-version = "0.2.2"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595"
+checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
dependencies = [
"displaydoc",
"yoke",
@@ -9882,9 +11551,9 @@ dependencies = [
[[package]]
name = "zerovec"
-version = "0.11.4"
+version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
+checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
dependencies = [
"yoke",
"zerofrom",
@@ -9893,21 +11562,33 @@ dependencies = [
[[package]]
name = "zerovec-derive"
-version = "0.11.1"
+version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f"
+checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
dependencies = [
"proc-macro2",
- "quote 1.0.40",
- "syn 2.0.104",
+ "quote 1.0.45",
+ "syn 2.0.117",
]
+[[package]]
+name = "zmij"
+version = "1.0.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
+
[[package]]
name = "zune-core"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f423a2c17029964870cfaabb1f13dfab7d092a62a29a89264f4d36990ca414a"
+[[package]]
+name = "zune-core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
+
[[package]]
name = "zune-inflate"
version = "0.2.54"
@@ -9919,9 +11600,18 @@ dependencies = [
[[package]]
name = "zune-jpeg"
-version = "0.4.20"
+version = "0.4.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
+dependencies = [
+ "zune-core 0.4.12",
+]
+
+[[package]]
+name = "zune-jpeg"
+version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc1f7e205ce79eb2da3cd71c5f55f3589785cb7c79f6a03d1c8d1491bda5d089"
+checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
dependencies = [
- "zune-core",
+ "zune-core 0.5.1",
]
diff --git a/Cargo.toml b/Cargo.toml
index 5c320e633..6d1335cc4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
[workspace]
-resolver = "2"
+resolver = "3"
members = [
"crates/delta",
@@ -14,9 +14,190 @@ redis23 = { package = "redis", version = "0.23.3", git = "https://github.com/rev
#authifier = { package = "authifier", version = "1.0.10", path = "../authifier/crates/authifier" }
#rocket_authifier = { package = "rocket_authifier", version = "1.0.10", path = "../authifier/crates/rocket_authifier" }
-# I'm 99% sure this is overloading the GitHub worker
-# hence builds have been failing since, let's just
-# disable it for now. In the future, we could use this
-# if we were rolling our own CI (that is now).
[profile.release]
lto = true
+
+[workspace.dependencies]
+# Async
+async-trait = "0.1.89"
+tokio = "1.49.0"
+async-channel = "2.3.1"
+futures = "0.3.32"
+async-std = "1.8.0"
+async-tungstenite = "0.17.0"
+futures-locks = "0.7.1"
+async-lock = "2.8.0"
+async-recursion = "1.0.4"
+
+# Error Handling
+anyhow = "1.0.100"
+thiserror = "2.0.18"
+sentry = "0.31.5"
+sentry-anyhow = "0.38.1"
+
+# Data Validation
+regex = "1.12.3"
+validator = "0.16"
+
+# Data Types
+uuid = "1.19.0"
+ulid = "1.2.1"
+nanoid = "0.4.0"
+typenum = "1.17.0"
+num_enum = "0.6.1"
+bitfield = "0.13.2"
+
+# Time
+chrono = "0.4.15"
+iso8601-timestamp = "0.2.10"
+
+# Data Collections
+lru = "0.16.3"
+indexmap = "2.13.1"
+dashmap = "5.2.0"
+moka = "0.12.8"
+lru_time_cache = "0.11.11"
+deadqueue = "0.2.4"
+
+# Web scraping
+scraper = "0.20.0"
+encoding_rs = "0.8.34"
+
+# Mail
+lettre = "0.10.0-alpha.4"
+
+# HTTP Requests
+reqwest = "0.13.2"
+isahc = "1.7"
+
+# Notifications
+fcm_v1 = "0.3.0"
+web-push = "0.10.0"
+revolt_a2 = "0.10"
+
+# Parsing
+logos = "0.15"
+
+# SVG rendering
+usvg = "0.44.0"
+resvg = "0.44.0"
+tiny-skia = "0.11.4"
+
+# Logging
+log = "0.4.29"
+pretty_env_logger = "0.4.0"
+
+# Redis
+redis-kiss = "0.1.4"
+fred = "8.0.1"
+
+# Serialisation
+bincode = "1.3.3"
+serde_json = "1.0.79"
+rmp-serde = "1.0.0"
+serde = { version = "1", features = ["derive"] }
+strum_macros = "0.26.4"
+
+# MongoDB
+bson = { version = "2.1.0" }
+mongodb = { version = "3.1.0" }
+
+# S3
+aws-config = "1.5.5"
+aws-sdk-s3 = "1.46.0"
+
+# Axum (HTTP server)
+axum-macros = "0.4.1"
+axum_typed_multipart = "0.12.1"
+axum = "0.7.5"
+axum-extra = "0.9"
+tower-http = "0.5.2"
+
+# Rocket (HTTP server)
+rocket = "0.5.1"
+rocket_empty = "0.1.1"
+revolt_rocket_okapi = "0.10.0"
+rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "072d90359b23e9b291df6b672c07c93de9c46011" }
+rocket_authifier = "1.0.16"
+rocket_prometheus = "0.10.0-rc.3"
+
+# Spec Generation
+utoipa = "4.2.3"
+revolt_okapi = "0.9.1"
+schemars = "0.8.8"
+utoipa-scalar = "0.1.0"
+
+# Image Processing
+jxl-oxide = "0.12.5"
+sha2 = "0.10.8"
+kamadak-exif = "0.5.4"
+webp = "0.3.0"
+image = "0.25.2" # avif encode requires dav1d system library: features = ["avif-native"]
+thumbhash = "0.1.0"
+lcms2 = "6.1.1" # for color profile processing
+
+# File processing
+revolt_clamav-client = "0.1.5"
+simdutf8 = "0.1.4"
+
+# Content type processing
+infer = "0.16.0"
+ffprobe = "0.4.0"
+imagesize = "0.13.0"
+
+# OpenTelemetry
+tracing = "0.1.44"
+tracing-subscriber = { version = "0.3.22", features = [
+ "env-filter",
+] } # consider https://crates.io/crates/better-tracing
+opentelemetry = { version = "0.31.0", features = ["logs"] }
+opentelemetry_sdk = { version = "0.31.0", features = ["logs"] }
+opentelemetry-otlp = { version = "0.31.0", features = ["logs"] }
+opentelemetry-appender-tracing = "0.31.1"
+
+# Authifier
+authifier = "1.0.16"
+
+# RabbitMQ
+lapin = "4.7.1"
+
+# Voice
+livekit-api = "0.4.4"
+livekit-protocol = "0.7.4"
+livekit-runtime = "0.4.0"
+
+# Other Utilities
+once_cell = "1.9.0"
+config = "0.13.3"
+cached = "0.44.0"
+rand = "0.8.5"
+base64 = "0.21.3"
+decancer = "3.3.3"
+linkify = "0.8.1"
+url-escape = "0.1.1"
+revolt_optional_struct = "0.2.0"
+unicode-segmentation = "1.10.1"
+querystring = "1.1.0"
+tempfile = "3.12.0"
+aes-gcm = "0.10.3"
+auto_ops = "0.3.0"
+url = "2.2.2"
+impl_ops = "0.1.1"
+lazy_static = "1.5.0"
+mime = "0.3.17"
+futures-lite = "2.6.1"
+
+# Build Dependencies
+vergen = "7.5.0"
+
+# Local packages
+revolt-coalesced = { version = "0.13.7", path = "crates/core/coalesced" }
+revolt-config = { version = "0.13.7", path = "crates/core/config" }
+revolt-database = { version = "0.13.7", path = "crates/core/database" }
+revolt-files = { version = "0.13.7", path = "crates/core/files" }
+revolt-models = { version = "0.13.7", path = "crates/core/models" }
+revolt-parser = { version = "0.13.7", path = "crates/core/parser" }
+revolt-permissions = { version = "0.13.7", path = "crates/core/permissions" }
+revolt-presence = { version = "0.13.7", path = "crates/core/presence" }
+revolt-ratelimits = { version = "0.13.7", path = "crates/core/ratelimits" }
+revolt-result = { version = "0.13.7", path = "crates/core/result" }
diff --git a/Dockerfile b/Dockerfile
index 524e7e0e9..aca233ec6 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,5 +1,5 @@
# Build Stage
-FROM --platform="${BUILDPLATFORM}" rust:1.86.0-slim-bookworm
+FROM --platform="${BUILDPLATFORM}" rust:1.92.0-slim-bookworm
USER 0:0
WORKDIR /home/rust/src
@@ -27,10 +27,14 @@ COPY crates/core/parser/Cargo.toml ./crates/core/parser/
COPY crates/core/permissions/Cargo.toml ./crates/core/permissions/
COPY crates/core/presence/Cargo.toml ./crates/core/presence/
COPY crates/core/result/Cargo.toml ./crates/core/result/
+COPY crates/core/coalesced/Cargo.toml ./crates/core/coalesced/
+COPY crates/core/ratelimits/Cargo.toml ./crates/core/ratelimits/
COPY crates/services/autumn/Cargo.toml ./crates/services/autumn/
COPY crates/services/january/Cargo.toml ./crates/services/january/
+COPY crates/services/gifbox/Cargo.toml ./crates/services/gifbox/
COPY crates/daemons/crond/Cargo.toml ./crates/daemons/crond/
COPY crates/daemons/pushd/Cargo.toml ./crates/daemons/pushd/
+COPY crates/daemons/voice-ingress/Cargo.toml ./crates/daemons/voice-ingress/
RUN sh /tmp/build-image-layer.sh deps
# Build all apps
diff --git a/Dockerfile.useCurrentArch b/Dockerfile.useCurrentArch
index f36c08081..9bcce8ac7 100644
--- a/Dockerfile.useCurrentArch
+++ b/Dockerfile.useCurrentArch
@@ -1,5 +1,5 @@
# Build Stage
-FROM rust:1.86.0-slim-bookworm
+FROM rust:1.92.0-slim-bookworm
USER 0:0
WORKDIR /home/rust/src
@@ -23,10 +23,14 @@ COPY crates/core/parser/Cargo.toml ./crates/core/parser/
COPY crates/core/permissions/Cargo.toml ./crates/core/permissions/
COPY crates/core/presence/Cargo.toml ./crates/core/presence/
COPY crates/core/result/Cargo.toml ./crates/core/result/
+COPY crates/core/coalesced/Cargo.toml ./crates/core/coalesced/
+COPY crates/core/ratelimits/Cargo.toml ./crates/core/ratelimits/
COPY crates/services/autumn/Cargo.toml ./crates/services/autumn/
COPY crates/services/january/Cargo.toml ./crates/services/january/
+COPY crates/services/gifbox/Cargo.toml ./crates/services/gifbox/
COPY crates/daemons/crond/Cargo.toml ./crates/daemons/crond/
COPY crates/daemons/pushd/Cargo.toml ./crates/daemons/pushd/
+COPY crates/daemons/voice-ingress/Cargo.toml ./crates/daemons/voice-ingress/
RUN sh /tmp/build-image-layer.sh deps
# Build all apps
diff --git a/README.md b/README.md
index 3dd25cf5b..e090149e7 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,15 @@
- Revolt Backend
+ Stoat Backend
- [](https://github.com/revoltchat/backend/stargazers)
- [](https://github.com/revoltchat/backend/network/members)
- [](https://github.com/revoltchat/backend/pulls)
- [](https://github.com/revoltchat/backend/issues)
- [](https://github.com/revoltchat/backend/graphs/contributors)
- [](https://github.com/revoltchat/backend/blob/main/LICENSE)
+ [](https://github.com/stoatchat/stoatchat/stargazers)
+ [](https://github.com/stoatchat/stoatchat/network/members)
+ [](https://github.com/stoatchat/stoatchat/pulls)
+ [](https://github.com/stoatchat/stoatchat/issues)
+ [](https://github.com/stoatchat/stoatchat/graphs/contributors)
+ [](https://github.com/stoatchat/stoatchat/blob/main/LICENSE)
-The services and libraries that power the Revolt service.
+The services and libraries that power the Stoat service.
| Crate | Path | Description | |
@@ -21,9 +21,11 @@ The services and libraries that power the Revolt service.
| `core/permissions` | [crates/core/permissions](crates/core/permissions) | Core: Permission Logic |     |
| `core/presence` | [crates/core/presence](crates/core/presence) | Core: User Presence |     |
| `core/result` | [crates/core/result](crates/core/result) | Core: Result and Error types |     |
+| `core/coalesced` | [crates/core/coalesced](crates/core/coalesced) | Core: Coalescion service |     |
| `delta` | [crates/delta](crates/delta) | REST API server |  |
| `bonfire` | [crates/bonfire](crates/bonfire) | WebSocket events server |  |
| `services/january` | [crates/services/january](crates/services/january) | Proxy server |  |
+| `services/gifbox` | [crates/services/gifbox](crates/services/gifbox) | Tenor proxy server |  |
| `services/autumn` | [crates/services/autumn](crates/services/autumn) | File server |  |
| `daemons/crond` | [crates/daemons/crond](crates/daemons/crond) | Timed data clean up daemon server |  |
| `daemons/pushd` | [crates/daemons/pushd](crates/daemons/pushd) | Push notification daemon server |  |
@@ -35,22 +37,19 @@ The services and libraries that power the Revolt service.
Rust 1.86.0 or higher.
-> [!CAUTION]
-> The events server has a significant performance regression between Rust 1.77.2 and 1.78.0 onwards, see [issue #341](https://github.com/revoltchat/backend/issues/341). This is currently solved by build time options but we are looking for a proper fix.
-
## Development Guide
-Before contributing, make yourself familiar with [our contribution guidelines](https://developers.revolt.chat/contrib.html) and the [technical documentation for this project](https://revoltchat.github.io/backend/).
+Before contributing, make yourself familiar with [our contribution guidelines](https://developers.stoat.chat/developing/contrib/) and the [technical documentation for this project](https://developers.stoat.chat/).
Before getting started, you'll want to install:
-- Rust toolchain (rustup recommended)
+- mise
- Docker
- Git
- mold (optional, faster compilation)
> A **default.nix** is available for Nix users!
-> Just run `nix-shell` and continue.
+> Run `nix-shell` to activate mise.
As a heads-up, the development environment uses the following ports:
@@ -66,15 +65,25 @@ As a heads-up, the development environment uses the following ports:
| `crates/bonfire` | 14703 |
| `crates/services/autumn` | 14704 |
| `crates/services/january` | 14705 |
+| `crates/services/gifbox` | 14706 |
Now you can clone and build the project:
```bash
-git clone https://github.com/revoltchat/backend revolt-backend
-cd revolt-backend
-cargo build
+git clone https://github.com/stoatchat/stoatchat stoat-backend
+cd stoat-backend
+mise install
+mise build
```
+> [!TIP]
+> You can override `BUILDER` in your `.env` file to run cargo with mold if you installed it:
+>
+> ```bash
+> # .env
+> BUILDER = "mold --run cargo"
+> ```
+
A default configuration `Revolt.toml` is present in this project that is suited for development.
If you'd like to change anything, create a `Revolt.overrides.toml` file and specify relevant variables.
@@ -111,7 +120,7 @@ If you'd like to change anything, create a `Revolt.overrides.toml` file and spec
> - "14672:15672"
> ```
>
-> And corresponding Revolt configuration:
+> With the corresponding Revolt configuration:
>
> ```toml
> # Revolt.overrides.toml
@@ -123,51 +132,41 @@ If you'd like to change anything, create a `Revolt.overrides.toml` file and spec
> [rabbit]
> port = 14072
> ```
+>
+> And mise configuration
+>
+> ```bash
+> #.env
+> DATABASE_PORT = "14017"
+> RABBIT_PORT = "14072"
+> REDIS_PORT = "14079"
+> ```
Then continue:
```bash
-# start other necessary services
-docker compose up -d
+cp livekit.example.yml livekit.yml
-# run everything together
-./scripts/start.sh
-# .. or individually
-# run the API server
-cargo run --bin revolt-delta
-# run the events server
-cargo run --bin revolt-bonfire
-# run the file server
-cargo run --bin revolt-autumn
-# run the proxy server
-cargo run --bin revolt-january
-# run the push daemon (not usually needed in regular development)
-cargo run --bin revolt-pushd
-
-# hint:
-# mold -run
-# mold -run ./scripts/start.sh
+mise start
```
-You can start a web client by doing the following:
+You can start a web client by doing the following in another terminal:
```bash
# if you do not have yarn yet and have a modern Node.js:
corepack enable
# clone the web client and run it:
-git clone --recursive https://github.com/revoltchat/revite
-cd revite
-yarn
-yarn build:deps
-echo "VITE_API_URL=http://local.revolt.chat:14702" > .env.local
-yarn dev --port 14701
+git clone --recursive https://github.com/stoatchat/for-web stoat-web
+cd stoat-web
+# refer to stoat-web/README.md for startup, creating an account and loging in
```
-Then go to http://local.revolt.chat:14701 to create an account/login.
-
When signing up, go to http://localhost:14080 to find confirmation/password reset emails.
+To stop all services, hit (CTRL + c) in the terminal you ran `mise start` and run `mise docker:stop`
+
+
## Deployment Guide
### Cutting new crate releases
@@ -196,14 +195,14 @@ Tag and push a new release by running:
just release
```
-If you have bumped the crate versions, proceed to [GitHub releases](https://github.com/revoltchat/backend/releases/new) to create a changelog.
+If you have bumped the crate versions, proceed to [GitHub releases](https://github.com/stoatchat/stoatchat/releases/new) to create a changelog.
## Testing
First, start the required services:
```sh
-docker compose -f docker-compose.db.yml up -d
+docker compose up -d
```
Now run tests for whichever database:
@@ -215,6 +214,6 @@ TEST_DB=MONGODB cargo nextest run
## License
-The Revolt backend is generally licensed under the [GNU Affero General Public License v3.0](https://github.com/revoltchat/backend/blob/master/LICENSE).
+The Stoat backend is generally licensed under the [GNU Affero General Public License v3.0](https://github.com/stoatchat/stoatchat/blob/main/LICENSE).
**Individual crates may supply their own licenses!**
diff --git a/Revolt.toml b/Revolt.toml
index 33e044348..7398ed53a 100644
--- a/Revolt.toml
+++ b/Revolt.toml
@@ -15,7 +15,7 @@ host = "127.0.0.1"
[hosts]
# Web locations of various services
# Defaults assume all services are reverse-proxied
-# See https://github.com/revoltchat/self-hosted/blob/master/Caddyfile
+# See https://github.com/stoatchat/self-hosted/blob/main/Caddyfile
#
# Remember to change these to https/wss where appropriate in production!
app = "http://local.revolt.chat:14701"
@@ -26,6 +26,11 @@ january = "http://local.revolt.chat:14705"
voso_legacy = ""
voso_legacy_ws = ""
+# Public urls for livekit nodes
+# each entry here should have a corresponding entry under `api.livekit.nodes`
+[hosts.livekit]
+worldwide = "ws://local.revolt.chat:14706"
+
[api]
[api.smtp]
@@ -40,6 +45,18 @@ port = 14025
use_tls = false
use_starttls = false
+[api.livekit]
+
+# Config for livekit nodes
+# Make sure to change the secret when deploying
+# The key and secret should match the values livekit is using
+[api.livekit.nodes.worldwide]
+url = "http://livekit"
+lat = 0.0
+lon = 0.0
+key = "worldwide"
+secret = "ZjCofRlfm6GGtjlifmNpCDkcQbEIIVC0"
+
[files.s3]
# S3 protocol endpoint
endpoint = "http://127.0.0.1:14009"
diff --git a/STYLE_GUIDE.md b/STYLE_GUIDE.md
deleted file mode 100644
index 7999b388a..000000000
--- a/STYLE_GUIDE.md
+++ /dev/null
@@ -1,73 +0,0 @@
-# Code Style Guide
-
-Beyond using Cargo format and Clippy, there are some specific code style guidelines laid out in this document for different parts of the project.
-
-## Writing Style
-
-- Shorten "identifier" to "Id" with that exact casing, i.e. Server Id.
-
-## `core/database` crate
-
-w.r.t. `model.rs` files
-
-- All struct definitions must be commented.
- ```rust
- /// Server
- pub struct Server {
- /// Name of the server
- pub name: String,
- ```
-- Struct definitions should not include derives unless necessary (if additional traits such as Hash are required) and instead use `auto_derived!` and `auto_derived_partial!`.
- ```rust
- auto_derived_partial!(
- /// Server
- pub struct Server { .. },
- "PartialServer"
- );
- ```
-- `auto_derived!` macro accepts multiple entries and should be used as such:
-
- ```rust
- auto_derived!(
- /// Optional fields on server object
- pub enum FieldsServer { .. }
-
- /// Optional fields on server object
- pub enum FieldsRole { .. }
- );
- ```
-
-- If special serialisation conditions are required, such as checking if a boolean is false, use the existing definitions for these functions from the crate root:
- ```rust
- #[serde(skip_serializing_if = "crate::if_false", default)]
- ```
-- `impl` blocks may be defined below the struct definitions and should be ordered in the same order of definition. Methods in the block must follow the same guidelines as traits where-in: methods are ordered in terms of CRUD, there are empty line breaks, and methods are commented.
-
-w.r.t. `ops` module for models
-
-- All traits must use a the name format `AbstractPlural` where Plural is the plural form of the collection. e.g. Servers
-- Traits defined must follow these guidelines:
-
- - Methods are ordered in terms of CRUD, create-read-update-delete ordering.
-
- ```rust
- #[async_trait]
- pub trait AbstractServerMembers: Sync + Send {
- /// Insert a new server member into the database
- async fn insert_member(&self, member: &Member) -> Result<()>;
-
- /// Fetch a server member by their id
- async fn fetch_member(&self, server_id: &str, user_id: &str) -> Result;
-
- /// Update information for a server member
- async fn update_member(&self, .. ) -> Result<()>;
-
- /// Delete a server member by their id
- async fn delete_member(&self, id: &MemberCompositeKey) -> Result<()>;
- }
- ```
-
- - There should be an empty line break between each method declaration.
- - All methods must have an appropriate comment.
-
-- When implementing the trait defined in `ops.rs` with each driver, the method declaration style should be the same for ease of searching: same ordering, same comments, same line breaks.
diff --git a/compose.yml b/compose.yml
index 89b295940..b7e73689d 100644
--- a/compose.yml
+++ b/compose.yml
@@ -8,18 +8,33 @@ services:
# MongoDB
database:
image: mongo
+ command: mongod --replSet rs0
ports:
- "27017:27017"
volumes:
- ./.data/db:/data/db
+ extra_hosts:
+ - "host.docker.internal:host-gateway"
+ healthcheck:
+ test: echo "try { rs.status() } catch (err) { rs.initiate({_id:'rs0',members:[{_id:0,host:'127.0.0.1:27017'}]}) }" | mongosh --port 27017 --quiet
+ interval: 5s
+ timeout: 30s
+ start_period: 0s
+ start_interval: 1s
+ retries: 30
+ ulimits:
+ nofile:
+ soft: 65536
+ hard: 65536
# MinIO
minio:
- image: minio/minio
- command: server /data
+ image: firstfinger/minio:latest
+ #command: server /data
environment:
MINIO_ROOT_USER: minioautumn
MINIO_ROOT_PASSWORD: minioautumn
+ MINIO_REGION: minio
volumes:
- ./.data/minio:/data
ports:
@@ -40,7 +55,7 @@ services:
# Rabbit
rabbit:
- image: rabbitmq:3-management
+ image: rabbitmq:4-management
environment:
RABBITMQ_DEFAULT_USER: rabbituser
RABBITMQ_DEFAULT_PASS: rabbitpass
@@ -55,7 +70,7 @@ services:
# Mock SMTP server
maildev:
- image: soulteary/maildev
+ image: maildev/maildev
ports:
- "14025:25"
- "14080:8080"
@@ -64,3 +79,10 @@ services:
MAILDEV_WEB_PORT: 8080
MAILDEV_INCOMING_USER: smtp
MAILDEV_INCOMING_PASS: smtp
+
+ livekit:
+ image: ghcr.io/stoatchat/livekit-server:v1.9.13
+ command: --config /etc/livekit.yml
+ network_mode: "host"
+ volumes:
+ - ./livekit.yml:/etc/livekit.yml
diff --git a/crates/bonfire/Cargo.toml b/crates/bonfire/Cargo.toml
index e447825d4..b2687266a 100644
--- a/crates/bonfire/Cargo.toml
+++ b/crates/bonfire/Cargo.toml
@@ -1,48 +1,46 @@
[package]
name = "revolt-bonfire"
-version = "0.8.8"
+version = "0.13.7"
license = "AGPL-3.0-or-later"
edition = "2021"
+publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# util
-log = "*"
-sentry = "0.31.5"
-lru = "0.7.6"
-ulid = "0.5.0"
-once_cell = "1.9.0"
-redis-kiss = "0.1.4"
-lru_time_cache = "0.11.11"
-async-channel = "2.3.1"
+log = { workspace = true }
+sentry = { workspace = true }
+lru = { workspace = true }
+ulid = { workspace = true }
+once_cell = { workspace = true }
+redis-kiss = { workspace = true }
+lru_time_cache = { workspace = true }
+async-channel = { workspace = true }
# parsing
-querystring = "1.1.0"
+querystring = { workspace = true }
+regex = { workspace = true }
# serde
-bincode = "1.3.3"
-serde_json = "1.0.79"
-rmp-serde = "1.0.0"
-serde = "1.0.136"
+bincode = { workspace = true }
+serde_json = { workspace = true }
+rmp-serde = { workspace = true }
+serde = { workspace = true }
# async
-futures = "0.3.21"
-async-tungstenite = { version = "0.17.0", features = ["async-std-runtime"] }
-async-std = { version = "1.8.0", features = [
- "tokio1",
- "tokio02",
- "attributes",
-] }
+futures = { workspace = true }
+async-tungstenite = { workspace = true, features = ["async-std-runtime"] }
+async-std = { workspace = true }
# core
-authifier = { version = "1.0.15" }
-revolt-result = { path = "../core/result" }
-revolt-models = { path = "../core/models" }
-revolt-config = { path = "../core/config" }
-revolt-database = { path = "../core/database" }
-revolt-permissions = { version = "0.8.8", path = "../core/permissions" }
-revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
+authifier = { workspace = true }
+revolt-result = { workspace = true }
+revolt-models = { workspace = true }
+revolt-config = { workspace = true }
+revolt-database = { workspace = true, features = ["voice"] }
+revolt-permissions = { workspace = true }
+revolt-presence = { workspace = true, features = ["redis-is-patched"] }
# redis
-fred = { version = "8.0.1", features = ["subscriber-client"] }
+fred = { workspace = true, features = ["subscriber-client"] }
diff --git a/crates/bonfire/Dockerfile b/crates/bonfire/Dockerfile
index d1829137b..9a608d930 100644
--- a/crates/bonfire/Dockerfile
+++ b/crates/bonfire/Dockerfile
@@ -1,5 +1,5 @@
# Build Stage
-FROM ghcr.io/revoltchat/base:latest AS builder
+FROM ghcr.io/stoatchat/base:latest AS builder
FROM debian:12 AS debian
# Bundle Stage
diff --git a/crates/bonfire/src/config.rs b/crates/bonfire/src/config.rs
index 5642d3baa..d70a63205 100644
--- a/crates/bonfire/src/config.rs
+++ b/crates/bonfire/src/config.rs
@@ -1,9 +1,15 @@
use async_tungstenite::tungstenite::{handshake, Message};
use futures::channel::oneshot::Sender;
+use once_cell::sync::Lazy;
+use regex::Regex;
use revolt_database::events::client::ReadyPayloadFields;
use revolt_result::{create_error, Result};
use serde::{Deserialize, Serialize};
+/// matches either a single word ie "users" or a key and value ie "settings[notifications]"
+static READY_PAYLOAD_FIELD_REGEX: Lazy =
+ Lazy::new(|| Regex::new(r#"^(\w+)(?:\[(\S+)\])?$"#).unwrap());
+
/// Enumeration of supported protocol formats
#[derive(Debug)]
pub enum ProtocolFormat {
@@ -17,6 +23,7 @@ pub struct ProtocolConfiguration {
protocol_version: i32,
format: ProtocolFormat,
session_token: Option,
+ ready_payload_fields: ReadyPayloadFields,
}
impl ProtocolConfiguration {
@@ -25,11 +32,13 @@ impl ProtocolConfiguration {
protocol_version: i32,
format: ProtocolFormat,
session_token: Option,
+ ready_payload_fields: ReadyPayloadFields,
) -> Self {
Self {
protocol_version,
format,
session_token,
+ ready_payload_fields,
}
}
@@ -86,14 +95,8 @@ impl ProtocolConfiguration {
}
/// Get ready payload fields
- pub fn get_ready_payload_fields(&self) -> Vec {
- vec![
- ReadyPayloadFields::Users,
- ReadyPayloadFields::Servers,
- ReadyPayloadFields::Channels,
- ReadyPayloadFields::Members,
- ReadyPayloadFields::Emoji,
- ]
+ pub fn get_ready_payload_fields(&self) -> &ReadyPayloadFields {
+ &self.ready_payload_fields
}
}
@@ -124,6 +127,23 @@ impl handshake::server::Callback for WebsocketHandshakeCallback {
let mut protocol_version = 1;
let mut format = ProtocolFormat::Json;
let mut session_token = None;
+ let mut ready_payload_fields = if params.iter().any(|(k, _)| *k == "ready") {
+ // If they pass the ready field, set all fields to false
+
+ ReadyPayloadFields {
+ users: false,
+ servers: false,
+ channels: false,
+ members: false,
+ emojis: false,
+ voice_states: false,
+ user_settings: Vec::new(),
+ channel_unreads: false,
+ policy_changes: false,
+ }
+ } else {
+ ReadyPayloadFields::default()
+ };
// Parse and map parameters from key-value to known variables.
for (key, value) in params {
@@ -139,6 +159,31 @@ impl handshake::server::Callback for WebsocketHandshakeCallback {
_ => {}
},
"token" => session_token = Some(value.into()),
+ "ready" => {
+ // Re-enable all the fields the client specifies
+ if let Some(captures) = READY_PAYLOAD_FIELD_REGEX.captures(value) {
+ if let Some(field) = captures.get(0) {
+ match field.as_str() {
+ "users" => ready_payload_fields.users = true,
+ "servers" => ready_payload_fields.servers = true,
+ "channels" => ready_payload_fields.channels = true,
+ "members" => ready_payload_fields.members = true,
+ "emojis" => ready_payload_fields.emojis = true,
+ "voice_states" => ready_payload_fields.voice_states = true,
+ "channel_unreads" => ready_payload_fields.channel_unreads = true,
+ "user_settings" => {
+ if let Some(subkey) = captures.get(1) {
+ ready_payload_fields
+ .user_settings
+ .push(subkey.as_str().to_string());
+ }
+ }
+ "policy_changes" => ready_payload_fields.policy_changes = true,
+ _ => {}
+ }
+ }
+ }
+ }
_ => {}
}
}
@@ -151,6 +196,7 @@ impl handshake::server::Callback for WebsocketHandshakeCallback {
protocol_version,
format,
session_token,
+ ready_payload_fields,
})
.is_ok()
{
diff --git a/crates/bonfire/src/events/impl.rs b/crates/bonfire/src/events/impl.rs
index 160e7c6c0..96a0f2ab0 100644
--- a/crates/bonfire/src/events/impl.rs
+++ b/crates/bonfire/src/events/impl.rs
@@ -1,9 +1,11 @@
-use std::collections::HashSet;
+use std::collections::{HashMap, HashSet};
use futures::future::join_all;
+use redis_kiss::AsyncCommands;
use revolt_database::{
events::client::{EventV1, ReadyPayloadFields},
util::permissions::DatabasePermissionQuery,
+ voice::{get_channel_voice_state, UserVoiceChannel},
Channel, Database, Member, MemberCompositeKey, Presence, RelationshipStatus,
};
use revolt_models::v0;
@@ -17,8 +19,9 @@ use super::state::{Cache, State};
impl Cache {
/// Check whether the current user can view a channel
pub async fn can_view_channel(&self, db: &Database, channel: &Channel) -> bool {
+ #[allow(deprecated)]
match &channel {
- Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
+ Channel::TextChannel { server, .. } => {
let member = self.members.get(server);
let server = self.servers.get(server);
let mut query =
@@ -95,21 +98,23 @@ impl State {
pub async fn generate_ready_payload(
&mut self,
db: &Database,
- fields: Vec,
+ fields: &ReadyPayloadFields,
) -> Result {
let user = self.clone_user();
self.cache.is_bot = user.bot.is_some();
// Fetch pending policy changes.
- let policy_changes = if user.bot.is_some() {
- vec![]
+ let policy_changes = if user.bot.is_some() || !fields.policy_changes {
+ None
} else {
- db.fetch_policy_changes()
- .await?
- .into_iter()
- .filter(|policy| policy.created_time > user.last_acknowledged_policy_change)
- .map(Into::into)
- .collect()
+ Some(
+ db.fetch_policy_changes()
+ .await?
+ .into_iter()
+ .filter(|policy| policy.created_time > user.last_acknowledged_policy_change)
+ .map(Into::into)
+ .collect(),
+ )
};
// Find all relationships to the user.
@@ -120,12 +125,7 @@ impl State {
.unwrap_or_default();
// Fetch all memberships with their corresponding servers.
- let members: Vec = db.fetch_all_memberships(&user.id).await?;
- self.cache.members = members
- .iter()
- .cloned()
- .map(|x| (x.id.server.clone(), x))
- .collect();
+ let mut members: Vec = db.fetch_all_memberships(&user.id).await?;
let server_ids: Vec = members.iter().map(|x| x.id.server.clone()).collect();
let servers = db.fetch_servers(&server_ids).await?;
@@ -154,6 +154,55 @@ impl State {
}
}
+ let voice_states = if fields.voice_states {
+ let mut voice_state_server_members: HashMap> = HashMap::new();
+
+ // fetch voice states for all the channels we can see
+ let mut voice_states = Vec::new();
+
+ for channel in channels.iter().filter(|c| {
+ matches!(
+ c,
+ Channel::DirectMessage { .. }
+ | Channel::Group { .. }
+ | Channel::TextChannel { voice: Some(_), .. }
+ )
+ }) {
+ if let Ok(Some(voice_state)) =
+ get_channel_voice_state(&UserVoiceChannel::from_channel(channel)).await
+ {
+ if let Some(server) = channel.server() {
+ let set = voice_state_server_members
+ .entry(server.to_string())
+ .or_default();
+
+ for participant in &voice_state.participants {
+ user_ids.insert(participant.id.clone());
+ set.insert(participant.id.clone());
+ }
+ } else {
+ for participant in &voice_state.participants {
+ user_ids.insert(participant.id.clone());
+ }
+ }
+
+ voice_states.push(voice_state);
+ }
+ }
+
+ // Fetch all the members for for the participants who are in a server
+ for (server, user_ids) in voice_state_server_members {
+ let user_ids = user_ids.into_iter().collect::>();
+ let voice_members = db.fetch_members(&server, &user_ids).await?;
+
+ members.extend(voice_members);
+ }
+
+ Some(voice_states)
+ } else {
+ None
+ };
+
// Fetch presence data for known users.
let online_ids = filter_online(&user_ids.iter().cloned().collect::>()).await;
@@ -167,8 +216,14 @@ impl State {
)
.await?;
+ self.cache.members = members
+ .iter()
+ .cloned()
+ .map(|x| (x.id.server.clone(), x))
+ .collect();
+
// Fetch customisations.
- let emojis = if fields.contains(&ReadyPayloadFields::Emoji) {
+ let emojis = if fields.emojis {
Some(
db.fetch_emoji_by_parent_ids(
&servers
@@ -176,25 +231,34 @@ impl State {
.map(|x| x.id.to_string())
.collect::>(),
)
- .await?,
+ .await?
+ .into_iter()
+ .map(|emoji| emoji.into())
+ .collect(),
)
} else {
None
};
// Fetch user settings
- let user_settings = if let Some(ReadyPayloadFields::UserSettings(keys)) = fields
- .iter()
- .find(|e| matches!(e, ReadyPayloadFields::UserSettings(_)))
- {
- Some(db.fetch_user_settings(&user.id, keys).await?)
+ let user_settings = if !fields.user_settings.is_empty() {
+ Some(
+ db.fetch_user_settings(&user.id, &fields.user_settings)
+ .await?,
+ )
} else {
None
};
// Fetch channel unreads
- let channel_unreads = if fields.contains(&ReadyPayloadFields::ChannelUnreads) {
- Some(db.fetch_unreads(&user.id).await?)
+ let channel_unreads = if fields.channel_unreads {
+ Some(
+ db.fetch_unreads(&user.id)
+ .await?
+ .into_iter()
+ .map(|unread| unread.into())
+ .collect(),
+ )
} else {
None
};
@@ -241,30 +305,27 @@ impl State {
}
Ok(EventV1::Ready {
- users: if fields.contains(&ReadyPayloadFields::Users) {
- Some(users)
- } else {
- None
- },
- servers: if fields.contains(&ReadyPayloadFields::Servers) {
+ users: if fields.users { Some(users) } else { None },
+ servers: if fields.servers {
Some(servers.into_iter().map(Into::into).collect())
} else {
None
},
- channels: if fields.contains(&ReadyPayloadFields::Channels) {
+ channels: if fields.channels {
Some(channels.into_iter().map(Into::into).collect())
} else {
None
},
- members: if fields.contains(&ReadyPayloadFields::Members) {
+ members: if fields.members {
Some(members.into_iter().map(Into::into).collect())
} else {
None
},
- emojis: emojis.map(|vec| vec.into_iter().map(Into::into).collect()),
+ voice_states,
+ emojis,
user_settings,
- channel_unreads: channel_unreads.map(|vec| vec.into_iter().map(Into::into).collect()),
+ channel_unreads,
policy_changes,
})
@@ -279,19 +340,14 @@ impl State {
let id = &id.to_string();
for (channel_id, channel) in &self.cache.channels {
- match channel {
- Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
- if server == id {
- channel_ids.insert(channel_id.clone());
-
- if self.cache.can_view_channel(db, channel).await {
- added_channels.push(channel_id.clone());
- } else {
- removed_channels.push(channel_id.clone());
- }
- }
+ if channel.server() == Some(id) {
+ channel_ids.insert(channel_id.clone());
+
+ if self.cache.can_view_channel(db, channel).await {
+ added_channels.push(channel_id.clone());
+ } else {
+ removed_channels.push(channel_id.clone());
}
- _ => {}
}
}
@@ -346,6 +402,11 @@ impl State {
/// Push presence change to the user and all associated server topics
pub async fn broadcast_presence_change(&self, target: bool) {
+ let config = revolt_config::config().await;
+ if config.disable_events_dont_use {
+ return;
+ }
+
if if let Some(status) = &self.cache.users.get(&self.cache.user_id).unwrap().status {
status.presence != Some(Presence::Invisible)
} else {
@@ -459,6 +520,7 @@ impl State {
server,
channels,
emojis: _,
+ voice_states: _,
} => {
self.insert_subscription(id.clone()).await;
diff --git a/crates/bonfire/src/events/state.rs b/crates/bonfire/src/events/state.rs
index eae398019..daa22269a 100644
--- a/crates/bonfire/src/events/state.rs
+++ b/crates/bonfire/src/events/state.rs
@@ -1,7 +1,5 @@
use std::{
- collections::{HashMap, HashSet},
- sync::Arc,
- time::Duration,
+ collections::{HashMap, HashSet}, num::NonZeroUsize, sync::Arc, time::Duration
};
use async_std::sync::{Mutex, RwLock};
@@ -57,7 +55,7 @@ impl Default for Cache {
members: Default::default(),
servers: Default::default(),
- seen_events: LruCache::new(20),
+ seen_events: LruCache::new(NonZeroUsize::new(20).unwrap()),
}
}
}
diff --git a/crates/bonfire/src/websocket.rs b/crates/bonfire/src/websocket.rs
index 913245e9c..ee5512896 100644
--- a/crates/bonfire/src/websocket.rs
+++ b/crates/bonfire/src/websocket.rs
@@ -5,7 +5,7 @@ use authifier::AuthifierEvent;
use fred::{
error::RedisErrorKind,
interfaces::{ClientLike, EventInterface, PubsubInterface},
- types::RedisConfig,
+ types::{ReconnectPolicy, RedisConfig},
};
use futures::{
channel::oneshot,
@@ -13,7 +13,7 @@ use futures::{
stream::{SplitSink, SplitStream},
FutureExt, SinkExt, StreamExt, TryStreamExt,
};
-use redis_kiss::{PayloadType, REDIS_PAYLOAD_TYPE, REDIS_URI};
+use redis_kiss::{get_connection, AsyncCommands, PayloadType, REDIS_PAYLOAD_TYPE, REDIS_URI};
use revolt_config::report_internal_error;
use revolt_database::{
events::{client::EventV1, server::ClientMessage},
@@ -32,6 +32,7 @@ use sentry::Level;
use crate::config::{ProtocolConfiguration, WebsocketHandshakeCallback};
use crate::events::state::{State, SubscriptionStateChange};
+use revolt_models::v0;
type WsReader = SplitStream>;
type WsWriter = SplitSink, async_tungstenite::tungstenite::Message>;
@@ -128,6 +129,14 @@ pub async fn client(db: &'static Database, stream: TcpStream, addr: SocketAddr)
return;
}
+ let slowmodes = fetch_user_slowmodes(&user_id).await.unwrap_or_default();
+ if !slowmodes.is_empty() {
+ let event = EventV1::UserSlowmodes { slowmodes };
+ if report_internal_error!(write.send(config.encode(&event)).await).is_err() {
+ return;
+ }
+ }
+
// Create presence session.
let (first_session, session_id) = create_session(&user_id, 0).await;
@@ -218,10 +227,16 @@ async fn listener(
kill_signal_r: async_channel::Receiver<()>,
write: &Mutex,
) {
- let redis_config = RedisConfig::from_url(&REDIS_URI).unwrap();
- let subscriber = match report_internal_error!(
- fred::types::Builder::from_config(redis_config).build_subscriber_client()
- ) {
+ let stoat_config = revolt_config::config().await;
+ let url = stoat_config
+ .database
+ .redis_pubsub
+ .unwrap_or(REDIS_URI.to_string());
+
+ let redis_config = RedisConfig::from_url(&url).unwrap();
+ let mut builder = fred::types::Builder::from_config(redis_config);
+ builder.set_policy(ReconnectPolicy::new_exponential(8, 100, 30_000, 2));
+ let subscriber = match report_internal_error!(builder.build_subscriber_client()) {
Ok(subscriber) => subscriber,
Err(_) => return,
};
@@ -230,16 +245,21 @@ async fn listener(
return;
}
+ // Let Fred automatically re-subscribe to tracked channels on reconnect.
+ subscriber.manage_subscriptions();
+
// Handle Redis connection dropping
let (clean_up_s, clean_up_r) = async_channel::bounded(1);
let clean_up_s = Arc::new(Mutex::new(clean_up_s));
subscriber.on_error(move |err| {
+ warn!("Redis subscriber error: {:?}", err);
if let RedisErrorKind::Canceled = err.kind() {
let clean_up_s = clean_up_s.clone();
spawn(async move {
clean_up_s.lock().await.send(()).await.ok();
});
}
+ // Transient errors (IO, timeout) are handled by the reconnect policy.
Ok(())
});
@@ -422,6 +442,8 @@ async fn worker(
mut read: WsReader,
write: &Mutex,
) {
+ let revolt_config = revolt_config::config().await;
+
loop {
let t1 = read.try_next().fuse();
let t2 = kill_signal_r.recv().fuse();
@@ -458,6 +480,10 @@ async fn worker(
match payload {
ClientMessage::BeginTyping { channel } => {
+ if revolt_config.disable_events_dont_use {
+ continue;
+ }
+
if !subscribed.read().await.contains(&channel) {
continue;
}
@@ -470,6 +496,10 @@ async fn worker(
.await;
}
ClientMessage::EndTyping { channel } => {
+ if revolt_config.disable_events_dont_use {
+ continue;
+ }
+
if !subscribed.read().await.contains(&channel) {
continue;
}
@@ -507,3 +537,42 @@ async fn worker(
}
}
}
+
+async fn fetch_user_slowmodes(user_id: &str) -> Option> {
+ let mut conn = get_connection().await.ok()?.into_inner();
+ let idx_key = format!("slowmode_idx:{}", user_id);
+
+ let channel_ids: Vec = conn.smembers(&idx_key).await.unwrap_or_default();
+ if channel_ids.is_empty() {
+ return Some(vec![]);
+ }
+
+ // Bulk fetch all TTLs in one round trip
+ let mut pipe = redis_kiss::redis::pipe();
+ for channel_id in &channel_ids {
+ pipe.ttl(format!("slowmode:{}:{}", user_id, channel_id));
+ }
+ let ttls: Vec = pipe.query_async(&mut conn).await.unwrap_or_default();
+
+ // Partition into alive/expired in one pass
+ let mut slowmodes = vec![];
+ let mut expired = vec![];
+ for (channel_id, ttl) in channel_ids.iter().zip(ttls.iter()) {
+ if *ttl > 0 {
+ slowmodes.push(v0::ChannelSlowmode {
+ channel_id: channel_id.clone(),
+ duration: *ttl as u64,
+ retry_after: *ttl as u64,
+ });
+ } else {
+ expired.push(channel_id.as_str());
+ }
+ }
+
+ // Bulk remove all expired members in one SREM call
+ if !expired.is_empty() {
+ conn.srem::<_, _, ()>(&idx_key, expired).await.ok();
+ }
+
+ Some(slowmodes)
+}
diff --git a/crates/core/coalesced/Cargo.toml b/crates/core/coalesced/Cargo.toml
new file mode 100644
index 000000000..1c8102adb
--- /dev/null
+++ b/crates/core/coalesced/Cargo.toml
@@ -0,0 +1,28 @@
+[package]
+name = "revolt-coalesced"
+version = "0.13.7"
+edition = "2021"
+license = "MIT"
+authors = ["Paul Makles ", "Zomatree "]
+description = "Revolt Backend: Coalescion service"
+repository = "https://github.com/stoatchat/stoatchat"
+
+[features]
+tokio = ["dep:tokio"]
+queue = ["dep:indexmap"]
+cache = ["dep:lru"]
+
+default = ["tokio"]
+
+[dependencies]
+tokio = { workspace = true, features = ["sync"], optional = true }
+indexmap = { workspace = true, optional = true }
+lru = { workspace = true, optional = true }
+
+[dev-dependencies]
+tokio = { workspace = true, features = [
+ "rt",
+ "rt-multi-thread",
+ "macros",
+ "time",
+] }
diff --git a/crates/core/coalesced/LICENSE b/crates/core/coalesced/LICENSE
new file mode 100644
index 000000000..7c2815b9f
--- /dev/null
+++ b/crates/core/coalesced/LICENSE
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) 2024 Pawel Makles
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/crates/core/coalesced/src/config.rs b/crates/core/coalesced/src/config.rs
new file mode 100644
index 000000000..23da16989
--- /dev/null
+++ b/crates/core/coalesced/src/config.rs
@@ -0,0 +1,24 @@
+#[derive(Clone, PartialEq, Eq, Debug)]
+/// Config values for [`CoalescionService`].
+pub struct CoalescionServiceConfig {
+ /// How many tasks are running at once
+ pub max_concurrent: Option,
+ /// Whether to queue tasks once `max_concurrent` is reached
+ #[cfg(feature = "queue")]
+ pub queue_requests: bool,
+ /// Max amount of tasks in the buffer queue
+ #[cfg(feature = "queue")]
+ pub max_queue: Option,
+}
+
+impl Default for CoalescionServiceConfig {
+ fn default() -> Self {
+ Self {
+ max_concurrent: Some(100),
+ #[cfg(feature = "queue")]
+ queue_requests: true,
+ #[cfg(feature = "queue")]
+ max_queue: Some(100)
+ }
+ }
+}
diff --git a/crates/core/coalesced/src/error.rs b/crates/core/coalesced/src/error.rs
new file mode 100644
index 000000000..6be897858
--- /dev/null
+++ b/crates/core/coalesced/src/error.rs
@@ -0,0 +1,27 @@
+use std::fmt;
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
+/// Coalescion service error.
+pub enum Error {
+ /// Failed to receive the actions return from the channel for unknown reason
+ RecvError,
+ /// Reached the `max_concurrent` amount of actions running at once and could not queue the action
+ MaxConcurrent,
+ /// Reached the `max_queue` amount of actions in the queue
+ MaxQueue,
+ /// Failed to downcast the type to the current type being returned, this will be most likely an ID collision
+ DowncastError,
+}
+
+impl fmt::Display for Error {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Error::RecvError => write!(f, "Unable to receive data from the channel"),
+ Error::MaxConcurrent => write!(f, "Max number of tasks running at once"),
+ Error::MaxQueue => write!(f, "Max number of tasks in queue"),
+ Error::DowncastError => write!(f, "Failed to downcast type, possible key collision with different types")
+ }
+ }
+}
+
+impl std::error::Error for Error {}
diff --git a/crates/core/coalesced/src/lib.rs b/crates/core/coalesced/src/lib.rs
new file mode 100644
index 000000000..89839e23a
--- /dev/null
+++ b/crates/core/coalesced/src/lib.rs
@@ -0,0 +1,39 @@
+//! # Coalesced
+//!
+//! Coalescion service to group, caching and queue duplicate actions.
+//! useful for deduplicating web requests, database lookups and other similar resource
+//! intensive or rate-limited actions.
+//!
+//! ## Features
+//! - `tokio`: Uses tokio for the async backend, this is currently the only backend.
+//! - `queue`: Whether to support queueing requests to only allow X amount of actions running at once.
+//! - `cache`: Whether to cache the actions results for future actions with the same id, uses an LRU cache internally.
+//!
+//! [`CoalescionService`] uses both [`Arc`] and [`RwLock`] internally and can be cheaply cloned to
+//! use in your codebase.
+//!
+//! It is common practice to wrap the service and in your own which delegates the executions to ensure all ids are tracked in one location across your codebase.
+//!
+//! All values are stored using [`Any`] and must be [`'static`] + [`Send`] + [`Sync`], if there is an id mismatch
+//! and a type is wrong the library will return an error, values returned from the service are also
+//! wrapped in an [`Arc`] as they are shared to each duplicate action.
+//!
+//! ## Example:
+//! ```rs
+//! use revolt_coalesced::CoalescionService;
+//!
+//! let service = CoalescionService::new();
+//!
+//! let user_id = "my_user_id";
+//! let user = service.execute(user_id, || async move {
+//! database.fetch_user(user_id).await.unwrap()
+//! }).await;
+//! ```
+
+mod config;
+mod error;
+mod service;
+
+pub use config::CoalescionServiceConfig;
+pub use error::Error;
+pub use service::CoalescionService;
diff --git a/crates/core/coalesced/src/service.rs b/crates/core/coalesced/src/service.rs
new file mode 100644
index 000000000..aadadc8a0
--- /dev/null
+++ b/crates/core/coalesced/src/service.rs
@@ -0,0 +1,208 @@
+use std::{any::Any, collections::HashMap, fmt::Debug, future::Future, hash::Hash, sync::Arc};
+
+use tokio::sync::{
+ watch::{channel as watch_channel, Receiver},
+ RwLock,
+};
+
+#[cfg(feature = "cache")]
+use lru::LruCache;
+
+#[cfg(feature = "queue")]
+use indexmap::IndexMap;
+
+use crate::{CoalescionServiceConfig, Error};
+
+#[derive(Debug, Clone)]
+#[allow(clippy::type_complexity)]
+/// # Coalescion service
+///
+/// See module description for example usage.
+pub struct CoalescionService {
+ config: Arc,
+ watchers: Arc, Error>>>>>>,
+ #[cfg(feature = "queue")]
+ queue: Arc, Error>>>>>>,
+ #[cfg(feature = "cache")]
+ cache: Option>>>>,
+}
+
+impl CoalescionService {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ pub fn from_config(config: CoalescionServiceConfig) -> Self {
+ Self {
+ config: Arc::new(config),
+ watchers: Arc::new(RwLock::new(HashMap::new())),
+ #[cfg(feature = "queue")]
+ queue: Arc::new(RwLock::new(IndexMap::new())),
+ #[cfg(feature = "cache")]
+ cache: None,
+ }
+ }
+
+ #[cfg(feature = "cache")]
+ pub fn from_cache(
+ config: CoalescionServiceConfig,
+ cache: LruCache>,
+ ) -> Self {
+ Self {
+ cache: Some(Arc::new(Mutex::new(cache))),
+ ..Self::from_config(config)
+ }
+ }
+
+ async fn wait_for(
+ &self,
+ mut receiver: Receiver, Error>>>,
+ ) -> Result, Error> {
+ receiver
+ .wait_for(|v| v.is_some())
+ .await
+ .map_err(|_| Error::RecvError)
+ .and_then(|r| r.clone().unwrap())
+ .and_then(|arc| Arc::downcast(arc).map_err(|_| Error::DowncastError))
+ }
+
+ async fn insert_and_execute<
+ Value: Send + Sync + 'static,
+ F: FnOnce() -> Fut,
+ Fut: Future,
+ >(
+ &self,
+ id: Id,
+ func: F,
+ ) -> Result, Error> {
+ let (send, recv) = watch_channel(None);
+
+ self.watchers.write().await.insert(id.clone(), recv);
+
+ let value = Ok(Arc::new(func().await));
+
+ send.send_modify(|opt| {
+ opt.replace(value.clone().map(|v| v as Arc));
+ });
+
+ #[cfg(feature = "cache")]
+ if let Some(cache) = self.cache.as_ref() {
+ if let Ok(value) = &value {
+ cache.lock().await.push(id.clone(), value.clone());
+ }
+ };
+
+ self.watchers.write().await.remove(&id);
+
+ value
+ }
+
+ /// Coalesces an function, the actual function may not run if one with the same id is already running,
+ /// queued to be ran, or cached, the id should be globally unique for this specific action.
+ pub async fn execute<
+ Value: Send + Sync + 'static,
+ F: FnOnce() -> Fut,
+ Fut: Future,
+ >(
+ &self,
+ id: Id,
+ func: F,
+ ) -> Result, Error> {
+ #[cfg(feature = "cache")]
+ if let Some(cache) = self.cache.as_ref() {
+ if let Some(value) = cache.lock().await.get(&id) {
+ return Arc::downcast::(value.clone()).map_err(|_| Error::DowncastError);
+ }
+ };
+
+ let (receiver, length) = {
+ let watchers = self.watchers.read().await;
+ let length = watchers.len();
+
+ (watchers.get(&id).cloned(), length)
+ };
+
+ if let Some(receiver) = receiver {
+ self.wait_for(receiver).await
+ } else {
+ match self.config.max_concurrent {
+ Some(max_concurrent) if length >= max_concurrent => {
+ #[cfg(feature = "queue")]
+ if self.config.queue_requests {
+ let (receiver, length) = {
+ let queue = self.queue.read().await;
+
+ (queue.get(&id).cloned(), queue.len())
+ };
+
+ if let Some(receiver) = receiver {
+ return self.wait_for(receiver).await;
+ } else {
+ if self
+ .config
+ .max_queue
+ .is_some_and(|max_queue| max_queue >= length)
+ {
+ return Err(Error::MaxQueue);
+ };
+
+ let (send, recv) = watch_channel(None);
+
+ self.queue.write().await.insert(id.clone(), recv);
+
+ loop {
+ let length = self.watchers.read().await.len();
+
+ if length < max_concurrent {
+ let first_key = {
+ let queue = self.queue.read().await;
+ queue.first().map(|v| v.0).cloned()
+ };
+
+ if first_key == Some(id.clone()) {
+ self.queue.write().await.shift_remove(&id);
+
+ let response = self.insert_and_execute(id, func).await;
+
+ send.send_modify(|opt| {
+ opt.replace(
+ response
+ .clone()
+ .map(|v| v as Arc),
+ );
+ });
+
+ return response;
+ }
+ }
+ }
+ }
+ } else {
+ Err(Error::MaxConcurrent)
+ }
+
+ #[cfg(not(feature = "queue"))]
+ Err(Error::MaxConcurrent)
+ }
+ _ => self.insert_and_execute(id, func).await,
+ }
+ }
+ }
+
+ /// Fetches the amount of currently running tasks
+ pub async fn current_task_count(&self) -> usize {
+ self.watchers.read().await.len()
+ }
+
+ #[cfg(feature = "queue")]
+ /// Fetches the current length of the queue
+ pub async fn current_queue_len(&self) -> usize {
+ self.queue.read().await.len()
+ }
+}
+
+impl Default for CoalescionService {
+ fn default() -> Self {
+ Self::from_config(CoalescionServiceConfig::default())
+ }
+}
diff --git a/crates/core/config/Cargo.toml b/crates/core/config/Cargo.toml
index 26cc1c25f..e9e329c7a 100644
--- a/crates/core/config/Cargo.toml
+++ b/crates/core/config/Cargo.toml
@@ -1,10 +1,11 @@
[package]
name = "revolt-config"
-version = "0.8.8"
+version = "0.13.7"
edition = "2021"
license = "MIT"
authors = ["Paul Makles "]
description = "Revolt Backend: Configuration"
+repository = "https://github.com/stoatchat/stoatchat"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -17,24 +18,24 @@ default = ["test", "sentry"]
[dependencies]
# Utility
-config = "0.13.3"
-cached = "0.44.0"
-once_cell = "1.18.0"
+config = { workspace = true }
+cached = { workspace = true }
+once_cell = { workspace = true }
# Serde
-serde = { version = "1", features = ["derive"] }
+serde = { workspace = true }
# Async
-futures-locks = "0.7.1"
-async-std = { version = "1.8.0", features = ["attributes"], optional = true }
+futures-locks = { workspace = true }
+async-std = { workspace = true, features = ["attributes"], optional = true }
# Logging
-log = "0.4.14"
-pretty_env_logger = "0.4.0"
+log = { workspace = true }
+pretty_env_logger = { workspace = true }
# Sentry
-sentry = { version = "0.31.5", optional = true }
-sentry-anyhow = { version = "0.38.1", optional = true }
+sentry = { workspace = true, optional = true }
+sentry-anyhow = { workspace = true, optional = true }
# Core
-revolt-result = { version = "0.8.8", path = "../result", optional = true }
+revolt-result = { workspace = true, optional = true }
diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml
index e3a04f2ae..e598a3dc8 100644
--- a/crates/core/config/Revolt.toml
+++ b/crates/core/config/Revolt.toml
@@ -1,4 +1,5 @@
production = false
+disable_events_dont_use = false
[database]
# MongoDB connection URL
@@ -11,7 +12,7 @@ redis = "redis://redis/"
[hosts]
# Web locations of various services
# Defaults assume all services are reverse-proxied
-# See https://github.com/revoltchat/self-hosted/blob/master/Caddyfile
+# See https://github.com/stoatchat/self-hosted/blob/main/Caddyfile
#
# Remember to change these to https/wss where appropriate in production!
app = "http://local.revolt.chat"
@@ -22,17 +23,23 @@ january = "http://local.revolt.chat/january"
voso_legacy = ""
voso_legacy_ws = ""
+[hosts.livekit]
+
[rabbit]
host = "rabbit"
port = 5672
username = "rabbituser"
password = "rabbitpass"
+default_exchange = "revolt.default"
+
+[rabbit.queues]
+acks = "internal.ack"
[api]
[api.registration]
# Whether an invite should be required for registration
-# See https://github.com/revoltchat/self-hosted#making-your-instance-invite-only
+# See https://github.com/stoatchat/self-hosted#making-your-instance-invite-only
invite_only = false
[api.smtp]
@@ -56,6 +63,8 @@ voso_legacy_token = ""
trust_cloudflare = false
# easypwned endpoint
easypwned = ""
+# Tenor API Key
+tenor_key = ""
[api.security.captcha]
# hCaptcha configuration
@@ -66,8 +75,15 @@ hcaptcha_sitekey = ""
# Maximum concurrent connections (to proxy server)
max_concurrent_connections = 50
-[api.users]
+[api.livekit]
+# How long to ring devices for when calling in dms/groups, in seconds
+call_ring_duration = 30
+
+[api.livekit.nodes]
+[api.users]
+# Minimum allowed length of usernames
+min_username_length = 2
[pushd]
# this changes the names of the queues to not overlap
@@ -79,12 +95,18 @@ production = true
# Increasing this will resolve mentions faster, but will consume more memory while resolving.
mass_mention_chunk_size = 200
+# How long pushd will cache resolved names for rendered message notifications.
+# Increasing this will result in lower database usage, but may result in a situation where a user/channel/role name changes
+# and the notifications still resolve to the old name.
+render_cache_time = 60
+
# none of these should need changing
exchange = "revolt.notifications"
message_queue = "notifications.origin.message"
mass_mention_queue = "notifications.origin.mass_mention" # handles messages that contain role or everyone mentions
fr_accepted_queue = "notifications.ingest.fr_accepted" # friend request accepted
fr_received_queue = "notifications.ingest.fr_received" # friend request received
+dm_call_queue = "notifications.ingest.dm_call" # direct message voice call
generic_queue = "notifications.ingest.generic" # generic messages (title + body)
ack_queue = "notifications.process.ack" # updates badges for apple devices
@@ -114,6 +136,8 @@ pkcs8 = ""
key_id = ""
team_id = ""
+[january]
+blocked_domains = []
[files]
# Encryption key for stored files
@@ -210,6 +234,10 @@ new_user_hours = 72
# (should be greater than any one file upload limit)
body_limit_size = 20_000_000
+# If any userids are entered here, only those users will be able to create servers.
+# Leave empty to allow all users to create servers
+restrict_server_creation = []
+
[features.limits.new_user]
# Limits imposed on new users
@@ -228,6 +256,18 @@ message_attachments = 5
# Maximum number of servers the user can create/join
servers = 50
+# Maximum audio frequency (Hz) in voice calls
+voice_quality = 16000
+
+# Whether the user can use video streams in voice calls
+video = true
+
+# Maximum resolution (width, height) of video streams in voice calls
+video_resolution = [1080, 720]
+
+# Minimum and maximum aspect ratio of video streams in voice calls
+video_aspect_ratio = [0.3, 2.5]
+
[features.limits.new_user.file_upload_size_limit]
# Maximum file size limits (in bytes)
attachments = 20_000_000
@@ -255,6 +295,18 @@ message_attachments = 5
# Maximum number of servers the user can create/join
servers = 100
+# Maximum audio frequency (Hz) in voice calls
+voice_quality = 16000
+
+# Whether the user can use video streams in voice calls
+video = true
+
+# Maximum resolution (width, height) of video streams in voice calls
+video_resolution = [1280, 720]
+
+# Minimum and maximum aspect ratio of video streams in voice calls
+video_aspect_ratio = [0.3, 2.5]
+
[features.limits.default.file_upload_size_limit]
# Maximum file size limits (in bytes)
attachments = 20_000_000
@@ -269,11 +321,19 @@ emojis = 500_000
# default: 5
process_message_delay_limit = 5
+[features.legal_links]
+# URLs for legal documents
+terms_of_service = ""
+privacy_policy = ""
+guidelines = ""
+
[sentry]
# Configuration for Sentry error reporting
api = ""
events = ""
+voice_ingress = ""
files = ""
proxy = ""
pushd = ""
crond = ""
+gifbox = ""
diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs
index 6c4d58de5..1ba041cac 100644
--- a/crates/core/config/src/lib.rs
+++ b/crates/core/config/src/lib.rs
@@ -1,7 +1,7 @@
-use std::collections::HashMap;
+use std::{collections::HashMap, path::Path};
use cached::proc_macro::cached;
-use config::{Config, File, FileFormat};
+use config::{Config, Environment, File, FileFormat};
use futures_locks::RwLock;
use once_cell::sync::Lazy;
use serde::Deserialize;
@@ -94,12 +94,23 @@ static CONFIG_BUILDER: Lazy> = Lazy::new(|| {
}
}
- for path in CONFIG_SEARCH_PATHS {
- if std::path::Path::new(path).exists() {
- builder = builder.add_source(File::new(path, FileFormat::Toml));
+ let cwd = std::env::current_dir().unwrap();
+ let mut cwd: Option<&Path> = Some(&cwd);
+
+ while let Some(path) = cwd {
+ for config_path in CONFIG_SEARCH_PATHS {
+ let config_path = path.join(config_path);
+ if config_path.exists() {
+ builder = builder
+ .add_source(File::new(config_path.to_str().unwrap(), FileFormat::Toml));
+ }
}
+
+ cwd = path.parent();
}
+ builder = builder.add_source(Environment::with_prefix("REVOLT").separator("__"));
+
builder.build().unwrap()
})
});
@@ -108,6 +119,12 @@ static CONFIG_BUILDER: Lazy> = Lazy::new(|| {
pub struct Database {
pub mongodb: String,
pub redis: String,
+ pub redis_pubsub: Option,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct RabbitQueues {
+ pub acks: String,
}
#[derive(Deserialize, Debug, Clone)]
@@ -116,6 +133,8 @@ pub struct Rabbit {
pub port: u16,
pub username: String,
pub password: String,
+ pub default_exchange: String,
+ pub queues: RabbitQueues,
}
#[derive(Deserialize, Debug, Clone)]
@@ -125,8 +144,7 @@ pub struct Hosts {
pub events: String,
pub autumn: String,
pub january: String,
- pub voso_legacy: String,
- pub voso_legacy_ws: String,
+ pub livekit: HashMap,
}
#[derive(Deserialize, Debug, Clone)]
@@ -190,6 +208,7 @@ pub struct ApiSecurity {
pub captcha: ApiSecurityCaptcha,
pub trust_cloudflare: bool,
pub easypwned: String,
+ pub tenor_key: String,
}
#[derive(Deserialize, Debug, Clone)]
@@ -197,9 +216,29 @@ pub struct ApiWorkers {
pub max_concurrent_connections: usize,
}
+#[derive(Deserialize, Debug, Clone)]
+pub struct ApiLiveKit {
+ pub call_ring_duration: usize,
+ pub nodes: HashMap,
+}
+
+#[derive(Deserialize, Debug, Clone)]
+pub struct LiveKitNode {
+ pub url: String,
+ pub lat: f64,
+ pub lon: f64,
+ pub key: String,
+ pub secret: String,
+
+ // whether to hide the node in the nodes list
+ #[serde(default)]
+ pub private: bool,
+}
+
#[derive(Deserialize, Debug, Clone)]
pub struct ApiUsers {
pub early_adopter_cutoff: Option,
+ pub min_username_length: usize,
}
#[derive(Deserialize, Debug, Clone)]
@@ -208,6 +247,7 @@ pub struct Api {
pub smtp: ApiSmtp,
pub security: ApiSecurity,
pub workers: ApiWorkers,
+ pub livekit: ApiLiveKit,
pub users: ApiUsers,
}
@@ -216,10 +256,12 @@ pub struct Pushd {
pub production: bool,
pub exchange: String,
pub mass_mention_chunk_size: usize,
+ pub render_cache_time: usize,
// Queues
pub message_queue: String,
pub mass_mention_queue: String,
+ pub dm_call_queue: String,
pub fr_accepted_queue: String,
pub fr_received_queue: String,
pub generic_queue: String,
@@ -250,6 +292,10 @@ impl Pushd {
self.get_routing_key(self.mass_mention_queue.clone())
}
+ pub fn get_dm_call_routing_key(&self) -> String {
+ self.get_routing_key(self.dm_call_queue.clone())
+ }
+
pub fn get_fr_accepted_routing_key(&self) -> String {
self.get_routing_key(self.fr_accepted_queue.clone())
}
@@ -263,6 +309,11 @@ impl Pushd {
}
}
+#[derive(Deserialize, Debug, Clone)]
+pub struct January {
+ pub blocked_domains: Vec,
+}
+
#[derive(Deserialize, Debug, Clone)]
pub struct FilesLimit {
pub min_file_size: usize,
@@ -307,6 +358,8 @@ pub struct GlobalLimits {
pub new_user_hours: usize,
pub body_limit_size: usize,
+
+ pub restrict_server_creation: Vec,
}
#[derive(Deserialize, Debug, Clone)]
@@ -317,6 +370,10 @@ pub struct FeaturesLimits {
pub message_length: usize,
pub message_attachments: usize,
pub servers: usize,
+ pub voice_quality: u32,
+ pub video: bool,
+ pub video_resolution: [u32; 2],
+ pub video_aspect_ratio: [f32; 2],
pub file_upload_size_limit: HashMap,
}
@@ -332,6 +389,16 @@ pub struct FeaturesLimitsCollection {
pub roles: HashMap,
}
+#[derive(Deserialize, Debug, Clone)]
+pub struct LegalLinks {
+ /// Terms of Service URL
+ pub terms_of_service: String,
+ /// Privacy Policy URL
+ pub privacy_policy: String,
+ /// Guidelines URL
+ pub guidelines: String,
+}
+
#[derive(Deserialize, Debug, Clone)]
pub struct FeaturesAdvanced {
#[serde(default)]
@@ -349,6 +416,7 @@ impl Default for FeaturesAdvanced {
#[derive(Deserialize, Debug, Clone)]
pub struct Features {
pub limits: FeaturesLimitsCollection,
+ pub legal_links: LegalLinks,
pub webhooks_enabled: bool,
pub mass_mentions_send_notifications: bool,
pub mass_mentions_enabled: bool,
@@ -361,10 +429,12 @@ pub struct Features {
pub struct Sentry {
pub api: String,
pub events: String,
+ pub voice_ingress: String,
pub files: String,
pub proxy: String,
pub pushd: String,
pub crond: String,
+ pub gifbox: String,
}
#[derive(Deserialize, Debug, Clone)]
@@ -374,10 +444,12 @@ pub struct Settings {
pub hosts: Hosts,
pub api: Api,
pub pushd: Pushd,
+ pub january: January,
pub files: Files,
pub features: Features,
pub sentry: Sentry,
pub production: bool,
+ pub disable_events_dont_use: bool,
}
impl Settings {
@@ -408,12 +480,14 @@ pub async fn config() -> Settings {
let mut config = read().await.try_deserialize::().unwrap();
// inject REDIS_URI for redis-kiss library
- if std::env::var("REDIS_URL").is_err() {
+ if std::env::var("REDIS_URI").is_err() {
std::env::set_var("REDIS_URI", config.database.redis.clone());
}
// auto-detect production nodes
- if config.hosts.api.contains("https") && config.hosts.api.contains("revolt.chat") {
+ if config.hosts.api.contains("https")
+ && (config.hosts.api.contains("revolt.chat") || config.hosts.api.contains("stoat.chat"))
+ {
config.production = true;
}
diff --git a/crates/core/database/Cargo.toml b/crates/core/database/Cargo.toml
index 02e554118..f820c899a 100644
--- a/crates/core/database/Cargo.toml
+++ b/crates/core/database/Cargo.toml
@@ -1,10 +1,11 @@
[package]
name = "revolt-database"
-version = "0.8.8"
+version = "0.13.7"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles "]
description = "Revolt Backend: Database Implementation"
+repository = "https://github.com/stoatchat/stoatchat"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -15,84 +16,88 @@ mongodb = ["dep:mongodb", "bson", "authifier/database-mongodb"]
# ... Other
tasks = ["isahc", "linkify", "url-escape"]
async-std-runtime = ["async-std", "authifier/async-std-runtime"]
-rocket-impl = ["rocket", "schemars", "revolt_okapi", "revolt_rocket_okapi", "authifier/rocket_impl"]
-axum-impl = ["axum"]
+rocket-impl = [
+ "rocket",
+ "schemars",
+ "revolt_okapi",
+ "revolt_rocket_okapi",
+ "authifier/rocket_impl",
+]
+axum-impl = ["axum", "revolt-result/axum"]
redis-is-patched = ["revolt-presence/redis-is-patched"]
+voice = ["livekit-api", "livekit-protocol", "livekit-runtime"]
# Default Features
default = ["mongodb", "async-std-runtime", "tasks"]
[dependencies]
# Core
-revolt-config = { version = "0.8.8", path = "../config", features = [
- "report-macros",
-] }
-revolt-result = { version = "0.8.8", path = "../result" }
-revolt-models = { version = "0.8.8", path = "../models", features = [
- "validator",
-] }
-revolt-presence = { version = "0.8.8", path = "../presence" }
-revolt-permissions = { version = "0.8.8", path = "../permissions", features = [
- "serde",
- "bson",
-] }
-revolt-parser = { version = "0.8.8", path = "../parser" }
+revolt-config = { workspace = true, features = ["report-macros"] }
+revolt-result = { workspace = true }
+revolt-models = { workspace = true, features = ["validator"] }
+revolt-presence = { workspace = true }
+revolt-permissions = { workspace = true, features = ["serde", "bson"] }
+revolt-parser = { workspace = true }
+revolt-coalesced = { workspace = true }
# Utility
-log = "0.4"
-lru = "0.11.0"
-rand = "0.8.5"
-ulid = "1.0.0"
-nanoid = "0.4.0"
-base64 = "0.21.3"
-once_cell = "1.17"
-indexmap = "1.9.1"
-decancer = "1.6.2"
-deadqueue = "0.2.4"
-linkify = { optional = true, version = "0.8.1" }
-url-escape = { optional = true, version = "0.1.1" }
-validator = { version = "0.16", features = ["derive"] }
-isahc = { optional = true, version = "1.7", features = ["json"] }
+log = { workspace = true }
+lru = { workspace = true }
+rand = { workspace = true }
+ulid = { workspace = true }
+nanoid = { workspace = true }
+base64 = { workspace = true }
+once_cell = { workspace = true }
+indexmap = { workspace = true }
+decancer = { workspace = true }
+deadqueue = { workspace = true }
+linkify = { workspace = true, optional = true }
+url-escape = { workspace = true, optional = true }
+validator = { workspace = true, features = ["derive"] }
+isahc = { workspace = true, features = ["json"], optional = true }
# Serialisation
-serde_json = "1"
-revolt_optional_struct = "0.2.0"
-serde = { version = "1", features = ["derive"] }
-iso8601-timestamp = { version = "0.2.10", features = ["serde", "bson"] }
+serde_json = { workspace = true }
+revolt_optional_struct = { workspace = true }
+serde = { workspace = true }
+iso8601-timestamp = { workspace = true, features = ["serde", "bson"] }
# Events
-redis-kiss = { version = "0.1.4" }
+redis-kiss = { workspace = true }
# Database
-bson = { optional = true, version = "2.1.0" }
-mongodb = { optional = true, version = "3.1.0" }
+bson = { workspace = true, optional = true }
+mongodb = { workspace = true, optional = true }
# Database Migration
-unicode-segmentation = "1.10.1"
-regex = "1"
+unicode-segmentation = { workspace = true }
+regex = { workspace = true }
# Async Language Features
-futures = "0.3.19"
-async-lock = "2.8.0"
-async-trait = "0.1.51"
-async-recursion = "1.0.4"
+futures = { workspace = true }
+async-lock = { workspace = true }
+async-trait = { workspace = true }
+async-recursion = { workspace = true }
# Async
-async-std = { version = "1.8.0", features = ["attributes"], optional = true }
+async-std = { workspace = true, features = ["attributes"], optional = true }
# Axum Impl
-axum = { version = "0.7.5", optional = true }
+axum = { workspace = true, optional = true }
# Rocket Impl
-schemars = { version = "0.8.8", optional = true }
-rocket = { version = "0.5.1", default-features = false, features = [
- "json",
-], optional = true }
-revolt_okapi = { version = "0.9.1", optional = true }
-revolt_rocket_okapi = { version = "0.10.0", optional = true }
+schemars = { workspace = true, optional = true }
+rocket = { workspace = true, features = ["json"], optional = true }
+revolt_okapi = { workspace = true, optional = true }
+revolt_rocket_okapi = { workspace = true, optional = true }
# Authifier
-authifier = { version = "1.0.15" }
+authifier = { workspace = true }
# RabbitMQ
-amqprs = { version = "1.7.0" }
+lapin = { workspace = true, features = ["tokio"] }
+
+# Voice
+livekit-api = { workspace = true, features = ["rustls-tls-native-roots"], optional = true }
+livekit-protocol = { workspace = true, optional = true }
+livekit-runtime = { workspace = true, features = ["tokio"], optional = true }
diff --git a/crates/core/database/fixtures/server_with_roles.json b/crates/core/database/fixtures/server_with_roles.json
index 0a76489dd..21ea5656e 100644
--- a/crates/core/database/fixtures/server_with_roles.json
+++ b/crates/core/database/fixtures/server_with_roles.json
@@ -47,6 +47,7 @@
],
"roles": {
"__ID:5__": {
+ "_id": "__ID:5__",
"name": "Moderator",
"permissions": {
"a": 545270208,
@@ -55,6 +56,7 @@
"rank": 1
},
"__ID:6__": {
+ "_id": "__ID:6__",
"name": "Owner",
"permissions": {
"a": 0,
diff --git a/crates/core/database/src/amqp/amqp.rs b/crates/core/database/src/amqp/amqp.rs
index b567c7f7c..b0e5e1750 100644
--- a/crates/core/database/src/amqp/amqp.rs
+++ b/crates/core/database/src/amqp/amqp.rs
@@ -1,30 +1,79 @@
use std::collections::HashSet;
+use std::sync::Arc;
use crate::events::rabbit::*;
use crate::User;
-use amqprs::channel::BasicPublishArguments;
-use amqprs::{channel::Channel, connection::Connection, error::Error as AMQPError};
-use amqprs::{BasicProperties, FieldTable};
+use lapin::{
+ options::BasicPublishOptions,
+ protocol::basic::AMQPProperties,
+ types::{AMQPValue, FieldTable},
+ Channel, Connection, ConnectionProperties, Error as AMQPError,
+};
use revolt_models::v0::PushNotification;
use revolt_presence::filter_online;
+use revolt_result::Result;
use serde_json::to_string;
#[derive(Clone)]
pub struct AMQP {
+ friend_request_accepted: Arc,
+ friend_request_received: Arc,
+ generic_message: Arc,
+ message_sent: Arc,
+ mass_mention_message_sent: Arc,
+ ack_notification_message: Arc,
+ dm_call_updated: Arc,
+ process_ack: Arc,
#[allow(unused)]
- connection: Connection,
- channel: Channel,
+ connection: Arc,
}
impl AMQP {
- pub fn new(connection: Connection, channel: Channel) -> AMQP {
- AMQP {
+ pub async fn new(connection: Arc) -> Self {
+ Self {
+ friend_request_accepted: Self::create_channel(&connection).await,
+ friend_request_received: Self::create_channel(&connection).await,
+ generic_message: Self::create_channel(&connection).await,
+ message_sent: Self::create_channel(&connection).await,
+ mass_mention_message_sent: Self::create_channel(&connection).await,
+ ack_notification_message: Self::create_channel(&connection).await,
+ dm_call_updated: Self::create_channel(&connection).await,
+ process_ack: Self::create_channel(&connection).await,
connection,
- channel,
}
}
+ pub async fn new_auto() -> Self {
+ let config = revolt_config::config().await;
+
+ let connection = Arc::new(
+ Connection::connect(
+ &format!(
+ "amqp://{}:{}@{}:{}",
+ &config.rabbit.username,
+ &config.rabbit.password,
+ &config.rabbit.host,
+ &config.rabbit.port,
+ ),
+ ConnectionProperties::default(),
+ )
+ .await
+ .expect("Failed to connect to RabbitMQ"),
+ );
+
+ Self::new(connection).await
+ }
+
+ async fn create_channel(connection: &Connection) -> Arc {
+ Arc::new(
+ connection
+ .create_channel()
+ .await
+ .expect("Failed to create channel"),
+ )
+ }
+
pub async fn friend_request_accepted(
&self,
accepted_request_user: &User,
@@ -42,19 +91,20 @@ impl AMQP {
config.pushd.get_fr_accepted_routing_key(),
payload
);
- self.channel
+
+ self.friend_request_accepted
.basic_publish(
- BasicProperties::default()
- .with_content_type("application/json")
- .with_persistence(true)
- .finish(),
- payload.into(),
- BasicPublishArguments::new(
- &config.pushd.exchange,
- &config.pushd.get_fr_accepted_routing_key(),
- ),
+ config.pushd.exchange.clone().into(),
+ config.pushd.get_fr_accepted_routing_key().into(),
+ BasicPublishOptions::default(),
+ payload.as_bytes(),
+ AMQPProperties::default()
+ .with_content_type("application/json".into())
+ .with_delivery_mode(2),
)
- .await
+ .await?;
+
+ Ok(())
}
pub async fn friend_request_received(
@@ -75,19 +125,19 @@ impl AMQP {
payload
);
- self.channel
+ self.friend_request_received
.basic_publish(
- BasicProperties::default()
- .with_content_type("application/json")
- .with_persistence(true)
- .finish(),
- payload.into(),
- BasicPublishArguments::new(
- &config.pushd.exchange,
- &config.pushd.get_fr_received_routing_key(),
- ),
+ config.pushd.exchange.clone().into(),
+ config.pushd.get_fr_received_routing_key().into(),
+ BasicPublishOptions::default(),
+ payload.as_bytes(),
+ AMQPProperties::default()
+ .with_content_type("application/json".into())
+ .with_delivery_mode(2),
)
- .await
+ .await?;
+
+ Ok(())
}
pub async fn generic_message(
@@ -112,19 +162,19 @@ impl AMQP {
payload
);
- self.channel
+ self.generic_message
.basic_publish(
- BasicProperties::default()
- .with_content_type("application/json")
- .with_persistence(true)
- .finish(),
- payload.into(),
- BasicPublishArguments::new(
- &config.pushd.exchange,
- &config.pushd.get_generic_routing_key(),
- ),
+ config.pushd.exchange.clone().into(),
+ config.pushd.get_generic_routing_key().into(),
+ BasicPublishOptions::default(),
+ payload.as_bytes(),
+ AMQPProperties::default()
+ .with_content_type("application/json".into())
+ .with_delivery_mode(2),
)
- .await
+ .await?;
+
+ Ok(())
}
pub async fn message_sent(
@@ -155,19 +205,19 @@ impl AMQP {
payload
);
- self.channel
+ self.message_sent
.basic_publish(
- BasicProperties::default()
- .with_content_type("application/json")
- .with_persistence(true)
- .finish(),
- payload.into(),
- BasicPublishArguments::new(
- &config.pushd.exchange,
- &config.pushd.get_message_routing_key(),
- ),
+ config.pushd.exchange.clone().into(),
+ config.pushd.get_message_routing_key().into(),
+ BasicPublishOptions::default(),
+ payload.as_bytes(),
+ AMQPProperties::default()
+ .with_content_type("application/json".into())
+ .with_delivery_mode(2),
)
- .await
+ .await?;
+
+ Ok(())
}
pub async fn mass_mention_message_sent(
@@ -190,19 +240,24 @@ impl AMQP {
routing_key, payload
);
- self.channel
+ self.mass_mention_message_sent
.basic_publish(
- BasicProperties::default()
- .with_content_type("application/json")
- .with_persistence(true)
- .finish(),
- payload.into(),
- BasicPublishArguments::new(&config.pushd.exchange, routing_key.as_str()),
+ config.pushd.exchange.clone().into(),
+ routing_key.into(),
+ BasicPublishOptions::default(),
+ payload.as_bytes(),
+ AMQPProperties::default()
+ .with_content_type("application/json".into())
+ .with_delivery_mode(2),
)
- .await
+ .await?;
+
+ Ok(())
}
- pub async fn ack_message(
+ /// # Sends an ack to pushd to update badges on iPhones.
+ /// Not to be confused with the process_ack function, which handles sending all acks to crond for processing.
+ pub async fn ack_notification_message(
&self,
user_id: String,
channel_id: String,
@@ -222,22 +277,106 @@ impl AMQP {
config.pushd.ack_queue, payload
);
- let mut headers = FieldTable::new();
+ let mut headers = FieldTable::default();
headers.insert(
- "x-deduplication-header".try_into().unwrap(),
- format!("{}-{}", &user_id, &channel_id).into(),
+ "x-deduplication-header".into(),
+ AMQPValue::LongString(format!("{}-{}", &user_id, &channel_id).into()),
);
- self.channel
+ self.ack_notification_message
.basic_publish(
- BasicProperties::default()
- .with_content_type("application/json")
- .with_persistence(true)
- //.with_headers(headers)
- .finish(),
- payload.into(),
- BasicPublishArguments::new(&config.pushd.exchange, &config.pushd.ack_queue),
+ config.pushd.exchange.clone().into(),
+ config.pushd.ack_queue.into(),
+ BasicPublishOptions::default(),
+ payload.as_bytes(),
+ AMQPProperties::default()
+ .with_content_type("application/json".into())
+ .with_delivery_mode(2),
)
- .await
+ .await?;
+
+ Ok(())
+ }
+
+ /// # DM Call Update
+ /// Used to send an update about a DM call, eg. start or end of a call.
+ /// Recipients can be used to narrow the scope of recipients, otherwise all recipients will be notified.
+ /// `ended` refers to the ringing period, not necessarily the call itself.
+ pub async fn dm_call_updated(
+ &self,
+ initiator_id: &str,
+ channel_id: &str,
+ started_at: Option<&str>,
+ ended: bool,
+ recipients: Option>,
+ ) -> Result<(), AMQPError> {
+ let config = revolt_config::config().await;
+
+ let payload = InternalDmCallPayload {
+ payload: DmCallPayload {
+ initiator_id: initiator_id.to_string(),
+ channel_id: channel_id.to_string(),
+ started_at: started_at.map(|f| f.to_string()),
+ ended,
+ },
+ recipients,
+ };
+ let payload = to_string(&payload).unwrap();
+
+ debug!(
+ "Sending dm call update payload on channel {}: {}",
+ config.pushd.get_dm_call_routing_key(),
+ payload
+ );
+
+ self.dm_call_updated
+ .basic_publish(
+ config.pushd.exchange.clone().into(),
+ config.pushd.get_dm_call_routing_key().into(),
+ BasicPublishOptions::default(),
+ payload.as_bytes(),
+ AMQPProperties::default()
+ .with_content_type("application/json".into())
+ .with_delivery_mode(2),
+ )
+ .await?;
+
+ Ok(())
+ }
+
+ /// # Send an ack to crond for processing
+ pub async fn process_ack(
+ &self,
+ user_id: &str,
+ channel_id: Option<&str>,
+ server_id: Option<&str>,
+ ) -> Result<(), AMQPError> {
+ let config = revolt_config::config().await;
+
+ let payload = AckEventPayload {
+ user_id: user_id.to_string(),
+ channel_id: channel_id.map(|value| value.to_string()),
+ server_id: server_id.map(|value| value.to_string()),
+ };
+ let payload = to_string(&payload).unwrap();
+
+ info!(
+ "Sending ack processor event on exchange {}, channel {}: {}",
+ config.rabbit.default_exchange, config.rabbit.queues.acks, payload
+ );
+
+ self.process_ack
+ .basic_publish(
+ config.rabbit.default_exchange.clone().into(),
+ config.rabbit.queues.acks.into(),
+ BasicPublishOptions::default(),
+ payload.as_bytes(),
+ AMQPProperties::default()
+ .with_content_type("application/json".into())
+ .with_delivery_mode(2),
+ )
+ .await?;
+
+ Ok(())
}
}
diff --git a/crates/core/database/src/drivers/mod.rs b/crates/core/database/src/drivers/mod.rs
index 2a2c7b851..df9a56efd 100644
--- a/crates/core/database/src/drivers/mod.rs
+++ b/crates/core/database/src/drivers/mod.rs
@@ -10,6 +10,7 @@ use authifier::config::SMTPSettings;
use authifier::config::Shield;
use authifier::config::Template;
use authifier::config::Templates;
+use authifier::config::EmailExpiryConfig;
use authifier::Authifier;
use rand::Rng;
use revolt_config::config;
@@ -35,7 +36,7 @@ pub enum DatabaseInfo {
}
/// Database
-#[derive(Clone)]
+#[derive(Clone, Debug)]
pub enum Database {
/// Mock database
Reference(ReferenceDb),
@@ -69,7 +70,7 @@ impl DatabaseInfo {
.await;
#[cfg(not(feature = "mongodb"))]
- return Err("MongoDB not enabled.".to_string())
+ return Err("MongoDB not enabled.".to_string());
} else {
DatabaseInfo::Reference.connect().await
}
@@ -90,7 +91,7 @@ impl DatabaseInfo {
.await;
#[cfg(not(feature = "mongodb"))]
- return Err("MongoDB not enabled.".to_string())
+ return Err("MongoDB not enabled.".to_string());
}
_ => unreachable!("must specify REFERENCE or MONGODB"),
}
@@ -137,29 +138,33 @@ impl Database {
.api
.smtp
.reply_to
- .unwrap_or("support@revolt.chat".into()),
+ .unwrap_or("support@stoat.chat".into()),
),
port: config.api.smtp.port,
use_tls: config.api.smtp.use_tls,
use_starttls: config.api.smtp.use_starttls,
},
- expiry: Default::default(),
+ expiry: EmailExpiryConfig {
+ expire_verification: 3600 * 24 * 7,
+ expire_password_reset: 3600 * 24,
+ expire_account_deletion: 3600 * 24,
+ },
templates: if config.production {
Templates {
verify: Template {
- title: "Verify your Revolt account.".into(),
+ title: "Verify your Stoat account.".into(),
text: include_str!("../../templates/verify.txt").into(),
url: format!("{}/login/verify/", config.hosts.app),
html: Some(include_str!("../../templates/verify.html").into()),
},
reset: Template {
- title: "Reset your Revolt password.".into(),
+ title: "Reset your Stoat password.".into(),
text: include_str!("../../templates/reset.txt").into(),
url: format!("{}/login/reset/", config.hosts.app),
html: Some(include_str!("../../templates/reset.html").into()),
},
reset_existing: Template {
- title: "You already have a Revolt account, reset your password."
+ title: "You already have a Stoat account, reset your password."
.into(),
text: include_str!("../../templates/reset-existing.txt").into(),
url: format!("{}/login/reset/", config.hosts.app),
diff --git a/crates/core/database/src/drivers/mongodb.rs b/crates/core/database/src/drivers/mongodb.rs
index fc5dbe3c2..baeddb1da 100644
--- a/crates/core/database/src/drivers/mongodb.rs
+++ b/crates/core/database/src/drivers/mongodb.rs
@@ -11,6 +11,7 @@ use serde::Serialize;
database_derived!(
/// MongoDB implementation
+ #[derive(Debug)]
pub struct MongoDb(pub ::mongodb::Client, pub String);
);
diff --git a/crates/core/database/src/drivers/reference.rs b/crates/core/database/src/drivers/reference.rs
index ff18f2533..e02eae644 100644
--- a/crates/core/database/src/drivers/reference.rs
+++ b/crates/core/database/src/drivers/reference.rs
@@ -10,7 +10,7 @@ use crate::{
database_derived!(
/// Reference implementation
- #[derive(Default)]
+ #[derive(Default, Debug)]
pub struct ReferenceDb {
pub bots: Arc>>,
pub channels: Arc>>,
diff --git a/crates/core/database/src/events/client.rs b/crates/core/database/src/events/client.rs
index aa9fb0b9f..31864cee3 100644
--- a/crates/core/database/src/events/client.rs
+++ b/crates/core/database/src/events/client.rs
@@ -3,10 +3,12 @@ use revolt_result::Error;
use serde::{Deserialize, Serialize};
use revolt_models::v0::{
- AppendMessage, Channel, ChannelUnread, Emoji, FieldsChannel, FieldsMember, FieldsMessage,
- FieldsRole, FieldsServer, FieldsUser, FieldsWebhook, Member, MemberCompositeKey, Message,
- PartialChannel, PartialMember, PartialMessage, PartialRole, PartialServer, PartialUser,
- PartialWebhook, PolicyChange, RemovalIntention, Report, Server, User, UserSettings, Webhook,
+ AppendMessage, Channel, ChannelSlowmode, ChannelUnread, ChannelVoiceState, Emoji,
+ FieldsChannel, FieldsMember, FieldsMessage, FieldsRole, FieldsServer, FieldsUser,
+ FieldsWebhook, Member, MemberCompositeKey, Message, PartialChannel, PartialEmoji,
+ PartialMember, PartialMessage, PartialRole, PartialServer, PartialUser, PartialUserVoiceState,
+ PartialWebhook, PolicyChange, RemovalIntention, Report, Server, User, UserSettings,
+ UserVoiceState, Webhook,
};
use crate::Database;
@@ -20,16 +22,33 @@ pub enum Ping {
}
/// Fields provided in Ready payload
-#[derive(PartialEq)]
-pub enum ReadyPayloadFields {
- Users,
- Servers,
- Channels,
- Members,
- Emoji,
-
- UserSettings(Vec),
- ChannelUnreads,
+#[derive(PartialEq, Debug, Clone, Deserialize)]
+pub struct ReadyPayloadFields {
+ pub users: bool,
+ pub servers: bool,
+ pub channels: bool,
+ pub members: bool,
+ pub emojis: bool,
+ pub voice_states: bool,
+ pub user_settings: Vec,
+ pub channel_unreads: bool,
+ pub policy_changes: bool,
+}
+
+impl Default for ReadyPayloadFields {
+ fn default() -> Self {
+ Self {
+ users: true,
+ servers: true,
+ channels: true,
+ members: true,
+ emojis: true,
+ voice_states: true,
+ user_settings: Vec::new(),
+ channel_unreads: false,
+ policy_changes: true,
+ }
+ }
}
/// Protocol Events
@@ -37,9 +56,13 @@ pub enum ReadyPayloadFields {
#[serde(tag = "type")]
pub enum EventV1 {
/// Multiple events
- Bulk { v: Vec },
+ Bulk {
+ v: Vec,
+ },
/// Error event
- Error { data: Error },
+ Error {
+ data: Error,
+ },
/// Successfully authenticated
Authenticated,
@@ -57,17 +80,22 @@ pub enum EventV1 {
members: Option>,
#[serde(skip_serializing_if = "Option::is_none")]
emojis: Option>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ voice_states: Option>,
#[serde(skip_serializing_if = "Option::is_none")]
user_settings: Option,
#[serde(skip_serializing_if = "Option::is_none")]
channel_unreads: Option>,
- policy_changes: Vec,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ policy_changes: Option>,
},
/// Ping response
- Pong { data: Ping },
+ Pong {
+ data: Ping,
+ },
/// New message
Message(Message),
@@ -88,7 +116,10 @@ pub enum EventV1 {
},
/// Delete message
- MessageDelete { id: String, channel: String },
+ MessageDelete {
+ id: String,
+ channel: String,
+ },
/// New reaction to a message
MessageReact {
@@ -114,7 +145,10 @@ pub enum EventV1 {
},
/// Bulk delete messages
- BulkMessageDelete { channel: String, ids: Vec },
+ BulkMessageDelete {
+ channel: String,
+ ids: Vec,
+ },
/// New server
ServerCreate {
@@ -122,6 +156,7 @@ pub enum EventV1 {
server: Server,
channels: Vec,
emojis: Vec,
+ voice_states: Vec,
},
/// Update existing server
@@ -133,7 +168,9 @@ pub enum EventV1 {
},
/// Delete server
- ServerDelete { id: String },
+ ServerDelete {
+ id: String,
+ },
/// Update existing server member
ServerMemberUpdate {
@@ -169,10 +206,16 @@ pub enum EventV1 {
},
/// Server role deleted
- ServerRoleDelete { id: String, role_id: String },
+ ServerRoleDelete {
+ id: String,
+ role_id: String,
+ },
/// Server roles ranks updated
- ServerRoleRanksUpdate { id: String, ranks: Vec },
+ ServerRoleRanksUpdate {
+ id: String,
+ ranks: Vec,
+ },
/// Update existing user
UserUpdate {
@@ -184,9 +227,15 @@ pub enum EventV1 {
},
/// Relationship with another user changed
- UserRelationship { id: String, user: User },
+ UserRelationship {
+ id: String,
+ user: User,
+ },
/// Settings updated remotely
- UserSettingsUpdate { id: String, update: UserSettings },
+ UserSettingsUpdate {
+ id: String,
+ update: UserSettings,
+ },
/// User has been platform banned or deleted their account
///
@@ -197,12 +246,23 @@ pub enum EventV1 {
/// - Server Memberships
///
/// User flags are specified to explain why a wipe is occurring though not all reasons will necessarily ever appear.
- UserPlatformWipe { user_id: String, flags: i32 },
+ UserPlatformWipe {
+ user_id: String,
+ flags: i32,
+ },
/// New emoji
EmojiCreate(Emoji),
+ /// Update existing emoji
+ EmojiUpdate {
+ id: String,
+ data: PartialEmoji,
+ },
+
/// Delete emoji
- EmojiDelete { id: String },
+ EmojiDelete {
+ id: String,
+ },
/// New report
ReportCreate(Report),
@@ -218,19 +278,33 @@ pub enum EventV1 {
},
/// Delete channel
- ChannelDelete { id: String },
+ ChannelDelete {
+ id: String,
+ },
/// User joins a group
- ChannelGroupJoin { id: String, user: String },
+ ChannelGroupJoin {
+ id: String,
+ user: String,
+ },
/// User leaves a group
- ChannelGroupLeave { id: String, user: String },
+ ChannelGroupLeave {
+ id: String,
+ user: String,
+ },
/// User started typing in a channel
- ChannelStartTyping { id: String, user: String },
+ ChannelStartTyping {
+ id: String,
+ user: String,
+ },
/// User stopped typing in a channel
- ChannelStopTyping { id: String, user: String },
+ ChannelStopTyping {
+ id: String,
+ user: String,
+ },
/// User acknowledged message in channel
ChannelAck {
@@ -250,10 +324,43 @@ pub enum EventV1 {
},
/// Delete webhook
- WebhookDelete { id: String },
+ WebhookDelete {
+ id: String,
+ },
/// Auth events
Auth(AuthifierEvent),
+
+ /// Voice events
+ VoiceChannelJoin {
+ id: String,
+ state: UserVoiceState,
+ },
+ VoiceChannelLeave {
+ id: String,
+ user: String,
+ },
+ VoiceChannelMove {
+ user: String,
+ from: String,
+ to: String,
+ state: UserVoiceState,
+ },
+ UserVoiceStateUpdate {
+ id: String,
+ channel_id: String,
+ data: PartialUserVoiceState,
+ },
+ UserMoveVoiceChannel {
+ node: String,
+ from: String,
+ to: String,
+ token: String,
+ },
+ /// User's active slowmodes
+ UserSlowmodes {
+ slowmodes: Vec,
+ },
}
impl EventV1 {
diff --git a/crates/core/database/src/events/rabbit.rs b/crates/core/database/src/events/rabbit.rs
index 0ae91be11..1673f4a45 100644
--- a/crates/core/database/src/events/rabbit.rs
+++ b/crates/core/database/src/events/rabbit.rs
@@ -37,6 +37,20 @@ pub struct GenericPayload {
pub user: User,
}
+#[derive(Serialize, Deserialize, Clone)]
+pub struct DmCallPayload {
+ pub initiator_id: String,
+ pub channel_id: String,
+ pub started_at: Option,
+ pub ended: bool,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct InternalDmCallPayload {
+ pub payload: DmCallPayload,
+ pub recipients: Option>,
+}
+
#[derive(Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
#[allow(clippy::large_enum_variant)]
@@ -46,6 +60,7 @@ pub enum PayloadKind {
FRReceived(FRReceivedPayload),
BadgeUpdate(usize),
Generic(GenericPayload),
+ DmCallStartEnd(DmCallPayload),
}
#[derive(Serialize, Deserialize)]
@@ -63,3 +78,11 @@ pub struct AckPayload {
pub channel_id: String,
pub message_id: String,
}
+
+/// This is not the same as the AckPayload above, as the state for this event is stored in redis to allow for state updates while the event is queued.
+#[derive(Serialize, Deserialize, Debug)]
+pub struct AckEventPayload {
+ pub user_id: String,
+ pub channel_id: Option,
+ pub server_id: Option,
+}
diff --git a/crates/core/database/src/lib.rs b/crates/core/database/src/lib.rs
index f9827d03c..778206dce 100644
--- a/crates/core/database/src/lib.rs
+++ b/crates/core/database/src/lib.rs
@@ -112,6 +112,10 @@ pub mod tasks;
mod amqp;
pub use amqp::amqp::AMQP;
+#[cfg(feature = "voice")]
+pub mod voice;
+
+
/// Utility function to check if a boolean value is false
pub fn if_false(t: &bool) -> bool {
!t
diff --git a/crates/core/database/src/models/admin_migrations/ops/mongodb/scripts.rs b/crates/core/database/src/models/admin_migrations/ops/mongodb/scripts.rs
index d164cab10..174a81a62 100644
--- a/crates/core/database/src/models/admin_migrations/ops/mongodb/scripts.rs
+++ b/crates/core/database/src/models/admin_migrations/ops/mongodb/scripts.rs
@@ -9,14 +9,13 @@ use crate::{
bson::{doc, from_bson, from_document, to_document, Bson, DateTime, Document},
options::FindOptions,
},
- AbstractChannels, AbstractServers, Channel, Invite, MongoDb, User, DISCRIMINATOR_SEARCH_SPACE,
+ AbstractServers, Invite, MongoDb, User, DISCRIMINATOR_SEARCH_SPACE,
};
use bson::{oid::ObjectId, to_bson};
use futures::StreamExt;
use iso8601_timestamp::Timestamp;
use rand::seq::SliceRandom;
-use revolt_permissions::DEFAULT_WEBHOOK_PERMISSIONS;
-use revolt_result::{Error, ErrorType};
+use revolt_permissions::{ChannelPermission, DEFAULT_WEBHOOK_PERMISSIONS};
use serde::{Deserialize, Serialize};
use unicode_segmentation::UnicodeSegmentation;
@@ -26,7 +25,7 @@ struct MigrationInfo {
revision: i32,
}
-pub const LATEST_REVISION: i32 = 42; // MUST BE +1 to last migration
+pub const LATEST_REVISION: i32 = 50; // MUST BE +1 to last migration
pub async fn migrate_database(db: &MongoDb) {
let migrations = db.col::("migrations");
@@ -914,6 +913,7 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
}
if revision <= 26 {
+ // Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
info!("Running migration [revision 26 / 15-05-2024]: fix invites being incorrectly serialized with wrong enum tagging.");
auto_derived!(
@@ -1080,6 +1080,14 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
channel_id: String,
}
+ #[allow(clippy::enum_variant_names)]
+ #[derive(serde::Serialize, serde::Deserialize)]
+ enum Channel {
+ Group { owner: String },
+ TextChannel { server: String },
+ VoiceChannel { server: String }
+ }
+
let webhooks = db
.db()
.collection::("channel_webhooks")
@@ -1091,8 +1099,8 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.await;
for webhook in webhooks {
- match db.fetch_channel(&webhook.channel_id).await {
- Ok(channel) => {
+ match db.col::("channels").find_one(doc! { "_id": &webhook.channel_id }).await.unwrap() {
+ Some(channel) => {
let creator_id = match channel {
Channel::Group { owner, .. } => owner,
Channel::TextChannel { server, .. }
@@ -1100,7 +1108,6 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
let server = db.fetch_server(&server).await.expect("server");
server.owner
}
- _ => unreachable!("not server or group channel!"),
};
db.db()
@@ -1118,17 +1125,13 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.await
.expect("update webhook");
}
- Err(Error {
- error_type: ErrorType::NotFound,
- ..
- }) => {
+ None => {
db.db()
.collection::("channel_webhooks")
.delete_one(doc! { "_id": webhook._id })
.await
.expect("failed to delete invalid webhook");
}
- Err(err) => panic!("{err:?}"),
}
}
}
@@ -1169,9 +1172,9 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
.expect("failed to update users");
}
- if revision <= 41 {
+ if revision <= 43 {
info!(
- "Running migration [revision 41 / 05-06-2025]: convert role ranks to uniform numbers."
+ "Running migration [revision 43 / 05-06-2025]: convert role ranks to uniform numbers."
);
#[derive(Serialize, Deserialize, Clone)]
@@ -1226,6 +1229,83 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
}
}
+ if revision <= 46 {
+ info!("Running migration [revision 46 / 29-04-2025]: Convert all `VoiceChannel`'s into `TextChannel`");
+
+ db.col::("channels")
+ .update_many(
+ doc! { "channel_type": "VoiceChannel" },
+ doc! {
+ "$set": {
+ "channel_type": "TextChannel",
+ "voice": {}
+ }
+ }
+ )
+ .await
+ .expect("Failed to update voice channels");
+ };
+
+ if revision <= 48 {
+ info!("Running migration [revision 48 / 22-10-2025]: Add Video + Listen to default permissions");
+
+ db.col::("servers")
+ .update_many(
+ doc! { },
+ doc! {
+ "$bit": {
+ "default_permissions": {
+ "or": (ChannelPermission::Video + ChannelPermission::Speak + ChannelPermission::Listen) as i64
+ },
+ }
+ }
+ )
+ .await
+ .expect("Failed to update default_permissions");
+ };
+
+ if revision <= 49 {
+ info!("Running migration [revision 49 / 12-12-2025]: Add _id key to roles");
+
+ #[derive(Serialize, Deserialize, Clone)]
+ struct Server {
+ #[serde(rename = "_id")]
+ pub id: String,
+ #[serde(default = "HashMap::::new")]
+ pub roles: HashMap,
+ }
+
+ let mut servers = db
+ .db()
+ .collection::("servers")
+ .find(doc! {
+ "roles": {
+ "$exists": true,
+ "$ne": {}
+ }
+ })
+ .await
+ .unwrap()
+ .map(|res| res.expect("Failed to decode Server { id, roles }"));
+
+ while let Some(server) = servers.next().await {
+ let mut doc = doc! {};
+
+ for id in server.roles.keys() {
+ doc.insert(
+ format!("roles.{id}._id"),
+ id,
+ );
+ }
+
+ db.db()
+ .collection::("servers")
+ .update_one(doc! { "_id": &server.id }, doc! { "$set": doc })
+ .await
+ .unwrap();
+ }
+ };
+
// Reminder to update LATEST_REVISION when adding new migrations.
LATEST_REVISION.max(revision)
}
diff --git a/crates/core/database/src/models/channel_invites/model.rs b/crates/core/database/src/models/channel_invites/model.rs
index e3af921ab..a5d877fee 100644
--- a/crates/core/database/src/models/channel_invites/model.rs
+++ b/crates/core/database/src/models/channel_invites/model.rs
@@ -69,7 +69,7 @@ impl Invite {
creator: creator.id.clone(),
channel: id.clone(),
}),
- Channel::TextChannel { id, server, .. } | Channel::VoiceChannel { id, server, .. } => {
+ Channel::TextChannel { id, server, .. } => {
Ok(Invite::Server {
code,
creator: creator.id.clone(),
diff --git a/crates/core/database/src/models/channels/model.rs b/crates/core/database/src/models/channels/model.rs
index 673e5e508..74b56e110 100644
--- a/crates/core/database/src/models/channels/model.rs
+++ b/crates/core/database/src/models/channels/model.rs
@@ -1,5 +1,7 @@
-use std::collections::HashMap;
+#![allow(deprecated)]
+use std::{borrow::Cow, collections::HashMap};
+use redis_kiss::get_connection;
use revolt_config::config;
use revolt_models::v0::{self, MessageAuthor};
use revolt_permissions::OverrideField;
@@ -8,8 +10,7 @@ use serde::{Deserialize, Serialize};
use ulid::Ulid;
use crate::{
- events::client::EventV1, Database, File, PartialServer,
- Server, SystemMessage, User, AMQP,
+ events::client::EventV1, Database, File, PartialServer, Server, SystemMessage, User, AMQP,
};
#[cfg(feature = "mongodb")]
@@ -106,39 +107,23 @@ auto_derived!(
/// Whether this channel is marked as not safe for work
#[serde(skip_serializing_if = "crate::if_false", default)]
nsfw: bool,
- },
- /// Voice channel belonging to a server
- VoiceChannel {
- /// Unique Id
- #[serde(rename = "_id")]
- id: String,
- /// Id of the server this channel belongs to
- server: String,
- /// Display name of the channel
- name: String,
+ /// Voice Information for when this channel is also a voice channel
#[serde(skip_serializing_if = "Option::is_none")]
- /// Channel description
- description: Option,
- /// Custom icon attachment
- #[serde(skip_serializing_if = "Option::is_none")]
- icon: Option,
+ voice: Option,
- /// Default permissions assigned to users in this channel
+ /// The channel's slowmode delay in seconds
#[serde(skip_serializing_if = "Option::is_none")]
- default_permissions: Option,
- /// Permissions assigned based on role to this channel
- #[serde(
- default = "HashMap::::new",
- skip_serializing_if = "HashMap::::is_empty"
- )]
- role_permissions: HashMap,
-
- /// Whether this channel is marked as not safe for work
- #[serde(skip_serializing_if = "crate::if_false", default)]
- nsfw: bool,
+ slowmode: Option,
},
}
+
+ #[derive(Default)]
+ pub struct VoiceInformation {
+ /// Maximium amount of users allowed in the voice channel at once
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub max_users: Option,
+ }
);
auto_derived!(
@@ -164,6 +149,10 @@ auto_derived!(
pub default_permissions: Option,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_message_id: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub voice: Option,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub slowmode: Option,
}
/// Optional fields on channel object
@@ -171,6 +160,7 @@ auto_derived!(
Description,
Icon,
DefaultPermissions,
+ Voice,
}
);
@@ -222,16 +212,21 @@ impl Channel {
default_permissions: None,
role_permissions: HashMap::new(),
nsfw: data.nsfw.unwrap_or(false),
+ voice: data.voice.map(|voice| voice.into()),
+ slowmode: None,
},
- v0::LegacyServerChannelType::Voice => Channel::VoiceChannel {
+ v0::LegacyServerChannelType::Voice => Channel::TextChannel {
id: id.clone(),
server: server.id.to_owned(),
name: data.name,
description: data.description,
icon: None,
+ last_message_id: None,
default_permissions: None,
role_permissions: HashMap::new(),
nsfw: data.nsfw.unwrap_or(false),
+ voice: Some(data.voice.unwrap_or_default().into()),
+ slowmode: None,
},
};
@@ -432,8 +427,28 @@ impl Channel {
Channel::DirectMessage { id, .. }
| Channel::Group { id, .. }
| Channel::SavedMessages { id, .. }
- | Channel::TextChannel { id, .. }
- | Channel::VoiceChannel { id, .. } => id,
+ | Channel::TextChannel { id, .. } => id,
+ }
+ }
+
+ /// Clone this channel's server id
+ pub fn server(&self) -> Option<&str> {
+ match self {
+ Channel::TextChannel { server, .. } => Some(server),
+ _ => None,
+ }
+ }
+
+ /// Gets this channel's voice information
+ pub fn voice(&self) -> Option> {
+ match self {
+ Self::DirectMessage { .. } | Self::Group { .. } => {
+ Some(Cow::Owned(VoiceInformation::default()))
+ }
+ Self::TextChannel {
+ voice: Some(voice), ..
+ } => Some(Cow::Borrowed(voice)),
+ _ => None,
}
}
@@ -450,12 +465,6 @@ impl Channel {
server,
role_permissions,
..
- }
- | Channel::VoiceChannel {
- id,
- server,
- role_permissions,
- ..
} => {
db.set_channel_role_permission(id, role_id, permissions)
.await?;
@@ -502,7 +511,7 @@ impl Channel {
clear: remove.into_iter().map(|v| v.into()).collect(),
}
.p(match self {
- Self::TextChannel { server, .. } | Self::VoiceChannel { server, .. } => server.clone(),
+ Self::TextChannel { server, .. } => server.clone(),
_ => id,
})
.await;
@@ -514,17 +523,13 @@ impl Channel {
pub fn remove_field(&mut self, field: &FieldsChannel) {
match field {
FieldsChannel::Description => match self {
- Self::Group { description, .. }
- | Self::TextChannel { description, .. }
- | Self::VoiceChannel { description, .. } => {
+ Self::Group { description, .. } | Self::TextChannel { description, .. } => {
description.take();
}
_ => {}
},
FieldsChannel::Icon => match self {
- Self::Group { icon, .. }
- | Self::TextChannel { icon, .. }
- | Self::VoiceChannel { icon, .. } => {
+ Self::Group { icon, .. } | Self::TextChannel { icon, .. } => {
icon.take();
}
_ => {}
@@ -533,15 +538,17 @@ impl Channel {
Self::TextChannel {
default_permissions,
..
- }
- | Self::VoiceChannel {
- default_permissions,
- ..
} => {
default_permissions.take();
}
_ => {}
},
+ FieldsChannel::Voice => match self {
+ Self::TextChannel { voice, .. } => {
+ voice.take();
+ }
+ _ => {}
+ },
}
}
@@ -553,6 +560,7 @@ impl Channel {
}
/// Apply partial channel to channel
+ #[allow(deprecated)]
pub fn apply_options(&mut self, partial: PartialChannel) {
match self {
Self::SavedMessages { .. } => {}
@@ -601,15 +609,7 @@ impl Channel {
nsfw,
default_permissions,
role_permissions,
- ..
- }
- | Self::VoiceChannel {
- name,
- description,
- icon,
- nsfw,
- default_permissions,
- role_permissions,
+ voice,
..
} => {
if let Some(v) = partial.name {
@@ -635,12 +635,16 @@ impl Channel {
if let Some(v) = partial.default_permissions {
default_permissions.replace(v);
}
+
+ if let Some(v) = partial.voice {
+ voice.replace(v);
+ }
}
}
}
/// Acknowledge a message
- pub async fn ack(&self, user: &str, message: &str) -> Result<()> {
+ pub async fn ack(&self, user: &str, message: &str, amqp: &AMQP) -> Result<()> {
EventV1::ChannelAck {
id: self.id().to_string(),
user: user.to_string(),
@@ -649,17 +653,7 @@ impl Channel {
.private(user.to_string())
.await;
- #[cfg(feature = "tasks")]
- crate::tasks::ack::queue_ack(
- self.id().to_string(),
- user.to_string(),
- crate::tasks::ack::AckEvent::AckMessage {
- id: message.to_string(),
- },
- )
- .await;
-
- Ok(())
+ crate::util::acker::ack_channel(user, self.id(), message, amqp).await
}
/// Remove user from a group
@@ -777,6 +771,7 @@ impl IntoDocumentPath for FieldsChannel {
FieldsChannel::Description => "description",
FieldsChannel::Icon => "icon",
FieldsChannel::DefaultPermissions => "default_permissions",
+ FieldsChannel::Voice => "voice",
})
}
}
diff --git a/crates/core/database/src/models/channels/ops/mongodb.rs b/crates/core/database/src/models/channels/ops/mongodb.rs
index b247a6834..79612f8d7 100644
--- a/crates/core/database/src/models/channels/ops/mongodb.rs
+++ b/crates/core/database/src/models/channels/ops/mongodb.rs
@@ -184,7 +184,7 @@ impl AbstractChannels for MongoDb {
async fn delete_channel(&self, channel: &Channel) -> Result<()> {
let id = channel.id().to_string();
let server_id = match channel {
- Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
+ Channel::TextChannel { server, .. } => {
Some(server)
}
_ => None,
diff --git a/crates/core/database/src/models/channels/ops/reference.rs b/crates/core/database/src/models/channels/ops/reference.rs
index 7d5c9a59f..518808bcc 100644
--- a/crates/core/database/src/models/channels/ops/reference.rs
+++ b/crates/core/database/src/models/channels/ops/reference.rs
@@ -94,9 +94,6 @@ impl AbstractChannels for ReferenceDb {
match &mut channel {
Channel::TextChannel {
role_permissions, ..
- }
- | Channel::VoiceChannel {
- role_permissions, ..
} => {
if role_permissions.get(role_id).is_some() {
role_permissions.remove(role_id);
diff --git a/crates/core/database/src/models/emojis/model.rs b/crates/core/database/src/models/emojis/model.rs
index 8294d7c72..55d13e8c3 100644
--- a/crates/core/database/src/models/emojis/model.rs
+++ b/crates/core/database/src/models/emojis/model.rs
@@ -2,6 +2,7 @@ use std::collections::HashSet;
use std::str::FromStr;
use once_cell::sync::Lazy;
+use revolt_models::v0;
use revolt_result::Result;
use ulid::Ulid;
@@ -11,7 +12,7 @@ use crate::Database;
static PERMISSIBLE_EMOJIS: Lazy> = Lazy::new(|| {
include_str!("unicode_emoji.txt")
.split('\n')
- .map(|x| x.into())
+ .map(|x| x.replace('\u{FE0F}', ""))
.collect()
});
@@ -41,6 +42,12 @@ auto_derived!(
Server { id: String },
Detached,
}
+
+ /// Partial representation of an emoji
+ pub struct PartialEmoji {
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub name: Option,
+ }
);
#[allow(clippy::disallowed_methods)]
@@ -75,13 +82,34 @@ impl Emoji {
db.detach_emoji(&self).await
}
+ /// Update an emoji
+ pub async fn update(&mut self, db: &Database, partial: PartialEmoji) -> Result<()> {
+ if let Some(name) = partial.name.clone() {
+ self.name = name;
+ }
+
+ db.update_emoji(&self.id, &partial).await?;
+
+ EventV1::EmojiUpdate {
+ id: self.id.clone(),
+ data: v0::PartialEmoji {
+ name: partial.name.clone(),
+ },
+ }
+ .p(self.parent().to_string())
+ .await;
+
+ Ok(())
+ }
+
/// Check whether we can use a given emoji
pub async fn can_use(db: &Database, emoji: &str) -> Result {
if Ulid::from_str(emoji).is_ok() {
db.fetch_emoji(emoji).await?;
Ok(true)
} else {
- Ok(PERMISSIBLE_EMOJIS.contains(emoji))
+ let sanitized_emoji = emoji.replace('\u{FE0F}', "");
+ Ok(PERMISSIBLE_EMOJIS.contains(&sanitized_emoji))
}
}
}
diff --git a/crates/core/database/src/models/emojis/ops.rs b/crates/core/database/src/models/emojis/ops.rs
index 26e23acaf..d28f30d4e 100644
--- a/crates/core/database/src/models/emojis/ops.rs
+++ b/crates/core/database/src/models/emojis/ops.rs
@@ -1,6 +1,6 @@
use revolt_result::Result;
-use crate::Emoji;
+use crate::{Emoji, PartialEmoji};
#[cfg(feature = "mongodb")]
mod mongodb;
@@ -20,6 +20,9 @@ pub trait AbstractEmojis: Sync + Send {
/// Fetch emoji by their parent ids
async fn fetch_emoji_by_parent_ids(&self, parent_ids: &[String]) -> Result>;
+ /// Update emoji with new information
+ async fn update_emoji(&self, emoji_id: &str, partial: &PartialEmoji) -> Result<()>;
+
/// Detach an emoji by its id
async fn detach_emoji(&self, emoji: &Emoji) -> Result<()>;
}
diff --git a/crates/core/database/src/models/emojis/ops/mongodb.rs b/crates/core/database/src/models/emojis/ops/mongodb.rs
index 6dd3137b7..ad7b557ca 100644
--- a/crates/core/database/src/models/emojis/ops/mongodb.rs
+++ b/crates/core/database/src/models/emojis/ops/mongodb.rs
@@ -1,7 +1,7 @@
use bson::Document;
use revolt_result::Result;
-use crate::Emoji;
+use crate::{Emoji, PartialEmoji};
use crate::MongoDb;
use super::AbstractEmojis;
@@ -46,6 +46,11 @@ impl AbstractEmojis for MongoDb {
)
}
+ /// Update emoji with new information
+ async fn update_emoji(&self, emoji_id: &str, partial: &PartialEmoji) -> Result<()> {
+ query!(self, update_one_by_id, COL, emoji_id, partial, vec![], None).map(|_| ())
+ }
+
/// Detach an emoji by its id
async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> {
self.col::(COL)
diff --git a/crates/core/database/src/models/emojis/ops/reference.rs b/crates/core/database/src/models/emojis/ops/reference.rs
index 2f0c2a2df..2f9be4352 100644
--- a/crates/core/database/src/models/emojis/ops/reference.rs
+++ b/crates/core/database/src/models/emojis/ops/reference.rs
@@ -1,6 +1,6 @@
use revolt_result::Result;
-use crate::Emoji;
+use crate::{Emoji, PartialEmoji};
use crate::EmojiParent;
use crate::ReferenceDb;
@@ -54,6 +54,19 @@ impl AbstractEmojis for ReferenceDb {
.collect())
}
+ /// Update emoji with new information
+ async fn update_emoji(&self, emoji_id: &str, partial: &PartialEmoji) -> Result<()> {
+ let mut emojis = self.emojis.lock().await;
+ if let Some(emoji) = emojis.get_mut(emoji_id) {
+ if let Some(name) = partial.name.clone() {
+ emoji.name = name;
+ }
+ Ok(())
+ } else {
+ Err(create_error!(NotFound))
+ }
+ }
+
/// Detach an emoji by its id
async fn detach_emoji(&self, emoji: &Emoji) -> Result<()> {
let mut emojis = self.emojis.lock().await;
diff --git a/crates/core/database/src/models/emojis/unicode_emoji.txt b/crates/core/database/src/models/emojis/unicode_emoji.txt
index bc8036e38..1b08dbe0b 100644
--- a/crates/core/database/src/models/emojis/unicode_emoji.txt
+++ b/crates/core/database/src/models/emojis/unicode_emoji.txt
@@ -1,1590 +1,247 @@
-💯
-🔢
-😀
-😃
-😄
-😁
-😆
-😆
-😅
-🤣
-😂
-🙂
-🙃
-😉
-😊
-😇
-🥰
-😍
-🤩
-😘
-😗
-☺️
-😚
-😙
-🥲
-😋
-😛
-😜
-🤪
-😝
-🤑
-🤗
-🤭
-🤫
-🤔
-🤐
-🤨
-😐
-😑
-😶
-😏
-😒
-🙄
-😬
-🤥
-😌
-😔
-😪
-🤤
-😴
-😷
-🤒
-🤕
-🤢
-🤮
-🤧
-🥵
-🥶
-🥴
-😵
-🤯
-🤠
-🥳
-🥸
-😎
-🤓
-🧐
-😕
-😟
-🙁
-☹️
-😮
-😯
-😲
-😳
-🥺
-😦
-😧
-😨
-😰
-😥
-😢
-😭
-😱
-😖
-😣
-😞
-😓
-😩
-😫
-🥱
-😤
-😡
-😡
-😠
-🤬
-😈
-👿
-💀
-☠️
-💩
-💩
-💩
-🤡
-👹
-👺
-👻
-👽
-👾
-🤖
-😺
-😸
-😹
-😻
-😼
-😽
-🙀
-😿
-😾
-🙈
-🙉
-🙊
-💋
-💌
-💘
-💝
-💖
-💗
-💓
-💞
-💕
-💟
-❣️
-💔
-❤️
-🧡
-💛
-💚
-💙
-💜
-🤎
-🖤
-🤍
-💢
-💥
-💥
-💫
-💦
-💨
-🕳️
-💣
-💬
-👁️🗨️
-🗨️
-🗯️
-💭
-💤
-👋
-🤚
-🖐️
-✋
-✋
-🖖
-👌
-🤌
-🤏
-✌️
-🤞
-🤟
-🤘
-🤙
-👈
-👉
-👆
-🖕
-🖕
-👇
+*️⃣
+0️⃣
+1️⃣
+2️⃣
+3️⃣
+4️⃣
+5️⃣
+6️⃣
+7️⃣
+8️⃣
+9️⃣
+©️
+®️
+‼️
+⁉️
+™️
+ℹ️
+↔️
+↕️
+↖️
+↗️
+↘️
+↙️
+↩️
+↪️
+⌚
+⌛
+⌨️
+⏏️
+⏩
+⏪
+⏫
+⏬
+⏭️
+⏮️
+⏯️
+⏰
+⏱️
+⏲️
+⏳
+⏸️
+⏹️
+⏺️
+Ⓜ️
+▪️
+▫️
+▶️
+◀️
+◻️
+◼️
+◽
+◾
+☀️
+☁️
+☂️
+☃️
+☄️
+☎️
+☑️
+☔
+☕
+☘️
☝️
-👍
-👍
-👎
-👎
-✊
-✊
-👊
-👊
-👊
-🤛
-🤜
-👏
-🙌
-👐
-🤲
-🤝
-🙏
+☝🏻
+☝🏼
+☝🏽
+☝🏾
+☝🏿
+☠️
+☢️
+☣️
+☦️
+☪️
+☮️
+☯️
+☸️
+☹️
+☺️
+♀️
+♂️
+♈
+♉
+♊
+♋
+♌
+♍
+♎
+♏
+♐
+♑
+♒
+♓
+♟️
+♠️
+♣️
+♥️
+♦️
+♨️
+♻️
+♾️
+♿
+⚒️
+⚓
+⚔️
+⚕️
+⚖️
+⚗️
+⚙️
+⚛️
+⚜️
+⚠️
+⚡
+⚧️
+⚪
+⚫
+⚰️
+⚱️
+⚽
+⚾
+⛄
+⛅
+⛈️
+⛎
+⛏️
+⛑️
+⛓️
+⛓️💥
+⛔
+⛩️
+⛪
+⛰️
+⛱️
+⛲
+⛳
+⛴️
+⛵
+⛷️
+⛸️
+⛹️
+⛹️♀️
+⛹️♂️
+⛹🏻
+⛹🏻♀️
+⛹🏻♂️
+⛹🏼
+⛹🏼♀️
+⛹🏼♂️
+⛹🏽
+⛹🏽♀️
+⛹🏽♂️
+⛹🏾
+⛹🏾♀️
+⛹🏾♂️
+⛹🏿
+⛹🏿♀️
+⛹🏿♂️
+⛺
+⛽
+✂️
+✅
+✈️
+✉️
+✊
+✊🏻
+✊🏼
+✊🏽
+✊🏾
+✊🏿
+✋
+✋🏻
+✋🏼
+✋🏽
+✋🏾
+✋🏿
+✌️
+✌🏻
+✌🏼
+✌🏽
+✌🏾
+✌🏿
✍️
-💅
-🤳
-💪
-🦾
-🦿
-🦵
-🦶
-👂
-🦻
-👃
-🧠
-🫀
-🫁
-🦷
-🦴
-👀
-👁️
-👅
-👄
-👶
-🧒
-👦
-👧
-🧑
-👱
-👨
-🧔
-👨🦰
-👨🦱
-👨🦳
-👨🦲
-👩
-👩🦰
-🧑🦰
-👩🦱
-🧑🦱
-👩🦳
-🧑🦳
-👩🦲
-🧑🦲
-👱♀️
-👱♀️
-👱♂️
-🧓
-👴
-👵
-🙍
-🙍♂️
-🙍♀️
-🙎
-🙎♂️
-🙎♀️
-🙅
-🙅♂️
-🙅♂️
-🙅♀️
-🙅♀️
-🙆
-🙆♂️
-🙆♀️
-💁
-💁
-💁♂️
-💁♂️
-💁♀️
-💁♀️
-🙋
-🙋♂️
-🙋♀️
-🧏
-🧏♂️
-🧏♀️
-🙇
-🙇♂️
-🙇♀️
-🤦
-🤦♂️
-🤦♀️
-🤷
-🤷♂️
-🤷♀️
-🧑⚕️
-👨⚕️
-👩⚕️
-🧑🎓
-👨🎓
-👩🎓
-🧑🏫
-👨🏫
-👩🏫
-🧑⚖️
-👨⚖️
-👩⚖️
-🧑🌾
-👨🌾
-👩🌾
-🧑🍳
-👨🍳
-👩🍳
-🧑🔧
-👨🔧
-👩🔧
-🧑🏭
-👨🏭
-👩🏭
-🧑💼
-👨💼
-👩💼
-🧑🔬
-👨🔬
-👩🔬
-🧑💻
-👨💻
-👩💻
-🧑🎤
-👨🎤
-👩🎤
-🧑🎨
-👨🎨
-👩🎨
-🧑✈️
-👨✈️
-👩✈️
-🧑🚀
-👨🚀
-👩🚀
-🧑🚒
-👨🚒
-👩🚒
-👮
-👮
-👮♂️
-👮♀️
-🕵️
-🕵️♂️
-🕵️♀️
-💂
-💂♂️
-💂♀️
-🥷
-👷
-👷♂️
-👷♀️
-🤴
-👸
-👳
-👳♂️
-👳♀️
-👲
-🧕
-🤵
-🤵♂️
-🤵♀️
-👰
-👰♂️
-👰♀️
-👰♀️
-🤰
-🤱
-👩🍼
-👨🍼
-🧑🍼
-👼
-🎅
-🤶
-🧑🎄
-🦸
-🦸♂️
-🦸♀️
-🦹
-🦹♂️
-🦹♀️
-🧙
-🧙♂️
-🧙♀️
-🧚
-🧚♂️
-🧚♀️
-🧛
-🧛♂️
-🧛♀️
-🧜
-🧜♂️
-🧜♀️
-🧝
-🧝♂️
-🧝♀️
-🧞
-🧞♂️
-🧞♀️
-🧟
-🧟♂️
-🧟♀️
-💆
-💆♂️
-💆♀️
-💇
-💇♂️
-💇♀️
-🚶
-🚶♂️
-🚶♀️
-🧍
-🧍♂️
-🧍♀️
-🧎
-🧎♂️
-🧎♀️
-🧑🦯
-👨🦯
-👩🦯
-🧑🦼
-👨🦼
-👩🦼
-🧑🦽
-👨🦽
-👩🦽
-🏃
-🏃
-🏃♂️
-🏃♀️
-💃
-💃
-🕺
-🕴️
-👯
-👯♂️
-👯♀️
-🧖
-🧖♂️
-🧖♀️
-🧗
-🧗♂️
-🧗♀️
-🤺
-🏇
-⛷️
-🏂
-🏌️
-🏌️♂️
-🏌️♀️
-🏄
-🏄♂️
-🏄♀️
-🚣
-🚣♂️
-🚣♀️
-🏊
-🏊♂️
-🏊♀️
-⛹️
-⛹️♂️
-⛹️♂️
-⛹️♀️
-⛹️♀️
-🏋️
-🏋️♂️
-🏋️♀️
-🚴
-🚴♂️
-🚴♀️
-🚵
-🚵♂️
-🚵♀️
-🤸
-🤸♂️
-🤸♀️
-🤼
-🤼♂️
-🤼♀️
-🤽
-🤽♂️
-🤽♀️
-🤾
-🤾♂️
-🤾♀️
-🤹
-🤹♂️
-🤹♀️
-🧘
-🧘♂️
-🧘♀️
-🛀
-🛌
-🧑🤝🧑
-👭
-👫
-👬
-💏
-👩❤️💋👨
-👨❤️💋👨
-👩❤️💋👩
-💑
-👩❤️👨
-👨❤️👨
-👩❤️👩
-👪
-👨👩👦
-👨👩👧
-👨👩👧👦
-👨👩👦👦
-👨👩👧👧
-👨👨👦
-👨👨👧
-👨👨👧👦
-👨👨👦👦
-👨👨👧👧
-👩👩👦
-👩👩👧
-👩👩👧👦
-👩👩👦👦
-👩👩👧👧
-👨👦
-👨👦👦
-👨👧
-👨👧👦
-👨👧👧
-👩👦
-👩👦👦
-👩👧
-👩👧👦
-👩👧👧
-🗣️
-👤
-👥
-🫂
-👣
-🐵
-🐒
-🦍
-🦧
-🐶
-🐕
-🦮
-🐕🦺
-🐩
-🐺
-🦊
-🦝
-🐱
-🐈
-🐈⬛
-🦁
-🐯
-🐅
-🐆
-🐴
-🐎
-🦄
-🦓
-🦌
-🦬
-🐮
-🐂
-🐃
-🐄
-🐷
-🐖
-🐗
-🐽
-🐏
-🐑
-🐐
-🐪
-🐫
-🦙
-🦒
-🐘
-🦣
-🦏
-🦛
-🐭
-🐁
-🐀
-🐹
-🐰
-🐇
-🐿️
-🦫
-🦔
-🦇
-🐻
-🐻❄️
-🐨
-🐼
-🦥
-🦦
-🦨
-🦘
-🦡
-🐾
-🐾
-🦃
-🐔
-🐓
-🐣
-🐤
-🐥
-🐦
-🐧
-🕊️
-🦅
-🦆
-🦢
-🦉
-🦤
-🪶
-🦩
-🦚
-🦜
-🐸
-🐊
-🐢
-🦎
-🐍
-🐲
-🐉
-🦕
-🦖
-🐳
-🐋
-🐬
-🐬
-🦭
-🐟
-🐠
-🐡
-🦈
-🐙
-🐚
-🐌
-🦋
-🐛
-🐜
-🐝
-🐝
-🪲
-🐞
-🦗
-🪳
-🕷️
-🕸️
-🦂
-🦟
-🪰
-🪱
-🦠
-💐
-🌸
-💮
-🏵️
-🌹
-🥀
-🌺
-🌻
-🌼
-🌷
-🌱
-🪴
-🌲
-🌳
-🌴
-🌵
-🌾
-🌿
-☘️
-🍀
-🍁
-🍂
-🍃
-🍇
-🍈
-🍉
-🍊
-🍊
-🍊
-🍋
-🍌
-🍌
-🍍
-🥭
-🍎
-🍏
-🍐
-🍑
-🍒
-🍓
-🫐
-🥝
-🍅
-🫒
-🥥
-🥑
-🍆
-🥔
-🥕
-🌽
-🌶️
-🫑
-🥒
-🥬
-🥦
-🧄
-🧅
-🍄
-🥜
-🌰
-🍞
-🥐
-🥖
-🫓
-🥨
-🥯
-🥞
-🧇
-🧀
-🍖
-🍗
-🥩
-🥓
-🍔
-🍟
-🍕
-🌭
-🥪
-🌮
-🌯
-🫔
-🥙
-🧆
-🥚
-🍳
-🥘
-🍲
-🫕
-🥣
-🥗
-🍿
-🧈
-🧂
-🥫
-🍱
-🍘
-🍙
-🍚
-🍛
-🍜
-🍝
-🍠
-🍢
-🍣
-🍤
-🍥
-🥮
-🍡
-🥟
-🥠
-🥡
-🦀
-🦞
-🦐
-🦑
-🦪
-🍦
-🍧
-🍨
-🍩
-🍪
-🎂
-🍰
-🧁
-🥧
-🍫
-🍬
-🍭
-🍮
-🍯
-🍼
-🥛
-☕
-🫖
-🍵
-🍶
-🍾
-🍷
-🍸
-🍹
-🍺
-🍻
-🥂
-🥃
-🥤
-🧋
-🧃
-🧉
-🧊
-🥢
-🍽️
-🍴
-🥄
-🔪
-🔪
-🏺
-🌍
-🌎
-🌏
-🌐
-🗺️
-🗾
-🧭
-🏔️
-⛰️
-🌋
-🗻
-🏕️
-🏖️
-🏜️
-🏝️
-🏞️
-🏟️
-🏛️
-🏗️
-🧱
-🪨
-🪵
-🛖
-🏘️
-🏚️
-🏠
-🏡
-🏢
-🏣
-🏤
-🏥
-🏦
-🏨
-🏩
-🏪
-🏫
-🏬
-🏭
-🏯
-🏰
-💒
-🗼
-🗽
-⛪
-🕌
-🛕
-🕍
-⛩️
-🕋
-⛲
-⛺
-🌁
-🌃
-🏙️
-🌄
-🌅
-🌆
-🌇
-🌉
-♨️
-🎠
-🎡
-🎢
-💈
-🎪
-🚂
-🚃
-🚄
-🚅
-🚆
-🚇
-🚈
-🚉
-🚊
-🚝
-🚞
-🚋
-🚌
-🚍
-🚎
-🚎
-🚐
-🚑
-🚒
-🚓
-🚔
-🚕
-🚖
-🚗
-🚗
-🚘
-🚙
-🛻
-🚚
-🚛
-🚜
-🏎️
-🏍️
-🛵
-🦽
-🦼
-🛺
-🚲
-🛴
-🛹
-🛼
-🚏
-🛣️
-🛤️
-🛢️
-⛽
-🚨
-🚥
-🚦
-🛑
-🚧
-⚓
-⛵
-⛵
-🛶
-🚤
-🛳️
-⛴️
-🛥️
-🚢
-✈️
-🛩️
-🛫
-🛬
-🪂
-💺
-🚁
-🚟
-🚠
-🚡
-🛰️
-🚀
-🛸
-🛎️
-🧳
-⌛
-⏳
-⌚
-⏰
-⏱️
-⏲️
-🕰️
-🕛
-🕧
-🕐
-🕜
-🕑
-🕝
-🕒
-🕞
-🕓
-🕟
-🕔
-🕠
-🕕
-🕡
-🕖
-🕢
-🕗
-🕣
-🕘
-🕤
-🕙
-🕥
-🕚
-🕦
-🌑
-🌒
-🌓
-🌔
-🌔
-🌕
-🌖
-🌗
-🌘
-🌙
-🌚
-🌛
-🌜
-🌡️
-☀️
-🌝
-🌞
-🪐
-⭐
-🌟
-🌠
-🌌
-☁️
-⛅
-⛈️
-🌤️
-🌥️
-🌦️
-🌧️
-🌨️
-🌩️
-🌪️
-🌫️
-🌬️
-🌀
-🌈
-🌂
-☂️
-☔
-⛱️
-⚡
-❄️
-☃️
-⛄
-☄️
-🔥
-💧
-🌊
-🎃
-🎄
-🎆
-🎇
-🧨
-✨
-🎈
-🎉
-🎊
-🎋
-🎍
-🎎
-🎏
-🎐
-🎑
-🧧
-🎀
-🎁
-🎗️
-🎟️
-🎫
-🎖️
-🏆
-🏅
-🥇
-🥈
-🥉
-⚽
-⚾
-🥎
-🏀
-🏐
-🏈
-🏉
-🎾
-🥏
-🎳
-🏏
-🏑
-🏒
-🥍
-🏓
-🏸
-🥊
-🥋
-🥅
-⛳
-⛸️
-🎣
-🤿
-🎽
-🎿
-🛷
-🥌
-🎯
-🪀
-🪁
-🎱
-🔮
-🪄
-🧿
-🎮
-🕹️
-🎰
-🎲
-🧩
-🧸
-🪅
-🪆
-♠️
-♥️
-♦️
-♣️
-♟️
-🃏
-🀄
-🎴
-🎭
-🖼️
-🎨
-🧵
-🪡
-🧶
-🪢
-👓
-🕶️
-🥽
-🥼
-🦺
-👔
-👕
-👕
-👖
-🧣
-🧤
-🧥
-🧦
-👗
-👘
-🥻
-🩱
-🩲
-🩳
-👙
-👚
-👛
-👜
-👝
-🛍️
-🎒
-🩴
-👞
-👞
-👟
-🥾
-🥿
-👠
-👡
-🩰
-👢
-👑
-👒
-🎩
-🎓
-🧢
-🪖
-⛑️
-📿
-💄
-💍
-💎
-🔇
-🔈
-🔉
-🔊
-📢
-📣
-📯
-🔔
-🔕
-🎼
-🎵
-🎶
-🎙️
-🎚️
-🎛️
-🎤
-🎧
-📻
-🎷
-🪗
-🎸
-🎹
-🎺
-🎻
-🪕
-🥁
-🪘
-📱
-📲
-☎️
-☎️
-📞
-📟
-📠
-🔋
-🔌
-💻
-🖥️
-🖨️
-⌨️
-🖱️
-🖲️
-💽
-💾
-💿
-📀
-🧮
-🎥
-🎞️
-📽️
-🎬
-📺
-📷
-📸
-📹
-📼
-🔍
-🔎
-🕯️
-💡
-🔦
-🏮
-🏮
-🪔
-📔
-📕
-📖
-📖
-📗
-📘
-📙
-📚
-📓
-📒
-📃
-📜
-📄
-📰
-🗞️
-📑
-🔖
-🏷️
-💰
-🪙
-💴
-💵
-💶
-💷
-💸
-💳
-🧾
-💹
-✉️
-📧
-📧
-📨
-📩
-📤
-📥
-📦
-📫
-📪
-📬
-📭
-📮
-🗳️
-✏️
-✒️
-🖋️
-🖊️
-🖌️
-🖍️
-📝
-📝
-💼
-📁
-📂
-🗂️
-📅
-📆
-🗒️
-🗓️
-📇
-📈
-📉
-📊
-📋
-📌
-📍
-📎
-🖇️
-📏
-📐
-✂️
-🗃️
-🗄️
-🗑️
-🔒
-🔓
-🔏
-🔐
-🔑
-🗝️
-🔨
-🪓
-⛏️
-⚒️
-🛠️
-🗡️
-⚔️
-🔫
-🪃
-🏹
-🛡️
-🪚
-🔧
-🪛
-🔩
-⚙️
-🗜️
-⚖️
-🦯
-🔗
-⛓️
-🪝
-🧰
-🧲
-🪜
-⚗️
-🧪
-🧫
-🧬
-🔬
-🔭
-📡
-💉
-🩸
-💊
-🩹
-🩺
-🚪
-🛗
-🪞
-🪟
-🛏️
-🛋️
-🪑
-🚽
-🪠
-🚿
-🛁
-🪤
-🪒
-🧴
-🧷
-🧹
-🧺
-🧻
-🪣
-🧼
-🪥
-🧽
-🧯
-🛒
-🚬
-⚰️
-🪦
-⚱️
-🗿
-🪧
-🏧
-🚮
-🚰
-♿
-🚹
-🚺
-🚻
-🚼
-🚾
-🛂
-🛃
-🛄
-🛅
-⚠️
-🚸
-⛔
-🚫
-🚳
-🚭
-🚯
-🚱
-🚷
-📵
-🔞
-☢️
-☣️
-⬆️
-↗️
-➡️
-↘️
-⬇️
-↙️
-⬅️
-↖️
-↕️
-↔️
-↩️
-↪️
-⤴️
-⤵️
-🔃
-🔄
-🔙
-🔚
-🔛
-🔜
-🔝
-🛐
-⚛️
-🕉️
-✡️
-☸️
-☯️
-✝️
-☦️
-☪️
-☮️
-🕎
-🔯
-♈
-♉
-♊
-♋
-♌
-♍
-♎
-♏
-♐
-♑
-♒
-♓
-⛎
-🔀
-🔁
-🔂
-▶️
-⏩
-⏭️
-⏯️
-◀️
-⏪
-⏮️
-🔼
-⏫
-🔽
-⏬
-⏸️
-⏹️
-⏺️
-⏏️
-🎦
-🔅
-🔆
-📶
-📳
-📴
-♀️
-♂️
-⚧️
+✍🏻
+✍🏼
+✍🏽
+✍🏾
+✍🏿
+✏️
+✒️
+✔️
✖️
-➕
-➖
-➗
-♾️
-‼️
-⁉️
+✝️
+✡️
+✨
+✳️
+✴️
+❄️
+❇️
+❌
+❎
❓
❔
❕
❗
-❗
-〰️
-💱
-💲
-⚕️
-♻️
-⚜️
-🔱
-📛
-🔰
-⭕
-✅
-☑️
-✔️
-❌
-❎
+❣️
+❤️
+❤️🔥
+❤️🩹
+➕
+➖
+➗
+➡️
➰
➿
+⤴️
+⤵️
+⬅️
+⬆️
+⬇️
+⬛
+⬜
+⭐
+⭕
+〰️
〽️
-✳️
-✴️
-❇️
-©️
-®️
-™️
-#️⃣
-*️⃣
-0️⃣
-1️⃣
-2️⃣
-3️⃣
-4️⃣
-5️⃣
-6️⃣
-7️⃣
-8️⃣
-9️⃣
-🔟
-🔠
-🔡
-🔣
-🔤
+㊗️
+㊙️
+🀄
+🃏
🅰️
-🆎
🅱️
+🅾️
+🅿️
+🆎
🆑
🆒
🆓
-ℹ️
🆔
-Ⓜ️
🆕
🆖
-🅾️
🆗
-🅿️
🆘
🆙
🆚
-🈁
-🈂️
-🈷️
-🈶
-🈯
-🉐
-🈹
-🈚
-🈲
-🉑
-🈸
-🈴
-🈳
-㊗️
-㊙️
-🈺
-🈵
-🔴
-🟠
-🟡
-🟢
-🔵
-🟣
-🟤
-⚫
-⚪
-🟥
-🟧
-🟨
-🟩
-🟦
-🟪
-🟫
-⬛
-⬜
-◼️
-◻️
-◾
-◽
-▪️
-▫️
-🔶
-🔷
-🔸
-🔹
-🔺
-🔻
-💠
-🔘
-🔳
-🔲
-🏁
-🚩
-🎌
-🏴
-🏳️
-🏳️🌈
-🏳️⚧️
-🏴☠️
+🇦
🇦🇨
🇦🇩
🇦🇪
@@ -1602,6 +259,7 @@
🇦🇼
🇦🇽
🇦🇿
+🇧
🇧🇦
🇧🇧
🇧🇩
@@ -1623,6 +281,7 @@
🇧🇼
🇧🇾
🇧🇿
+🇨
🇨🇦
🇨🇨
🇨🇩
@@ -1636,6 +295,7 @@
🇨🇳
🇨🇴
🇨🇵
+🇨🇶
🇨🇷
🇨🇺
🇨🇻
@@ -1643,6 +303,7 @@
🇨🇽
🇨🇾
🇨🇿
+🇩
🇩🇪
🇩🇬
🇩🇯
@@ -1650,6 +311,7 @@
🇩🇲
🇩🇴
🇩🇿
+🇪
🇪🇦
🇪🇨
🇪🇪
@@ -1659,16 +321,16 @@
🇪🇸
🇪🇹
🇪🇺
-🇪🇺
+🇫
🇫🇮
🇫🇯
🇫🇰
🇫🇲
🇫🇴
🇫🇷
+🇬
🇬🇦
🇬🇧
-🇬🇧
🇬🇩
🇬🇪
🇬🇫
@@ -1686,12 +348,14 @@
🇬🇺
🇬🇼
🇬🇾
+🇭
🇭🇰
🇭🇲
🇭🇳
🇭🇷
🇭🇹
🇭🇺
+🇮
🇮🇨
🇮🇩
🇮🇪
@@ -1703,10 +367,12 @@
🇮🇷
🇮🇸
🇮🇹
+🇯
🇯🇪
🇯🇲
🇯🇴
🇯🇵
+🇰
🇰🇪
🇰🇬
🇰🇭
@@ -1718,6 +384,7 @@
🇰🇼
🇰🇾
🇰🇿
+🇱
🇱🇦
🇱🇧
🇱🇨
@@ -1729,6 +396,7 @@
🇱🇺
🇱🇻
🇱🇾
+🇲
🇲🇦
🇲🇨
🇲🇩
@@ -1752,6 +420,7 @@
🇲🇽
🇲🇾
🇲🇿
+🇳
🇳🇦
🇳🇨
🇳🇪
@@ -1764,7 +433,9 @@
🇳🇷
🇳🇺
🇳🇿
+🇴
🇴🇲
+🇵
🇵🇦
🇵🇪
🇵🇫
@@ -1779,12 +450,15 @@
🇵🇹
🇵🇼
🇵🇾
+🇶
🇶🇦
+🇷
🇷🇪
🇷🇴
🇷🇸
🇷🇺
🇷🇼
+🇸
🇸🇦
🇸🇧
🇸🇨
@@ -1806,6 +480,7 @@
🇸🇽
🇸🇾
🇸🇿
+🇹
🇹🇦
🇹🇨
🇹🇩
@@ -1823,6 +498,7 @@
🇹🇻
🇹🇼
🇹🇿
+🇺
🇺🇦
🇺🇬
🇺🇲
@@ -1830,6 +506,7 @@
🇺🇸
🇺🇾
🇺🇿
+🇻
🇻🇦
🇻🇨
🇻🇪
@@ -1837,14 +514,3456 @@
🇻🇮
🇻🇳
🇻🇺
+🇼
🇼🇫
🇼🇸
+🇽
🇽🇰
+🇾
🇾🇪
🇾🇹
+🇿
🇿🇦
🇿🇲
🇿🇼
+🈁
+🈂️
+🈚
+🈯
+🈲
+🈳
+🈴
+🈵
+🈶
+🈷️
+🈸
+🈹
+🈺
+🉐
+🉑
+🌀
+🌁
+🌂
+🌃
+🌄
+🌅
+🌆
+🌇
+🌈
+🌉
+🌊
+🌋
+🌌
+🌍
+🌎
+🌏
+🌐
+🌑
+🌒
+🌓
+🌔
+🌕
+🌖
+🌗
+🌘
+🌙
+🌚
+🌛
+🌜
+🌝
+🌞
+🌟
+🌠
+🌡️
+🌤️
+🌥️
+🌦️
+🌧️
+🌨️
+🌩️
+🌪️
+🌫️
+🌬️
+🌭
+🌮
+🌯
+🌰
+🌱
+🌲
+🌳
+🌴
+🌵
+🌶️
+🌷
+🌸
+🌹
+🌺
+🌻
+🌼
+🌽
+🌾
+🌿
+🍀
+🍁
+🍂
+🍃
+🍄
+🍄🟫
+🍅
+🍆
+🍇
+🍈
+🍉
+🍊
+🍋
+🍋🟩
+🍌
+🍍
+🍎
+🍏
+🍐
+🍑
+🍒
+🍓
+🍔
+🍕
+🍖
+🍗
+🍘
+🍙
+🍚
+🍛
+🍜
+🍝
+🍞
+🍟
+🍠
+🍡
+🍢
+🍣
+🍤
+🍥
+🍦
+🍧
+🍨
+🍩
+🍪
+🍫
+🍬
+🍭
+🍮
+🍯
+🍰
+🍱
+🍲
+🍳
+🍴
+🍵
+🍶
+🍷
+🍸
+🍹
+🍺
+🍻
+🍼
+🍽️
+🍾
+🍿
+🎀
+🎁
+🎂
+🎃
+🎄
+🎅
+🎅🏻
+🎅🏼
+🎅🏽
+🎅🏾
+🎅🏿
+🎆
+🎇
+🎈
+🎉
+🎊
+🎋
+🎌
+🎍
+🎎
+🎏
+🎐
+🎑
+🎒
+🎓
+🎖️
+🎗️
+🎙️
+🎚️
+🎛️
+🎞️
+🎟️
+🎠
+🎡
+🎢
+🎣
+🎤
+🎥
+🎦
+🎧
+🎨
+🎩
+🎪
+🎫
+🎬
+🎭
+🎮
+🎯
+🎰
+🎱
+🎲
+🎳
+🎴
+🎵
+🎶
+🎷
+🎸
+🎹
+🎺
+🎻
+🎼
+🎽
+🎾
+🎿
+🏀
+🏁
+🏂
+🏂🏻
+🏂🏼
+🏂🏽
+🏂🏾
+🏂🏿
+🏃
+🏃♀️
+🏃♀️➡️
+🏃♂️
+🏃♂️➡️
+🏃➡️
+🏃🏻
+🏃🏻♀️
+🏃🏻♀️➡️
+🏃🏻♂️
+🏃🏻♂️➡️
+🏃🏻➡️
+🏃🏼
+🏃🏼♀️
+🏃🏼♀️➡️
+🏃🏼♂️
+🏃🏼♂️➡️
+🏃🏼➡️
+🏃🏽
+🏃🏽♀️
+🏃🏽♀️➡️
+🏃🏽♂️
+🏃🏽♂️➡️
+🏃🏽➡️
+🏃🏾
+🏃🏾♀️
+🏃🏾♀️➡️
+🏃🏾♂️
+🏃🏾♂️➡️
+🏃🏾➡️
+🏃🏿
+🏃🏿♀️
+🏃🏿♀️➡️
+🏃🏿♂️
+🏃🏿♂️➡️
+🏃🏿➡️
+🏄
+🏄♀️
+🏄♂️
+🏄🏻
+🏄🏻♀️
+🏄🏻♂️
+🏄🏼
+🏄🏼♀️
+🏄🏼♂️
+🏄🏽
+🏄🏽♀️
+🏄🏽♂️
+🏄🏾
+🏄🏾♀️
+🏄🏾♂️
+🏄🏿
+🏄🏿♀️
+🏄🏿♂️
+🏅
+🏆
+🏇
+🏇🏻
+🏇🏼
+🏇🏽
+🏇🏾
+🏇🏿
+🏈
+🏉
+🏊
+🏊♀️
+🏊♂️
+🏊🏻
+🏊🏻♀️
+🏊🏻♂️
+🏊🏼
+🏊🏼♀️
+🏊🏼♂️
+🏊🏽
+🏊🏽♀️
+🏊🏽♂️
+🏊🏾
+🏊🏾♀️
+🏊🏾♂️
+🏊🏿
+🏊🏿♀️
+🏊🏿♂️
+🏋️
+🏋️♀️
+🏋️♂️
+🏋🏻
+🏋🏻♀️
+🏋🏻♂️
+🏋🏼
+🏋🏼♀️
+🏋🏼♂️
+🏋🏽
+🏋🏽♀️
+🏋🏽♂️
+🏋🏾
+🏋🏾♀️
+🏋🏾♂️
+🏋🏿
+🏋🏿♀️
+🏋🏿♂️
+🏌️
+🏌️♀️
+🏌️♂️
+🏌🏻
+🏌🏻♀️
+🏌🏻♂️
+🏌🏼
+🏌🏼♀️
+🏌🏼♂️
+🏌🏽
+🏌🏽♀️
+🏌🏽♂️
+🏌🏾
+🏌🏾♀️
+🏌🏾♂️
+🏌🏿
+🏌🏿♀️
+🏌🏿♂️
+🏍️
+🏎️
+🏏
+🏐
+🏑
+🏒
+🏓
+🏔️
+🏕️
+🏖️
+🏗️
+🏘️
+🏙️
+🏚️
+🏛️
+🏜️
+🏝️
+🏞️
+🏟️
+🏠
+🏡
+🏢
+🏣
+🏤
+🏥
+🏦
+🏧
+🏨
+🏩
+🏪
+🏫
+🏬
+🏭
+🏮
+🏯
+🏰
+🏳️
+🏳️⚧️
+🏳️🌈
+🏴
+🏴☠️
🏴
🏴
-🏴
\ No newline at end of file
+🏴
+🏵️
+🏷️
+🏸
+🏹
+🏺
+🐀
+🐁
+🐂
+🐃
+🐄
+🐅
+🐆
+🐇
+🐈
+🐈⬛
+🐉
+🐊
+🐋
+🐌
+🐍
+🐎
+🐏
+🐐
+🐑
+🐒
+🐓
+🐔
+🐕
+🐕🦺
+🐖
+🐗
+🐘
+🐙
+🐚
+🐛
+🐜
+🐝
+🐞
+🐟
+🐠
+🐡
+🐢
+🐣
+🐤
+🐥
+🐦
+🐦⬛
+🐦🔥
+🐧
+🐨
+🐩
+🐪
+🐫
+🐬
+🐭
+🐮
+🐯
+🐰
+🐱
+🐲
+🐳
+🐴
+🐵
+🐶
+🐷
+🐸
+🐹
+🐺
+🐻
+🐻❄️
+🐼
+🐽
+🐾
+🐿️
+👀
+👁️
+👁️🗨️
+👂
+👂🏻
+👂🏼
+👂🏽
+👂🏾
+👂🏿
+👃
+👃🏻
+👃🏼
+👃🏽
+👃🏾
+👃🏿
+👄
+👅
+👆
+👆🏻
+👆🏼
+👆🏽
+👆🏾
+👆🏿
+👇
+👇🏻
+👇🏼
+👇🏽
+👇🏾
+👇🏿
+👈
+👈🏻
+👈🏼
+👈🏽
+👈🏾
+👈🏿
+👉
+👉🏻
+👉🏼
+👉🏽
+👉🏾
+👉🏿
+👊
+👊🏻
+👊🏼
+👊🏽
+👊🏾
+👊🏿
+👋
+👋🏻
+👋🏼
+👋🏽
+👋🏾
+👋🏿
+👌
+👌🏻
+👌🏼
+👌🏽
+👌🏾
+👌🏿
+👍
+👍🏻
+👍🏼
+👍🏽
+👍🏾
+👍🏿
+👎
+👎🏻
+👎🏼
+👎🏽
+👎🏾
+👎🏿
+👏
+👏🏻
+👏🏼
+👏🏽
+👏🏾
+👏🏿
+👐
+👐🏻
+👐🏼
+👐🏽
+👐🏾
+👐🏿
+👑
+👒
+👓
+👔
+👕
+👖
+👗
+👘
+👙
+👚
+👛
+👜
+👝
+👞
+👟
+👠
+👡
+👢
+👣
+👤
+👥
+👦
+👦🏻
+👦🏼
+👦🏽
+👦🏾
+👦🏿
+👧
+👧🏻
+👧🏼
+👧🏽
+👧🏾
+👧🏿
+👨
+👨⚕️
+👨⚖️
+👨✈️
+👨❤️👨
+👨❤️💋👨
+👨🌾
+👨🍳
+👨🍼
+👨🎓
+👨🎤
+👨🎨
+👨🏫
+👨🏭
+👨👦
+👨👦👦
+👨👧
+👨👧👦
+👨👧👧
+👨👨👦
+👨👨👦👦
+👨👨👧
+👨👨👧👦
+👨👨👧👧
+👨👩👦
+👨👩👦👦
+👨👩👧
+👨👩👧👦
+👨👩👧👧
+👨💻
+👨💼
+👨🔧
+👨🔬
+👨🚀
+👨🚒
+👨🦯
+👨🦯➡️
+👨🦰
+👨🦱
+👨🦲
+👨🦳
+👨🦼
+👨🦼➡️
+👨🦽
+👨🦽➡️
+👨🏻
+👨🏻⚕️
+👨🏻⚖️
+👨🏻✈️
+👨🏻❤️👨🏻
+👨🏻❤️👨🏼
+👨🏻❤️👨🏽
+👨🏻❤️👨🏾
+👨🏻❤️👨🏿
+👨🏻❤️💋👨🏻
+👨🏻❤️💋👨🏼
+👨🏻❤️💋👨🏽
+👨🏻❤️💋👨🏾
+👨🏻❤️💋👨🏿
+👨🏻🌾
+👨🏻🍳
+👨🏻🍼
+👨🏻🎓
+👨🏻🎤
+👨🏻🎨
+👨🏻🏫
+👨🏻🏭
+👨🏻🐰👨🏼
+👨🏻🐰👨🏽
+👨🏻🐰👨🏾
+👨🏻🐰👨🏿
+👨🏻💻
+👨🏻💼
+👨🏻🔧
+👨🏻🔬
+👨🏻🚀
+👨🏻🚒
+👨🏻🤝👨🏼
+👨🏻🤝👨🏽
+👨🏻🤝👨🏾
+👨🏻🤝👨🏿
+👨🏻🦯
+👨🏻🦯➡️
+👨🏻🦰
+👨🏻🦱
+👨🏻🦲
+👨🏻🦳
+👨🏻🦼
+👨🏻🦼➡️
+👨🏻🦽
+👨🏻🦽➡️
+👨🏻👨🏼
+👨🏻👨🏽
+👨🏻👨🏾
+👨🏻👨🏿
+👨🏼
+👨🏼⚕️
+👨🏼⚖️
+👨🏼✈️
+👨🏼❤️👨🏻
+👨🏼❤️👨🏼
+👨🏼❤️👨🏽
+👨🏼❤️👨🏾
+👨🏼❤️👨🏿
+👨🏼❤️💋👨🏻
+👨🏼❤️💋👨🏼
+👨🏼❤️💋👨🏽
+👨🏼❤️💋👨🏾
+👨🏼❤️💋👨🏿
+👨🏼🌾
+👨🏼🍳
+👨🏼🍼
+👨🏼🎓
+👨🏼🎤
+👨🏼🎨
+👨🏼🏫
+👨🏼🏭
+👨🏼🐰👨🏻
+👨🏼🐰👨🏽
+👨🏼🐰👨🏾
+👨🏼🐰👨🏿
+👨🏼💻
+👨🏼💼
+👨🏼🔧
+👨🏼🔬
+👨🏼🚀
+👨🏼🚒
+👨🏼🤝👨🏻
+👨🏼🤝👨🏽
+👨🏼🤝👨🏾
+👨🏼🤝👨🏿
+👨🏼🦯
+👨🏼🦯➡️
+👨🏼🦰
+👨🏼🦱
+👨🏼🦲
+👨🏼🦳
+👨🏼🦼
+👨🏼🦼➡️
+👨🏼🦽
+👨🏼🦽➡️
+👨🏼👨🏻
+👨🏼👨🏽
+👨🏼👨🏾
+👨🏼👨🏿
+👨🏽
+👨🏽⚕️
+👨🏽⚖️
+👨🏽✈️
+👨🏽❤️👨🏻
+👨🏽❤️👨🏼
+👨🏽❤️👨🏽
+👨🏽❤️👨🏾
+👨🏽❤️👨🏿
+👨🏽❤️💋👨🏻
+👨🏽❤️💋👨🏼
+👨🏽❤️💋👨🏽
+👨🏽❤️💋👨🏾
+👨🏽❤️💋👨🏿
+👨🏽🌾
+👨🏽🍳
+👨🏽🍼
+👨🏽🎓
+👨🏽🎤
+👨🏽🎨
+👨🏽🏫
+👨🏽🏭
+👨🏽🐰👨🏻
+👨🏽🐰👨🏼
+👨🏽🐰👨🏾
+👨🏽🐰👨🏿
+👨🏽💻
+👨🏽💼
+👨🏽🔧
+👨🏽🔬
+👨🏽🚀
+👨🏽🚒
+👨🏽🤝👨🏻
+👨🏽🤝👨🏼
+👨🏽🤝👨🏾
+👨🏽🤝👨🏿
+👨🏽🦯
+👨🏽🦯➡️
+👨🏽🦰
+👨🏽🦱
+👨🏽🦲
+👨🏽🦳
+👨🏽🦼
+👨🏽🦼➡️
+👨🏽🦽
+👨🏽🦽➡️
+👨🏽👨🏻
+👨🏽👨🏼
+👨🏽👨🏾
+👨🏽👨🏿
+👨🏾
+👨🏾⚕️
+👨🏾⚖️
+👨🏾✈️
+👨🏾❤️👨🏻
+👨🏾❤️👨🏼
+👨🏾❤️👨🏽
+👨🏾❤️👨🏾
+👨🏾❤️👨🏿
+👨🏾❤️💋👨🏻
+👨🏾❤️💋👨🏼
+👨🏾❤️💋👨🏽
+👨🏾❤️💋👨🏾
+👨🏾❤️💋👨🏿
+👨🏾🌾
+👨🏾🍳
+👨🏾🍼
+👨🏾🎓
+👨🏾🎤
+👨🏾🎨
+👨🏾🏫
+👨🏾🏭
+👨🏾🐰👨🏻
+👨🏾🐰👨🏼
+👨🏾🐰👨🏽
+👨🏾🐰👨🏿
+👨🏾💻
+👨🏾💼
+👨🏾🔧
+👨🏾🔬
+👨🏾🚀
+👨🏾🚒
+👨🏾🤝👨🏻
+👨🏾🤝👨🏼
+👨🏾🤝👨🏽
+👨🏾🤝👨🏿
+👨🏾🦯
+👨🏾🦯➡️
+👨🏾🦰
+👨🏾🦱
+👨🏾🦲
+👨🏾🦳
+👨🏾🦼
+👨🏾🦼➡️
+👨🏾🦽
+👨🏾🦽➡️
+👨🏾👨🏻
+👨🏾👨🏼
+👨🏾👨🏽
+👨🏾👨🏿
+👨🏿
+👨🏿⚕️
+👨🏿⚖️
+👨🏿✈️
+👨🏿❤️👨🏻
+👨🏿❤️👨🏼
+👨🏿❤️👨🏽
+👨🏿❤️👨🏾
+👨🏿❤️👨🏿
+👨🏿❤️💋👨🏻
+👨🏿❤️💋👨🏼
+👨🏿❤️💋👨🏽
+👨🏿❤️💋👨🏾
+👨🏿❤️💋👨🏿
+👨🏿🌾
+👨🏿🍳
+👨🏿🍼
+👨🏿🎓
+👨🏿🎤
+👨🏿🎨
+👨🏿🏫
+👨🏿🏭
+👨🏿🐰👨🏻
+👨🏿🐰👨🏼
+👨🏿🐰👨🏽
+👨🏿🐰👨🏾
+👨🏿💻
+👨🏿💼
+👨🏿🔧
+👨🏿🔬
+👨🏿🚀
+👨🏿🚒
+👨🏿🤝👨🏻
+👨🏿🤝👨🏼
+👨🏿🤝👨🏽
+👨🏿🤝👨🏾
+👨🏿🦯
+👨🏿🦯➡️
+👨🏿🦰
+👨🏿🦱
+👨🏿🦲
+👨🏿🦳
+👨🏿🦼
+👨🏿🦼➡️
+👨🏿🦽
+👨🏿🦽➡️
+👨🏿👨🏻
+👨🏿👨🏼
+👨🏿👨🏽
+👨🏿👨🏾
+👩
+👩⚕️
+👩⚖️
+👩✈️
+👩❤️👨
+👩❤️👩
+👩❤️💋👨
+👩❤️💋👩
+👩🌾
+👩🍳
+👩🍼
+👩🎓
+👩🎤
+👩🎨
+👩🏫
+👩🏭
+👩👦
+👩👦👦
+👩👧
+👩👧👦
+👩👧👧
+👩👩👦
+👩👩👦👦
+👩👩👧
+👩👩👧👦
+👩👩👧👧
+👩💻
+👩💼
+👩🔧
+👩🔬
+👩🚀
+👩🚒
+👩🦯
+👩🦯➡️
+👩🦰
+👩🦱
+👩🦲
+👩🦳
+👩🦼
+👩🦼➡️
+👩🦽
+👩🦽➡️
+👩🏻
+👩🏻⚕️
+👩🏻⚖️
+👩🏻✈️
+👩🏻❤️👨🏻
+👩🏻❤️👨🏼
+👩🏻❤️👨🏽
+👩🏻❤️👨🏾
+👩🏻❤️👨🏿
+👩🏻❤️👩🏻
+👩🏻❤️👩🏼
+👩🏻❤️👩🏽
+👩🏻❤️👩🏾
+👩🏻❤️👩🏿
+👩🏻❤️💋👨🏻
+👩🏻❤️💋👨🏼
+👩🏻❤️💋👨🏽
+👩🏻❤️💋👨🏾
+👩🏻❤️💋👨🏿
+👩🏻❤️💋👩🏻
+👩🏻❤️💋👩🏼
+👩🏻❤️💋👩🏽
+👩🏻❤️💋👩🏾
+👩🏻❤️💋👩🏿
+👩🏻🌾
+👩🏻🍳
+👩🏻🍼
+👩🏻🎓
+👩🏻🎤
+👩🏻🎨
+👩🏻🏫
+👩🏻🏭
+👩🏻🐰👩🏼
+👩🏻🐰👩🏽
+👩🏻🐰👩🏾
+👩🏻🐰👩🏿
+👩🏻💻
+👩🏻💼
+👩🏻🔧
+👩🏻🔬
+👩🏻🚀
+👩🏻🚒
+👩🏻🤝👨🏼
+👩🏻🤝👨🏽
+👩🏻🤝👨🏾
+👩🏻🤝👨🏿
+👩🏻🤝👩🏼
+👩🏻🤝👩🏽
+👩🏻🤝👩🏾
+👩🏻🤝👩🏿
+👩🏻🦯
+👩🏻🦯➡️
+👩🏻🦰
+👩🏻🦱
+👩🏻🦲
+👩🏻🦳
+👩🏻🦼
+👩🏻🦼➡️
+👩🏻🦽
+👩🏻🦽➡️
+👩🏻👩🏼
+👩🏻👩🏽
+👩🏻👩🏾
+👩🏻👩🏿
+👩🏼
+👩🏼⚕️
+👩🏼⚖️
+👩🏼✈️
+👩🏼❤️👨🏻
+👩🏼❤️👨🏼
+👩🏼❤️👨🏽
+👩🏼❤️👨🏾
+👩🏼❤️👨🏿
+👩🏼❤️👩🏻
+👩🏼❤️👩🏼
+👩🏼❤️👩🏽
+👩🏼❤️👩🏾
+👩🏼❤️👩🏿
+👩🏼❤️💋👨🏻
+👩🏼❤️💋👨🏼
+👩🏼❤️💋👨🏽
+👩🏼❤️💋👨🏾
+👩🏼❤️💋👨🏿
+👩🏼❤️💋👩🏻
+👩🏼❤️💋👩🏼
+👩🏼❤️💋👩🏽
+👩🏼❤️💋👩🏾
+👩🏼❤️💋👩🏿
+👩🏼🌾
+👩🏼🍳
+👩🏼🍼
+👩🏼🎓
+👩🏼🎤
+👩🏼🎨
+👩🏼🏫
+👩🏼🏭
+👩🏼🐰👩🏻
+👩🏼🐰👩🏽
+👩🏼🐰👩🏾
+👩🏼🐰👩🏿
+👩🏼💻
+👩🏼💼
+👩🏼🔧
+👩🏼🔬
+👩🏼🚀
+👩🏼🚒
+👩🏼🤝👨🏻
+👩🏼🤝👨🏽
+👩🏼🤝👨🏾
+👩🏼🤝👨🏿
+👩🏼🤝👩🏻
+👩🏼🤝👩🏽
+👩🏼🤝👩🏾
+👩🏼🤝👩🏿
+👩🏼🦯
+👩🏼🦯➡️
+👩🏼🦰
+👩🏼🦱
+👩🏼🦲
+👩🏼🦳
+👩🏼🦼
+👩🏼🦼➡️
+👩🏼🦽
+👩🏼🦽➡️
+👩🏼👩🏻
+👩🏼👩🏽
+👩🏼👩🏾
+👩🏼👩🏿
+👩🏽
+👩🏽⚕️
+👩🏽⚖️
+👩🏽✈️
+👩🏽❤️👨🏻
+👩🏽❤️👨🏼
+👩🏽❤️👨🏽
+👩🏽❤️👨🏾
+👩🏽❤️👨🏿
+👩🏽❤️👩🏻
+👩🏽❤️👩🏼
+👩🏽❤️👩🏽
+👩🏽❤️👩🏾
+👩🏽❤️👩🏿
+👩🏽❤️💋👨🏻
+👩🏽❤️💋👨🏼
+👩🏽❤️💋👨🏽
+👩🏽❤️💋👨🏾
+👩🏽❤️💋👨🏿
+👩🏽❤️💋👩🏻
+👩🏽❤️💋👩🏼
+👩🏽❤️💋👩🏽
+👩🏽❤️💋👩🏾
+👩🏽❤️💋👩🏿
+👩🏽🌾
+👩🏽🍳
+👩🏽🍼
+👩🏽🎓
+👩🏽🎤
+👩🏽🎨
+👩🏽🏫
+👩🏽🏭
+👩🏽🐰👩🏻
+👩🏽🐰👩🏼
+👩🏽🐰👩🏾
+👩🏽🐰👩🏿
+👩🏽💻
+👩🏽💼
+👩🏽🔧
+👩🏽🔬
+👩🏽🚀
+👩🏽🚒
+👩🏽🤝👨🏻
+👩🏽🤝👨🏼
+👩🏽🤝👨🏾
+👩🏽🤝👨🏿
+👩🏽🤝👩🏻
+👩🏽🤝👩🏼
+👩🏽🤝👩🏾
+👩🏽🤝👩🏿
+👩🏽🦯
+👩🏽🦯➡️
+👩🏽🦰
+👩🏽🦱
+👩🏽🦲
+👩🏽🦳
+👩🏽🦼
+👩🏽🦼➡️
+👩🏽🦽
+👩🏽🦽➡️
+👩🏽👩🏻
+👩🏽👩🏼
+👩🏽👩🏾
+👩🏽👩🏿
+👩🏾
+👩🏾⚕️
+👩🏾⚖️
+👩🏾✈️
+👩🏾❤️👨🏻
+👩🏾❤️👨🏼
+👩🏾❤️👨🏽
+👩🏾❤️👨🏾
+👩🏾❤️👨🏿
+👩🏾❤️👩🏻
+👩🏾❤️👩🏼
+👩🏾❤️👩🏽
+👩🏾❤️👩🏾
+👩🏾❤️👩🏿
+👩🏾❤️💋👨🏻
+👩🏾❤️💋👨🏼
+👩🏾❤️💋👨🏽
+👩🏾❤️💋👨🏾
+👩🏾❤️💋👨🏿
+👩🏾❤️💋👩🏻
+👩🏾❤️💋👩🏼
+👩🏾❤️💋👩🏽
+👩🏾❤️💋👩🏾
+👩🏾❤️💋👩🏿
+👩🏾🌾
+👩🏾🍳
+👩🏾🍼
+👩🏾🎓
+👩🏾🎤
+👩🏾🎨
+👩🏾🏫
+👩🏾🏭
+👩🏾🐰👩🏻
+👩🏾🐰👩🏼
+👩🏾🐰👩🏽
+👩🏾🐰👩🏿
+👩🏾💻
+👩🏾💼
+👩🏾🔧
+👩🏾🔬
+👩🏾🚀
+👩🏾🚒
+👩🏾🤝👨🏻
+👩🏾🤝👨🏼
+👩🏾🤝👨🏽
+👩🏾🤝👨🏿
+👩🏾🤝👩🏻
+👩🏾🤝👩🏼
+👩🏾🤝👩🏽
+👩🏾🤝👩🏿
+👩🏾🦯
+👩🏾🦯➡️
+👩🏾🦰
+👩🏾🦱
+👩🏾🦲
+👩🏾🦳
+👩🏾🦼
+👩🏾🦼➡️
+👩🏾🦽
+👩🏾🦽➡️
+👩🏾👩🏻
+👩🏾👩🏼
+👩🏾👩🏽
+👩🏾👩🏿
+👩🏿
+👩🏿⚕️
+👩🏿⚖️
+👩🏿✈️
+👩🏿❤️👨🏻
+👩🏿❤️👨🏼
+👩🏿❤️👨🏽
+👩🏿❤️👨🏾
+👩🏿❤️👨🏿
+👩🏿❤️👩🏻
+👩🏿❤️👩🏼
+👩🏿❤️👩🏽
+👩🏿❤️👩🏾
+👩🏿❤️👩🏿
+👩🏿❤️💋👨🏻
+👩🏿❤️💋👨🏼
+👩🏿❤️💋👨🏽
+👩🏿❤️💋👨🏾
+👩🏿❤️💋👨🏿
+👩🏿❤️💋👩🏻
+👩🏿❤️💋👩🏼
+👩🏿❤️💋👩🏽
+👩🏿❤️💋👩🏾
+👩🏿❤️💋👩🏿
+👩🏿🌾
+👩🏿🍳
+👩🏿🍼
+👩🏿🎓
+👩🏿🎤
+👩🏿🎨
+👩🏿🏫
+👩🏿🏭
+👩🏿🐰👩🏻
+👩🏿🐰👩🏼
+👩🏿🐰👩🏽
+👩🏿🐰👩🏾
+👩🏿💻
+👩🏿💼
+👩🏿🔧
+👩🏿🔬
+👩🏿🚀
+👩🏿🚒
+👩🏿🤝👨🏻
+👩🏿🤝👨🏼
+👩🏿🤝👨🏽
+👩🏿🤝👨🏾
+👩🏿🤝👩🏻
+👩🏿🤝👩🏼
+👩🏿🤝👩🏽
+👩🏿🤝👩🏾
+👩🏿🦯
+👩🏿🦯➡️
+👩🏿🦰
+👩🏿🦱
+👩🏿🦲
+👩🏿🦳
+👩🏿🦼
+👩🏿🦼➡️
+👩🏿🦽
+👩🏿🦽➡️
+👩🏿👩🏻
+👩🏿👩🏼
+👩🏿👩🏽
+👩🏿👩🏾
+👪
+👫
+👫🏻
+👫🏼
+👫🏽
+👫🏾
+👫🏿
+👬
+👬🏻
+👬🏼
+👬🏽
+👬🏾
+👬🏿
+👭
+👭🏻
+👭🏼
+👭🏽
+👭🏾
+👭🏿
+👮
+👮♀️
+👮♂️
+👮🏻
+👮🏻♀️
+👮🏻♂️
+👮🏼
+👮🏼♀️
+👮🏼♂️
+👮🏽
+👮🏽♀️
+👮🏽♂️
+👮🏾
+👮🏾♀️
+👮🏾♂️
+👮🏿
+👮🏿♀️
+👮🏿♂️
+👯
+👯♀️
+👯♂️
+👯🏻
+👯🏻♀️
+👯🏻♂️
+👯🏼
+👯🏼♀️
+👯🏼♂️
+👯🏽
+👯🏽♀️
+👯🏽♂️
+👯🏾
+👯🏾♀️
+👯🏾♂️
+👯🏿
+👯🏿♀️
+👯🏿♂️
+👰
+👰♀️
+👰♂️
+👰🏻
+👰🏻♀️
+👰🏻♂️
+👰🏼
+👰🏼♀️
+👰🏼♂️
+👰🏽
+👰🏽♀️
+👰🏽♂️
+👰🏾
+👰🏾♀️
+👰🏾♂️
+👰🏿
+👰🏿♀️
+👰🏿♂️
+👱
+👱♀️
+👱♂️
+👱🏻
+👱🏻♀️
+👱🏻♂️
+👱🏼
+👱🏼♀️
+👱🏼♂️
+👱🏽
+👱🏽♀️
+👱🏽♂️
+👱🏾
+👱🏾♀️
+👱🏾♂️
+👱🏿
+👱🏿♀️
+👱🏿♂️
+👲
+👲🏻
+👲🏼
+👲🏽
+👲🏾
+👲🏿
+👳
+👳♀️
+👳♂️
+👳🏻
+👳🏻♀️
+👳🏻♂️
+👳🏼
+👳🏼♀️
+👳🏼♂️
+👳🏽
+👳🏽♀️
+👳🏽♂️
+👳🏾
+👳🏾♀️
+👳🏾♂️
+👳🏿
+👳🏿♀️
+👳🏿♂️
+👴
+👴🏻
+👴🏼
+👴🏽
+👴🏾
+👴🏿
+👵
+👵🏻
+👵🏼
+👵🏽
+👵🏾
+👵🏿
+👶
+👶🏻
+👶🏼
+👶🏽
+👶🏾
+👶🏿
+👷
+👷♀️
+👷♂️
+👷🏻
+👷🏻♀️
+👷🏻♂️
+👷🏼
+👷🏼♀️
+👷🏼♂️
+👷🏽
+👷🏽♀️
+👷🏽♂️
+👷🏾
+👷🏾♀️
+👷🏾♂️
+👷🏿
+👷🏿♀️
+👷🏿♂️
+👸
+👸🏻
+👸🏼
+👸🏽
+👸🏾
+👸🏿
+👹
+👺
+👻
+👼
+👼🏻
+👼🏼
+👼🏽
+👼🏾
+👼🏿
+👽
+👾
+👿
+💀
+💁
+💁♀️
+💁♂️
+💁🏻
+💁🏻♀️
+💁🏻♂️
+💁🏼
+💁🏼♀️
+💁🏼♂️
+💁🏽
+💁🏽♀️
+💁🏽♂️
+💁🏾
+💁🏾♀️
+💁🏾♂️
+💁🏿
+💁🏿♀️
+💁🏿♂️
+💂
+💂♀️
+💂♂️
+💂🏻
+💂🏻♀️
+💂🏻♂️
+💂🏼
+💂🏼♀️
+💂🏼♂️
+💂🏽
+💂🏽♀️
+💂🏽♂️
+💂🏾
+💂🏾♀️
+💂🏾♂️
+💂🏿
+💂🏿♀️
+💂🏿♂️
+💃
+💃🏻
+💃🏼
+💃🏽
+💃🏾
+💃🏿
+💄
+💅
+💅🏻
+💅🏼
+💅🏽
+💅🏾
+💅🏿
+💆
+💆♀️
+💆♂️
+💆🏻
+💆🏻♀️
+💆🏻♂️
+💆🏼
+💆🏼♀️
+💆🏼♂️
+💆🏽
+💆🏽♀️
+💆🏽♂️
+💆🏾
+💆🏾♀️
+💆🏾♂️
+💆🏿
+💆🏿♀️
+💆🏿♂️
+💇
+💇♀️
+💇♂️
+💇🏻
+💇🏻♀️
+💇🏻♂️
+💇🏼
+💇🏼♀️
+💇🏼♂️
+💇🏽
+💇🏽♀️
+💇🏽♂️
+💇🏾
+💇🏾♀️
+💇🏾♂️
+💇🏿
+💇🏿♀️
+💇🏿♂️
+💈
+💉
+💊
+💋
+💌
+💍
+💎
+💏
+💏🏻
+💏🏼
+💏🏽
+💏🏾
+💏🏿
+💐
+💑
+💑🏻
+💑🏼
+💑🏽
+💑🏾
+💑🏿
+💒
+💓
+💔
+💕
+💖
+💗
+💘
+💙
+💚
+💛
+💜
+💝
+💞
+💟
+💠
+💡
+💢
+💣
+💤
+💥
+💦
+💧
+💨
+💩
+💪
+💪🏻
+💪🏼
+💪🏽
+💪🏾
+💪🏿
+💫
+💬
+💭
+💮
+💯
+💰
+💱
+💲
+💳
+💴
+💵
+💶
+💷
+💸
+💹
+💺
+💻
+💼
+💽
+💾
+💿
+📀
+📁
+📂
+📃
+📄
+📅
+📆
+📇
+📈
+📉
+📊
+📋
+📌
+📍
+📎
+📏
+📐
+📑
+📒
+📓
+📔
+📕
+📖
+📗
+📘
+📙
+📚
+📛
+📜
+📝
+📞
+📟
+📠
+📡
+📢
+📣
+📤
+📥
+📦
+📧
+📨
+📩
+📪
+📫
+📬
+📭
+📮
+📯
+📰
+📱
+📲
+📳
+📴
+📵
+📶
+📷
+📸
+📹
+📺
+📻
+📼
+📽️
+📿
+🔀
+🔁
+🔂
+🔃
+🔄
+🔅
+🔆
+🔇
+🔈
+🔉
+🔊
+🔋
+🔌
+🔍
+🔎
+🔏
+🔐
+🔑
+🔒
+🔓
+🔔
+🔕
+🔖
+🔗
+🔘
+🔙
+🔚
+🔛
+🔜
+🔝
+🔞
+🔟
+🔠
+🔡
+🔢
+🔣
+🔤
+🔥
+🔦
+🔧
+🔨
+🔩
+🔪
+🔫
+🔬
+🔭
+🔮
+🔯
+🔰
+🔱
+🔲
+🔳
+🔴
+🔵
+🔶
+🔷
+🔸
+🔹
+🔺
+🔻
+🔼
+🔽
+🕉️
+🕊️
+🕋
+🕌
+🕍
+🕎
+🕐
+🕑
+🕒
+🕓
+🕔
+🕕
+🕖
+🕗
+🕘
+🕙
+🕚
+🕛
+🕜
+🕝
+🕞
+🕟
+🕠
+🕡
+🕢
+🕣
+🕤
+🕥
+🕦
+🕧
+🕯️
+🕰️
+🕳️
+🕴️
+🕴🏻
+🕴🏼
+🕴🏽
+🕴🏾
+🕴🏿
+🕵️
+🕵️♀️
+🕵️♂️
+🕵🏻
+🕵🏻♀️
+🕵🏻♂️
+🕵🏼
+🕵🏼♀️
+🕵🏼♂️
+🕵🏽
+🕵🏽♀️
+🕵🏽♂️
+🕵🏾
+🕵🏾♀️
+🕵🏾♂️
+🕵🏿
+🕵🏿♀️
+🕵🏿♂️
+🕶️
+🕷️
+🕸️
+🕹️
+🕺
+🕺🏻
+🕺🏼
+🕺🏽
+🕺🏾
+🕺🏿
+🖇️
+🖊️
+🖋️
+🖌️
+🖍️
+🖐️
+🖐🏻
+🖐🏼
+🖐🏽
+🖐🏾
+🖐🏿
+🖕
+🖕🏻
+🖕🏼
+🖕🏽
+🖕🏾
+🖕🏿
+🖖
+🖖🏻
+🖖🏼
+🖖🏽
+🖖🏾
+🖖🏿
+🖤
+🖥️
+🖨️
+🖱️
+🖲️
+🖼️
+🗂️
+🗃️
+🗄️
+🗑️
+🗒️
+🗓️
+🗜️
+🗝️
+🗞️
+🗡️
+🗣️
+🗨️
+🗯️
+🗳️
+🗺️
+🗻
+🗼
+🗽
+🗾
+🗿
+😀
+😁
+😂
+😃
+😄
+😅
+😆
+😇
+😈
+😉
+😊
+😋
+😌
+😍
+😎
+😏
+😐
+😑
+😒
+😓
+😔
+😕
+😖
+😗
+😘
+😙
+😚
+😛
+😜
+😝
+😞
+😟
+😠
+😡
+😢
+😣
+😤
+😥
+😦
+😧
+😨
+😩
+😪
+😫
+😬
+😭
+😮
+😮💨
+😯
+😰
+😱
+😲
+😳
+😴
+😵
+😵💫
+😶
+😶🌫️
+😷
+😸
+😹
+😺
+😻
+😼
+😽
+😾
+😿
+🙀
+🙁
+🙂
+🙂↔️
+🙂↕️
+🙃
+🙄
+🙅
+🙅♀️
+🙅♂️
+🙅🏻
+🙅🏻♀️
+🙅🏻♂️
+🙅🏼
+🙅🏼♀️
+🙅🏼♂️
+🙅🏽
+🙅🏽♀️
+🙅🏽♂️
+🙅🏾
+🙅🏾♀️
+🙅🏾♂️
+🙅🏿
+🙅🏿♀️
+🙅🏿♂️
+🙆
+🙆♀️
+🙆♂️
+🙆🏻
+🙆🏻♀️
+🙆🏻♂️
+🙆🏼
+🙆🏼♀️
+🙆🏼♂️
+🙆🏽
+🙆🏽♀️
+🙆🏽♂️
+🙆🏾
+🙆🏾♀️
+🙆🏾♂️
+🙆🏿
+🙆🏿♀️
+🙆🏿♂️
+🙇
+🙇♀️
+🙇♂️
+🙇🏻
+🙇🏻♀️
+🙇🏻♂️
+🙇🏼
+🙇🏼♀️
+🙇🏼♂️
+🙇🏽
+🙇🏽♀️
+🙇🏽♂️
+🙇🏾
+🙇🏾♀️
+🙇🏾♂️
+🙇🏿
+🙇🏿♀️
+🙇🏿♂️
+🙈
+🙉
+🙊
+🙋
+🙋♀️
+🙋♂️
+🙋🏻
+🙋🏻♀️
+🙋🏻♂️
+🙋🏼
+🙋🏼♀️
+🙋🏼♂️
+🙋🏽
+🙋🏽♀️
+🙋🏽♂️
+🙋🏾
+🙋🏾♀️
+🙋🏾♂️
+🙋🏿
+🙋🏿♀️
+🙋🏿♂️
+🙌
+🙌🏻
+🙌🏼
+🙌🏽
+🙌🏾
+🙌🏿
+🙍
+🙍♀️
+🙍♂️
+🙍🏻
+🙍🏻♀️
+🙍🏻♂️
+🙍🏼
+🙍🏼♀️
+🙍🏼♂️
+🙍🏽
+🙍🏽♀️
+🙍🏽♂️
+🙍🏾
+🙍🏾♀️
+🙍🏾♂️
+🙍🏿
+🙍🏿♀️
+🙍🏿♂️
+🙎
+🙎♀️
+🙎♂️
+🙎🏻
+🙎🏻♀️
+🙎🏻♂️
+🙎🏼
+🙎🏼♀️
+🙎🏼♂️
+🙎🏽
+🙎🏽♀️
+🙎🏽♂️
+🙎🏾
+🙎🏾♀️
+🙎🏾♂️
+🙎🏿
+🙎🏿♀️
+🙎🏿♂️
+🙏
+🙏🏻
+🙏🏼
+🙏🏽
+🙏🏾
+🙏🏿
+🚀
+🚁
+🚂
+🚃
+🚄
+🚅
+🚆
+🚇
+🚈
+🚉
+🚊
+🚋
+🚌
+🚍
+🚎
+🚏
+🚐
+🚑
+🚒
+🚓
+🚔
+🚕
+🚖
+🚗
+🚘
+🚙
+🚚
+🚛
+🚜
+🚝
+🚞
+🚟
+🚠
+🚡
+🚢
+🚣
+🚣♀️
+🚣♂️
+🚣🏻
+🚣🏻♀️
+🚣🏻♂️
+🚣🏼
+🚣🏼♀️
+🚣🏼♂️
+🚣🏽
+🚣🏽♀️
+🚣🏽♂️
+🚣🏾
+🚣🏾♀️
+🚣🏾♂️
+🚣🏿
+🚣🏿♀️
+🚣🏿♂️
+🚤
+🚥
+🚦
+🚧
+🚨
+🚩
+🚪
+🚫
+🚬
+🚭
+🚮
+🚯
+🚰
+🚱
+🚲
+🚳
+🚴
+🚴♀️
+🚴♂️
+🚴🏻
+🚴🏻♀️
+🚴🏻♂️
+🚴🏼
+🚴🏼♀️
+🚴🏼♂️
+🚴🏽
+🚴🏽♀️
+🚴🏽♂️
+🚴🏾
+🚴🏾♀️
+🚴🏾♂️
+🚴🏿
+🚴🏿♀️
+🚴🏿♂️
+🚵
+🚵♀️
+🚵♂️
+🚵🏻
+🚵🏻♀️
+🚵🏻♂️
+🚵🏼
+🚵🏼♀️
+🚵🏼♂️
+🚵🏽
+🚵🏽♀️
+🚵🏽♂️
+🚵🏾
+🚵🏾♀️
+🚵🏾♂️
+🚵🏿
+🚵🏿♀️
+🚵🏿♂️
+🚶
+🚶♀️
+🚶♀️➡️
+🚶♂️
+🚶♂️➡️
+🚶➡️
+🚶🏻
+🚶🏻♀️
+🚶🏻♀️➡️
+🚶🏻♂️
+🚶🏻♂️➡️
+🚶🏻➡️
+🚶🏼
+🚶🏼♀️
+🚶🏼♀️➡️
+🚶🏼♂️
+🚶🏼♂️➡️
+🚶🏼➡️
+🚶🏽
+🚶🏽♀️
+🚶🏽♀️➡️
+🚶🏽♂️
+🚶🏽♂️➡️
+🚶🏽➡️
+🚶🏾
+🚶🏾♀️
+🚶🏾♀️➡️
+🚶🏾♂️
+🚶🏾♂️➡️
+🚶🏾➡️
+🚶🏿
+🚶🏿♀️
+🚶🏿♀️➡️
+🚶🏿♂️
+🚶🏿♂️➡️
+🚶🏿➡️
+🚷
+🚸
+🚹
+🚺
+🚻
+🚼
+🚽
+🚾
+🚿
+🛀
+🛀🏻
+🛀🏼
+🛀🏽
+🛀🏾
+🛀🏿
+🛁
+🛂
+🛃
+🛄
+🛅
+🛋️
+🛌
+🛌🏻
+🛌🏼
+🛌🏽
+🛌🏾
+🛌🏿
+🛍️
+🛎️
+🛏️
+🛐
+🛑
+🛒
+🛕
+🛖
+🛗
+
+🛜
+🛝
+🛞
+🛟
+🛠️
+🛡️
+🛢️
+🛣️
+🛤️
+🛥️
+🛩️
+🛫
+🛬
+🛰️
+🛳️
+🛴
+🛵
+🛶
+🛷
+🛸
+🛹
+🛺
+🛻
+🛼
+🟠
+🟡
+🟢
+🟣
+🟤
+🟥
+🟦
+🟧
+🟨
+🟩
+🟪
+🟫
+🟰
+🤌
+🤌🏻
+🤌🏼
+🤌🏽
+🤌🏾
+🤌🏿
+🤍
+🤎
+🤏
+🤏🏻
+🤏🏼
+🤏🏽
+🤏🏾
+🤏🏿
+🤐
+🤑
+🤒
+🤓
+🤔
+🤕
+🤖
+🤗
+🤘
+🤘🏻
+🤘🏼
+🤘🏽
+🤘🏾
+🤘🏿
+🤙
+🤙🏻
+🤙🏼
+🤙🏽
+🤙🏾
+🤙🏿
+🤚
+🤚🏻
+🤚🏼
+🤚🏽
+🤚🏾
+🤚🏿
+🤛
+🤛🏻
+🤛🏼
+🤛🏽
+🤛🏾
+🤛🏿
+🤜
+🤜🏻
+🤜🏼
+🤜🏽
+🤜🏾
+🤜🏿
+🤝
+🤝🏻
+🤝🏼
+🤝🏽
+🤝🏾
+🤝🏿
+🤞
+🤞🏻
+🤞🏼
+🤞🏽
+🤞🏾
+🤞🏿
+🤟
+🤟🏻
+🤟🏼
+🤟🏽
+🤟🏾
+🤟🏿
+🤠
+🤡
+🤢
+🤣
+🤤
+🤥
+🤦
+🤦♀️
+🤦♂️
+🤦🏻
+🤦🏻♀️
+🤦🏻♂️
+🤦🏼
+🤦🏼♀️
+🤦🏼♂️
+🤦🏽
+🤦🏽♀️
+🤦🏽♂️
+🤦🏾
+🤦🏾♀️
+🤦🏾♂️
+🤦🏿
+🤦🏿♀️
+🤦🏿♂️
+🤧
+🤨
+🤩
+🤪
+🤫
+🤬
+🤭
+🤮
+🤯
+🤰
+🤰🏻
+🤰🏼
+🤰🏽
+🤰🏾
+🤰🏿
+🤱
+🤱🏻
+🤱🏼
+🤱🏽
+🤱🏾
+🤱🏿
+🤲
+🤲🏻
+🤲🏼
+🤲🏽
+🤲🏾
+🤲🏿
+🤳
+🤳🏻
+🤳🏼
+🤳🏽
+🤳🏾
+🤳🏿
+🤴
+🤴🏻
+🤴🏼
+🤴🏽
+🤴🏾
+🤴🏿
+🤵
+🤵♀️
+🤵♂️
+🤵🏻
+🤵🏻♀️
+🤵🏻♂️
+🤵🏼
+🤵🏼♀️
+🤵🏼♂️
+🤵🏽
+🤵🏽♀️
+🤵🏽♂️
+🤵🏾
+🤵🏾♀️
+🤵🏾♂️
+🤵🏿
+🤵🏿♀️
+🤵🏿♂️
+🤶
+🤶🏻
+🤶🏼
+🤶🏽
+🤶🏾
+🤶🏿
+🤷
+🤷♀️
+🤷♂️
+🤷🏻
+🤷🏻♀️
+🤷🏻♂️
+🤷🏼
+🤷🏼♀️
+🤷🏼♂️
+🤷🏽
+🤷🏽♀️
+🤷🏽♂️
+🤷🏾
+🤷🏾♀️
+🤷🏾♂️
+🤷🏿
+🤷🏿♀️
+🤷🏿♂️
+🤸
+🤸♀️
+🤸♂️
+🤸🏻
+🤸🏻♀️
+🤸🏻♂️
+🤸🏼
+🤸🏼♀️
+🤸🏼♂️
+🤸🏽
+🤸🏽♀️
+🤸🏽♂️
+🤸🏾
+🤸🏾♀️
+🤸🏾♂️
+🤸🏿
+🤸🏿♀️
+🤸🏿♂️
+🤹
+🤹♀️
+🤹♂️
+🤹🏻
+🤹🏻♀️
+🤹🏻♂️
+🤹🏼
+🤹🏼♀️
+🤹🏼♂️
+🤹🏽
+🤹🏽♀️
+🤹🏽♂️
+🤹🏾
+🤹🏾♀️
+🤹🏾♂️
+🤹🏿
+🤹🏿♀️
+🤹🏿♂️
+🤺
+🤼
+🤼♀️
+🤼♂️
+🤼🏻
+🤼🏻♀️
+🤼🏻♂️
+🤼🏼
+🤼🏼♀️
+🤼🏼♂️
+🤼🏽
+🤼🏽♀️
+🤼🏽♂️
+🤼🏾
+🤼🏾♀️
+🤼🏾♂️
+🤼🏿
+🤼🏿♀️
+🤼🏿♂️
+🤽
+🤽♀️
+🤽♂️
+🤽🏻
+🤽🏻♀️
+🤽🏻♂️
+🤽🏼
+🤽🏼♀️
+🤽🏼♂️
+🤽🏽
+🤽🏽♀️
+🤽🏽♂️
+🤽🏾
+🤽🏾♀️
+🤽🏾♂️
+🤽🏿
+🤽🏿♀️
+🤽🏿♂️
+🤾
+🤾♀️
+🤾♂️
+🤾🏻
+🤾🏻♀️
+🤾🏻♂️
+🤾🏼
+🤾🏼♀️
+🤾🏼♂️
+🤾🏽
+🤾🏽♀️
+🤾🏽♂️
+🤾🏾
+🤾🏾♀️
+🤾🏾♂️
+🤾🏿
+🤾🏿♀️
+🤾🏿♂️
+🤿
+🥀
+🥁
+🥂
+🥃
+🥄
+🥅
+🥇
+🥈
+🥉
+🥊
+🥋
+🥌
+🥍
+🥎
+🥏
+🥐
+🥑
+🥒
+🥓
+🥔
+🥕
+🥖
+🥗
+🥘
+🥙
+🥚
+🥛
+🥜
+🥝
+🥞
+🥟
+🥠
+🥡
+🥢
+🥣
+🥤
+🥥
+🥦
+🥧
+🥨
+🥩
+🥪
+🥫
+🥬
+🥭
+🥮
+🥯
+🥰
+🥱
+🥲
+🥳
+🥴
+🥵
+🥶
+🥷
+🥷🏻
+🥷🏼
+🥷🏽
+🥷🏾
+🥷🏿
+🥸
+🥹
+🥺
+🥻
+🥼
+🥽
+🥾
+🥿
+🦀
+🦁
+🦂
+🦃
+🦄
+🦅
+🦆
+🦇
+🦈
+🦉
+🦊
+🦋
+🦌
+🦍
+🦎
+🦏
+🦐
+🦑
+🦒
+🦓
+🦔
+🦕
+🦖
+🦗
+🦘
+🦙
+🦚
+🦛
+🦜
+🦝
+🦞
+🦟
+🦠
+🦡
+🦢
+🦣
+🦤
+🦥
+🦦
+🦧
+🦨
+🦩
+🦪
+🦫
+🦬
+🦭
+🦮
+🦯
+🦴
+🦵
+🦵🏻
+🦵🏼
+🦵🏽
+🦵🏾
+🦵🏿
+🦶
+🦶🏻
+🦶🏼
+🦶🏽
+🦶🏾
+🦶🏿
+🦷
+🦸
+🦸♀️
+🦸♂️
+🦸🏻
+🦸🏻♀️
+🦸🏻♂️
+🦸🏼
+🦸🏼♀️
+🦸🏼♂️
+🦸🏽
+🦸🏽♀️
+🦸🏽♂️
+🦸🏾
+🦸🏾♀️
+🦸🏾♂️
+🦸🏿
+🦸🏿♀️
+🦸🏿♂️
+🦹
+🦹♀️
+🦹♂️
+🦹🏻
+🦹🏻♀️
+🦹🏻♂️
+🦹🏼
+🦹🏼♀️
+🦹🏼♂️
+🦹🏽
+🦹🏽♀️
+🦹🏽♂️
+🦹🏾
+🦹🏾♀️
+🦹🏾♂️
+🦹🏿
+🦹🏿♀️
+🦹🏿♂️
+🦺
+🦻
+🦻🏻
+🦻🏼
+🦻🏽
+🦻🏾
+🦻🏿
+🦼
+🦽
+🦾
+🦿
+🧀
+🧁
+🧂
+🧃
+🧄
+🧅
+🧆
+🧇
+🧈
+🧉
+🧊
+🧋
+🧌
+🧍
+🧍♀️
+🧍♂️
+🧍🏻
+🧍🏻♀️
+🧍🏻♂️
+🧍🏼
+🧍🏼♀️
+🧍🏼♂️
+🧍🏽
+🧍🏽♀️
+🧍🏽♂️
+🧍🏾
+🧍🏾♀️
+🧍🏾♂️
+🧍🏿
+🧍🏿♀️
+🧍🏿♂️
+🧎
+🧎♀️
+🧎♀️➡️
+🧎♂️
+🧎♂️➡️
+🧎➡️
+🧎🏻
+🧎🏻♀️
+🧎🏻♀️➡️
+🧎🏻♂️
+🧎🏻♂️➡️
+🧎🏻➡️
+🧎🏼
+🧎🏼♀️
+🧎🏼♀️➡️
+🧎🏼♂️
+🧎🏼♂️➡️
+🧎🏼➡️
+🧎🏽
+🧎🏽♀️
+🧎🏽♀️➡️
+🧎🏽♂️
+🧎🏽♂️➡️
+🧎🏽➡️
+🧎🏾
+🧎🏾♀️
+🧎🏾♀️➡️
+🧎🏾♂️
+🧎🏾♂️➡️
+🧎🏾➡️
+🧎🏿
+🧎🏿♀️
+🧎🏿♀️➡️
+🧎🏿♂️
+🧎🏿♂️➡️
+🧎🏿➡️
+🧏
+🧏♀️
+🧏♂️
+🧏🏻
+🧏🏻♀️
+🧏🏻♂️
+🧏🏼
+🧏🏼♀️
+🧏🏼♂️
+🧏🏽
+🧏🏽♀️
+🧏🏽♂️
+🧏🏾
+🧏🏾♀️
+🧏🏾♂️
+🧏🏿
+🧏🏿♀️
+🧏🏿♂️
+🧐
+🧑
+🧑⚕️
+🧑⚖️
+🧑✈️
+🧑🌾
+🧑🍳
+🧑🍼
+🧑🎄
+🧑🎓
+🧑🎤
+🧑🎨
+🧑🏫
+🧑🏭
+🧑💻
+🧑💼
+🧑🔧
+🧑🔬
+🧑🚀
+🧑🚒
+🧑🤝🧑
+🧑🦯
+🧑🦯➡️
+🧑🦰
+🧑🦱
+🧑🦲
+🧑🦳
+🧑🦼
+🧑🦼➡️
+🧑🦽
+🧑🦽➡️
+🧑🧑🧒
+🧑🧑🧒🧒
+🧑🧒
+🧑🧒🧒
+🧑🩰
+🧑🏻
+🧑🏻⚕️
+🧑🏻⚖️
+🧑🏻✈️
+🧑🏻❤️💋🧑🏼
+🧑🏻❤️💋🧑🏽
+🧑🏻❤️💋🧑🏾
+🧑🏻❤️💋🧑🏿
+🧑🏻❤️🧑🏼
+🧑🏻❤️🧑🏽
+🧑🏻❤️🧑🏾
+🧑🏻❤️🧑🏿
+🧑🏻🌾
+🧑🏻🍳
+🧑🏻🍼
+🧑🏻🎄
+🧑🏻🎓
+🧑🏻🎤
+🧑🏻🎨
+🧑🏻🏫
+🧑🏻🏭
+🧑🏻🐰🧑🏼
+🧑🏻🐰🧑🏽
+🧑🏻🐰🧑🏾
+🧑🏻🐰🧑🏿
+🧑🏻💻
+🧑🏻💼
+🧑🏻🔧
+🧑🏻🔬
+🧑🏻🚀
+🧑🏻🚒
+🧑🏻🤝🧑🏻
+🧑🏻🤝🧑🏼
+🧑🏻🤝🧑🏽
+🧑🏻🤝🧑🏾
+🧑🏻🤝🧑🏿
+🧑🏻🦯
+🧑🏻🦯➡️
+🧑🏻🦰
+🧑🏻🦱
+🧑🏻🦲
+🧑🏻🦳
+🧑🏻🦼
+🧑🏻🦼➡️
+🧑🏻🦽
+🧑🏻🦽➡️
+🧑🏻🩰
+🧑🏻🧑🏼
+🧑🏻🧑🏽
+🧑🏻🧑🏾
+🧑🏻🧑🏿
+🧑🏼
+🧑🏼⚕️
+🧑🏼⚖️
+🧑🏼✈️
+🧑🏼❤️💋🧑🏻
+🧑🏼❤️💋🧑🏽
+🧑🏼❤️💋🧑🏾
+🧑🏼❤️💋🧑🏿
+🧑🏼❤️🧑🏻
+🧑🏼❤️🧑🏽
+🧑🏼❤️🧑🏾
+🧑🏼❤️🧑🏿
+🧑🏼🌾
+🧑🏼🍳
+🧑🏼🍼
+🧑🏼🎄
+🧑🏼🎓
+🧑🏼🎤
+🧑🏼🎨
+🧑🏼🏫
+🧑🏼🏭
+🧑🏼🐰🧑🏻
+🧑🏼🐰🧑🏽
+🧑🏼🐰🧑🏾
+🧑🏼🐰🧑🏿
+🧑🏼💻
+🧑🏼💼
+🧑🏼🔧
+🧑🏼🔬
+🧑🏼🚀
+🧑🏼🚒
+🧑🏼🤝🧑🏻
+🧑🏼🤝🧑🏼
+🧑🏼🤝🧑🏽
+🧑🏼🤝🧑🏾
+🧑🏼🤝🧑🏿
+🧑🏼🦯
+🧑🏼🦯➡️
+🧑🏼🦰
+🧑🏼🦱
+🧑🏼🦲
+🧑🏼🦳
+🧑🏼🦼
+🧑🏼🦼➡️
+🧑🏼🦽
+🧑🏼🦽➡️
+🧑🏼🩰
+🧑🏼🧑🏻
+🧑🏼🧑🏽
+🧑🏼🧑🏾
+🧑🏼🧑🏿
+🧑🏽
+🧑🏽⚕️
+🧑🏽⚖️
+🧑🏽✈️
+🧑🏽❤️💋🧑🏻
+🧑🏽❤️💋🧑🏼
+🧑🏽❤️💋🧑🏾
+🧑🏽❤️💋🧑🏿
+🧑🏽❤️🧑🏻
+🧑🏽❤️🧑🏼
+🧑🏽❤️🧑🏾
+🧑🏽❤️🧑🏿
+🧑🏽🌾
+🧑🏽🍳
+🧑🏽🍼
+🧑🏽🎄
+🧑🏽🎓
+🧑🏽🎤
+🧑🏽🎨
+🧑🏽🏫
+🧑🏽🏭
+🧑🏽🐰🧑🏻
+🧑🏽🐰🧑🏼
+🧑🏽🐰🧑🏾
+🧑🏽🐰🧑🏿
+🧑🏽💻
+🧑🏽💼
+🧑🏽🔧
+🧑🏽🔬
+🧑🏽🚀
+🧑🏽🚒
+🧑🏽🤝🧑🏻
+🧑🏽🤝🧑🏼
+🧑🏽🤝🧑🏽
+🧑🏽🤝🧑🏾
+🧑🏽🤝🧑🏿
+🧑🏽🦯
+🧑🏽🦯➡️
+🧑🏽🦰
+🧑🏽🦱
+🧑🏽🦲
+🧑🏽🦳
+🧑🏽🦼
+🧑🏽🦼➡️
+🧑🏽🦽
+🧑🏽🦽➡️
+🧑🏽🩰
+🧑🏽🧑🏻
+🧑🏽🧑🏼
+🧑🏽🧑🏾
+🧑🏽🧑🏿
+🧑🏾
+🧑🏾⚕️
+🧑🏾⚖️
+🧑🏾✈️
+🧑🏾❤️💋🧑🏻
+🧑🏾❤️💋🧑🏼
+🧑🏾❤️💋🧑🏽
+🧑🏾❤️💋🧑🏿
+🧑🏾❤️🧑🏻
+🧑🏾❤️🧑🏼
+🧑🏾❤️🧑🏽
+🧑🏾❤️🧑🏿
+🧑🏾🌾
+🧑🏾🍳
+🧑🏾🍼
+🧑🏾🎄
+🧑🏾🎓
+🧑🏾🎤
+🧑🏾🎨
+🧑🏾🏫
+🧑🏾🏭
+🧑🏾🐰🧑🏻
+🧑🏾🐰🧑🏼
+🧑🏾🐰🧑🏽
+🧑🏾🐰🧑🏿
+🧑🏾💻
+🧑🏾💼
+🧑🏾🔧
+🧑🏾🔬
+🧑🏾🚀
+🧑🏾🚒
+🧑🏾🤝🧑🏻
+🧑🏾🤝🧑🏼
+🧑🏾🤝🧑🏽
+🧑🏾🤝🧑🏾
+🧑🏾🤝🧑🏿
+🧑🏾🦯
+🧑🏾🦯➡️
+🧑🏾🦰
+🧑🏾🦱
+🧑🏾🦲
+🧑🏾🦳
+🧑🏾🦼
+🧑🏾🦼➡️
+🧑🏾🦽
+🧑🏾🦽➡️
+🧑🏾🩰
+🧑🏾🧑🏻
+🧑🏾🧑🏼
+🧑🏾🧑🏽
+🧑🏾🧑🏿
+🧑🏿
+🧑🏿⚕️
+🧑🏿⚖️
+🧑🏿✈️
+🧑🏿❤️💋🧑🏻
+🧑🏿❤️💋🧑🏼
+🧑🏿❤️💋🧑🏽
+🧑🏿❤️💋🧑🏾
+🧑🏿❤️🧑🏻
+🧑🏿❤️🧑🏼
+🧑🏿❤️🧑🏽
+🧑🏿❤️🧑🏾
+🧑🏿🌾
+🧑🏿🍳
+🧑🏿🍼
+🧑🏿🎄
+🧑🏿🎓
+🧑🏿🎤
+🧑🏿🎨
+🧑🏿🏫
+🧑🏿🏭
+🧑🏿🐰🧑🏻
+🧑🏿🐰🧑🏼
+🧑🏿🐰🧑🏽
+🧑🏿🐰🧑🏾
+🧑🏿💻
+🧑🏿💼
+🧑🏿🔧
+🧑🏿🔬
+🧑🏿🚀
+🧑🏿🚒
+🧑🏿🤝🧑🏻
+🧑🏿🤝🧑🏼
+🧑🏿🤝🧑🏽
+🧑🏿🤝🧑🏾
+🧑🏿🤝🧑🏿
+🧑🏿🦯
+🧑🏿🦯➡️
+🧑🏿🦰
+🧑🏿🦱
+🧑🏿🦲
+🧑🏿🦳
+🧑🏿🦼
+🧑🏿🦼➡️
+🧑🏿🦽
+🧑🏿🦽➡️
+🧑🏿🩰
+🧑🏿🧑🏻
+🧑🏿🧑🏼
+🧑🏿🧑🏽
+🧑🏿🧑🏾
+🧒
+🧒🏻
+🧒🏼
+🧒🏽
+🧒🏾
+🧒🏿
+🧓
+🧓🏻
+🧓🏼
+🧓🏽
+🧓🏾
+🧓🏿
+🧔
+🧔♀️
+🧔♂️
+🧔🏻
+🧔🏻♀️
+🧔🏻♂️
+🧔🏼
+🧔🏼♀️
+🧔🏼♂️
+🧔🏽
+🧔🏽♀️
+🧔🏽♂️
+🧔🏾
+🧔🏾♀️
+🧔🏾♂️
+🧔🏿
+🧔🏿♀️
+🧔🏿♂️
+🧕
+🧕🏻
+🧕🏼
+🧕🏽
+🧕🏾
+🧕🏿
+🧖
+🧖♀️
+🧖♂️
+🧖🏻
+🧖🏻♀️
+🧖🏻♂️
+🧖🏼
+🧖🏼♀️
+🧖🏼♂️
+🧖🏽
+🧖🏽♀️
+🧖🏽♂️
+🧖🏾
+🧖🏾♀️
+🧖🏾♂️
+🧖🏿
+🧖🏿♀️
+🧖🏿♂️
+🧗
+🧗♀️
+🧗♂️
+🧗🏻
+🧗🏻♀️
+🧗🏻♂️
+🧗🏼
+🧗🏼♀️
+🧗🏼♂️
+🧗🏽
+🧗🏽♀️
+🧗🏽♂️
+🧗🏾
+🧗🏾♀️
+🧗🏾♂️
+🧗🏿
+🧗🏿♀️
+🧗🏿♂️
+🧘
+🧘♀️
+🧘♂️
+🧘🏻
+🧘🏻♀️
+🧘🏻♂️
+🧘🏼
+🧘🏼♀️
+🧘🏼♂️
+🧘🏽
+🧘🏽♀️
+🧘🏽♂️
+🧘🏾
+🧘🏾♀️
+🧘🏾♂️
+🧘🏿
+🧘🏿♀️
+🧘🏿♂️
+🧙
+🧙♀️
+🧙♂️
+🧙🏻
+🧙🏻♀️
+🧙🏻♂️
+🧙🏼
+🧙🏼♀️
+🧙🏼♂️
+🧙🏽
+🧙🏽♀️
+🧙🏽♂️
+🧙🏾
+🧙🏾♀️
+🧙🏾♂️
+🧙🏿
+🧙🏿♀️
+🧙🏿♂️
+🧚
+🧚♀️
+🧚♂️
+🧚🏻
+🧚🏻♀️
+🧚🏻♂️
+🧚🏼
+🧚🏼♀️
+🧚🏼♂️
+🧚🏽
+🧚🏽♀️
+🧚🏽♂️
+🧚🏾
+🧚🏾♀️
+🧚🏾♂️
+🧚🏿
+🧚🏿♀️
+🧚🏿♂️
+🧛
+🧛♀️
+🧛♂️
+🧛🏻
+🧛🏻♀️
+🧛🏻♂️
+🧛🏼
+🧛🏼♀️
+🧛🏼♂️
+🧛🏽
+🧛🏽♀️
+🧛🏽♂️
+🧛🏾
+🧛🏾♀️
+🧛🏾♂️
+🧛🏿
+🧛🏿♀️
+🧛🏿♂️
+🧜
+🧜♀️
+🧜♂️
+🧜🏻
+🧜🏻♀️
+🧜🏻♂️
+🧜🏼
+🧜🏼♀️
+🧜🏼♂️
+🧜🏽
+🧜🏽♀️
+🧜🏽♂️
+🧜🏾
+🧜🏾♀️
+🧜🏾♂️
+🧜🏿
+🧜🏿♀️
+🧜🏿♂️
+🧝
+🧝♀️
+🧝♂️
+🧝🏻
+🧝🏻♀️
+🧝🏻♂️
+🧝🏼
+🧝🏼♀️
+🧝🏼♂️
+🧝🏽
+🧝🏽♀️
+🧝🏽♂️
+🧝🏾
+🧝🏾♀️
+🧝🏾♂️
+🧝🏿
+🧝🏿♀️
+🧝🏿♂️
+🧞
+🧞♀️
+🧞♂️
+🧟
+🧟♀️
+🧟♂️
+🧠
+🧡
+🧢
+🧣
+🧤
+🧥
+🧦
+🧧
+🧨
+🧩
+🧪
+🧫
+🧬
+🧭
+🧮
+🧯
+🧰
+🧱
+🧲
+🧳
+🧴
+🧵
+🧶
+🧷
+🧸
+🧹
+🧺
+🧻
+🧼
+🧽
+🧾
+🧿
+🩰
+🩱
+🩲
+🩳
+🩴
+🩵
+🩶
+🩷
+🩸
+🩹
+🩺
+🩻
+🩼
+🪀
+🪁
+🪂
+🪃
+🪄
+🪅
+🪆
+🪇
+🪈
+
+
+
+
+🪐
+🪑
+🪒
+🪓
+🪔
+🪕
+🪖
+🪗
+🪘
+🪙
+🪚
+🪛
+🪜
+🪝
+🪞
+🪟
+🪠
+🪡
+🪢
+🪣
+🪤
+🪥
+🪦
+🪧
+🪨
+🪩
+🪪
+🪫
+🪬
+🪭
+🪮
+🪯
+🪰
+🪱
+🪲
+🪳
+🪴
+🪵
+🪶
+🪷
+🪸
+🪹
+🪺
+🪻
+🪼
+🪽
+
+🪿
+🫀
+🫁
+🫂
+🫃
+🫃🏻
+🫃🏼
+🫃🏽
+🫃🏾
+🫃🏿
+🫄
+🫄🏻
+🫄🏼
+🫄🏽
+🫄🏾
+🫄🏿
+🫅
+🫅🏻
+🫅🏼
+🫅🏽
+🫅🏾
+🫅🏿
+
+
+
+🫎
+🫏
+🫐
+🫑
+🫒
+🫓
+🫔
+🫕
+🫖
+🫗
+🫘
+🫙
+🫚
+🫛
+
+
+🫠
+🫡
+🫢
+🫣
+🫤
+🫥
+🫦
+🫧
+🫨
+
+
+
+🫰
+🫰🏻
+🫰🏼
+🫰🏽
+🫰🏾
+🫰🏿
+🫱
+🫱🏻
+🫱🏻🫲🏼
+🫱🏻🫲🏽
+🫱🏻🫲🏾
+🫱🏻🫲🏿
+🫱🏼
+🫱🏼🫲🏻
+🫱🏼🫲🏽
+🫱🏼🫲🏾
+🫱🏼🫲🏿
+🫱🏽
+🫱🏽🫲🏻
+🫱🏽🫲🏼
+🫱🏽🫲🏾
+🫱🏽🫲🏿
+🫱🏾
+🫱🏾🫲🏻
+🫱🏾🫲🏼
+🫱🏾🫲🏽
+🫱🏾🫲🏿
+🫱🏿
+🫱🏿🫲🏻
+🫱🏿🫲🏼
+🫱🏿🫲🏽
+🫱🏿🫲🏾
+🫲
+🫲🏻
+🫲🏼
+🫲🏽
+🫲🏾
+🫲🏿
+🫳
+🫳🏻
+🫳🏼
+🫳🏽
+🫳🏾
+🫳🏿
+🫴
+🫴🏻
+🫴🏼
+🫴🏽
+🫴🏾
+🫴🏿
+🫵
+🫵🏻
+🫵🏼
+🫵🏽
+🫵🏾
+🫵🏿
+🫶
+🫶🏻
+🫶🏼
+🫶🏽
+🫶🏾
+🫶🏿
+🫷
+🫷🏻
+🫷🏼
+🫷🏽
+🫷🏾
+🫷🏿
+🫸
+🫸🏻
+🫸🏼
+🫸🏽
+🫸🏾
+🫸🏿
\ No newline at end of file
diff --git a/crates/core/database/src/models/file_hashes/model.rs b/crates/core/database/src/models/file_hashes/model.rs
index cec7a39db..dfc9b96e5 100644
--- a/crates/core/database/src/models/file_hashes/model.rs
+++ b/crates/core/database/src/models/file_hashes/model.rs
@@ -45,7 +45,8 @@ auto_derived!(
Image {
width: isize,
height: isize,
- // animated: bool // TODO: https://docs.rs/image/latest/image/trait.AnimationDecoder.html for APNG support
+ thumbhash: Option>,
+ animated: Option,
},
/// File is a video with specific dimensions
Video { width: isize, height: isize },
diff --git a/crates/core/database/src/models/file_hashes/ops.rs b/crates/core/database/src/models/file_hashes/ops.rs
index 4b249e4cb..cdd6c936e 100644
--- a/crates/core/database/src/models/file_hashes/ops.rs
+++ b/crates/core/database/src/models/file_hashes/ops.rs
@@ -17,6 +17,12 @@ pub trait AbstractAttachmentHashes: Sync + Send {
/// Update an attachment hash nonce value.
async fn set_attachment_hash_nonce(&self, hash: &str, nonce: &str) -> Result<()>;
+ /// Updates the attachments animated metadata value.
+ ///
+ /// The primary use for this is to update the metadata for existing uploaded files, this
+ /// can only be used for images.
+ async fn set_attachment_hash_animated(&self, hash: &str, animated: bool) -> Result<()>;
+
/// Delete attachment hash by id.
async fn delete_attachment_hash(&self, id: &str) -> Result<()>;
}
diff --git a/crates/core/database/src/models/file_hashes/ops/mongodb.rs b/crates/core/database/src/models/file_hashes/ops/mongodb.rs
index e2f06067d..9e67a0a56 100644
--- a/crates/core/database/src/models/file_hashes/ops/mongodb.rs
+++ b/crates/core/database/src/models/file_hashes/ops/mongodb.rs
@@ -48,6 +48,29 @@ impl AbstractAttachmentHashes for MongoDb {
.map_err(|_| create_database_error!("update_one", COL))
}
+ /// Updates the attachments animated metadata value.
+ ///
+ /// The primary use for this is to update the metadata for existing uploaded files, this
+ /// can only be used for images.
+ async fn set_attachment_hash_animated(&self, hash: &str, animated: bool) -> Result<()> {
+ self.col::(COL)
+ .update_one(
+ doc! {
+ "_id": hash,
+ "metadata.type": "Image",
+ "metadata.animated": { "$exists": false },
+ },
+ doc! {
+ "$set": {
+ "metadata.animated": animated
+ }
+ },
+ )
+ .await
+ .map(|_| ())
+ .map_err(|_| create_database_error!("update_one", COL))
+ }
+
/// Delete attachment hash by id.
async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
query!(self, delete_one_by_id, COL, id).map(|_| ())
diff --git a/crates/core/database/src/models/file_hashes/ops/reference.rs b/crates/core/database/src/models/file_hashes/ops/reference.rs
index 7733523e3..3c54d9e2d 100644
--- a/crates/core/database/src/models/file_hashes/ops/reference.rs
+++ b/crates/core/database/src/models/file_hashes/ops/reference.rs
@@ -1,7 +1,6 @@
use revolt_result::Result;
-use crate::FileHash;
-use crate::ReferenceDb;
+use crate::{FileHash, Metadata, ReferenceDb};
use super::AbstractAttachmentHashes;
@@ -39,6 +38,29 @@ impl AbstractAttachmentHashes for ReferenceDb {
}
}
+ /// Updates the attachments animated metadata value.
+ ///
+ /// The primary use for this is to update the metadata for existing uploaded files, this
+ /// can only be used for images.
+ async fn set_attachment_hash_animated(&self, hash: &str, animated: bool) -> Result<()> {
+ let mut hashes = self.file_hashes.lock().await;
+ if let Some(FileHash {
+ metadata:
+ Metadata::Image {
+ animated: Some(animated_metadata),
+ ..
+ },
+ ..
+ }) = hashes.get_mut(hash)
+ {
+ *animated_metadata = animated;
+
+ Ok(())
+ } else {
+ Err(create_error!(NotFound))
+ }
+ }
+
/// Delete attachment hash by id.
async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
let mut file_hashes = self.file_hashes.lock().await;
diff --git a/crates/core/database/src/models/files/model.rs b/crates/core/database/src/models/files/model.rs
index 5ea78f9b2..1aaf8b824 100644
--- a/crates/core/database/src/models/files/model.rs
+++ b/crates/core/database/src/models/files/model.rs
@@ -70,6 +70,7 @@ auto_derived!(
LegacyGroupIcon,
ChannelIcon,
ServerIcon,
+ RoleIcon,
}
/// Information about what the file was used for
@@ -239,4 +240,23 @@ impl File {
)
.await
}
+
+ /// Use a file for a role icon
+ pub async fn use_role_icon(
+ db: &Database,
+ id: &str,
+ parent: &str,
+ uploader_id: &str,
+ ) -> Result {
+ db.find_and_use_attachment(
+ id,
+ "icons",
+ FileUsedFor {
+ id: parent.to_owned(),
+ object_type: FileUsedForType::RoleIcon,
+ },
+ uploader_id.to_owned(),
+ )
+ .await
+ }
}
diff --git a/crates/core/database/src/models/messages/model.rs b/crates/core/database/src/models/messages/model.rs
index 53d380122..bacb3c53f 100644
--- a/crates/core/database/src/models/messages/model.rs
+++ b/crates/core/database/src/models/messages/model.rs
@@ -1,5 +1,3 @@
-use std::{collections::HashSet, hash::RandomState};
-
use indexmap::{IndexMap, IndexSet};
use iso8601_timestamp::Timestamp;
use revolt_config::{config, FeaturesLimits};
@@ -9,6 +7,8 @@ use revolt_models::v0::{
};
use revolt_permissions::{calculate_channel_permissions, ChannelPermission, PermissionValue};
use revolt_result::{ErrorType, Result};
+use std::time::SystemTime;
+use std::{collections::HashSet, hash::RandomState};
use ulid::Ulid;
use validator::Validate;
@@ -114,6 +114,11 @@ auto_derived!(
MessagePinned { id: String, by: String },
#[serde(rename = "message_unpinned")]
MessageUnpinned { id: String, by: String },
+ #[serde(rename = "call_started")]
+ CallStarted {
+ by: String,
+ finished_at: Option,
+ },
}
/// Name and / or avatar override information
@@ -326,9 +331,7 @@ impl Message {
}
let server_id = match channel {
- Channel::TextChannel { ref server, .. } | Channel::VoiceChannel { ref server, .. } => {
- Some(server.clone())
- }
+ Channel::TextChannel { ref server, .. } => Some(server.clone()),
_ => None,
};
@@ -385,6 +388,7 @@ impl Message {
mut role_mentions,
mut mentions_everyone,
mut mentions_online,
+ ..
} = message_mentions;
if allow_mass_mentions && server_id.is_some() && !role_mentions.is_empty() {
@@ -440,7 +444,8 @@ impl Message {
}
// Verify replies are valid.
- let mut replies = HashSet::new();
+ let mut replies = Vec::new();
+
if let Some(entries) = data.replies {
if entries.len() > config.features.limits.global.message_replies {
return Err(create_error!(TooManyReplies {
@@ -448,6 +453,8 @@ impl Message {
}));
}
+ replies.reserve(entries.len());
+
for ReplyIntent {
id,
mention,
@@ -461,7 +468,12 @@ impl Message {
user_mentions.insert(message.author.to_owned());
}
- replies.insert(message.id);
+ // This is O(n^2), but this is faster than a HashSet
+ // when n < 20; as long as the message_replies limit
+ // is reasonable, this will be fast.
+ if !replies.contains(&message.id) {
+ replies.push(message.id);
+ }
}
// If the referenced message doesn't exist and fail_if_not_exists
// is set to false, send the message without the reply.
@@ -478,6 +490,7 @@ impl Message {
// Validate the mentions go to users in the channel/server
if !user_mentions.is_empty() {
+ #[allow(deprecated)]
match channel {
Channel::DirectMessage { ref recipients, .. }
| Channel::Group { ref recipients, .. } => {
@@ -485,8 +498,7 @@ impl Message {
user_mentions.retain(|m| recipients_hash.contains(m));
role_mentions.clear();
}
- Channel::TextChannel { ref server, .. }
- | Channel::VoiceChannel { ref server, .. } => {
+ Channel::TextChannel { ref server, .. } => {
let mentions_vec = Vec::from_iter(user_mentions.iter().cloned());
let valid_members = db.fetch_members(server.as_str(), &mentions_vec[..]).await;
@@ -534,9 +546,7 @@ impl Message {
}
if !replies.is_empty() {
- message
- .replies
- .replace(replies.into_iter().collect::>());
+ message.replies.replace(replies);
}
// Calculate final message flags
@@ -678,9 +688,13 @@ impl Message {
)
.await?;
+ let is_dm_or_group = matches!(
+ channel,
+ Channel::DirectMessage { .. } | Channel::Group { .. }
+ );
if !self.has_suppressed_notifications()
- && (self.mentions.is_some() || self.contains_mass_push_mention())
+ && (is_dm_or_group || self.mentions.is_some() || self.contains_mass_push_mention())
{
// send Push notifications
#[cfg(feature = "tasks")]
@@ -691,7 +705,7 @@ impl Message {
Some(
PushNotification::from(
self.clone().into_model(user, member),
- Some(author),
+ Some(author.clone()),
channel.to_owned().into(),
)
.await,
@@ -699,7 +713,11 @@ impl Message {
self.clone(),
match channel {
Channel::DirectMessage { recipients, .. }
- | Channel::Group { recipients, .. } => recipients.clone(),
+ | Channel::Group { recipients, .. } => recipients
+ .iter()
+ .filter(|uid| *uid != author.id())
+ .cloned()
+ .collect(),
Channel::TextChannel { .. } => {
self.mentions.clone().unwrap_or_default()
}
@@ -794,7 +812,7 @@ impl Message {
query: MessageQuery,
perspective: &User,
include_users: Option,
- server_id: Option,
+ server_id: Option<&str>,
) -> Result {
let messages: Vec = db
.fetch_messages(query)
@@ -837,6 +855,7 @@ impl Message {
v0::SystemMessage::MessageUnpinned { by, .. } => {
users.push(by.clone());
}
+ v0::SystemMessage::CallStarted { by, .. } => users.push(by.clone()),
}
}
users
@@ -851,7 +870,7 @@ impl Message {
users,
members: if let Some(server_id) = server_id {
Some(
- db.fetch_members(&server_id, &user_ids)
+ db.fetch_members(server_id, &user_ids)
.await?
.into_iter()
.map(Into::into)
@@ -1020,6 +1039,31 @@ impl Message {
Ok(())
}
+ /// Bulk delete messages by an author since a given time
+ pub async fn bulk_delete_by_author_since(
+ db: &Database,
+ channels: &[String],
+ author: &str,
+ since: SystemTime,
+ ) -> Result<()> {
+ let deleted_groups = db
+ .delete_messages_by_author_since(channels, author, since)
+ .await?;
+
+ for (channel_id, message_ids) in deleted_groups {
+ if !message_ids.is_empty() {
+ EventV1::BulkMessageDelete {
+ channel: channel_id.clone(),
+ ids: message_ids,
+ }
+ .p(channel_id)
+ .await;
+ }
+ }
+
+ Ok(())
+ }
+
/// Remove a reaction from a message
pub async fn remove_reaction(&self, db: &Database, user: &str, emoji: &str) -> Result<()> {
// Check if it actually exists
diff --git a/crates/core/database/src/models/messages/ops.rs b/crates/core/database/src/models/messages/ops.rs
index 4d54e83cb..6ebdf4c3b 100644
--- a/crates/core/database/src/models/messages/ops.rs
+++ b/crates/core/database/src/models/messages/ops.rs
@@ -1,3 +1,5 @@
+use std::collections::HashMap;
+use std::time::SystemTime;
use revolt_result::Result;
use crate::{AppendMessage, FieldsMessage, Message, MessageQuery, PartialMessage};
@@ -40,4 +42,12 @@ pub trait AbstractMessages: Sync + Send {
/// Delete messages from a channel by their ids and corresponding channel id
async fn delete_messages(&self, channel: &str, ids: &[String]) -> Result<()>;
+
+ /// Delete all messages from a specific author in a server from a certain ULID onwards
+ async fn delete_messages_by_author_since(
+ &self,
+ channels: &[String],
+ author: &str,
+ since: SystemTime
+ ) -> Result>>;
}
diff --git a/crates/core/database/src/models/messages/ops/mongodb.rs b/crates/core/database/src/models/messages/ops/mongodb.rs
index 449e71efd..2a79ed5b4 100644
--- a/crates/core/database/src/models/messages/ops/mongodb.rs
+++ b/crates/core/database/src/models/messages/ops/mongodb.rs
@@ -1,8 +1,12 @@
use bson::{to_bson, Document};
use futures::try_join;
+use futures::StreamExt;
use mongodb::options::FindOptions;
use revolt_models::v0::MessageSort;
use revolt_result::Result;
+use std::collections::{HashMap, HashSet};
+use std::time::SystemTime;
+use ulid::Ulid;
use crate::{
AppendMessage, DocumentId, FieldsMessage, IntoDocumentPath, Message, MessageQuery,
@@ -306,6 +310,112 @@ impl AbstractMessages for MongoDb {
.map(|_| ())
.map_err(|_| create_database_error!("delete_many", COL))
}
+
+ /// Delete all messages from a specific author in a server from a certain ULID onwards
+ async fn delete_messages_by_author_since(
+ &self,
+ channels: &[String],
+ author: &str,
+ since: SystemTime,
+ ) -> Result>> {
+ let threshold_ulid = Ulid::from_datetime(since).to_string();
+
+ let filter = doc! {
+ "author": author,
+ "channel": { "$in": channels },
+ "_id": { "$gte": &threshold_ulid }
+ };
+
+ let pipeline = vec![
+ doc! { "$match": filter.clone() },
+ doc! {
+ "$project": {
+ "channel": 1_i32,
+ "message_id": "$_id",
+ "attachment_ids": {
+ "$map": {
+ "input": { "$ifNull": ["$attachments", Vec::::new()] },
+ "as": "a",
+ "in": "$$a._id"
+ }
+ }
+ }
+ },
+ doc! {
+ "$group": {
+ "_id": "$channel",
+ "message_ids": { "$push": "$message_id" },
+ "attachment_ids_nested": { "$push": "$attachment_ids" }
+ }
+ },
+ doc! {
+ "$project": {
+ "message_ids": 1_i32,
+ "attachment_ids": {
+ "$reduce": {
+ "input": "$attachment_ids_nested",
+ "initialValue": Vec::::new(),
+ "in": { "$setUnion": ["$$value", "$$this"] }
+ }
+ }
+ }
+ },
+ ];
+
+ #[derive(serde::Deserialize)]
+ struct AggregatedChannel {
+ #[serde(rename = "_id")]
+ channel: String,
+ message_ids: Vec,
+ #[serde(default)]
+ attachment_ids: Vec,
+ }
+
+ let mut cursor = self
+ .col::(COL)
+ .aggregate(pipeline)
+ .await
+ .map_err(|_| create_database_error!("aggregate", COL))?
+ .with_type::();
+
+ let mut deleted_messages: HashMap> = HashMap::new();
+ let mut attachment_ids: HashSet = HashSet::new();
+
+ while let Some(result) = cursor.next().await {
+ if let Ok(item) = result {
+ for id in item.attachment_ids {
+ attachment_ids.insert(id);
+ }
+ deleted_messages.insert(item.channel, item.message_ids);
+ }
+ }
+
+ // Mark attachments as deleted before deleting messages
+ if !attachment_ids.is_empty() {
+ self.col::("attachments")
+ .update_many(
+ doc! {
+ "_id": {
+ "$in": attachment_ids.into_iter().collect::>()
+ }
+ },
+ doc! {
+ "$set": {
+ "deleted": true
+ }
+ },
+ )
+ .await
+ .map_err(|_| create_database_error!("update_many", "attachments"))?;
+ }
+
+ self.col::(COL)
+ .delete_many(filter)
+ .await
+ .map_err(|_| create_database_error!("delete_many", COL))?;
+
+ Ok(deleted_messages)
+ }
}
impl IntoDocumentPath for FieldsMessage {
diff --git a/crates/core/database/src/models/messages/ops/reference.rs b/crates/core/database/src/models/messages/ops/reference.rs
index ae5b43f03..56bf1da9c 100644
--- a/crates/core/database/src/models/messages/ops/reference.rs
+++ b/crates/core/database/src/models/messages/ops/reference.rs
@@ -1,7 +1,9 @@
+use std::collections::HashMap;
use futures::future::try_join_all;
use indexmap::IndexSet;
use revolt_result::Result;
-
+use std::time::SystemTime;
+use ulid::Ulid;
use crate::{AppendMessage, FieldsMessage, Message, MessageQuery, PartialMessage, ReferenceDb};
use super::AbstractMessages;
@@ -247,7 +249,7 @@ impl AbstractMessages for ReferenceDb {
let mut messages = self.messages.lock().await;
if let Some(message) = messages.get_mut(id) {
if let Some(users) = message.reactions.get_mut(emoji) {
- users.remove(&user.to_string());
+ users.swap_remove(&user.to_string());
}
Ok(())
@@ -260,7 +262,7 @@ impl AbstractMessages for ReferenceDb {
async fn clear_reaction(&self, id: &str, emoji: &str) -> Result<()> {
let mut messages = self.messages.lock().await;
if let Some(message) = messages.get_mut(id) {
- message.reactions.remove(emoji);
+ message.reactions.swap_remove(emoji);
Ok(())
} else {
Err(create_error!(NotFound))
@@ -286,4 +288,63 @@ impl AbstractMessages for ReferenceDb {
Ok(())
}
+
+ /// Delete all messages from a specific author in a list of channels from a certain ULID onwards
+ async fn delete_messages_by_author_since(
+ &self,
+ channels: &[String],
+ author: &str,
+ since: SystemTime
+ ) -> Result>> {
+ let threshold_ulid = Ulid::from_datetime(since).to_string();
+ let mut deleted_messages: HashMap> = HashMap::new();
+ let mut attachment_ids: Vec = Vec::new();
+
+ let messages = self.messages.lock().await;
+
+ // First pass: collect attachment IDs and message IDs to delete
+ for (id, message) in messages.iter() {
+ let should_delete = message.author == author
+ && channels.contains(&message.channel)
+ && id.as_str() >= threshold_ulid.as_str();
+
+ if should_delete {
+ // Collect attachment IDs
+ if let Some(attachments) = &message.attachments {
+ for attachment in attachments {
+ attachment_ids.push(attachment.id.clone());
+ }
+ }
+
+ deleted_messages
+ .entry(message.channel.clone())
+ .or_default()
+ .push(id.clone());
+ }
+ }
+ drop(messages);
+
+ // Mark attachments as deleted
+ if !attachment_ids.is_empty() {
+ let mut files = self.files.lock().await;
+ for attachment_id in attachment_ids {
+ if let Some(file) = files.get_mut(&attachment_id) {
+ file.deleted = Some(true);
+ }
+ }
+ }
+
+ // Delete the messages
+ self.messages
+ .lock()
+ .await
+ .retain(|id, message| {
+ let should_keep = !(message.author == author
+ && channels.contains(&message.channel)
+ && id.as_str() >= threshold_ulid.as_str());
+ should_keep
+ });
+
+ Ok(deleted_messages)
+ }
}
diff --git a/crates/core/database/src/models/server_members/model.rs b/crates/core/database/src/models/server_members/model.rs
index ee5e354bc..e242c62f5 100644
--- a/crates/core/database/src/models/server_members/model.rs
+++ b/crates/core/database/src/models/server_members/model.rs
@@ -7,6 +7,14 @@ use crate::{
Server, SystemMessage, User,
};
+fn default_true() -> bool {
+ true
+}
+
+fn is_true(x: &bool) -> bool {
+ *x
+}
+
auto_derived_partial!(
/// Server Member
pub struct Member {
@@ -30,6 +38,13 @@ auto_derived_partial!(
/// Timestamp this member is timed out until
#[serde(skip_serializing_if = "Option::is_none")]
pub timeout: Option,
+
+ /// Whether the member is server-wide voice muted
+ #[serde(skip_serializing_if = "is_true", default = "default_true")]
+ pub can_publish: bool,
+ /// Whether the member is server-wide voice deafened
+ #[serde(skip_serializing_if = "is_true", default = "default_true")]
+ pub can_receive: bool,
// This value only exists in the database, not the models.
// If it is not-None, the database layer should return None to member fetching queries.
// pub pending_deletion_at: Option
@@ -53,7 +68,10 @@ auto_derived!(
Avatar,
Roles,
Timeout,
+ CanReceive,
+ CanPublish,
JoinedAt,
+ VoiceChannel,
}
/// Member removal intention
@@ -73,6 +91,8 @@ impl Default for Member {
avatar: None,
roles: vec![],
timeout: None,
+ can_publish: true,
+ can_receive: true,
}
}
}
@@ -127,6 +147,20 @@ impl Member {
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
+ #[allow(unused_mut)]
+ let mut voice_states = Vec::new();
+
+ #[cfg(feature = "voice")]
+ for channel in &channels {
+ if let Ok(Some(voice_state)) = crate::voice::get_channel_voice_state(
+ &crate::voice::UserVoiceChannel::from_channel(channel),
+ )
+ .await
+ {
+ voice_states.push(voice_state)
+ }
+ }
+
EventV1::ServerMemberJoin {
id: server.id.clone(),
user: user.id.clone(),
@@ -144,6 +178,7 @@ impl Member {
.map(|channel| channel.into())
.collect(),
emojis: emojis.into_iter().map(|emoji| emoji.into()).collect(),
+ voice_states,
}
.private(user.id.clone())
.await;
@@ -193,11 +228,14 @@ impl Member {
pub fn remove_field(&mut self, field: &FieldsMember) {
match field {
- FieldsMember::JoinedAt => (),
+ FieldsMember::JoinedAt => {}
FieldsMember::Avatar => self.avatar = None,
FieldsMember::Nickname => self.nickname = None,
FieldsMember::Roles => self.roles.clear(),
FieldsMember::Timeout => self.timeout = None,
+ FieldsMember::CanReceive => self.can_receive = true,
+ FieldsMember::CanPublish => self.can_publish = true,
+ FieldsMember::VoiceChannel => {}
}
}
diff --git a/crates/core/database/src/models/server_members/ops/mongodb.rs b/crates/core/database/src/models/server_members/ops/mongodb.rs
index cd9c50954..377e5877a 100644
--- a/crates/core/database/src/models/server_members/ops/mongodb.rs
+++ b/crates/core/database/src/models/server_members/ops/mongodb.rs
@@ -330,12 +330,15 @@ impl AbstractServerMembers for MongoDb {
impl IntoDocumentPath for FieldsMember {
fn as_path(&self) -> Option<&'static str> {
- Some(match self {
- FieldsMember::JoinedAt => "joined_at",
- FieldsMember::Avatar => "avatar",
- FieldsMember::Nickname => "nickname",
- FieldsMember::Roles => "roles",
- FieldsMember::Timeout => "timeout",
- })
+ match self {
+ FieldsMember::JoinedAt => Some("joined_at"),
+ FieldsMember::Avatar => Some("avatar"),
+ FieldsMember::Nickname => Some("nickname"),
+ FieldsMember::Roles => Some("roles"),
+ FieldsMember::Timeout => Some("timeout"),
+ FieldsMember::CanPublish => Some("can_publish"),
+ FieldsMember::CanReceive => Some("can_receive"),
+ FieldsMember::VoiceChannel => None,
+ }
}
}
diff --git a/crates/core/database/src/models/servers/model.rs b/crates/core/database/src/models/servers/model.rs
index eea8f3922..50c45c59e 100644
--- a/crates/core/database/src/models/servers/model.rs
+++ b/crates/core/database/src/models/servers/model.rs
@@ -68,6 +68,9 @@ auto_derived_partial!(
auto_derived_partial!(
/// Role
pub struct Role {
+ /// Unique Id
+ #[serde(rename = "_id")]
+ pub id: String,
/// Role name
pub name: String,
/// Permissions available to this role
@@ -83,6 +86,9 @@ auto_derived_partial!(
/// Ranking of this role
#[serde(default)]
pub rank: i64,
+ /// Custom icon attachment
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub icon: Option,
},
"PartialRole"
);
@@ -126,6 +132,7 @@ auto_derived!(
/// Optional fields on server object
pub enum FieldsRole {
Colour,
+ Icon,
}
);
@@ -246,7 +253,6 @@ impl Server {
role.update(
db,
&self.id,
- role_id,
PartialRole {
permissions: Some(permissions),
..Default::default()
@@ -297,29 +303,41 @@ impl Role {
/// Into optional struct
pub fn into_optional(self) -> PartialRole {
PartialRole {
+ id: Some(self.id),
name: Some(self.name),
permissions: Some(self.permissions),
colour: self.colour,
hoist: Some(self.hoist),
rank: Some(self.rank),
+ icon: self.icon,
}
}
/// Create a role
- pub async fn create(&self, db: &Database, server_id: &str) -> Result {
- let role_id = Ulid::new().to_string();
- db.insert_role(server_id, &role_id, self).await?;
+ pub async fn create(db: &Database, server: &Server, name: String) -> Result {
+ let role = Role {
+ id: Ulid::new().to_string(),
+ name,
+ // Rank of the new role should be below the lowest role
+ rank: server.roles.len() as i64,
+ colour: None,
+ hoist: false,
+ permissions: Default::default(),
+ icon: None,
+ };
+
+ db.insert_role(&server.id, &role).await?;
EventV1::ServerRoleUpdate {
- id: server_id.to_string(),
- role_id: role_id.to_string(),
- data: self.clone().into_optional().into(),
+ id: server.id.clone(),
+ role_id: role.id.clone(),
+ data: role.clone().into_optional().into(),
clear: vec![],
}
- .p(server_id.to_string())
+ .p(server.id.clone())
.await;
- Ok(role_id)
+ Ok(role)
}
/// Update server data
@@ -327,7 +345,6 @@ impl Role {
&mut self,
db: &Database,
server_id: &str,
- role_id: &str,
partial: PartialRole,
remove: Vec,
) -> Result<()> {
@@ -337,14 +354,14 @@ impl Role {
self.apply_options(partial.clone());
- db.update_role(server_id, role_id, &partial, remove.clone())
+ db.update_role(server_id, &self.id, &partial, remove.clone())
.await?;
EventV1::ServerRoleUpdate {
id: server_id.to_string(),
- role_id: role_id.to_string(),
+ role_id: self.id.clone(),
data: partial.into(),
- clear: vec![],
+ clear: remove.into_iter().map(Into::into).collect(),
}
.p(server_id.to_string())
.await;
@@ -356,19 +373,20 @@ impl Role {
pub fn remove_field(&mut self, field: &FieldsRole) {
match field {
FieldsRole::Colour => self.colour = None,
+ FieldsRole::Icon => self.icon = None,
}
}
/// Delete a role
- pub async fn delete(self, db: &Database, server_id: &str, role_id: &str) -> Result<()> {
+ pub async fn delete(self, db: &Database, server_id: &str) -> Result<()> {
EventV1::ServerRoleDelete {
id: server_id.to_string(),
- role_id: role_id.to_string(),
+ role_id: self.id.clone(),
}
.p(server_id.to_string())
.await;
- db.delete_role(server_id, role_id).await
+ db.delete_role(server_id, &self.id).await
}
}
diff --git a/crates/core/database/src/models/servers/ops.rs b/crates/core/database/src/models/servers/ops.rs
index ee07ba8ff..c7ae6228d 100644
--- a/crates/core/database/src/models/servers/ops.rs
+++ b/crates/core/database/src/models/servers/ops.rs
@@ -29,7 +29,7 @@ pub trait AbstractServers: Sync + Send {
async fn delete_server(&self, id: &str) -> Result<()>;
/// Insert a new role into server object
- async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()>;
+ async fn insert_role(&self, server_id: &str, role: &Role) -> Result<()>;
/// Update an existing role on a server
async fn update_role(
diff --git a/crates/core/database/src/models/servers/ops/mongodb.rs b/crates/core/database/src/models/servers/ops/mongodb.rs
index 958963c9c..964de4103 100644
--- a/crates/core/database/src/models/servers/ops/mongodb.rs
+++ b/crates/core/database/src/models/servers/ops/mongodb.rs
@@ -69,7 +69,7 @@ impl AbstractServers for MongoDb {
}
/// Insert a new role into server object
- async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()> {
+ async fn insert_role(&self, server_id: &str, role: &Role) -> Result<()> {
self.col::(COL)
.update_one(
doc! {
@@ -77,7 +77,7 @@ impl AbstractServers for MongoDb {
},
doc! {
"$set": {
- "roles.".to_owned() + role_id: to_document(role)
+ "roles.".to_owned() + role.id.as_str(): to_document(role)
.map_err(|_| create_database_error!("to_document", "role"))?
}
},
@@ -172,6 +172,7 @@ impl IntoDocumentPath for FieldsRole {
fn as_path(&self) -> Option<&'static str> {
Some(match self {
FieldsRole::Colour => "colour",
+ FieldsRole::Icon => "icon",
})
}
}
diff --git a/crates/core/database/src/models/servers/ops/reference.rs b/crates/core/database/src/models/servers/ops/reference.rs
index 18ee1b384..572311bde 100644
--- a/crates/core/database/src/models/servers/ops/reference.rs
+++ b/crates/core/database/src/models/servers/ops/reference.rs
@@ -72,10 +72,10 @@ impl AbstractServers for ReferenceDb {
}
/// Insert a new role into server object
- async fn insert_role(&self, server_id: &str, role_id: &str, role: &Role) -> Result<()> {
+ async fn insert_role(&self, server_id: &str, role: &Role) -> Result<()> {
let mut servers = self.servers.lock().await;
if let Some(server) = servers.get_mut(server_id) {
- server.roles.insert(role_id.to_string(), role.clone());
+ server.roles.insert(role.id.clone(), role.clone());
Ok(())
} else {
Err(create_error!(NotFound))
diff --git a/crates/core/database/src/models/users/axum.rs b/crates/core/database/src/models/users/axum.rs
index da83c5b13..6b1f2f322 100644
--- a/crates/core/database/src/models/users/axum.rs
+++ b/crates/core/database/src/models/users/axum.rs
@@ -1,14 +1,20 @@
-use axum::{extract::FromRequestParts, http::request::Parts};
+use axum::{extract::{FromRef, FromRequestParts}, http::request::Parts};
use revolt_result::{create_error, Error, Result};
use crate::{Database, User};
#[async_trait::async_trait]
-impl FromRequestParts for User {
+impl FromRequestParts for User
+where
+ Database: FromRef,
+ S: Send + Sync
+{
type Rejection = Error;
- async fn from_request_parts(parts: &mut Parts, db: &Database) -> Result {
+ async fn from_request_parts(parts: &mut Parts, state: &S) -> Result {
+ let db = Database::from_ref(state);
+
if let Some(Ok(bot_token)) = parts.headers.get("x-bot-token").map(|v| v.to_str()) {
let bot = db.fetch_bot_by_token(bot_token).await?;
db.fetch_user(&bot.id).await
diff --git a/crates/core/database/src/models/users/model.rs b/crates/core/database/src/models/users/model.rs
index 0c2b6cee8..bea37fac0 100644
--- a/crates/core/database/src/models/users/model.rs
+++ b/crates/core/database/src/models/users/model.rs
@@ -7,6 +7,7 @@ use futures::future::join_all;
use iso8601_timestamp::Timestamp;
use once_cell::sync::Lazy;
use rand::seq::SliceRandom;
+use regex::{Regex, RegexBuilder};
use revolt_config::{config, FeaturesLimits};
use revolt_models::v0::{self, UserBadges, UserFlags};
use revolt_presence::filter_online;
@@ -163,6 +164,13 @@ pub static DISCRIMINATOR_SEARCH_SPACE: Lazy> = Lazy::new(|| {
set.into_iter().collect()
});
+static BLOCKED_USERNAME_PATTERNS: Lazy = Lazy::new(|| {
+ RegexBuilder::new("`{3}|(discord|rvlt|guilded|stt)\\.gg|(revolt|stoat)\\.chat|https?:\\/\\/")
+ .case_insensitive(true)
+ .build()
+ .unwrap()
+});
+
#[allow(clippy::derivable_impls)]
impl Default for User {
fn default() -> Self {
@@ -198,11 +206,13 @@ impl User {
I: Into>,
D: Into >,
{
- let username = User::validate_username(username)?;
+ let new_username = User::sanitise_username(&username).await?;
+ User::validate_username(&new_username)?;
+
let mut user = User {
id: account_id.into().unwrap_or_else(|| Ulid::new().to_string()),
- discriminator: User::find_discriminator(db, &username, None).await?,
- username,
+ discriminator: User::find_discriminator(db, &new_username, None).await?,
+ username: new_username.clone(),
last_acknowledged_policy_change: Timestamp::now_utc(),
..Default::default()
};
@@ -278,39 +288,40 @@ impl User {
}
}
- /// Sanitise and validate a username can be used
- pub fn validate_username(username: String) -> Result {
- // Copy the username for validation
+ /// Validate a username
+ ///
+ /// This will check if the username is a blocked name or contains a blocked pattern.
+ fn validate_username(username: &str) -> Result<()> {
let username_lowercase = username.to_lowercase();
- // Block homoglyphs
- if decancer::cure(&username_lowercase).into_str() != username_lowercase {
+ const BLOCKED_USERNAMES: &[&str] = &["admin", "revolt", "stoat"];
+
+ if BLOCKED_USERNAMES.contains(&username_lowercase.as_str())
+ || BLOCKED_USERNAME_PATTERNS.is_match(username)
+ {
return Err(create_error!(InvalidUsername));
}
- // Ensure the username itself isn't blocked
- const BLOCKED_USERNAMES: &[&str] = &["admin", "revolt"];
-
- for username in BLOCKED_USERNAMES {
- if username_lowercase == *username {
- return Err(create_error!(InvalidUsername));
- }
- }
+ Ok(())
+ }
- // Ensure none of the following substrings show up in the username
- const BLOCKED_SUBSTRINGS: &[&str] = &[
- "```",
- "discord.gg",
- "rvlt.gg",
- "guilded.gg",
- "https://",
- "http://",
- ];
-
- for substr in BLOCKED_SUBSTRINGS {
- if username_lowercase.contains(substr) {
- return Err(create_error!(InvalidUsername));
- }
+ /// Sanitise a username
+ ///
+ /// This will clean up Unicode homoglyphs and pad to the min username length with underscores.
+ async fn sanitise_username(username: &str) -> Result {
+ let options = decancer::Options::default().retain_capitalization();
+ let mut username = decancer::cure(username, options)
+ .map_err(|_| create_error!(InvalidUsername))?
+ .to_string();
+
+ let config = revolt_config::config().await;
+ let username_length_diff = config
+ .api
+ .users
+ .min_username_length
+ .saturating_sub(username.len());
+ if username_length_diff > 0 {
+ username.push_str(&"_".repeat(username_length_diff))
}
Ok(username)
@@ -416,12 +427,14 @@ impl User {
/// Update a user's username
pub async fn update_username(&mut self, db: &Database, username: String) -> Result<()> {
- let username = User::validate_username(username)?;
- if self.username.to_lowercase() == username.to_lowercase() {
+ let new_username = User::sanitise_username(&username).await?;
+ User::validate_username(&new_username)?;
+
+ if self.username.to_lowercase() == new_username.to_lowercase() {
self.update(
db,
PartialUser {
- username: Some(username),
+ username: Some(new_username),
..Default::default()
},
vec![],
@@ -434,12 +447,12 @@ impl User {
discriminator: Some(
User::find_discriminator(
db,
- &username,
+ &new_username,
Some((self.discriminator.to_string(), self.id.clone())),
)
.await?,
),
- username: Some(username),
+ username: Some(new_username),
..Default::default()
},
vec![],
@@ -825,3 +838,109 @@ impl User {
badges
}
}
+
+#[cfg(test)]
+mod tests {
+ use crate::User;
+
+ #[test]
+ fn username_validation_blocked_names() {
+ let username_admin = "Admin";
+ let username_revolt = "Revolt";
+ let username_stoat = "Stoat";
+ let username_allowed = "Allowed";
+
+ assert!(User::validate_username(username_admin).is_err());
+ assert!(User::validate_username(username_revolt).is_err());
+ assert!(User::validate_username(username_stoat).is_err());
+ assert!(User::validate_username(username_allowed).is_ok());
+ }
+
+ #[test]
+ fn username_validation_blocked_patterns() {
+ let username_grave = "```_test";
+ let username_discord = "discord.gg_test";
+ let username_rvlt = "rvlt.gg_test";
+ let username_guilded = "guilded.gg_test";
+ let username_stt = "stt.gg_test";
+ let username_revolt = "revolt.chat_test";
+ let username_stoat = "stoat.chat_test";
+ let username_http = "http://_test";
+ let username_https = "https://_test";
+
+ assert!(User::validate_username(username_grave).is_err());
+ assert!(User::validate_username(username_discord).is_err());
+ assert!(User::validate_username(username_rvlt).is_err());
+ assert!(User::validate_username(username_guilded).is_err());
+ assert!(User::validate_username(username_stt).is_err());
+ assert!(User::validate_username(username_revolt).is_err());
+ assert!(User::validate_username(username_stoat).is_err());
+ assert!(User::validate_username(username_http).is_err());
+ assert!(User::validate_username(username_https).is_err());
+ }
+
+ #[async_std::test]
+ async fn username_sanitisation_clean() {
+ let username_clean = "Test";
+
+ let username_clean_sanitised = User::sanitise_username(username_clean).await;
+
+ assert!(username_clean_sanitised.is_ok());
+ assert_eq!(username_clean, username_clean_sanitised.unwrap());
+ }
+
+ #[async_std::test]
+ async fn username_sanitisation_homoglyphs() {
+ let username_homoglyphs = "𝔽𝕌Ňℕy";
+
+ let username_homoglyphs_sanitised =
+ User::sanitise_username(username_homoglyphs).await.unwrap();
+
+ assert_ne!(username_homoglyphs, username_homoglyphs_sanitised);
+ assert_eq!("funny", username_homoglyphs_sanitised);
+ }
+
+ #[async_std::test]
+ async fn username_sanitisation_padding() {
+ let username_padding = "a";
+
+ let username = User::sanitise_username(username_padding).await.unwrap();
+
+ assert_eq!("a_", username);
+ }
+
+ #[async_std::test]
+ async fn create_user() {
+ use revolt_result::Result;
+
+ database_test!(|db| async move {
+ let mut created_clean = User::create(&db, "Test".to_string(), None, None)
+ .await
+ .unwrap();
+
+ assert_eq!("Test", created_clean.username);
+
+ created_clean
+ .update_username(&db, "Test2".to_string())
+ .await
+ .unwrap();
+
+ assert_eq!("Test2", created_clean.username);
+
+ let created_invalid_result: Result<_> =
+ User::create(&db, "stoat.chat".to_string(), None, None).await;
+
+ assert!(created_invalid_result.is_err());
+
+ let mut updated_invalid = User::create(&db, "Test".to_string(), None, None)
+ .await
+ .unwrap();
+
+ let updated_invalid_update_result = updated_invalid
+ .update_username(&db, "http://test".to_string())
+ .await;
+
+ assert!(updated_invalid_update_result.is_err());
+ });
+ }
+}
diff --git a/crates/core/database/src/tasks/ack.rs b/crates/core/database/src/tasks/ack.rs
index c12e41f3c..24e3805ee 100644
--- a/crates/core/database/src/tasks/ack.rs
+++ b/crates/core/database/src/tasks/ack.rs
@@ -14,7 +14,7 @@ use validator::HasLen;
use revolt_result::Result;
use super::DelayedTask;
-use crate::Channel::{TextChannel, VoiceChannel};
+use crate::Channel::TextChannel;
/// Enumeration of possible events
#[derive(Debug, Eq, PartialEq)]
@@ -105,7 +105,11 @@ pub async fn handle_ack_event(
if mentions_acked > 0 {
if let Err(err) = amqp
- .ack_message(user.to_string(), channel.to_string(), id.to_owned())
+ .ack_notification_message(
+ user.to_string(),
+ channel.to_string(),
+ id.to_owned(),
+ )
.await
{
revolt_config::capture_error(&err);
@@ -191,17 +195,12 @@ pub async fn handle_ack_event(
.await
.expect("Failed to fetch channel from db");
- match channel {
- TextChannel { server, .. } | VoiceChannel { server, .. } => {
- if let Err(err) =
- amqp.mass_mention_message_sent(server, mass_mentions).await
- {
- revolt_config::capture_error(&err);
- }
- }
- _ => {
- panic!("Unknown channel type when sending mass mention event");
+ if let TextChannel { server, .. } = channel {
+ if let Err(err) = amqp.mass_mention_message_sent(server, mass_mentions).await {
+ revolt_config::capture_error(&err);
}
+ } else {
+ panic!("Unknown channel type when sending mass mention event");
}
}
}
diff --git a/crates/core/database/src/util/acker.rs b/crates/core/database/src/util/acker.rs
new file mode 100644
index 000000000..8cc104c57
--- /dev/null
+++ b/crates/core/database/src/util/acker.rs
@@ -0,0 +1,77 @@
+use redis_kiss::{get_connection, AsyncCommands};
+use revolt_permissions::{calculate_channel_permissions, ChannelPermission};
+use revolt_result::{Result, ToRevoltError};
+
+use crate::{events::client::EventV1, Channel, Database, Server, User, AMQP};
+
+pub async fn ack_channel(user: &str, channel: &str, message: &str, amqp: &AMQP) -> Result<()> {
+ let mut redis = get_connection()
+ .await
+ .map_err(|_| create_error!(InternalError))?;
+
+ let old: Option = redis
+ .getset(format!("acker:{user}+{channel}"), message)
+ .await
+ .to_internal_error()?;
+
+ if old.is_none() || old.unwrap() == message {
+ amqp.process_ack(user, Some(channel), None)
+ .await
+ .to_internal_error()?;
+ }
+
+ Ok(())
+}
+
+pub async fn ack_server(user: &User, server: &Server, db: &Database, amqp: &AMQP) -> Result<()> {
+ let mut redis = get_connection()
+ .await
+ .map_err(|_| create_error!(InternalError))?;
+
+ let channels = db.fetch_channels(&server.channels).await?;
+ let query = crate::util::permissions::DatabasePermissionQuery::new(db, user).server(server);
+
+ for channel in channels {
+ let channel_id = channel.id();
+ let mut q = query.clone().channel(&channel);
+
+ if calculate_channel_permissions(&mut q)
+ .await
+ .has_channel_permission(ChannelPermission::ViewChannel)
+ {
+ let channel_last_msg = match &channel {
+ Channel::TextChannel {
+ last_message_id, ..
+ } => last_message_id,
+ _ => unreachable!(),
+ }
+ .clone();
+
+ if let Some(channel_last_msg) = channel_last_msg {
+ let old: Option = redis
+ .getset(
+ format!("acker:{}+{}", user.id, channel_id),
+ &channel_last_msg,
+ )
+ .await
+ .to_internal_error()?;
+
+ if old.is_none() || old.unwrap() == channel_last_msg {
+ amqp.process_ack(&user.id, Some(channel_id), Some(&server.id))
+ .await
+ .to_internal_error()?;
+
+ EventV1::ChannelAck {
+ id: channel_id.to_string(),
+ user: user.id.clone(),
+ message_id: channel_last_msg,
+ }
+ .private(user.id.clone())
+ .await;
+ }
+ }
+ }
+ }
+
+ Ok(())
+}
diff --git a/crates/core/database/src/util/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs
index de64a4bf2..45b80e55f 100644
--- a/crates/core/database/src/util/bridge/v0.rs
+++ b/crates/core/database/src/util/bridge/v0.rs
@@ -143,6 +143,7 @@ impl From for FieldsWebhook {
}
impl From for Channel {
+ #[allow(deprecated)]
fn from(value: crate::Channel) -> Self {
match value {
crate::Channel::SavedMessages { id, user } => Channel::SavedMessages { id, user },
@@ -188,6 +189,8 @@ impl From for Channel {
default_permissions,
role_permissions,
nsfw,
+ voice,
+ slowmode,
} => Channel::TextChannel {
id,
server,
@@ -198,31 +201,15 @@ impl From for Channel {
default_permissions,
role_permissions,
nsfw,
- },
- crate::Channel::VoiceChannel {
- id,
- server,
- name,
- description,
- icon,
- default_permissions,
- role_permissions,
- nsfw,
- } => Channel::VoiceChannel {
- id,
- server,
- name,
- description,
- icon: icon.map(|file| file.into()),
- default_permissions,
- role_permissions,
- nsfw,
+ voice: voice.map(|voice| voice.into()),
+ slowmode,
},
}
}
}
impl From for crate::Channel {
+ #[allow(deprecated)]
fn from(value: Channel) -> crate::Channel {
match value {
Channel::SavedMessages { id, user } => crate::Channel::SavedMessages { id, user },
@@ -268,6 +255,8 @@ impl From for crate::Channel {
default_permissions,
role_permissions,
nsfw,
+ voice,
+ slowmode,
} => crate::Channel::TextChannel {
id,
server,
@@ -278,25 +267,8 @@ impl From for crate::Channel {
default_permissions,
role_permissions,
nsfw,
- },
- Channel::VoiceChannel {
- id,
- server,
- name,
- description,
- icon,
- default_permissions,
- role_permissions,
- nsfw,
- } => crate::Channel::VoiceChannel {
- id,
- server,
- name,
- description,
- icon: icon.map(|file| file.into()),
- default_permissions,
- role_permissions,
- nsfw,
+ voice: voice.map(|voice| voice.into()),
+ slowmode,
},
}
}
@@ -315,6 +287,8 @@ impl From for PartialChannel {
role_permissions: value.role_permissions,
default_permissions: value.default_permissions,
last_message_id: value.last_message_id,
+ voice: value.voice.map(|voice| voice.into()),
+ slowmode: value.slowmode,
}
}
}
@@ -332,6 +306,8 @@ impl From for crate::PartialChannel {
role_permissions: value.role_permissions,
default_permissions: value.default_permissions,
last_message_id: value.last_message_id,
+ voice: value.voice.map(|voice| voice.into()),
+ slowmode: value.slowmode,
}
}
}
@@ -342,6 +318,7 @@ impl From for crate::FieldsChannel {
FieldsChannel::Description => crate::FieldsChannel::Description,
FieldsChannel::Icon => crate::FieldsChannel::Icon,
FieldsChannel::DefaultPermissions => crate::FieldsChannel::DefaultPermissions,
+ FieldsChannel::Voice => crate::FieldsChannel::Voice,
}
}
}
@@ -352,6 +329,7 @@ impl From for FieldsChannel {
crate::FieldsChannel::Description => FieldsChannel::Description,
crate::FieldsChannel::Icon => FieldsChannel::Icon,
crate::FieldsChannel::DefaultPermissions => FieldsChannel::DefaultPermissions,
+ crate::FieldsChannel::Voice => FieldsChannel::Voice,
}
}
}
@@ -434,9 +412,16 @@ impl From for Metadata {
match value {
crate::Metadata::File => Metadata::File,
crate::Metadata::Text => Metadata::Text,
- crate::Metadata::Image { width, height } => Metadata::Image {
+ crate::Metadata::Image {
+ width,
+ height,
+ thumbhash,
+ animated,
+ } => Metadata::Image {
width: width as usize,
height: height as usize,
+ thumbhash,
+ animated,
},
crate::Metadata::Video { width, height } => Metadata::Video {
width: width as usize,
@@ -452,9 +437,16 @@ impl From for crate::Metadata {
match value {
Metadata::File => crate::Metadata::File,
Metadata::Text => crate::Metadata::Text,
- Metadata::Image { width, height } => crate::Metadata::Image {
+ Metadata::Image {
+ width,
+ height,
+ thumbhash,
+ animated,
+ } => crate::Metadata::Image {
width: width as isize,
height: height as isize,
+ thumbhash,
+ animated,
},
Metadata::Video { width, height } => crate::Metadata::Video {
width: width as isize,
@@ -543,6 +535,9 @@ impl From for SystemMessage {
crate::SystemMessage::UserRemove { id, by } => Self::UserRemove { id, by },
crate::SystemMessage::MessagePinned { id, by } => Self::MessagePinned { id, by },
crate::SystemMessage::MessageUnpinned { id, by } => Self::MessageUnpinned { id, by },
+ crate::SystemMessage::CallStarted { by, finished_at } => {
+ Self::CallStarted { by, finished_at }
+ }
}
}
}
@@ -639,6 +634,8 @@ impl From for Member {
avatar: value.avatar.map(|f| f.into()),
roles: value.roles,
timeout: value.timeout,
+ can_publish: value.can_publish,
+ can_receive: value.can_receive,
}
}
}
@@ -652,6 +649,8 @@ impl From for crate::Member {
avatar: value.avatar.map(|f| f.into()),
roles: value.roles,
timeout: value.timeout,
+ can_publish: value.can_publish,
+ can_receive: value.can_receive,
}
}
}
@@ -665,6 +664,8 @@ impl From for PartialMember {
avatar: value.avatar.map(|f| f.into()),
roles: value.roles,
timeout: value.timeout,
+ can_publish: value.can_publish,
+ can_receive: value.can_receive,
}
}
}
@@ -678,6 +679,8 @@ impl From for crate::PartialMember {
avatar: value.avatar.map(|f| f.into()),
roles: value.roles,
timeout: value.timeout,
+ can_publish: value.can_publish,
+ can_receive: value.can_receive,
}
}
}
@@ -707,7 +710,10 @@ impl From for FieldsMember {
crate::FieldsMember::Nickname => FieldsMember::Nickname,
crate::FieldsMember::Roles => FieldsMember::Roles,
crate::FieldsMember::Timeout => FieldsMember::Timeout,
+ crate::FieldsMember::CanReceive => FieldsMember::CanReceive,
+ crate::FieldsMember::CanPublish => FieldsMember::CanPublish,
crate::FieldsMember::JoinedAt => FieldsMember::JoinedAt,
+ crate::FieldsMember::VoiceChannel => FieldsMember::VoiceChannel,
}
}
}
@@ -719,7 +725,10 @@ impl From for crate::FieldsMember {
FieldsMember::Nickname => crate::FieldsMember::Nickname,
FieldsMember::Roles => crate::FieldsMember::Roles,
FieldsMember::Timeout => crate::FieldsMember::Timeout,
+ FieldsMember::CanReceive => crate::FieldsMember::CanReceive,
+ FieldsMember::CanPublish => crate::FieldsMember::CanPublish,
FieldsMember::JoinedAt => crate::FieldsMember::JoinedAt,
+ FieldsMember::VoiceChannel => crate::FieldsMember::VoiceChannel,
}
}
}
@@ -911,11 +920,13 @@ impl From for crate::SystemMessageChannels {
impl From for Role {
fn from(value: crate::Role) -> Self {
Role {
+ id: value.id,
name: value.name,
permissions: value.permissions,
colour: value.colour,
hoist: value.hoist,
rank: value.rank,
+ icon: value.icon.map(|f| f.into()),
}
}
}
@@ -923,11 +934,13 @@ impl From for Role {
impl From for crate::Role {
fn from(value: Role) -> crate::Role {
crate::Role {
+ id: value.id,
name: value.name,
permissions: value.permissions,
colour: value.colour,
hoist: value.hoist,
rank: value.rank,
+ icon: value.icon.map(|f| f.into()),
}
}
}
@@ -935,11 +948,13 @@ impl From for crate::Role {
impl From for PartialRole {
fn from(value: crate::PartialRole) -> Self {
PartialRole {
+ id: value.id,
name: value.name,
permissions: value.permissions,
colour: value.colour,
hoist: value.hoist,
rank: value.rank,
+ icon: value.icon.map(|f| f.into()),
}
}
}
@@ -947,11 +962,13 @@ impl From for PartialRole {
impl From for crate::PartialRole {
fn from(value: PartialRole) -> crate::PartialRole {
crate::PartialRole {
+ id: value.id,
name: value.name,
permissions: value.permissions,
colour: value.colour,
hoist: value.hoist,
rank: value.rank,
+ icon: value.icon.map(|f| f.into()),
}
}
}
@@ -960,6 +977,7 @@ impl From for FieldsRole {
fn from(value: crate::FieldsRole) -> Self {
match value {
crate::FieldsRole::Colour => FieldsRole::Colour,
+ crate::FieldsRole::Icon => FieldsRole::Icon,
}
}
}
@@ -968,6 +986,7 @@ impl From for crate::FieldsRole {
fn from(value: FieldsRole) -> Self {
match value {
FieldsRole::Colour => crate::FieldsRole::Colour,
+ FieldsRole::Icon => crate::FieldsRole::Icon,
}
}
}
@@ -1387,3 +1406,19 @@ impl From for crate::FieldsMessage {
}
}
}
+
+impl From for crate::VoiceInformation {
+ fn from(value: VoiceInformation) -> Self {
+ crate::VoiceInformation {
+ max_users: value.max_users,
+ }
+ }
+}
+
+impl From for VoiceInformation {
+ fn from(value: crate::VoiceInformation) -> Self {
+ VoiceInformation {
+ max_users: value.max_users,
+ }
+ }
+}
diff --git a/crates/core/database/src/util/bulk_permissions.rs b/crates/core/database/src/util/bulk_permissions.rs
index 9e7a1f7a1..976b0d692 100644
--- a/crates/core/database/src/util/bulk_permissions.rs
+++ b/crates/core/database/src/util/bulk_permissions.rs
@@ -144,10 +144,6 @@ impl<'z> BulkDatabasePermissionQuery<'z> {
Channel::TextChannel {
default_permissions,
..
- }
- | Channel::VoiceChannel {
- default_permissions,
- ..
} => default_permissions.unwrap_or_default().into(),
_ => Default::default(),
}
@@ -156,16 +152,14 @@ impl<'z> BulkDatabasePermissionQuery<'z> {
}
}
- #[allow(dead_code)]
+ #[allow(dead_code, deprecated)]
fn get_channel_type(&mut self) -> ChannelType {
if let Some(channel) = &self.channel {
match channel {
Channel::DirectMessage { .. } => ChannelType::DirectMessage,
Channel::Group { .. } => ChannelType::Group,
Channel::SavedMessages { .. } => ChannelType::SavedMessages,
- Channel::TextChannel { .. } | Channel::VoiceChannel { .. } => {
- ChannelType::ServerChannel
- }
+ Channel::TextChannel { .. } => ChannelType::ServerChannel,
}
} else {
ChannelType::Unknown
@@ -179,9 +173,6 @@ impl<'z> BulkDatabasePermissionQuery<'z> {
match channel {
Channel::TextChannel {
role_permissions, ..
- }
- | Channel::VoiceChannel {
- role_permissions, ..
} => role_permissions,
_ => panic!("Not supported for non-server channels"),
}
@@ -208,12 +199,6 @@ async fn calculate_members_permissions<'a>(
role_permissions,
default_permissions,
..
- }
- | Channel::VoiceChannel {
- id,
- role_permissions,
- default_permissions,
- ..
} => (id, role_permissions, default_permissions),
_ => panic!("Calculation of member permissions must be done on a server channel"),
};
diff --git a/crates/core/database/src/util/funcs.rs b/crates/core/database/src/util/funcs.rs
new file mode 100644
index 000000000..24f9906d4
--- /dev/null
+++ b/crates/core/database/src/util/funcs.rs
@@ -0,0 +1,24 @@
+use crate::Database;
+use revolt_result::Result;
+
+/// Formats a user's name depending on their optional features and location.
+/// Factors in server display names and user display names before falling back to username#discriminator.
+/// Passing a server in which the user is not a member will result in an Err.
+pub async fn format_display_name(
+ db: &Database,
+ user_id: &str,
+ server_id: Option<&str>,
+) -> Result {
+ if let Some(server_id) = server_id {
+ let member = db.fetch_member(server_id, user_id).await?;
+ if let Some(nick) = member.nickname {
+ return Ok(nick);
+ }
+ }
+
+ let user = db.fetch_user(user_id).await?;
+ if let Some(display) = user.display_name {
+ return Ok(display);
+ }
+ Ok(format!("{}#{}", user.username, user.discriminator))
+}
diff --git a/crates/core/database/src/util/mod.rs b/crates/core/database/src/util/mod.rs
index 1baf7d8ba..ae2817ac0 100644
--- a/crates/core/database/src/util/mod.rs
+++ b/crates/core/database/src/util/mod.rs
@@ -1,6 +1,10 @@
+pub mod acker;
pub mod bridge;
pub mod bulk_permissions;
+mod funcs;
pub mod idempotency;
pub mod permissions;
pub mod reference;
pub mod test_fixtures;
+
+pub use funcs::*;
diff --git a/crates/core/database/src/util/permissions.rs b/crates/core/database/src/util/permissions.rs
index e592988eb..85db48267 100644
--- a/crates/core/database/src/util/permissions.rs
+++ b/crates/core/database/src/util/permissions.rs
@@ -185,9 +185,26 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
}
}
+ async fn do_we_have_publish_overwrites(&mut self) -> bool {
+ if let Some(member) = &self.member {
+ member.can_publish
+ } else {
+ true
+ }
+ }
+
+ async fn do_we_have_receive_overwrites(&mut self) -> bool {
+ if let Some(member) = &self.member {
+ member.can_receive
+ } else {
+ true
+ }
+ }
+
// * For calculating channel permission
/// Get the type of the channel
+ #[allow(deprecated)]
async fn get_channel_type(&mut self) -> ChannelType {
if let Some(channel) = &self.channel {
match channel {
@@ -199,9 +216,7 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
Cow::Borrowed(Channel::SavedMessages { .. })
| Cow::Owned(Channel::SavedMessages { .. }) => ChannelType::SavedMessages,
Cow::Borrowed(Channel::TextChannel { .. })
- | Cow::Owned(Channel::TextChannel { .. })
- | Cow::Borrowed(Channel::VoiceChannel { .. })
- | Cow::Owned(Channel::VoiceChannel { .. }) => ChannelType::ServerChannel,
+ | Cow::Owned(Channel::TextChannel { .. }) => ChannelType::ServerChannel,
}
} else {
ChannelType::Unknown
@@ -225,14 +240,6 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
| Cow::Owned(Channel::TextChannel {
default_permissions,
..
- })
- | Cow::Borrowed(Channel::VoiceChannel {
- default_permissions,
- ..
- })
- | Cow::Owned(Channel::VoiceChannel {
- default_permissions,
- ..
}) => default_permissions.unwrap_or_default().into(),
_ => Default::default(),
}
@@ -250,12 +257,6 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
})
| Cow::Owned(Channel::TextChannel {
role_permissions, ..
- })
- | Cow::Borrowed(Channel::VoiceChannel {
- role_permissions, ..
- })
- | Cow::Owned(Channel::VoiceChannel {
- role_permissions, ..
}) => {
if let Some(server) = &self.server {
let member_roles = self
@@ -343,11 +344,10 @@ impl PermissionQuery for DatabasePermissionQuery<'_> {
/// (this will only ever be called for server channels, use unimplemented!() for other code paths)
async fn set_server_from_channel(&mut self) {
if let Some(channel) = &self.channel {
+ #[allow(deprecated)]
match channel {
Cow::Borrowed(Channel::TextChannel { server, .. })
- | Cow::Owned(Channel::TextChannel { server, .. })
- | Cow::Borrowed(Channel::VoiceChannel { server, .. })
- | Cow::Owned(Channel::VoiceChannel { server, .. }) => {
+ | Cow::Owned(Channel::TextChannel { server, .. }) => {
if let Some(known_server) =
// I'm not sure why I can't just pattern match both at once here?
// It throws some weird error and the provided fix doesn't work :/
diff --git a/crates/core/database/src/voice/mod.rs b/crates/core/database/src/voice/mod.rs
new file mode 100644
index 000000000..836de92ad
--- /dev/null
+++ b/crates/core/database/src/voice/mod.rs
@@ -0,0 +1,696 @@
+use std::fmt::{Display, Write};
+
+use crate::{
+ events::client::EventV1,
+ models::{Channel, User},
+ util::{permissions::DatabasePermissionQuery, reference::Reference},
+ Database, Server,
+};
+use iso8601_timestamp::{Duration, Timestamp};
+use livekit_protocol::ParticipantPermission;
+use redis_kiss::{
+ get_connection as _get_connection,
+ redis::{FromRedisValue, Pipeline, RedisError, RedisWrite, ToRedisArgs, Value},
+ AsyncCommands, Conn,
+};
+use revolt_config::FeaturesLimits;
+use revolt_models::v0::{self, PartialUserVoiceState, UserVoiceState};
+use revolt_permissions::{calculate_channel_permissions, ChannelPermission, PermissionValue};
+use revolt_result::{create_error, Result, ToRevoltError};
+
+mod voice_client;
+pub use voice_client::VoiceClient;
+
+async fn get_connection() -> Result {
+ _get_connection()
+ .await
+ .map_err(|_| create_error!(InternalError))
+}
+
+pub async fn raise_if_in_voice(user: &User, channel: &UserVoiceChannel) -> Result<()> {
+ let mut conn = get_connection().await?;
+
+ if user.bot.is_some() {
+ // bots can be in as many voice channels as it wants so we just check if its already connected to the one its trying to connect to
+ if conn
+ .sismember(format!("vc:{}", &user.id), channel)
+ .await
+ .to_internal_error()?
+ {
+ return Err(create_error!(AlreadyConnected));
+ };
+ } else if conn
+ .scard::<_, u32>(format!("vc:{}", &user.id)) // check if the current vc set is empty
+ .await
+ .to_internal_error()?
+ > 0
+ {
+ return Err(create_error!(AlreadyConnected));
+ };
+
+ Ok(())
+}
+
+pub async fn set_channel_node(channel_id: &str, node: &str) -> Result<()> {
+ get_connection()
+ .await?
+ .set(format!("node:{channel_id}"), node)
+ .await
+ .to_internal_error()
+}
+
+pub async fn get_channel_node(channel_id: &str) -> Result> {
+ get_connection()
+ .await?
+ .get(format!("node:{channel_id}"))
+ .await
+ .to_internal_error()
+}
+
+pub async fn delete_channel_node(channel_id: &str) -> Result<()> {
+ get_connection()
+ .await?
+ .del(format!("node:{channel_id}"))
+ .await
+ .to_internal_error()
+}
+
+pub async fn get_user_voice_channels(user_id: &str) -> Result> {
+ get_connection()
+ .await?
+ .smembers(format!("vc:{user_id}"))
+ .await
+ .to_internal_error()
+}
+
+pub async fn set_user_moved_from_voice(
+ old_channel_id: &str,
+ new_channel: &UserVoiceChannel,
+ user_id: &str,
+) -> Result<()> {
+ get_connection()
+ .await?
+ .set_ex(
+ format!("moved_from:{user_id}:{old_channel_id}"),
+ new_channel,
+ 10,
+ )
+ .await
+ .to_internal_error()
+}
+
+pub async fn get_user_moved_from_voice(channel_id: &str, user_id: &str) -> Result> {
+ get_connection()
+ .await?
+ .get_del(format!("moved_from:{user_id}:{channel_id}"))
+ .await
+ .to_internal_error()
+}
+
+pub async fn set_user_moved_to_voice(
+ new_channel_id: &str,
+ old_channel: &UserVoiceChannel,
+ user_id: &str,
+) -> Result<()> {
+ get_connection()
+ .await?
+ .set_ex(
+ format!("moved_to:{user_id}:{new_channel_id}"),
+ old_channel,
+ 10,
+ )
+ .await
+ .to_internal_error()
+}
+
+pub async fn get_user_moved_to_voice(
+ channel_id: &str,
+ user_id: &str,
+) -> Result > {
+ get_connection()
+ .await?
+ .get_del(format!("moved_to:{user_id}:{channel_id}"))
+ .await
+ .to_internal_error()
+}
+
+pub async fn is_in_voice_channel(user_id: &str, channel: &UserVoiceChannel) -> Result {
+ get_connection()
+ .await?
+ .sismember(format!("vc:{user_id}"), channel)
+ .await
+ .to_internal_error()
+}
+
+pub async fn get_user_voice_channel_in_server(
+ user_id: &str,
+ server_id: &str,
+) -> Result> {
+ let mut conn = get_connection().await?;
+
+ let unique_key = format!("{user_id}:{server_id}");
+
+ conn.get(&unique_key).await.to_internal_error()
+}
+
+pub fn get_allowed_sources(
+ limits: &FeaturesLimits,
+ permissions: PermissionValue,
+) -> Vec<&'static str> {
+ let mut allowed_sources = Vec::new();
+
+ if permissions.has(ChannelPermission::Speak as u64) {
+ allowed_sources.push("microphone")
+ };
+
+ if permissions.has(ChannelPermission::Video as u64) && limits.video {
+ allowed_sources.extend(["camera", "screen_share", "screen_share_audio"]);
+ };
+
+ allowed_sources
+}
+
+pub async fn create_voice_state(
+ channel: &UserVoiceChannel,
+ user_id: &str,
+ joined_at: Timestamp,
+) -> Result {
+ let unique_key = format!(
+ "{}:{}",
+ &user_id,
+ channel.server_id.as_ref().unwrap_or(&channel.id)
+ );
+
+ let voice_state = UserVoiceState {
+ joined_at,
+ id: user_id.to_string(),
+ is_receiving: true,
+ is_publishing: false,
+ screensharing: false,
+ camera: false,
+ };
+
+ Pipeline::new()
+ .sadd(format!("vc_members:{}", &channel.id), user_id)
+ .sadd(format!("vc:{user_id}"), channel)
+ .set(&unique_key, &channel.id)
+ .set(
+ format!("joined_at:{unique_key}"),
+ joined_at
+ .duration_since(Timestamp::UNIX_EPOCH)
+ .whole_milliseconds() as i64,
+ )
+ .set(
+ format!("is_publishing:{unique_key}"),
+ voice_state.is_publishing,
+ )
+ .set(
+ format!("is_receiving:{unique_key}"),
+ voice_state.is_receiving,
+ )
+ .set(
+ format!("screensharing:{unique_key}"),
+ voice_state.screensharing,
+ )
+ .set(format!("camera:{unique_key}"), voice_state.camera)
+ .query_async::<_, ()>(&mut get_connection().await?.into_inner())
+ .await
+ .to_internal_error()?;
+
+ Ok(voice_state)
+}
+
+pub async fn delete_voice_state(channel: &UserVoiceChannel, user_id: &str) -> Result<()> {
+ let unique_key = format!(
+ "{}:{}",
+ &user_id,
+ channel.server_id.as_ref().unwrap_or(&channel.id)
+ );
+
+ Pipeline::new()
+ .srem(format!("vc_members:{}", &channel.id), user_id)
+ .srem(format!("vc:{user_id}"), channel)
+ .del(&[
+ format!("joined_at:{unique_key}"),
+ format!("is_publishing:{unique_key}"),
+ format!("is_receiving:{unique_key}"),
+ format!("screensharing:{unique_key}"),
+ format!("camera:{unique_key}"),
+ unique_key.clone(),
+ ])
+ .query_async(&mut get_connection().await?.into_inner())
+ .await
+ .to_internal_error()
+}
+
+pub async fn delete_channel_voice_state(
+ channel: &UserVoiceChannel,
+ user_ids: &[String],
+) -> Result<()> {
+ let parent_id = channel.server_id.as_ref().unwrap_or(&channel.id);
+
+ let mut pipeline = Pipeline::new();
+ pipeline.del(format!("vc_members:{}", &channel.id));
+ pipeline.del(format!("node:{}", &channel.id));
+
+ for user_id in user_ids {
+ let unique_key = format!("{user_id}:{parent_id}");
+
+ pipeline.srem(format!("vc:{user_id}"), channel).del(&[
+ format!("joined_at:{unique_key}"),
+ format!("is_publishing:{unique_key}"),
+ format!("is_receiving:{unique_key}"),
+ format!("screensharing:{unique_key}"),
+ format!("camera:{unique_key}"),
+ unique_key.clone(),
+ ]);
+ }
+
+ pipeline
+ .query_async(&mut get_connection().await?.into_inner())
+ .await
+ .to_internal_error()
+}
+
+pub async fn update_voice_state_tracks(
+ channel: &UserVoiceChannel,
+ user_id: &str,
+ added: bool,
+ track: i32,
+) -> Result {
+ let partial = match track {
+ /* TrackSource::Unknown */ 0 => PartialUserVoiceState::default(),
+ /* TrackSource::Camera */
+ 1 => PartialUserVoiceState {
+ camera: Some(added),
+ ..Default::default()
+ },
+ /* TrackSource::Microphone */
+ 2 => PartialUserVoiceState {
+ is_publishing: Some(added),
+ ..Default::default()
+ },
+ /* TrackSource::ScreenShare | TrackSource::ScreenShareAudio */
+ 3 | 4 => PartialUserVoiceState {
+ screensharing: Some(added),
+ ..Default::default()
+ },
+ _ => unreachable!(),
+ };
+
+ update_voice_state(channel, user_id, &partial).await?;
+
+ Ok(partial)
+}
+
+pub async fn update_voice_state(
+ channel: &UserVoiceChannel,
+ user_id: &str,
+ partial: &PartialUserVoiceState,
+) -> Result<()> {
+ let unique_key = format!(
+ "{}:{}",
+ &user_id,
+ channel.server_id.as_ref().unwrap_or(&channel.id)
+ );
+
+ let mut pipeline = Pipeline::new();
+
+ if let Some(camera) = &partial.camera {
+ pipeline.set(format!("camera:{unique_key}"), camera);
+ };
+
+ if let Some(is_publishing) = &partial.is_publishing {
+ pipeline.set(format!("is_publishing:{unique_key}"), is_publishing);
+ }
+
+ if let Some(is_receiving) = &partial.is_receiving {
+ pipeline.set(format!("is_receiving:{unique_key}"), is_receiving);
+ }
+
+ if let Some(screensharing) = &partial.screensharing {
+ pipeline.set(format!("screensharing:{unique_key}"), screensharing);
+ }
+
+ pipeline
+ .query_async(&mut get_connection().await?.into_inner())
+ .await
+ .to_internal_error()
+}
+
+pub async fn get_voice_channel_members(channel: &UserVoiceChannel) -> Result>> {
+ get_connection()
+ .await?
+ .smembers::<_, Option>>(format!("vc_members:{}", &channel.id))
+ .await
+ .to_internal_error()
+ .map(|opt| opt.and_then(|v| if v.is_empty() { None } else { Some(v) }))
+}
+
+pub async fn get_voice_state(
+ channel: &UserVoiceChannel,
+ user_id: &str,
+) -> Result> {
+ let unique_key = format!(
+ "{}:{}",
+ &user_id,
+ channel.server_id.as_ref().unwrap_or(&channel.id)
+ );
+
+ let (joined_at, is_publishing, is_receiving, screensharing, camera) = get_connection()
+ .await?
+ .mget(&[
+ format!("joined_at:{unique_key}"),
+ format!("is_publishing:{unique_key}"),
+ format!("is_receiving:{unique_key}"),
+ format!("screensharing:{unique_key}"),
+ format!("camera:{unique_key}"),
+ ])
+ .await
+ .to_internal_error()?;
+
+ match (
+ joined_at,
+ is_publishing,
+ is_receiving,
+ screensharing,
+ camera,
+ ) {
+ (
+ Some(joined_at),
+ Some(is_publishing),
+ Some(is_receiving),
+ Some(screensharing),
+ Some(camera),
+ ) => Ok(Some(v0::UserVoiceState {
+ joined_at: Timestamp::UNIX_EPOCH
+ .checked_add(Duration::milliseconds(joined_at))
+ .unwrap(),
+ id: user_id.to_string(),
+ is_receiving,
+ is_publishing,
+ screensharing,
+ camera,
+ })),
+ _ => Ok(None),
+ }
+}
+
+pub async fn get_channel_voice_state(
+ channel: &UserVoiceChannel,
+) -> Result > {
+ let members = get_voice_channel_members(channel).await?;
+
+ if let Some(members) = members {
+ let mut participants = Vec::with_capacity(members.len());
+
+ for user_id in members {
+ if let Some(voice_state) = get_voice_state(channel, &user_id).await? {
+ participants.push(voice_state);
+ } else {
+ log::info!("Voice state not found but member in voice channel members, removing.");
+
+ delete_voice_state(channel, &user_id).await?;
+ }
+ }
+
+ // In case a user voice state failed to be fetched, the vec's capacity will be larger than the length, shrink it
+ participants.shrink_to_fit();
+
+ Ok(Some(v0::ChannelVoiceState {
+ id: channel.id.clone(),
+ participants,
+ }))
+ } else {
+ Ok(None)
+ }
+}
+
+pub async fn move_user(user: &str, from_channel_id: &str, to_channel_id: &str) -> Result<()> {
+ get_connection()
+ .await?
+ .smove(
+ format!("vc_members:{from_channel_id}"),
+ format!("vc_members:{to_channel_id}"),
+ user,
+ )
+ .await
+ .to_internal_error()
+}
+
+pub async fn sync_voice_permissions(
+ db: &Database,
+ voice_client: &VoiceClient,
+ channel: &Channel,
+ server: Option<&Server>,
+ role_id: Option<&str>,
+) -> Result<()> {
+ let user_voice_channel = UserVoiceChannel::from_channel(channel);
+
+ let Some(node) = get_channel_node(channel.id()).await? else {
+ return Ok(());
+ };
+
+ for user_id in get_voice_channel_members(&user_voice_channel)
+ .await?
+ .iter()
+ .flatten()
+ {
+ let user = Reference::from_unchecked(user_id).as_user(db).await?;
+
+ sync_user_voice_permissions(db, voice_client, &node, &user, channel, server, role_id)
+ .await?;
+ }
+
+ Ok(())
+}
+
+pub async fn sync_user_voice_permissions(
+ db: &Database,
+ voice_client: &VoiceClient,
+ node: &str,
+ user: &User,
+ channel: &Channel,
+ server: Option<&Server>,
+ role_id: Option<&str>,
+) -> Result<()> {
+ let channel_id = channel.id();
+ let server_id = server.as_ref().map(|s| s.id.as_str());
+
+ let member = match server_id {
+ Some(server_id) => Some(
+ Reference::from_unchecked(&user.id)
+ .as_member(db, server_id)
+ .await?,
+ ),
+ None => None,
+ };
+
+ if role_id.is_none_or(|role_id| {
+ member
+ .as_ref()
+ .is_none_or(|member| member.roles.iter().any(|r| r == role_id))
+ }) {
+ let user_voice_channel = UserVoiceChannel::from_channel(channel);
+
+ let Some(voice_state) = get_voice_state(&user_voice_channel, &user.id).await? else {
+ return Ok(());
+ };
+
+ let mut query = DatabasePermissionQuery::new(db, user)
+ .channel(channel)
+ .user(user);
+
+ if let (Some(server), Some(member)) = (server, member.as_ref()) {
+ query = query.member(member).server(server)
+ }
+
+ let permissions = calculate_channel_permissions(&mut query).await;
+ let limits = user.limits().await;
+
+ let mut update_event = PartialUserVoiceState {
+ id: Some(user.id.clone()),
+ ..Default::default()
+ };
+
+ let before = update_event.clone();
+
+ let can_video =
+ limits.video && permissions.has_channel_permission(ChannelPermission::Video);
+ let can_speak = permissions.has_channel_permission(ChannelPermission::Speak);
+ let can_listen = permissions.has_channel_permission(ChannelPermission::Listen);
+
+ update_event.camera = voice_state.camera.then_some(can_video);
+ update_event.screensharing = voice_state.screensharing.then_some(can_video);
+ update_event.is_publishing = voice_state.is_publishing.then_some(can_speak);
+
+ update_voice_state(&user_voice_channel, &user.id, &update_event).await?;
+
+ voice_client
+ .update_permissions(
+ node,
+ user,
+ channel_id,
+ ParticipantPermission {
+ can_subscribe: can_listen,
+ can_publish: can_speak,
+ can_publish_data: can_speak,
+ ..Default::default()
+ },
+ )
+ .await?;
+
+ if update_event != before {
+ EventV1::UserVoiceStateUpdate {
+ id: user.id.clone(),
+ channel_id: channel_id.to_string(),
+ data: update_event,
+ }
+ .p(channel_id.to_string())
+ .await;
+ };
+ };
+
+ Ok(())
+}
+
+pub async fn set_channel_call_started_system_message(
+ channel_id: &str,
+ message_id: &str,
+) -> Result<()> {
+ get_connection()
+ .await?
+ .set(format!("call_started_message:{channel_id}"), message_id)
+ .await
+ .to_internal_error()
+}
+
+pub async fn take_channel_call_started_system_message(channel_id: &str) -> Result > {
+ get_connection()
+ .await?
+ .get_del(format!("call_started_message:{channel_id}"))
+ .await
+ .to_internal_error()
+}
+
+pub async fn set_call_notification_recipients(
+ channel_id: &str,
+ user_id: &str,
+ recipients: &[String],
+) -> Result<()> {
+ get_connection()
+ .await?
+ .set_ex(
+ format!("call_notification_recipients:{channel_id}-{user_id}"),
+ recipients,
+ 10,
+ )
+ .await
+ .to_internal_error()
+}
+
+pub async fn get_call_notification_recipients(
+ channel_id: &str,
+ user_id: &str,
+) -> Result >> {
+ get_connection()
+ .await?
+ .get_del(format!(
+ "call_notification_recipients:{channel_id}-{user_id}"
+ ))
+ .await
+ .to_internal_error()
+}
+
+pub async fn remove_user_from_voice_channels(
+ voice_client: &VoiceClient,
+ user_id: &str,
+) -> Result<()> {
+ for channel in get_user_voice_channels(user_id).await? {
+ remove_user_from_voice_channel(voice_client, &channel, user_id).await?;
+ }
+
+ Ok(())
+}
+
+pub async fn remove_user_from_voice_channel(
+ voice_client: &VoiceClient,
+ channel: &UserVoiceChannel,
+ user_id: &str,
+) -> Result<()> {
+ if let Some(node) = get_channel_node(&channel.id).await? {
+ let _ = voice_client.remove_user(&node, user_id, &channel.id).await;
+ }
+
+ delete_voice_state(channel, user_id).await?;
+
+ Ok(())
+}
+
+pub async fn delete_voice_channel(
+ voice_client: &VoiceClient,
+ channel: &UserVoiceChannel,
+) -> Result<()> {
+ if let Some(users) = get_voice_channel_members(channel).await? {
+ let node = get_channel_node(&channel.id).await?.unwrap();
+ voice_client.delete_room(&node, &channel.id).await?;
+
+ delete_channel_voice_state(channel, &users).await?;
+ };
+
+ Ok(())
+}
+
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub struct RoomMetadata {
+ pub server: Option,
+}
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct UserVoiceChannel {
+ pub id: String,
+ pub server_id: Option,
+}
+
+impl UserVoiceChannel {
+ pub fn from_string(input: String) -> Self {
+ let mut parts = input.splitn(2, '-');
+
+ Self {
+ id: parts.next().unwrap().to_string(),
+ server_id: parts.next().map(ToString::to_string),
+ }
+ }
+
+ pub fn from_channel(channel: &Channel) -> Self {
+ Self {
+ id: channel.id().to_string(),
+ server_id: channel.server().map(ToString::to_string),
+ }
+ }
+}
+
+impl Display for UserVoiceChannel {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.write_str(&self.id)?;
+
+ if let Some(server_id) = &self.server_id {
+ f.write_char('-')?;
+ f.write_str(server_id)?
+ };
+
+ Ok(())
+ }
+}
+
+impl ToRedisArgs for UserVoiceChannel {
+ fn write_redis_args(&self, out: &mut W) {
+ out.write_arg_fmt(self);
+ }
+}
+
+impl FromRedisValue for UserVoiceChannel {
+ fn from_redis_value(v: &Value) -> Result {
+ String::from_redis_value(v).map(UserVoiceChannel::from_string)
+ }
+}
diff --git a/crates/core/database/src/voice/voice_client.rs b/crates/core/database/src/voice/voice_client.rs
new file mode 100644
index 000000000..94d1f22bb
--- /dev/null
+++ b/crates/core/database/src/voice/voice_client.rs
@@ -0,0 +1,162 @@
+use crate::{
+ models::{Channel, User},
+ voice::RoomMetadata,
+ Database,
+};
+use livekit_api::{
+ access_token::{AccessToken, VideoGrants},
+ services::room::{CreateRoomOptions, RoomClient as InnerRoomClient, UpdateParticipantOptions},
+};
+use livekit_protocol::{ParticipantInfo, ParticipantPermission, Room};
+use revolt_config::{config, LiveKitNode};
+use revolt_permissions::{ChannelPermission, PermissionValue};
+use revolt_result::{create_error, Result, ToRevoltError};
+use std::{collections::HashMap, time::Duration};
+
+use super::get_allowed_sources;
+
+#[derive(Debug)]
+pub struct RoomClient {
+ pub client: InnerRoomClient,
+ pub node: LiveKitNode,
+}
+
+#[derive(Debug)]
+pub struct VoiceClient {
+ pub rooms: HashMap,
+}
+
+impl VoiceClient {
+ pub fn new(nodes: HashMap) -> Self {
+ Self {
+ rooms: nodes
+ .into_iter()
+ .map(|(name, node)| {
+ (
+ name,
+ RoomClient {
+ client: InnerRoomClient::with_api_key(
+ &node.url,
+ &node.key,
+ &node.secret,
+ ),
+ node,
+ },
+ )
+ })
+ .collect(),
+ }
+ }
+
+ pub fn is_enabled(&self) -> bool {
+ !self.rooms.is_empty()
+ }
+
+ pub async fn from_revolt_config() -> Self {
+ let config = config().await;
+
+ Self::new(config.api.livekit.nodes.clone())
+ }
+
+ pub fn get_node(&self, name: &str) -> Result<&RoomClient> {
+ self.rooms
+ .get(name)
+ .ok_or_else(|| create_error!(UnknownNode))
+ }
+
+ pub async fn create_token(
+ &self,
+ node: &str,
+ db: &Database,
+ user: &User,
+ permissions: PermissionValue,
+ channel: &Channel,
+ ) -> Result {
+ let room = self.get_node(node)?;
+
+ let limits = user.limits().await;
+ let allowed_sources = get_allowed_sources(&limits, permissions);
+
+ AccessToken::with_api_key(&room.node.key, &room.node.secret)
+ .with_name(&format!("{}#{}", user.username, user.discriminator))
+ .with_identity(&user.id)
+ .with_metadata(
+ &serde_json::to_string(&user.clone().into(db, None).await).to_internal_error()?,
+ )
+ .with_ttl(Duration::from_secs(10))
+ .with_grants(VideoGrants {
+ room_join: true,
+ can_publish: true,
+ can_publish_data: false,
+ can_publish_sources: allowed_sources
+ .into_iter()
+ .map(ToString::to_string)
+ .collect(),
+ can_subscribe: permissions.has_channel_permission(ChannelPermission::Listen),
+ room: channel.id().to_string(),
+ ..Default::default()
+ })
+ .to_jwt()
+ .to_internal_error()
+ }
+
+ pub async fn create_room(&self, node: &str, channel: &Channel) -> Result {
+ let room = self.get_node(node)?;
+
+ let metadata = RoomMetadata {
+ server: channel.server().map(|id| id.to_string()),
+ };
+
+ room.client
+ .create_room(
+ channel.id(),
+ CreateRoomOptions {
+ empty_timeout: 5 * 60, // 5 minutes,
+ metadata: serde_json::to_string(&metadata).to_internal_error()?,
+ ..Default::default()
+ },
+ )
+ .await
+ .to_internal_error()
+ }
+
+ pub async fn update_permissions(
+ &self,
+ node: &str,
+ user: &User,
+ channel_id: &str,
+ new_permissions: ParticipantPermission,
+ ) -> Result {
+ let room = self.get_node(node)?;
+
+ room.client
+ .update_participant(
+ channel_id,
+ &user.id,
+ UpdateParticipantOptions {
+ permission: Some(new_permissions),
+ ..Default::default()
+ },
+ )
+ .await
+ .to_internal_error()
+ }
+
+ pub async fn remove_user(&self, node: &str, user_id: &str, channel_id: &str) -> Result<()> {
+ let room = self.get_node(node)?;
+
+ room.client
+ .remove_participant(channel_id, user_id)
+ .await
+ .to_internal_error()
+ }
+
+ pub async fn delete_room(&self, node: &str, channel_id: &str) -> Result<()> {
+ let room = self.get_node(node)?;
+
+ room.client
+ .delete_room(channel_id)
+ .await
+ .to_internal_error()
+ }
+}
diff --git a/crates/core/database/templates/deletion.html b/crates/core/database/templates/deletion.html
index d25138838..e6cb83eae 100644
--- a/crates/core/database/templates/deletion.html
+++ b/crates/core/database/templates/deletion.html
@@ -1,7 +1,7 @@
-
+
@@ -42,7 +42,7 @@
-
+
@@ -107,7 +107,7 @@
This email is intended for {{email}}
- Sent from Revolt
+ Sent from Stoat
Made in Europe
diff --git a/crates/core/database/templates/deletion.original.html b/crates/core/database/templates/deletion.original.html
index 0da330eac..e3ad4e508 100644
--- a/crates/core/database/templates/deletion.original.html
+++ b/crates/core/database/templates/deletion.original.html
@@ -1,38 +1,37 @@
-
-
-
-
-
-
-
-
-
Account Deletion
-
- You requested to have your account deleted, if you did not perform
- this action please take measures to secure your account immediately.
-
-
Confirm
-
-
- This email is intended for {{email}}
- Sent from Revolt
- Made in Europe
-
-
- Revolt Platforms Ltd. is a company incorporated and registered under the
- laws of England and Wales.
- Registered Company Number: 16260658
- Registered Office:
- Suite 5703 Unit 3A, 34-35 Hatton Garden,
- Holborn, United Kingdom, EC1N 8DX
-
+
+
+
+
+
+
+
+
+
+
+
Account Deletion
+
+ You requested to have your account deleted, if you did not perform
+ this action please take measures to secure your account immediately.
+
+
Confirm
-
-
+
+ This email is intended for {{email}}
+ Sent from Stoat
+ Made in Europe
+
+
+ Revolt Platforms Ltd. is a company incorporated and registered under the
+ laws of England and Wales.
+ Registered Company Number: 16260658
+ Registered Office:
+ Suite 5703 Unit 3A, 34-35 Hatton Garden,
+ Holborn, United Kingdom, EC1N 8DX
+
+
+
+
+
\ No newline at end of file
diff --git a/crates/core/database/templates/deletion.txt b/crates/core/database/templates/deletion.txt
index b6c2871b0..648059a17 100644
--- a/crates/core/database/templates/deletion.txt
+++ b/crates/core/database/templates/deletion.txt
@@ -3,7 +3,7 @@ You requested to have your account deleted, if you did not perform this action p
Please navigate to: {{url}}
This email is intended for {{email}}
-Sent by Revolt
+Sent by Stoat
Made in Europe
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
diff --git a/crates/core/database/templates/deletion.whitelabel.txt b/crates/core/database/templates/deletion.whitelabel.txt
index 491280aac..b040a51ed 100644
--- a/crates/core/database/templates/deletion.whitelabel.txt
+++ b/crates/core/database/templates/deletion.whitelabel.txt
@@ -4,6 +4,6 @@ Please navigate to: {{url}}
This email is intended for {{email}}
-This email has no association with Revolt or Revolt Platforms Ltd.
+This email has no association with Stoat or Revolt Platforms Ltd.
Learn more about third party instances here:
-https://developers.revolt.chat/faq.html
+https://developers.stoat.chat/faq/
diff --git a/crates/core/database/templates/reset-existing.html b/crates/core/database/templates/reset-existing.html
index 00ef782b6..1ed6424d3 100644
--- a/crates/core/database/templates/reset-existing.html
+++ b/crates/core/database/templates/reset-existing.html
@@ -1,7 +1,7 @@
-
+
@@ -42,7 +42,7 @@
-
+
@@ -120,7 +120,7 @@
This email is intended for {{email}}
- Sent from Revolt
+ Sent from Stoat
Made in Europe
diff --git a/crates/core/database/templates/reset-existing.original.html b/crates/core/database/templates/reset-existing.original.html
index 1273a69c4..a68c3d35d 100644
--- a/crates/core/database/templates/reset-existing.original.html
+++ b/crates/core/database/templates/reset-existing.original.html
@@ -9,7 +9,8 @@
Password Reset
@@ -25,7 +26,7 @@ Password Reset
This email is intended for {{email}}
- Sent from Revolt
+ Sent from Stoat
Made in Europe
diff --git a/crates/core/database/templates/reset-existing.txt b/crates/core/database/templates/reset-existing.txt
index 8c8fc9edc..6637f9086 100644
--- a/crates/core/database/templates/reset-existing.txt
+++ b/crates/core/database/templates/reset-existing.txt
@@ -8,7 +8,7 @@ password on it, click below to continue.
Please navigate to: {{url}}
This email is intended for {{email}}
-Sent by Revolt
+Sent by Stoat
Made in Europe
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
diff --git a/crates/core/database/templates/reset.html b/crates/core/database/templates/reset.html
index 9b938f7a4..b2f05cf2e 100644
--- a/crates/core/database/templates/reset.html
+++ b/crates/core/database/templates/reset.html
@@ -1,7 +1,7 @@
-
+
@@ -42,7 +42,7 @@
-
+
@@ -104,7 +104,7 @@
This email is intended for {{email}}
- Sent from Revolt
+ Sent from Stoat
Made in Europe
diff --git a/crates/core/database/templates/reset.original.html b/crates/core/database/templates/reset.original.html
index f15d03289..76b7b5c6e 100644
--- a/crates/core/database/templates/reset.original.html
+++ b/crates/core/database/templates/reset.original.html
@@ -1,34 +1,34 @@
-
-
-
-
-
-
-
-
-
Password Reset
-
You requested a password reset, click below to continue.
-
Reset
-
-
- This email is intended for {{email}}
- Sent from Revolt
- Made in Europe
-
-
- Revolt Platforms Ltd. is a company incorporated and registered under the
- laws of England and Wales.
- Registered Company Number: 16260658
- Registered Office:
- Suite 5703 Unit 3A, 34-35 Hatton Garden,
- Holborn, United Kingdom, EC1N 8DX
-
+
+
+
+
+
+
+
+
+
+
+
Password Reset
+
You requested a password reset, click below to continue.
+
Reset
-
-
+
+ This email is intended for {{email}}
+ Sent from Stoat
+ Made in Europe
+
+
+ Revolt Platforms Ltd. is a company incorporated and registered under the
+ laws of England and Wales.
+ Registered Company Number: 16260658
+ Registered Office:
+ Suite 5703 Unit 3A, 34-35 Hatton Garden,
+ Holborn, United Kingdom, EC1N 8DX
+
+
+
+
+
\ No newline at end of file
diff --git a/crates/core/database/templates/reset.txt b/crates/core/database/templates/reset.txt
index 020032c71..266a3e2ed 100644
--- a/crates/core/database/templates/reset.txt
+++ b/crates/core/database/templates/reset.txt
@@ -3,7 +3,7 @@ You requested a password reset, if you did not perform this action you can safel
Please navigate to: {{url}}
This email is intended for {{email}}
-Sent by Revolt
+Sent by Stoat
Made in Europe
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
diff --git a/crates/core/database/templates/reset.whitelabel.txt b/crates/core/database/templates/reset.whitelabel.txt
index 9ac5028fc..10e8ca2d2 100644
--- a/crates/core/database/templates/reset.whitelabel.txt
+++ b/crates/core/database/templates/reset.whitelabel.txt
@@ -4,6 +4,6 @@ Please navigate to: {{url}}
This email is intended for {{email}}
-This email has no association with Revolt or Revolt Platforms Ltd.
+This email has no association with Stoat or Revolt Platforms Ltd.
Learn more about third party instances here:
-https://developers.revolt.chat/faq.html
+https://developers.stoat.chat/faq/
diff --git a/crates/core/database/templates/suspension.html b/crates/core/database/templates/suspension.html
index 5002c2580..27b7331e7 100644
--- a/crates/core/database/templates/suspension.html
+++ b/crates/core/database/templates/suspension.html
@@ -1,7 +1,7 @@
-
+
@@ -42,7 +42,7 @@
-
+
@@ -107,7 +107,7 @@
Further violations may result in a permanent ban depending on
severity, please abide by the
- Acceptable Usage Policy .
+ Community Guidelines .
@@ -134,7 +134,7 @@
This email is intended for {{email}}
- Sent from Revolt
+ Sent from Stoat
Made in Europe
diff --git a/crates/core/database/templates/suspension.original.html b/crates/core/database/templates/suspension.original.html
index fbbbb4168..de49512d2 100644
--- a/crates/core/database/templates/suspension.original.html
+++ b/crates/core/database/templates/suspension.original.html
@@ -1,46 +1,45 @@
-
-
-
-
-
-
-
-
-
Account Suspended
-
Your account has been suspended, for one or more reasons:
-
-
- You will be able to use your account again in {{duration}} days.
-
-
- Further violations may result in a permanent ban depending on
- severity, please abide by the
- Acceptable Usage Policy .
-
-
Ban evasion is prohibited and will be dealt with accordingly.
-
-
- This email is intended for {{email}}
- Sent from Revolt
- Made in Europe
-
-
- Revolt Platforms Ltd. is a company incorporated and registered under the
- laws of England and Wales.
- Registered Company Number: 16260658
- Registered Office:
- Suite 5703 Unit 3A, 34-35 Hatton Garden,
- Holborn, United Kingdom, EC1N 8DX
-
+
+
+
+
+
+
+
+
+
+
+
Account Suspended
+
Your account has been suspended, for one or more reasons:
+
+
+ You will be able to use your account again in {{duration}} days.
+
+
+ Further violations may result in a permanent ban depending on
+ severity, please abide by the
+ Acceptable Usage Policy .
+
+
Ban evasion is prohibited and will be dealt with accordingly.
-
-
+
+ This email is intended for {{email}}
+ Sent from Stoat
+ Made in Europe
+
+
+ Revolt Platforms Ltd. is a company incorporated and registered under the
+ laws of England and Wales.
+ Registered Company Number: 16260658
+ Registered Office:
+ Suite 5703 Unit 3A, 34-35 Hatton Garden,
+ Holborn, United Kingdom, EC1N 8DX
+
+
+
+
+
\ No newline at end of file
diff --git a/crates/core/database/templates/suspension.txt b/crates/core/database/templates/suspension.txt
index 30d48d846..293f8a0c2 100644
--- a/crates/core/database/templates/suspension.txt
+++ b/crates/core/database/templates/suspension.txt
@@ -3,12 +3,12 @@ Your account has been suspended, for one or more reasons:
You will be able to use your account again in {{duration}} days.
-Further violations may result in a permanent ban depending on severity, please abide by the Acceptable Usage Policy (https://revolt.chat/aup).
+Further violations may result in a permanent ban depending on severity, please abide by the Community Guidelines (https://stoat.chat/legal/community-guidelines).
Ban evasion is prohibited and will be dealt with accordingly.
This email is intended for {{email}}
-Sent by Revolt
+Sent by Stoat
Made in Europe
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
diff --git a/crates/core/database/templates/suspension.whitelabel.txt b/crates/core/database/templates/suspension.whitelabel.txt
index 45d9aa560..bd82145d4 100644
--- a/crates/core/database/templates/suspension.whitelabel.txt
+++ b/crates/core/database/templates/suspension.whitelabel.txt
@@ -3,6 +3,6 @@ Your account has been suspended, for one or more reasons:
This email is intended for {{email}}
-This email has no association with Revolt or Revolt Platforms Ltd.
+This email has no association with Stoat or Revolt Platforms Ltd.
Learn more about third party instances here:
-https://developers.revolt.chat/faq.html
+https://developers.stoat.chat/faq/
diff --git a/crates/core/database/templates/verify.html b/crates/core/database/templates/verify.html
index 8ab9713d0..10a4ab6ae 100644
--- a/crates/core/database/templates/verify.html
+++ b/crates/core/database/templates/verify.html
@@ -1,7 +1,7 @@
-
+
@@ -42,7 +42,7 @@
-
+
@@ -104,7 +104,7 @@
This email is intended for {{email}}
- Sent from Revolt
+ Sent from Stoat
Made in Europe
diff --git a/crates/core/database/templates/verify.original.html b/crates/core/database/templates/verify.original.html
index 75ae25cea..107d73077 100644
--- a/crates/core/database/templates/verify.original.html
+++ b/crates/core/database/templates/verify.original.html
@@ -1,35 +1,34 @@
-
-
-
-
-
-
-
-
-
Almost there!
-
To complete your sign up, we just need to verify your email.
-
Confirm
-
-
- This email is intended for {{email}}
- Sent from Revolt
- Made in Europe
-
-
- Revolt Platforms Ltd. is a company incorporated and registered under the
- laws of England and Wales.
- Registered Company Number: 16260658
- Registered Office:
- Suite 5703 Unit 3A, 34-35 Hatton Garden,
- Holborn, United Kingdom, EC1N 8DX
-
+
+
+
+
+
+
+
+
+
+
+
Almost there!
+
To complete your sign up, we just need to verify your email.
+
Confirm
-
-
+
+ This email is intended for {{email}}
+ Sent from Stoat
+ Made in Europe
+
+
+ Revolt Platforms Ltd. is a company incorporated and registered under the
+ laws of England and Wales.
+ Registered Company Number: 16260658
+ Registered Office:
+ Suite 5703 Unit 3A, 34-35 Hatton Garden,
+ Holborn, United Kingdom, EC1N 8DX
+
+
+
+
+
\ No newline at end of file
diff --git a/crates/core/database/templates/verify.txt b/crates/core/database/templates/verify.txt
index 7fa871eef..cafd09d41 100644
--- a/crates/core/database/templates/verify.txt
+++ b/crates/core/database/templates/verify.txt
@@ -4,7 +4,7 @@ To complete your sign up, we just need to verify your email.
Please navigate to: {{url}}
This email is intended for {{email}}
-Sent by Revolt
+Sent by Stoat
Made in Europe
Revolt Platforms Ltd. is a company incorporated and registered under the laws of England and Wales.
diff --git a/crates/core/database/templates/verify.whitelabel.txt b/crates/core/database/templates/verify.whitelabel.txt
index fb54088df..16f1c4b13 100644
--- a/crates/core/database/templates/verify.whitelabel.txt
+++ b/crates/core/database/templates/verify.whitelabel.txt
@@ -5,6 +5,6 @@ Please navigate to: {{url}}
This email is intended for {{email}}
-This email has no association with Revolt or Revolt Platforms Ltd.
+This email has no association with Stoat or Revolt Platforms Ltd.
Learn more about third party instances here:
-https://developers.revolt.chat/faq.html
+https://developers.stoat.chat/faq/
diff --git a/crates/core/files/Cargo.toml b/crates/core/files/Cargo.toml
index 7cc219f1c..c60bf678e 100644
--- a/crates/core/files/Cargo.toml
+++ b/crates/core/files/Cargo.toml
@@ -1,38 +1,48 @@
[package]
name = "revolt-files"
-version = "0.8.8"
+version = "0.13.7"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles
"]
description = "Revolt Backend: S3 and encryption subroutines"
+repository = "https://github.com/stoatchat/stoatchat"
[dependencies]
-tracing = "0.1"
+anyhow = { workspace = true }
+thiserror = { workspace = true }
-ffprobe = "0.4.0"
-imagesize = "0.13.0"
-tempfile = "3.12.0"
+tracing = { workspace = true }
-base64 = "0.22.1"
-aes-gcm = "0.10.3"
-typenum = "1.17.0"
+tokio = { workspace = true }
+async-trait = { workspace = true }
-aws-config = "1.5.5"
-aws-sdk-s3 = { version = "1.46.0", features = ["behavior-version-latest"] }
+ffprobe = { workspace = true }
+imagesize = { workspace = true }
+tempfile = { workspace = true }
-revolt-config = { version = "0.8.8", path = "../config", features = [
+base64 = { workspace = true }
+aes-gcm = { workspace = true }
+typenum = { workspace = true }
+
+aws-config = { workspace = true }
+aws-sdk-s3 = { workspace = true, features = ["behavior-version-latest"] }
+
+revolt-config = { workspace = true, features = [
"report-macros",
] }
-revolt-result = { version = "0.8.8", path = "../result" }
+revolt-result = { workspace = true }
# image processing
-jxl-oxide = "0.8.1"
-image = { version = "0.25.2" }
+jxl-oxide = { workspace = true, features = ["image"] }
+image = { workspace = true }
# svg rendering
-usvg = "0.44.0"
-resvg = "0.44.0"
-tiny-skia = "0.11.4"
+usvg = { workspace = true }
+resvg = { workspace = true }
+tiny-skia = { workspace = true }
# encoding
-webp = "0.3.0"
+webp = { workspace = true }
+
+[dev-dependencies]
+uuid = { workspace = true, features = ["v4"] }
diff --git a/crates/core/files/src/implementation/encryption_impl.rs b/crates/core/files/src/implementation/encryption_impl.rs
new file mode 100644
index 000000000..25da9cfd4
--- /dev/null
+++ b/crates/core/files/src/implementation/encryption_impl.rs
@@ -0,0 +1,75 @@
+use aes_gcm::{
+ aead::{Aead, AeadCore, AeadMutInPlace, OsRng},
+ Aes256Gcm, Key, KeyInit, Nonce,
+};
+use base64::{prelude::BASE64_STANDARD, Engine};
+
+use crate::EncryptionRepository;
+
+pub struct EncryptionKey {
+ key: String,
+}
+
+impl EncryptionKey {
+ pub async fn from_config() -> EncryptionKey {
+ EncryptionKey::new(revolt_config::config().await.files.encryption_key)
+ }
+
+ pub fn new(key: String) -> EncryptionKey {
+ EncryptionKey { key }
+ }
+
+ fn create_cipher(&self) -> Aes256Gcm {
+ let key = &BASE64_STANDARD
+ .decode(self.key.clone())
+ .expect("valid base64 string")[..];
+ let key: &Key = key.into();
+ Aes256Gcm::new(key)
+ }
+}
+
+impl EncryptionRepository for EncryptionKey {
+ fn decrypt_buffer(&self, mut buf: Vec, iv: &str) -> anyhow::Result> {
+ let iv = &BASE64_STANDARD.decode(iv).unwrap()[..];
+ let iv: &Nonce = iv.into();
+
+ self.create_cipher()
+ .decrypt_in_place(iv, b"", &mut buf)
+ .map_err(|error| {
+ tracing::error!("{}", error);
+ anyhow::anyhow!("EncryptionRepository: decryption failed")
+ })?;
+
+ Ok(buf)
+ }
+
+ fn encrypt_buffer(&self, buf: &[u8]) -> anyhow::Result<(Vec, String)> {
+ let iv = Aes256Gcm::generate_nonce(&mut OsRng);
+
+ let buf = self.create_cipher().encrypt(&iv, buf).map_err(|error| {
+ tracing::error!("{}", error);
+ anyhow::anyhow!("EncryptionRepository: encryption failed")
+ })?;
+
+ Ok((buf, BASE64_STANDARD.encode(iv)))
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_encrypt_and_decrypt() {
+ let encryption =
+ EncryptionKey::new("XkbJ8gBzrouQ+15Ri23xCC81+aZE26Z6+gXzglFxOD4=".to_string());
+
+ let buf: Vec = vec![67];
+ let (ciphertext, iv) = encryption.encrypt_buffer(&buf[..]).unwrap();
+ assert_eq!(ciphertext.len(), 17);
+
+ let plaintext = encryption.decrypt_buffer(ciphertext, &iv).unwrap();
+ assert_eq!(plaintext.len(), 1);
+ assert_eq!(plaintext[0], 67);
+ }
+}
diff --git a/crates/core/files/src/implementation/media_impl.rs b/crates/core/files/src/implementation/media_impl.rs
new file mode 100644
index 000000000..7a7fad848
--- /dev/null
+++ b/crates/core/files/src/implementation/media_impl.rs
@@ -0,0 +1,395 @@
+use anyhow::Result;
+use image::{AnimationDecoder, DynamicImage, ImageBuffer, ImageReader};
+use jxl_oxide::integration::JxlDecoder;
+use revolt_config::report_internal_error;
+use std::io::{BufRead, Read, Seek};
+use tempfile::NamedTempFile;
+use tiny_skia::Pixmap;
+
+use crate::{MediaError, MediaRepository};
+
+pub struct MediaImpl {
+ config: revolt_config::Files,
+}
+
+impl MediaImpl {
+ pub async fn from_config() -> MediaImpl {
+ MediaImpl {
+ config: revolt_config::config().await.files,
+ }
+ }
+
+ pub fn new(config: revolt_config::Files) -> MediaImpl {
+ MediaImpl { config }
+ }
+}
+
+impl MediaRepository for MediaImpl {
+ fn image_size(&self, f: &NamedTempFile) -> Option<(usize, usize)> {
+ if let Ok(size) = imagesize::size(f.path())
+ .inspect_err(|err| tracing::error!("Failed to generate image size! {err:?}"))
+ {
+ Some((size.width, size.height))
+ } else {
+ None
+ }
+ }
+
+ fn is_animated(&self, f: &NamedTempFile, mime: &str) -> Option {
+ match mime {
+ // Current behaviour is to assume GIFs are animated, this checks for at least 2 frames
+ "image/gif" => {
+ let file = std::fs::File::open(f.path()).ok()?;
+ let reader = std::io::BufReader::new(file);
+ let decoder = image::codecs::gif::GifDecoder::new(reader).ok()?;
+ Some(decoder.into_frames().take(2).count() > 1)
+ }
+ "image/png" => {
+ let file = std::fs::File::open(f.path()).ok()?;
+ let reader = std::io::BufReader::new(file);
+ let decoder = image::codecs::png::PngDecoder::new(reader).ok()?;
+ decoder.is_apng().ok()
+ }
+ "image/webp" => {
+ let file = std::fs::File::open(f.path()).ok()?;
+ let reader = std::io::BufReader::new(file);
+ let decoder = image::codecs::webp::WebPDecoder::new(reader).ok()?;
+ Some(decoder.has_animation())
+ }
+ _ => Some(false),
+ }
+ }
+
+ fn image_size_vec(&self, v: &[u8], mime: &str) -> Option<(usize, usize)> {
+ match mime {
+ "image/svg+xml" => {
+ let tree =
+ report_internal_error!(usvg::Tree::from_data(v, &Default::default())).ok()?;
+
+ let size = tree.size();
+ Some((size.width() as usize, size.height() as usize))
+ }
+ _ => {
+ if let Ok(size) = imagesize::blob_size(v)
+ .inspect_err(|err| tracing::error!("Failed to generate image size! {err:?}"))
+ {
+ Some((size.width, size.height))
+ } else {
+ None
+ }
+ }
+ }
+ }
+
+ fn decode_image(
+ &self,
+ reader: &mut R,
+ mime: &str,
+ ) -> Result {
+ match mime {
+ "image/jxl" => {
+ let decoder =
+ JxlDecoder::new(reader).map_err(|e| MediaError::from(anyhow::anyhow!(e)))?;
+
+ DynamicImage::from_decoder(decoder)
+ .map_err(|e| MediaError::from(anyhow::anyhow!(e)))
+ }
+ "image/svg+xml" => {
+ let mut buf = Vec::new();
+ reader
+ .read_to_end(&mut buf)
+ .map_err(|e| MediaError::from(anyhow::anyhow!(e)))?;
+
+ let tree: usvg::Tree = usvg::Tree::from_data(&buf, &Default::default())
+ .map_err(|e| MediaError::from(anyhow::anyhow!(e)))?;
+
+ let size = tree.size();
+ let mut pixmap = Pixmap::new(size.width() as u32, size.height() as u32)
+ .ok_or_else(|| MediaError::ImageProcessingFailed {
+ cause: "failed to create Pixmap, likely zero sized".to_string(),
+ })?;
+
+ let mut pixmap_mut = pixmap.as_mut();
+ resvg::render(&tree, Default::default(), &mut pixmap_mut);
+
+ Ok(DynamicImage::ImageRgba8(
+ ImageBuffer::from_vec(
+ size.width() as u32,
+ size.height() as u32,
+ pixmap.data().to_vec(),
+ )
+ .ok_or_else(|| MediaError::ImageProcessingFailed {
+ cause: "buffer is not big enough".to_string(),
+ })?,
+ ))
+ }
+ _ => {
+ let image: ImageReader<&mut R> = image::ImageReader::new(reader)
+ .with_guessed_format()
+ .map_err(|e| MediaError::from(anyhow::anyhow!(e)))?;
+
+ let image: Result = image
+ .decode()
+ .map_err(|e| MediaError::from(anyhow::anyhow!(e)));
+
+ image
+ }
+ }
+ }
+
+ fn is_valid_image(&self, reader: &mut R, mime: &str) -> bool {
+ match mime {
+ "image/jxl" => jxl_oxide::JxlImage::builder()
+ .read(reader)
+ .inspect_err(|err| tracing::error!("Failed to read JXL! {err:?}"))
+ .is_ok(),
+ _ => !matches!(
+ image::ImageReader::new(reader)
+ .with_guessed_format()
+ .inspect_err(|err| tracing::error!("Failed to read image! {err:?}"))
+ .map(|f| f.decode()),
+ Err(_) | Ok(Err(_))
+ ),
+ }
+ }
+
+ fn create_thumbnail(&self, image: DynamicImage, tag: &str) -> Vec {
+ let [w, h] = self.config.preview.get(tag).unwrap();
+
+ let image = image.thumbnail(image.width().min(*w as u32), image.height().min(*h as u32));
+ let image = match image {
+ DynamicImage::ImageRgb8(_) => image,
+ DynamicImage::ImageRgba8(_) => image,
+ _ => {
+ if image.has_alpha() {
+ image.to_rgba8().into()
+ } else {
+ image.to_rgb8().into()
+ }
+ }
+ };
+
+ let encoder = webp::Encoder::from_image(&image).expect("Could not create encoder.");
+ if self.config.webp_quality != 100.0 {
+ encoder.encode(self.config.webp_quality).to_vec()
+ } else {
+ encoder.encode_lossless().to_vec()
+ }
+ }
+
+ fn video_size(&self, f: &NamedTempFile) -> Option<(i64, i64)> {
+ if let Ok(data) = ffprobe::ffprobe(f.path())
+ .inspect_err(|err| tracing::error!("Failed to ffprobe file! {err:?}"))
+ {
+ for stream in data.streams {
+ if let (Some(w), Some(h)) = (stream.width, stream.height) {
+ return Some((w, h));
+ }
+ }
+
+ None
+ } else {
+ None
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::{MediaImpl, MediaRepository};
+ use std::io::{Cursor, Write};
+ use tempfile::NamedTempFile;
+
+ #[tokio::test]
+ async fn asset_test_jpeg() {
+ let media = MediaImpl::from_config().await;
+ let buf = include_bytes!("../../tests/assets/test.jpeg");
+ assert_eq!(media.image_size_vec(buf, "image/jpeg"), Some((655, 582)));
+
+ let mut reader = Cursor::new(buf);
+ let image = media.decode_image(&mut reader, "image/jpeg").unwrap();
+ media.create_thumbnail(image, "attachments");
+ }
+
+ #[tokio::test]
+ async fn asset_test_jpeg_is_not_animated() {
+ let media = MediaImpl::from_config().await;
+ let mut f = NamedTempFile::new().unwrap();
+ f.write_all(include_bytes!("../../tests/assets/test.jpeg"))
+ .unwrap();
+ assert_eq!(media.is_animated(&f, "image/jpeg"), Some(false));
+ }
+
+ #[tokio::test]
+ async fn asset_test_jpeg_extra_bytes() {
+ let media = MediaImpl::from_config().await;
+ let buf = [
+ &include_bytes!("../../tests/assets/test.jpeg")[..],
+ &[0u8; 16],
+ ]
+ .concat();
+ assert_eq!(media.image_size_vec(&buf, "image/jpeg"), Some((655, 582)));
+
+ let mut reader = Cursor::new(buf);
+ let image = media.decode_image(&mut reader, "image/jpeg").unwrap();
+ media.create_thumbnail(image, "emojis");
+ }
+
+ #[tokio::test]
+ async fn asset_test_png() {
+ let media = MediaImpl::from_config().await;
+ let buf = include_bytes!("../../tests/assets/test.png");
+ assert_eq!(media.image_size_vec(buf, "image/png"), Some((900, 900)));
+
+ let mut reader = Cursor::new(buf);
+ let image = media.decode_image(&mut reader, "image/png").unwrap();
+ media.create_thumbnail(image, "emojis");
+ }
+
+ #[tokio::test]
+ async fn asset_test_png_is_not_animated() {
+ let media = MediaImpl::from_config().await;
+ let mut f = NamedTempFile::new().unwrap();
+ f.write_all(include_bytes!("../../tests/assets/test.png"))
+ .unwrap();
+ assert_eq!(media.is_animated(&f, "image/png"), Some(false));
+ }
+
+ #[tokio::test]
+ async fn asset_test_png_extra_bytes() {
+ let media = MediaImpl::from_config().await;
+ let buf = [
+ &include_bytes!("../../tests/assets/test.png")[..],
+ &[0u8; 16],
+ ]
+ .concat();
+ assert_eq!(media.image_size_vec(&buf, "image/png"), Some((900, 900)));
+
+ let mut reader = Cursor::new(buf);
+ let image = media.decode_image(&mut reader, "image/png").unwrap();
+ media.create_thumbnail(image, "emojis");
+ }
+
+ #[tokio::test]
+ async fn asset_test_floating_point_png() {
+ let media = MediaImpl::from_config().await;
+ let buf = include_bytes!("../../tests/assets/test-float.png");
+ assert_eq!(media.image_size_vec(buf, "image/png"), Some((300, 300)));
+
+ let mut reader = Cursor::new(buf);
+ let image = media.decode_image(&mut reader, "image/png").unwrap();
+ media.create_thumbnail(image, "avatars");
+ }
+
+ #[tokio::test]
+ async fn asset_test_corrupted_png() {
+ let media = MediaImpl::from_config().await;
+ let buf = include_bytes!("../../tests/assets/corrupted.png");
+ assert_eq!(media.image_size_vec(buf, "image/png"), Some((900, 900)));
+
+ let mut reader = Cursor::new(buf);
+ media.decode_image(&mut reader, "image/png").unwrap_err();
+ }
+
+ #[tokio::test]
+ async fn asset_test_animated_png() {
+ let media = MediaImpl::from_config().await;
+ let buf = include_bytes!("../../tests/assets/anim-icos.apng");
+ assert_eq!(media.image_size_vec(buf, "image/png"), Some((128, 128)));
+
+ let mut reader = Cursor::new(buf);
+ let image = media.decode_image(&mut reader, "image/png").unwrap();
+ media.create_thumbnail(image, "attachments");
+ }
+
+ #[tokio::test]
+ async fn asset_test_animated_png_is_animated() {
+ let media = MediaImpl::from_config().await;
+ let mut f = NamedTempFile::new().unwrap();
+ f.write_all(include_bytes!("../../tests/assets/anim-icos.apng"))
+ .unwrap();
+ assert_eq!(media.is_animated(&f, "image/png"), Some(true));
+ }
+
+ #[tokio::test]
+ async fn asset_test_jxl() {
+ let media = MediaImpl::from_config().await;
+ let buf = include_bytes!("../../tests/assets/dice.jxl");
+ assert_eq!(media.image_size_vec(buf, "image/jxl"), Some((800, 600)));
+
+ let mut reader = Cursor::new(buf);
+ let image = media.decode_image(&mut reader, "image/jxl").unwrap();
+ media.create_thumbnail(image, "attachments");
+ }
+
+ #[tokio::test]
+ async fn asset_test_animated_jxl() {
+ let media = MediaImpl::from_config().await;
+ let buf = include_bytes!("../../tests/assets/anim-icos.jxl");
+ assert_eq!(media.image_size_vec(buf, "image/jxl"), Some((128, 128)));
+
+ let mut reader = Cursor::new(buf);
+ let image = media.decode_image(&mut reader, "image/jxl").unwrap();
+ media.create_thumbnail(image, "attachments");
+ }
+
+ #[tokio::test]
+ async fn asset_test_webp() {
+ let media = MediaImpl::from_config().await;
+ let buf = include_bytes!("../../tests/assets/dice.webp");
+ assert_eq!(media.image_size_vec(buf, "image/webp"), Some((800, 600)));
+
+ let mut reader = Cursor::new(buf);
+ let image = media.decode_image(&mut reader, "image/webp").unwrap();
+ media.create_thumbnail(image, "attachments");
+ }
+
+ #[tokio::test]
+ async fn asset_test_webp_is_not_animated() {
+ let media = MediaImpl::from_config().await;
+ let mut f = NamedTempFile::new().unwrap();
+ f.write_all(include_bytes!("../../tests/assets/dice.webp"))
+ .unwrap();
+ assert_eq!(media.is_animated(&f, "image/webp"), Some(false));
+ }
+
+ #[tokio::test]
+ async fn asset_test_animated_webp() {
+ let media = MediaImpl::from_config().await;
+ let buf = include_bytes!("../../tests/assets/anim-icos.webp");
+ assert_eq!(media.image_size_vec(buf, "image/webp"), Some((128, 128)));
+
+ let mut reader = Cursor::new(buf);
+ let image = media.decode_image(&mut reader, "image/webp").unwrap();
+ media.create_thumbnail(image, "attachments");
+ }
+
+ #[tokio::test]
+ async fn asset_test_animated_webp_is_animated() {
+ let media = MediaImpl::from_config().await;
+ let mut f = NamedTempFile::new().unwrap();
+ f.write_all(include_bytes!("../../tests/assets/anim-icos.webp"))
+ .unwrap();
+ assert_eq!(media.is_animated(&f, "image/webp"), Some(true));
+ }
+
+ #[tokio::test]
+ async fn asset_test_animated_gif() {
+ let media = MediaImpl::from_config().await;
+ let buf = include_bytes!("../../tests/assets/anim-icos.gif");
+ assert_eq!(media.image_size_vec(buf, "image/gif"), Some((128, 128)));
+
+ let mut reader = Cursor::new(buf);
+ let image = media.decode_image(&mut reader, "image/gif").unwrap();
+ media.create_thumbnail(image, "attachments");
+ }
+
+ #[tokio::test]
+ async fn asset_test_animated_gif_is_animated() {
+ let media = MediaImpl::from_config().await;
+ let mut f = NamedTempFile::new().unwrap();
+ f.write_all(include_bytes!("../../tests/assets/anim-icos.gif"))
+ .unwrap();
+ assert_eq!(media.is_animated(&f, "image/gif"), Some(true));
+ }
+}
diff --git a/crates/core/files/src/implementation/mod.rs b/crates/core/files/src/implementation/mod.rs
new file mode 100644
index 000000000..fa96ac22d
--- /dev/null
+++ b/crates/core/files/src/implementation/mod.rs
@@ -0,0 +1,7 @@
+mod encryption_impl;
+mod media_impl;
+mod s3_impl;
+
+pub use encryption_impl::EncryptionKey;
+pub use media_impl::MediaImpl;
+pub use s3_impl::S3Storage;
diff --git a/crates/core/files/src/implementation/s3_impl.rs b/crates/core/files/src/implementation/s3_impl.rs
new file mode 100644
index 000000000..c27d62012
--- /dev/null
+++ b/crates/core/files/src/implementation/s3_impl.rs
@@ -0,0 +1,118 @@
+use std::io::Write;
+
+use anyhow::Context;
+use aws_sdk_s3::{
+ config::{Credentials, Region},
+ Client, Config,
+};
+use revolt_config::FilesS3;
+
+use crate::{EncryptionRepository, FileStorageRepository};
+
+pub struct S3Storage {
+ client: Client,
+ encryption: ER,
+}
+
+impl S3Storage {
+ pub async fn from_config(encryption: ER) -> S3Storage {
+ S3Storage::new(encryption, revolt_config::config().await.files.s3)
+ }
+
+ pub fn new(encryption: ER, s3_config: FilesS3) -> S3Storage {
+ let provider_name = "my-creds";
+ let creds = Credentials::new(
+ s3_config.access_key_id,
+ s3_config.secret_access_key,
+ None,
+ None,
+ provider_name,
+ );
+
+ let config = Config::builder()
+ .region(Region::new(s3_config.region))
+ .endpoint_url(s3_config.endpoint)
+ .force_path_style(s3_config.path_style_buckets)
+ .credentials_provider(creds)
+ .build();
+
+ S3Storage {
+ client: Client::from_conf(config),
+ encryption,
+ }
+ }
+}
+
+#[async_trait::async_trait]
+impl FileStorageRepository for S3Storage {
+ async fn create_bucket(&self, bucket_id: &str) -> anyhow::Result<()> {
+ self.client
+ .create_bucket()
+ .bucket(bucket_id)
+ .send()
+ .await
+ .with_context(|| format!("failed to create bucket {bucket_id}"))?;
+
+ Ok(())
+ }
+
+ async fn fetch_and_decrypt_file(
+ &self,
+ bucket_id: &str,
+ path: &str,
+ iv: &str,
+ ) -> anyhow::Result> {
+ let mut object = self
+ .client
+ .get_object()
+ .bucket(bucket_id)
+ .key(path)
+ .send()
+ .await
+ .with_context(|| format!("failed to get object at {path} in {bucket_id}"))?;
+
+ let mut buf = vec![];
+ while let Some(bytes) = object.body.next().await {
+ let data = bytes?;
+ buf.write_all(&data)?;
+ }
+
+ if iv.is_empty() {
+ Ok(buf)
+ } else {
+ self.encryption.decrypt_buffer(buf, iv)
+ }
+ }
+
+ async fn encrypt_and_upload_file(
+ &self,
+ bucket_id: &str,
+ path: &str,
+ buf: &[u8],
+ ) -> anyhow::Result {
+ let (buf, iv) = self.encryption.encrypt_buffer(buf)?;
+
+ self.client
+ .put_object()
+ .bucket(bucket_id)
+ .key(path)
+ .body(buf.into())
+ .send()
+ .await
+ .with_context(|| format!("failed to put object at {path} in {bucket_id}"))?;
+
+ Ok(iv)
+ }
+
+ async fn delete_file(&self, bucket_id: &str, path: &str) -> anyhow::Result<()> {
+ self.client
+ .delete_object()
+ .bucket(bucket_id)
+ .key(path)
+ .send()
+ .await
+ .with_context(|| format!("failed to delete object at {path} in {bucket_id}"))?;
+
+ Ok(())
+ }
+}
diff --git a/crates/core/files/src/lib.rs b/crates/core/files/src/lib.rs
index dacc988e5..0f71f095c 100644
--- a/crates/core/files/src/lib.rs
+++ b/crates/core/files/src/lib.rs
@@ -1,290 +1,210 @@
-use std::io::{BufRead, Read, Seek, Write};
+mod implementation;
+mod repositories;
-use aes_gcm::{
- aead::{AeadCore, AeadMutInPlace, OsRng},
- Aes256Gcm, Key, KeyInit, Nonce,
-};
-use image::{DynamicImage, ImageBuffer};
-use revolt_config::{config, report_internal_error, FilesS3};
-use revolt_result::{create_error, Result};
+pub use implementation::*;
+pub use repositories::*;
-use aws_sdk_s3::{
- config::{Credentials, Region},
- Client, Config,
-};
+use std::io::{BufRead, Read, Seek};
+
+use image::DynamicImage;
+use revolt_config::{report_internal_error, Files, FilesLimit, FilesS3};
+use revolt_result::Result;
-use base64::prelude::*;
use tempfile::NamedTempFile;
-use tiny_skia::Pixmap;
-/// Size of the authentication tag in the buffer
pub const AUTHENTICATION_TAG_SIZE_BYTES: usize = 16;
-/// Create an S3 client
-pub fn create_client(s3_config: FilesS3) -> Client {
- let provider_name = "my-creds";
- let creds = Credentials::new(
- s3_config.access_key_id,
- s3_config.secret_access_key,
- None,
- None,
- provider_name,
- );
-
- let config = Config::builder()
- .region(Region::new(s3_config.region))
- .endpoint_url(s3_config.endpoint)
- .force_path_style(s3_config.path_style_buckets)
- .credentials_provider(creds)
- .build();
-
- Client::from_conf(config)
-}
-
-/// Create an AES-256-GCM cipher
-pub fn create_cipher(key: &str) -> Aes256Gcm {
- let key = &BASE64_STANDARD.decode(key).expect("valid base64 string")[..];
- let key: &Key = key.into();
- Aes256Gcm::new(key)
-}
-
/// Fetch a file from S3 (and decrypt it)
-pub async fn fetch_from_s3(bucket_id: &str, path: &str, nonce: &str) -> Result> {
- let config = config().await;
- let client = create_client(config.files.s3);
-
- // Send a request for the file
- let mut obj =
- report_internal_error!(client.get_object().bucket(bucket_id).key(path).send().await)?;
-
- // Read the file from remote
- let mut buf = vec![];
- while let Some(bytes) = obj.body.next().await {
- let data = report_internal_error!(bytes)?;
- report_internal_error!(buf.write_all(&data))?;
- // is there a more efficient way to do this?
- // we just want the Vec
- }
-
- // File is not encrypted
- if nonce.is_empty() {
- return Ok(buf);
- }
-
- // Recover nonce as bytes
- let nonce = &BASE64_STANDARD.decode(nonce).unwrap()[..];
- let nonce: &Nonce = nonce.into();
-
- // Decrypt the file
- create_cipher(&config.files.encryption_key)
- .decrypt_in_place(nonce, b"", &mut buf)
- .map_err(|_| create_error!(InternalError))?;
-
- Ok(buf)
+pub async fn fetch_from_s3(bucket_id: &str, path: &str, iv: &str) -> Result> {
+ let encryption = implementation::EncryptionKey::from_config().await;
+ let storage = implementation::S3Storage::from_config(encryption).await;
+ report_internal_error!(storage.fetch_and_decrypt_file(bucket_id, path, iv).await)
}
/// Encrypt and upload a file to S3 (returning its nonce/IV)
pub async fn upload_to_s3(bucket_id: &str, path: &str, buf: &[u8]) -> Result {
- let config = config().await;
- let client = create_client(config.files.s3);
-
- // Generate a nonce
- let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
-
- // Extend the buffer for in-place encryption
- let mut buf = [buf, &[0; AUTHENTICATION_TAG_SIZE_BYTES]].concat();
-
- // Encrypt the file in place
- create_cipher(&config.files.encryption_key)
- .encrypt_in_place(&nonce, b"", &mut buf)
- .map_err(|_| create_error!(InternalError))?;
-
- // Upload the file to remote
- report_internal_error!(
- client
- .put_object()
- .bucket(bucket_id)
- .key(path)
- .body(buf.into())
- .send()
- .await
- )?;
-
- Ok(BASE64_STANDARD.encode(nonce))
+ let encryption = implementation::EncryptionKey::from_config().await;
+ let storage = implementation::S3Storage::from_config(encryption).await;
+ report_internal_error!(storage.encrypt_and_upload_file(bucket_id, path, buf).await)
}
/// Delete a file from S3 by path
pub async fn delete_from_s3(bucket_id: &str, path: &str) -> Result<()> {
- let config = config().await;
- let client = create_client(config.files.s3);
-
- report_internal_error!(
- client
- .delete_object()
- .bucket(bucket_id)
- .key(path)
- .send()
- .await
- )?;
-
- Ok(())
+ let encryption = implementation::EncryptionKey::from_config().await;
+ let storage = implementation::S3Storage::from_config(encryption).await;
+ report_internal_error!(storage.delete_file(bucket_id, path).await)
}
/// Determine size of image at temp file
pub fn image_size(f: &NamedTempFile) -> Option<(usize, usize)> {
- if let Ok(size) = imagesize::size(f.path())
- .inspect_err(|err| tracing::error!("Failed to generate image size! {err:?}"))
- {
- Some((size.width, size.height))
- } else {
- None
- }
+ let media = MediaImpl::new(Files {
+ blocked_mime_types: Default::default(),
+ clamd_host: Default::default(),
+ encryption_key: Default::default(),
+ limit: FilesLimit {
+ max_mega_pixels: 0,
+ max_pixel_side: 0,
+ min_file_size: 0,
+ min_resolution: [0, 0],
+ },
+ preview: Default::default(),
+ s3: FilesS3 {
+ access_key_id: Default::default(),
+ default_bucket: Default::default(),
+ endpoint: Default::default(),
+ path_style_buckets: Default::default(),
+ region: Default::default(),
+ secret_access_key: Default::default(),
+ },
+ scan_mime_types: Default::default(),
+ webp_quality: Default::default(),
+ });
+
+ media.image_size(f)
}
/// Determine size of image with buffer
pub fn image_size_vec(v: &[u8], mime: &str) -> Option<(usize, usize)> {
- match mime {
- "image/svg+xml" => {
- let tree =
- report_internal_error!(usvg::Tree::from_data(v, &Default::default())).ok()?;
+ let media = MediaImpl::new(Files {
+ blocked_mime_types: Default::default(),
+ clamd_host: Default::default(),
+ encryption_key: Default::default(),
+ limit: FilesLimit {
+ max_mega_pixels: 0,
+ max_pixel_side: 0,
+ min_file_size: 0,
+ min_resolution: [0, 0],
+ },
+ preview: Default::default(),
+ s3: FilesS3 {
+ access_key_id: Default::default(),
+ default_bucket: Default::default(),
+ endpoint: Default::default(),
+ path_style_buckets: Default::default(),
+ region: Default::default(),
+ secret_access_key: Default::default(),
+ },
+ scan_mime_types: Default::default(),
+ webp_quality: Default::default(),
+ });
+
+ media.image_size_vec(v, mime)
+}
- let size = tree.size();
- Some((size.width() as usize, size.height() as usize))
- }
- _ => {
- if let Ok(size) = imagesize::blob_size(v)
- .inspect_err(|err| tracing::error!("Failed to generate image size! {err:?}"))
- {
- Some((size.width, size.height))
- } else {
- None
- }
- }
- }
+/// Check whether an image file contains animation data
+pub fn is_animated(f: &NamedTempFile, mime: &str) -> Option {
+ let media = MediaImpl::new(Files {
+ blocked_mime_types: Default::default(),
+ clamd_host: Default::default(),
+ encryption_key: Default::default(),
+ limit: FilesLimit {
+ max_mega_pixels: 0,
+ max_pixel_side: 0,
+ min_file_size: 0,
+ min_resolution: [0, 0],
+ },
+ preview: Default::default(),
+ s3: FilesS3 {
+ access_key_id: Default::default(),
+ default_bucket: Default::default(),
+ endpoint: Default::default(),
+ path_style_buckets: Default::default(),
+ region: Default::default(),
+ secret_access_key: Default::default(),
+ },
+ scan_mime_types: Default::default(),
+ webp_quality: Default::default(),
+ });
+
+ media.is_animated(f, mime)
}
/// Determine size of video at temp file
pub fn video_size(f: &NamedTempFile) -> Option<(i64, i64)> {
- if let Ok(data) = ffprobe::ffprobe(f.path())
- .inspect_err(|err| tracing::error!("Failed to ffprobe file! {err:?}"))
- {
- // Use first valid stream
- for stream in data.streams {
- if let (Some(w), Some(h)) = (stream.width, stream.height) {
- return Some((w, h));
- }
- }
-
- None
- } else {
- None
- }
+ let media = MediaImpl::new(Files {
+ blocked_mime_types: Default::default(),
+ clamd_host: Default::default(),
+ encryption_key: Default::default(),
+ limit: FilesLimit {
+ max_mega_pixels: 0,
+ max_pixel_side: 0,
+ min_file_size: 0,
+ min_resolution: [0, 0],
+ },
+ preview: Default::default(),
+ s3: FilesS3 {
+ access_key_id: Default::default(),
+ default_bucket: Default::default(),
+ endpoint: Default::default(),
+ path_style_buckets: Default::default(),
+ region: Default::default(),
+ secret_access_key: Default::default(),
+ },
+ scan_mime_types: Default::default(),
+ webp_quality: Default::default(),
+ });
+
+ media.video_size(f)
}
/// Decode image from reader
pub fn decode_image(reader: &mut R, mime: &str) -> Result {
- match mime {
- // Read image using jxl-oxide crate
- "image/jxl" => {
- let jxl_image = report_internal_error!(jxl_oxide::JxlImage::builder().read(reader))?;
- if let Ok(frame) = jxl_image.render_frame(0) {
- match frame.color_channels().len() {
- 3 => Ok(DynamicImage::ImageRgb8(
- DynamicImage::ImageRgb32F(
- ImageBuffer::from_vec(
- jxl_image.width(),
- jxl_image.height(),
- frame.image().buf().to_vec(),
- )
- .ok_or_else(|| create_error!(ImageProcessingFailed))?,
- )
- .to_rgb8(),
- )),
- 4 => Ok(DynamicImage::ImageRgba8(
- DynamicImage::ImageRgba32F(
- ImageBuffer::from_vec(
- jxl_image.width(),
- jxl_image.height(),
- frame.image().buf().to_vec(),
- )
- .ok_or_else(|| create_error!(ImageProcessingFailed))?,
- )
- .to_rgba8(),
- )),
- _ => Err(create_error!(ImageProcessingFailed)),
- }
- } else {
- Err(create_error!(ImageProcessingFailed))
- }
- }
- // Read image using resvg
- "image/svg+xml" => {
- // usvg doesn't support Read trait so copy to buffer
- let mut buf = Vec::new();
- report_internal_error!(reader.read_to_end(&mut buf))?;
-
- let tree = report_internal_error!(usvg::Tree::from_data(&buf, &Default::default()))?;
- let size = tree.size();
- let mut pixmap = Pixmap::new(size.width() as u32, size.height() as u32)
- .ok_or_else(|| create_error!(ImageProcessingFailed))?;
-
- let mut pixmap_mut = pixmap.as_mut();
- resvg::render(&tree, Default::default(), &mut pixmap_mut);
-
- Ok(DynamicImage::ImageRgba8(
- ImageBuffer::from_vec(
- size.width() as u32,
- size.height() as u32,
- pixmap.data().to_vec(),
- )
- .ok_or_else(|| create_error!(ImageProcessingFailed))?,
- ))
- }
- // Check if we can read using image-rs crate
- _ => report_internal_error!(report_internal_error!(
- image::ImageReader::new(reader).with_guessed_format()
- )?
- .decode()),
- }
+ let media = MediaImpl::new(Files {
+ blocked_mime_types: Default::default(),
+ clamd_host: Default::default(),
+ encryption_key: Default::default(),
+ limit: FilesLimit {
+ max_mega_pixels: 0,
+ max_pixel_side: 0,
+ min_file_size: 0,
+ min_resolution: [0, 0],
+ },
+ preview: Default::default(),
+ s3: FilesS3 {
+ access_key_id: Default::default(),
+ default_bucket: Default::default(),
+ endpoint: Default::default(),
+ path_style_buckets: Default::default(),
+ region: Default::default(),
+ secret_access_key: Default::default(),
+ },
+ scan_mime_types: Default::default(),
+ webp_quality: Default::default(),
+ });
+
+ report_internal_error!(media.decode_image(reader, mime))
}
/// Check whether given reader has a valid image
pub fn is_valid_image(reader: &mut R, mime: &str) -> bool {
- match mime {
- // Check if we can read using jxl-oxide crate
- "image/jxl" => jxl_oxide::JxlImage::builder()
- .read(reader)
- .inspect_err(|err| tracing::error!("Failed to read JXL! {err:?}"))
- .is_ok(),
- // Check if we can read using image-rs crate
- _ => !matches!(
- image::ImageReader::new(reader)
- .with_guessed_format()
- .inspect_err(|err| tracing::error!("Failed to read image! {err:?}"))
- .map(|f| f.decode()),
- Err(_) | Ok(Err(_))
- ),
- }
+ let media = MediaImpl::new(Files {
+ blocked_mime_types: Default::default(),
+ clamd_host: Default::default(),
+ encryption_key: Default::default(),
+ limit: FilesLimit {
+ max_mega_pixels: 0,
+ max_pixel_side: 0,
+ min_file_size: 0,
+ min_resolution: [0, 0],
+ },
+ preview: Default::default(),
+ s3: FilesS3 {
+ access_key_id: Default::default(),
+ default_bucket: Default::default(),
+ endpoint: Default::default(),
+ path_style_buckets: Default::default(),
+ region: Default::default(),
+ secret_access_key: Default::default(),
+ },
+ scan_mime_types: Default::default(),
+ webp_quality: Default::default(),
+ });
+
+ media.is_valid_image(reader, mime)
}
/// Create thumbnail from given image
pub async fn create_thumbnail(image: DynamicImage, tag: &str) -> Vec {
- // Load configuration
- let config = config().await;
- let [w, h] = config.files.preview.get(tag).unwrap();
-
- // Create thumbnail
- //.resize(width as u32, height as u32, image::imageops::FilterType::Gaussian)
- // resize is about 2.5x slower,
- // thumbnail doesn't have terrible quality
- // so we use thumbnail
- let image = image.thumbnail(image.width().min(*w as u32), image.height().min(*h as u32));
-
- // Encode it into WEBP
- let encoder = webp::Encoder::from_image(&image).expect("Could not create encoder.");
- if config.files.webp_quality != 100.0 {
- encoder.encode(config.files.webp_quality).to_vec()
- } else {
- encoder.encode_lossless().to_vec()
- }
+ let media = MediaImpl::from_config().await;
+ media.create_thumbnail(image, tag)
}
diff --git a/crates/core/files/src/repositories/encryption_repository.rs b/crates/core/files/src/repositories/encryption_repository.rs
new file mode 100644
index 000000000..98e2a4988
--- /dev/null
+++ b/crates/core/files/src/repositories/encryption_repository.rs
@@ -0,0 +1,6 @@
+use anyhow::Result;
+
+pub trait EncryptionRepository: Send + Sync + 'static {
+ fn decrypt_buffer(&self, buf: Vec, iv: &str) -> anyhow::Result>;
+ fn encrypt_buffer(&self, buf: &[u8]) -> Result<(Vec, String)>;
+}
diff --git a/crates/core/files/src/repositories/file_storage_repository.rs b/crates/core/files/src/repositories/file_storage_repository.rs
new file mode 100644
index 000000000..af97be808
--- /dev/null
+++ b/crates/core/files/src/repositories/file_storage_repository.rs
@@ -0,0 +1,22 @@
+use anyhow::Result;
+
+#[async_trait::async_trait]
+pub trait FileStorageRepository: Send + Sync + 'static {
+ async fn create_bucket(&self, bucket_id: &str) -> anyhow::Result<()>;
+
+ async fn fetch_and_decrypt_file(
+ &self,
+ bucket_id: &str,
+ path: &str,
+ iv: &str,
+ ) -> Result>;
+
+ async fn encrypt_and_upload_file(
+ &self,
+ bucket_id: &str,
+ path: &str,
+ buf: &[u8],
+ ) -> Result;
+
+ async fn delete_file(&self, bucket_id: &str, path: &str) -> Result<()>;
+}
diff --git a/crates/core/files/src/repositories/media_repository.rs b/crates/core/files/src/repositories/media_repository.rs
new file mode 100644
index 000000000..6c489d9b3
--- /dev/null
+++ b/crates/core/files/src/repositories/media_repository.rs
@@ -0,0 +1,33 @@
+use anyhow::Result;
+use image::DynamicImage;
+use std::io::{BufRead, Read, Seek};
+use tempfile::NamedTempFile;
+use thiserror::Error;
+
+pub trait MediaRepository: Send + Sync + 'static {
+ fn image_size(&self, f: &NamedTempFile) -> Option<(usize, usize)>;
+
+ fn is_animated(&self, f: &NamedTempFile, mime: &str) -> Option;
+
+ fn image_size_vec(&self, v: &[u8], mime: &str) -> Option<(usize, usize)>;
+
+ fn decode_image(
+ &self,
+ reader: &mut R,
+ mime: &str,
+ ) -> Result;
+
+ fn is_valid_image(&self, reader: &mut R, mime: &str) -> bool;
+
+ fn create_thumbnail(&self, image: DynamicImage, tag: &str) -> Vec;
+
+ fn video_size(&self, f: &NamedTempFile) -> Option<(i64, i64)>;
+}
+
+#[derive(Debug, Error)]
+pub enum MediaError {
+ #[error("image processing failed because {cause}")]
+ ImageProcessingFailed { cause: String },
+ #[error(transparent)]
+ Unknown(#[from] anyhow::Error),
+}
diff --git a/crates/core/files/src/repositories/mod.rs b/crates/core/files/src/repositories/mod.rs
new file mode 100644
index 000000000..cb0749e72
--- /dev/null
+++ b/crates/core/files/src/repositories/mod.rs
@@ -0,0 +1,7 @@
+mod encryption_repository;
+mod file_storage_repository;
+mod media_repository;
+
+pub use encryption_repository::EncryptionRepository;
+pub use file_storage_repository::FileStorageRepository;
+pub use media_repository::{MediaError, MediaRepository};
diff --git a/crates/core/files/tests/assets/anim-icos.apng b/crates/core/files/tests/assets/anim-icos.apng
new file mode 100644
index 000000000..b50cbe364
Binary files /dev/null and b/crates/core/files/tests/assets/anim-icos.apng differ
diff --git a/crates/core/files/tests/assets/anim-icos.gif b/crates/core/files/tests/assets/anim-icos.gif
new file mode 100644
index 000000000..5b81b05eb
Binary files /dev/null and b/crates/core/files/tests/assets/anim-icos.gif differ
diff --git a/crates/core/files/tests/assets/anim-icos.jxl b/crates/core/files/tests/assets/anim-icos.jxl
new file mode 100644
index 000000000..bf3d1e445
Binary files /dev/null and b/crates/core/files/tests/assets/anim-icos.jxl differ
diff --git a/crates/core/files/tests/assets/anim-icos.webp b/crates/core/files/tests/assets/anim-icos.webp
new file mode 100644
index 000000000..ebf34e141
Binary files /dev/null and b/crates/core/files/tests/assets/anim-icos.webp differ
diff --git a/crates/core/files/tests/assets/corrupted.png b/crates/core/files/tests/assets/corrupted.png
new file mode 100644
index 000000000..f0fcce474
Binary files /dev/null and b/crates/core/files/tests/assets/corrupted.png differ
diff --git a/crates/core/files/tests/assets/dice.jxl b/crates/core/files/tests/assets/dice.jxl
new file mode 100644
index 000000000..d1a5a7d0d
Binary files /dev/null and b/crates/core/files/tests/assets/dice.jxl differ
diff --git a/crates/core/files/tests/assets/dice.webp b/crates/core/files/tests/assets/dice.webp
new file mode 100644
index 000000000..ee3dfe535
Binary files /dev/null and b/crates/core/files/tests/assets/dice.webp differ
diff --git a/crates/core/files/tests/assets/test-float.png b/crates/core/files/tests/assets/test-float.png
new file mode 100644
index 000000000..036e9d4d4
Binary files /dev/null and b/crates/core/files/tests/assets/test-float.png differ
diff --git a/crates/core/files/tests/assets/test.jpeg b/crates/core/files/tests/assets/test.jpeg
new file mode 100644
index 000000000..504bd6bcf
Binary files /dev/null and b/crates/core/files/tests/assets/test.jpeg differ
diff --git a/crates/core/files/tests/assets/test.png b/crates/core/files/tests/assets/test.png
new file mode 100644
index 000000000..9de075cdf
Binary files /dev/null and b/crates/core/files/tests/assets/test.png differ
diff --git a/crates/core/files/tests/integration_test.rs b/crates/core/files/tests/integration_test.rs
new file mode 100644
index 000000000..1827a55a8
--- /dev/null
+++ b/crates/core/files/tests/integration_test.rs
@@ -0,0 +1,31 @@
+use std::io::Cursor;
+
+use revolt_files::{EncryptionKey, FileStorageRepository, MediaImpl, MediaRepository, S3Storage};
+
+#[tokio::test]
+async fn test_image_roundtrip_png() {
+ let media = MediaImpl::from_config().await;
+ let encryption = EncryptionKey::from_config().await;
+ let s3 = S3Storage::from_config(encryption).await;
+
+ let buf = include_bytes!("./assets/test.png");
+ let bucket_id = uuid::Uuid::new_v4().to_string();
+
+ s3.create_bucket(&bucket_id).await.unwrap();
+
+ let mut reader = Cursor::new(buf);
+ media.decode_image(&mut reader, "image/png").unwrap();
+
+ let iv = s3
+ .encrypt_and_upload_file(&bucket_id, "/my-file", buf)
+ .await
+ .unwrap();
+
+ let buf = s3
+ .fetch_and_decrypt_file(&bucket_id, "/my-file", &iv)
+ .await
+ .unwrap();
+
+ let mut reader = Cursor::new(buf);
+ media.decode_image(&mut reader, "image/png").unwrap();
+}
diff --git a/crates/core/files/tests/s3_test.rs b/crates/core/files/tests/s3_test.rs
new file mode 100644
index 000000000..95d1afcf1
--- /dev/null
+++ b/crates/core/files/tests/s3_test.rs
@@ -0,0 +1,42 @@
+use revolt_files::{EncryptionKey, FileStorageRepository, S3Storage};
+
+#[tokio::test]
+async fn test_upload_and_download() {
+ let encryption = EncryptionKey::from_config().await;
+ let s3 = S3Storage::from_config(encryption).await;
+
+ let buf = [67];
+ let bucket_id = uuid::Uuid::new_v4().to_string();
+
+ s3.create_bucket(&bucket_id).await.unwrap();
+
+ let iv = s3
+ .encrypt_and_upload_file(&bucket_id, "/my-file", &buf)
+ .await
+ .unwrap();
+
+ let buf = s3
+ .fetch_and_decrypt_file(&bucket_id, "/my-file", &iv)
+ .await
+ .unwrap();
+
+ assert_eq!(buf.len(), 1);
+ assert_eq!(buf[0], 67);
+}
+
+#[tokio::test]
+async fn test_upload_and_delete() {
+ let encryption = EncryptionKey::from_config().await;
+ let s3 = S3Storage::from_config(encryption).await;
+
+ let buf = [67];
+ let bucket_id = uuid::Uuid::new_v4().to_string();
+
+ s3.create_bucket(&bucket_id).await.unwrap();
+
+ s3.encrypt_and_upload_file(&bucket_id, "/my-file", &buf)
+ .await
+ .unwrap();
+
+ s3.delete_file(&bucket_id, "/my-file").await.unwrap();
+}
diff --git a/crates/core/models/Cargo.toml b/crates/core/models/Cargo.toml
index e232fd6cc..c92f3f8a2 100644
--- a/crates/core/models/Cargo.toml
+++ b/crates/core/models/Cargo.toml
@@ -1,10 +1,11 @@
[package]
name = "revolt-models"
-version = "0.8.8"
+version = "0.13.7"
edition = "2021"
license = "MIT"
authors = ["Paul Makles "]
description = "Revolt Backend: API Models"
+repository = "https://github.com/stoatchat/stoatchat"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -20,26 +21,26 @@ default = ["serde", "partials", "rocket"]
[dependencies]
# Core
-revolt-config = { version = "0.8.8", path = "../config" }
-revolt-permissions = { version = "0.8.8", path = "../permissions" }
+revolt-config = { workspace = true }
+revolt-permissions = { workspace = true }
# Utility
-regex = "1.11"
-indexmap = "1.9.3"
-once_cell = "1.17.1"
-num_enum = "0.6.1"
+regex = { workspace = true }
+indexmap = { workspace = true }
+once_cell = { workspace = true }
+num_enum = { workspace = true }
# Rocket
-rocket = { optional = true, version = "0.5.0-rc.2", default-features = false }
+rocket = { workspace = true, optional = true }
# Serialisation
-revolt_optional_struct = { version = "0.2.0", optional = true }
-serde = { version = "1", features = ["derive"], optional = true }
-iso8601-timestamp = { version = "0.2.11", features = ["schema", "bson"] }
+revolt_optional_struct = { workspace = true, optional = true }
+serde = { workspace = true, optional = true }
+iso8601-timestamp = { workspace = true, features = ["schema", "bson"] }
# Spec Generation
-schemars = { version = "0.8.8", optional = true, features = ["indexmap1"] }
-utoipa = { version = "4.2.3", optional = true }
+schemars = { workspace = true, features = ["indexmap2"], optional = true }
+utoipa = { workspace = true, optional = true }
# Validation
-validator = { version = "0.16.0", optional = true, features = ["derive"] }
+validator = { workspace = true, features = ["derive"], optional = true }
diff --git a/crates/core/models/src/v0/channels.rs b/crates/core/models/src/v0/channels.rs
index ec25257b8..faddee749 100644
--- a/crates/core/models/src/v0/channels.rs
+++ b/crates/core/models/src/v0/channels.rs
@@ -1,4 +1,5 @@
-use super::File;
+#![allow(deprecated)]
+use super::{File, UserVoiceState};
use revolt_permissions::{Override, OverrideField};
use std::collections::{HashMap, HashSet};
@@ -107,46 +108,27 @@ auto_derived!(
serde(skip_serializing_if = "crate::if_false", default)
)]
nsfw: bool,
- },
- /// Voice channel belonging to a server
- VoiceChannel {
- /// Unique Id
- #[cfg_attr(feature = "serde", serde(rename = "_id"))]
- id: String,
- /// Id of the server this channel belongs to
- server: String,
-
- /// Display name of the channel
- name: String,
- #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
- /// Channel description
- description: Option,
- /// Custom icon attachment
- #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
- icon: Option,
- /// Default permissions assigned to users in this channel
+ /// Voice Information for when this channel is also a voice channel
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
- default_permissions: Option,
- /// Permissions assigned based on role to this channel
- #[cfg_attr(
- feature = "serde",
- serde(
- default = "HashMap::::new",
- skip_serializing_if = "HashMap::::is_empty"
- )
- )]
- role_permissions: HashMap,
+ voice: Option,
- /// Whether this channel is marked as not safe for work
- #[cfg_attr(
- feature = "serde",
- serde(skip_serializing_if = "crate::if_false", default)
- )]
- nsfw: bool,
+ /// The channel's slowmode delay in seconds
+ #[serde(skip_serializing_if = "Option::is_none")]
+ slowmode: Option,
},
}
+ /// Voice information for a channel
+ #[derive(Default)]
+ #[cfg_attr(feature = "validator", derive(validator::Validate))]
+ pub struct VoiceInformation {
+ /// Maximium amount of users allowed in the voice channel at once
+ #[cfg_attr(feature = "validator", validate(range(min = 1)))]
+ #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
+ pub max_users: Option,
+ }
+
/// Partial representation of a channel
#[derive(Default)]
pub struct PartialChannel {
@@ -170,6 +152,10 @@ auto_derived!(
pub default_permissions: Option,
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub last_message_id: Option,
+ #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
+ pub voice: Option,
+ #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
+ pub slowmode: Option,
}
/// Optional fields on channel object
@@ -177,6 +163,7 @@ auto_derived!(
Description,
Icon,
DefaultPermissions,
+ Voice,
}
/// New webhook information
@@ -205,6 +192,13 @@ auto_derived!(
/// Whether this channel is archived
pub archived: Option,
+ /// Voice Information for voice channels
+ pub voice: Option,
+
+ /// The channel's slow mode delay in seconds, up to 6 hours
+ #[cfg_attr(feature = "validator", validate(range(min = 0, max = 21600)))]
+ pub slowmode: Option,
+
/// Fields to remove from channel
#[cfg_attr(feature = "serde", serde(default))]
pub remove: Vec,
@@ -260,6 +254,10 @@ auto_derived!(
/// Whether this channel is age restricted
#[serde(skip_serializing_if = "Option::is_none")]
pub nsfw: Option,
+
+ /// Voice Information for when this channel is also a voice channel
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub voice: Option,
}
/// New default permissions
@@ -270,7 +268,7 @@ auto_derived!(
permissions: u64,
},
Field {
- /// Allow / deny values to set for members in this `TextChannel` or `VoiceChannel`
+ /// Allow / deny values to set for members in this server channel
permissions: Override,
},
}
@@ -289,9 +287,38 @@ auto_derived!(
}
/// Voice server token response
- pub struct LegacyCreateVoiceUserResponse {
+ pub struct CreateVoiceUserResponse {
/// Token for authenticating with the voice server
- token: String,
+ pub token: String,
+ /// Url of the livekit server to connect to
+ pub url: String,
+ }
+
+ /// Voice state for a channel
+ pub struct ChannelVoiceState {
+ pub id: String,
+ /// The states of the users who are connected to the channel
+ pub participants: Vec,
+ }
+
+ /// Join a voice channel
+ pub struct DataJoinCall {
+ /// Name of the node to join
+ pub node: Option,
+ /// Whether to force disconnect any other existing voice connections
+ ///
+ /// Useful for disconnecting on another device and joining on a new.
+ pub force_disconnect: Option,
+ /// Users which should be notified of the call starting
+ ///
+ /// Only used when the user is the first one connected.
+ pub recipients: Option>,
+ }
+
+ pub struct ChannelSlowmode {
+ pub channel_id: String,
+ pub duration: u64,
+ pub retry_after: u64,
}
);
@@ -302,8 +329,7 @@ impl Channel {
Channel::DirectMessage { id, .. }
| Channel::Group { id, .. }
| Channel::SavedMessages { id, .. }
- | Channel::TextChannel { id, .. }
- | Channel::VoiceChannel { id, .. } => id,
+ | Channel::TextChannel { id, .. } => id,
}
}
@@ -315,9 +341,7 @@ impl Channel {
match self {
Channel::DirectMessage { .. } => None,
Channel::SavedMessages { .. } => Some("Saved Messages"),
- Channel::TextChannel { name, .. }
- | Channel::Group { name, .. }
- | Channel::VoiceChannel { name, .. } => Some(name),
+ Channel::TextChannel { name, .. } | Channel::Group { name, .. } => Some(name),
}
}
}
diff --git a/crates/core/models/src/v0/emojis.rs b/crates/core/models/src/v0/emojis.rs
index 2d7c015e1..58d47429a 100644
--- a/crates/core/models/src/v0/emojis.rs
+++ b/crates/core/models/src/v0/emojis.rs
@@ -54,4 +54,22 @@ auto_derived!(
#[serde(default)]
pub nsfw: bool,
}
+
+ /// Partial emoji representation
+ #[derive(Default)]
+ pub struct PartialEmoji {
+ #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
+ pub name: Option,
+ }
+
+ /// Edit emoji information
+ #[cfg_attr(feature = "validator", derive(Validate))]
+ pub struct DataEditEmoji {
+ /// Emoji name
+ #[cfg_attr(
+ feature = "validator",
+ validate(length(min = 1, max = 32), regex = "RE_EMOJI")
+ )]
+ pub name: Option,
+ }
);
diff --git a/crates/core/models/src/v0/files.rs b/crates/core/models/src/v0/files.rs
index b996483cb..2d68b7f2b 100644
--- a/crates/core/models/src/v0/files.rs
+++ b/crates/core/models/src/v0/files.rs
@@ -46,8 +46,13 @@ auto_derived!(
File,
/// File contains textual data and should be displayed as such
Text,
- /// File is an image with specific dimensions
- Image { width: usize, height: usize },
+ /// File is an image with specific dimensions, and may be animated
+ Image {
+ width: usize,
+ height: usize,
+ thumbhash: Option>,
+ animated: Option,
+ },
/// File is a video with specific dimensions
Video { width: usize, height: usize },
/// File is audio
diff --git a/crates/core/models/src/v0/messages.rs b/crates/core/models/src/v0/messages.rs
index 2116d98ba..cb8f3f246 100644
--- a/crates/core/models/src/v0/messages.rs
+++ b/crates/core/models/src/v0/messages.rs
@@ -132,6 +132,11 @@ auto_derived!(
MessagePinned { id: String, by: String },
#[serde(rename = "message_unpinned")]
MessageUnpinned { id: String, by: String },
+ #[serde(rename = "call_started")]
+ CallStarted {
+ by: String,
+ finished_at: Option,
+ },
}
/// Name and / or avatar override information
@@ -199,6 +204,9 @@ auto_derived!(
pub image: Option,
/// Message content or system message information
pub body: String,
+ /// The raw body, if the body has been rendered
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub raw_body: Option,
/// Unique tag, usually the channel ID
pub tag: String,
/// Timestamp at which this notification was created
@@ -253,7 +261,7 @@ auto_derived!(
pub nonce: Option,
/// Message content to send
- #[cfg_attr(feature = "validator", validate(length(min = 0, max = 2000)))]
+ #[cfg_attr(feature = "validator", validate(length(min = 0)))]
pub content: Option,
/// Attachments to include in message
pub attachments: Option>,
@@ -337,7 +345,7 @@ auto_derived!(
#[cfg_attr(feature = "validator", derive(Validate))]
pub struct DataEditMessage {
/// New message content
- #[cfg_attr(feature = "validator", validate(length(min = 1, max = 2000)))]
+ #[cfg_attr(feature = "validator", validate(length(min = 1)))]
pub content: Option,
/// Embeds to include in the message
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 10)))]
@@ -383,6 +391,7 @@ auto_derived!(
);
/// Message Author Abstraction
+#[derive(Clone)]
pub enum MessageAuthor<'a> {
User(&'a User),
Webhook(&'a Webhook),
@@ -445,6 +454,7 @@ impl From for String {
}
SystemMessage::MessagePinned { .. } => "Message pinned.".to_string(),
SystemMessage::MessageUnpinned { .. } => "Message unpinned.".to_string(),
+ SystemMessage::CallStarted { .. } => "Call started.".to_string(),
}
}
}
@@ -505,6 +515,7 @@ impl PushNotification {
icon,
image,
body,
+ raw_body: None,
tag: channel.id().to_string(),
timestamp,
url: format!("{}/channel/{}/{}", config.hosts.app, channel.id(), msg.id),
diff --git a/crates/core/models/src/v0/server_bans.rs b/crates/core/models/src/v0/server_bans.rs
index b6debfcdc..940a3738b 100644
--- a/crates/core/models/src/v0/server_bans.rs
+++ b/crates/core/models/src/v0/server_bans.rs
@@ -19,6 +19,9 @@ auto_derived!(
/// Ban reason
#[cfg_attr(feature = "validator", validate(length(min = 0, max = 1024)))]
pub reason: Option,
+ /// Messages to delete in seconds
+ #[cfg_attr(feature = "validator", validate(range(min = 0, max = 604800)))]
+ pub delete_message_seconds: Option,
}
/// Just enough information to list a ban
diff --git a/crates/core/models/src/v0/server_members.rs b/crates/core/models/src/v0/server_members.rs
index 048f696bc..41ec99ae8 100644
--- a/crates/core/models/src/v0/server_members.rs
+++ b/crates/core/models/src/v0/server_members.rs
@@ -31,6 +31,14 @@ pub static RE_COLOUR: Lazy = Lazy::new(|| {
Regex::new(r"(?i)^(?:[a-z ]+|var\(--[a-z\d-]+\)|rgba?\([\d, ]+\)|#[a-f0-9]+|(repeating-)?(linear|conic|radial)-gradient\(([a-z ]+|var\(--[a-z\d-]+\)|rgba?\([\d, ]+\)|#[a-f0-9]+|\d+deg)([ ]+(\d{1,3}%|0))?(,[ ]*([a-z ]+|var\(--[a-z\d-]+\)|rgba?\([\d, ]+\)|#[a-f0-9]+)([ ]+(\d{1,3}%|0))?)+\))$").unwrap()
});
+fn default_true() -> bool {
+ true
+}
+
+fn is_true(x: &bool) -> bool {
+ *x
+}
+
auto_derived_partial!(
/// Server Member
pub struct Member {
@@ -57,6 +65,13 @@ auto_derived_partial!(
/// Timestamp this member is timed out until
#[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
pub timeout: Option,
+
+ /// Whether the member is server-wide voice muted
+ #[serde(skip_serializing_if = "is_true", default = "default_true")]
+ pub can_publish: bool,
+ /// Whether the member is server-wide voice deafened
+ #[serde(skip_serializing_if = "is_true", default = "default_true")]
+ pub can_receive: bool,
},
"PartialMember"
);
@@ -77,7 +92,10 @@ auto_derived!(
Avatar,
Roles,
Timeout,
+ CanReceive,
+ CanPublish,
JoinedAt,
+ VoiceChannel,
}
/// Member removal intention
@@ -124,6 +142,12 @@ auto_derived!(
pub roles: Option>,
/// Timestamp this member is timed out until
pub timeout: Option,
+ /// server-wide voice muted
+ pub can_publish: Option,
+ /// server-wide voice deafened
+ pub can_receive: Option,
+ /// voice channel to move to if already in a voice channel
+ pub voice_channel: Option,
/// Fields to remove from channel object
#[cfg_attr(feature = "serde", serde(default))]
pub remove: Vec,
diff --git a/crates/core/models/src/v0/servers.rs b/crates/core/models/src/v0/servers.rs
index c3ed20a8b..2aab765e6 100644
--- a/crates/core/models/src/v0/servers.rs
+++ b/crates/core/models/src/v0/servers.rs
@@ -85,6 +85,9 @@ auto_derived_partial!(
auto_derived_partial!(
/// Role
pub struct Role {
+ /// Unique Id
+ #[cfg_attr(feature = "serde", serde(rename = "_id"))]
+ pub id: String,
/// Role name
pub name: String,
/// Permissions available to this role
@@ -103,6 +106,9 @@ auto_derived_partial!(
/// Ranking of this role
#[cfg_attr(feature = "serde", serde(default))]
pub rank: i64,
+ /// Role icon
+ #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
+ pub icon: Option,
},
"PartialRole"
);
@@ -120,6 +126,7 @@ auto_derived!(
/// Optional fields on server object
pub enum FieldsRole {
Colour,
+ Icon,
}
/// Channel category
@@ -181,6 +188,7 @@ auto_derived!(
}
/// Response after creating new role
+ // TODO: remove this in favor of just Role
pub struct NewRoleResponse {
/// Id of the role
pub id: String,
@@ -248,6 +256,9 @@ auto_derived!(
/// Must be enabled in order to show up on [Revolt Discover](https://rvlt.gg).
pub analytics: Option,
+ /// User id of the new owner
+ pub owner: Option,
+
/// Fields to remove from server object
#[cfg_attr(feature = "serde", serde(default))]
pub remove: Vec,
@@ -271,6 +282,11 @@ auto_derived!(
///
/// **Removed** - no effect, use the edit server role positions route
pub rank: Option,
+ /// Role icon
+ ///
+ /// Provide an Autumn attachment Id.
+ #[cfg_attr(feature = "validator", validate(length(min = 1, max = 128)))]
+ pub icon: Option,
/// Fields to remove from role object
#[cfg_attr(feature = "serde", serde(default))]
pub remove: Vec,
diff --git a/crates/core/models/src/v0/users.rs b/crates/core/models/src/v0/users.rs
index fc681e003..73957768b 100644
--- a/crates/core/models/src/v0/users.rs
+++ b/crates/core/models/src/v0/users.rs
@@ -1,3 +1,4 @@
+use iso8601_timestamp::Timestamp;
use once_cell::sync::Lazy;
use regex::Regex;
@@ -279,6 +280,19 @@ auto_derived!(
}
);
+auto_derived_partial!(
+ /// Voice State information for a user
+ pub struct UserVoiceState {
+ pub id: String,
+ pub joined_at: Timestamp,
+ pub is_receiving: bool,
+ pub is_publishing: bool,
+ pub screensharing: bool,
+ pub camera: bool,
+ },
+ "PartialUserVoiceState"
+);
+
pub trait CheckRelationship {
fn with(&self, user: &str) -> RelationshipStatus;
}
diff --git a/crates/core/parser/Cargo.toml b/crates/core/parser/Cargo.toml
index a6e1869b7..5bb079acf 100644
--- a/crates/core/parser/Cargo.toml
+++ b/crates/core/parser/Cargo.toml
@@ -1,10 +1,11 @@
[package]
name = "revolt-parser"
-version = "0.8.8"
+version = "0.13.7"
edition = "2021"
license = "MIT"
authors = ["Zomatree ", "Paul Makles "]
description = "Revolt Backend: Message Parser"
+repository = "https://github.com/stoatchat/stoatchat"
[dependencies]
-logos = { version = "0.15" }
+logos = { workspace = true }
diff --git a/crates/core/parser/src/lib.rs b/crates/core/parser/src/lib.rs
index 4ebbac630..27211c1ba 100644
--- a/crates/core/parser/src/lib.rs
+++ b/crates/core/parser/src/lib.rs
@@ -16,23 +16,29 @@ pub enum MessageToken<'a> {
UserMention(&'a str),
#[regex("<%(?&id)>", |lex| &lex.slice()[2..lex.slice().len() - 1],)]
RoleMention(&'a str),
+ #[regex("<#(?&id)>", |lex| &lex.slice()[2..lex.slice().len() - 1],)]
+ ChannelMention(&'a str),
+ #[regex(":(?&id):", |lex| &lex.slice()[1..lex.slice().len() - 1],)]
+ Emoji(&'a str),
#[token("@everyone")]
MentionEveryone,
#[token("@online")]
- MentionOnline
+ MentionOnline,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct MessageResults {
pub user_mentions: HashSet,
pub role_mentions: HashSet,
+ pub channel_mentions: HashSet,
+ pub emojis: HashSet,
pub mentions_everyone: bool,
- pub mentions_online: bool
+ pub mentions_online: bool,
}
struct MessageParserIterator<'a, I> {
inner: I,
- temp: VecDeque>
+ temp: VecDeque>,
}
impl<'a, I: Iterator- >> Iterator for MessageParserIterator<'a, I> {
@@ -55,11 +61,11 @@ impl<'a, I: Iterator
- >> Iterator for MessageParserIterato
if next_token == Some(MessageToken::CodeblockMarker(ty)) {
self.temp.clear();
self.temp.push_back(MessageToken::CodeblockMarker(ty));
- break next_token
+ break next_token;
} else if let Some(token) = next_token {
self.temp.push_back(token);
} else {
- break Some(MessageToken::CodeblockMarker(ty))
+ break Some(MessageToken::CodeblockMarker(ty));
}
}
} else {
@@ -69,10 +75,10 @@ impl<'a, I: Iterator
- >> Iterator for MessageParserIterato
}
}
-pub fn parse_message_iter(text: &str) -> impl Iterator
- + '_ {
+pub fn parse_message_iter(text: &str) -> impl Iterator
- > + '_ {
MessageParserIterator {
inner: MessageToken::lexer(text).flatten(),
- temp: VecDeque::new()
+ temp: VecDeque::new(),
}
}
@@ -82,13 +88,23 @@ pub fn parse_message(text: &str) -> MessageResults {
for token in parse_message_iter(text) {
match token {
MessageToken::Escape => {}
- MessageToken::CodeblockMarker(_) => {},
- MessageToken::UserMention(id) => { results.user_mentions.insert(id.to_string()); },
- MessageToken::RoleMention(id) => { results.role_mentions.insert(id.to_string()); },
+ MessageToken::CodeblockMarker(_) => {}
+ MessageToken::UserMention(id) => {
+ results.user_mentions.insert(id.to_string());
+ }
+ MessageToken::RoleMention(id) => {
+ results.role_mentions.insert(id.to_string());
+ }
+ MessageToken::ChannelMention(id) => {
+ results.channel_mentions.insert(id.to_string());
+ }
+ MessageToken::Emoji(id) => {
+ results.emojis.insert(id.to_string());
+ }
MessageToken::MentionEveryone => results.mentions_everyone = true,
MessageToken::MentionOnline => results.mentions_online = true,
};
- };
+ }
results
}
@@ -109,7 +125,10 @@ mod tests {
let output = parse_message_iter("Hello <@01FD58YK5W7QRV5H3D64KTQYX3>.").collect::
>();
assert_eq!(output.len(), 1);
- assert_eq!(output[0], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
+ assert_eq!(
+ output[0],
+ MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
}
#[test]
@@ -117,7 +136,10 @@ mod tests {
let output = parse_message_iter("Hello <%01FD58YK5W7QRV5H3D64KTQYX3>.").collect::>();
assert_eq!(output.len(), 1);
- assert_eq!(output[0], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
+ assert_eq!(
+ output[0],
+ MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
}
#[test]
@@ -138,29 +160,57 @@ mod tests {
#[test]
fn test_everything() {
- let output = parse_message_iter("Hello <@01FD58YK5W7QRV5H3D64KTQYX3>, <%01FD58YK5W7QRV5H3D64KTQYX3>, @everyone and @online.").collect::>();
-
- assert_eq!(output.len(), 4);
- assert_eq!(output[0], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
- assert_eq!(output[1], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
- assert_eq!(output[2], MessageToken::MentionEveryone);
- assert_eq!(output[3], MessageToken::MentionOnline);
+ let output = parse_message_iter("Hello <@01FD58YK5W7QRV5H3D64KTQYX3>, <%01FD58YK5W7QRV5H3D64KTQYX3>, <#01FD58YK5W7QRV5H3D64KTQYX3> @everyone and @online. :01FD58YK5W7QRV5H3D64KTQYX3:").collect::>();
+
+ assert_eq!(output.len(), 6);
+ assert_eq!(
+ output[0],
+ MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
+ assert_eq!(
+ output[1],
+ MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
+ assert_eq!(
+ output[2],
+ MessageToken::ChannelMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
+ assert_eq!(output[3], MessageToken::MentionEveryone);
+ assert_eq!(output[4], MessageToken::MentionOnline);
+ assert_eq!(output[5], MessageToken::Emoji("01FD58YK5W7QRV5H3D64KTQYX3"));
}
#[test]
fn test_everything_no_spaces() {
- let output = parse_message_iter("<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online").collect::>();
-
- assert_eq!(output.len(), 4);
- assert_eq!(output[0], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
- assert_eq!(output[1], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
- assert_eq!(output[2], MessageToken::MentionEveryone);
- assert_eq!(output[3], MessageToken::MentionOnline);
+ let output = parse_message_iter(
+ "<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3><#01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online:01FD58YK5W7QRV5H3D64KTQYX3:",
+ )
+ .collect::>();
+
+ assert_eq!(output.len(), 6);
+ assert_eq!(
+ output[0],
+ MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
+ assert_eq!(
+ output[1],
+ MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
+ assert_eq!(
+ output[2],
+ MessageToken::ChannelMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
+ assert_eq!(output[3], MessageToken::MentionEveryone);
+ assert_eq!(output[4], MessageToken::MentionOnline);
+ assert_eq!(output[5], MessageToken::Emoji("01FD58YK5W7QRV5H3D64KTQYX3"));
}
#[test]
fn test_codeblock_no_mentions() {
- let output = parse_message_iter("```\n<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online\n```").collect::>();
+ let output = parse_message_iter(
+ "```\n<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3><#01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online:01FD58YK5W7QRV5H3D64KTQYX3:\n```",
+ )
+ .collect::>();
assert_eq!(output.len(), 2);
assert_eq!(output[0], MessageToken::CodeblockMarker(3));
@@ -169,19 +219,36 @@ mod tests {
#[test]
fn test_uncontained_codeblock_should_mention() {
- let output = parse_message_iter("```\n<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online").collect::>();
+ let output = parse_message_iter(
+ "```\n<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3><#01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online:01FD58YK5W7QRV5H3D64KTQYX3:",
+ )
+ .collect::>();
- assert_eq!(output.len(), 5);
+ assert_eq!(output.len(), 7);
assert_eq!(output[0], MessageToken::CodeblockMarker(3));
- assert_eq!(output[1], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
- assert_eq!(output[2], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
- assert_eq!(output[3], MessageToken::MentionEveryone);
- assert_eq!(output[4], MessageToken::MentionOnline);
+ assert_eq!(
+ output[1],
+ MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
+ assert_eq!(
+ output[2],
+ MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
+ assert_eq!(
+ output[3],
+ MessageToken::ChannelMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
+ assert_eq!(output[4], MessageToken::MentionEveryone);
+ assert_eq!(output[5], MessageToken::MentionOnline);
+ assert_eq!(output[6], MessageToken::Emoji("01FD58YK5W7QRV5H3D64KTQYX3"));
}
#[test]
fn test_inline_codeblock_no_mentions() {
- let output = parse_message_iter("`<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online`").collect::>();
+ let output = parse_message_iter(
+ "`<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3><#01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online:01FD58YK5W7QRV5H3D64KTQYX3:`",
+ )
+ .collect::>();
assert_eq!(output.len(), 2);
assert_eq!(output[0], MessageToken::CodeblockMarker(1));
@@ -190,19 +257,33 @@ mod tests {
#[test]
fn test_uncontained_inline_codeblock_should_mention() {
- let output = parse_message_iter("`<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online").collect::>();
+ let output = parse_message_iter(
+ "`<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3><#01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online:01FD58YK5W7QRV5H3D64KTQYX3:",
+ )
+ .collect::>();
- assert_eq!(output.len(), 5);
+ assert_eq!(output.len(), 7);
assert_eq!(output[0], MessageToken::CodeblockMarker(1));
- assert_eq!(output[1], MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3"));
- assert_eq!(output[2], MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3"));
- assert_eq!(output[3], MessageToken::MentionEveryone);
- assert_eq!(output[4], MessageToken::MentionOnline);
+ assert_eq!(
+ output[1],
+ MessageToken::UserMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
+ assert_eq!(
+ output[2],
+ MessageToken::RoleMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
+ assert_eq!(
+ output[3],
+ MessageToken::ChannelMention("01FD58YK5W7QRV5H3D64KTQYX3")
+ );
+ assert_eq!(output[4], MessageToken::MentionEveryone);
+ assert_eq!(output[5], MessageToken::MentionOnline);
+ assert_eq!(output[6], MessageToken::Emoji("01FD58YK5W7QRV5H3D64KTQYX3"));
}
#[test]
fn test_codeblock_with_language_no_mentions() {
- let output = parse_message_iter("```rust\n<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online```").collect::>();
+ let output = parse_message_iter("```rust\n<@01FD58YK5W7QRV5H3D64KTQYX3><%01FD58YK5W7QRV5H3D64KTQYX3><#01FD58YK5W7QRV5H3D64KTQYX3>@everyone@online:01FD58YK5W7QRV5H3D64KTQYX3:```").collect::>();
assert_eq!(output.len(), 2);
assert_eq!(output[0], MessageToken::CodeblockMarker(3));
@@ -220,7 +301,8 @@ mod tests {
#[test]
fn test_double_inline_codeblock_with_backticks_inside() {
- let output = parse_message_iter("``this `should` not `ping` @everyone``").collect::>();
+ let output =
+ parse_message_iter("``this `should` not `ping` @everyone``").collect::>();
assert_eq!(output.len(), 2);
assert_eq!(output[0], MessageToken::CodeblockMarker(2));
@@ -238,7 +320,8 @@ mod tests {
#[test]
fn test_escaped_codeblock() {
- let output = parse_message_iter("i am ~~not~~ pinging \\`@everyone` ok.").collect::>();
+ let output =
+ parse_message_iter("i am ~~not~~ pinging \\`@everyone` ok.").collect::>();
assert_eq!(output.len(), 3);
assert_eq!(output[0], MessageToken::Escape);
@@ -253,4 +336,4 @@ mod tests {
assert_eq!(output.len(), 1);
assert_eq!(output[0], MessageToken::Escape);
}
-}
\ No newline at end of file
+}
diff --git a/crates/core/permissions/Cargo.toml b/crates/core/permissions/Cargo.toml
index d5ee32165..03ed629d6 100644
--- a/crates/core/permissions/Cargo.toml
+++ b/crates/core/permissions/Cargo.toml
@@ -1,10 +1,11 @@
[package]
name = "revolt-permissions"
-version = "0.8.8"
+version = "0.13.7"
edition = "2021"
license = "MIT"
authors = ["Paul Makles "]
description = "Revolt Backend: Permission Logic"
+repository = "https://github.com/stoatchat/stoatchat"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -17,23 +18,23 @@ try-from-primitive = ["dep:num_enum"]
[dev-dependencies]
# Async
-async-std = { version = "1.8.0", features = ["attributes"] }
+async-std = { workspace = true, features = ["attributes"] }
[dependencies]
# Core
-revolt-result = { version = "0.8.8", path = "../result" }
+revolt-result = { workspace = true }
# Utility
-auto_ops = "0.3.0"
-once_cell = "1.17"
-num_enum = { version = "0.6.1", optional = true }
+auto_ops = { workspace = true }
+once_cell = { workspace = true }
+num_enum = { workspace = true, optional = true }
# Async
-async-trait = "0.1.51"
+async-trait = { workspace = true }
# Serialisation
-serde = { version = "1", features = ["derive"], optional = true }
-bson = { version = "2.1.0", optional = true }
+serde = { workspace = true, optional = true }
+bson = { workspace = true, optional = true }
# Spec Generation
-schemars = { version = "0.8.8", optional = true }
+schemars = { workspace = true, optional = true }
diff --git a/crates/core/permissions/src/impl.rs b/crates/core/permissions/src/impl.rs
index 9975eeda5..064fee3e6 100644
--- a/crates/core/permissions/src/impl.rs
+++ b/crates/core/permissions/src/impl.rs
@@ -61,6 +61,15 @@ pub async fn calculate_server_permissions(query: &mut P) ->
permissions.apply(role_override);
}
+ if !query.do_we_have_publish_overwrites().await {
+ permissions.revoke(ChannelPermission::Speak as u64);
+ permissions.revoke(ChannelPermission::Video as u64);
+ }
+
+ if !query.do_we_have_receive_overwrites().await {
+ permissions.revoke(ChannelPermission::Listen as u64);
+ }
+
if query.are_we_timed_out().await {
permissions.restrict(*ALLOW_IN_TIMEOUT);
}
diff --git a/crates/core/permissions/src/models/channel.rs b/crates/core/permissions/src/models/channel.rs
index aca22b5c2..fc6fe49a0 100644
--- a/crates/core/permissions/src/models/channel.rs
+++ b/crates/core/permissions/src/models/channel.rs
@@ -75,6 +75,8 @@ pub enum ChannelPermission {
Masquerade = 1 << 28,
/// React to messages with emojis
React = 1 << 29,
+ /// Bypass slowmode
+ BypassSlowmode = 1 << 39,
// * Voice permissions
/// Connect to a voice channel
@@ -89,6 +91,8 @@ pub enum ChannelPermission {
DeafenMembers = 1 << 34,
/// Move members between voice channels
MoveMembers = 1 << 35,
+ /// Listen to other users
+ Listen = 1 << 36,
// * Channel permissions two electric boogaloo
/// Mention everyone and online members
@@ -97,7 +101,7 @@ pub enum ChannelPermission {
MentionRoles = 1 << 38,
// * Misc. permissions
- // % Bits 38 to 52: free area
+ // % Bits 39 to 52: free area
// % Bits 53 to 64: do not use
// * Grant all permissions
@@ -130,14 +134,16 @@ pub static DEFAULT_PERMISSION: Lazy = Lazy::new(|| {
+ ChannelPermission::SendEmbeds
+ ChannelPermission::UploadFiles
+ ChannelPermission::Connect
- + ChannelPermission::Speak,
+ + ChannelPermission::Speak
+ + ChannelPermission::Listen
+ + ChannelPermission::Video
)
});
pub static DEFAULT_PERMISSION_SAVED_MESSAGES: u64 = ChannelPermission::GrantAllSafe as u64;
pub static DEFAULT_PERMISSION_DIRECT_MESSAGE: Lazy = Lazy::new(|| {
- DEFAULT_PERMISSION.add(ChannelPermission::ManageChannel + ChannelPermission::React)
+ DEFAULT_PERMISSION.add(ChannelPermission::ManageChannel + ChannelPermission::React + ChannelPermission::Masquerade)
});
pub static DEFAULT_PERMISSION_SERVER: Lazy = Lazy::new(|| {
diff --git a/crates/core/permissions/src/models/server.rs b/crates/core/permissions/src/models/server.rs
index e6355eedc..8391029a0 100644
--- a/crates/core/permissions/src/models/server.rs
+++ b/crates/core/permissions/src/models/server.rs
@@ -39,7 +39,7 @@ pub enum DataPermissionPoly {
permissions: u64,
},
Field {
- /// Allow / deny values to set for members in this `TextChannel` or `VoiceChannel`
+ /// Allow / deny values to set for members in this server channel
permissions: Override,
},
}
diff --git a/crates/core/permissions/src/test.rs b/crates/core/permissions/src/test.rs
index e4dadd485..93e1dea2c 100644
--- a/crates/core/permissions/src/test.rs
+++ b/crates/core/permissions/src/test.rs
@@ -64,6 +64,14 @@ async fn validate_user_permissions() {
unreachable!()
}
+ async fn do_we_have_publish_overwrites(&mut self) -> bool {
+ true
+ }
+
+ async fn do_we_have_receive_overwrites(&mut self) -> bool {
+ true
+ }
+
async fn get_channel_type(&mut self) -> ChannelType {
ChannelType::DirectMessage
}
@@ -153,6 +161,14 @@ async fn validate_group_permissions() {
unreachable!()
}
+ async fn do_we_have_publish_overwrites(&mut self) -> bool {
+ true
+ }
+
+ async fn do_we_have_receive_overwrites(&mut self) -> bool {
+ true
+ }
+
async fn get_channel_type(&mut self) -> ChannelType {
ChannelType::Group
}
@@ -254,6 +270,14 @@ async fn validate_server_permissions() {
false
}
+ async fn do_we_have_publish_overwrites(&mut self) -> bool {
+ true
+ }
+
+ async fn do_we_have_receive_overwrites(&mut self) -> bool {
+ true
+ }
+
async fn get_channel_type(&mut self) -> ChannelType {
ChannelType::ServerChannel
}
@@ -346,6 +370,14 @@ async fn validate_timed_out_member() {
true
}
+ async fn do_we_have_publish_overwrites(&mut self) -> bool {
+ true
+ }
+
+ async fn do_we_have_receive_overwrites(&mut self) -> bool {
+ true
+ }
+
async fn get_channel_type(&mut self) -> ChannelType {
ChannelType::ServerChannel
}
diff --git a/crates/core/permissions/src/trait.rs b/crates/core/permissions/src/trait.rs
index d4a7c3d36..96b42caf8 100644
--- a/crates/core/permissions/src/trait.rs
+++ b/crates/core/permissions/src/trait.rs
@@ -39,6 +39,12 @@ pub trait PermissionQuery {
/// Is our perspective user timed out on this server?
async fn are_we_timed_out(&mut self) -> bool;
+ /// Is the member muted?
+ async fn do_we_have_publish_overwrites(&mut self) -> bool;
+
+ /// Is the member deafend?
+ async fn do_we_have_receive_overwrites(&mut self) -> bool;
+
// * For calculating channel permission
/// Get the type of the channel
diff --git a/crates/core/presence/Cargo.toml b/crates/core/presence/Cargo.toml
index 2e53c3e70..7868342d3 100644
--- a/crates/core/presence/Cargo.toml
+++ b/crates/core/presence/Cargo.toml
@@ -1,10 +1,11 @@
[package]
name = "revolt-presence"
-version = "0.8.8"
+version = "0.13.7"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles "]
description = "Revolt Backend: User Presence"
+repository = "https://github.com/stoatchat/stoatchat"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -13,16 +14,16 @@ redis-is-patched = []
[dev-dependencies]
# Async
-async-std = { version = "1.8.0", features = ["attributes"] }
+async-std = { workspace = true, features = ["attributes"] }
# Config for loading Redis URI
-revolt-config = { version = "0.8.8", path = "../config" }
+revolt-config = { workspace = true }
[dependencies]
# Utility
-log = "0.4.17"
-rand = "0.8.5"
-once_cell = "1.17.1"
+log = { workspace = true }
+rand = { workspace = true }
+once_cell = { workspace = true }
# Redis
-redis-kiss = "0.1.4"
+redis-kiss = { workspace = true }
diff --git a/crates/core/ratelimits/Cargo.toml b/crates/core/ratelimits/Cargo.toml
new file mode 100644
index 000000000..e606d151f
--- /dev/null
+++ b/crates/core/ratelimits/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "revolt-ratelimits"
+version = "0.13.7"
+edition = "2024"
+license = "MIT"
+authors = ["Zomatree ", "Paul Makles "]
+description = "Revolt Backend: Ratelimit Handler"
+repository = "https://github.com/stoatchat/stoatchat"
+
+[features]
+rocket = [
+ "dep:rocket",
+ "dep:revolt_rocket_okapi",
+ "revolt-database/rocket-impl",
+]
+axum = ["dep:axum", "revolt-database/axum-impl"]
+
+default = ["rocket", "axum"]
+
+[dependencies]
+revolt-database = { workspace = true }
+revolt-result = { workspace = true }
+revolt-config = { workspace = true }
+
+rocket = { workspace = true, optional = true }
+revolt_rocket_okapi = { workspace = true, optional = true }
+
+axum = { workspace = true, optional = true, features = ["macros"] }
+
+serde = { workspace = true }
+authifier = { workspace = true }
+dashmap = { workspace = true }
+async-trait = { workspace = true }
+log = { workspace = true }
diff --git a/crates/core/ratelimits/src/axum.rs b/crates/core/ratelimits/src/axum.rs
new file mode 100644
index 000000000..f50cf8671
--- /dev/null
+++ b/crates/core/ratelimits/src/axum.rs
@@ -0,0 +1,194 @@
+use std::net::SocketAddr;
+
+use async_trait::async_trait;
+use axum::{
+ Json, RequestPartsExt, Router,
+ body::Body,
+ extract::{ConnectInfo, FromRef, FromRequestParts, State},
+ http::{HeaderValue, Request, StatusCode, request::Parts},
+ middleware::Next,
+ response::{IntoResponse, Response},
+ routing::get,
+};
+use revolt_database::{Database, User};
+use revolt_config::config;
+
+use crate::ratelimiter::{RatelimitInformation, Ratelimiter, RequestKind};
+
+#[derive(Clone, Copy)]
+pub struct AxumRequestKind;
+
+impl RequestKind for AxumRequestKind {
+ type R<'a> = Parts;
+}
+
+pub type RatelimitStorage = crate::ratelimiter::RatelimitStorage;
+
+fn to_ip(parts: &Parts) -> String {
+ parts
+ .extensions
+ .get::>()
+ .map(|info| info.ip().to_string())
+ .unwrap_or_default()
+}
+
+async fn to_real_ip(parts: &Parts) -> String {
+ if config().await.api.security.trust_cloudflare {
+ parts
+ .headers
+ .get("CF-Connecting-IP")
+ .map(|x| x.to_str().unwrap().to_string())
+ .unwrap_or_else(|| to_ip(parts))
+ } else {
+ to_ip(parts)
+ }
+}
+
+#[async_trait]
+impl FromRequestParts for Ratelimiter
+where
+ Database: FromRef,
+ RatelimitStorage: FromRef,
+{
+ type Rejection = Json;
+
+ async fn from_request_parts(parts: &mut Parts, state: &S) -> Result {
+ if parts
+ .extensions
+ .get::>>()
+ .is_none()
+ {
+ let storage = RatelimitStorage::from_ref(state);
+
+ let identifier = if let Ok(user) = parts.extract_with_state::(state).await {
+ user.id
+ } else {
+ to_real_ip(parts).await
+ };
+
+ let (bucket, resource) = storage.resolver.resolve_bucket(parts);
+ let limit = storage.resolver.resolve_bucket_limit(bucket);
+
+ let ratelimiter =
+ Ratelimiter::from(&storage.map, &identifier, limit, (bucket, resource));
+
+ parts.extensions.insert(ratelimiter.map_err(Json));
+ };
+
+ *parts
+ .extensions
+ .get::>>()
+ .unwrap()
+ }
+}
+
+#[async_trait]
+impl FromRequestParts for RatelimitInformation
+where
+ Database: FromRef,
+ RatelimitStorage: FromRef,
+{
+ type Rejection = Json;
+
+ async fn from_request_parts(parts: &mut Parts, state: &S) -> Result {
+ if parts
+ .extensions
+ .get::>>()
+ .is_none()
+ {
+ let ratelimiter = parts.extract_with_state::(state).await;
+
+ parts.extensions.insert(ratelimiter);
+ };
+
+ let ratelimiter = *parts
+ .extensions
+ .get::>>()
+ .unwrap();
+
+ match ratelimiter {
+ Ok(ratelimter) => Ok(RatelimitInformation::Success(ratelimter)),
+ Err(ratelimiter) => Err(Json(RatelimitInformation::Failure {
+ retry_after: ratelimiter.reset,
+ })),
+ }
+ }
+}
+
+pub async fn ratelimit_middleware(
+ State(database): State,
+ State(ratelimit_storage): State,
+ request: Request,
+ next: Next,
+) -> Response {
+ #[derive(axum::extract::FromRef)]
+ struct TempState {
+ database: Database,
+ ratelimit_storage: RatelimitStorage,
+ }
+
+ let state = TempState {
+ database,
+ ratelimit_storage,
+ };
+
+ let (mut parts, body) = request.into_parts();
+
+ let res = Ratelimiter::from_request_parts(&mut parts, &state).await;
+
+ let (Ok(ratelimiter) | Err(Json(ratelimiter))) = &res;
+
+ let mut response = if res.is_ok() {
+ let request = Request::from_parts(parts, body);
+
+ next.run(request).await
+ } else {
+ let ratelimit_info = RatelimitInformation::from_request_parts(&mut parts, &state).await;
+
+ ratelimit_info.map(Json).into_response()
+ };
+
+ let Ratelimiter {
+ key,
+ limit,
+ remaining,
+ reset,
+ } = ratelimiter;
+
+ let headers = response.headers_mut();
+
+ headers.insert(
+ "X-RateLimit-Limit",
+ HeaderValue::from_str(&limit.to_string()).unwrap(),
+ );
+ headers.insert(
+ "X-RateLimit-Bucket",
+ HeaderValue::from_str(&key.to_string()).unwrap(),
+ );
+ headers.insert(
+ "X-RateLimit-Remaining",
+ HeaderValue::from_str(&remaining.to_string()).unwrap(),
+ );
+ headers.insert(
+ "X-RateLimit-Reset-After",
+ HeaderValue::from_str(&reset.to_string()).unwrap(),
+ );
+
+ if res.is_err() {
+ *response.status_mut() = StatusCode::TOO_MANY_REQUESTS;
+ };
+
+ response
+}
+
+async fn ratelimit_info(info: RatelimitInformation) -> Json {
+ Json(info)
+}
+
+pub fn routes() -> Router
+where
+ Database: FromRef,
+ RatelimitStorage: FromRef,
+{
+ Router::new().route("/ratelimit", get(ratelimit_info))
+}
diff --git a/crates/core/ratelimits/src/lib.rs b/crates/core/ratelimits/src/lib.rs
new file mode 100644
index 000000000..5eaa81080
--- /dev/null
+++ b/crates/core/ratelimits/src/lib.rs
@@ -0,0 +1,7 @@
+pub mod ratelimiter;
+
+#[cfg(feature = "rocket")]
+pub mod rocket;
+
+#[cfg(feature = "axum")]
+pub mod axum;
diff --git a/crates/core/ratelimits/src/ratelimiter.rs b/crates/core/ratelimits/src/ratelimiter.rs
new file mode 100644
index 000000000..a232feb32
--- /dev/null
+++ b/crates/core/ratelimits/src/ratelimiter.rs
@@ -0,0 +1,145 @@
+use std::collections::hash_map::DefaultHasher;
+use std::hash::Hasher;
+use std::ops::Add;
+use std::sync::Arc;
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
+
+use serde::Serialize;
+
+use dashmap::DashMap;
+
+pub trait RequestKind {
+ type R<'a>;
+}
+
+pub trait RatelimitResolver: Send + Sync {
+ fn resolve_bucket<'a>(&self, request: &'a R) -> (&'a str, Option<&'a str>);
+ fn resolve_bucket_limit(&self, bucket: &str) -> u32;
+}
+
+#[derive(Clone)]
+pub struct RatelimitStorage {
+ pub resolver: Arc RatelimitResolver>>,
+ pub map: Arc>,
+}
+
+impl RatelimitStorage {
+ pub fn new RatelimitResolver> + 'static>(resolver: R) -> Self {
+ Self {
+ resolver: Arc::new(resolver),
+ map: Arc::new(DashMap::new()),
+ }
+ }
+}
+
+/// Ratelimit Bucket
+#[derive(Clone, Copy, Debug)]
+pub struct Entry {
+ used: u32,
+ reset: u128,
+}
+
+/// Get the current time from Unix Epoch as a Duration
+fn now() -> Duration {
+ SystemTime::now()
+ .duration_since(UNIX_EPOCH)
+ .expect("Time went backwards...")
+}
+
+impl Entry {
+ /// Find bucket by its key
+ pub fn from(map: &DashMap, key: u64) -> Entry {
+ map.get(&key).map(|x| *x).unwrap_or_else(|| Entry {
+ used: 0,
+ reset: now().add(Duration::from_secs(10)).as_millis(),
+ })
+ }
+
+ /// Deduct one unit from the bucket and save
+ pub fn deduct(&mut self) {
+ let current_time = now().as_millis();
+ if current_time > self.reset {
+ self.used = 1;
+ self.reset = now().add(Duration::from_secs(10)).as_millis();
+ } else {
+ self.used += 1;
+ }
+ }
+
+ /// Save information
+ pub fn save(self, map: &DashMap, key: u64) {
+ map.insert(key, self);
+ }
+
+ /// Get remaining units in the bucket
+ pub fn get_remaining(&self, limit: u32) -> u32 {
+ if now().as_millis() > self.reset {
+ limit
+ } else {
+ limit - self.used
+ }
+ }
+
+ /// Get how long bucket has until reset
+ pub fn left_until_reset(&self) -> u128 {
+ let current_time = now().as_millis();
+ self.reset.saturating_sub(current_time)
+ }
+}
+
+/// Ratelimit Guard
+#[derive(Serialize, Clone, Copy, Debug)]
+#[allow(dead_code)]
+pub struct Ratelimiter {
+ pub key: u64,
+ pub limit: u32,
+ pub remaining: u32,
+ pub reset: u128,
+}
+
+impl Ratelimiter {
+ /// Generate guard from identifier and target bucket
+ pub fn from(
+ map: &DashMap,
+ identifier: &str,
+ limit: u32,
+ (bucket, resource): (&str, Option<&str>),
+ ) -> Result {
+ let mut key = DefaultHasher::new();
+ key.write(identifier.as_bytes());
+ key.write(bucket.as_bytes());
+
+ if let Some(id) = resource {
+ key.write(id.as_bytes());
+ }
+
+ let key = key.finish();
+ let mut entry = Entry::from(map, key);
+
+ let remaining = entry.get_remaining(limit);
+ let reset = entry.left_until_reset();
+ let mut ratelimiter = Ratelimiter {
+ key,
+ limit,
+ remaining,
+ reset,
+ };
+ if remaining == 0 {
+ return Err(ratelimiter);
+ }
+
+ entry.deduct();
+ entry.save(map, key);
+ ratelimiter.remaining -= 1;
+ ratelimiter.reset = entry.left_until_reset();
+
+ Ok(ratelimiter)
+ }
+}
+
+#[derive(Serialize)]
+#[serde(untagged)]
+pub enum RatelimitInformation {
+ Success(Ratelimiter),
+ Failure { retry_after: u128 },
+}
diff --git a/crates/core/ratelimits/src/rocket.rs b/crates/core/ratelimits/src/rocket.rs
new file mode 100644
index 000000000..f790abb9f
--- /dev/null
+++ b/crates/core/ratelimits/src/rocket.rs
@@ -0,0 +1,163 @@
+use async_trait::async_trait;
+use log::info;
+use revolt_config::config;
+use rocket::fairing::{Fairing, Info, Kind};
+use rocket::http::uri::Origin;
+use rocket::http::{Method, Status};
+use rocket::request::{FromRequest, Outcome};
+use rocket::serde::json::Json;
+use rocket::{Data, Request, Response, State};
+
+use revolt_rocket_okapi::r#gen::OpenApiGenerator;
+use revolt_rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
+
+use authifier::models::Session;
+
+use crate::ratelimiter::RequestKind;
+use crate::ratelimiter::{RatelimitInformation, Ratelimiter};
+
+#[derive(Clone, Copy)]
+pub struct RocketRequestKind;
+
+impl RequestKind for RocketRequestKind {
+ type R<'a> = Request<'a>;
+}
+
+pub type RatelimitStorage = crate::ratelimiter::RatelimitStorage;
+
+/// Find the remote IP of the client
+fn to_ip(request: &'_ rocket::Request<'_>) -> String {
+ request
+ .client_ip()
+ .map(|r| r.to_string())
+ .unwrap_or_default()
+}
+
+/// Find the actual IP of the client
+async fn to_real_ip(request: &'_ rocket::Request<'_>) -> String {
+ if config().await.api.security.trust_cloudflare {
+ request
+ .headers()
+ .get_one("CF-Connecting-IP")
+ .map(|x| x.to_string())
+ .unwrap_or_else(|| to_ip(request))
+ } else {
+ to_ip(request)
+ }
+}
+
+#[async_trait]
+impl<'r> FromRequest<'r> for Ratelimiter {
+ type Error = Ratelimiter;
+
+ async fn from_request<'a>(request: &'r rocket::Request<'a>) -> Outcome {
+ let ratelimiter = request
+ .local_cache_async(async {
+ use rocket::outcome::Outcome;
+
+ let storage = request.guard::<&State>().await.unwrap();
+
+ let identifier = if let Outcome::Success(session) = request.guard::().await
+ {
+ session.id
+ } else {
+ to_real_ip(request).await
+ };
+
+ let (bucket, resource) = storage.resolver.resolve_bucket(request);
+ let limit = storage.resolver.resolve_bucket_limit(bucket);
+
+ Ratelimiter::from(&storage.map, &identifier, limit, (bucket, resource))
+ })
+ .await;
+
+ match ratelimiter {
+ Ok(ratelimiter) => Outcome::Success(*ratelimiter),
+ Err(ratelimiter) => Outcome::Error((Status::TooManyRequests, *ratelimiter)),
+ }
+ }
+}
+
+impl OpenApiFromRequest<'_> for Ratelimiter {
+ fn from_request_input(
+ _gen: &mut OpenApiGenerator,
+ _name: String,
+ _required: bool,
+ ) -> revolt_rocket_okapi::Result {
+ Ok(RequestHeaderInput::None)
+ }
+}
+
+/// Attach ratelimiter to the Rocket application
+pub struct RatelimitFairing;
+
+#[async_trait]
+impl Fairing for RatelimitFairing {
+ fn info(&self) -> Info {
+ Info {
+ name: "Ratelimiter",
+ kind: Kind::Request | Kind::Response,
+ }
+ }
+
+ async fn on_request(&self, request: &mut Request<'_>, _: &mut Data<'_>) {
+ use rocket::outcome::Outcome;
+ if let Outcome::Error(_) = request.guard::().await {
+ info!(
+ "User rate-limited on route {}! (IP = {:?})",
+ request.uri(),
+ to_real_ip(request).await
+ );
+
+ request.set_method(Method::Get);
+ request.set_uri(Origin::parse("/ratelimit").unwrap())
+ }
+ }
+
+ async fn on_response<'r>(&self, request: &'r Request<'_>, response: &mut Response<'r>) {
+ let guard = request.guard::().await;
+ let (Outcome::Success(ratelimiter) | Outcome::Error((_, ratelimiter))) = guard else {
+ unreachable!()
+ };
+ let Ratelimiter {
+ key,
+ limit,
+ remaining,
+ reset,
+ } = ratelimiter;
+
+ response.set_raw_header("X-RateLimit-Limit", limit.to_string());
+ response.set_raw_header("X-RateLimit-Bucket", key.to_string());
+ response.set_raw_header("X-RateLimit-Remaining", remaining.to_string());
+ response.set_raw_header("X-RateLimit-Reset-After", reset.to_string());
+
+ if guard.is_error() {
+ response.set_status(Status::TooManyRequests);
+ }
+ }
+}
+
+#[async_trait]
+impl<'r> FromRequest<'r> for RatelimitInformation {
+ type Error = u128;
+
+ async fn from_request(request: &'r rocket::Request<'_>) -> Outcome {
+ let info = match request.guard::().await {
+ Outcome::Success(ratelimiter) => RatelimitInformation::Success(ratelimiter),
+ Outcome::Error((_, ratelimiter)) => RatelimitInformation::Failure {
+ retry_after: ratelimiter.reset,
+ },
+ _ => unreachable!(),
+ };
+ Outcome::Success(info)
+ }
+}
+
+#[rocket::get("/ratelimit")]
+fn ratelimit_info(info: RatelimitInformation) -> Json {
+ Json(info)
+}
+
+pub fn routes() -> Vec {
+ rocket::routes![ratelimit_info]
+}
diff --git a/crates/core/result/Cargo.toml b/crates/core/result/Cargo.toml
index 3a25d206f..9cc5db3f7 100644
--- a/crates/core/result/Cargo.toml
+++ b/crates/core/result/Cargo.toml
@@ -1,10 +1,11 @@
[package]
name = "revolt-result"
-version = "0.8.8"
+version = "0.13.7"
edition = "2021"
license = "MIT"
authors = ["Paul Makles "]
description = "Revolt Backend: Result and Error types"
+repository = "https://github.com/stoatchat/stoatchat"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -15,22 +16,27 @@ utoipa = ["dep:utoipa"]
rocket = ["dep:rocket", "dep:serde_json"]
axum = ["dep:axum", "dep:serde_json"]
okapi = ["dep:revolt_rocket_okapi", "dep:revolt_okapi", "schemas"]
+sentry = ["dep:sentry"]
-default = ["serde"]
+default = ["serde", "sentry"]
[dependencies]
# Serialisation
-serde_json = { version = "1", optional = true }
-serde = { version = "1", features = ["derive"], optional = true }
+serde_json = { workspace = true, optional = true }
+serde = { workspace = true, optional = true }
# Spec Generation
-schemars = { version = "0.8.8", optional = true }
-utoipa = { version = "4.2.3", optional = true }
+schemars = { workspace = true, optional = true }
+utoipa = { workspace = true, optional = true }
# Rocket
-rocket = { optional = true, version = "0.5.0-rc.2", default-features = false }
-revolt_rocket_okapi = { version = "0.10.0", optional = true }
-revolt_okapi = { version = "0.9.1", optional = true }
+rocket = { workspace = true, optional = true }
+revolt_rocket_okapi = { workspace = true, optional = true }
+revolt_okapi = { workspace = true, optional = true }
+# utilities
+log = { workspace = true }
# Axum
-axum = { version = "0.7.5", optional = true }
+axum = { workspace = true, optional = true }
+
+sentry = { workspace = true, optional = true }
diff --git a/crates/core/result/src/axum.rs b/crates/core/result/src/axum.rs
index e4caf8183..440c4fc4b 100644
--- a/crates/core/result/src/axum.rs
+++ b/crates/core/result/src/axum.rs
@@ -24,6 +24,7 @@ impl IntoResponse for Error {
ErrorType::UnknownChannel => StatusCode::NOT_FOUND,
ErrorType::UnknownMessage => StatusCode::NOT_FOUND,
ErrorType::UnknownAttachment => StatusCode::BAD_REQUEST,
+ ErrorType::CannotDeleteMessage => StatusCode::FORBIDDEN,
ErrorType::CannotEditMessage => StatusCode::FORBIDDEN,
ErrorType::CannotJoinCall => StatusCode::BAD_REQUEST,
ErrorType::TooManyAttachments { .. } => StatusCode::BAD_REQUEST,
@@ -36,7 +37,9 @@ impl IntoResponse for Error {
ErrorType::NotInGroup => StatusCode::NOT_FOUND,
ErrorType::AlreadyPinned => StatusCode::BAD_REQUEST,
ErrorType::NotPinned => StatusCode::BAD_REQUEST,
+ ErrorType::InSlowmode { retry_after: _ } => StatusCode::TOO_MANY_REQUESTS,
+ ErrorType::CantCreateServers => StatusCode::FORBIDDEN,
ErrorType::UnknownServer => StatusCode::NOT_FOUND,
ErrorType::InvalidRole => StatusCode::NOT_FOUND,
ErrorType::Banned => StatusCode::FORBIDDEN,
@@ -62,6 +65,7 @@ impl IntoResponse for Error {
ErrorType::NotPrivileged => StatusCode::FORBIDDEN,
ErrorType::CannotGiveMissingPermissions => StatusCode::FORBIDDEN,
ErrorType::NotOwner => StatusCode::FORBIDDEN,
+ ErrorType::IsElevated => StatusCode::FORBIDDEN,
ErrorType::DatabaseError { .. } => StatusCode::INTERNAL_SERVER_ERROR,
ErrorType::InternalError => StatusCode::INTERNAL_SERVER_ERROR,
@@ -73,8 +77,13 @@ impl IntoResponse for Error {
ErrorType::DuplicateNonce => StatusCode::CONFLICT,
ErrorType::VosoUnavailable => StatusCode::BAD_REQUEST,
ErrorType::NotFound => StatusCode::NOT_FOUND,
- ErrorType::NoEffect => StatusCode::OK,
+ ErrorType::NoEffect => StatusCode::BAD_REQUEST,
ErrorType::FailedValidation { .. } => StatusCode::BAD_REQUEST,
+ ErrorType::LiveKitUnavailable => StatusCode::BAD_REQUEST,
+ ErrorType::NotConnected => StatusCode::BAD_REQUEST,
+ ErrorType::NotAVoiceChannel => StatusCode::BAD_REQUEST,
+ ErrorType::AlreadyConnected => StatusCode::BAD_REQUEST,
+ ErrorType::UnknownNode => StatusCode::BAD_REQUEST,
ErrorType::InvalidFlagValue => StatusCode::BAD_REQUEST,
ErrorType::FeatureDisabled { .. } => StatusCode::BAD_REQUEST,
diff --git a/crates/core/result/src/lib.rs b/crates/core/result/src/lib.rs
index 10b56d5d4..4124a0476 100644
--- a/crates/core/result/src/lib.rs
+++ b/crates/core/result/src/lib.rs
@@ -1,4 +1,5 @@
use std::fmt::Display;
+use std::panic::Location;
#[cfg(feature = "serde")]
#[macro_use]
@@ -77,6 +78,7 @@ pub enum ErrorType {
UnknownChannel,
UnknownAttachment,
UnknownMessage,
+ CannotDeleteMessage,
CannotEditMessage,
CannotJoinCall,
TooManyAttachments {
@@ -101,8 +103,12 @@ pub enum ErrorType {
NotInGroup,
AlreadyPinned,
NotPinned,
+ InSlowmode {
+ retry_after: u64,
+ },
// ? Server related errors
+ CantCreateServers,
UnknownServer,
InvalidRole,
Banned,
@@ -138,6 +144,7 @@ pub enum ErrorType {
NotPrivileged,
CannotGiveMissingPermissions,
NotOwner,
+ IsElevated,
// ? General errors
DatabaseError {
@@ -158,6 +165,12 @@ pub enum ErrorType {
error: String,
},
+ // ? Voice errors
+ LiveKitUnavailable,
+ NotAVoiceChannel,
+ AlreadyConnected,
+ NotConnected,
+ UnknownNode,
// ? Micro-service errors
ProxyError,
FileTooSmall,
@@ -197,6 +210,58 @@ macro_rules! create_database_error {
};
}
+#[macro_export]
+#[cfg(debug_assertions)]
+macro_rules! query {
+ ( $self: ident, $type: ident, $collection: expr, $($rest:expr),+ ) => {
+ Ok($self.$type($collection, $($rest),+).await.unwrap())
+ };
+}
+
+#[macro_export]
+#[cfg(not(debug_assertions))]
+macro_rules! query {
+ ( $self: ident, $type: ident, $collection: expr, $($rest:expr),+ ) => {
+ $self.$type($collection, $($rest),+).await
+ .map_err(|_| create_database_error!(stringify!($type), $collection))
+ };
+}
+
+pub trait ToRevoltError {
+ #[track_caller]
+ fn to_internal_error(self) -> Result;
+}
+
+impl ToRevoltError for Result {
+ #[track_caller]
+ fn to_internal_error(self) -> Result {
+ let loc = Location::caller();
+
+ self.map_err(|e| {
+ log::error!("{e:?}");
+ #[cfg(feature = "sentry")]
+ sentry::capture_error(&e);
+
+ Error {
+ error_type: ErrorType::InternalError,
+ location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column()),
+ }
+ })
+ }
+}
+
+impl ToRevoltError for Option {
+ #[track_caller]
+ fn to_internal_error(self) -> Result {
+ let loc = Location::caller();
+
+ self.ok_or_else(|| Error {
+ error_type: ErrorType::InternalError,
+ location: format!("{}:{}:{}", loc.file(), loc.line(), loc.column()),
+ })
+ }
+}
+
#[cfg(test)]
mod tests {
use crate::ErrorType;
diff --git a/crates/core/result/src/rocket.rs b/crates/core/result/src/rocket.rs
index 1d996a173..76719149a 100644
--- a/crates/core/result/src/rocket.rs
+++ b/crates/core/result/src/rocket.rs
@@ -30,6 +30,7 @@ impl<'r> Responder<'r, 'static> for Error {
ErrorType::UnknownChannel => Status::NotFound,
ErrorType::UnknownMessage => Status::NotFound,
ErrorType::UnknownAttachment => Status::BadRequest,
+ ErrorType::CannotDeleteMessage => Status::Forbidden,
ErrorType::CannotEditMessage => Status::Forbidden,
ErrorType::CannotJoinCall => Status::BadRequest,
ErrorType::TooManyAttachments { .. } => Status::BadRequest,
@@ -42,8 +43,10 @@ impl<'r> Responder<'r, 'static> for Error {
ErrorType::NotInGroup => Status::NotFound,
ErrorType::AlreadyPinned => Status::BadRequest,
ErrorType::NotPinned => Status::BadRequest,
+ ErrorType::InSlowmode { retry_after: _ } => Status::TooManyRequests,
ErrorType::InvalidFlagValue => Status::BadRequest,
+ ErrorType::CantCreateServers => Status::Forbidden,
ErrorType::UnknownServer => Status::NotFound,
ErrorType::InvalidRole => Status::NotFound,
ErrorType::Banned => Status::Forbidden,
@@ -69,6 +72,7 @@ impl<'r> Responder<'r, 'static> for Error {
ErrorType::NotPrivileged => Status::Forbidden,
ErrorType::CannotGiveMissingPermissions => Status::Forbidden,
ErrorType::NotOwner => Status::Forbidden,
+ ErrorType::IsElevated => Status::Forbidden,
ErrorType::DatabaseError { .. } => Status::InternalServerError,
ErrorType::InternalError => Status::InternalServerError,
@@ -78,10 +82,14 @@ impl<'r> Responder<'r, 'static> for Error {
ErrorType::InvalidSession => Status::Unauthorized,
ErrorType::NotAuthenticated => Status::Unauthorized,
ErrorType::DuplicateNonce => Status::Conflict,
- ErrorType::VosoUnavailable => Status::BadRequest,
ErrorType::NotFound => Status::NotFound,
- ErrorType::NoEffect => Status::Ok,
+ ErrorType::NoEffect => Status::BadRequest,
ErrorType::FailedValidation { .. } => Status::BadRequest,
+ ErrorType::LiveKitUnavailable => Status::BadRequest,
+ ErrorType::NotAVoiceChannel => Status::BadRequest,
+ ErrorType::AlreadyConnected => Status::BadRequest,
+ ErrorType::NotConnected => Status::BadRequest,
+ ErrorType::UnknownNode => Status::BadRequest,
ErrorType::FeatureDisabled { .. } => Status::BadRequest,
ErrorType::ProxyError => Status::BadRequest,
@@ -90,6 +98,7 @@ impl<'r> Responder<'r, 'static> for Error {
ErrorType::FileTypeNotAllowed => Status::BadRequest,
ErrorType::ImageProcessingFailed => Status::InternalServerError,
ErrorType::NoEmbedData => Status::BadRequest,
+ ErrorType::VosoUnavailable => Status::BadRequest,
};
// Serialize the error data structure into JSON.
diff --git a/crates/daemons/crond/Cargo.toml b/crates/daemons/crond/Cargo.toml
index 2c55f2f76..bb2f6ced7 100644
--- a/crates/daemons/crond/Cargo.toml
+++ b/crates/daemons/crond/Cargo.toml
@@ -1,22 +1,37 @@
[package]
name = "revolt-crond"
-version = "0.8.8"
+version = "0.13.7"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles "]
edition = "2021"
description = "Revolt Daemon Service: Timed data clean up tasks"
+publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# Utility
-log = "0.4"
+log = { workspace = true }
# Async
-tokio = { version = "1" }
+tokio = { workspace = true }
+
+# Redis
+redis-kiss = { workspace = true }
+
+# RabbitMQ
+lapin = { workspace = true }
+futures-lite = { workspace = true }
+
+# Processing
+serde_json = { workspace = true }
+revolt_optional_struct = { workspace = true }
+serde = { workspace = true }
+iso8601-timestamp = { workspace = true, features = ["serde", "bson"] }
# Core
-revolt-database = { version = "0.8.8", path = "../../core/database" }
-revolt-result = { version = "0.8.8", path = "../../core/result" }
-revolt-config = { version = "0.8.8", path = "../../core/config" }
-revolt-files = { version = "0.8.8", path = "../../core/files" }
+revolt-database = { workspace = true }
+revolt-result = { workspace = true }
+revolt-config = { workspace = true }
+revolt-files = { workspace = true }
+revolt-permissions = { workspace = true }
diff --git a/crates/daemons/crond/Dockerfile b/crates/daemons/crond/Dockerfile
index 8a4c915a9..f18524573 100644
--- a/crates/daemons/crond/Dockerfile
+++ b/crates/daemons/crond/Dockerfile
@@ -1,10 +1,11 @@
# Build Stage
-FROM ghcr.io/revoltchat/base:latest AS builder
+FROM ghcr.io/stoatchat/base:latest AS builder
FROM debian:12 AS debian
# Bundle Stage
FROM gcr.io/distroless/cc-debian12:nonroot
COPY --from=builder /home/rust/src/target/release/revolt-crond ./
+COPY --from=debian /usr/bin/uname /usr/bin/uname
USER nonroot
CMD ["./revolt-crond"]
\ No newline at end of file
diff --git a/crates/daemons/crond/src/main.rs b/crates/daemons/crond/src/main.rs
index b232a10a6..2b9a82f65 100644
--- a/crates/daemons/crond/src/main.rs
+++ b/crates/daemons/crond/src/main.rs
@@ -1,7 +1,7 @@
use revolt_config::configure;
-use revolt_database::DatabaseInfo;
+use revolt_database::{DatabaseInfo, AMQP};
use revolt_result::Result;
-use tasks::{file_deletion, prune_dangling_files, prune_members};
+use tasks::{acks, file_deletion, prune_dangling_files, prune_members};
use tokio::try_join;
pub mod tasks;
@@ -11,10 +11,13 @@ async fn main() -> Result<()> {
configure!(crond);
let db = DatabaseInfo::Auto.connect().await.expect("database");
+ let amqp = AMQP::new_auto().await;
+
try_join!(
file_deletion::task(db.clone()),
prune_dangling_files::task(db.clone()),
- prune_members::task(db.clone())
+ prune_members::task(db.clone()),
+ acks::task(db.clone(), amqp.clone()),
)
.map(|_| ())
}
diff --git a/crates/daemons/crond/src/tasks/acks.rs b/crates/daemons/crond/src/tasks/acks.rs
new file mode 100644
index 000000000..7576bac9b
--- /dev/null
+++ b/crates/daemons/crond/src/tasks/acks.rs
@@ -0,0 +1,164 @@
+use futures_lite::stream::StreamExt;
+use lapin::{
+ options::*,
+ types::FieldTable,
+ uri::{AMQPAuthority, AMQPQueryString, AMQPUri, AMQPUserInfo},
+ ConnectionBuilder, ConnectionProperties, ExchangeKind,
+};
+use log::{debug, info};
+use redis_kiss::{get_connection, AsyncCommands, Conn as RedisConnection};
+use revolt_config::config;
+use revolt_database::{events::rabbit::AckEventPayload, Database, AMQP};
+use revolt_result::{Result, ToRevoltError};
+use serde_json;
+
+pub async fn task(db: Database, amqp: AMQP) -> Result<()> {
+ let config = config().await;
+
+ let mut redis = get_connection()
+ .await
+ .expect("Failed to get redis connection");
+
+ let uri = AMQPUri {
+ scheme: lapin::uri::AMQPScheme::AMQP,
+ authority: AMQPAuthority {
+ userinfo: AMQPUserInfo {
+ username: config.rabbit.username,
+ password: config.rabbit.password,
+ },
+ host: config.rabbit.host,
+ port: config.rabbit.port,
+ },
+ vhost: "/".to_string(),
+ query: AMQPQueryString::default(),
+ };
+
+ let connection = ConnectionBuilder::new()
+ .expect("Builder")
+ .with_uri(uri)
+ .with_properties(ConnectionProperties::default())
+ .connect()
+ .await
+ .expect("Failed to connect to rabbitmq");
+
+ let reader_channel = connection
+ .create_channel()
+ .await
+ .expect("Failed to create channel");
+
+ reader_channel
+ .exchange_declare(
+ config.rabbit.default_exchange.clone().into(),
+ ExchangeKind::Topic,
+ ExchangeDeclareOptions {
+ durable: true,
+ ..Default::default()
+ },
+ FieldTable::default(),
+ )
+ .await
+ .expect("Failed to declare exchange");
+
+ reader_channel
+ .queue_declare(
+ config.rabbit.queues.acks.clone().into(),
+ QueueDeclareOptions {
+ durable: true,
+ ..Default::default()
+ },
+ FieldTable::default(),
+ )
+ .await
+ .expect("Failed to bind queue");
+
+ reader_channel
+ .queue_bind(
+ config.rabbit.queues.acks.clone().into(),
+ config.rabbit.default_exchange.into(),
+ config.rabbit.queues.acks.clone().into(),
+ QueueBindOptions::default(),
+ FieldTable::default(),
+ )
+ .await
+ .expect("Failed to bind channel");
+
+ let mut consumer = reader_channel
+ .basic_consume(
+ config.rabbit.queues.acks.into(),
+ "crond-ack-consumer".into(),
+ BasicConsumeOptions::default(),
+ FieldTable::default(),
+ )
+ .await
+ .expect("Failed to create consumer");
+
+ while let Some(delivery) = consumer.next().await {
+ if let Ok(delivery) = delivery {
+ let payload = serde_json::from_slice::(&delivery.data);
+
+ if let Ok(payload) = payload {
+ debug!("Received ack event: {payload:?}");
+
+ if let Err(e) = process_channel_ack(
+ &db,
+ &amqp,
+ payload.user_id,
+ payload.channel_id.unwrap(),
+ &mut redis,
+ )
+ .await
+ {
+ revolt_config::capture_error(&e);
+ _ = delivery.reject(BasicRejectOptions { requeue: false }).await;
+ } else {
+ _ = delivery.ack(BasicAckOptions { multiple: false }).await;
+ }
+ } else {
+ revolt_config::capture_message(
+ format!("Failed to decode ack data: {:?}", delivery.data).as_str(),
+ revolt_config::Level::Error,
+ );
+ }
+ }
+ }
+ Ok(())
+}
+
+#[allow(clippy::disallowed_methods)]
+async fn process_channel_ack(
+ db: &Database,
+ amqp: &AMQP,
+ user: String,
+ channel: String,
+ redis: &mut RedisConnection,
+) -> Result<()> {
+ let message_id: Option = redis
+ .get_del(format!("acker:{user}+{channel}"))
+ .await
+ .to_internal_error()?;
+
+ if let Some(message_id) = message_id {
+ let unread = db.fetch_unread(&user, &channel).await?;
+ let updated = db.acknowledge_message(&channel, &user, &message_id).await?;
+
+ info!("Set new state for ack: {}:{}:{}", channel, user, message_id);
+
+ if let (Some(before), Some(after)) = (unread, updated) {
+ let before_mentions = before.mentions.unwrap_or_default().len();
+ let after_mentions = after.mentions.unwrap_or_default().len();
+
+ if after_mentions < before_mentions {
+ if let Err(err) = amqp
+ .ack_notification_message(user.to_string(), channel.to_string(), message_id)
+ .await
+ {
+ revolt_config::capture_error(&err);
+ }
+ };
+ }
+
+ Ok(())
+ } else {
+ Err(message_id.to_internal_error().expect_err("no err"))
+ }
+}
diff --git a/crates/daemons/crond/src/tasks/file_deletion.rs b/crates/daemons/crond/src/tasks/file_deletion.rs
index 912173b3f..42942bf35 100644
--- a/crates/daemons/crond/src/tasks/file_deletion.rs
+++ b/crates/daemons/crond/src/tasks/file_deletion.rs
@@ -11,22 +11,24 @@ pub async fn task(db: Database) -> Result<()> {
let files = db.fetch_deleted_attachments().await?;
for file in files {
- let count = db
- .count_file_hash_references(file.hash.as_ref().expect("no `hash` present"))
- .await?;
-
- // No other files reference this file on disk anymore
- if count <= 1 {
- let file_hash = db
- .fetch_attachment_hash(file.hash.as_ref().expect("no `hash` present"))
+ if let Some(hash) = &file.hash {
+ let count = db
+ .count_file_hash_references(hash)
.await?;
- // Delete from S3
- delete_from_s3(&file_hash.bucket_id, &file_hash.path).await?;
+ // No other files reference this file on disk anymore
+ if count <= 1 {
+ let file_hash = db
+ .fetch_attachment_hash(hash)
+ .await?;
- // Delete the hash
- db.delete_attachment_hash(&file_hash.id).await?;
- info!("Deleted file hash {}", file_hash.id);
+ // Delete from S3
+ delete_from_s3(&file_hash.bucket_id, &file_hash.path).await?;
+
+ // Delete the hash
+ db.delete_attachment_hash(&file_hash.id).await?;
+ info!("Deleted file hash {}", file_hash.id);
+ }
}
// Delete the file
diff --git a/crates/daemons/crond/src/tasks/mod.rs b/crates/daemons/crond/src/tasks/mod.rs
index 060f55d89..a7dd9040c 100644
--- a/crates/daemons/crond/src/tasks/mod.rs
+++ b/crates/daemons/crond/src/tasks/mod.rs
@@ -1,3 +1,4 @@
+pub mod acks;
pub mod file_deletion;
pub mod prune_dangling_files;
pub mod prune_members;
diff --git a/crates/daemons/pushd/Cargo.toml b/crates/daemons/pushd/Cargo.toml
index 40a280081..9f38020ef 100644
--- a/crates/daemons/pushd/Cargo.toml
+++ b/crates/daemons/pushd/Cargo.toml
@@ -1,42 +1,40 @@
[package]
name = "revolt-pushd"
-version = "0.8.8"
+version = "0.13.7"
edition = "2021"
license = "AGPL-3.0-or-later"
+publish = false
[dependencies]
-revolt-result = { version = "0.8.8", path = "../../core/result" }
-revolt-config = { version = "0.8.8", path = "../../core/config", features = [
- "report-macros",
- "anyhow"
-] }
-revolt-database = { version = "0.8.8", path = "../../core/database" }
-revolt-models = { version = "0.8.8", path = "../../core/models", features = [
- "validator",
-] }
-revolt-presence = { version = "0.8.8", path = "../../core/presence", features = [
- "redis-is-patched",
-] }
+revolt-result = { workspace = true }
+revolt-config = { workspace = true, features = ["report-macros", "anyhow"] }
+revolt-database = { workspace = true }
+revolt-models = { workspace = true, features = ["validator"] }
+revolt-presence = { workspace = true, features = ["redis-is-patched"] }
+revolt-parser = { workspace = true }
-anyhow = { version = "1.0.98" }
+anyhow = { workspace = true }
-amqprs = { version = "1.7.0" }
-fcm_v1 = "0.3.0"
-web-push = "0.10.0"
-isahc = { optional = true, version = "1.7", features = ["json"] }
-revolt_a2 = { version = "0.10", default-features = false, features = ["ring"] }
-tokio = "1.39.2"
-async-trait = "0.1.81"
-ulid = "1.0.0"
+lapin = { workspace = true }
+fcm_v1 = { workspace = true }
+web-push = { workspace = true }
+isahc = { workspace = true, features = ["json"], optional = true }
+revolt_a2 = { workspace = true, features = ["ring"] }
+redis-kiss = { workspace = true }
+tokio = { workspace = true }
+async-trait = { workspace = true }
+ulid = { workspace = true }
-authifier = "1.0.15"
+authifier = { workspace = true }
-log = "0.4.11"
-pretty_env_logger = "0.4.0"
+log = { workspace = true }
+pretty_env_logger = { workspace = true }
+
+regex = { workspace = true }
#serialization
-serde_json = "1"
-revolt_optional_struct = "0.2.0"
-serde = { version = "1", features = ["derive"] }
-iso8601-timestamp = { version = "0.2.10", features = ["serde", "bson"] }
-base64 = "0.22.1"
+serde_json = { workspace = true }
+revolt_optional_struct = { workspace = true }
+serde = { workspace = true }
+iso8601-timestamp = { workspace = true, features = ["serde", "bson"] }
+base64 = { workspace = true }
diff --git a/crates/daemons/pushd/Dockerfile b/crates/daemons/pushd/Dockerfile
index 8123002a0..38bfbc0ee 100644
--- a/crates/daemons/pushd/Dockerfile
+++ b/crates/daemons/pushd/Dockerfile
@@ -1,5 +1,5 @@
# Build Stage
-FROM ghcr.io/revoltchat/base:latest AS builder
+FROM ghcr.io/stoatchat/base:latest AS builder
FROM debian:12 AS debian
# Bundle Stage
diff --git a/crates/daemons/pushd/src/consumers/inbound/ack.rs b/crates/daemons/pushd/src/consumers/inbound/ack.rs
index 77dd5d444..77df8b272 100644
--- a/crates/daemons/pushd/src/consumers/inbound/ack.rs
+++ b/crates/daemons/pushd/src/consumers/inbound/ack.rs
@@ -1,96 +1,69 @@
-use crate::consumers::inbound::internal::*;
-use amqprs::{
- channel::{BasicPublishArguments, Channel},
- connection::Connection,
- consumer::AsyncConsumer,
- BasicProperties, Deliver,
-};
+use std::sync::Arc;
+
+use crate::utils::Consumer;
+use anyhow::Result;
use async_trait::async_trait;
+use lapin::{message::Delivery, Channel, Connection};
use revolt_database::{events::rabbit::*, Database};
+#[derive(Clone)]
+#[allow(unused)]
pub struct AckConsumer {
- #[allow(dead_code)]
db: Database,
authifier_db: authifier::Database,
- conn: Option,
- channel: Option,
-}
-
-impl Channeled for AckConsumer {
- fn get_connection(&self) -> Option<&Connection> {
- if self.conn.is_none() {
- None
- } else {
- Some(self.conn.as_ref().unwrap())
- }
- }
-
- fn get_channel(&self) -> Option<&Channel> {
- if self.channel.is_none() {
- None
- } else {
- Some(self.channel.as_ref().unwrap())
- }
- }
-
- fn set_connection(&mut self, conn: Connection) {
- self.conn = Some(conn);
- }
-
- fn set_channel(&mut self, channel: Channel) {
- self.channel = Some(channel)
- }
+ connection: Arc,
+ channel: Arc,
}
-impl AckConsumer {
- pub fn new(db: Database, authifier_db: authifier::Database) -> AckConsumer {
- AckConsumer {
+#[async_trait]
+impl Consumer for AckConsumer {
+ async fn create(
+ db: Database,
+ authifier_db: authifier::Database,
+ connection: Arc,
+ channel: Arc,
+ ) -> Self {
+ Self {
db,
authifier_db,
- conn: None,
- channel: None,
+ connection,
+ channel,
}
}
-}
-#[allow(unused_variables)]
-#[async_trait]
-impl AsyncConsumer for AckConsumer {
+ fn channel(&self) -> &Arc {
+ &self.channel
+ }
+
/// This consumer processes all acks the platform receives, and sends relevant badge updates to apple platforms.
- async fn consume(
- &mut self,
- channel: &Channel,
- deliver: Deliver,
- basic_properties: BasicProperties,
- content: Vec,
- ) {
- let content = String::from_utf8(content).unwrap();
- let payload: AckPayload = serde_json::from_str(content.as_str()).unwrap();
+ async fn consume(&self, delivery: Delivery) -> Result<()> {
+ let payload: AckPayload = serde_json::from_slice(&delivery.data)?;
// Step 1: fetch unreads and don't continue if there's no unreads
- #[allow(clippy::disallowed_methods)]
- let unreads = self.db.fetch_unread_mentions(&payload.user_id).await;
+ // #[allow(clippy::disallowed_methods)]
debug!("Processing unreads for {:}", &payload.user_id);
- if let Ok(u) = &unreads {
+ let unreads = if let Ok(u) = self.db.fetch_unread_mentions(&payload.user_id).await {
if u.is_empty() {
debug!(
"Discarding unread task (no mentions found) for {:}",
&payload.user_id
);
- return;
- }
+ return Ok(());
+ };
+
+ u
} else {
- return;
- }
+ return Ok(());
+ };
if let Ok(sessions) = self.authifier_db.find_sessions(&payload.user_id).await {
let config = revolt_config::config().await;
// Step 2: find any apple sessions, since we don't need to calculate this for anything else.
// If there's no apple sessions, we can return early
- let apple_sessions: Vec<&authifier::models::Session> = sessions
- .iter()
+ let mut apple_sessions = sessions
+ .into_iter()
.filter(|session| {
if let Some(sub) = &session.subscription {
sub.endpoint == "apn"
@@ -98,19 +71,19 @@ impl AsyncConsumer for AckConsumer {
false
}
})
- .collect();
+ .peekable();
- if apple_sessions.is_empty() {
+ if apple_sessions.peek().is_none() {
debug!(
"Discarding unread task (no apn sessions found) for {:}",
&payload.user_id
);
- return;
+ return Ok(());
}
// Step 3: calculate the actual mention count, since we have to send it out
let mut mention_count = 0;
- for u in &unreads.unwrap() {
+ for u in &unreads {
mention_count += u.mentions.as_ref().unwrap().len()
}
@@ -123,26 +96,22 @@ impl AsyncConsumer for AckConsumer {
token: session.subscription.as_ref().unwrap().auth.clone(),
extras: Default::default(),
};
- let raw_service_payload = serde_json::to_string(&service_payload);
-
- if let Ok(p) = raw_service_payload {
- let args = BasicPublishArguments::new(
- config.pushd.exchange.as_str(),
- config.pushd.apn.queue.as_str(),
- )
- .finish();
-
- log::debug!(
- "Publishing ack to apn session {}",
- session.subscription.as_ref().unwrap().auth
- );
-
- publish_message(self, p.into(), args).await;
- } else {
- log::warn!("Failed to serialize ack badge update payload!");
- revolt_config::capture_error(&raw_service_payload.unwrap_err());
- }
+ let payload = serde_json::to_string(&service_payload)?;
+
+ log::debug!(
+ "Publishing ack to apn session {}",
+ session.subscription.as_ref().unwrap().auth
+ );
+
+ self.publish_message(
+ payload.as_bytes(),
+ &config.pushd.exchange,
+ &config.pushd.apn.queue,
+ )
+ .await?;
}
}
+
+ Ok(())
}
}
diff --git a/crates/daemons/pushd/src/consumers/inbound/dm_call.rs b/crates/daemons/pushd/src/consumers/inbound/dm_call.rs
new file mode 100644
index 000000000..3175d8d8f
--- /dev/null
+++ b/crates/daemons/pushd/src/consumers/inbound/dm_call.rs
@@ -0,0 +1,112 @@
+use std::{collections::HashMap, sync::Arc};
+
+use crate::utils::Consumer;
+use anyhow::Result;
+use async_trait::async_trait;
+use lapin::{message::Delivery, Channel, Connection};
+use log::debug;
+use revolt_database::{events::rabbit::*, Database};
+
+#[derive(Clone)]
+#[allow(unused)]
+pub struct DmCallConsumer {
+ db: Database,
+ authifier_db: authifier::Database,
+ connection: Arc,
+ channel: Arc,
+}
+
+#[async_trait]
+impl Consumer for DmCallConsumer {
+ async fn create(
+ db: Database,
+ authifier_db: authifier::Database,
+ connection: Arc,
+ channel: Arc,
+ ) -> Self {
+ Self {
+ db,
+ authifier_db,
+ connection,
+ channel,
+ }
+ }
+
+ fn channel(&self) -> &Arc {
+ &self.channel
+ }
+
+ /// This consumer handles delegating messages into their respective platform queues.
+ async fn consume(&self, delivery: Delivery) -> Result<()> {
+ let _p: InternalDmCallPayload = serde_json::from_slice(&delivery.data)?;
+ let payload = _p.payload;
+
+ debug!("Received dm call start/stop event");
+
+ let (revolt_database::Channel::DirectMessage { recipients, .. }
+ | revolt_database::Channel::Group { recipients, .. }) =
+ self.db.fetch_channel(&payload.channel_id).await?
+ else {
+ warn!(
+ "Discarding dm call start/stop event for non-dm/group channel {}",
+ payload.channel_id
+ );
+
+ return Ok(());
+ };
+
+ let call_recipients = if let Some(user_recipients) = _p.recipients {
+ user_recipients
+ .into_iter()
+ .filter(|user_id| recipients.contains(user_id) && user_id != &payload.initiator_id)
+ .collect()
+ } else {
+ recipients
+ .into_iter()
+ .filter(|user_id| user_id != &payload.initiator_id)
+ .collect::>()
+ };
+
+ let config = revolt_config::config().await;
+
+ for user_id in call_recipients {
+ if let Ok(sessions) = self.authifier_db.find_sessions(&user_id).await {
+ for session in sessions {
+ if let Some(sub) = session.subscription {
+ let mut sendable = PayloadToService {
+ notification: PayloadKind::DmCallStartEnd(payload.clone()),
+ token: sub.auth,
+ user_id: session.user_id,
+ session_id: session.id,
+ extras: HashMap::new(),
+ };
+
+ let routing_key = match sub.endpoint.as_str() {
+ "apn" => &config.pushd.apn.queue,
+ "fcm" => &config.pushd.fcm.queue,
+ endpoint => {
+ sendable.extras.insert("p256dh".to_string(), sub.p256dh);
+ sendable
+ .extras
+ .insert("endpoint".to_string(), endpoint.to_string());
+
+ &config.pushd.vapid.queue
+ }
+ };
+
+ let payload = serde_json::to_string(&sendable)?;
+
+ self.publish_message(
+ payload.as_bytes(),
+ &config.pushd.exchange,
+ routing_key,
+ )
+ .await?;
+ }
+ }
+ }
+ }
+
+ Ok(())
+ }
+}
diff --git a/crates/daemons/pushd/src/consumers/inbound/fr_accepted.rs b/crates/daemons/pushd/src/consumers/inbound/fr_accepted.rs
index d525138c5..bcfa1c2f0 100644
--- a/crates/daemons/pushd/src/consumers/inbound/fr_accepted.rs
+++ b/crates/daemons/pushd/src/consumers/inbound/fr_accepted.rs
@@ -1,70 +1,44 @@
-use std::collections::HashMap;
+use std::{collections::HashMap, sync::Arc};
-use crate::consumers::inbound::internal::*;
-use amqprs::{
- channel::{BasicPublishArguments, Channel},
- connection::Connection,
- consumer::AsyncConsumer,
- BasicProperties, Deliver,
-};
+use crate::utils::Consumer;
use anyhow::Result;
use async_trait::async_trait;
+use lapin::{message::Delivery, Channel, Connection};
use log::debug;
use revolt_database::{events::rabbit::*, Database};
+#[derive(Clone)]
+#[allow(unused)]
pub struct FRAcceptedConsumer {
- #[allow(dead_code)]
db: Database,
authifier_db: authifier::Database,
- conn: Option,
- channel: Option,
+ connection: Arc,
+ channel: Arc,
}
-impl Channeled for FRAcceptedConsumer {
- fn get_connection(&self) -> Option<&Connection> {
- if self.conn.is_none() {
- None
- } else {
- Some(self.conn.as_ref().unwrap())
- }
- }
-
- fn get_channel(&self) -> Option<&Channel> {
- if self.channel.is_none() {
- None
- } else {
- Some(self.channel.as_ref().unwrap())
- }
- }
-
- fn set_connection(&mut self, conn: Connection) {
- self.conn = Some(conn);
- }
-
- fn set_channel(&mut self, channel: Channel) {
- self.channel = Some(channel)
- }
-}
-
-impl FRAcceptedConsumer {
- pub fn new(db: Database, authifier_db: authifier::Database) -> FRAcceptedConsumer {
- FRAcceptedConsumer {
+#[async_trait]
+impl Consumer for FRAcceptedConsumer {
+ async fn create(
+ db: Database,
+ authifier_db: authifier::Database,
+ connection: Arc,
+ channel: Arc,
+ ) -> Self {
+ Self {
db,
authifier_db,
- conn: None,
- channel: None,
+ connection,
+ channel,
}
}
- async fn consume_event(
- &mut self,
- _channel: &Channel,
- _deliver: Deliver,
- _basic_properties: BasicProperties,
- content: Vec,
- ) -> Result<()> {
- let content = String::from_utf8(content)?;
- let payload: FRAcceptedPayload = serde_json::from_str(content.as_str())?;
+ fn channel(&self) -> &Arc {
+ &self.channel
+ }
+
+ /// This consumer handles delegating messages into their respective platform queues.
+ async fn consume(&self, delivery: Delivery) -> Result<()> {
+ let payload: FRAcceptedPayload = serde_json::from_slice(&delivery.data)?;
debug!("Received FR accept event");
@@ -80,36 +54,23 @@ impl FRAcceptedConsumer {
extras: HashMap::new(),
};
- let args: BasicPublishArguments;
-
- if sub.endpoint == "apn" {
- args = BasicPublishArguments::new(
- config.pushd.exchange.as_str(),
- config.pushd.apn.queue.as_str(),
- )
- .finish();
- } else if sub.endpoint == "fcm" {
- args = BasicPublishArguments::new(
- config.pushd.exchange.as_str(),
- config.pushd.fcm.queue.as_str(),
- )
- .finish();
- } else {
- // web push (vapid)
- args = BasicPublishArguments::new(
- config.pushd.exchange.as_str(),
- config.pushd.vapid.queue.as_str(),
- )
- .finish();
- sendable.extras.insert("p265dh".to_string(), sub.p256dh);
- sendable
- .extras
- .insert("endpoint".to_string(), sub.endpoint.clone());
- }
+ let routing_key = match sub.endpoint.as_str() {
+ "apn" => &config.pushd.apn.queue,
+ "fcm" => &config.pushd.fcm.queue,
+ endpoint => {
+ sendable.extras.insert("p256dh".to_string(), sub.p256dh);
+ sendable
+ .extras
+ .insert("endpoint".to_string(), endpoint.to_string());
+
+ &config.pushd.vapid.queue
+ }
+ };
let payload = serde_json::to_string(&sendable)?;
- publish_message(self, payload.into(), args).await;
+ self.publish_message(payload.as_bytes(), &config.pushd.exchange, routing_key)
+ .await?;
}
}
}
@@ -117,24 +78,3 @@ impl FRAcceptedConsumer {
Ok(())
}
}
-
-#[allow(unused_variables)]
-#[async_trait]
-impl AsyncConsumer for FRAcceptedConsumer {
- /// This consumer handles delegating messages into their respective platform queues.
- async fn consume(
- &mut self,
- channel: &Channel,
- deliver: Deliver,
- basic_properties: BasicProperties,
- content: Vec,
- ) {
- if let Err(err) = self
- .consume_event(channel, deliver, basic_properties, content)
- .await
- {
- revolt_config::capture_anyhow(&err);
- eprintln!("Failed to process friend request accepted event: {err:?}");
- }
- }
-}
diff --git a/crates/daemons/pushd/src/consumers/inbound/fr_received.rs b/crates/daemons/pushd/src/consumers/inbound/fr_received.rs
index f64bc92b8..66fce72df 100644
--- a/crates/daemons/pushd/src/consumers/inbound/fr_received.rs
+++ b/crates/daemons/pushd/src/consumers/inbound/fr_received.rs
@@ -1,70 +1,44 @@
-use std::collections::HashMap;
+use std::{collections::HashMap, sync::Arc};
-use crate::consumers::inbound::internal::*;
-use amqprs::{
- channel::{BasicPublishArguments, Channel},
- connection::Connection,
- consumer::AsyncConsumer,
- BasicProperties, Deliver,
-};
+use crate::utils::Consumer;
use anyhow::Result;
use async_trait::async_trait;
+use lapin::{message::Delivery, Channel, Connection};
use log::debug;
use revolt_database::{events::rabbit::*, Database};
+#[derive(Clone)]
+#[allow(unused)]
pub struct FRReceivedConsumer {
- #[allow(dead_code)]
db: Database,
authifier_db: authifier::Database,
- conn: Option,
- channel: Option,
+ connection: Arc,
+ channel: Arc,
}
-impl Channeled for FRReceivedConsumer {
- fn get_connection(&self) -> Option<&Connection> {
- if self.conn.is_none() {
- None
- } else {
- Some(self.conn.as_ref().unwrap())
- }
- }
-
- fn get_channel(&self) -> Option<&Channel> {
- if self.channel.is_none() {
- None
- } else {
- Some(self.channel.as_ref().unwrap())
- }
- }
-
- fn set_connection(&mut self, conn: Connection) {
- self.conn = Some(conn);
- }
-
- fn set_channel(&mut self, channel: Channel) {
- self.channel = Some(channel)
- }
-}
-
-impl FRReceivedConsumer {
- pub fn new(db: Database, authifier_db: authifier::Database) -> FRReceivedConsumer {
- FRReceivedConsumer {
+#[async_trait]
+impl Consumer for FRReceivedConsumer {
+ async fn create(
+ db: Database,
+ authifier_db: authifier::Database,
+ connection: Arc,
+ channel: Arc,
+ ) -> Self {
+ Self {
db,
authifier_db,
- conn: None,
- channel: None,
+ connection,
+ channel,
}
}
- async fn consume_event(
- &mut self,
- _channel: &Channel,
- _deliver: Deliver,
- _basic_properties: BasicProperties,
- content: Vec,
- ) -> Result<()> {
- let content = String::from_utf8(content)?;
- let payload: FRReceivedPayload = serde_json::from_str(content.as_str())?;
+ fn channel(&self) -> &Arc {
+ &self.channel
+ }
+
+ /// This consumer handles delegating messages into their respective platform queues.
+ async fn consume(&self, delivery: Delivery) -> Result<()> {
+ let payload: FRReceivedPayload = serde_json::from_slice(&delivery.data)?;
debug!("Received FR received event");
@@ -80,36 +54,23 @@ impl FRReceivedConsumer {
extras: HashMap::new(),
};
- let args: BasicPublishArguments;
-
- if sub.endpoint == "apn" {
- args = BasicPublishArguments::new(
- config.pushd.exchange.as_str(),
- config.pushd.apn.queue.as_str(),
- )
- .finish();
- } else if sub.endpoint == "fcm" {
- args = BasicPublishArguments::new(
- config.pushd.exchange.as_str(),
- config.pushd.fcm.queue.as_str(),
- )
- .finish();
- } else {
- // web push (vapid)
- args = BasicPublishArguments::new(
- config.pushd.exchange.as_str(),
- config.pushd.vapid.queue.as_str(),
- )
- .finish();
- sendable.extras.insert("p265dh".to_string(), sub.p256dh);
- sendable
- .extras
- .insert("endpoint".to_string(), sub.endpoint.clone());
- }
+ let routing_key = match sub.endpoint.as_str() {
+ "apn" => &config.pushd.apn.queue,
+ "fcm" => &config.pushd.fcm.queue,
+ endpoint => {
+ sendable.extras.insert("p256dh".to_string(), sub.p256dh);
+ sendable
+ .extras
+ .insert("endpoint".to_string(), endpoint.to_string());
+
+ &config.pushd.vapid.queue
+ }
+ };
let payload = serde_json::to_string(&sendable)?;
- publish_message(self, payload.into(), args).await;
+ self.publish_message(payload.as_bytes(), &config.pushd.exchange, routing_key)
+ .await?;
}
}
}
@@ -117,24 +78,3 @@ impl FRReceivedConsumer {
Ok(())
}
}
-
-#[allow(unused_variables)]
-#[async_trait]
-impl AsyncConsumer for FRReceivedConsumer {
- /// This consumer handles delegating messages into their respective platform queues.
- async fn consume(
- &mut self,
- channel: &Channel,
- deliver: Deliver,
- basic_properties: BasicProperties,
- content: Vec,
- ) {
- if let Err(err) = self
- .consume_event(channel, deliver, basic_properties, content)
- .await
- {
- revolt_config::capture_anyhow(&err);
- eprintln!("Failed to process friend request received event: {err:?}");
- }
- }
-}
diff --git a/crates/daemons/pushd/src/consumers/inbound/generic.rs b/crates/daemons/pushd/src/consumers/inbound/generic.rs
index aa3950d71..f413661d8 100644
--- a/crates/daemons/pushd/src/consumers/inbound/generic.rs
+++ b/crates/daemons/pushd/src/consumers/inbound/generic.rs
@@ -1,70 +1,44 @@
-use std::collections::HashMap;
+use std::{collections::HashMap, sync::Arc};
-use crate::consumers::inbound::internal::*;
-use amqprs::{
- channel::{BasicPublishArguments, Channel},
- connection::Connection,
- consumer::AsyncConsumer,
- BasicProperties, Deliver,
-};
+use crate::utils::Consumer;
use anyhow::Result;
use async_trait::async_trait;
+use lapin::{message::Delivery, Channel, Connection};
use log::debug;
use revolt_database::{events::rabbit::*, Database};
+#[derive(Clone)]
+#[allow(unused)]
pub struct GenericConsumer {
- #[allow(dead_code)]
db: Database,
authifier_db: authifier::Database,
- conn: Option,
- channel: Option,
+ connection: Arc,
+ channel: Arc,
}
-impl Channeled for GenericConsumer {
- fn get_connection(&self) -> Option<&Connection> {
- if self.conn.is_none() {
- None
- } else {
- Some(self.conn.as_ref().unwrap())
- }
- }
-
- fn get_channel(&self) -> Option<&Channel> {
- if self.channel.is_none() {
- None
- } else {
- Some(self.channel.as_ref().unwrap())
- }
- }
-
- fn set_connection(&mut self, conn: Connection) {
- self.conn = Some(conn);
- }
-
- fn set_channel(&mut self, channel: Channel) {
- self.channel = Some(channel)
- }
-}
-
-impl GenericConsumer {
- pub fn new(db: Database, authifier_db: authifier::Database) -> GenericConsumer {
- GenericConsumer {
+#[async_trait]
+impl Consumer for GenericConsumer {
+ async fn create(
+ db: Database,
+ authifier_db: authifier::Database,
+ connection: Arc,
+ channel: Arc,
+ ) -> Self {
+ Self {
db,
authifier_db,
- conn: None,
- channel: None,
+ connection,
+ channel,
}
}
- async fn consume_event(
- &mut self,
- _channel: &Channel,
- _deliver: Deliver,
- _basic_properties: BasicProperties,
- content: Vec,
- ) -> Result<()> {
- let content = String::from_utf8(content)?;
- let payload: MessageSentPayload = serde_json::from_str(content.as_str())?;
+ fn channel(&self) -> &Arc {
+ &self.channel
+ }
+
+ /// This consumer handles delegating messages into their respective platform queues.
+ async fn consume(&self, delivery: Delivery) -> Result<()> {
+ let payload: MessageSentPayload = serde_json::from_slice(&delivery.data)?;
debug!("Received message event on origin");
@@ -86,36 +60,23 @@ impl GenericConsumer {
extras: HashMap::new(),
};
- let args: BasicPublishArguments;
-
- if sub.endpoint == "apn" {
- args = BasicPublishArguments::new(
- config.pushd.exchange.as_str(),
- config.pushd.apn.queue.as_str(),
- )
- .finish();
- } else if sub.endpoint == "fcm" {
- args = BasicPublishArguments::new(
- config.pushd.exchange.as_str(),
- config.pushd.fcm.queue.as_str(),
- )
- .finish();
- } else {
- // web push (vapid)
- args = BasicPublishArguments::new(
- config.pushd.exchange.as_str(),
- config.pushd.vapid.queue.as_str(),
- )
- .finish();
- sendable.extras.insert("p265dh".to_string(), sub.p256dh);
- sendable
- .extras
- .insert("endpoint".to_string(), sub.endpoint.clone());
- }
+ let routing_key = match sub.endpoint.as_str() {
+ "apn" => &config.pushd.apn.queue,
+ "fcm" => &config.pushd.fcm.queue,
+ endpoint => {
+ sendable.extras.insert("p256dh".to_string(), sub.p256dh);
+ sendable
+ .extras
+ .insert("endpoint".to_string(), endpoint.to_string());
+
+ &config.pushd.vapid.queue
+ }
+ };
let payload = serde_json::to_string(&sendable)?;
- publish_message(self, payload.into(), args).await;
+ self.publish_message(payload.as_bytes(), &config.pushd.exchange, routing_key)
+ .await?;
}
}
}
@@ -123,24 +84,3 @@ impl GenericConsumer {
Ok(())
}
}
-
-#[allow(unused_variables)]
-#[async_trait]
-impl AsyncConsumer for GenericConsumer {
- /// This consumer handles delegating messages into their respective platform queues.
- async fn consume(
- &mut self,
- channel: &Channel,
- deliver: Deliver,
- basic_properties: BasicProperties,
- content: Vec,
- ) {
- if let Err(err) = self
- .consume_event(channel, deliver, basic_properties, content)
- .await
- {
- revolt_config::capture_anyhow(&err);
- eprintln!("Failed to process generic event: {err:?}");
- }
- }
-}
diff --git a/crates/daemons/pushd/src/consumers/inbound/internal.rs b/crates/daemons/pushd/src/consumers/inbound/internal.rs
deleted file mode 100644
index 387c08b52..000000000
--- a/crates/daemons/pushd/src/consumers/inbound/internal.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-use amqprs::{
- channel::{BasicPublishArguments, Channel},
- connection::{Connection, OpenConnectionArguments},
- BasicProperties,
-};
-use log::{debug, warn};
-
-pub(crate) trait Channeled {
- #[allow(unused)]
- fn get_connection(&self) -> Option<&Connection>;
- fn get_channel(&self) -> Option<&Channel>;
- fn set_connection(&mut self, conn: Connection);
- fn set_channel(&mut self, channel: Channel);
-}
-
-pub(crate) async fn make_channel