Compare commits

..

29 Commits

Author SHA1 Message Date
Zomatree
805f2c4528 feat: env var to control rabbitmq event ingress
Signed-off-by: Zomatree <me@zomatree.live>
2026-03-15 19:31:04 +00:00
Zomatree
5a6155fdb6 fix: update incorrect env var name
Signed-off-by: Zomatree <me@zomatree.live>
2026-03-10 01:46:02 +00:00
Zomatree
c5844b6281 fix/hosepipe 2026-03-05 04:24:54 +00:00
Tom
24d0d2b726 fix: no node state set on channel creation (#653)
* fix: no node state set on channel creation

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>

* Update "join_call" logic to handle issue of "user is already connected to a channel that cannot be found"

Fixes stoatchat/for-web#951

Signed-off-by: Chris Hultin <chris.hultin@gmail.com>

* feat: remove node key when channel is empty or deleted

---------

Signed-off-by: IAmTomahawkx <iamtomahawkx@gmail.com>
Signed-off-by: Chris Hultin <chris.hultin@gmail.com>
Co-authored-by: Chris Hultin <chris.hultin@gmail.com>
2026-03-02 20:45:42 -08:00
Kristian Kjærgård
f777e2863c feat: add bug report template for issue tracking (#627)
Signed-off-by: Bobby <31723128+kris6673@users.noreply.github.com>
2026-03-01 11:16:45 -08:00
Jacob Schlecht
a80ad1cbe5 fix: Fix typo for p256dh in vapid notification flow (#622)
* fix: Fix typo for p256dh in vapid notification flow

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

* fix: Revert .clone() change as it works fine without that change

This commit was made without the use of generative AI.

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>

---------

Signed-off-by: Jacob Schlecht <dadadah@echoha.us>
2026-03-01 11:06:13 -08:00
Christopher Hultin
0b522ebddc feat: expose global and user limits in root API response (#644)
* feat: expose global and user limits in root API response

Signed-off-by: Chris Hultin <chris.hultin@gmail.com>

* add missing file upload limits

Signed-off-by: Chris Hultin <chris.hultin@gmail.com>

* extract logic to generate "UserLimits" struct from "config"

Signed-off-by: Chris Hultin <chris.hultin@gmail.com>

* convert user limits from feature limits function to impl function

Signed-off-by: Chris Hultin <chris.hultin@gmail.com>

* picking nits

Signed-off-by: Chris Hultin <chris.hultin@gmail.com>

---------

Signed-off-by: Chris Hultin <chris.hultin@gmail.com>
2026-03-01 11:03:49 -08:00
stoat-tofu[bot]
6bd045eb79 chore: modify .github/workflows/release-webhook.yml 2026-02-20 20:45:25 +00:00
Tom
0698e115e8 fix: default video resolution is a non-existent size (#601)
fixes #588
2026-02-18 10:07:35 +00:00
stoat-release[bot]
8e3765df4c chore(main): release 0.11.5 (#598)
* chore(main): release 0.11.5

* chore: update Cargo.lock

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

---------

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-17 22:14:57 +00:00
Paul Makles
1c98ead695 revert: disable user update events (#593)
* Revert "fix: cut presence traffic too while we engineer a new events architecture (#561)"

This reverts commit 1f8ea963ad.

* Revert "fix: cut events traffic while we engineer a new events architecture (#559)"

This reverts commit a11986ba1a.

* chore: only revert user update
2026-02-17 22:13:17 +00:00
stoat-release[bot]
8849d03cd2 chore(main): release 0.11.4 (#591)
* chore(main): release 0.11.4

* chore: update Cargo.lock

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

---------

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-16 20:02:01 +00:00
Paul Makles
a75e4eabfc fix: add separate config option for redis events replica url (#590) 2026-02-16 19:37:04 +00:00
stoat-release[bot]
74f0c537e6 chore(main): release 0.11.3 (#562)
* chore(main): release 0.11.3

* chore: update Cargo.lock

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

---------

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-13 19:50:32 +00:00
Paul Makles
1f8ea963ad fix: cut presence traffic too while we engineer a new events architecture (#561)
Signed-off-by: izzy <me@insrt.uk>
2026-02-13 19:49:23 +00:00
stoat-release[bot]
7c7f4f24de chore(main): release 0.11.2 (#560)
* chore(main): release 0.11.2

* chore: update Cargo.lock

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

---------

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-13 19:42:13 +00:00
Paul Makles
a11986ba1a fix: cut events traffic while we engineer a new events architecture (#559)
Signed-off-by: izzy <me@insrt.uk>
2026-02-13 19:40:51 +00:00
stoat-release[bot]
2e87696c83 chore(main): release 0.11.1 (#554) 2026-02-13 00:21:29 +00:00
Angelo Kontaxis
94cb916231 fix: bots in multiple voice channel logic (#544) 2026-02-13 00:11:15 +00:00
Paul Makles
95c7dbc793 ci: do not interpolate directly into release workflows (#545) 2026-02-12 16:47:05 +00:00
stoat-release[bot]
ba798490e2 chore(main): release 0.11.0 (#525)
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-10 20:48:36 +00:00
Paul Makles
5132270f2e feat: appeal to the almighty Spamhaus (#524) 2026-02-10 20:46:49 +00:00
Tanner Stirrat
4a0957b5ec Update repo readme header (#511)
Update README.md

Signed-off-by: Tanner Stirrat <tstirrat@gmail.com>
2026-02-09 20:51:56 +00:00
stoat-release[bot]
1bfb2a7214 chore(main): release 0.10.3 (#509)
* chore(main): release 0.10.3

* chore: update Cargo.lock

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

---------

Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-02-09 20:51:30 +00:00
Paul Makles
84483cee7a fix: update Revolt -> Stoat in email titles/desc. (#508) 2026-02-07 16:24:53 +00:00
stoat-release[bot]
4bfd79c42c chore(main): release 0.10.2 (#506)
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-01-25 22:43:56 +00:00
Paul Makles
413aa04dca fix: thumbnailification requires rgb8/rgba8 (#505) 2026-01-25 22:36:31 +00:00
stoat-release[bot]
5d3e65e076 chore(main): release 0.10.1 (#504)
Co-authored-by: stoat-release[bot] <245062572+stoat-release[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-01-25 21:27:20 +00:00
Paul Makles
98da8a28a0 fix: use Rust 1.92.0 for Docker build (#503) 2026-01-25 21:03:34 +00:00
55 changed files with 738 additions and 204 deletions

147
.github/ISSUE_TEMPLATE/bug.yml vendored Normal file
View File

@@ -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.

View File

@@ -45,8 +45,9 @@ jobs:
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 '${{ steps.rp.outputs.prs }}' | jq -r '.[0].number')
PR_NUMBER=$(echo "$RP_PR_OUTPUT_JSON" | jq -r '.[0].number')
gh pr checkout "$PR_NUMBER"
cargo update -w

View File

@@ -12,8 +12,12 @@ jobs:
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/${{ github.repository }}/releases/tag/${{ github.event.release.tag_name }}"
curl -X POST "${{ secrets.STOAT_WEBHOOK_UPDATES_URL }}" \
RELEASE_URL="https://github.com/${REPOSITORY}/releases/tag/${TAG_NAME}"
curl -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-d "{\"content\": \"$RELEASE_URL\"}"
-d "{\"content\": \"$RELEASE_URL\"}"

View File

@@ -1,3 +1,3 @@
{
".": "0.10.0"
".": "0.11.5"
}

View File

@@ -1,5 +1,68 @@
# Changelog
## [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` -&gt; `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)

46
Cargo.lock generated
View File

@@ -475,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",
@@ -6798,7 +6798,7 @@ dependencies = [
[[package]]
name = "revolt-autumn"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"axum",
"axum-macros",
@@ -6836,10 +6836,12 @@ dependencies = [
[[package]]
name = "revolt-bonfire"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"amqprs",
"async-channel 2.5.0",
"async-std",
"async-trait",
"async-tungstenite",
"authifier",
"bincode",
@@ -6867,7 +6869,7 @@ dependencies = [
[[package]]
name = "revolt-coalesced"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"indexmap 2.13.0",
"lru 0.16.3",
@@ -6876,7 +6878,7 @@ dependencies = [
[[package]]
name = "revolt-config"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"async-std",
"cached",
@@ -6893,7 +6895,7 @@ dependencies = [
[[package]]
name = "revolt-crond"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"log",
"revolt-config",
@@ -6905,7 +6907,7 @@ dependencies = [
[[package]]
name = "revolt-database"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"amqprs",
"async-lock 2.8.0",
@@ -6955,7 +6957,7 @@ dependencies = [
[[package]]
name = "revolt-delta"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"amqprs",
"async-channel 1.9.0",
@@ -7005,7 +7007,7 @@ dependencies = [
[[package]]
name = "revolt-files"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"aes-gcm",
"anyhow",
@@ -7033,7 +7035,7 @@ dependencies = [
[[package]]
name = "revolt-gifbox"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"axum",
"axum-extra",
@@ -7056,7 +7058,7 @@ dependencies = [
[[package]]
name = "revolt-january"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"async-recursion",
"axum",
@@ -7084,7 +7086,7 @@ dependencies = [
[[package]]
name = "revolt-models"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"indexmap 1.9.3",
"iso8601-timestamp",
@@ -7103,14 +7105,14 @@ dependencies = [
[[package]]
name = "revolt-parser"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"logos",
]
[[package]]
name = "revolt-permissions"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"async-std",
"async-trait",
@@ -7125,7 +7127,7 @@ dependencies = [
[[package]]
name = "revolt-presence"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"async-std",
"log",
@@ -7137,7 +7139,7 @@ dependencies = [
[[package]]
name = "revolt-pushd"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"amqprs",
"anyhow",
@@ -7165,7 +7167,7 @@ dependencies = [
[[package]]
name = "revolt-ratelimits"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"async-trait",
"authifier",
@@ -7182,7 +7184,7 @@ dependencies = [
[[package]]
name = "revolt-result"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"axum",
"log",
@@ -7198,7 +7200,7 @@ dependencies = [
[[package]]
name = "revolt-voice-ingress"
version = "0.10.0"
version = "0.11.5"
dependencies = [
"amqprs",
"async-std",
@@ -7411,9 +7413,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",

View File

@@ -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

View File

@@ -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

View File

@@ -1,6 +1,6 @@
<div align="center">
<h1>
Revolt Backend
Stoat Backend
[![Stars](https://img.shields.io/github/stars/revoltchat/backend?style=flat-square&logoColor=white)](https://github.com/revoltchat/backend/stargazers)
[![Forks](https://img.shields.io/github/forks/revoltchat/backend?style=flat-square&logoColor=white)](https://github.com/revoltchat/backend/network/members)

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-bonfire"
version = "0.10.0"
version = "0.11.5"
license = "AGPL-3.0-or-later"
edition = "2021"
publish = false
@@ -36,9 +36,10 @@ async-std = { version = "1.8.0", features = [
"tokio02",
"attributes",
] }
async-trait = "0.1.89"
# 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" }
@@ -48,3 +49,6 @@ revolt-presence = { path = "../core/presence", features = ["redis-is-patched"] }
# redis
fred = { version = "8.0.1", features = ["subscriber-client"] }
# Redis
amqprs = { version = "1.7.0" }

View File

@@ -169,7 +169,9 @@ impl State {
}) {
if let Ok(Some(voice_state)) = get_channel_voice_state(channel).await {
if let Some(server) = channel.server() {
let set = voice_state_server_members.entry(server.to_string()).or_default();
let set = voice_state_server_members
.entry(server.to_string())
.or_default();
for participant in &voice_state.participants {
user_ids.insert(participant.id.clone());
@@ -396,28 +398,29 @@ impl State {
}
/// Push presence change to the user and all associated server topics
pub async fn broadcast_presence_change(&self, target: bool) {
if if let Some(status) = &self.cache.users.get(&self.cache.user_id).unwrap().status {
status.presence != Some(Presence::Invisible)
} else {
true
} {
let event = EventV1::UserUpdate {
id: self.cache.user_id.clone(),
data: v0::PartialUser {
online: Some(target),
..Default::default()
},
clear: vec![],
event_id: Some(ulid::Ulid::new().to_string()),
};
pub async fn broadcast_presence_change(&self, _target: bool) {
// disabled events
// if if let Some(status) = &self.cache.users.get(&self.cache.user_id).unwrap().status {
// status.presence != Some(Presence::Invisible)
// } else {
// true
// } {
// let event = EventV1::UserUpdate {
// id: self.cache.user_id.clone(),
// data: v0::PartialUser {
// online: Some(target),
// ..Default::default()
// },
// clear: vec![],
// event_id: Some(ulid::Ulid::new().to_string()),
// };
for server in self.cache.servers.keys() {
event.clone().p(server.clone()).await;
}
// for server in self.cache.servers.keys() {
// event.clone().p(server.clone()).await;
// }
event.p(self.cache.user_id.clone()).await;
}
// event.p(self.cache.user_id.clone()).await;
// }
}
/// Handle an incoming event for protocol version 1

View File

@@ -1,6 +1,18 @@
use std::env;
use amqprs::{
channel::{
BasicConsumeArguments, Channel, ExchangeDeclareArguments, QueueBindArguments,
QueueDeclareArguments,
},
connection::{Connection, OpenConnectionArguments},
consumer::AsyncConsumer,
BasicProperties, Deliver,
};
use async_std::net::TcpListener;
use async_trait::async_trait;
use redis_kiss::AsyncCommands;
use revolt_database::util::rabbit::set_rabbitmq_connection;
use revolt_presence::clear_region;
#[macro_use]
@@ -31,6 +43,55 @@ async fn main() {
let try_socket = TcpListener::bind(bind).await;
let listener = try_socket.expect("Failed to bind");
let config = revolt_config::config().await;
let rmq_conn = Connection::open(&OpenConnectionArguments::new(
&config.rabbit.host,
config.rabbit.port,
&config.rabbit.username,
&config.rabbit.password,
))
.await
.expect("Failed to connect to RabbitMQ");
set_rabbitmq_connection(rmq_conn.clone());
if std::env::var("ENABLE_RABBITMQ_INGRESS").as_deref().is_ok_and(|v| v == "1") {
let channel = rmq_conn
.open_channel(None)
.await
.expect("Failed to open RabbitMQ channel.");
channel
.exchange_declare(
ExchangeDeclareArguments::new("events", "fanout")
.durable(true)
.finish(),
)
.await
.expect("Failed to declare exchange");
channel
.queue_declare(QueueDeclareArguments::new("events").durable(true).finish())
.await
.expect("Failed to declare queue");
channel
.queue_bind(QueueBindArguments::new("events", "events", "events"))
.await
.expect("Failed to bind queue");
channel
.basic_consume(
RabbitToRedisConsumer,
BasicConsumeArguments::new("events", "")
.manual_ack(false)
.finish(),
)
.await
.expect("Failed to consume channel");
}
// Start accepting new connections and spawn a client for each connection.
while let Ok((stream, addr)) = listener.accept().await {
async_std::task::spawn(async move {
@@ -40,3 +101,32 @@ async fn main() {
});
}
}
struct RabbitToRedisConsumer;
#[async_trait]
impl AsyncConsumer for RabbitToRedisConsumer {
async fn consume(
&mut self,
_channel: &Channel,
_deliver: Deliver,
basic_properties: BasicProperties,
content: Vec<u8>,
) {
let mut redis_conn = redis_kiss::get_connection()
.await
.expect("Failed to connect to Redis.");
let pubsub_channel = basic_properties
.headers()
.expect("No headers")
.get(&"c".try_into().unwrap())
.expect("No channel header")
.to_string();
redis_conn
.publish(pubsub_channel, content)
.await
.expect("failed to publish")
}
}

View File

@@ -218,7 +218,13 @@ async fn listener(
kill_signal_r: async_channel::Receiver<()>,
write: &Mutex<WsWriter>,
) {
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()
) {
@@ -457,6 +463,7 @@ async fn worker(
};
match payload {
// disabled events
ClientMessage::BeginTyping { channel } => {
if !subscribed.read().await.contains(&channel) {
continue;

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-coalesced"
version = "0.10.0"
version = "0.11.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>", "Zomatree <me@zomatree.live>"]

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-config"
version = "0.10.0"
version = "0.11.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -38,4 +38,4 @@ sentry = { version = "0.31.5", optional = true }
sentry-anyhow = { version = "0.38.1", optional = true }
# Core
revolt-result = { version = "0.10.0", path = "../result", optional = true }
revolt-result = { version = "0.11.5", path = "../result", optional = true }

View File

@@ -244,7 +244,7 @@ voice_quality = 16000
# Whether the user can use video streams in voice calls
video = true
# Mamimum resolution (width, height) of video streams in voice calls
# 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
@@ -283,8 +283,8 @@ voice_quality = 16000
# Whether the user can use video streams in voice calls
video = true
# Mamimum resolution (width, height) of video streams in voice calls
video_resolution = [1080, 720]
# 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]
@@ -312,4 +312,4 @@ files = ""
proxy = ""
pushd = ""
crond = ""
gifbox = ""
gifbox = ""

View File

@@ -117,6 +117,7 @@ static CONFIG_BUILDER: Lazy<RwLock<Config>> = Lazy::new(|| {
pub struct Database {
pub mongodb: String,
pub redis: String,
pub redis_pubsub: Option<String>,
}
#[derive(Deserialize, Debug, Clone)]
@@ -448,7 +449,7 @@ pub async fn config() -> Settings {
let mut config = read().await.try_deserialize::<Settings>().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());
}

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-database"
version = "0.10.0"
version = "0.11.5"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -32,19 +32,19 @@ default = ["mongodb", "async-std-runtime", "tasks"]
[dependencies]
# Core
revolt-config = { version = "0.10.0", path = "../config", features = [
revolt-config = { version = "0.11.5", path = "../config", features = [
"report-macros",
] }
revolt-result = { version = "0.10.0", path = "../result" }
revolt-models = { version = "0.10.0", path = "../models", features = [
revolt-result = { version = "0.11.5", path = "../result" }
revolt-models = { version = "0.11.5", path = "../models", features = [
"validator",
] }
revolt-presence = { version = "0.10.0", path = "../presence" }
revolt-permissions = { version = "0.10.0", path = "../permissions", features = [
revolt-presence = { version = "0.11.5", path = "../presence" }
revolt-permissions = { version = "0.11.5", path = "../permissions", features = [
"serde",
"bson",
] }
revolt-parser = { version = "0.10.0", path = "../parser" }
revolt-parser = { version = "0.11.5", path = "../parser" }
# Utility
log = "0.4"
@@ -100,7 +100,7 @@ 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" }

View File

@@ -11,6 +11,8 @@ use revolt_presence::filter_online;
use serde_json::to_string;
// TODO: move away from storing Connection and Channel to using thread local singletons - #659
#[derive(Clone)]
pub struct AMQP {
#[allow(unused)]

View File

@@ -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;
@@ -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),

View File

@@ -1,12 +1,20 @@
use amqprs::{
channel::{BasicPublishArguments},
BasicProperties, FieldTable,
};
use authifier::AuthifierEvent;
use revolt_result::Error;
use serde::{Deserialize, Serialize};
use revolt_models::v0::{
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
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;
use crate::{util::rabbit::get_channel, Database};
/// Ping Packet
#[derive(Serialize, Deserialize, Debug, Clone)]
@@ -304,14 +312,23 @@ pub enum EventV1 {
impl EventV1 {
/// Publish helper wrapper
pub async fn p(self, channel: String) {
#[cfg(not(debug_assertions))]
redis_kiss::p(channel, self).await;
#[cfg(debug_assertions)]
info!("Publishing event to {channel}: {self:?}");
#[cfg(debug_assertions)]
redis_kiss::publish(channel, self).await.unwrap();
let rmq = get_channel().await;
let mut headers = FieldTable::new();
headers.insert("c".try_into().unwrap(), channel.clone().into());
let mut properties = BasicProperties::default();
properties.with_headers(headers);
rmq.basic_publish(
properties,
serde_json::to_string(&self).unwrap().into_bytes(),
BasicPublishArguments::new("events", "events"),
)
.await
.unwrap();
}
/// Publish user event

View File

@@ -5,5 +5,6 @@ pub mod idempotency;
pub mod permissions;
pub mod reference;
pub mod test_fixtures;
pub mod rabbit;
pub use funcs::*;

View File

@@ -0,0 +1,48 @@
use amqprs::{channel::Channel, connection::Connection};
use once_cell::sync::OnceCell;
use std::{
collections::HashMap, future::ready, sync::{LazyLock, RwLock}, thread::{ThreadId, current}
};
static RABBIT_CONNECTION: OnceCell<Connection> = OnceCell::new();
static RABBIT_CHANNELS: LazyLock<RwLock<HashMap<ThreadId, Channel>>> =
LazyLock::new(|| RwLock::new(HashMap::new()));
pub async fn get_channel_with_init<F: AsyncFnOnce(Channel) -> Channel>(init: F) -> Channel {
let conn = RABBIT_CONNECTION
.get()
.expect("Rabbit connection is not initialised.");
let thread_id = current().id();
let channel = RABBIT_CHANNELS
.read()
.expect("Channels poisioned")
.get(&thread_id)
.cloned();
if let Some(channel) = channel {
channel
} else {
let mut channel =
conn.open_channel(None)
.await
.expect("Failed to open rabbitmq channel");
channel = init(channel).await;
RABBIT_CHANNELS
.write()
.expect("Channels poisioned")
.insert(thread_id, channel.clone());
channel
}
}
pub async fn get_channel() -> Channel {
get_channel_with_init(ready).await
}
pub fn set_rabbitmq_connection(connection: Connection) -> bool {
RABBIT_CONNECTION.set(connection).is_ok()
}

View File

@@ -16,29 +16,33 @@ mod voice_client;
pub use voice_client::VoiceClient;
async fn get_connection() -> Result<Conn> {
_get_connection().await.map_err(|_| create_error!(InternalError))
_get_connection()
.await
.map_err(|_| create_error!(InternalError))
}
pub async fn raise_if_in_voice(user: &User, channel_id: &str) -> 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
&& conn.sismember(format!("vc:{}", &user.id), channel_id)
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_id)
.await
.to_internal_error()?
{
Err(create_error!(AlreadyConnected))
{
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
{
Err(create_error!(AlreadyConnected))
} else {
Ok(())
}
return Err(create_error!(AlreadyConnected));
};
Ok(())
}
pub async fn set_channel_node(channel: &str, node: &str) -> Result<()> {
@@ -57,6 +61,14 @@ pub async fn get_channel_node(channel: &str) -> Result<Option<String>> {
.to_internal_error()
}
pub async fn delete_channel_node(channel: &str) -> Result<()> {
get_connection()
.await?
.del(format!("node:{channel}"))
.await
.to_internal_error()
}
pub async fn get_user_voice_channels(user_id: &str) -> Result<Vec<String>> {
get_connection()
.await?
@@ -224,6 +236,7 @@ pub async fn delete_channel_voice_state(
let mut pipeline = Pipeline::new();
pipeline.del(format!("vc_members:{channel_id}"));
pipeline.del(format!("node:{channel_id}"));
for user_id in user_ids {
let unique_key = format!("{user_id}:{parent_id}");
@@ -565,15 +578,24 @@ pub async fn get_call_notification_recipients(
.to_internal_error()
}
pub async fn remove_user_from_voice_channels(db: &Database, voice_client: &VoiceClient, user_id: &str) -> Result<()> {
pub async fn remove_user_from_voice_channels(
db: &Database,
voice_client: &VoiceClient,
user_id: &str,
) -> Result<()> {
for channel_id in get_user_voice_channels(user_id).await? {
remove_user_from_voice_channel(db, voice_client, &channel_id, user_id).await?;
};
}
Ok(())
}
pub async fn remove_user_from_voice_channel(db: &Database, voice_client: &VoiceClient, channel_id: &str, user_id: &str) -> Result<()> {
pub async fn remove_user_from_voice_channel(
db: &Database,
voice_client: &VoiceClient,
channel_id: &str,
user_id: &str,
) -> Result<()> {
if let Some(node) = get_channel_node(channel_id).await? {
let _ = voice_client.remove_user(&node, user_id, channel_id).await;
}
@@ -585,7 +607,11 @@ pub async fn remove_user_from_voice_channel(db: &Database, voice_client: &VoiceC
Ok(())
}
pub async fn delete_voice_channel(voice_client: &VoiceClient, channel_id: &str, server_id: Option<&str>) -> Result<()> {
pub async fn delete_voice_channel(
voice_client: &VoiceClient,
channel_id: &str,
server_id: Option<&str>,
) -> Result<()> {
if let Some(users) = get_voice_channel_members(channel_id).await? {
let node = get_channel_node(channel_id).await?.unwrap();
@@ -595,4 +621,4 @@ pub async fn delete_voice_channel(voice_client: &VoiceClient, channel_id: &str,
};
Ok(())
}
}

View File

@@ -4,6 +4,6 @@ Please navigate to: {{url}}
This email is intended for {{email}}
This email has no association with Revolt or Revolt Platforms Ltd.
This email has no association with Stoat or Revolt Platforms Ltd.
Learn more about third party instances here:
https://developers.revolt.chat/faq.html

View File

@@ -5,6 +5,6 @@ Please navigate to: {{url}}
This email is intended for {{email}}
This email has no association with Revolt or Revolt Platforms Ltd.
This email has no association with Stoat or Revolt Platforms Ltd.
Learn more about third party instances here:
https://developers.revolt.chat/faq.html

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-files"
version = "0.10.0"
version = "0.11.5"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -27,10 +27,10 @@ typenum = "1.17.0"
aws-config = "1.5.5"
aws-sdk-s3 = { version = "1.46.0", features = ["behavior-version-latest"] }
revolt-config = { version = "0.10.0", path = "../config", features = [
revolt-config = { version = "0.11.5", path = "../config", features = [
"report-macros",
] }
revolt-result = { version = "0.10.0", path = "../result" }
revolt-result = { version = "0.11.5", path = "../result" }
# image processing
jxl-oxide = { workspace = true }

View File

@@ -132,6 +132,17 @@ impl MediaRepository for MediaImpl {
let [w, h] = self.config.preview.get(tag).unwrap();
let image = image.thumbnail(image.width().min(*w as u32), image.height().min(*h as u32));
let image = match image {
DynamicImage::ImageRgb8(_) => image,
DynamicImage::ImageRgba8(_) => image,
_ => {
if image.has_alpha() {
image.to_rgba8().into()
} else {
image.to_rgb8().into()
}
}
};
let encoder = webp::Encoder::from_image(&image).expect("Could not create encoder.");
if self.config.webp_quality != 100.0 {
@@ -216,6 +227,17 @@ mod tests {
media.create_thumbnail(image, "emojis");
}
#[tokio::test]
async fn asset_test_floating_point_png() {
let media = MediaImpl::from_config().await;
let buf = include_bytes!("../../tests/assets/test-float.png");
assert_eq!(media.image_size_vec(buf, "image/png"), Some((300, 300)));
let mut reader = Cursor::new(buf);
let image = media.decode_image(&mut reader, "image/png").unwrap();
media.create_thumbnail(image, "avatars");
}
#[tokio::test]
async fn asset_test_corrupted_png() {
let media = MediaImpl::from_config().await;

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-models"
version = "0.10.0"
version = "0.11.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -21,8 +21,8 @@ default = ["serde", "partials", "rocket"]
[dependencies]
# Core
revolt-config = { version = "0.10.0", path = "../config" }
revolt-permissions = { version = "0.10.0", path = "../permissions" }
revolt-config = { version = "0.11.5", path = "../config" }
revolt-permissions = { version = "0.11.5", path = "../permissions" }
# Utility
regex = "1.11"

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-parser"
version = "0.10.0"
version = "0.11.5"
edition = "2021"
license = "MIT"
authors = ["Zomatree <me@zomatree.live>", "Paul Makles <me@insrt.uk>"]

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-permissions"
version = "0.10.0"
version = "0.11.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -22,7 +22,7 @@ async-std = { version = "1.8.0", features = ["attributes"] }
[dependencies]
# Core
revolt-result = { version = "0.10.0", path = "../result" }
revolt-result = { version = "0.11.5", path = "../result" }
# Utility
auto_ops = "0.3.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-presence"
version = "0.10.0"
version = "0.11.5"
edition = "2021"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
@@ -17,7 +17,7 @@ redis-is-patched = []
async-std = { version = "1.8.0", features = ["attributes"] }
# Config for loading Redis URI
revolt-config = { version = "0.10.0", path = "../config" }
revolt-config = { version = "0.11.5", path = "../config" }
[dependencies]
# Utility

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-ratelimits"
version = "0.10.0"
version = "0.11.5"
edition = "2024"
license = "MIT"
authors = ["Zomatree <me@zomatree.live>", "Paul Makles <me@insrt.uk>"]
@@ -18,9 +18,9 @@ axum = ["dep:axum", "revolt-database/axum-impl"]
default = ["rocket", "axum"]
[dependencies]
revolt-database = { version = "0.10.0", path = "../database" }
revolt-result = { version = "0.10.0", path = "../result" }
revolt-config = { version = "0.10.0", path = "../config" }
revolt-database = { version = "0.11.5", path = "../database" }
revolt-result = { version = "0.11.5", path = "../result" }
revolt-config = { version = "0.11.5", path = "../config" }
rocket = { version = "0.5.1", optional = true }
revolt_rocket_okapi = { version = "0.10.0", optional = true }
@@ -28,7 +28,7 @@ revolt_rocket_okapi = { version = "0.10.0", optional = true }
axum = { version = "0.7.5", optional = true, features = ["macros"] }
serde = { version = "1", features = ["derive"] }
authifier = { version = "1.0.15" }
authifier = { version = "1.0.16" }
dashmap = "5.2.0"
async-trait = "0.1.81"
log = "0.4"

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-result"
version = "0.10.0"
version = "0.11.5"
edition = "2021"
license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"]

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-crond"
version = "0.10.0"
version = "0.11.5"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <me@insrt.uk>"]
edition = "2021"
@@ -17,7 +17,7 @@ log = "0.4"
tokio = { version = "1" }
# Core
revolt-database = { version = "0.10.0", path = "../../core/database" }
revolt-result = { version = "0.10.0", path = "../../core/result" }
revolt-config = { version = "0.10.0", path = "../../core/config" }
revolt-files = { version = "0.10.0", path = "../../core/files" }
revolt-database = { version = "0.11.5", path = "../../core/database" }
revolt-result = { version = "0.11.5", path = "../../core/result" }
revolt-config = { version = "0.11.5", path = "../../core/config" }
revolt-files = { version = "0.11.5", path = "../../core/files" }

View File

@@ -1,21 +1,21 @@
[package]
name = "revolt-pushd"
version = "0.10.0"
version = "0.11.5"
edition = "2021"
license = "AGPL-3.0-or-later"
publish = false
[dependencies]
revolt-result = { version = "0.10.0", path = "../../core/result" }
revolt-config = { version = "0.10.0", path = "../../core/config", features = [
revolt-result = { version = "0.11.5", path = "../../core/result" }
revolt-config = { version = "0.11.5", path = "../../core/config", features = [
"report-macros",
"anyhow"
"anyhow",
] }
revolt-database = { version = "0.10.0", path = "../../core/database" }
revolt-models = { version = "0.10.0", path = "../../core/models", features = [
revolt-database = { version = "0.11.5", path = "../../core/database" }
revolt-models = { version = "0.11.5", path = "../../core/models", features = [
"validator",
] }
revolt-presence = { version = "0.10.0", path = "../../core/presence", features = [
revolt-presence = { version = "0.11.5", path = "../../core/presence", features = [
"redis-is-patched",
] }
@@ -30,7 +30,7 @@ tokio = "1.39.2"
async-trait = "0.1.81"
ulid = "1.0.0"
authifier = "1.0.15"
authifier = "1.0.16"
log = "0.4.11"
pretty_env_logger = "0.4.0"

View File

@@ -128,7 +128,7 @@ impl DmCallConsumer {
config.pushd.vapid.queue.as_str(),
)
.finish();
sendable.extras.insert("p265dh".to_string(), sub.p256dh);
sendable.extras.insert("p256dh".to_string(), sub.p256dh);
sendable
.extras
.insert("endpoint".to_string(), sub.endpoint.clone());

View File

@@ -101,7 +101,7 @@ impl FRAcceptedConsumer {
config.pushd.vapid.queue.as_str(),
)
.finish();
sendable.extras.insert("p265dh".to_string(), sub.p256dh);
sendable.extras.insert("p256dh".to_string(), sub.p256dh);
sendable
.extras
.insert("endpoint".to_string(), sub.endpoint.clone());

View File

@@ -101,7 +101,7 @@ impl FRReceivedConsumer {
config.pushd.vapid.queue.as_str(),
)
.finish();
sendable.extras.insert("p265dh".to_string(), sub.p256dh);
sendable.extras.insert("p256dh".to_string(), sub.p256dh);
sendable
.extras
.insert("endpoint".to_string(), sub.endpoint.clone());

View File

@@ -107,7 +107,7 @@ impl GenericConsumer {
config.pushd.vapid.queue.as_str(),
)
.finish();
sendable.extras.insert("p265dh".to_string(), sub.p256dh);
sendable.extras.insert("p256dh".to_string(), sub.p256dh);
sendable
.extras
.insert("endpoint".to_string(), sub.endpoint.clone());

View File

@@ -104,7 +104,7 @@ impl MassMessageConsumer {
config.pushd.vapid.queue.as_str(),
)
.finish();
sendable.extras.insert("p265dh".to_string(), sub.p256dh);
sendable.extras.insert("p256dh".to_string(), sub.p256dh);
sendable
.extras
.insert("endpoint".to_string(), sub.endpoint.clone());

View File

@@ -107,7 +107,7 @@ impl MessageConsumer {
config.pushd.vapid.queue.as_str(),
)
.finish();
sendable.extras.insert("p265dh".to_string(), sub.p256dh);
sendable.extras.insert("p256dh".to_string(), sub.p256dh);
sendable
.extras
.insert("endpoint".to_string(), sub.endpoint.clone());

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-voice-ingress"
version = "0.10.0"
version = "0.11.5"
license = "AGPL-3.0-or-later"
edition = "2021"
publish = false

View File

@@ -5,9 +5,8 @@ use revolt_database::{
iso8601_timestamp::{Duration, Timestamp},
util::reference::Reference,
voice::{
create_voice_state, delete_voice_state,
get_user_moved_from_voice, get_user_moved_to_voice,
update_voice_state_tracks, VoiceClient,
create_voice_state, delete_channel_node, delete_voice_state, get_user_moved_from_voice,
get_user_moved_to_voice, get_voice_channel_members, update_voice_state_tracks, VoiceClient,
},
Database, AMQP,
};
@@ -160,6 +159,8 @@ pub async fn ingress(
// let members = get_voice_channel_members(channel_id).await?;
// if members.is_none_or(|m| m.is_empty()) {
// delete_channel_node(channel_id).await?;
//
// // The channel is empty so send out an "end" message for ringing
// if let Err(e) = amqp
// .dm_call_updated(user_id, channel_id, None, true, None)

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-delta"
version = "0.10.0"
version = "0.11.5"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018"
@@ -57,7 +57,7 @@ lettre = "0.10.0-alpha.4"
rocket = { version = "0.5.1", default-features = false, features = ["json"] }
rocket_cors = { git = "https://github.com/lawliet89/rocket_cors", rev = "072d90359b23e9b291df6b672c07c93de9c46011" }
rocket_empty = { version = "0.1.1", features = ["schema"] }
rocket_authifier = { version = "1.0.15" }
rocket_authifier = { version = "1.0.16" }
rocket_prometheus = "0.10.0-rc.3"
# spec generation
@@ -68,7 +68,7 @@ revolt_rocket_okapi = { version = "0.10.0", features = ["swagger"] }
amqprs = { version = "1.7.0" }
# core
authifier = "1.0.15"
authifier = "1.0.16"
revolt-config = { path = "../core/config" }
revolt-database = { path = "../core/database", features = [
"rocket-impl",

View File

@@ -9,8 +9,7 @@ pub mod routes;
pub mod util;
use revolt_config::config;
use revolt_database::events::client::EventV1;
use revolt_database::AMQP;
use revolt_database::{AMQP, util::rabbit::{get_channel, get_channel_with_init, set_rabbitmq_connection}};
use revolt_ratelimits::rocket as ratelimiter;
use rocket::{Build, Rocket};
use rocket_cors::{AllowedOrigins, CorsOptions};
@@ -19,11 +18,9 @@ use std::net::Ipv4Addr;
use std::str::FromStr;
use amqprs::{
channel::ExchangeDeclareArguments,
channel::{Channel, ExchangeDeclareArguments},
connection::{Connection, OpenConnectionArguments},
};
use async_std::channel::unbounded;
use authifier::AuthifierEvent;
use rocket::data::ToByteUnit;
use revolt_database::voice::VoiceClient;
@@ -39,28 +36,9 @@ pub async fn web() -> Rocket<Build> {
log::info!("database_here {db:?}");
db.migrate_database().await.unwrap();
// Setup Authifier event channel
let (_, receiver) = unbounded();
// Setup Authifier
let authifier = db.clone().to_authifier().await;
// Launch a listener for Authifier events
async_std::task::spawn(async move {
while let Ok(event) = receiver.recv().await {
match &event {
AuthifierEvent::CreateSession { .. } | AuthifierEvent::CreateAccount { .. } => {
EventV1::Auth(event).global().await
}
AuthifierEvent::DeleteSession { user_id, .. }
| AuthifierEvent::DeleteAllSessions { user_id, .. } => {
let id = user_id.to_string();
EventV1::Auth(event).private(id).await
}
}
}
});
// Configure CORS
let cors = CorsOptions {
allowed_origins: AllowedOrigins::All,
@@ -121,19 +99,19 @@ pub async fn web() -> Rocket<Build> {
.await
.expect("Failed to connect to RabbitMQ");
let channel = connection
.open_channel(None)
.await
.expect("Failed to open RabbitMQ channel");
set_rabbitmq_connection(connection.clone());
let channel = get_channel_with_init(|channel: Channel| async {
channel
.exchange_declare(
ExchangeDeclareArguments::new(&config.pushd.exchange, "direct")
.durable(true)
.finish(),
)
.await
.expect("Failed to declare exchange");
channel
.exchange_declare(
ExchangeDeclareArguments::new(&config.pushd.exchange, "direct")
.durable(true)
.finish(),
)
.await
.expect("Failed to declare exchange");
channel
}).await;
let amqp = AMQP::new(connection, channel);

View File

@@ -3,7 +3,7 @@ use revolt_database::{
util::{permissions::perms, reference::Reference},
voice::{
delete_voice_state, get_channel_node, get_user_voice_channels, get_voice_channel_members,
raise_if_in_voice, set_call_notification_recipients, VoiceClient,
raise_if_in_voice, set_call_notification_recipients, set_channel_node, VoiceClient,
},
Database, User,
};
@@ -60,6 +60,7 @@ pub async fn call(
}
let existing_node = get_channel_node(channel.id()).await?;
let has_existing_node = existing_node.is_some(); // we move existing_node in the next statement so this is the quickest way to know if we need to set it.
let node = existing_node
.or(node)
@@ -86,9 +87,11 @@ pub async fn call(
let channel = Reference::from_unchecked(&channel_id)
.as_channel(db)
.await?;
.await;
delete_voice_state(&channel_id, channel.server(), &user.id).await?;
if channel.is_ok() {
delete_voice_state(&channel_id, channel.unwrap().server(), &user.id).await?;
}
}
} else {
raise_if_in_voice(&user, channel.id()).await?;
@@ -100,6 +103,10 @@ pub async fn call(
let room = voice_client.create_room(&node, &channel).await?;
if !has_existing_node {
set_channel_node(channel.id(), &node).await?;
}
log::debug!("Created room {}", room.name);
if let Some(recipients) = recipients {

View File

@@ -1,3 +1,4 @@
use std::collections::HashMap;
use revolt_config::config;
use revolt_result::Result;
use rocket::serde::json::Json;
@@ -54,6 +55,80 @@ pub struct RevoltFeatures {
pub january: Feature,
/// Voice server configuration
pub livekit: VoiceFeature,
/// Limits
pub limits: LimitsConfig,
}
/// # Limits For Users
#[derive(Serialize, JsonSchema, Debug)]
pub struct LimitsConfig {
/// Global Limits
pub global: GlobalLimits,
/// New User Limits
pub new_user: UserLimits,
/// Default User Limits
pub default: UserLimits,
}
/// # Global limits
#[derive(Serialize, JsonSchema, Debug)]
pub struct GlobalLimits {
/// max group size
group_size: i64,
/// max message embeds
message_embeds: i64,
/// max replies
message_replies: i64,
/// max reactions per message
message_reactions: i64,
/// max server emoji
server_emoji: i64,
/// max server roles
server_roles: i64,
/// max server channels
server_channels: i64,
body_limit_size: i64,
}
/// # User Limits
#[derive(Serialize, JsonSchema, Debug)]
pub struct UserLimits {
/// Max Outgoing Friend Requests
pub outgoing_friend_requests: i64,
/// Max Owned Bots
pub bots: i64,
/// Max message content length
pub message_length: i64,
/// max message attachments
pub message_attachments: i64,
/// max servers
pub servers: i64,
/// max audio quality
pub voice_quality: i64,
/// video streaming enabled
pub video: bool,
/// max video resolution (vertical, horizontal)
pub video_resolution: [i64; 2],
/// min/max aspect ratios
pub video_aspect_ratio: [f64; 2],
pub file_upload_size_limits: HashMap<String, usize>,
}
impl UserLimits {
fn from_feature_limits(fl: revolt_config::FeaturesLimits) -> UserLimits {
UserLimits {
outgoing_friend_requests: fl.outgoing_friend_requests as i64,
bots: fl.bots as i64,
message_length: fl.message_length as i64,
message_attachments: fl.message_attachments as i64,
servers: fl.servers as i64,
voice_quality: fl.voice_quality as i64,
video: fl.video,
video_resolution: [fl.video_resolution[0] as i64, fl.video_resolution[1] as i64],
video_aspect_ratio: [fl.video_aspect_ratio[0] as f64, fl.video_aspect_ratio[1] as f64],
file_upload_size_limits: fl.file_upload_size_limit,
}
}
}
/// # Build Information
@@ -134,6 +209,20 @@ pub async fn root() -> Result<Json<RevoltConfig>> {
})
.collect(),
},
limits: LimitsConfig {
global: GlobalLimits {
group_size: config.features.limits.global.group_size as i64,
message_embeds: config.features.limits.global.message_embeds as i64,
message_replies: config.features.limits.global.message_replies as i64,
message_reactions: config.features.limits.global.message_reactions as i64,
server_emoji: config.features.limits.global.server_emoji as i64,
server_roles: config.features.limits.global.server_roles as i64,
server_channels: config.features.limits.global.server_channels as i64,
body_limit_size: config.features.limits.global.body_limit_size as i64,
},
new_user: UserLimits::from_feature_limits(config.features.limits.new_user),
default: UserLimits::from_feature_limits(config.features.limits.default),
}
},
ws: config.hosts.events,
app: config.hosts.app,

View File

@@ -6,7 +6,9 @@ use futures::StreamExt;
use rand::Rng;
use redis_kiss::redis::aio::PubSub;
use revolt_database::{
events::client::EventV1, Channel, Database, Member, Message, PartialRole, Server, User, AMQP,
events::client::EventV1,
util::rabbit::{get_channel, set_rabbitmq_connection},
Channel, Database, Member, Message, PartialRole, Server, User, AMQP,
};
use revolt_database::{util::idempotency::IdempotencyKey, Role};
use revolt_models::v0;
@@ -59,10 +61,16 @@ impl TestHarness {
)
.await
.unwrap();
let channel = connection.open_channel(None).await.unwrap();
set_rabbitmq_connection(connection.clone());
let channel = get_channel().await;
let amqp = AMQP::new(connection, channel);
async_std::task::spawn(async {
});
TestHarness {
client,
authifier,

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-autumn"
version = "0.10.0"
version = "0.11.5"
edition = "2021"
license = "AGPL-3.0-or-later"
publish = false
@@ -44,16 +44,16 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Core crates
revolt-files = { version = "0.10.0", path = "../../core/files" }
revolt-config = { version = "0.10.0", path = "../../core/config" }
revolt-database = { version = "0.10.0", path = "../../core/database", features = [
revolt-files = { version = "0.11.5", path = "../../core/files" }
revolt-config = { version = "0.11.5", path = "../../core/config" }
revolt-database = { version = "0.11.5", path = "../../core/database", features = [
"axum-impl",
] }
revolt-result = { version = "0.10.0", path = "../../core/result", features = [
revolt-result = { version = "0.11.5", path = "../../core/result", features = [
"utoipa",
"axum",
] }
revolt-ratelimits = { version = "0.10.0", path = "../../core/ratelimits", features = ["axum"] }
revolt-ratelimits = { version = "0.11.5", path = "../../core/ratelimits", features = ["axum"] }
# Axum / web server
tempfile = "3.12.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-gifbox"
version = "0.10.0"
version = "0.11.5"
edition = "2021"
license = "AGPL-3.0-or-later"
publish = false
@@ -17,19 +17,19 @@ tokio = { version = "1.0", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
# Core crates
revolt-config = { version = "0.10.0", path = "../../core/config" }
revolt-models = { version = "0.10.0", path = "../../core/models" }
revolt-result = { version = "0.10.0", path = "../../core/result", features = [
revolt-config = { version = "0.11.5", path = "../../core/config" }
revolt-models = { version = "0.11.5", path = "../../core/models" }
revolt-result = { version = "0.11.5", path = "../../core/result", features = [
"utoipa",
"axum",
] }
revolt-coalesced = { version = "0.10.0", path = "../../core/coalesced", features = [
revolt-coalesced = { version = "0.11.5", path = "../../core/coalesced", features = [
"queue",
] }
revolt-database = { version = "0.10.0", path = "../../core/database", features = [
revolt-database = { version = "0.11.5", path = "../../core/database", features = [
"axum-impl",
] }
revolt-ratelimits = { version = "0.10.0", path = "../../core/ratelimits", features = [
revolt-ratelimits = { version = "0.11.5", path = "../../core/ratelimits", features = [
"axum",
] }

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-january"
version = "0.10.0"
version = "0.11.5"
edition = "2021"
license = "AGPL-3.0-or-later"
publish = false
@@ -33,13 +33,13 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
# Core crates
revolt-config = { version = "0.10.0", path = "../../core/config" }
revolt-models = { version = "0.10.0", path = "../../core/models" }
revolt-result = { version = "0.10.0", path = "../../core/result", features = [
revolt-config = { version = "0.11.5", path = "../../core/config" }
revolt-models = { version = "0.11.5", path = "../../core/models" }
revolt-result = { version = "0.11.5", path = "../../core/result", features = [
"utoipa",
"axum",
] }
revolt-files = { version = "0.10.0", path = "../../core/files" }
revolt-files = { version = "0.11.5", path = "../../core/files" }
# Axum / web server
axum = { version = "0.7.5" }

8
git-town.toml Normal file
View File

@@ -0,0 +1,8 @@
# See https://www.git-town.com/configuration-file for details
[branches]
main = "main"
[hosting]
forge-type = "github"
github-connector = "gh"

View File

@@ -1 +1 @@
0.10.0
0.11.5