diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
new file mode 100644
index 00000000..867978dd
--- /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 00000000..da918526
--- /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 00f7ef71..00000000
--- 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.yaml b/.github/workflows/docker.yaml
index 5a63353a..2be1f438 100644
--- a/.github/workflows/docker.yaml
+++ b/.github/workflows/docker.yaml
@@ -5,19 +5,22 @@ 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
+ runs-on: arc-runner-set
if: github.event_name == 'pull_request'
steps:
# Configure build environment
@@ -38,7 +41,7 @@ jobs:
cache-to: type=gha,scope=buildx-base-multi-arch,mode=max
publish:
- runs-on: self-hosted
+ runs-on: arc-runner-set
if: github.event_name != 'pull_request'
name: Publish Docker images
steps:
@@ -197,3 +200,22 @@ jobs:
build-args: |
BASE_IMAGE=ghcr.io/${{ github.repository_owner }}/base:latest
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:latest
+ 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 00000000..26a065fc
--- /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 00000000..451deda7
--- /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 00000000..678b60c1
--- /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 00000000..ce02d841
--- /dev/null
+++ b/.github/workflows/publish-crates.yml
@@ -0,0 +1,24 @@
+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
+ run: mise publish --workspace
diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
new file mode 100644
index 00000000..63d9a88e
--- /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 00000000..3996b1f8
--- /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/rust.yaml b/.github/workflows/rust.yaml
index ad6fa7ad..08e210a3 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,58 @@ 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
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
with:
- toolchain: stable
- override: true
- components: rustfmt, clippy
- - name: Install cargo-nextest
- uses: baptiste0928/cargo-install@v1
+ persist-credentials: false
+
+ # 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: Setup Mise
+ uses: immich-app/devtools/actions/use-mise@cd24790a7f5f6439ac32cc94f5523cb2de8bfa8c # use-mise-action-v1.1.0
with:
- crate: cargo-nextest
- locked: true
+ github-token: ${{ secrets.GITHUB_TOKEN }}
- - name: Run cargo build
- uses: actions-rs/cargo@v1
- with:
- command: build
-
- - name: Run services in background
- run: |
- docker compose -f compose.yml up -d
-
- - name: Run cargo test
+ - run: mise build
+ - run: mise docker:start
+
+ - name: Reference Test
env:
TEST_DB: REFERENCE
run: |
- cargo nextest run
+ mise test
- - name: Run cargo test (with MongoDB)
+ - name: MongoDB Test
env:
TEST_DB: MONGODB
MONGODB: mongodb://localhost
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: stoatchat/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 +74,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 b74d8bed..00000000
--- 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: "stoatchat"){
- 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 64523ffe..00000000
--- 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: "stoatchat"){
- 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 00000000..1374d58c
--- /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 8483bc43..ae3bfadb 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 00000000..f5238e77
--- /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 00000000..2e584eaf
--- /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 00000000..116f1016
--- /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 00000000..c045a4bd
--- /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 00000000..51d39d61
--- /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 00000000..742d586f
--- /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 00000000..ae3cdb65
--- /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 00000000..753779c1
--- /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 00000000..27df8f9a
--- /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 00000000..07915c17
--- /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 00000000..ce4bc491
--- /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 00000000..85bea49a
--- /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 00000000..431c5a52
--- /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 00000000..bc72192b
--- /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 00000000..a16634fc
--- /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 00000000..1cbb96ba
--- /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 00000000..848ad35d
--- /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 00000000..53d4a3f2
--- /dev/null
+++ b/.release-please-manifest.json
@@ -0,0 +1,3 @@
+{
+ ".": "0.12.0"
+}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..7153a56f
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,186 @@
+# Changelog
+
+## [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 be9eef94..99a062dc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -85,6 +85,15 @@ 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"
@@ -122,7 +146,7 @@ dependencies = [
"bytes 1.10.1",
"serde",
"serde_bytes_ng",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
@@ -136,9 +160,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.99"
+version = "1.0.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100"
+checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
dependencies = [
"backtrace",
]
@@ -178,6 +202,15 @@ 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 = "async-attributes"
version = "1.1.2"
@@ -239,7 +272,7 @@ dependencies = [
"futures-lite 2.6.1",
"once_cell",
"tokio 0.2.25",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
@@ -442,9 +475,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",
@@ -496,6 +529,26 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
+[[package]]
+name = "av-scenechange"
+version = "0.14.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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"
@@ -543,7 +596,7 @@ dependencies = [
"http 1.3.1",
"ring",
"time",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tracing",
"url",
"zeroize",
@@ -746,7 +799,7 @@ checksum = "1e190749ea56f8c42bf15dd76c65e14f8f765233e6df9b0506d9d934ebef867c"
dependencies = [
"futures-util",
"pin-project-lite 0.2.16",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
@@ -822,11 +875,11 @@ dependencies = [
"hyper-util",
"pin-project-lite 0.2.16",
"rustls 0.21.12",
- "rustls 0.23.31",
+ "rustls 0.23.32",
"rustls-native-certs 0.8.1",
"rustls-pki-types",
- "tokio 1.47.1",
- "tokio-rustls 0.26.2",
+ "tokio 1.49.0",
+ "tokio-rustls 0.26.3",
"tower",
"tracing",
]
@@ -879,7 +932,7 @@ dependencies = [
"http-body 1.0.1",
"pin-project-lite 0.2.16",
"pin-utils",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tracing",
]
@@ -895,7 +948,7 @@ dependencies = [
"http 0.2.12",
"http 1.3.1",
"pin-project-lite 0.2.16",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tracing",
"zeroize",
]
@@ -922,7 +975,7 @@ dependencies = [
"ryu",
"serde",
"time",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tokio-util",
]
@@ -978,7 +1031,7 @@ dependencies = [
"serde_path_to_error",
"serde_urlencoded",
"sync_wrapper 1.0.2",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tower",
"tower-layer",
"tower-service",
@@ -1056,7 +1109,7 @@ dependencies = [
"futures-util",
"tempfile",
"thiserror 1.0.69",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"uuid",
]
@@ -1067,7 +1120,7 @@ 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.106",
@@ -1214,9 +1267,12 @@ checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394"
[[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"
@@ -1263,6 +1319,16 @@ dependencies = [
"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"
@@ -1275,7 +1341,7 @@ dependencies = [
"getrandom 0.2.16",
"getrandom 0.3.3",
"hex",
- "indexmap 2.11.1",
+ "indexmap 2.13.0",
"js-sys",
"once_cell",
"rand 0.9.2",
@@ -1288,9 +1354,9 @@ 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"
@@ -1352,7 +1418,7 @@ dependencies = [
"instant",
"once_cell",
"thiserror 1.0.69",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
@@ -1382,9 +1448,9 @@ checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6"
[[package]]
name = "cc"
-version = "1.2.37"
+version = "1.2.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65193589c6404eb80b450d618eaf9a2cafaaafd57ecce47370519ef674a7bd44"
+checksum = "80f41ae168f955c12fb8960b057d70d0ca153fb83182b57d86380443527be7e9"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -1412,16 +1478,6 @@ dependencies = [
"uuid",
]
-[[package]]
-name = "cfg-expr"
-version = "0.15.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02"
-dependencies = [
- "smallvec",
- "target-lexicon",
-]
-
[[package]]
name = "cfg-if"
version = "1.0.3"
@@ -1508,7 +1564,7 @@ dependencies = [
"futures-core",
"memchr",
"pin-project-lite 0.2.16",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tokio-util",
]
@@ -1630,6 +1686,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"
@@ -1874,6 +1939,16 @@ dependencies = [
"darling_macro 0.20.11",
]
+[[package]]
+name = "darling"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0"
+dependencies = [
+ "darling_core 0.21.3",
+ "darling_macro 0.21.3",
+]
+
[[package]]
name = "darling_core"
version = "0.13.4"
@@ -1916,6 +1991,20 @@ dependencies = [
"syn 2.0.106",
]
+[[package]]
+name = "darling_core"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote 1.0.40",
+ "strsim 0.11.1",
+ "syn 2.0.106",
+]
+
[[package]]
name = "darling_macro"
version = "0.13.4"
@@ -1949,6 +2038,17 @@ dependencies = [
"syn 2.0.106",
]
+[[package]]
+name = "darling_macro"
+version = "0.21.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81"
+dependencies = [
+ "darling_core 0.21.3",
+ "quote 1.0.40",
+ "syn 2.0.106",
+]
+
[[package]]
name = "dashmap"
version = "5.5.3"
@@ -1981,7 +2081,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72ff1266be84e4e04a81e2d1cbb998cb271b374fb73ce780245ef96c037c50cd"
dependencies = [
"crossbeam-queue",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
@@ -2316,7 +2416,7 @@ 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.106",
@@ -2438,9 +2538,9 @@ dependencies = [
[[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",
@@ -2554,9 +2654,9 @@ dependencies = [
[[package]]
name = "find-msvc-tools"
-version = "0.1.1"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fd99930f64d146689264c637b5af2f0233a933bef0d8570e2526bf9e083192d"
+checksum = "1ced73b1dacfc750a6db6c0a0c3a3853c8b41997e2e2c563dc90804ae6867959"
[[package]]
name = "findshlibs"
@@ -2570,6 +2670,12 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "fixedbitset"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
+
[[package]]
name = "flate2"
version = "1.1.2"
@@ -2601,6 +2707,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"
@@ -2667,7 +2779,7 @@ dependencies = [
"redis-protocol",
"semver",
"socket2 0.5.10",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tokio-stream",
"tokio-util",
"url",
@@ -2786,7 +2898,7 @@ checksum = "45ec6fe3675af967e67c5536c0b9d44e34e6c52f86bedc4ea49c5317b8e94d06"
dependencies = [
"futures-channel",
"futures-task",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
@@ -2924,7 +3036,7 @@ dependencies = [
"js-sys",
"libc",
"r-efi",
- "wasi 0.14.5+wasi-0.2.4",
+ "wasi 0.14.7+wasi-0.2.4",
"wasm-bindgen",
]
@@ -2960,6 +3072,16 @@ 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"
@@ -3031,9 +3153,9 @@ dependencies = [
"futures-sink",
"futures-util",
"http 0.2.12",
- "indexmap 2.11.1",
+ "indexmap 2.13.0",
"slab",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tokio-util",
"tracing",
]
@@ -3050,9 +3172,9 @@ dependencies = [
"futures-core",
"futures-sink",
"http 1.3.1",
- "indexmap 2.11.1",
+ "indexmap 2.13.0",
"slab",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tokio-util",
"tracing",
]
@@ -3114,7 +3236,18 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"allocator-api2",
"equivalent",
- "foldhash",
+ "foldhash 0.1.5",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.16.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash 0.2.0",
]
[[package]]
@@ -3141,6 +3274,12 @@ dependencies = [
"http 1.3.1",
]
+[[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"
@@ -3187,7 +3326,7 @@ dependencies = [
"rand 0.8.5",
"thiserror 1.0.69",
"tinyvec",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tracing",
"url",
]
@@ -3209,7 +3348,7 @@ dependencies = [
"resolv-conf",
"smallvec",
"thiserror 1.0.69",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tracing",
]
@@ -3375,7 +3514,7 @@ dependencies = [
"itoa",
"pin-project-lite 0.2.16",
"socket2 0.5.10",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tower-service",
"tracing",
"want",
@@ -3400,7 +3539,7 @@ dependencies = [
"pin-project-lite 0.2.16",
"pin-utils",
"smallvec",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"want",
]
@@ -3416,7 +3555,7 @@ dependencies = [
"log",
"rustls 0.21.12",
"rustls-native-certs 0.6.3",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tokio-rustls 0.24.1",
]
@@ -3432,7 +3571,7 @@ dependencies = [
"hyper-util",
"rustls 0.22.4",
"rustls-pki-types",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tokio-rustls 0.25.0",
"tower-service",
"webpki-roots 0.26.11",
@@ -3447,11 +3586,11 @@ dependencies = [
"http 1.3.1",
"hyper 1.7.0",
"hyper-util",
- "rustls 0.23.31",
+ "rustls 0.23.32",
"rustls-native-certs 0.8.1",
"rustls-pki-types",
- "tokio 1.47.1",
- "tokio-rustls 0.26.2",
+ "tokio 1.49.0",
+ "tokio-rustls 0.26.3",
"tower-service",
]
@@ -3464,7 +3603,7 @@ dependencies = [
"bytes 1.10.1",
"hyper 0.14.32",
"native-tls",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tokio-native-tls",
]
@@ -3479,16 +3618,16 @@ dependencies = [
"hyper 1.7.0",
"hyper-util",
"native-tls",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
-version = "0.1.16"
+version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d9b05277c7e8da2c93a568989bb6207bef0112e8d17df7a6eda4a3cf143bc5e"
+checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8"
dependencies = [
"base64 0.22.1",
"bytes 1.10.1",
@@ -3504,7 +3643,7 @@ dependencies = [
"pin-project-lite 0.2.16",
"socket2 0.6.0",
"system-configuration 0.6.1",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tower-service",
"tracing",
"windows-registry",
@@ -3686,15 +3825,15 @@ checksum = "cd62e6b5e86ea8eeeb8db1de02880a6abc01a397b2ebb64b5d74ac255318f5cb"
[[package]]
name = "image"
-version = "0.25.8"
+version = "0.25.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "529feb3e6769d234375c4cf1ee2ce713682b8e76538cb13f9fc23e1400a591e7"
+checksum = "e6506c6c10786659413faa717ceebcb8f70731c0a60cbae39795fdf114519c1a"
dependencies = [
"bytemuck",
"byteorder-lite",
"color_quant",
"exr",
- "gif",
+ "gif 0.14.1",
"image-webp 0.2.4",
"moxcms",
"num-traits",
@@ -3704,8 +3843,8 @@ dependencies = [
"rayon",
"rgb",
"tiff",
- "zune-core",
- "zune-jpeg",
+ "zune-core 0.5.1",
+ "zune-jpeg 0.5.11",
]
[[package]]
@@ -3759,13 +3898,14 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.11.1"
+version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921"
+checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
- "hashbrown 0.15.5",
+ "hashbrown 0.16.1",
"serde",
+ "serde_core",
]
[[package]]
@@ -3812,17 +3952,6 @@ dependencies = [
"syn 2.0.106",
]
-[[package]]
-name = "io-uring"
-version = "0.7.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "046fa2d4d00aea763528b4950358d0ead425372445dc8ff86312b3c69ff7727b"
-dependencies = [
- "bitflags 2.9.4",
- "cfg-if",
- "libc",
-]
-
[[package]]
name = "ipconfig"
version = "0.3.2"
@@ -3903,6 +4032,15 @@ dependencies = [
"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"
@@ -3921,6 +4059,15 @@ 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"
@@ -3939,9 +4086,9 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.78"
+version = "0.3.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c0b063578492ceec17683ef2f8c5e89121fbd0b172cbc280635ab7567db2738"
+checksum = "852f13bec5eba4ba9afbeb93fd7c13fe56147f055939ae21c43a29a0ecb2702e"
dependencies = [
"once_cell",
"wasm-bindgen",
@@ -3958,6 +4105,19 @@ dependencies = [
"serde",
]
+[[package]]
+name = "jsonwebtoken"
+version = "9.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde"
+dependencies = [
+ "base64 0.22.1",
+ "js-sys",
+ "ring",
+ "serde",
+ "serde_json",
+]
+
[[package]]
name = "jwt-simple"
version = "0.11.9"
@@ -3993,13 +4153,32 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "jxl-bitstream"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b480e752277e29eb4054f69546887a9b84656fe78c08f54ba5850ced98a378fe"
+dependencies = [
+ "tracing",
+]
+
[[package]]
name = "jxl-coding"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da5b5093904e940bc11ef50e872c7bdf7b6e88653f012b925f8479daf212b5c9"
dependencies = [
- "jxl-bitstream",
+ "jxl-bitstream 0.4.1",
+ "tracing",
+]
+
+[[package]]
+name = "jxl-coding"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd972bcd125e776f1eb241ac50e39f956095a1c2770c64736c968f8946bd9a3c"
+dependencies = [
+ "jxl-bitstream 1.1.0",
"tracing",
]
@@ -4009,10 +4188,25 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cb1c31e10054079df585633fc14fb9e4c96565c58c05b983c502e2472b57fa0"
dependencies = [
- "jxl-bitstream",
- "jxl-coding",
- "jxl-grid",
- "jxl-threadpool",
+ "jxl-bitstream 0.4.1",
+ "jxl-coding 0.4.1",
+ "jxl-grid 0.4.2",
+ "jxl-threadpool 0.1.2",
+ "tracing",
+]
+
+[[package]]
+name = "jxl-color"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f316b1358c1711755b3ee8e8cb5c4a1dad12e796233088a7a513440782de80b2"
+dependencies = [
+ "jxl-bitstream 1.1.0",
+ "jxl-coding 1.0.1",
+ "jxl-grid 0.6.1",
+ "jxl-image 0.13.0",
+ "jxl-oxide-common",
+ "jxl-threadpool 1.0.0",
"tracing",
]
@@ -4022,13 +4216,30 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e35b289aa0f24044167d83a1c29ae2b99210c5082ab7e3e90dacbcae818aa0a2"
dependencies = [
- "jxl-bitstream",
- "jxl-coding",
- "jxl-grid",
- "jxl-image",
- "jxl-modular",
- "jxl-threadpool",
- "jxl-vardct",
+ "jxl-bitstream 0.4.1",
+ "jxl-coding 0.4.1",
+ "jxl-grid 0.4.2",
+ "jxl-image 0.9.0",
+ "jxl-modular 0.7.1",
+ "jxl-threadpool 0.1.2",
+ "jxl-vardct 0.7.0",
+ "tracing",
+]
+
+[[package]]
+name = "jxl-frame"
+version = "0.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d967c6fd669c7c01060b5022d8835fa82fd46b06ffc98b549f17600a097c2b3"
+dependencies = [
+ "jxl-bitstream 1.1.0",
+ "jxl-coding 1.0.1",
+ "jxl-grid 0.6.1",
+ "jxl-image 0.13.0",
+ "jxl-modular 0.11.2",
+ "jxl-oxide-common",
+ "jxl-threadpool 1.0.0",
+ "jxl-vardct 0.11.1",
"tracing",
]
@@ -4041,15 +4252,54 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "jxl-grid"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0e0ef92d5d60e76bf41098e57e985f523185e08fad54268da448637feca6989"
+dependencies = [
+ "tracing",
+]
+
[[package]]
name = "jxl-image"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b31b17ed3bd0e3b65e7b06628f5930e009dda6cd17638cf5159a20a3feedec6"
dependencies = [
- "jxl-bitstream",
- "jxl-color",
- "jxl-grid",
+ "jxl-bitstream 0.4.1",
+ "jxl-color 0.7.1",
+ "jxl-grid 0.4.2",
+ "tracing",
+]
+
+[[package]]
+name = "jxl-image"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5f752d62577c702a94dbbce4045caf08cb58639e8a4d56464b40ecf33ffe565"
+dependencies = [
+ "jxl-bitstream 1.1.0",
+ "jxl-grid 0.6.1",
+ "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 1.1.0",
+ "jxl-frame 0.13.3",
+ "jxl-grid 0.6.1",
+ "jxl-image 0.13.0",
+ "jxl-modular 0.11.2",
+ "jxl-oxide-common",
+ "jxl-threadpool 1.0.0",
+ "jxl-vardct 0.11.1",
"tracing",
]
@@ -4059,10 +4309,24 @@ version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da3b9fb8f46e63a14ecedefbd0f873b04162aaf8a09676b630c31bc8dadc4638"
dependencies = [
- "jxl-bitstream",
- "jxl-coding",
- "jxl-grid",
- "jxl-threadpool",
+ "jxl-bitstream 0.4.1",
+ "jxl-coding 0.4.1",
+ "jxl-grid 0.4.2",
+ "jxl-threadpool 0.1.2",
+ "tracing",
+]
+
+[[package]]
+name = "jxl-modular"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da758b2f989aafd9eeb39489fe43d7be5a3a0d2ad61cf1bad705eb6990a6053c"
+dependencies = [
+ "jxl-bitstream 1.1.0",
+ "jxl-coding 1.0.1",
+ "jxl-grid 0.6.1",
+ "jxl-oxide-common",
+ "jxl-threadpool 1.0.0",
"tracing",
]
@@ -4072,31 +4336,81 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ba1ee3895e6c62b131994807b1ee6d179013613a86c01c203369af8d1e8d2f0"
dependencies = [
- "jxl-bitstream",
- "jxl-color",
- "jxl-frame",
- "jxl-grid",
- "jxl-image",
- "jxl-render",
- "jxl-threadpool",
+ "jxl-bitstream 0.4.1",
+ "jxl-color 0.7.1",
+ "jxl-frame 0.9.0",
+ "jxl-grid 0.4.2",
+ "jxl-image 0.9.0",
+ "jxl-render 0.8.2",
+ "jxl-threadpool 0.1.2",
"tracing",
]
+[[package]]
+name = "jxl-oxide"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee8ecd2678ed70c1eda42b811ccb2e25ab836edeb18e7f1178c1f917ed36b772"
+dependencies = [
+ "brotli-decompressor",
+ "bytemuck",
+ "image",
+ "jxl-bitstream 1.1.0",
+ "jxl-color 0.11.0",
+ "jxl-frame 0.13.3",
+ "jxl-grid 0.6.1",
+ "jxl-image 0.13.0",
+ "jxl-jbr",
+ "jxl-oxide-common",
+ "jxl-render 0.12.3",
+ "jxl-threadpool 1.0.0",
+ "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 1.1.0",
+]
+
[[package]]
name = "jxl-render"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "203a79b3025b86f875cc97a6f39e1fcc6314f915b2f6b69f767fca45fd482a11"
dependencies = [
- "jxl-bitstream",
- "jxl-coding",
- "jxl-color",
- "jxl-frame",
- "jxl-grid",
- "jxl-image",
- "jxl-modular",
- "jxl-threadpool",
- "jxl-vardct",
+ "jxl-bitstream 0.4.1",
+ "jxl-coding 0.4.1",
+ "jxl-color 0.7.1",
+ "jxl-frame 0.9.0",
+ "jxl-grid 0.4.2",
+ "jxl-image 0.9.0",
+ "jxl-modular 0.7.1",
+ "jxl-threadpool 0.1.2",
+ "jxl-vardct 0.7.0",
+ "tracing",
+]
+
+[[package]]
+name = "jxl-render"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa0c3100918bd3c41bb0f8ce1f4f1664e48f3032ff8eeab0d6a2cfc3276f462d"
+dependencies = [
+ "bytemuck",
+ "jxl-bitstream 1.1.0",
+ "jxl-coding 1.0.1",
+ "jxl-color 0.11.0",
+ "jxl-frame 0.13.3",
+ "jxl-grid 0.6.1",
+ "jxl-image 0.13.0",
+ "jxl-modular 0.11.2",
+ "jxl-oxide-common",
+ "jxl-threadpool 1.0.0",
+ "jxl-vardct 0.11.1",
"tracing",
]
@@ -4111,17 +4425,43 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "jxl-threadpool"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "25f15eb830aa77a7f21148d72e153562a26bfe570139bd4922eab1908dd499d3"
+dependencies = [
+ "rayon",
+ "rayon-core",
+ "tracing",
+]
+
[[package]]
name = "jxl-vardct"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16af82a1ad770887cad720bfd3cc6a6d023faf377036989a24cf2c6538b649e0"
dependencies = [
- "jxl-bitstream",
- "jxl-coding",
- "jxl-grid",
- "jxl-modular",
- "jxl-threadpool",
+ "jxl-bitstream 0.4.1",
+ "jxl-coding 0.4.1",
+ "jxl-grid 0.4.2",
+ "jxl-modular 0.7.1",
+ "jxl-threadpool 0.1.2",
+ "tracing",
+]
+
+[[package]]
+name = "jxl-vardct"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce72a18c6d3a47172ab6c479be2bdb56f22066b5d7092663f03b4490820b4511"
+dependencies = [
+ "jxl-bitstream 1.1.0",
+ "jxl-coding 1.0.1",
+ "jxl-grid 0.6.1",
+ "jxl-modular 0.11.2",
+ "jxl-oxide-common",
+ "jxl-threadpool 1.0.0",
"tracing",
]
@@ -4203,7 +4543,7 @@ dependencies = [
"once_cell",
"quoted_printable",
"socket2 0.4.10",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
@@ -4318,6 +4658,68 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956"
+[[package]]
+name = "livekit-api"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a17951fa8d398241f4a9503b57a7b38b3f40fb9dc94954f17b9fe99683e6b9b"
+dependencies = [
+ "base64 0.21.7",
+ "http 0.2.12",
+ "jsonwebtoken",
+ "livekit-protocol",
+ "log",
+ "parking_lot",
+ "pbjson-types",
+ "prost",
+ "rand 0.9.2",
+ "reqwest 0.11.27",
+ "scopeguard",
+ "serde",
+ "serde_json",
+ "sha2",
+ "thiserror 1.0.69",
+ "url",
+]
+
+[[package]]
+name = "livekit-protocol"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45c90494efa508ec40228f870affec648826d23e1a9621a4430f67d187901eb4"
+dependencies = [
+ "futures-util",
+ "livekit-runtime 0.4.0",
+ "parking_lot",
+ "pbjson",
+ "pbjson-types",
+ "prost",
+ "prost-types",
+ "serde",
+ "thiserror 1.0.69",
+ "tokio 1.49.0",
+]
+
+[[package]]
+name = "livekit-runtime"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ae53eb874eb86e96e8ccffc31b5a00926d46174cbcb1c24ce4e57b1fcc5c8f6"
+dependencies = [
+ "tokio 1.49.0",
+ "tokio-stream",
+]
+
+[[package]]
+name = "livekit-runtime"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "532e84c6cdc5fe774f2b5d9912597b5f3bea561927a48296d03e24549d21c3f6"
+dependencies = [
+ "tokio 1.49.0",
+ "tokio-stream",
+]
+
[[package]]
name = "lock_api"
version = "0.4.13"
@@ -4435,6 +4837,15 @@ dependencies = [
"hashbrown 0.15.5",
]
+[[package]]
+name = "lru"
+version = "0.16.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
+dependencies = [
+ "hashbrown 0.16.1",
+]
+
[[package]]
name = "lru-cache"
version = "0.1.2"
@@ -4638,7 +5049,7 @@ dependencies = [
"log",
"metrics",
"thiserror 1.0.69",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tracing",
"tracing-subscriber",
]
@@ -4723,7 +5134,7 @@ dependencies = [
"percent-encoding",
"rand 0.8.5",
"rustc_version_runtime",
- "rustls 0.23.31",
+ "rustls 0.23.32",
"rustversion",
"serde",
"serde_bytes",
@@ -4735,8 +5146,8 @@ dependencies = [
"strsim 0.11.1",
"take_mut",
"thiserror 1.0.69",
- "tokio 1.47.1",
- "tokio-rustls 0.26.2",
+ "tokio 1.49.0",
+ "tokio-rustls 0.26.3",
"tokio-util",
"typed-builder",
"uuid",
@@ -4779,11 +5190,17 @@ dependencies = [
"memchr",
"mime",
"spin",
- "tokio 1.47.1",
+ "tokio 1.49.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.2"
@@ -5165,12 +5582,55 @@ 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.10.1",
+ "chrono",
+ "pbjson",
+ "pbjson-build",
+ "prost",
+ "prost-build",
+ "serde",
+]
+
[[package]]
name = "pbkdf2"
version = "0.11.0"
@@ -5255,7 +5715,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21e0a3a33733faeaf8651dfee72dd0f388f0c8e5ad496a3478fa5a922f49cfa8"
dependencies = [
"memchr",
- "thiserror 2.0.16",
+ "thiserror 2.0.18",
"ucd-trie",
]
@@ -5292,6 +5752,16 @@ dependencies = [
"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.0",
+]
+
[[package]]
name = "phf"
version = "0.10.1"
@@ -5477,12 +5947,12 @@ checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
[[package]]
name = "plist"
-version = "1.7.4"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3af6b589e163c5a788fab00ce0c0366f6efbb9959c2f9874b224936af7fce7e1"
+checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
dependencies = [
"base64 0.22.1",
- "indexmap 2.11.1",
+ "indexmap 2.13.0",
"quick-xml",
"serde",
"time",
@@ -5733,10 +6203,63 @@ dependencies = [
]
[[package]]
-name = "pxfm"
-version = "0.1.23"
+name = "prost"
+version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f55f4fedc84ed39cb7a489322318976425e42a147e2be79d8f878e2884f94e84"
+checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29"
+dependencies = [
+ "bytes 1.10.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.10.1",
+ "heck 0.5.0",
+ "itertools 0.12.1",
+ "log",
+ "multimap",
+ "once_cell",
+ "petgraph",
+ "prettyplease",
+ "prost",
+ "prost-types",
+ "regex",
+ "syn 2.0.106",
+ "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.40",
+ "syn 2.0.106",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0"
+dependencies = [
+ "prost",
+]
+
+[[package]]
+name = "pxfm"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "83f9b339b02259ada5c0f4a389b7fb472f933aa17ce176fd2ad98f28bb401fde"
dependencies = [
"num-traits",
]
@@ -5978,19 +6501,21 @@ dependencies = [
[[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 0.12.1",
+ "itertools 0.14.0",
"libc",
"libfuzzer-sys",
"log",
@@ -5999,23 +6524,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.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5825c26fddd16ab9f515930d49028a630efec172e903483c94796cfe31893e6b"
+checksum = "ef69c1990ceef18a116855938e74793a5f7496ee907562bd0857b6ac734ab285"
dependencies = [
"avif-serialize",
"imgref",
@@ -6071,7 +6594,7 @@ dependencies = [
"ryu",
"sha1_smol",
"socket2 0.4.10",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tokio-util",
"url",
]
@@ -6137,9 +6660,9 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.11.2"
+version = "1.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
+checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
dependencies = [
"aho-corasick",
"memchr",
@@ -6149,9 +6672,9 @@ dependencies = [
[[package]]
name = "regex-automata"
-version = "0.4.10"
+version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
+checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
dependencies = [
"aho-corasick",
"memchr",
@@ -6200,7 +6723,7 @@ dependencies = [
"serde_urlencoded",
"sync_wrapper 0.1.2",
"system-configuration 0.5.1",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tokio-native-tls",
"tower-service",
"url",
@@ -6239,7 +6762,7 @@ dependencies = [
"serde_json",
"serde_urlencoded",
"sync_wrapper 1.0.2",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tokio-native-tls",
"tower",
"tower-http 0.6.6",
@@ -6262,7 +6785,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",
@@ -6270,12 +6793,12 @@ dependencies = [
"svgtypes",
"tiny-skia",
"usvg",
- "zune-jpeg",
+ "zune-jpeg 0.4.21",
]
[[package]]
name = "revolt-autumn"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"axum",
"axum-macros",
@@ -6284,7 +6807,7 @@ dependencies = [
"image",
"imagesize",
"infer",
- "jxl-oxide",
+ "jxl-oxide 0.8.1",
"kamadak-exif",
"lazy_static",
"moka",
@@ -6301,7 +6824,8 @@ dependencies = [
"simdutf8",
"strum_macros",
"tempfile",
- "tokio 1.47.1",
+ "thumbhash",
+ "tokio 1.49.0",
"tower-http 0.5.2",
"tracing",
"tracing-subscriber",
@@ -6313,7 +6837,7 @@ dependencies = [
[[package]]
name = "revolt-bonfire"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"async-channel 2.5.0",
"async-std",
@@ -6344,16 +6868,16 @@ dependencies = [
[[package]]
name = "revolt-coalesced"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
- "indexmap 1.9.3",
- "lru 0.7.8",
- "tokio 1.47.1",
+ "indexmap 2.13.0",
+ "lru 0.16.3",
+ "tokio 1.49.0",
]
[[package]]
name = "revolt-config"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"async-std",
"cached",
@@ -6370,19 +6894,19 @@ dependencies = [
[[package]]
name = "revolt-crond"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"log",
"revolt-config",
"revolt-database",
"revolt-files",
"revolt-result",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
name = "revolt-database"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"amqprs",
"async-lock 2.8.0",
@@ -6400,6 +6924,9 @@ dependencies = [
"isahc",
"iso8601-timestamp",
"linkify 0.8.1",
+ "livekit-api",
+ "livekit-protocol",
+ "livekit-runtime 0.3.1",
"log",
"lru 0.11.1",
"mongodb",
@@ -6429,7 +6956,7 @@ dependencies = [
[[package]]
name = "revolt-delta"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"amqprs",
"async-channel 1.9.0",
@@ -6444,6 +6971,8 @@ dependencies = [
"iso8601-timestamp",
"lettre",
"linkify 0.6.0",
+ "livekit-api",
+ "livekit-protocol",
"log",
"lru 0.7.8",
"nanoid",
@@ -6477,30 +7006,35 @@ dependencies = [
[[package]]
name = "revolt-files"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"aes-gcm",
+ "anyhow",
+ "async-trait",
"aws-config",
"aws-sdk-s3",
"base64 0.22.1",
"ffprobe",
"image",
"imagesize",
- "jxl-oxide",
+ "jxl-oxide 0.12.5",
"resvg",
"revolt-config",
"revolt-result",
"tempfile",
+ "thiserror 2.0.18",
"tiny-skia",
+ "tokio 1.49.0",
"tracing",
"typenum",
"usvg",
+ "uuid",
"webp",
]
[[package]]
name = "revolt-gifbox"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"axum",
"axum-extra",
@@ -6514,7 +7048,8 @@ dependencies = [
"revolt-result",
"serde",
"serde_json",
- "tokio 1.47.1",
+ "tokio 1.49.0",
+ "tower-http 0.5.2",
"tracing",
"utoipa",
"utoipa-scalar",
@@ -6522,7 +7057,7 @@ dependencies = [
[[package]]
name = "revolt-january"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"async-recursion",
"axum",
@@ -6541,7 +7076,7 @@ dependencies = [
"serde",
"serde_json",
"tempfile",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tracing",
"tracing-subscriber",
"utoipa",
@@ -6550,7 +7085,7 @@ dependencies = [
[[package]]
name = "revolt-models"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"indexmap 1.9.3",
"iso8601-timestamp",
@@ -6569,14 +7104,14 @@ dependencies = [
[[package]]
name = "revolt-parser"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"logos",
]
[[package]]
name = "revolt-permissions"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"async-std",
"async-trait",
@@ -6591,7 +7126,7 @@ dependencies = [
[[package]]
name = "revolt-presence"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"async-std",
"log",
@@ -6603,7 +7138,7 @@ dependencies = [
[[package]]
name = "revolt-pushd"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"amqprs",
"anyhow",
@@ -6615,23 +7150,26 @@ dependencies = [
"iso8601-timestamp",
"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.49.0",
"ulid 1.2.1",
"web-push",
]
[[package]]
name = "revolt-ratelimits"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"async-trait",
"authifier",
@@ -6648,18 +7186,48 @@ dependencies = [
[[package]]
name = "revolt-result"
-version = "0.8.9"
+version = "0.12.0"
dependencies = [
"axum",
+ "log",
"revolt_okapi",
"revolt_rocket_okapi",
"rocket",
"schemars 0.8.22",
+ "sentry",
"serde",
"serde_json",
"utoipa",
]
+[[package]]
+name = "revolt-voice-ingress"
+version = "0.12.0"
+dependencies = [
+ "amqprs",
+ "async-std",
+ "chrono",
+ "futures",
+ "livekit-api",
+ "livekit-protocol",
+ "livekit-runtime 0.3.1",
+ "log",
+ "lru 0.7.8",
+ "redis-kiss",
+ "revolt-config",
+ "revolt-database",
+ "revolt-models",
+ "revolt-permissions",
+ "revolt-result",
+ "rmp-serde",
+ "rocket",
+ "rocket_empty",
+ "sentry",
+ "serde",
+ "serde_json",
+ "ulid 0.5.0",
+]
+
[[package]]
name = "revolt_a2"
version = "0.10.1"
@@ -6681,7 +7249,7 @@ dependencies = [
"serde",
"serde_json",
"thiserror 1.0.69",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
@@ -6821,7 +7389,7 @@ dependencies = [
"either",
"figment",
"futures",
- "indexmap 2.11.1",
+ "indexmap 2.13.0",
"log",
"memchr",
"multer",
@@ -6837,7 +7405,7 @@ dependencies = [
"state",
"tempfile",
"time",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tokio-stream",
"tokio-util",
"ubyte",
@@ -6847,9 +7415,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",
@@ -6869,7 +7437,7 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46"
dependencies = [
"devise",
"glob",
- "indexmap 2.11.1",
+ "indexmap 2.13.0",
"proc-macro2",
"quote 1.0.40",
"rocket_http",
@@ -6916,7 +7484,7 @@ dependencies = [
"futures",
"http 0.2.12",
"hyper 0.14.32",
- "indexmap 2.11.1",
+ "indexmap 2.13.0",
"log",
"memchr",
"pear",
@@ -6928,7 +7496,7 @@ dependencies = [
"stable-pattern",
"state",
"time",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"uncased",
]
@@ -7074,16 +7642,16 @@ dependencies = [
[[package]]
name = "rustls"
-version = "0.23.31"
+version = "0.23.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0ebcbd2f03de0fc1122ad9bb24b127a5a6cd51d72604a3f3c50ac459762b6cc"
+checksum = "cd3c25631629d034ce7cd9940adc9d45762d46de2b0f57193c4443b92c6d4d40"
dependencies = [
"aws-lc-rs",
"log",
"once_cell",
"ring",
"rustls-pki-types",
- "rustls-webpki 0.103.5",
+ "rustls-webpki 0.103.6",
"subtle",
"zeroize",
]
@@ -7162,9 +7730,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
-version = "0.103.5"
+version = "0.103.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5a37813727b78798e53c2bec3f5e8fe12a6d6f8389bf9ca7802add4c9905ad8"
+checksum = "8572f3c2cb9934231157b45499fc41e1f58c589fdfb81a844ba873265e80f8eb"
dependencies = [
"aws-lc-rs",
"ring",
@@ -7419,7 +7987,7 @@ dependencies = [
"sentry-debug-images",
"sentry-panic",
"sentry-tracing",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"ureq",
]
@@ -7556,7 +8124,7 @@ dependencies = [
"rand 0.9.2",
"serde",
"serde_json",
- "thiserror 2.0.16",
+ "thiserror 2.0.18",
"time",
"url",
"uuid",
@@ -7564,9 +8132,9 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.223"
+version = "1.0.225"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a505d71960adde88e293da5cb5eda57093379f64e61cf77bf0e6a63af07a7bac"
+checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d"
dependencies = [
"serde_core",
"serde_derive",
@@ -7574,9 +8142,9 @@ dependencies = [
[[package]]
name = "serde_bytes"
-version = "0.11.18"
+version = "0.11.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fe07b5d88710e3b807c16a06ccbc9dfecd5fff6a4d2745c59e3e26774f10de6a"
+checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8"
dependencies = [
"serde",
"serde_core",
@@ -7593,18 +8161,18 @@ dependencies = [
[[package]]
name = "serde_core"
-version = "1.0.223"
+version = "1.0.225"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20f57cbd357666aa7b3ac84a90b4ea328f1d4ddb6772b430caa5d9e1309bb9e9"
+checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.223"
+version = "1.0.225"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3d428d07faf17e306e699ec1e91996e5a165ba5d6bce5b5155173e91a8a01a56"
+checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516"
dependencies = [
"proc-macro2",
"quote 1.0.40",
@@ -7628,7 +8196,7 @@ version = "1.0.145"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
dependencies = [
- "indexmap 2.11.1",
+ "indexmap 2.13.0",
"itoa",
"memchr",
"ryu",
@@ -7638,9 +8206,9 @@ dependencies = [
[[package]]
name = "serde_path_to_error"
-version = "0.1.19"
+version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a30a8abed938137c7183c173848e3c9b3517f5e038226849a4ecc9b21a4b4e2a"
+checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
dependencies = [
"itoa",
"serde",
@@ -7670,15 +8238,15 @@ dependencies = [
[[package]]
name = "serde_with"
-version = "3.14.0"
+version = "3.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5"
+checksum = "c522100790450cf78eeac1507263d0a350d4d5b30df0c8e1fe051a10c22b376e"
dependencies = [
"base64 0.22.1",
"chrono",
"hex",
"indexmap 1.9.3",
- "indexmap 2.11.1",
+ "indexmap 2.13.0",
"schemars 0.9.0",
"schemars 1.0.4",
"serde",
@@ -7690,11 +8258,11 @@ dependencies = [
[[package]]
name = "serde_with_macros"
-version = "3.14.0"
+version = "3.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f"
+checksum = "327ada00f7d64abaac1e55a6911e90cf665aa051b9a561c7006c157f4633135e"
dependencies = [
- "darling 0.20.11",
+ "darling 0.21.3",
"proc-macro2",
"quote 1.0.40",
"syn 2.0.106",
@@ -8009,7 +8577,7 @@ 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",
"rustversion",
@@ -8168,19 +8736,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"
@@ -8199,12 +8754,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
-[[package]]
-name = "target-lexicon"
-version = "0.12.16"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
-
[[package]]
name = "tempfile"
version = "3.22.0"
@@ -8249,11 +8798,11 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.16"
+version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
+checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
- "thiserror-impl 2.0.16",
+ "thiserror-impl 2.0.18",
]
[[package]]
@@ -8269,9 +8818,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
-version = "2.0.16"
+version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
+checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote 1.0.40",
@@ -8287,6 +8836,12 @@ 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.10.3"
@@ -8298,16 +8853,17 @@ dependencies = [
"half",
"quick-error 2.0.1",
"weezl",
- "zune-jpeg",
+ "zune-jpeg 0.4.21",
]
[[package]]
name = "time"
-version = "0.3.43"
+version = "0.3.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83bde6f1ec10e72d583d91623c939f623002284ef622b87de38cfd546cbf2031"
+checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
dependencies = [
"deranged",
+ "itoa",
"libc",
"num-conv",
"num_threads",
@@ -8406,29 +8962,26 @@ dependencies = [
[[package]]
name = "tokio"
-version = "1.47.1"
+version = "1.49.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89e49afdadebb872d3145a5638b59eb0691ea23e46ca484037cfab3b76b95038"
+checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
dependencies = [
- "backtrace",
"bytes 1.10.1",
- "io-uring",
"libc",
"mio",
"parking_lot",
"pin-project-lite 0.2.16",
"signal-hook-registry",
- "slab",
"socket2 0.6.0",
"tokio-macros",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.0",
]
[[package]]
name = "tokio-macros"
-version = "2.5.0"
+version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
+checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5"
dependencies = [
"proc-macro2",
"quote 1.0.40",
@@ -8442,7 +8995,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
@@ -8452,7 +9005,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls 0.21.12",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
@@ -8463,17 +9016,17 @@ checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f"
dependencies = [
"rustls 0.22.4",
"rustls-pki-types",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
name = "tokio-rustls"
-version = "0.26.2"
+version = "0.26.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
+checksum = "05f63835928ca123f1bef57abbcd23bb2ba0ac9ae1235f1e65bda0d06e7786bd"
dependencies = [
- "rustls 0.23.31",
- "tokio 1.47.1",
+ "rustls 0.23.32",
+ "tokio 1.49.0",
]
[[package]]
@@ -8484,7 +9037,7 @@ checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
dependencies = [
"futures-core",
"pin-project-lite 0.2.16",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
@@ -8498,7 +9051,7 @@ dependencies = [
"futures-io",
"futures-sink",
"pin-project-lite 0.2.16",
- "tokio 1.47.1",
+ "tokio 1.49.0",
]
[[package]]
@@ -8537,7 +9090,7 @@ version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
- "indexmap 2.11.1",
+ "indexmap 2.13.0",
"toml_datetime",
"winnow 0.5.40",
]
@@ -8548,7 +9101,7 @@ version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
- "indexmap 2.11.1",
+ "indexmap 2.13.0",
"serde",
"serde_spanned",
"toml_datetime",
@@ -8584,7 +9137,7 @@ dependencies = [
"futures-util",
"pin-project-lite 0.2.16",
"sync_wrapper 1.0.2",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tower-layer",
"tower-service",
"tracing",
@@ -8638,9 +9191,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
-version = "0.1.41"
+version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"log",
"pin-project-lite 0.2.16",
@@ -8650,9 +9203,9 @@ dependencies = [
[[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",
@@ -8661,9 +9214,9 @@ dependencies = [
[[package]]
name = "tracing-core"
-version = "0.1.34"
+version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678"
+checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
"valuable",
@@ -8692,9 +9245,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
-version = "0.3.20"
+version = "0.3.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5"
+checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e"
dependencies = [
"matchers",
"nu-ansi-term",
@@ -9026,7 +9579,7 @@ version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23"
dependencies = [
- "indexmap 2.11.1",
+ "indexmap 2.13.0",
"serde",
"serde_json",
"utoipa-gen",
@@ -9060,13 +9613,13 @@ dependencies = [
[[package]]
name = "uuid"
-version = "1.18.1"
+version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2"
+checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a"
dependencies = [
"getrandom 0.3.3",
"js-sys",
- "serde",
+ "serde_core",
"wasm-bindgen",
]
@@ -9174,12 +9727,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"
@@ -9215,18 +9762,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasi"
-version = "0.14.5+wasi-0.2.4"
+version = "0.14.7+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4494f6290a82f5fe584817a676a34b9d6763e8d9d18204009fb31dceca98fd4"
+checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
dependencies = [
"wasip2",
]
[[package]]
name = "wasip2"
-version = "1.0.0+wasi-0.2.4"
+version = "1.0.1+wasi-0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "03fa2761397e5bd52002cd7e73110c71af2109aca4e521a9f40473fe685b0a24"
+checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
dependencies = [
"wit-bindgen",
]
@@ -9242,9 +9789,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
-version = "0.2.101"
+version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7e14915cadd45b529bb8d1f343c4ed0ac1de926144b746e2710f9cd05df6603b"
+checksum = "ab10a69fbd0a177f5f649ad4d8d3305499c42bab9aef2f7ff592d0ec8f833819"
dependencies = [
"cfg-if",
"once_cell",
@@ -9255,9 +9802,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.101"
+version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e28d1ba982ca7923fd01448d5c30c6864d0a14109560296a162f80f305fb93bb"
+checksum = "0bb702423545a6007bbc368fde243ba47ca275e549c8a28617f56f6ba53b1d1c"
dependencies = [
"bumpalo",
"log",
@@ -9269,9 +9816,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.51"
+version = "0.4.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ca85039a9b469b38336411d6d6ced91f3fc87109a2a27b0c197663f5144dffe"
+checksum = "a0b221ff421256839509adbb55998214a70d829d3a28c69b4a6672e9d2a42f67"
dependencies = [
"cfg-if",
"js-sys",
@@ -9282,9 +9829,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.101"
+version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7c3d463ae3eff775b0c45df9da45d68837702ac35af998361e2c84e7c5ec1b0d"
+checksum = "fc65f4f411d91494355917b605e1480033152658d71f722a90647f56a70c88a0"
dependencies = [
"quote 1.0.40",
"wasm-bindgen-macro-support",
@@ -9292,9 +9839,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.101"
+version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7bb4ce89b08211f923caf51d527662b75bdc9c9c7aab40f86dcb9fb85ac552aa"
+checksum = "ffc003a991398a8ee604a401e194b6b3a39677b3173d6e74495eb51b82e99a32"
dependencies = [
"proc-macro2",
"quote 1.0.40",
@@ -9305,9 +9852,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.101"
+version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f143854a3b13752c6950862c906306adb27c7e839f7414cec8fea35beab624c1"
+checksum = "293c37f4efa430ca14db3721dfbe48d8c33308096bd44d80ebaa775ab71ba1cf"
dependencies = [
"unicode-ident",
]
@@ -9336,9 +9883,9 @@ dependencies = [
[[package]]
name = "web-sys"
-version = "0.3.78"
+version = "0.3.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77e4b637749ff0d92b8fad63aa1f7cff3cbe125fd49c175cd6345e7272638b12"
+checksum = "fbe734895e869dc429d78c4b433f8d17d95f8d05317440b4fad5ab2d33e596dc"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -9845,9 +10392,9 @@ dependencies = [
[[package]]
name = "wit-bindgen"
-version = "0.45.1"
+version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c573471f125075647d03df72e026074b7203790d41351cd6edc96f46bcccd36"
+checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
[[package]]
name = "writeable"
@@ -9876,6 +10423,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"
@@ -9940,7 +10493,7 @@ dependencies = [
"serde",
"serde_json",
"time",
- "tokio 1.47.1",
+ "tokio 1.49.0",
"tower-service",
"url",
]
@@ -10031,6 +10584,12 @@ 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"
@@ -10046,5 +10605,14 @@ version = "0.4.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29ce2c8a9384ad323cf564b67da86e21d3cfdff87908bc1223ed5c99bc792713"
dependencies = [
- "zune-core",
+ "zune-core 0.4.12",
+]
+
+[[package]]
+name = "zune-jpeg"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2959ca473aae96a14ecedf501d20b3608d2825ba280d5adb57d651721885b0c2"
+dependencies = [
+ "zune-core 0.5.1",
]
diff --git a/Cargo.toml b/Cargo.toml
index 5c320e63..10989fb1 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,9 +14,37 @@ 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 = { version = "1.49.0", features = ["macros", "rt"] }
+
+# Error Handling
+anyhow = "1.0.100"
+thiserror = "2.0.18"
+
+# Other Utilities
+uuid = { version = "1.19.0", features = ["v4"] }
+
+# Axum (HTTP server)
+axum-macros = "0.4.1"
+axum_typed_multipart = "0.12.1"
+axum = { version = "0.7.5", features = ["multipart"] }
+tower-http = { version = "0.5.2", features = ["cors", "trace"] }
+
+# Image Processing
+jxl-oxide = { version = "0.12.5", features = ["image"] }
+image = "0.25.9"
+
+# 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 = { version = "0.31.1" }
diff --git a/Dockerfile b/Dockerfile
index 7c9a1e44..aca233ec 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
@@ -34,6 +34,7 @@ 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 ac9a438f..9bcce8ac 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
@@ -30,6 +30,7 @@ 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 1f1ccbb0..e090149e 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 | |
@@ -37,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:
@@ -73,11 +70,20 @@ As a heads-up, the development environment uses the following ports:
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.
@@ -114,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
@@ -126,53 +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 tenor proxy
-cargo run --bin revolt-gifbox
-# 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
@@ -201,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:
@@ -220,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 33e04434..7398ed53 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 7999b388..00000000
--- 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 89b29594..a01c965e 100644
--- a/compose.yml
+++ b/compose.yml
@@ -12,6 +12,10 @@ services:
- "27017:27017"
volumes:
- ./.data/db:/data/db
+ ulimits:
+ nofile:
+ soft: 65536
+ hard: 65536
# MinIO
minio:
@@ -40,7 +44,7 @@ services:
# Rabbit
rabbit:
- image: rabbitmq:3-management
+ image: rabbitmq:4-management
environment:
RABBITMQ_DEFAULT_USER: rabbituser
RABBITMQ_DEFAULT_PASS: rabbitpass
@@ -55,7 +59,7 @@ services:
# Mock SMTP server
maildev:
- image: soulteary/maildev
+ image: maildev/maildev
ports:
- "14025:25"
- "14080:8080"
@@ -64,3 +68,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 1928fb46..e4481b1c 100644
--- a/crates/bonfire/Cargo.toml
+++ b/crates/bonfire/Cargo.toml
@@ -1,8 +1,9 @@
[package]
name = "revolt-bonfire"
-version = "0.8.9"
+version = "0.12.0"
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
@@ -37,12 +38,12 @@ async-std = { version = "1.8.0", features = [
] }
# core
-authifier = { version = "1.0.15" }
+authifier = { version = "1.0.16" }
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.9", path = "../core/permissions" }
+revolt-database = { path = "../core/database", features = ["voice"] }
+revolt-permissions = { path = "../core/permissions" }
revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
# redis
diff --git a/crates/bonfire/src/config.rs b/crates/bonfire/src/config.rs
index 05f7cf5a..d70a6320 100644
--- a/crates/bonfire/src/config.rs
+++ b/crates/bonfire/src/config.rs
@@ -136,6 +136,7 @@ impl handshake::server::Callback for WebsocketHandshakeCallback {
channels: false,
members: false,
emojis: false,
+ voice_states: false,
user_settings: Vec::new(),
channel_unreads: false,
policy_changes: false,
@@ -168,6 +169,7 @@ impl handshake::server::Callback for WebsocketHandshakeCallback {
"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) {
diff --git a/crates/bonfire/src/events/impl.rs b/crates/bonfire/src/events/impl.rs
index 83b0d4c6..12113537 100644
--- a/crates/bonfire/src/events/impl.rs
+++ b/crates/bonfire/src/events/impl.rs
@@ -1,9 +1,10 @@
-use std::collections::HashSet;
+use std::collections::{HashMap, HashSet};
use futures::future::join_all;
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 +18,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 =
@@ -122,12 +124,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?;
@@ -156,6 +153,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;
@@ -169,6 +215,12 @@ impl State {
)
.await?;
+ self.cache.members = members
+ .iter()
+ .cloned()
+ .map(|x| (x.id.server.clone(), x))
+ .collect();
+
// Fetch customisations.
let emojis = if fields.emojis {
Some(
@@ -268,6 +320,8 @@ impl State {
} else {
None
},
+ voice_states,
+
emojis,
user_settings,
channel_unreads,
@@ -285,19 +339,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 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());
- }
- }
+ if self.cache.can_view_channel(db, channel).await {
+ added_channels.push(channel_id.clone());
+ } else {
+ removed_channels.push(channel_id.clone());
}
- _ => {}
}
}
@@ -352,6 +401,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 {
@@ -465,6 +519,7 @@ impl State {
server,
channels,
emojis: _,
+ voice_states: _,
} => {
self.insert_subscription(id.clone()).await;
diff --git a/crates/bonfire/src/websocket.rs b/crates/bonfire/src/websocket.rs
index 913245e9..ec4202dc 100644
--- a/crates/bonfire/src/websocket.rs
+++ b/crates/bonfire/src/websocket.rs
@@ -218,7 +218,13 @@ async fn listener(
kill_signal_r: async_channel::Receiver<()>,
write: &Mutex,
) {
- let redis_config = RedisConfig::from_url(&REDIS_URI).unwrap();
+ 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 subscriber = match report_internal_error!(
fred::types::Builder::from_config(redis_config).build_subscriber_client()
) {
@@ -422,6 +428,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 +466,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 +482,10 @@ async fn worker(
.await;
}
ClientMessage::EndTyping { channel } => {
+ if revolt_config.disable_events_dont_use {
+ continue;
+ }
+
if !subscribed.read().await.contains(&channel) {
continue;
}
diff --git a/crates/core/coalesced/Cargo.toml b/crates/core/coalesced/Cargo.toml
index 32dad132..4b589c0d 100644
--- a/crates/core/coalesced/Cargo.toml
+++ b/crates/core/coalesced/Cargo.toml
@@ -1,10 +1,11 @@
[package]
name = "revolt-coalesced"
-version = "0.8.9"
+version = "0.12.0"
edition = "2021"
license = "MIT"
authors = ["Paul Makles ", "Zomatree "]
description = "Revolt Backend: Coalescion service"
+repository = "https://github.com/stoatchat/stoatchat"
[features]
tokio = ["dep:tokio"]
@@ -15,8 +16,13 @@ default = ["tokio"]
[dependencies]
tokio = { version = "1.47.0", features = ["sync"], optional = true }
-indexmap = { version = "*", optional = true }
-lru = { version = "*", optional = true }
+indexmap = { version = "2.13.0", optional = true }
+lru = { version = "0.16.3", optional = true }
[dev-dependencies]
-tokio = { version = "1.47.0", features = ["rt", "rt-multi-thread", "macros", "time"] }
+tokio = { version = "1.47.0", features = [
+ "rt",
+ "rt-multi-thread",
+ "macros",
+ "time",
+] }
diff --git a/crates/core/config/Cargo.toml b/crates/core/config/Cargo.toml
index 2a6dbb9e..ad1ee469 100644
--- a/crates/core/config/Cargo.toml
+++ b/crates/core/config/Cargo.toml
@@ -1,10 +1,11 @@
[package]
name = "revolt-config"
-version = "0.8.9"
+version = "0.12.0"
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
@@ -37,4 +38,4 @@ sentry = { version = "0.31.5", optional = true }
sentry-anyhow = { version = "0.38.1", optional = true }
# Core
-revolt-result = { version = "0.8.9", path = "../result", optional = true }
+revolt-result = { version = "0.12.0", path = "../result", optional = true }
diff --git a/crates/core/config/Revolt.toml b/crates/core/config/Revolt.toml
index 446a22da..f701622d 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,6 +23,8 @@ january = "http://local.revolt.chat/january"
voso_legacy = ""
voso_legacy_ws = ""
+[hosts.livekit]
+
[rabbit]
host = "rabbit"
port = 5672
@@ -32,7 +35,7 @@ password = "rabbitpass"
[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]
@@ -71,8 +74,13 @@ 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]
[pushd]
# this changes the names of the queues to not overlap
@@ -84,12 +92,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
@@ -218,6 +232,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
@@ -236,6 +254,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
@@ -263,6 +293,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
@@ -281,8 +323,9 @@ process_message_delay_limit = 5
# Configuration for Sentry error reporting
api = ""
events = ""
+voice_ingress = ""
files = ""
proxy = ""
pushd = ""
crond = ""
-gifbox = ""
\ No newline at end of file
+gifbox = ""
diff --git a/crates/core/config/src/lib.rs b/crates/core/config/src/lib.rs
index 0084abed..41dc94d4 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,7 @@ static CONFIG_BUILDER: Lazy> = Lazy::new(|| {
pub struct Database {
pub mongodb: String,
pub redis: String,
+ pub redis_pubsub: Option,
}
#[derive(Deserialize, Debug, Clone)]
@@ -125,8 +137,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)]
@@ -199,6 +210,25 @@ 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,
@@ -210,6 +240,7 @@ pub struct Api {
pub smtp: ApiSmtp,
pub security: ApiSecurity,
pub workers: ApiWorkers,
+ pub livekit: ApiLiveKit,
pub users: ApiUsers,
}
@@ -218,10 +249,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,
@@ -252,6 +285,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())
}
@@ -309,6 +346,8 @@ pub struct GlobalLimits {
pub new_user_hours: usize,
pub body_limit_size: usize,
+
+ pub restrict_server_creation: Vec,
}
#[derive(Deserialize, Debug, Clone)]
@@ -319,6 +358,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,
}
@@ -365,6 +408,7 @@ 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,
@@ -383,6 +427,7 @@ pub struct Settings {
pub features: Features,
pub sentry: Sentry,
pub production: bool,
+ pub disable_events_dont_use: bool,
}
impl Settings {
@@ -413,12 +458,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 47095e3d..05a66179 100644
--- a/crates/core/database/Cargo.toml
+++ b/crates/core/database/Cargo.toml
@@ -1,10 +1,11 @@
[package]
name = "revolt-database"
-version = "0.8.9"
+version = "0.12.0"
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,28 +16,35 @@ 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.9", path = "../config", features = [
+revolt-config = { version = "0.12.0", path = "../config", features = [
"report-macros",
] }
-revolt-result = { version = "0.8.9", path = "../result" }
-revolt-models = { version = "0.8.9", path = "../models", features = [
+revolt-result = { version = "0.12.0", path = "../result" }
+revolt-models = { version = "0.12.0", path = "../models", features = [
"validator",
] }
-revolt-presence = { version = "0.8.9", path = "../presence" }
-revolt-permissions = { version = "0.8.9", path = "../permissions", features = [
+revolt-presence = { version = "0.12.0", path = "../presence" }
+revolt-permissions = { version = "0.12.0", path = "../permissions", features = [
"serde",
"bson",
] }
-revolt-parser = { version = "0.8.9", path = "../parser" }
+revolt-parser = { version = "0.12.0", path = "../parser" }
# Utility
log = "0.4"
@@ -92,7 +100,12 @@ revolt_okapi = { version = "0.9.1", optional = true }
revolt_rocket_okapi = { version = "0.10.0", optional = true }
# Authifier
-authifier = { version = "1.0.15" }
+authifier = { version = "1.0.16" }
# RabbitMQ
amqprs = { version = "1.7.0" }
+
+# Voice
+livekit-api = { version = "0.4.4", optional = true }
+livekit-protocol = { version = "0.4.0", optional = true }
+livekit-runtime = { version = "0.3.1", 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 0a76489d..21ea5656 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 b567c7f7..b29105ed 100644
--- a/crates/core/database/src/amqp/amqp.rs
+++ b/crates/core/database/src/amqp/amqp.rs
@@ -2,7 +2,8 @@ use std::collections::HashSet;
use crate::events::rabbit::*;
use crate::User;
-use amqprs::channel::BasicPublishArguments;
+use amqprs::channel::{BasicPublishArguments, ExchangeDeclareArguments};
+use amqprs::connection::OpenConnectionArguments;
use amqprs::{channel::Channel, connection::Connection, error::Error as AMQPError};
use amqprs::{BasicProperties, FieldTable};
use revolt_models::v0::PushNotification;
@@ -25,6 +26,35 @@ impl AMQP {
}
}
+ pub async fn new_auto() -> AMQP {
+ let config = revolt_config::config().await;
+
+ let connection = Connection::open(&OpenConnectionArguments::new(
+ &config.rabbit.host,
+ config.rabbit.port,
+ &config.rabbit.username,
+ &config.rabbit.password,
+ ))
+ .await
+ .expect("Failed to connect to RabbitMQ");
+
+ let channel = connection
+ .open_channel(None)
+ .await
+ .expect("Failed to open RabbitMQ channel");
+
+ channel
+ .exchange_declare(
+ ExchangeDeclareArguments::new(&config.pushd.exchange, "direct")
+ .durable(true)
+ .finish(),
+ )
+ .await
+ .expect("Failed to declare exchange");
+
+ AMQP::new(connection, channel)
+ }
+
pub async fn friend_request_accepted(
&self,
accepted_request_user: &User,
@@ -240,4 +270,50 @@ impl AMQP {
)
.await
}
+
+ /// # 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.channel
+ .basic_publish(
+ BasicProperties::default()
+ .with_content_type("application/json")
+ .with_persistence(true)
+ .finish(),
+ payload.into(),
+ BasicPublishArguments::new(
+ &config.pushd.exchange,
+ &config.pushd.get_dm_call_routing_key(),
+ ),
+ )
+ .await
+ }
}
diff --git a/crates/core/database/src/drivers/mod.rs b/crates/core/database/src/drivers/mod.rs
index 2a2c7b85..df9a56ef 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 fc5dbe3c..baeddb1d 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 680e7959..1c1a7ba4 100644
--- a/crates/core/database/src/drivers/reference.rs
+++ b/crates/core/database/src/drivers/reference.rs
@@ -14,7 +14,7 @@ use crate::{
database_derived!(
/// Reference implementation
- #[derive(Default)]
+ #[derive(Default, Debug)]
pub struct ReferenceDb {
pub admin_audits: Arc>>,
pub admin_cases: Arc>>,
diff --git a/crates/core/database/src/events/client.rs b/crates/core/database/src/events/client.rs
index 996347ca..4615273d 100644
--- a/crates/core/database/src/events/client.rs
+++ b/crates/core/database/src/events/client.rs
@@ -3,10 +3,7 @@ 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, ChannelUnread, ChannelVoiceState, Emoji, FieldsChannel, FieldsMember, FieldsMessage, FieldsRole, FieldsServer, FieldsUser, FieldsWebhook, Member, MemberCompositeKey, Message, PartialChannel, PartialMember, PartialMessage, PartialRole, PartialServer, PartialUser, PartialUserVoiceState, PartialWebhook, PolicyChange, RemovalIntention, Report, Server, User, UserSettings, UserVoiceState, Webhook
};
use crate::Database;
@@ -27,6 +24,7 @@ pub struct ReadyPayloadFields {
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,
@@ -40,6 +38,7 @@ impl Default for ReadyPayloadFields {
channels: true,
members: true,
emojis: true,
+ voice_states: true,
user_settings: Vec::new(),
channel_unreads: false,
policy_changes: true,
@@ -72,6 +71,8 @@ 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,
@@ -138,6 +139,7 @@ pub enum EventV1 {
server: Server,
channels: Vec,
emojis: Vec,
+ voice_states: Vec
},
/// Update existing server
@@ -270,6 +272,33 @@ pub enum EventV1 {
/// 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,
+ }
}
impl EventV1 {
diff --git a/crates/core/database/src/events/rabbit.rs b/crates/core/database/src/events/rabbit.rs
index 0ae91be1..6f5c9ab3 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)]
diff --git a/crates/core/database/src/lib.rs b/crates/core/database/src/lib.rs
index f9827d03..778206dc 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 d164cab1..174a81a6 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 e3af921a..a5d877fe 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 673e5e50..025b7934 100644
--- a/crates/core/database/src/models/channels/model.rs
+++ b/crates/core/database/src/models/channels/model.rs
@@ -1,4 +1,5 @@
-use std::collections::HashMap;
+#![allow(deprecated)]
+use std::{borrow::Cow, collections::HashMap};
use revolt_config::config;
use revolt_models::v0::{self, MessageAuthor};
@@ -8,8 +9,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,38 +106,22 @@ auto_derived!(
/// Whether this channel is marked as not safe for work
#[serde(skip_serializing_if = "crate::if_false", default)]
nsfw: bool,
+
+ /// Voice Information for when this channel is also a voice channel
+ #[serde(skip_serializing_if = "Option::is_none")]
+ voice: Option,
+
+ /// The channel's slowmode delay in seconds
+ #[serde(skip_serializing_if = "Option::is_none")]
+ slowmode: Option,
},
- /// 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,
- #[serde(skip_serializing_if = "Option::is_none")]
- /// Channel description
- description: Option,
- /// Custom icon attachment
- #[serde(skip_serializing_if = "Option::is_none")]
- icon: Option,
-
- /// Default permissions assigned to users in this channel
- #[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,
- },
+ #[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,
}
);
@@ -164,6 +148,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 +159,7 @@ auto_derived!(
Description,
Icon,
DefaultPermissions,
+ Voice,
}
);
@@ -222,16 +211,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 +426,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 +464,6 @@ impl Channel {
server,
role_permissions,
..
- }
- | Channel::VoiceChannel {
- id,
- server,
- role_permissions,
- ..
} => {
db.set_channel_role_permission(id, role_id, permissions)
.await?;
@@ -502,7 +510,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 +522,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 +537,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 +559,7 @@ impl Channel {
}
/// Apply partial channel to channel
+ #[allow(deprecated)]
pub fn apply_options(&mut self, partial: PartialChannel) {
match self {
Self::SavedMessages { .. } => {}
@@ -601,15 +608,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,6 +634,10 @@ impl Channel {
if let Some(v) = partial.default_permissions {
default_permissions.replace(v);
}
+
+ if let Some(v) = partial.voice {
+ voice.replace(v);
+ }
}
}
}
@@ -777,6 +780,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 b247a683..79612f8d 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 7d5c9a59..518808bc 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/file_hashes/model.rs b/crates/core/database/src/models/file_hashes/model.rs
index cec7a39d..dfc9b96e 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 4b249e4c..cdd6c936 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 e2f06067..9e67a0a5 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 7733523e..3c54d9e2 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/messages/model.rs b/crates/core/database/src/models/messages/model.rs
index 53d38012..bacb3c53 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 4d54e83c..6ebdf4c3 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 449e71ef..2a79ed5b 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 ae5b43f0..6f30de6f 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;
@@ -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 142fc33c..bab3cf8d 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,
}
}
}
@@ -128,6 +148,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(),
@@ -145,6 +179,7 @@ impl Member {
.map(|channel| channel.into())
.collect(),
emojis: emojis.into_iter().map(|emoji| emoji.into()).collect(),
+ voice_states,
}
.private(user.id.clone())
.await;
@@ -196,11 +231,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 cd7c891f..4045a66e 100644
--- a/crates/core/database/src/models/server_members/ops/mongodb.rs
+++ b/crates/core/database/src/models/server_members/ops/mongodb.rs
@@ -414,12 +414,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 eea8f392..183dd3d2 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
@@ -246,7 +249,6 @@ impl Server {
role.update(
db,
&self.id,
- role_id,
PartialRole {
permissions: Some(permissions),
..Default::default()
@@ -297,6 +299,7 @@ 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,
@@ -306,20 +309,29 @@ impl Role {
}
/// 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(),
+ };
+
+ 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 +339,6 @@ impl Role {
&mut self,
db: &Database,
server_id: &str,
- role_id: &str,
partial: PartialRole,
remove: Vec,
) -> Result<()> {
@@ -337,14 +348,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;
@@ -360,15 +371,15 @@ impl Role {
}
/// 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 ee07ba8f..c7ae6228 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 958963c9..ecdff53b 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: to_document(role)
.map_err(|_| create_database_error!("to_document", "role"))?
}
},
diff --git a/crates/core/database/src/models/servers/ops/reference.rs b/crates/core/database/src/models/servers/ops/reference.rs
index 18ee1b38..572311bd 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/tasks/ack.rs b/crates/core/database/src/tasks/ack.rs
index c12e41f3..c26de03b 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)]
@@ -191,17 +191,14 @@ 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/bridge/v0.rs b/crates/core/database/src/util/bridge/v0.rs
index 36b2115e..c9df432f 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 }
+ }
}
}
}
@@ -640,6 +635,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,
}
}
}
@@ -653,6 +650,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,
}
}
}
@@ -666,6 +665,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,
}
}
}
@@ -679,6 +680,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,
}
}
}
@@ -708,7 +711,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,
}
}
}
@@ -720,7 +726,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,
}
}
}
@@ -912,6 +921,7 @@ 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,
@@ -924,6 +934,7 @@ 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,
@@ -936,6 +947,7 @@ 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,
@@ -948,6 +960,7 @@ 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,
@@ -1389,6 +1402,22 @@ 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,
+ }
+ }
+}
+
impl From for AdminUser {
fn from(value: crate::AdminUser) -> Self {
AdminUser {
diff --git a/crates/core/database/src/util/bulk_permissions.rs b/crates/core/database/src/util/bulk_permissions.rs
index 9e7a1f7a..976b0d69 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 00000000..24f9906d
--- /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 065151aa..10c02a5c 100644
--- a/crates/core/database/src/util/mod.rs
+++ b/crates/core/database/src/util/mod.rs
@@ -1,7 +1,10 @@
pub mod basic;
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 e592988e..85db4826 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 00000000..836de92a
--- /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