feat(sdk): Add the ability to send out custom message events
This commit is contained in:
parent
7a21bdd573
commit
bae6b33497
6 changed files with 190 additions and 36 deletions
|
@ -43,10 +43,12 @@ use ruma::{
|
||||||
},
|
},
|
||||||
room_key::RoomKeyToDeviceEventContent,
|
room_key::RoomKeyToDeviceEventContent,
|
||||||
secret::request::SecretName,
|
secret::request::SecretName,
|
||||||
AnyMessageEventContent, AnyRoomEvent, AnyToDeviceEvent, SyncMessageEvent, ToDeviceEvent,
|
AnyMessageEventContent, AnyRoomEvent, AnyToDeviceEvent, EventContent, SyncMessageEvent,
|
||||||
|
ToDeviceEvent,
|
||||||
},
|
},
|
||||||
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UInt, UserId,
|
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UInt, UserId,
|
||||||
};
|
};
|
||||||
|
use serde_json::Value;
|
||||||
use tracing::{debug, error, info, trace, warn};
|
use tracing::{debug, error, info, trace, warn};
|
||||||
|
|
||||||
#[cfg(feature = "sled_cryptostore")]
|
#[cfg(feature = "sled_cryptostore")]
|
||||||
|
@ -642,6 +644,9 @@ impl OlmMachine {
|
||||||
/// [`should_share_group_session`] method if a new group session needs to
|
/// [`should_share_group_session`] method if a new group session needs to
|
||||||
/// be shared.
|
/// be shared.
|
||||||
///
|
///
|
||||||
|
/// **Note**: This method doesn't support encrypting custom events, see the
|
||||||
|
/// [`encrypt_raw()`] method to do so.
|
||||||
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `room_id` - The id of the room for which the message should be
|
/// * `room_id` - The id of the room for which the message should be
|
||||||
|
@ -656,12 +661,45 @@ impl OlmMachine {
|
||||||
///
|
///
|
||||||
/// [`should_share_group_session`]: #method.should_share_group_session
|
/// [`should_share_group_session`]: #method.should_share_group_session
|
||||||
/// [`share_group_session`]: #method.share_group_session
|
/// [`share_group_session`]: #method.share_group_session
|
||||||
|
/// [`encrypt_raw()`]: #method.encrypt_raw
|
||||||
pub async fn encrypt(
|
pub async fn encrypt(
|
||||||
&self,
|
&self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
content: AnyMessageEventContent,
|
content: AnyMessageEventContent,
|
||||||
) -> MegolmResult<EncryptedEventContent> {
|
) -> MegolmResult<EncryptedEventContent> {
|
||||||
self.group_session_manager.encrypt(room_id, content).await
|
let event_type = content.event_type().to_owned();
|
||||||
|
let content = serde_json::to_value(content)?;
|
||||||
|
|
||||||
|
self.group_session_manager.encrypt(room_id, content, &event_type).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encrypt a json [`Value`] content for the given room.
|
||||||
|
///
|
||||||
|
/// This method is equivalent to the [`encrypt()`] method but allows custom
|
||||||
|
/// events to be encrypted.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `room_id` - The id of the room for which the message should be
|
||||||
|
/// encrypted.
|
||||||
|
///
|
||||||
|
/// * `content` - The plaintext content of the message that should be
|
||||||
|
/// encrypted as a json [`Value`].
|
||||||
|
///
|
||||||
|
/// * `event_type` - The plaintext type of the event.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics if a group session for the given room wasn't shared beforehand.
|
||||||
|
///
|
||||||
|
/// [`encrypt()`]: #method.encrypt
|
||||||
|
pub async fn encrypt_raw(
|
||||||
|
&self,
|
||||||
|
room_id: &RoomId,
|
||||||
|
content: Value,
|
||||||
|
event_type: &str,
|
||||||
|
) -> MegolmResult<EncryptedEventContent> {
|
||||||
|
self.group_session_manager.encrypt(room_id, content, event_type).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Invalidate the currently active outbound group session for the given
|
/// Invalidate the currently active outbound group session for the given
|
||||||
|
|
|
@ -136,11 +136,11 @@ mod test {
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::EncryptionSettings;
|
use super::EncryptionSettings;
|
||||||
use crate::ReadOnlyAccount;
|
use crate::{MegolmError, ReadOnlyAccount};
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
async fn expiration() {
|
async fn expiration() -> Result<(), MegolmError> {
|
||||||
let settings = EncryptionSettings { rotation_period_msgs: 1, ..Default::default() };
|
let settings = EncryptionSettings { rotation_period_msgs: 1, ..Default::default() };
|
||||||
|
|
||||||
let account = ReadOnlyAccount::new(&user_id!("@alice:example.org"), "DEVICEID".into());
|
let account = ReadOnlyAccount::new(&user_id!("@alice:example.org"), "DEVICEID".into());
|
||||||
|
@ -151,9 +151,12 @@ mod test {
|
||||||
|
|
||||||
assert!(!session.expired());
|
assert!(!session.expired());
|
||||||
let _ = session
|
let _ = session
|
||||||
.encrypt(AnyMessageEventContent::RoomMessage(MessageEventContent::text_plain(
|
.encrypt(
|
||||||
"Test message",
|
serde_json::to_value(AnyMessageEventContent::RoomMessage(
|
||||||
)))
|
MessageEventContent::text_plain("Test message"),
|
||||||
|
))?,
|
||||||
|
"m.room.message",
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
assert!(session.expired());
|
assert!(session.expired());
|
||||||
|
|
||||||
|
@ -170,5 +173,7 @@ mod test {
|
||||||
assert!(!session.expired());
|
assert!(!session.expired());
|
||||||
session.creation_time = Arc::new(Instant::now() - Duration::from_secs(60 * 60));
|
session.creation_time = Arc::new(Instant::now() - Duration::from_secs(60 * 60));
|
||||||
assert!(session.expired());
|
assert!(session.expired());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,12 +41,12 @@ use ruma::{
|
||||||
history_visibility::HistoryVisibility,
|
history_visibility::HistoryVisibility,
|
||||||
},
|
},
|
||||||
room_key::RoomKeyToDeviceEventContent,
|
room_key::RoomKeyToDeviceEventContent,
|
||||||
AnyMessageEventContent, AnyToDeviceEventContent, EventContent,
|
AnyToDeviceEventContent,
|
||||||
},
|
},
|
||||||
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UserId,
|
DeviceId, DeviceIdBox, DeviceKeyAlgorithm, EventEncryptionAlgorithm, RoomId, UserId,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::json;
|
use serde_json::{json, Value};
|
||||||
use tracing::{debug, error, trace};
|
use tracing::{debug, error, trace};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -268,20 +268,23 @@ impl OutboundGroupSession {
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `content` - The plaintext content of the message that should be
|
/// * `content` - The plaintext content of the message that should be
|
||||||
/// encrypted.
|
/// encrypted in raw json [`Value`] form.
|
||||||
|
///
|
||||||
|
/// * `event_type` - The plaintext type of the event, the outer type of the
|
||||||
|
/// event will become `m.room.encrypted`.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
///
|
///
|
||||||
/// Panics if the content can't be serialized.
|
/// Panics if the content can't be serialized.
|
||||||
pub async fn encrypt(&self, content: AnyMessageEventContent) -> EncryptedEventContent {
|
pub async fn encrypt(&self, content: Value, event_type: &str) -> EncryptedEventContent {
|
||||||
let json_content = json!({
|
let json_content = json!({
|
||||||
"content": content,
|
"content": content,
|
||||||
"room_id": &*self.room_id,
|
"room_id": &*self.room_id,
|
||||||
"type": content.event_type(),
|
"type": event_type,
|
||||||
});
|
});
|
||||||
|
|
||||||
let plaintext = json_content.to_string();
|
let plaintext = json_content.to_string();
|
||||||
let relation = content.relation();
|
let relation = serde_json::from_value(content).ok();
|
||||||
|
|
||||||
let ciphertext = self.encrypt_helper(plaintext).await;
|
let ciphertext = self.encrypt_helper(plaintext).await;
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,10 @@ pub(crate) mod test {
|
||||||
};
|
};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::olm::{InboundGroupSession, ReadOnlyAccount, Session};
|
use crate::{
|
||||||
|
olm::{InboundGroupSession, ReadOnlyAccount, Session},
|
||||||
|
MegolmError,
|
||||||
|
};
|
||||||
|
|
||||||
fn alice_id() -> UserId {
|
fn alice_id() -> UserId {
|
||||||
user_id!("@alice:example.org")
|
user_id!("@alice:example.org")
|
||||||
|
@ -223,7 +226,7 @@ pub(crate) mod test {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn edit_decryption() {
|
async fn edit_decryption() -> Result<(), MegolmError> {
|
||||||
let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id());
|
let alice = ReadOnlyAccount::new(&alice_id(), &alice_device_id());
|
||||||
let room_id = room_id!("!test:localhost");
|
let room_id = room_id!("!test:localhost");
|
||||||
let event_id = event_id!("$1234adfad:asdf");
|
let event_id = event_id!("$1234adfad:asdf");
|
||||||
|
@ -247,15 +250,18 @@ pub(crate) mod test {
|
||||||
&room_id,
|
&room_id,
|
||||||
outbound.session_key().await,
|
outbound.session_key().await,
|
||||||
None,
|
None,
|
||||||
)
|
)?;
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(0, inbound.first_known_index());
|
assert_eq!(0, inbound.first_known_index());
|
||||||
|
|
||||||
assert_eq!(outbound.session_id(), inbound.session_id());
|
assert_eq!(outbound.session_id(), inbound.session_id());
|
||||||
|
|
||||||
let encrypted_content =
|
let encrypted_content = outbound
|
||||||
outbound.encrypt(AnyMessageEventContent::RoomMessage(content)).await;
|
.encrypt(
|
||||||
|
serde_json::to_value(AnyMessageEventContent::RoomMessage(content))?,
|
||||||
|
"m.room.message",
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
let event = json!({
|
let event = json!({
|
||||||
"sender": alice.user_id(),
|
"sender": alice.user_id(),
|
||||||
|
@ -276,15 +282,17 @@ pub(crate) mod test {
|
||||||
panic!("Invalid event type")
|
panic!("Invalid event type")
|
||||||
};
|
};
|
||||||
|
|
||||||
let decrypted = inbound.decrypt(&event).await.unwrap().0;
|
let decrypted = inbound.decrypt(&event).await?.0;
|
||||||
|
|
||||||
if let AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(e)) =
|
if let AnySyncRoomEvent::Message(AnySyncMessageEvent::RoomMessage(e)) =
|
||||||
decrypted.deserialize().unwrap()
|
decrypted.deserialize()?
|
||||||
{
|
{
|
||||||
assert_matches!(e.content.relates_to, Some(Relation::Replacement(_)));
|
assert_matches!(e.content.relates_to, Some(Relation::Replacement(_)));
|
||||||
} else {
|
} else {
|
||||||
panic!("Invalid event type")
|
panic!("Invalid event type")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
|
|
|
@ -23,12 +23,13 @@ use matrix_sdk_common::{executor::spawn, uuid::Uuid};
|
||||||
use ruma::{
|
use ruma::{
|
||||||
events::{
|
events::{
|
||||||
room::{encrypted::EncryptedEventContent, history_visibility::HistoryVisibility},
|
room::{encrypted::EncryptedEventContent, history_visibility::HistoryVisibility},
|
||||||
AnyMessageEventContent, AnyToDeviceEventContent, EventType,
|
AnyToDeviceEventContent, EventType,
|
||||||
},
|
},
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
to_device::DeviceIdOrAllDevices,
|
to_device::DeviceIdOrAllDevices,
|
||||||
DeviceId, DeviceIdBox, RoomId, UserId,
|
DeviceId, DeviceIdBox, RoomId, UserId,
|
||||||
};
|
};
|
||||||
|
use serde_json::Value;
|
||||||
use tracing::{debug, info, trace};
|
use tracing::{debug, info, trace};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -155,7 +156,8 @@ impl GroupSessionManager {
|
||||||
pub async fn encrypt(
|
pub async fn encrypt(
|
||||||
&self,
|
&self,
|
||||||
room_id: &RoomId,
|
room_id: &RoomId,
|
||||||
content: AnyMessageEventContent,
|
content: Value,
|
||||||
|
event_type: &str,
|
||||||
) -> MegolmResult<EncryptedEventContent> {
|
) -> MegolmResult<EncryptedEventContent> {
|
||||||
let session = if let Some(s) = self.sessions.get(room_id) {
|
let session = if let Some(s) = self.sessions.get(room_id) {
|
||||||
s
|
s
|
||||||
|
@ -167,7 +169,7 @@ impl GroupSessionManager {
|
||||||
panic!("Session expired");
|
panic!("Session expired");
|
||||||
}
|
}
|
||||||
|
|
||||||
let content = session.encrypt(content).await;
|
let content = session.encrypt(content, event_type).await;
|
||||||
|
|
||||||
let mut changes = Changes::default();
|
let mut changes = Changes::default();
|
||||||
changes.outbound_group_sessions.push(session);
|
changes.outbound_group_sessions.push(session);
|
||||||
|
|
|
@ -38,7 +38,7 @@ use ruma::{
|
||||||
EncryptedFile,
|
EncryptedFile,
|
||||||
},
|
},
|
||||||
tag::TagInfo,
|
tag::TagInfo,
|
||||||
AnyMessageEventContent, AnyStateEventContent,
|
AnyMessageEventContent, AnyStateEventContent, EventContent,
|
||||||
},
|
},
|
||||||
receipt::ReceiptType,
|
receipt::ReceiptType,
|
||||||
serde::Raw,
|
serde::Raw,
|
||||||
|
@ -337,13 +337,25 @@ impl Joined {
|
||||||
/// If the encryption feature is enabled this method will transparently
|
/// If the encryption feature is enabled this method will transparently
|
||||||
/// encrypt the room message if this room is encrypted.
|
/// encrypt the room message if this room is encrypted.
|
||||||
///
|
///
|
||||||
|
/// **Note**: This method does not support sending custom events, the
|
||||||
|
/// [`send_raw()`] method can be used for that instead.
|
||||||
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `content` - The content of the message event.
|
/// * `content` - The content of the message event.
|
||||||
///
|
///
|
||||||
/// * `txn_id` - A unique `Uuid` that can be attached to a `MessageEvent`
|
/// * `txn_id` - A locally-unique ID describing a message transaction with
|
||||||
/// held in its unsigned field as `transaction_id`. If not given one is
|
/// the homeserver. Unless you're doing something special, you can pass in
|
||||||
/// created for the message.
|
/// `None` which will create a suitable one for you automatically.
|
||||||
|
/// * On the sending side, this field is used for re-trying earlier
|
||||||
|
/// failed transactions. Subsequent messages *must never* re-use an
|
||||||
|
/// earlier transaction ID.
|
||||||
|
/// * On the receiving side, the field is used for recognizing our own
|
||||||
|
/// messages when they arrive down the sync: the server includes the
|
||||||
|
/// ID in the [`Unsigned`] field [`transaction_id`] of the
|
||||||
|
/// corresponding [`SyncMessageEvent`], but only for the *sending*
|
||||||
|
/// device. Other devices will not see it. This is then used to ignore
|
||||||
|
/// events sent by our own device and/or to implement local echo.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
|
@ -376,31 +388,117 @@ impl Joined {
|
||||||
/// }
|
/// }
|
||||||
/// # matrix_sdk::Result::Ok(()) });
|
/// # matrix_sdk::Result::Ok(()) });
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`send_raw()`]: #method.send_raw
|
||||||
|
/// [`SyncMessageEvent`]: ruma::events::SyncMessageEvent
|
||||||
|
/// [`Unsigned`]: ruma::events::Unsigned
|
||||||
|
/// [`transaction_id`]: ruma::events::Unsigned#structfield.transaction_id
|
||||||
pub async fn send(
|
pub async fn send(
|
||||||
&self,
|
&self,
|
||||||
content: impl Into<AnyMessageEventContent>,
|
content: impl Into<AnyMessageEventContent>,
|
||||||
txn_id: Option<Uuid>,
|
txn_id: Option<Uuid>,
|
||||||
) -> Result<send_message_event::Response> {
|
) -> Result<send_message_event::Response> {
|
||||||
|
let content = content.into();
|
||||||
|
let event_type = content.event_type().to_owned();
|
||||||
|
let content = serde_json::to_value(content)?;
|
||||||
|
|
||||||
|
self.send_raw(content, &event_type, txn_id).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Send a room message to this room from a json `Value`.
|
||||||
|
///
|
||||||
|
/// Returns the parsed response from the server.
|
||||||
|
///
|
||||||
|
/// If the encryption feature is enabled this method will transparently
|
||||||
|
/// encrypt the room message if this room is encrypted.
|
||||||
|
///
|
||||||
|
/// This method is equivalent to the [`send()`] method but allows sending
|
||||||
|
/// custom events.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `content` - The content of the event as a json `Value`.
|
||||||
|
///
|
||||||
|
/// * `event_type` - The type of the event.
|
||||||
|
///
|
||||||
|
/// * `txn_id` - A locally-unique ID describing a message transaction with
|
||||||
|
/// the homeserver. Unless you're doing something special, you can pass in
|
||||||
|
/// `None` which will create a suitable one for you automatically.
|
||||||
|
/// * On the sending side, this field is used for re-trying earlier
|
||||||
|
/// failed transactions. Subsequent messages *must never* re-use an
|
||||||
|
/// earlier transaction ID.
|
||||||
|
/// * On the receiving side, the field is used for recognizing our own
|
||||||
|
/// messages when they arrive down the sync: the server includes the
|
||||||
|
/// ID in the [`Unsigned`] field [`transaction_id`] of the
|
||||||
|
/// corresponding [`SyncMessageEvent`], but only for the *sending*
|
||||||
|
/// device. Other devices will not see it. This is then used to ignore
|
||||||
|
/// events sent by our own device and/or to implement local echo.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```no_run
|
||||||
|
/// # use std::sync::{Arc, RwLock};
|
||||||
|
/// # use matrix_sdk::{Client, config::SyncSettings};
|
||||||
|
/// # use url::Url;
|
||||||
|
/// # use futures::executor::block_on;
|
||||||
|
/// # use matrix_sdk::ruma::room_id;
|
||||||
|
/// # use std::convert::TryFrom;
|
||||||
|
/// # block_on(async {
|
||||||
|
/// # let homeserver = Url::parse("http://localhost:8080")?;
|
||||||
|
/// # let mut client = Client::new(homeserver)?;
|
||||||
|
/// # let room_id = room_id!("!test:localhost");
|
||||||
|
/// use serde_json::json;
|
||||||
|
///
|
||||||
|
/// let content = json!({
|
||||||
|
/// "body": "Hello world",
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// if let Some(room) = client.get_joined_room(&room_id) {
|
||||||
|
/// room.send_raw(content, "m.room.message", None).await?;
|
||||||
|
/// }
|
||||||
|
/// # matrix_sdk::Result::Ok(()) });
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`send()`]: #method.send
|
||||||
|
/// [`SyncMessageEvent`]: ruma::events::SyncMessageEvent
|
||||||
|
/// [`Unsigned`]: ruma::events::Unsigned
|
||||||
|
/// [`transaction_id`]: ruma::events::Unsigned#structfield.transaction_id
|
||||||
|
pub async fn send_raw(
|
||||||
|
&self,
|
||||||
|
content: Value,
|
||||||
|
event_type: &str,
|
||||||
|
txn_id: Option<Uuid>,
|
||||||
|
) -> Result<send_message_event::Response> {
|
||||||
|
let txn_id = txn_id.unwrap_or_else(Uuid::new_v4).to_string();
|
||||||
|
|
||||||
#[cfg(not(feature = "encryption"))]
|
#[cfg(not(feature = "encryption"))]
|
||||||
let content: AnyMessageEventContent = content.into();
|
let content = Raw::from_json(serde_json::value::to_raw_value(&content)?);
|
||||||
|
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
let content = if self.is_encrypted() {
|
let (content, event_type) = if self.is_encrypted() {
|
||||||
if !self.are_members_synced() {
|
if !self.are_members_synced() {
|
||||||
self.request_members().await?;
|
self.request_members().await?;
|
||||||
// TODO query keys here?
|
// TODO query keys here?
|
||||||
}
|
}
|
||||||
|
|
||||||
self.preshare_group_session().await?;
|
self.preshare_group_session().await?;
|
||||||
AnyMessageEventContent::RoomEncrypted(
|
|
||||||
self.client.base_client.encrypt(self.inner.room_id(), content).await?,
|
let olm =
|
||||||
)
|
self.client.base_client.olm_machine().await.expect("Olm machine wasn't started");
|
||||||
|
|
||||||
|
let encrypted_content =
|
||||||
|
olm.encrypt_raw(self.inner.room_id(), content, event_type).await?;
|
||||||
|
|
||||||
|
(AnyMessageEventContent::RoomEncrypted(encrypted_content).into(), "m.room.encrypted")
|
||||||
} else {
|
} else {
|
||||||
content.into()
|
(Raw::from_json(serde_json::value::to_raw_value(&content)?), event_type)
|
||||||
};
|
};
|
||||||
|
|
||||||
let txn_id = txn_id.unwrap_or_else(Uuid::new_v4).to_string();
|
let request = send_message_event::Request::new_raw(
|
||||||
let request = send_message_event::Request::new(self.inner.room_id(), &txn_id, &content);
|
self.inner.room_id(),
|
||||||
|
&txn_id,
|
||||||
|
event_type,
|
||||||
|
content,
|
||||||
|
);
|
||||||
|
|
||||||
let response = self.client.send(request, None).await?;
|
let response = self.client.send(request, None).await?;
|
||||||
Ok(response)
|
Ok(response)
|
||||||
|
|
Loading…
Reference in a new issue