1
0
Fork 0
mirror of https://gitlab.com/famedly/conduit.git synced 2025-06-27 16:35:59 +00:00

feat(admin): show media command

This commit is contained in:
Matthias Ahouansou 2025-05-06 00:47:20 +01:00
parent fd16e9c509
commit a189b66ca6
No known key found for this signature in database
5 changed files with 145 additions and 62 deletions

27
Cargo.lock generated
View file

@ -1,6 +1,6 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
version = 4
[[package]]
name = "addr2line"
@ -396,6 +396,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.6.0"
@ -1401,12 +1407,23 @@ dependencies = [
"byteorder",
"color_quant",
"gif",
"image-webp",
"num-traits",
"png",
"zune-core",
"zune-jpeg",
]
[[package]]
name = "image-webp"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f79afb8cbee2ef20f59ccd477a218c12a93943d075b492015ecb1bb81f8ee904"
dependencies = [
"byteorder-lite",
"quick-error 2.0.1",
]
[[package]]
name = "indexmap"
version = "1.9.3"
@ -2107,6 +2124,12 @@ version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
version = "1.0.36"
@ -2259,7 +2282,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00"
dependencies = [
"hostname",
"quick-error",
"quick-error 1.2.3",
]
[[package]]

View file

@ -84,6 +84,7 @@ image = { version = "0.25", default-features = false, features = [
"gif",
"jpeg",
"png",
"webp",
] }
# Used for creating media filenames
hex = "0.4"

View file

