mirror of
https://forgejo.ellis.link/continuwuation/continuwuity.git
synced 2025-07-12 05:16:22 +02:00
feat(902): Upload files for admin commands that are too long
This commit is contained in:
parent
e61a593932
commit
7b60f5368d
2 changed files with 108 additions and 9 deletions
|
@ -192,8 +192,9 @@ pub async fn revoke_admin(&self, user_id: &UserId) -> Result {
|
||||||
|
|
||||||
| Err(e) => return Err!(error!(?e, "Failure occurred while attempting revoke.")),
|
| Err(e) => return Err!(error!(?e, "Failure occurred while attempting revoke.")),
|
||||||
|
|
||||||
| Ok(event) if !matches!(event.membership, Invite | Knock | Join) =>
|
| Ok(event) if !matches!(event.membership, Invite | Knock | Join) => {
|
||||||
return Err!("Cannot revoke {user_id} in membership state {:?}.", event.membership),
|
return Err!("Cannot revoke {user_id} in membership state {:?}.", event.membership);
|
||||||
|
},
|
||||||
|
|
||||||
| Ok(event) => {
|
| Ok(event) => {
|
||||||
assert!(
|
assert!(
|
||||||
|
|
|
@ -9,6 +9,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use conduwuit::{Err, utils};
|
||||||
use conduwuit_core::{
|
use conduwuit_core::{
|
||||||
Error, Event, Result, Server, debug, err, error, error::default_log, pdu::PduBuilder,
|
Error, Event, Result, Server, debug, err, error, error::default_log, pdu::PduBuilder,
|
||||||
};
|
};
|
||||||
|
@ -16,15 +17,20 @@ pub use create::create_admin_room;
|
||||||
use futures::{Future, FutureExt, TryFutureExt};
|
use futures::{Future, FutureExt, TryFutureExt};
|
||||||
use loole::{Receiver, Sender};
|
use loole::{Receiver, Sender};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
OwnedEventId, OwnedRoomId, RoomId, UserId,
|
Mxc, OwnedEventId, OwnedMxcUri, OwnedRoomId, RoomId, UInt, UserId,
|
||||||
events::{
|
events::{
|
||||||
Mentions,
|
Mentions,
|
||||||
room::message::{Relation, RoomMessageEventContent},
|
room::{
|
||||||
|
MediaSource,
|
||||||
|
message::{
|
||||||
|
FileInfo, FileMessageEventContent, MessageType, Relation, RoomMessageEventContent,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::{Dep, account_data, globals, rooms, rooms::state::RoomMutexGuard};
|
use crate::{Dep, account_data, globals, media::MXC_LENGTH, rooms, rooms::state::RoomMutexGuard};
|
||||||
|
|
||||||
pub struct Service {
|
pub struct Service {
|
||||||
services: Services,
|
services: Services,
|
||||||
|
@ -45,6 +51,7 @@ struct Services {
|
||||||
state_accessor: Dep<rooms::state_accessor::Service>,
|
state_accessor: Dep<rooms::state_accessor::Service>,
|
||||||
account_data: Dep<account_data::Service>,
|
account_data: Dep<account_data::Service>,
|
||||||
services: StdRwLock<Option<Weak<crate::Services>>>,
|
services: StdRwLock<Option<Weak<crate::Services>>>,
|
||||||
|
media: Dep<crate::media::Service>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inputs to a command are a multi-line string, optional reply_id, and optional
|
/// Inputs to a command are a multi-line string, optional reply_id, and optional
|
||||||
|
@ -94,6 +101,7 @@ impl crate::Service for Service {
|
||||||
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
|
.depend::<rooms::state_accessor::Service>("rooms::state_accessor"),
|
||||||
account_data: args.depend::<account_data::Service>("account_data"),
|
account_data: args.depend::<account_data::Service>("account_data"),
|
||||||
services: None.into(),
|
services: None.into(),
|
||||||
|
media: args.depend::<crate::media::Service>("media"),
|
||||||
},
|
},
|
||||||
channel: loole::bounded(COMMAND_QUEUE_LIMIT),
|
channel: loole::bounded(COMMAND_QUEUE_LIMIT),
|
||||||
handle: RwLock::new(None),
|
handle: RwLock::new(None),
|
||||||
|
@ -157,8 +165,64 @@ impl Service {
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends a message to the admin room as the admin user (see send_text() for
|
/// Either returns a small-enough message, or converts a large message into
|
||||||
/// convenience).
|
/// a file
|
||||||
|
pub async fn text_or_file(
|
||||||
|
&self,
|
||||||
|
message_content: RoomMessageEventContent,
|
||||||
|
) -> RoomMessageEventContent {
|
||||||
|
let body_len = Self::collate_msg_size(&message_content);
|
||||||
|
if body_len > 60000 {
|
||||||
|
// Intercept and send as file
|
||||||
|
let file = self
|
||||||
|
.text_to_file(message_content.body())
|
||||||
|
.await
|
||||||
|
.expect("failed to create text file");
|
||||||
|
let metadata = FileInfo {
|
||||||
|
mimetype: Some("text/markdown".to_owned()),
|
||||||
|
size: Some(UInt::new_saturating(message_content.body().len() as u64)),
|
||||||
|
thumbnail_info: None,
|
||||||
|
thumbnail_source: None,
|
||||||
|
};
|
||||||
|
let content = FileMessageEventContent {
|
||||||
|
body: "Output was too large to send as text.".to_owned(),
|
||||||
|
formatted: None,
|
||||||
|
filename: Some("output.md".to_owned()),
|
||||||
|
source: MediaSource::Plain(file),
|
||||||
|
info: Some(Box::new(metadata)),
|
||||||
|
};
|
||||||
|
RoomMessageEventContent::new(MessageType::File(content))
|
||||||
|
} else {
|
||||||
|
message_content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn collate_msg_size(content: &RoomMessageEventContent) -> u64 {
|
||||||
|
content
|
||||||
|
.body()
|
||||||
|
.len()
|
||||||
|
.saturating_add(match &content.msgtype {
|
||||||
|
| MessageType::Text(t) =>
|
||||||
|
if t.formatted.is_some() {
|
||||||
|
t.formatted.as_ref().map_or(0, |f| f.body.len())
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
| MessageType::Notice(n) =>
|
||||||
|
if n.formatted.is_some() {
|
||||||
|
n.formatted.as_ref().map_or(0, |f| f.body.len())
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
| _ => 0,
|
||||||
|
})
|
||||||
|
.try_into()
|
||||||
|
.expect("size too large")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a message to the admin room as the admin user (see send_text()
|
||||||
|
/// for convenience).
|
||||||
pub async fn send_message(&self, message_content: RoomMessageEventContent) -> Result<()> {
|
pub async fn send_message(&self, message_content: RoomMessageEventContent) -> Result<()> {
|
||||||
let user_id = &self.services.globals.server_user;
|
let user_id = &self.services.globals.server_user;
|
||||||
let room_id = self.get_admin_room().await?;
|
let room_id = self.get_admin_room().await?;
|
||||||
|
@ -178,6 +242,36 @@ impl Service {
|
||||||
self.send_message(message_content).await
|
self.send_message(message_content).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Casts a text body into a file and creates a file for it.
|
||||||
|
pub async fn text_to_file(&self, body: &str) -> Result<OwnedMxcUri> {
|
||||||
|
let mxc = Mxc {
|
||||||
|
server_name: self.services.globals.server_name(),
|
||||||
|
media_id: &utils::random_string(MXC_LENGTH),
|
||||||
|
};
|
||||||
|
match self
|
||||||
|
.services
|
||||||
|
.media
|
||||||
|
.create(
|
||||||
|
&mxc,
|
||||||
|
Some(self.services.globals.server_user.as_ref()),
|
||||||
|
Some(&utils::content_disposition::make_content_disposition(
|
||||||
|
None,
|
||||||
|
Some("text/markdown"),
|
||||||
|
Some("output.md"),
|
||||||
|
)),
|
||||||
|
Some("text/markdown"),
|
||||||
|
body.as_bytes(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
| Ok(()) => Ok(mxc.to_string().into()),
|
||||||
|
| Err(e) => {
|
||||||
|
error!("Failed to upload text to file: {e}");
|
||||||
|
Err!(Request(Unknown("Failed to upload text to file")))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Posts a command to the command processor queue and returns. Processing
|
/// Posts a command to the command processor queue and returns. Processing
|
||||||
/// will take place on the service worker's task asynchronously. Errors if
|
/// will take place on the service worker's task asynchronously. Errors if
|
||||||
/// the queue is full.
|
/// the queue is full.
|
||||||
|
@ -325,11 +419,15 @@ impl Service {
|
||||||
assert!(self.user_is_admin(user_id).await, "sender is not admin");
|
assert!(self.user_is_admin(user_id).await, "sender is not admin");
|
||||||
|
|
||||||
let state_lock = self.services.state.mutex.lock(room_id).await;
|
let state_lock = self.services.state.mutex.lock(room_id).await;
|
||||||
|
|
||||||
if let Err(e) = self
|
if let Err(e) = self
|
||||||
.services
|
.services
|
||||||
.timeline
|
.timeline
|
||||||
.build_and_append_pdu(PduBuilder::timeline(&content), user_id, room_id, &state_lock)
|
.build_and_append_pdu(
|
||||||
|
PduBuilder::timeline(&self.text_or_file(content).await),
|
||||||
|
user_id,
|
||||||
|
room_id,
|
||||||
|
&state_lock,
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
self.handle_response_error(e, room_id, user_id, &state_lock)
|
self.handle_response_error(e, room_id, user_id, &state_lock)
|
||||||
|
|
Loading…
Add table
Reference in a new issue