Files
continuwuity/src/api/client/send.rs
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

135 lines
3.7 KiB
Rust
Raw Normal View History

use std::collections::BTreeMap;
use axum::extract::State;
2026-08-02 10:14:17 +02:00
use conduwuit::{
Err, Result, err,
matrix::pdu::{PartialPdu, sticky},
utils,
};
2026-10-03 20:16:34 +01:00
use ruma::{
OwnedEventId, api::client::message::send_message_event, events::MessageLikeEventType,
};
use serde::Deserialize;
use serde_json::from_str;
use crate::{Ruma, client_ip::ClientIp};
/// # `PUT /_matrix/client/v3/rooms/{roomId}/send/{eventType}/{txnId}`
///
/// Send a message event into the room.
///
/// - Is a NOOP if the txn id was already used before and returns the same event
/// id again
/// - The only requirement for the content is that it has to be valid json
/// - Tries to send the event into the room, auth rules will determine if it is
/// allowed
pub(crate) async fn send_message_event_route(
State(services): State<crate::State>,
ClientIp(client_ip): ClientIp, // NOTE: required for updating device metadata
body: Ruma<send_message_event::v3::Request>,
) -> Result<send_message_event::v3::Response> {
let sender_user = body.identity.expect_sender_user()?;
let sender_device = body.identity.sender_device();
if services.users.is_suspended(sender_user).await? {
return Err!(Request(UserSuspended("You cannot perform this action while suspended.")));
}
services
.users
.update_device_last_seen(sender_user, sender_device, client_ip)
.await;
// Forbid m.room.encrypted if encryption is disabled
if MessageLikeEventType::RoomEncrypted == body.event_type && !services.config.allow_encryption
{
return Err!(Request(Forbidden("Encryption has been disabled")));
}
if body.event_type == MessageLikeEventType::CallInvite
&& services.rooms.directory.is_public_room(&body.room_id).await
{
return Err!(Request(Forbidden("Room call invites are not allowed in public rooms")));
}
// Check if this is a new transaction id
if let Ok(response) = services
.transactions
.get_client_txn(sender_user, sender_device, &body.txn_id)
.await
{
// The client might have sent a txnid of the /sendToDevice endpoint
// This txnid has no response associated with it
if response.is_empty() {
return Err!(Request(InvalidParam(
"Tried to use txn id already used for an incompatible endpoint."
)));
}
2026-04-11 14:52:49 -04:00
let event_id = utils::string_from_bytes(&response)
.map(TryInto::try_into)
.map_err(|e| err!(Database("Invalid event_id in txnid data: {e:?}")))??;
return Ok(send_message_event::v3::Response::new(event_id));
}
let mut unsigned = BTreeMap::new();
unsigned.insert("transaction_id".to_owned(), body.txn_id.to_string().into());
let content = from_str(body.body.body.json().get())
.map_err(|e| err!(Request(BadJson("Invalid JSON body: {e}"))))?;
2026-10-03 20:16:34 +01:00
let redacts = if body.body.event_type == MessageLikeEventType::RoomRedaction {
from_str::<MaybeRedaction>(body.body.body.json().get())
.ok()
.and_then(|r| r.redacts)
} else {
None
};
let event_id = services
.rooms
.timeline
.build_and_append_pdu(
2026-04-10 11:47:01 -04:00
PartialPdu {
event_type: body.event_type.clone().into(),
content,
2026-08-02 10:14:17 +02:00
sticky: services
.config
.allow_sticky_events
.then_some(body.sticky_duration_ms)
.flatten()
.map(sticky::object),
unsigned: Some(unsigned),
timestamp: if body.identity.is_appservice()
|| services.admin.user_is_admin(sender_user).await
{
body.timestamp
} else {
None
},
2026-10-03 20:16:34 +01:00
redacts,
..Default::default()
},
sender_user,
2025-09-17 20:46:03 +00:00
Some(&body.room_id),
None,
)
.await?;
services.transactions.add_client_txnid(
sender_user,
sender_device,
&body.txn_id,
event_id.as_bytes(),
);
2026-04-11 14:52:49 -04:00
Ok(send_message_event::v3::Response::new(event_id))
}
2026-10-03 20:16:34 +01:00
/// Struct containing exclusively the `redacts` field for MSC4169 compatible
/// redaction moving.
#[derive(Deserialize)]
struct MaybeRedaction {
#[serde(default)]
redacts: Option<OwnedEventId>,
}