@ -193,7 +193,7 @@ pub async fn get_content_auth_route(
get_content(&body.server_name, body.media_id.clone(), true, true).await
}
async fn get_content(
pub async fn get_content(
server_name: &ServerName,
media_id: String,
allow_remote: bool,

View file

@ -9,7 +9,7 @@ mod device;
mod directory;
mod filter;
mod keys;
mod media;
pub mod media;
mod membership;
mod message;
mod openid;

View file

@ -9,6 +9,7 @@ use std::{
use bytesize::ByteSize;
use chrono::DateTime;
use clap::{Args, Parser};
use image::GenericImageView;
use regex::Regex;
use ruma::{
api::appservice::Registration,
@ -20,21 +21,25 @@ use ruma::{
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
join_rules::{JoinRule, RoomJoinRulesEventContent},
member::{MembershipState, RoomMemberEventContent},
message::RoomMessageEventContent,
message::{
FileMessageEventContent, ImageMessageEventContent, MessageType,
RoomMessageEventContent,
},
name::RoomNameEventContent,
power_levels::RoomPowerLevelsEventContent,
topic::RoomTopicEventContent,
MediaSource,
},
TimelineEventType,
},
EventId, MilliSecondsSinceUnixEpoch, MxcUri, OwnedRoomAliasId, OwnedRoomId, OwnedServerName,
RoomAliasId, RoomId, RoomVersionId, ServerName, UserId,
EventId, MilliSecondsSinceUnixEpoch, MxcUri, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId,
OwnedServerName, RoomAliasId, RoomId, RoomVersionId, ServerName, UserId,
};
use serde_json::value::to_raw_value;
use tokio::sync::{mpsc, Mutex, RwLock};
use crate::{
api::client_server::{leave_all_rooms, AUTO_GEN_PASSWORD_LENGTH},
api::client_server::{self, leave_all_rooms, AUTO_GEN_PASSWORD_LENGTH},
services,
utils::{self, HtmlEscape},
Error, PduEvent, Result,
@ -42,7 +47,7 @@ use crate::{
use super::{
media::{
BlockedMediaInfo, FileInfo, MediaListItem, MediaQuery, MediaQueryFileInfo,
size, BlockedMediaInfo, FileInfo, MediaListItem, MediaQuery, MediaQueryFileInfo,
MediaQueryThumbInfo, ServerNameOrUserId,
},
pdu::PduBuilder,
@ -138,6 +143,12 @@ enum AdminCommand {
mxc: Box<MxcUri>,
},
/// Sends a message with the requested media attached, so that you can view it easily
ShowMedia {
/// The MXC URI of the media you want to view
mxc: Box<MxcUri>,
},
/// Lists all the media matching the specified requirements
ListMedia {
#[command(flatten)]
@ -439,8 +450,8 @@ impl Service {
tokio::select! {
Some(event) = receiver.recv() => {
let message_content = match event {
AdminRoomEvent::SendMessage(content) => content,
AdminRoomEvent::ProcessMessage(room_message) => self.process_admin_message(room_message).await
AdminRoomEvent::SendMessage(content) => content.into(),
AdminRoomEvent::ProcessMessage(room_message) => self.process_admin_message(room_message).await,
};
let mutex_state = Arc::clone(
@ -491,7 +502,7 @@ impl Service {
}
// Parse and process a message from the admin room
async fn process_admin_message(&self, room_message: String) -> RoomMessageEventContent {
async fn process_admin_message(&self, room_message: String) -> MessageType {
let mut lines = room_message.lines().filter(|l| !l.trim().is_empty());
let command_line = lines.next().expect("each string has at least one line");
let body: Vec<_> = lines.collect();
@ -503,7 +514,7 @@ impl Service {
let message = error.replace("server.name", server_name.as_str());
let html_message = self.usage_to_html(&message, server_name);
return RoomMessageEventContent::text_html(message, html_message);
return RoomMessageEventContent::text_html(message, html_message).into();
}
};
@ -519,7 +530,7 @@ impl Service {
<pre>\n{error}\n</pre>",
);
RoomMessageEventContent::text_html(markdown_message, html_message)
RoomMessageEventContent::text_html(markdown_message, html_message).into()
}
}
}
@ -550,7 +561,7 @@ impl Service {
&self,
command: AdminCommand,
body: Vec<&str>,
) -> Result<RoomMessageEventContent> {
) -> Result<MessageType> {
let reply_message_content = match command {
AdminCommand::RegisterAppservice => {
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
@ -574,7 +585,7 @@ impl Service {
RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
)
}
}.into()
}
AdminCommand::UnregisterAppservice {
appservice_identifier,
@ -587,7 +598,7 @@ impl Service {
Err(e) => RoomMessageEventContent::text_plain(format!(
"Failed to unregister appservice: {e}"
)),
},
}.into(),
AdminCommand::ListAppservices => {
let appservices = services().appservice.iter_ids().await;
let output = format!(
@ -595,7 +606,7 @@ impl Service {
appservices.len(),
appservices.join(", ")
);
RoomMessageEventContent::text_plain(output)
RoomMessageEventContent::text_plain(output).into()
}
AdminCommand::ListRooms => {
let room_ids = services().rooms.metadata.iter_ids();
@ -616,7 +627,7 @@ impl Service {
.collect::<Vec<_>>()
.join("\n")
);
RoomMessageEventContent::text_plain(output)
RoomMessageEventContent::text_plain(output).into()
}
AdminCommand::ListLocalUsers => match services().users.list_local_users() {
Ok(users) => {
@ -625,7 +636,7 @@ impl Service {
RoomMessageEventContent::text_plain(&msg)
}
Err(e) => RoomMessageEventContent::text_plain(e.to_string()),
},
}.into(),
AdminCommand::IncomingFederation => {
let map = services().globals.roomid_federationhandletime.read().await;
let mut msg: String = format!("Handling {} incoming pdus:\n", map.len());
@ -640,7 +651,7 @@ impl Service {
elapsed.as_secs() % 60
);
}
RoomMessageEventContent::text_plain(&msg)
RoomMessageEventContent::text_plain(&msg).into()
}
AdminCommand::GetAuthChain { event_id } => {
let event_id = Arc::<EventId>::from(event_id);
@ -666,7 +677,7 @@ impl Service {
))
} else {
RoomMessageEventContent::text_plain("Event not found.")
}
}.into()
}
AdminCommand::ParsePdu => {
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
@ -700,7 +711,7 @@ impl Service {
}
} else {
RoomMessageEventContent::text_plain("Expected code block in command body.")
}
}.into()
}
AdminCommand::GetPdu { event_id } => {
let mut outlier = false;
@ -738,7 +749,7 @@ impl Service {
)
}
None => RoomMessageEventContent::text_plain("PDU not found."),
}
}.into()
}
AdminCommand::MemoryUsage => {
let response1 = services().memory_usage().await;
@ -746,21 +757,21 @@ impl Service {
RoomMessageEventContent::text_plain(format!(
"Services:\n{response1}\n\nDatabase:\n{response2}"
))
)).into()
}
AdminCommand::ClearDatabaseCaches { amount } => {
services().globals.db.clear_caches(amount);
RoomMessageEventContent::text_plain("Done.")
RoomMessageEventContent::text_plain("Done.").into()
}
AdminCommand::ClearServiceCaches { amount } => {
services().clear_caches(amount).await;
RoomMessageEventContent::text_plain("Done.")
RoomMessageEventContent::text_plain("Done.").into()
}
AdminCommand::ShowConfig => {
// Construct and send the response
RoomMessageEventContent::text_plain(format!("{}", services().globals.config))
RoomMessageEventContent::text_plain(format!("{}", services().globals.config)).into()
}
AdminCommand::ResetPassword { username } => {
let user_id = match UserId::parse_with_server_name(
@ -771,7 +782,7 @@ impl Service {
Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!(
"The supplied username is not a valid username: {e}"
)))
)).into())
}
};
@ -779,7 +790,7 @@ impl Service {
if user_id.server_name() != services().globals.server_name() {
return Ok(RoomMessageEventContent::text_plain(
"The specified user is not from this server!",
));
).into());
};
// Check if the specified user is valid
@ -793,7 +804,7 @@ impl Service {
{
return Ok(RoomMessageEventContent::text_plain(
"The specified user does not exist!",
));
).into());
}
let new_password = utils::random_string(AUTO_GEN_PASSWORD_LENGTH);
@ -808,7 +819,7 @@ impl Service {
Err(e) => RoomMessageEventContent::text_plain(format!(
"Couldn't reset the password for user {user_id}: {e}"
)),
}
}.into()
}
AdminCommand::CreateUser { username, password } => {
let password =
@ -822,7 +833,7 @@ impl Service {
Err(e) => {
return Ok(RoomMessageEventContent::text_plain(format!(
"The supplied username is not a valid username: {e}"
)))
)).into())
}
};
@ -830,18 +841,18 @@ impl Service {
if user_id.server_name() != services().globals.server_name() {
return Ok(RoomMessageEventContent::text_plain(
"The specified user is not from this server!",
));
).into());
};
if user_id.is_historical() {
return Ok(RoomMessageEventContent::text_plain(format!(
"Userid {user_id} is not allowed due to historical"
)));
)).into());
}
if services().users.exists(&user_id)? {
return Ok(RoomMessageEventContent::text_plain(format!(
"Userid {user_id} already exists"
)));
)).into());
}
// Create user
services().users.create(&user_id, Some(password.as_str()))?;
@ -878,7 +889,7 @@ impl Service {
// Inhibit login does not work for guests
RoomMessageEventContent::text_plain(format!(
"Created user with user_id: {user_id} and password: {password}"
))
)).into()
}
AdminCommand::AllowRegistration { status } => {
if let Some(status) = status {
@ -896,15 +907,15 @@ impl Service {
"Registration is currently disabled"
},
)
}
}.into()
}
AdminCommand::DisableRoom { room_id } => {
services().rooms.metadata.disable_room(&room_id, true)?;
RoomMessageEventContent::text_plain("Room disabled.")
RoomMessageEventContent::text_plain("Room disabled.").into()
}
AdminCommand::EnableRoom { room_id } => {
services().rooms.metadata.disable_room(&room_id, false)?;
RoomMessageEventContent::text_plain("Room enabled.")
RoomMessageEventContent::text_plain("Room enabled.").into()
}
AdminCommand::DeactivateUser {
leave_rooms,
@ -954,7 +965,7 @@ impl Service {
"User {user_id} has been deactivated, but {failed_purged_media} media failed to be purged, check the logs for more details"
))
}
}
}.into()
}
AdminCommand::DeactivateAll {
leave_rooms,
@ -1027,11 +1038,11 @@ impl Service {
RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
)
}
}.into()
}
AdminCommand::QueryMedia { mxc } => {
let Ok((server_name, media_id)) = mxc.parts() else {
return Ok(RoomMessageEventContent::text_plain("Invalid media MXC"));
return Ok(RoomMessageEventContent::text_plain("Invalid media MXC").into());
};
let MediaQuery{ is_blocked, source_file, thumbnails } = services().media.query(server_name, media_id)?;
@ -1118,7 +1129,55 @@ impl Service {
}
}
RoomMessageEventContent::text_plain(message)
RoomMessageEventContent::text_plain(message).into()
}
AdminCommand::ShowMedia { mxc } => {
let Ok((server_name, media_id)) = mxc.parts() else {
return Ok(RoomMessageEventContent::text_plain("Invalid media MXC").into());
};
// TODO: Bypass blocking once MSC3911 is implemented (linking media to events)
let ruma::api::client::authenticated_media::get_content::v1::Response {
file,
content_type,
content_disposition,
} = client_server::media::get_content(server_name, media_id.to_owned(), true, true).await?;
if let Ok(image) = image::load_from_memory(&file) {
let filename = content_disposition.and_then(|cd| cd.filename);
let (width, height) = image.dimensions();
MessageType::Image(ImageMessageEventContent {
body: filename.clone().unwrap_or_default(),
formatted: None,
filename,
source: MediaSource::Plain(OwnedMxcUri::from(mxc.to_owned())),
info: Some(Box::new(ruma::events::room::ImageInfo {
height: Some(height.into()),
width: Some(width.into()),
mimetype: content_type,
size: size(&file)?.try_into().ok(),
thumbnail_info: None,
thumbnail_source: None,
blurhash: None,
})),
})
} else {
let filename = content_disposition.and_then(|cd| cd.filename);
MessageType::File(FileMessageEventContent {
body: filename.clone().unwrap_or_default(),
formatted: None,
filename,
source: MediaSource::Plain(OwnedMxcUri::from(mxc.to_owned())),
info: Some(Box::new(ruma::events::room::message::FileInfo {
mimetype: content_type,
size: size(&file)?.try_into().ok(),
thumbnail_info: None,
thumbnail_source: None,
})),
})
}
}
AdminCommand::ListMedia {
user_server_filter: ListMediaArgs {
@ -1183,7 +1242,7 @@ impl Service {
html_message.push_str("</tbody></table>");
RoomMessageEventContent::text_html(markdown_message, html_message)
RoomMessageEventContent::text_html(markdown_message, html_message).into()
},
AdminCommand::PurgeMedia => media_from_body(body).map_or_else(
|message| message,
@ -1196,7 +1255,7 @@ impl Service {
RoomMessageEventContent::text_plain(format!(
"Failed to delete {failed_count} media, check logs for more details"
))
}
}.into()
},
),
AdminCommand::PurgeMediaFromUsers {
@ -1232,7 +1291,7 @@ impl Service {
RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
)
}
}.into()
}
AdminCommand::PurgeMediaFromServer {
server_id: server_name,
@ -1260,7 +1319,7 @@ impl Service {
RoomMessageEventContent::text_plain(format!(
"Failed to purge {failed_count} media, check logs for more details"
))
}
}.into()
}
AdminCommand::BlockMedia { and_purge, reason } => media_from_body(body).map_or_else(
|message| message,
@ -1283,7 +1342,7 @@ impl Service {
(false, false) => RoomMessageEventContent::text_plain(format!(
"Failed to block {failed_count}, and purge {failed_purge_count} media, check logs for more details"
))
}
}.into()
},
),
AdminCommand::BlockMediaFromUsers { from_last, reason } => {
@ -1321,7 +1380,7 @@ impl Service {
RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
)
}
}.into()
}
AdminCommand::ListBlockedMedia => {
let mut markdown_message = String::from(
@ -1361,7 +1420,7 @@ impl Service {
html_message.push_str("</tbody></table>");
RoomMessageEventContent::text_html(markdown_message, html_message)
RoomMessageEventContent::text_html(markdown_message, html_message).into()
}
AdminCommand::UnblockMedia => media_from_body(body).map_or_else(
|message| message,
@ -1374,7 +1433,7 @@ impl Service {
RoomMessageEventContent::text_plain(format!(
"Failed to unblock {failed_count} media, check logs for more details"
))
}
}.into()
},
),
AdminCommand::SignJson => {
@ -1399,7 +1458,7 @@ impl Service {
RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
)
}
}.into()
}
AdminCommand::VerifyJson => {
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```"
@ -1460,7 +1519,7 @@ impl Service {
RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
)
}
}.into()
}
AdminCommand::HashAndSignEvent { room_version_id } => {
if body.len() > 2
@ -1490,7 +1549,7 @@ impl Service {
RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
)
}
}.into()
}
AdminCommand::RemoveAlias { alias } => {
if alias.server_name() != services().globals.server_name() {
@ -1515,7 +1574,7 @@ impl Service {
.alias
.remove_alias(&alias, services().globals.server_user())?;
RoomMessageEventContent::text_plain("Alias removed successfully")
}
}.into()
}
};
@ -2010,7 +2069,7 @@ impl Service {
fn userids_from_body<'a>(
body: &'a [&'a str],
) -> Result<Result<Vec<&'a UserId>, RoomMessageEventContent>, Error> {
) -> Result<Result<Vec<&'a UserId>, MessageType>, Error> {
let users = body.to_owned().drain(1..body.len() - 1).collect::<Vec<_>>();
let mut user_ids = Vec::new();
@ -2071,15 +2130,14 @@ fn userids_from_body<'a>(
return Ok(Err(RoomMessageEventContent::text_html(
markdown_message,
html_message,
)));
)
.into()));
}
Ok(Ok(user_ids))
}
fn media_from_body(
body: Vec<&str>,
) -> Result<Vec<(OwnedServerName, String)>, RoomMessageEventContent> {
fn media_from_body(body: Vec<&str>) -> Result<Vec<(OwnedServerName, String)>, MessageType> {
if body.len() > 2 && body[0].trim() == "```" && body.last().unwrap().trim() == "```" {
Ok(body
.clone()
@ -2094,7 +2152,8 @@ fn media_from_body(
} else {
Err(RoomMessageEventContent::text_plain(
"Expected code block in command body. Add --help for details.",
))
)
.into())
}
}