Merge branch 'main' into feat/elasticsearch

Signed-off-by: Zomatree <me@zomatree.live>
This commit is contained in:
Zomatree
2026-04-16 22:58:18 +01:00
39 changed files with 259 additions and 117 deletions

24
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,24 @@
<!-- Describe your changes -->
Fixes # (issue)
## How was this PR tested?
<!-- What did you do to test your changes? -->
- [ ] 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
...

View File

@@ -16,4 +16,13 @@ idiomatic_version_file_enable_tools = ["rust"]
[tasks.start] [tasks.start]
description = "Run all services" description = "Run all services"
depends = ["docker:start", "build"] depends = ["docker:start", "build"]
wait_for = ["docker:start", "build"]
run = [{ task = "service:*" }] 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 }

View File

@@ -2,4 +2,4 @@
#MISE description="Build project" #MISE description="Build project"
set -e set -e
cargo build "$@" ${BUILDER} build "$@"

View File

@@ -3,3 +3,11 @@
set -e set -e
docker compose up -d 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}

View File

@@ -1,3 +1,3 @@
{ {
".": "0.12.0" ".": "0.12.1"
} }

View File

@@ -1,5 +1,15 @@
# Changelog # Changelog
## [0.12.1](https://github.com/stoatchat/stoatchat/compare/v0.12.0...v0.12.1) (2026-04-10)
### Bug Fixes
* add migration to update existing files to be animated ([#705](https://github.com/stoatchat/stoatchat/issues/705)) ([f2c056a](https://github.com/stoatchat/stoatchat/commit/f2c056a1515be493b195f3f5db5886c2ddf36700))
* don't send self dm notifications ([#706](https://github.com/stoatchat/stoatchat/issues/706)) ([f30b729](https://github.com/stoatchat/stoatchat/commit/f30b729ca90d0be6853c57ab4935694e5e59ae56))
* mise start + missing docker image ([#564](https://github.com/stoatchat/stoatchat/issues/564)) ([fb8fe16](https://github.com/stoatchat/stoatchat/commit/fb8fe1655776791421284a6a093e86f0320c258a))
* test failure due to wrong assertion ([#707](https://github.com/stoatchat/stoatchat/issues/707)) ([f81e329](https://github.com/stoatchat/stoatchat/commit/f81e3291bdd57af9ceedb2987b111acc7051d69c))
## [0.12.0](https://github.com/stoatchat/stoatchat/compare/v0.11.5...v0.12.0) (2026-03-28) ## [0.12.0](https://github.com/stoatchat/stoatchat/compare/v0.11.5...v0.12.0) (2026-03-28)

36
Cargo.lock generated
View File

@@ -6874,7 +6874,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-autumn" name = "revolt-autumn"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"axum", "axum",
"axum-macros", "axum-macros",
@@ -6913,7 +6913,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"async-channel 2.5.0", "async-channel 2.5.0",
"async-std", "async-std",
@@ -6944,7 +6944,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-coalesced" name = "revolt-coalesced"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"indexmap 2.13.0", "indexmap 2.13.0",
"lru 0.16.3", "lru 0.16.3",
@@ -6953,7 +6953,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-config" name = "revolt-config"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"async-std", "async-std",
"cached", "cached",
@@ -6970,7 +6970,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-crond" name = "revolt-crond"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"log", "log",
"revolt-config", "revolt-config",
@@ -6982,7 +6982,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-database" name = "revolt-database"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"amqprs", "amqprs",
"async-lock 2.8.0", "async-lock 2.8.0",
@@ -7032,7 +7032,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-delta" name = "revolt-delta"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"amqprs", "amqprs",
"async-channel 1.9.0", "async-channel 1.9.0",
@@ -7083,7 +7083,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-files" name = "revolt-files"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"aes-gcm", "aes-gcm",
"anyhow", "anyhow",
@@ -7111,7 +7111,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-gifbox" name = "revolt-gifbox"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"axum", "axum",
"axum-extra", "axum-extra",
@@ -7134,7 +7134,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-january" name = "revolt-january"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"async-recursion", "async-recursion",
"axum", "axum",
@@ -7162,7 +7162,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-models" name = "revolt-models"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"indexmap 1.9.3", "indexmap 1.9.3",
"iso8601-timestamp", "iso8601-timestamp",
@@ -7181,14 +7181,14 @@ dependencies = [
[[package]] [[package]]
name = "revolt-parser" name = "revolt-parser"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"logos", "logos",
] ]
[[package]] [[package]]
name = "revolt-permissions" name = "revolt-permissions"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-trait", "async-trait",
@@ -7203,7 +7203,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-presence" name = "revolt-presence"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"async-std", "async-std",
"log", "log",
@@ -7215,7 +7215,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-pushd" name = "revolt-pushd"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"amqprs", "amqprs",
"anyhow", "anyhow",
@@ -7246,7 +7246,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-ratelimits" name = "revolt-ratelimits"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"authifier", "authifier",
@@ -7263,7 +7263,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-result" name = "revolt-result"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"axum", "axum",
"log", "log",
@@ -7310,7 +7310,7 @@ dependencies = [
[[package]] [[package]]
name = "revolt-voice-ingress" name = "revolt-voice-ingress"
version = "0.12.0" version = "0.12.1"
dependencies = [ dependencies = [
"amqprs", "amqprs",
"async-std", "async-std",

View File

@@ -76,6 +76,14 @@ mise install
mise build 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. 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. If you'd like to change anything, create a `Revolt.overrides.toml` file and specify relevant variables.
@@ -112,7 +120,7 @@ If you'd like to change anything, create a `Revolt.overrides.toml` file and spec
> - "14672:15672" > - "14672:15672"
> ``` > ```
> >
> And corresponding Revolt configuration: > With the corresponding Revolt configuration:
> >
> ```toml > ```toml
> # Revolt.overrides.toml > # Revolt.overrides.toml
@@ -124,32 +132,25 @@ If you'd like to change anything, create a `Revolt.overrides.toml` file and spec
> [rabbit] > [rabbit]
> port = 14072 > port = 14072
> ``` > ```
>
> And mise configuration
>
> ```bash
> #.env
> DATABASE_PORT = "14017"
> RABBIT_PORT = "14072"
> REDIS_PORT = "14079"
> ```
Then continue: Then continue:
```bash ```bash
# start other necessary services cp livekit.example.yml livekit.yml
docker compose up -d
# run the API server mise start
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 <cargo build, cargo run, etc...>
# mold -run ./scripts/start.sh
``` ```
You can start a web client by doing the following: You can start a web client by doing the following in another terminal:
```bash ```bash
# if you do not have yarn yet and have a modern Node.js: # if you do not have yarn yet and have a modern Node.js:
@@ -163,6 +164,9 @@ cd stoat-web
When signing up, go to http://localhost:14080 to find confirmation/password reset emails. 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 ## Deployment Guide
### Cutting new crate releases ### Cutting new crate releases
@@ -198,7 +202,7 @@ If you have bumped the crate versions, proceed to [GitHub releases](https://gith
First, start the required services: First, start the required services:
```sh ```sh
docker compose -f docker-compose.db.yml up -d docker compose up -d
``` ```
Now run tests for whichever database: Now run tests for whichever database:

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-bonfire" name = "revolt-bonfire"
version = "0.12.0" version = "0.12.1"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
edition = "2021" edition = "2021"
publish = false publish = false

View File

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

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-config" name = "revolt-config"
version = "0.12.0" version = "0.12.1"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
authors = ["Paul Makles <me@insrt.uk>"] 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 } sentry-anyhow = { version = "0.38.1", optional = true }
# Core # Core
revolt-result = { version = "0.12.0", path = "../result", optional = true } revolt-result = { version = "0.12.1", path = "../result", optional = true }

View File

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

View File

@@ -46,8 +46,7 @@ auto_derived!(
width: isize, width: isize,
height: isize, height: isize,
thumbhash: Option<Vec<u8>>, thumbhash: Option<Vec<u8>>,
#[serde(default)] animated: Option<bool>,
animated: bool,
}, },
/// File is a video with specific dimensions /// File is a video with specific dimensions
Video { width: isize, height: isize }, Video { width: isize, height: isize },

View File

@@ -17,6 +17,12 @@ pub trait AbstractAttachmentHashes: Sync + Send {
/// Update an attachment hash nonce value. /// Update an attachment hash nonce value.
async fn set_attachment_hash_nonce(&self, hash: &str, nonce: &str) -> Result<()>; 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. /// Delete attachment hash by id.
async fn delete_attachment_hash(&self, id: &str) -> Result<()>; async fn delete_attachment_hash(&self, id: &str) -> Result<()>;
} }

View File

@@ -48,6 +48,29 @@ impl AbstractAttachmentHashes for MongoDb {
.map_err(|_| create_database_error!("update_one", COL)) .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::<FileHash>(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. /// Delete attachment hash by id.
async fn delete_attachment_hash(&self, id: &str) -> Result<()> { async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
query!(self, delete_one_by_id, COL, id).map(|_| ()) query!(self, delete_one_by_id, COL, id).map(|_| ())

View File

@@ -1,7 +1,6 @@
use revolt_result::Result; use revolt_result::Result;
use crate::FileHash; use crate::{FileHash, Metadata, ReferenceDb};
use crate::ReferenceDb;
use super::AbstractAttachmentHashes; 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. /// Delete attachment hash by id.
async fn delete_attachment_hash(&self, id: &str) -> Result<()> { async fn delete_attachment_hash(&self, id: &str) -> Result<()> {
let mut file_hashes = self.file_hashes.lock().await; let mut file_hashes = self.file_hashes.lock().await;

View File

@@ -731,7 +731,7 @@ impl Message {
}, },
member.map(Into::into), member.map(Into::into),
), ),
Some(author), Some(author.clone()),
channel.to_owned().into(), channel.to_owned().into(),
) )
.await, .await,
@@ -739,7 +739,11 @@ impl Message {
self.clone(), self.clone(),
match channel { match channel {
Channel::DirectMessage { recipients, .. } Channel::DirectMessage { recipients, .. }
| Channel::Group { recipients, .. } => recipients.clone(), | Channel::Group { recipients, .. } => recipients
.iter()
.filter(|uid| *uid != author.id())
.cloned()
.collect(),
Channel::TextChannel { .. } => { Channel::TextChannel { .. } => {
self.mentions.clone().unwrap_or_default() self.mentions.clone().unwrap_or_default()
} }

View File

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

View File

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

View File

@@ -51,7 +51,7 @@ auto_derived!(
width: usize, width: usize,
height: usize, height: usize,
thumbhash: Option<Vec<u8>>, thumbhash: Option<Vec<u8>>,
animated: bool, animated: Option<bool>,
}, },
/// File is a video with specific dimensions /// File is a video with specific dimensions
Video { width: usize, height: usize }, Video { width: usize, height: usize },

View File

@@ -391,6 +391,7 @@ auto_derived!(
); );
/// Message Author Abstraction /// Message Author Abstraction
#[derive(Clone)]
pub enum MessageAuthor<'a> { pub enum MessageAuthor<'a> {
User(&'a User), User(&'a User),
Webhook(&'a Webhook), Webhook(&'a Webhook),

View File

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

View File

@@ -262,7 +262,7 @@ mod tests {
) )
.collect::<Vec<_>>(); .collect::<Vec<_>>();
assert_eq!(output.len(), 6); assert_eq!(output.len(), 7);
assert_eq!(output[0], MessageToken::CodeblockMarker(1)); assert_eq!(output[0], MessageToken::CodeblockMarker(1));
assert_eq!( assert_eq!(
output[1], output[1],

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-ratelimits" name = "revolt-ratelimits"
version = "0.12.0" version = "0.12.1"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
authors = ["Zomatree <me@zomatree.live>", "Paul Makles <me@insrt.uk>"] authors = ["Zomatree <me@zomatree.live>", "Paul Makles <me@insrt.uk>"]
@@ -18,9 +18,9 @@ axum = ["dep:axum", "revolt-database/axum-impl"]
default = ["rocket", "axum"] default = ["rocket", "axum"]
[dependencies] [dependencies]
revolt-database = { version = "0.12.0", path = "../database" } revolt-database = { version = "0.12.1", path = "../database" }
revolt-result = { version = "0.12.0", path = "../result" } revolt-result = { version = "0.12.1", path = "../result" }
revolt-config = { version = "0.12.0", path = "../config" } revolt-config = { version = "0.12.1", path = "../config" }
rocket = { version = "0.5.1", optional = true } rocket = { version = "0.5.1", optional = true }
revolt_rocket_okapi = { version = "0.10.0", optional = true } revolt_rocket_okapi = { version = "0.10.0", optional = true }

View File

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

View File

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

View File

@@ -1,24 +1,24 @@
[package] [package]
name = "revolt-pushd" name = "revolt-pushd"
version = "0.12.0" version = "0.12.1"
edition = "2021" edition = "2021"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
publish = false publish = false
[dependencies] [dependencies]
revolt-result = { version = "0.12.0", path = "../../core/result" } revolt-result = { version = "0.12.1", path = "../../core/result" }
revolt-config = { version = "0.12.0", path = "../../core/config", features = [ revolt-config = { version = "0.12.1", path = "../../core/config", features = [
"report-macros", "report-macros",
"anyhow", "anyhow",
] } ] }
revolt-database = { version = "0.12.0", path = "../../core/database" } revolt-database = { version = "0.12.1", path = "../../core/database" }
revolt-models = { version = "0.12.0", path = "../../core/models", features = [ revolt-models = { version = "0.12.1", path = "../../core/models", features = [
"validator", "validator",
] } ] }
revolt-presence = { version = "0.12.0", path = "../../core/presence", features = [ revolt-presence = { version = "0.12.1", path = "../../core/presence", features = [
"redis-is-patched", "redis-is-patched",
] } ] }
revolt-parser = { version = "0.12.0", path = "../../core/parser" } revolt-parser = { version = "0.12.1", path = "../../core/parser" }
anyhow = { version = "1.0.98" } anyhow = { version = "1.0.98" }

View File

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

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "revolt-delta" name = "revolt-delta"
version = "0.12.0" version = "0.12.1"
license = "AGPL-3.0-or-later" license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"] authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018" edition = "2018"

View File

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

View File

@@ -1,5 +1,5 @@
use std::{ use std::{
io::{Cursor, Read}, io::{Cursor, Read, Write},
time::Duration, time::Duration,
}; };
@@ -15,9 +15,10 @@ use lazy_static::lazy_static;
use revolt_config::{config, report_internal_error}; use revolt_config::{config, report_internal_error};
use revolt_database::{iso8601_timestamp::Timestamp, Database, FileHash, Metadata, User}; use revolt_database::{iso8601_timestamp::Timestamp, Database, FileHash, Metadata, User};
use revolt_files::{ use revolt_files::{
create_thumbnail, decode_image, fetch_from_s3, upload_to_s3, AUTHENTICATION_TAG_SIZE_BYTES, create_thumbnail, decode_image, fetch_from_s3, is_animated, upload_to_s3,
AUTHENTICATION_TAG_SIZE_BYTES,
}; };
use revolt_result::{create_error, Error, Result}; use revolt_result::{create_error, Error, Result, ToRevoltError};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sha2::Digest; use sha2::Digest;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
@@ -25,7 +26,9 @@ use tokio::time::Instant;
use tower_http::cors::{AllowHeaders, Any, CorsLayer}; use tower_http::cors::{AllowHeaders, Any, CorsLayer};
use utoipa::ToSchema; use utoipa::ToSchema;
use crate::{exif::strip_metadata, metadata::generate_metadata, mime_type::determine_mime_type, AppState}; use crate::{
exif::strip_metadata, metadata::generate_metadata, mime_type::determine_mime_type, AppState,
};
/// Build the API router /// Build the API router
pub async fn router() -> Router<AppState> { pub async fn router() -> Router<AppState> {
@@ -380,7 +383,30 @@ async fn fetch_preview(
let hash = file.as_hash(&db).await?; let hash = file.as_hash(&db).await?;
let is_animated = matches!(hash.metadata, Metadata::Image { animated: true, .. }); let mut data = None;
// If animated is unset, check the file contents to see if it is animated and update the filehash
let is_animated = match &hash.metadata {
Metadata::Image {
animated: Some(value),
..
} => *value,
Metadata::Image { animated: None, .. } => {
let file_data = retrieve_file_by_hash(&hash).await?;
let mut named_file = NamedTempFile::new().to_internal_error()?;
named_file.write(&file_data).to_internal_error()?;
data = Some(file_data);
// If it fails for some reason, set it to not be animated
let animated = is_animated(&named_file, &hash.content_type).unwrap_or(false);
db.set_attachment_hash_animated(&hash.id, animated).await?;
animated
}
_ => false,
};
// Only process image files and don't process GIFs if not avatar or icon // Only process image files and don't process GIFs if not avatar or icon
if !matches!(hash.metadata, Metadata::Image { .. }) if !matches!(hash.metadata, Metadata::Image { .. })
@@ -392,7 +418,11 @@ async fn fetch_preview(
} }
// Original image data // Original image data
let data = retrieve_file_by_hash(&hash).await?; let data = if let Some(data) = data {
data
} else {
retrieve_file_by_hash(&hash).await?
};
// Read image and create thumbnail // Read image and create thumbnail
let data = create_thumbnail( let data = create_thumbnail(

View File

@@ -37,7 +37,7 @@ pub fn generate_metadata(f: &NamedTempFile, mime_type: &str) -> Metadata {
thumbhash::rgba_to_thumb_hash(width as usize, height as usize, &rgba) thumbhash::rgba_to_thumb_hash(width as usize, height as usize, &rgba)
}) })
.ok(), .ok(),
animated: is_animated(f, mime_type).unwrap_or(false), animated: is_animated(f, mime_type).or(Some(false)),
}) })
.unwrap_or_default() .unwrap_or_default()
} else if mime_type.starts_with("video/") { } else if mime_type.starts_with("video/") {

View File

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

View File

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

View File

@@ -4,6 +4,8 @@ sidebar_position: 1
# Endpoints # Endpoints
[![Run in Yaak](https://external.stoatusercontent.com/proxy?url=https://yaak.app/static/button.svg)](https://yaak.app/button/run?name=Stoat+API&url=https%3A%2F%2Fstoat.chat%2Fapi%2Fopenapi.json)
**We are moving stuff around currently following the rebrand, guidance will follow soon!** **We are moving stuff around currently following the rebrand, guidance will follow soon!**
You can connect to the API on the following URLs: You can connect to the API on the following URLs:

View File

@@ -23,7 +23,7 @@ You can contribute translations through [**Weblate**](https://translate.stoat.ch
### 3. Donate ### 3. Donate
Stoat is not backed by a big company, is not currently monetised (for example, via a subscription service) and does not serve you advertisements; as such, Stoat currently relies entirely on donations. Stoat is not backed by a big company, is not currently monetised (for example, via a subscription service) and does not serve you advertisements; as such, Stoat currently relies entirely on donations.
You can learn more about donating [here](https://wiki.revolt.chat/notes/project/financial-support/) - if you want to make a larger donation, please consult me first. You can learn more about donating [here](https://ko-fi.com/stoatchat) - if you want to make a larger donation, please consult me first.
### 4. Join the project ### 4. Join the project

View File

@@ -1 +1 @@
0.12.0 0.12.1