feat: Add support for MSC4322

This commit is contained in:
Ginger committed 2026-11-04 15:47:54 -05:00
1 parent 4b4918d677
commit 7b8af120aa
7 files changed
+114 -19

No files matched your search

Generated
+11 -11
View File
@@ -4997,7 +4997,7 @@ dependencies = [
[[package]]
name = "ruma"
version = "0.17.2"
source = "git+https://github.com/ruma/ruma.git?rev=0ed7fba34213f18a628a56f527c6cc795cd7eef5#0ed7fba34213f18a628a56f527c6cc795cd7eef5"
source = "git+https://github.com/gingershaped/ruwuma.git?rev=1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62#1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62"
dependencies = [
"assign",
"js_int",
@@ -5016,7 +5016,7 @@ dependencies = [
[[package]]
name = "ruma-appservice-api"
version = "0.17.1"
source = "git+https://github.com/ruma/ruma.git?rev=0ed7fba34213f18a628a56f527c6cc795cd7eef5#0ed7fba34213f18a628a56f527c6cc795cd7eef5"
source = "git+https://github.com/gingershaped/ruwuma.git?rev=1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62#1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62"
dependencies = [
"http",
"js_int",
@@ -5029,7 +5029,7 @@ dependencies = [
[[package]]
name = "ruma-client-api"
version = "0.25.2"
source = "git+https://github.com/ruma/ruma.git?rev=0ed7fba34213f18a628a56f527c6cc795cd7eef5#0ed7fba34213f18a628a56f527c6cc795cd7eef5"
source = "git+https://github.com/gingershaped/ruwuma.git?rev=1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62#1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62"
dependencies = [
"as_variant",
"assign",
@@ -5051,7 +5051,7 @@ dependencies = [
[[package]]
name = "ruma-common"
version = "0.20.2"
source = "git+https://github.com/ruma/ruma.git?rev=0ed7fba34213f18a628a56f527c6cc795cd7eef5#0ed7fba34213f18a628a56f527c6cc795cd7eef5"
source = "git+https://github.com/gingershaped/ruwuma.git?rev=1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62#1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62"
dependencies = [
"as_variant",
"base64 0.23.1",
@@ -5084,7 +5084,7 @@ dependencies = [
[[package]]
name = "ruma-events"
version = "0.35.2"
source = "git+https://github.com/ruma/ruma.git?rev=0ed7fba34213f18a628a56f527c6cc795cd7eef5#0ed7fba34213f18a628a56f527c6cc795cd7eef5"
source = "git+https://github.com/gingershaped/ruwuma.git?rev=1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62#1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62"
dependencies = [
"as_variant",
"indexmap 2.14.2",
@@ -5105,7 +5105,7 @@ dependencies = [
[[package]]
name = "ruma-federation-api"
version = "0.16.1"
source = "git+https://github.com/ruma/ruma.git?rev=0ed7fba34213f18a628a56f527c6cc795cd7eef5#0ed7fba34213f18a628a56f527c6cc795cd7eef5"
source = "git+https://github.com/gingershaped/ruwuma.git?rev=1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62#1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62"
dependencies = [
"bytes",
"http",
@@ -5127,7 +5127,7 @@ dependencies = [
[[package]]
name = "ruma-identifiers-validation"
version = "0.14.0"
source = "git+https://github.com/ruma/ruma.git?rev=0ed7fba34213f18a628a56f527c6cc795cd7eef5#0ed7fba34213f18a628a56f527c6cc795cd7eef5"
source = "git+https://github.com/gingershaped/ruwuma.git?rev=1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62#1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62"
dependencies = [
"js_int",
"thiserror 2.0.21",
@@ -5136,7 +5136,7 @@ dependencies = [
[[package]]
name = "ruma-macros"
version = "0.20.2"
source = "git+https://github.com/ruma/ruma.git?rev=0ed7fba34213f18a628a56f527c6cc795cd7eef5#0ed7fba34213f18a628a56f527c6cc795cd7eef5"
source = "git+https://github.com/gingershaped/ruwuma.git?rev=1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62#1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62"
dependencies = [
"as_variant",
"cfg-if",
@@ -5152,7 +5152,7 @@ dependencies = [
[[package]]
name = "ruma-push-gateway-api"
version = "0.16.0"
source = "git+https://github.com/ruma/ruma.git?rev=0ed7fba34213f18a628a56f527c6cc795cd7eef5#0ed7fba34213f18a628a56f527c6cc795cd7eef5"
source = "git+https://github.com/gingershaped/ruwuma.git?rev=1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62#1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62"
dependencies = [
"js_int",
"ruma-common",
@@ -5164,7 +5164,7 @@ dependencies = [
[[package]]
name = "ruma-signatures"
version = "0.22.1"
source = "git+https://github.com/ruma/ruma.git?rev=0ed7fba34213f18a628a56f527c6cc795cd7eef5#0ed7fba34213f18a628a56f527c6cc795cd7eef5"
source = "git+https://github.com/gingershaped/ruwuma.git?rev=1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62#1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62"
dependencies = [
"base64 0.23.1",
"ed25519-dalek 3.0.0",
@@ -5181,7 +5181,7 @@ dependencies = [
[[package]]
name = "ruma-state-res"
version = "0.18.0"
source = "git+https://github.com/ruma/ruma.git?rev=0ed7fba34213f18a628a56f527c6cc795cd7eef5#0ed7fba34213f18a628a56f527c6cc795cd7eef5"
source = "git+https://github.com/gingershaped/ruwuma.git?rev=1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62#1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62"
dependencies = [
"js_int",
"ruma-common",
+3 -2
View File
@@ -348,8 +348,8 @@ version = "1.1.1"
# Used for matrix spec type definitions and helpers
[workspace.dependencies.ruma]
git = "https://github.com/ruma/ruma.git"
rev = "0ed7fba34213f18a628a56f527c6cc795cd7eef5"
git = "https://github.com/gingershaped/ruwuma.git"
rev = "1e2c07dc7b65df9a35d9e3c2cedc13857dae8a62"
features = [
"appservice-api-c",
"client-api",
@@ -381,6 +381,7 @@ features = [
"unstable-msc4143",
"unstable-msc4293",
"unstable-msc4363",
"unstable-msc4322",
"unstable-msc4406",
"unstable-msc4439",
"unstable-msc4354",
+22 -1
View File
@@ -18,7 +18,7 @@ use ruma::{
get_content, get_content_as_filename, get_content_thumbnail, get_media_config,
get_media_preview,
},
media::create_content,
media::{self, create_content},
},
};
use service::media::mxc::Mxc;
@@ -238,6 +238,27 @@ pub(crate) async fn get_media_preview_route(
})
}
/// # `POST /_matrix/client/unstable/uk.timedout.msc4322/media/redact/{server_name}/{media_id}`
pub(crate) async fn unstable_redact_media_route(
State(services): State<crate::State>,
body: Ruma<media::redact::unstable::Request>,
) -> Result<media::redact::unstable::Response> {
let user = body.identity.expect_sender_user()?;
let mxc = Mxc {
server_name: &body.server_name,
media_id: &body.media_id,
};
if !(services.media.user_owns(user, &mxc).await || services.admin.user_is_admin(user).await) {
return Err!(Request(Forbidden("You cannot redact this media.")));
}
services.media.redact(&mxc).await?;
Ok(media::redact::unstable::Response::new())
}
async fn fetch_thumbnail_meta(
services: &Services,
mxc: &Mxc<'_>,
+1
View File
@@ -157,6 +157,7 @@ pub fn build(router: Router<State>, state: State) -> Router<State> {
.ruma_route(&client::get_content_as_filename_route)
.ruma_route(&client::get_media_preview_route)
.ruma_route(&client::get_media_config_route)
.ruma_route(&client::unstable_redact_media_route)
.ruma_route(&client::get_devices_route)
.ruma_route(&client::get_device_route)
.ruma_route(&client::update_device_route)
+4
View File
@@ -38,6 +38,10 @@ pub(crate) async fn get_content_route(
content_disposition,
);
services
.media
.track_remote_download(&body.identity, &body.media_id);
Ok(get_content::v1::Response::new(
ContentMetadata::new(),
FileOrLocation::File(content),
+28 -3
View File
@@ -1,12 +1,15 @@
use std::{sync::Arc, time::Duration};
use std::{collections::HashSet, sync::Arc, time::Duration};
use conduwuit::{
Err, Result, debug, debug_info, err,
Err, Result, at, debug, debug_info, err,
utils::{ReadyExt, str_from_bytes, stream::TryIgnore, string_from_bytes},
};
use database::{Database, Interfix, Map};
use futures::StreamExt;
use ruma::{OwnedMxcUri, OwnedUserId, UserId, http_headers::ContentDisposition};
use ruma::{
OwnedMxcUri, OwnedServerName, OwnedUserId, ServerName, UserId,
http_headers::ContentDisposition,
};
use super::{preview::UrlPreviewData, thumbnail::Dim};
use crate::media::mxc::Mxc;
@@ -15,6 +18,8 @@ pub(crate) struct Data {
mediaid_file: Arc<Map>,
mediaid_user: Arc<Map>,
url_previews: Arc<Map>,
mediaidremote_remotedownload: Arc<Map>,
mediaid_redacted: Arc<Map>,
}
#[derive(Debug)]
@@ -30,6 +35,8 @@ impl Data {
mediaid_file: db["mediaid_file"].clone(),
mediaid_user: db["mediaid_user"].clone(),
url_previews: db["url_previews"].clone(),
mediaidremote_remotedownload: db["mediaidremote_remotedownload"].clone(),
mediaid_redacted: db["mediaid_redacted"].clone(),
}
}
@@ -348,4 +355,22 @@ impl Data {
audio_size,
})
}
pub(super) fn track_remote_download(&self, remote: &ServerName, media_id: &str) {
self.mediaidremote_remotedownload
.put_raw((media_id, remote), []);
}
pub(super) async fn find_remote_downloads(&self, media_id: &str) -> HashSet<OwnedServerName> {
self.mediaidremote_remotedownload
.keys_prefix::<(String, OwnedServerName), _>(media_id)
.ignore_err()
.map(at!(1))
.collect()
.await
}
pub(super) fn mark_redacted(&self, media_id: &str) {
self.mediaid_redacted.insert(media_id, []);
}
}
+45 -2
View File
@@ -5,7 +5,7 @@ mod preview;
mod remote;
mod tests;
mod thumbnail;
use std::{path::PathBuf, sync::Arc, time::SystemTime};
use std::{collections::HashSet, path::PathBuf, sync::Arc, time::SystemTime};
use async_trait::async_trait;
use base64::{Engine as _, engine::general_purpose};
@@ -19,7 +19,11 @@ use conduwuit::{
},
warn,
};
use ruma::{OwnedMxcUri, UserId, http_headers::ContentDisposition};
use ruma::{
OwnedMxcUri, OwnedServerName, ServerName, UserId,
api::federation::transactions::edu::{Edu, MediaRedactionContent},
http_headers::ContentDisposition,
};
use tokio::{
fs,
io::{AsyncReadExt, AsyncWriteExt, BufReader},
@@ -158,6 +162,31 @@ impl Service {
}
}
/// Mark a media file as redacted and delete it from the database.
pub async fn redact(&self, mxc: &Mxc<'_>) -> Result<()> {
self.db.mark_redacted(mxc.media_id);
self.delete(mxc).await?;
let edu = Edu::MediaRedaction(MediaRedactionContent::new(mxc.media_id.to_owned()));
for server in self.find_remote_downloads(mxc.media_id).await {
if let Err(err) = self.services.sending.send_edu_server(&server, &edu) {
error!(?server, %mxc, ?err, "Failed to queue media redaction EDU");
}
}
Ok(())
}
/// Check if a user uploaded a file with the given MXC.
pub async fn user_owns(&self, user: &UserId, mxc: &Mxc<'_>) -> bool {
// TODO: do this more efficiently when we refactor this garbage fire of
// a service
let mxc = OwnedMxcUri::from(mxc.to_string());
self.db.get_all_user_mxcs(user).await.contains(&mxc)
}
/// Deletes all media by the specified user
///
/// currently, this is only practical for local users
@@ -255,6 +284,20 @@ impl Service {
Ok(mxcs)
}
/// Mark a remote server as having downloaded a media file.
pub fn track_remote_download(&self, remote: &ServerName, media_id: &str) {
self.db.track_remote_download(remote, media_id);
}
/// Find all remote servers that have downloaded the local media file with
/// the given ID.
pub fn find_remote_downloads(
&self,
media_id: &str,
) -> impl Future<Output = HashSet<OwnedServerName>> {
self.db.find_remote_downloads(media_id)
}
/// Deletes all media files in the given time frame.
/// Returns a usize with the amount of media files deleted.
pub async fn delete_all_media_within_timeframe(