Update existing events for spec r0.5.0 and add m.fully_read and
m.room.message.feedback.
This commit is contained in:
parent
9f43f37f41
commit
ba2538dda9
@ -15,4 +15,29 @@ pub struct HangupEventContent {
|
||||
pub call_id: String,
|
||||
/// The version of the VoIP specification this messages adheres to.
|
||||
pub version: u64,
|
||||
/// Optional error reason for the hangup.
|
||||
pub reason: Option<Reason>,
|
||||
}
|
||||
|
||||
/// A reason for a hangup.
|
||||
///
|
||||
/// This should not be provided when the user naturally ends or rejects the call. When there was an
|
||||
/// error in the call negotiation, this should be `ice_failed` for when ICE negotiation fails or
|
||||
/// `invite_timeout` for when the other party did not answer in time.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub enum Reason {
|
||||
/// ICE negotiation failure.
|
||||
#[serde(rename = "ice_failed")]
|
||||
IceFailed,
|
||||
|
||||
/// Party did not answer in time.
|
||||
#[serde(rename = "invite_timeout")]
|
||||
InviteTimeout,
|
||||
}
|
||||
|
||||
impl_enum! {
|
||||
Reason {
|
||||
IceFailed => "ice_failed",
|
||||
InviteTimeout => "invite_timeout",
|
||||
}
|
||||
}
|
||||
|
@ -6,15 +6,25 @@ use crate::{
|
||||
answer::AnswerEvent, candidates::CandidatesEvent, hangup::HangupEvent, invite::InviteEvent,
|
||||
},
|
||||
direct::DirectEvent,
|
||||
fully_read::FullyReadEvent,
|
||||
presence::PresenceEvent,
|
||||
receipt::ReceiptEvent,
|
||||
room::{
|
||||
aliases::AliasesEvent, avatar::AvatarEvent, canonical_alias::CanonicalAliasEvent,
|
||||
create::CreateEvent, guest_access::GuestAccessEvent,
|
||||
history_visibility::HistoryVisibilityEvent, join_rules::JoinRulesEvent,
|
||||
member::MemberEvent, message::MessageEvent, name::NameEvent,
|
||||
pinned_events::PinnedEventsEvent, power_levels::PowerLevelsEvent,
|
||||
redaction::RedactionEvent, third_party_invite::ThirdPartyInviteEvent, topic::TopicEvent,
|
||||
aliases::AliasesEvent,
|
||||
avatar::AvatarEvent,
|
||||
canonical_alias::CanonicalAliasEvent,
|
||||
create::CreateEvent,
|
||||
guest_access::GuestAccessEvent,
|
||||
history_visibility::HistoryVisibilityEvent,
|
||||
join_rules::JoinRulesEvent,
|
||||
member::MemberEvent,
|
||||
message::{feedback::FeedbackEvent, MessageEvent},
|
||||
name::NameEvent,
|
||||
pinned_events::PinnedEventsEvent,
|
||||
power_levels::PowerLevelsEvent,
|
||||
redaction::RedactionEvent,
|
||||
third_party_invite::ThirdPartyInviteEvent,
|
||||
topic::TopicEvent,
|
||||
},
|
||||
tag::TagEvent,
|
||||
typing::TypingEvent,
|
||||
@ -38,6 +48,8 @@ pub enum Event {
|
||||
CallInvite(InviteEvent),
|
||||
/// m.direct
|
||||
Direct(DirectEvent),
|
||||
/// m.fully_read
|
||||
FullyRead(FullyReadEvent),
|
||||
/// m.presence
|
||||
Presence(PresenceEvent),
|
||||
/// m.receipt
|
||||
@ -60,6 +72,8 @@ pub enum Event {
|
||||
RoomMember(MemberEvent),
|
||||
/// m.room.message
|
||||
RoomMessage(MessageEvent),
|
||||
/// m.room.message.feedback
|
||||
RoomMessageFeedback(FeedbackEvent),
|
||||
/// m.room.name
|
||||
RoomName(NameEvent),
|
||||
/// m.room.pinned_events
|
||||
@ -114,6 +128,8 @@ pub enum RoomEvent {
|
||||
RoomMember(MemberEvent),
|
||||
/// m.room.message
|
||||
RoomMessage(MessageEvent),
|
||||
/// m.room.message.feedback
|
||||
RoomMessageFeedback(FeedbackEvent),
|
||||
/// m.room.name
|
||||
RoomName(NameEvent),
|
||||
/// m.room.pinned_events
|
||||
@ -177,6 +193,7 @@ impl Serialize for Event {
|
||||
Event::CallHangup(ref event) => event.serialize(serializer),
|
||||
Event::CallInvite(ref event) => event.serialize(serializer),
|
||||
Event::Direct(ref event) => event.serialize(serializer),
|
||||
Event::FullyRead(ref event) => event.serialize(serializer),
|
||||
Event::Presence(ref event) => event.serialize(serializer),
|
||||
Event::Receipt(ref event) => event.serialize(serializer),
|
||||
Event::RoomAliases(ref event) => event.serialize(serializer),
|
||||
@ -188,6 +205,7 @@ impl Serialize for Event {
|
||||
Event::RoomJoinRules(ref event) => event.serialize(serializer),
|
||||
Event::RoomMember(ref event) => event.serialize(serializer),
|
||||
Event::RoomMessage(ref event) => event.serialize(serializer),
|
||||
Event::RoomMessageFeedback(ref event) => event.serialize(serializer),
|
||||
Event::RoomName(ref event) => event.serialize(serializer),
|
||||
Event::RoomPinnedEvents(ref event) => event.serialize(serializer),
|
||||
Event::RoomPowerLevels(ref event) => event.serialize(serializer),
|
||||
@ -261,6 +279,14 @@ impl<'de> Deserialize<'de> for Event {
|
||||
|
||||
Ok(Event::Direct(event))
|
||||
}
|
||||
EventType::FullyRead => {
|
||||
let event = match from_value::<FullyReadEvent>(value) {
|
||||
Ok(event) => event,
|
||||
Err(error) => return Err(D::Error::custom(error.to_string())),
|
||||
};
|
||||
|
||||
Ok(Event::FullyRead(event))
|
||||
}
|
||||
EventType::Presence => {
|
||||
let event = match from_value::<PresenceEvent>(value) {
|
||||
Ok(event) => event,
|
||||
@ -349,6 +375,14 @@ impl<'de> Deserialize<'de> for Event {
|
||||
|
||||
Ok(Event::RoomMessage(event))
|
||||
}
|
||||
EventType::RoomMessageFeedback => {
|
||||
let event = match from_value::<FeedbackEvent>(value) {
|
||||
Ok(event) => event,
|
||||
Err(error) => return Err(D::Error::custom(error.to_string())),
|
||||
};
|
||||
|
||||
Ok(Event::RoomMessageFeedback(event))
|
||||
}
|
||||
EventType::RoomName => {
|
||||
let event = match from_value::<NameEvent>(value) {
|
||||
Ok(event) => event,
|
||||
@ -463,6 +497,7 @@ impl Serialize for RoomEvent {
|
||||
RoomEvent::RoomJoinRules(ref event) => event.serialize(serializer),
|
||||
RoomEvent::RoomMember(ref event) => event.serialize(serializer),
|
||||
RoomEvent::RoomMessage(ref event) => event.serialize(serializer),
|
||||
RoomEvent::RoomMessageFeedback(ref event) => event.serialize(serializer),
|
||||
RoomEvent::RoomName(ref event) => event.serialize(serializer),
|
||||
RoomEvent::RoomPinnedEvents(ref event) => event.serialize(serializer),
|
||||
RoomEvent::RoomPowerLevels(ref event) => event.serialize(serializer),
|
||||
@ -597,6 +632,14 @@ impl<'de> Deserialize<'de> for RoomEvent {
|
||||
|
||||
Ok(RoomEvent::RoomMessage(event))
|
||||
}
|
||||
EventType::RoomMessageFeedback => {
|
||||
let event = match from_value::<FeedbackEvent>(value) {
|
||||
Ok(event) => event,
|
||||
Err(error) => return Err(D::Error::custom(error.to_string())),
|
||||
};
|
||||
|
||||
Ok(RoomEvent::RoomMessageFeedback(event))
|
||||
}
|
||||
EventType::RoomName => {
|
||||
let event = match from_value::<NameEvent>(value) {
|
||||
Ok(event) => event,
|
||||
@ -663,6 +706,7 @@ impl<'de> Deserialize<'de> for RoomEvent {
|
||||
}
|
||||
}
|
||||
EventType::Direct
|
||||
| EventType::FullyRead
|
||||
| EventType::Presence
|
||||
| EventType::Receipt
|
||||
| EventType::Tag
|
||||
@ -830,9 +874,11 @@ impl<'de> Deserialize<'de> for StateEvent {
|
||||
| EventType::CallHangup
|
||||
| EventType::CallInvite
|
||||
| EventType::Direct
|
||||
| EventType::FullyRead
|
||||
| EventType::Presence
|
||||
| EventType::Receipt
|
||||
| EventType::RoomMessage
|
||||
| EventType::RoomMessageFeedback
|
||||
| EventType::RoomRedaction
|
||||
| EventType::Tag
|
||||
| EventType::Typing => Err(D::Error::custom("not a state event".to_string())),
|
||||
@ -855,6 +901,7 @@ impl_from_t_for_event!(CandidatesEvent, CallCandidates);
|
||||
impl_from_t_for_event!(HangupEvent, CallHangup);
|
||||
impl_from_t_for_event!(InviteEvent, CallInvite);
|
||||
impl_from_t_for_event!(DirectEvent, Direct);
|
||||
impl_from_t_for_event!(FullyReadEvent, FullyRead);
|
||||
impl_from_t_for_event!(PresenceEvent, Presence);
|
||||
impl_from_t_for_event!(ReceiptEvent, Receipt);
|
||||
impl_from_t_for_event!(AliasesEvent, RoomAliases);
|
||||
@ -866,6 +913,7 @@ impl_from_t_for_event!(HistoryVisibilityEvent, RoomHistoryVisibility);
|
||||
impl_from_t_for_event!(JoinRulesEvent, RoomJoinRules);
|
||||
impl_from_t_for_event!(MemberEvent, RoomMember);
|
||||
impl_from_t_for_event!(MessageEvent, RoomMessage);
|
||||
impl_from_t_for_event!(FeedbackEvent, RoomMessageFeedback);
|
||||
impl_from_t_for_event!(NameEvent, RoomName);
|
||||
impl_from_t_for_event!(PinnedEventsEvent, RoomPinnedEvents);
|
||||
impl_from_t_for_event!(PowerLevelsEvent, RoomPowerLevels);
|
||||
@ -901,6 +949,7 @@ impl_from_t_for_room_event!(HistoryVisibilityEvent, RoomHistoryVisibility);
|
||||
impl_from_t_for_room_event!(JoinRulesEvent, RoomJoinRules);
|
||||
impl_from_t_for_room_event!(MemberEvent, RoomMember);
|
||||
impl_from_t_for_room_event!(MessageEvent, RoomMessage);
|
||||
impl_from_t_for_room_event!(FeedbackEvent, RoomMessageFeedback);
|
||||
impl_from_t_for_room_event!(NameEvent, RoomName);
|
||||
impl_from_t_for_room_event!(PinnedEventsEvent, RoomPinnedEvents);
|
||||
impl_from_t_for_room_event!(PowerLevelsEvent, RoomPowerLevels);
|
||||
|
@ -10,9 +10,13 @@ use crate::{
|
||||
answer::AnswerEvent, candidates::CandidatesEvent, hangup::HangupEvent, invite::InviteEvent,
|
||||
},
|
||||
direct::DirectEvent,
|
||||
fully_read::FullyReadEvent,
|
||||
presence::PresenceEvent,
|
||||
receipt::ReceiptEvent,
|
||||
room::{message::MessageEvent, redaction::RedactionEvent},
|
||||
room::{
|
||||
message::{feedback::FeedbackEvent, MessageEvent},
|
||||
redaction::RedactionEvent,
|
||||
},
|
||||
tag::TagEvent,
|
||||
typing::TypingEvent,
|
||||
CustomEvent, CustomRoomEvent, EventType,
|
||||
@ -23,6 +27,8 @@ use crate::{
|
||||
pub enum Event {
|
||||
/// m.direct
|
||||
Direct(DirectEvent),
|
||||
/// m.fully_read
|
||||
FullyRead(FullyReadEvent),
|
||||
/// m.presence
|
||||
Presence(PresenceEvent),
|
||||
/// m.receipt
|
||||
@ -49,6 +55,8 @@ pub enum RoomEvent {
|
||||
CallInvite(InviteEvent),
|
||||
/// m.room.message
|
||||
RoomMessage(MessageEvent),
|
||||
/// m.room.message.feedback
|
||||
RoomMessageFeedback(FeedbackEvent),
|
||||
/// m.room.redaction
|
||||
RoomRedaction(RedactionEvent),
|
||||
/// Any room event that is not part of the specification.
|
||||
@ -62,6 +70,7 @@ impl Serialize for Event {
|
||||
{
|
||||
match *self {
|
||||
Event::Direct(ref event) => event.serialize(serializer),
|
||||
Event::FullyRead(ref event) => event.serialize(serializer),
|
||||
Event::Presence(ref event) => event.serialize(serializer),
|
||||
Event::Receipt(ref event) => event.serialize(serializer),
|
||||
Event::Tag(ref event) => event.serialize(serializer),
|
||||
@ -97,6 +106,14 @@ impl<'de> Deserialize<'de> for Event {
|
||||
|
||||
Ok(Event::Direct(event))
|
||||
}
|
||||
EventType::FullyRead => {
|
||||
let event = match from_value::<FullyReadEvent>(value) {
|
||||
Ok(event) => event,
|
||||
Err(error) => return Err(D::Error::custom(error.to_string())),
|
||||
};
|
||||
|
||||
Ok(Event::FullyRead(event))
|
||||
}
|
||||
EventType::Presence => {
|
||||
let event = match from_value::<PresenceEvent>(value) {
|
||||
Ok(event) => event,
|
||||
@ -150,6 +167,7 @@ impl<'de> Deserialize<'de> for Event {
|
||||
| EventType::RoomJoinRules
|
||||
| EventType::RoomMember
|
||||
| EventType::RoomMessage
|
||||
| EventType::RoomMessageFeedback
|
||||
| EventType::RoomName
|
||||
| EventType::RoomPinnedEvents
|
||||
| EventType::RoomPowerLevels
|
||||
@ -173,6 +191,7 @@ impl Serialize for RoomEvent {
|
||||
RoomEvent::CallHangup(ref event) => event.serialize(serializer),
|
||||
RoomEvent::CallInvite(ref event) => event.serialize(serializer),
|
||||
RoomEvent::RoomMessage(ref event) => event.serialize(serializer),
|
||||
RoomEvent::RoomMessageFeedback(ref event) => event.serialize(serializer),
|
||||
RoomEvent::RoomRedaction(ref event) => event.serialize(serializer),
|
||||
RoomEvent::CustomRoom(ref event) => event.serialize(serializer),
|
||||
}
|
||||
@ -237,6 +256,14 @@ impl<'de> Deserialize<'de> for RoomEvent {
|
||||
|
||||
Ok(RoomEvent::RoomMessage(event))
|
||||
}
|
||||
EventType::RoomMessageFeedback => {
|
||||
let event = match from_value::<FeedbackEvent>(value) {
|
||||
Ok(event) => event,
|
||||
Err(error) => return Err(D::Error::custom(error.to_string())),
|
||||
};
|
||||
|
||||
Ok(RoomEvent::RoomMessageFeedback(event))
|
||||
}
|
||||
EventType::RoomRedaction => {
|
||||
let event = match from_value::<RedactionEvent>(value) {
|
||||
Ok(event) => event,
|
||||
@ -254,6 +281,7 @@ impl<'de> Deserialize<'de> for RoomEvent {
|
||||
Ok(RoomEvent::CustomRoom(event))
|
||||
}
|
||||
EventType::Direct
|
||||
| EventType::FullyRead
|
||||
| EventType::Presence
|
||||
| EventType::Receipt
|
||||
| EventType::RoomAliases
|
||||
@ -287,6 +315,7 @@ macro_rules! impl_from_t_for_event {
|
||||
}
|
||||
|
||||
impl_from_t_for_event!(DirectEvent, Direct);
|
||||
impl_from_t_for_event!(FullyReadEvent, FullyRead);
|
||||
impl_from_t_for_event!(PresenceEvent, Presence);
|
||||
impl_from_t_for_event!(ReceiptEvent, Receipt);
|
||||
impl_from_t_for_event!(TagEvent, Tag);
|
||||
@ -308,5 +337,6 @@ impl_from_t_for_room_event!(CandidatesEvent, CallCandidates);
|
||||
impl_from_t_for_room_event!(HangupEvent, CallHangup);
|
||||
impl_from_t_for_room_event!(InviteEvent, CallInvite);
|
||||
impl_from_t_for_room_event!(MessageEvent, RoomMessage);
|
||||
impl_from_t_for_room_event!(FeedbackEvent, RoomMessageFeedback);
|
||||
impl_from_t_for_room_event!(RedactionEvent, RoomRedaction);
|
||||
impl_from_t_for_room_event!(CustomRoomEvent, CustomRoom);
|
||||
|
@ -85,7 +85,7 @@ mod tests {
|
||||
assert!(direct_rooms.contains(&rooms[0]));
|
||||
assert!(direct_rooms.contains(&rooms[1]));
|
||||
}
|
||||
_ => assert!(false),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
match from_str::<collections::only::Event>(&json_data).unwrap() {
|
||||
@ -96,7 +96,7 @@ mod tests {
|
||||
assert!(direct_rooms.contains(&rooms[0]));
|
||||
assert!(direct_rooms.contains(&rooms[1]));
|
||||
}
|
||||
_ => assert!(false),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
22
src/fully_read.rs
Normal file
22
src/fully_read.rs
Normal file
@ -0,0 +1,22 @@
|
||||
//! Types for the *m.fully_read* event.
|
||||
|
||||
use ruma_identifiers::{EventId, RoomId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
event! {
|
||||
/// The current location of the user's read marker in a room.
|
||||
///
|
||||
/// This event appears in the user's room account data for the room the marker is applicable
|
||||
/// for.
|
||||
pub struct FullyReadEvent(FullyReadEventContent) {
|
||||
/// The unique identifier for the room associated with this event.
|
||||
pub room_id: RoomId
|
||||
}
|
||||
}
|
||||
|
||||
/// The payload of a `FullyReadEvent`.
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct FullyReadEventContent {
|
||||
/// The event the user's read marker is located at in the room.
|
||||
pub event_id: EventId,
|
||||
}
|
@ -118,6 +118,7 @@ pub mod collections {
|
||||
pub mod only;
|
||||
}
|
||||
pub mod direct;
|
||||
pub mod fully_read;
|
||||
pub mod presence;
|
||||
pub mod receipt;
|
||||
pub mod room;
|
||||
@ -142,6 +143,8 @@ pub enum EventType {
|
||||
CallInvite,
|
||||
/// m.direct
|
||||
Direct,
|
||||
/// m.fully_read
|
||||
FullyRead,
|
||||
/// m.presence
|
||||
Presence,
|
||||
/// m.receipt
|
||||
@ -164,6 +167,8 @@ pub enum EventType {
|
||||
RoomMember,
|
||||
/// m.room.message
|
||||
RoomMessage,
|
||||
/// m.room.message.feedback
|
||||
RoomMessageFeedback,
|
||||
/// m.room.name
|
||||
RoomName,
|
||||
/// m.room.pinned_events
|
||||
@ -253,6 +258,7 @@ impl Display for EventType {
|
||||
EventType::CallHangup => "m.call.hangup",
|
||||
EventType::CallInvite => "m.call.invite",
|
||||
EventType::Direct => "m.direct",
|
||||
EventType::FullyRead => "m.fully_read",
|
||||
EventType::Presence => "m.presence",
|
||||
EventType::Receipt => "m.receipt",
|
||||
EventType::RoomAliases => "m.room.aliases",
|
||||
@ -264,6 +270,7 @@ impl Display for EventType {
|
||||
EventType::RoomJoinRules => "m.room.join_rules",
|
||||
EventType::RoomMember => "m.room.member",
|
||||
EventType::RoomMessage => "m.room.message",
|
||||
EventType::RoomMessageFeedback => "m.room.message.feedback",
|
||||
EventType::RoomName => "m.room.name",
|
||||
EventType::RoomPinnedEvents => "m.room.pinned_events",
|
||||
EventType::RoomPowerLevels => "m.room.power_levels",
|
||||
@ -287,6 +294,7 @@ impl<'a> From<&'a str> for EventType {
|
||||
"m.call.hangup" => EventType::CallHangup,
|
||||
"m.call.invite" => EventType::CallInvite,
|
||||
"m.direct" => EventType::Direct,
|
||||
"m.fully_read" => EventType::FullyRead,
|
||||
"m.presence" => EventType::Presence,
|
||||
"m.receipt" => EventType::Receipt,
|
||||
"m.room.aliases" => EventType::RoomAliases,
|
||||
@ -298,6 +306,7 @@ impl<'a> From<&'a str> for EventType {
|
||||
"m.room.join_rules" => EventType::RoomJoinRules,
|
||||
"m.room.member" => EventType::RoomMember,
|
||||
"m.room.message" => EventType::RoomMessage,
|
||||
"m.room.message.feedback" => EventType::RoomMessageFeedback,
|
||||
"m.room.name" => EventType::RoomName,
|
||||
"m.room.pinned_events" => EventType::RoomPinnedEvents,
|
||||
"m.room.power_levels" => EventType::RoomPowerLevels,
|
||||
|
@ -78,7 +78,7 @@ mod tests {
|
||||
avatar_url: Some("mxc://localhost:wefuiwegh8742w".to_string()),
|
||||
currently_active: Some(false),
|
||||
displayname: None,
|
||||
last_active_ago: Some(2478593),
|
||||
last_active_ago: Some(2_478_593),
|
||||
presence: PresenceState::Online,
|
||||
},
|
||||
event_type: EventType::Presence,
|
||||
|
@ -9,11 +9,7 @@ event! {
|
||||
/// Informs the client of new receipts.
|
||||
pub struct ReceiptEvent(ReceiptEventContent) {
|
||||
/// The unique identifier for the room associated with this event.
|
||||
///
|
||||
/// This can be `None` if the event came from a context where there is
|
||||
/// no ambiguity which room it belongs to, like a `/sync` response for example.
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
pub room_id: Option<RoomId>
|
||||
pub room_id: RoomId
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,7 +25,7 @@ pub struct Receipts {
|
||||
/// A collection of users who have sent *m.read* receipts for this event.
|
||||
#[serde(rename = "m.read")]
|
||||
#[serde(default)]
|
||||
pub m_read: UserReceipts,
|
||||
pub read: UserReceipts,
|
||||
}
|
||||
|
||||
/// A mapping of user ID to receipt.
|
||||
@ -40,6 +36,6 @@ pub type UserReceipts = HashMap<UserId, Receipt>;
|
||||
/// An acknowledgement of an event.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Receipt {
|
||||
/// The timestamp the receipt was sent at.
|
||||
/// The timestamp (milliseconds since the Unix epoch) when the receipt was sent.
|
||||
pub ts: u64,
|
||||
}
|
||||
|
@ -18,11 +18,6 @@ pub struct AvatarEventContent {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub info: Option<ImageInfo>,
|
||||
/// Information about the avatar thumbnail image.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_info: Option<ImageInfo>,
|
||||
/// URL of the avatar thumbnail image.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_url: Option<String>,
|
||||
/// URL of the avatar image.
|
||||
pub url: String,
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! Types for the *m.room.create* event.
|
||||
|
||||
use ruma_identifiers::UserId;
|
||||
use ruma_identifiers::{EventId, RoomId, RoomVersionId, UserId};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
state_event! {
|
||||
@ -10,11 +10,24 @@ state_event! {
|
||||
}
|
||||
|
||||
/// The payload of a `CreateEvent`.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct CreateEventContent {
|
||||
/// The `user_id` of the room creator. This is set by the homeserver.
|
||||
pub creator: UserId,
|
||||
/// Whether or not this room's data should be transferred to other homeservers.
|
||||
#[serde(rename = "m.federate")]
|
||||
pub federate: Option<bool>,
|
||||
/// The version of the room. Defaults to "1" if the key does not exist.
|
||||
pub room_version: RoomVersionId,
|
||||
/// A reference to the room this room replaces, if the previous room was upgraded.
|
||||
pub predecessor: PreviousRoom,
|
||||
}
|
||||
|
||||
/// A reference to an old room replaced during a room version upgrade.
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct PreviousRoom {
|
||||
/// The ID of the old room.
|
||||
pub room_id: RoomId,
|
||||
/// The event ID of the last known event in the old room.
|
||||
pub event_id: EventId,
|
||||
}
|
||||
|
@ -4,38 +4,40 @@ use ruma_identifiers::UserId;
|
||||
use ruma_signatures::Signatures;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::stripped::StrippedState;
|
||||
|
||||
state_event! {
|
||||
/// The current membership state of a user in the room.
|
||||
///
|
||||
/// Adjusts the membership state for a user in a room. It is preferable to use the membership
|
||||
/// APIs (``/rooms/<room id>/invite`` etc) when performing membership actions rather than
|
||||
/// APIs (`/rooms/<room id>/invite` etc) when performing membership actions rather than
|
||||
/// adjusting the state directly as there are a restricted set of valid transformations. For
|
||||
/// example, user A cannot force user B to join a room, and trying to force this state change
|
||||
/// directly will fail.
|
||||
///
|
||||
/// The *third_party_invite* property will be set if this invite is an *invite* event and is the
|
||||
/// The `third_party_invite` property will be set if this invite is an *invite* event and is the
|
||||
/// successor of an *m.room.third_party_invite* event, and absent otherwise.
|
||||
///
|
||||
/// This event may also include an *invite_room_state* key outside the *content* key. If
|
||||
/// This event may also include an `invite_room_state` key inside the event's unsigned data. If
|
||||
/// present, this contains an array of `StrippedState` events. These events provide information
|
||||
/// on a few select state events such as the room name.
|
||||
pub struct MemberEvent(MemberEventContent) {
|
||||
/// A subset of the state of the room at the time of the invite.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub invite_room_state: Option<Vec<StrippedState>>
|
||||
}
|
||||
/// on a subset of state events such as the room name.
|
||||
///
|
||||
/// The user for which a membership applies is represented by the `state_key`. Under some
|
||||
/// conditions, the `sender` and `state_key` may not match - this may be interpreted as the
|
||||
/// `sender` affecting the membership state of the `state_key` user.
|
||||
///
|
||||
/// The membership for a given user can change over time. Previous membership can be retrieved
|
||||
/// from the `prev_content` object on an event. If not present, the user's previous membership
|
||||
/// must be assumed as leave.
|
||||
pub struct MemberEvent(MemberEventContent) {}
|
||||
}
|
||||
|
||||
/// The payload of a `MemberEvent`.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct MemberEventContent {
|
||||
/// The avatar URL for this user.
|
||||
/// The avatar URL for this user, if any. This is added by the homeserver.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub avatar_url: Option<String>,
|
||||
|
||||
/// The display name for this user.
|
||||
/// The display name for this user, if any. This is added by the homeserver.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub displayname: Option<String>,
|
||||
|
||||
|
@ -1,9 +1,12 @@
|
||||
//! Types for the *m.room.message* event.
|
||||
|
||||
use ruma_identifiers::EventId;
|
||||
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};
|
||||
use serde_json::{from_value, Value};
|
||||
|
||||
use super::{ImageInfo, ThumbnailInfo};
|
||||
use super::{EncryptedFile, ImageInfo, ThumbnailInfo};
|
||||
|
||||
pub mod feedback;
|
||||
|
||||
room_event! {
|
||||
/// A message sent to a room.
|
||||
@ -47,6 +50,7 @@ pub enum MessageType {
|
||||
}
|
||||
|
||||
/// The payload of a message event.
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum MessageEventContent {
|
||||
/// An audio message.
|
||||
@ -84,8 +88,13 @@ pub struct AudioMessageEventContent {
|
||||
pub info: Option<AudioInfo>,
|
||||
/// The message type. Always *m.audio*.
|
||||
pub msgtype: MessageType,
|
||||
/// The URL to the audio clip.
|
||||
pub url: String,
|
||||
/// The URL to the audio clip. Required if the file is unencrypted. The URL (typically
|
||||
/// [MXC URI](https://matrix.org/docs/spec/client_server/r0.5.0#mxc-uri)) to the audio clip.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub url: Option<String>,
|
||||
/// Required if the audio clip is encrypted. Information on the encrypted audio clip.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub file: Option<EncryptedFile>,
|
||||
}
|
||||
|
||||
/// Metadata about an audio clip.
|
||||
@ -109,6 +118,13 @@ pub struct EmoteMessageEventContent {
|
||||
pub body: String,
|
||||
/// The message type. Always *m.emote*.
|
||||
pub msgtype: MessageType,
|
||||
/// The format used in the `formatted_body`. Currently only `org.matrix.custom.html` is
|
||||
/// supported.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub format: Option<String>,
|
||||
/// The formatted version of the `body`. This is required if `format` is specified.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub formatted_body: Option<String>,
|
||||
}
|
||||
|
||||
/// The payload of a file message.
|
||||
@ -118,14 +134,20 @@ pub struct FileMessageEventContent {
|
||||
/// original upload.
|
||||
pub body: String,
|
||||
/// The original filename of the uploaded file.
|
||||
pub filename: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub filename: Option<String>,
|
||||
/// Metadata about the file referred to in `url`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub info: Option<FileInfo>,
|
||||
/// The message type. Always *m.file*.
|
||||
pub msgtype: MessageType,
|
||||
/// The URL to the file.
|
||||
pub url: String,
|
||||
/// The URL to the file. Required if the file is unencrypted. The URL (typically
|
||||
/// [MXC URI](https://matrix.org/docs/spec/client_server/r0.5.0#mxc-uri)) to the file.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub url: Option<String>,
|
||||
/// Required if file is encrypted. Information on the encrypted file.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub file: Option<EncryptedFile>,
|
||||
}
|
||||
|
||||
/// Metadata about a file.
|
||||
@ -138,9 +160,12 @@ pub struct FileInfo {
|
||||
/// Metadata about the image referred to in `thumbnail_url`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_info: Option<ThumbnailInfo>,
|
||||
/// The URL to the thumbnail of the file.
|
||||
/// The URL to the thumbnail of the file. Only present if the thumbnail is unencrypted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_url: Option<String>,
|
||||
/// Information on the encrypted thumbnail file. Only present if the thumbnail is encrypted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_file: Option<EncryptedFile>,
|
||||
}
|
||||
|
||||
/// The payload of an image message.
|
||||
@ -154,8 +179,12 @@ pub struct ImageMessageEventContent {
|
||||
pub info: Option<ImageInfo>,
|
||||
/// The message type. Always *m.image*.
|
||||
pub msgtype: MessageType,
|
||||
/// The URL to the image.
|
||||
pub url: String,
|
||||
/// The URL to the image. Required if the file is unencrypted. The URL (typically
|
||||
/// [MXC URI](https://matrix.org/docs/spec/client_server/r0.5.0#mxc-uri)) to the image.
|
||||
pub url: Option<String>,
|
||||
/// Required if image is encrypted. Information on the encrypted image.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub file: Option<EncryptedFile>,
|
||||
}
|
||||
|
||||
/// The payload of a location message.
|
||||
@ -176,12 +205,17 @@ pub struct LocationMessageEventContent {
|
||||
/// Thumbnail info associated with a location.
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct LocationInfo {
|
||||
/// Metadata about the image referred to in `thumbnail_url`.
|
||||
/// Metadata about the image referred to in `thumbnail_url` or `thumbnail_file`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_info: Option<ThumbnailInfo>,
|
||||
/// The URL to a thumbnail of the location being represented.
|
||||
/// The URL to a thumbnail of the location being represented. Only present if the thumbnail is
|
||||
/// unencrypted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_url: Option<String>,
|
||||
/// Information on an encrypted thumbnail of the location being represented. Only present if the
|
||||
/// thumbnail is encrypted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_file: Option<EncryptedFile>,
|
||||
}
|
||||
|
||||
/// The payload of a notice message.
|
||||
@ -191,6 +225,11 @@ pub struct NoticeMessageEventContent {
|
||||
pub body: String,
|
||||
/// The message type. Always *m.notice*.
|
||||
pub msgtype: MessageType,
|
||||
/// Information about related messages for
|
||||
/// [rich replies](https://matrix.org/docs/spec/client_server/r0.5.0#rich-replies).
|
||||
#[serde(rename = "m.relates_to")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub relates_to: Option<RelatesTo>,
|
||||
}
|
||||
|
||||
/// The payload of a text message.
|
||||
@ -200,6 +239,18 @@ pub struct TextMessageEventContent {
|
||||
pub body: String,
|
||||
/// The message type. Always *m.text*.
|
||||
pub msgtype: MessageType,
|
||||
/// The format used in the `formatted_body`. Currently only `org.matrix.custom.html` is
|
||||
/// supported.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub format: Option<String>,
|
||||
/// The formatted version of the `body`. This is required if `format` is specified.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub formatted_body: Option<String>,
|
||||
/// Information about related messages for
|
||||
/// [rich replies](https://matrix.org/docs/spec/client_server/r0.5.0#rich-replies).
|
||||
#[serde(rename = "m.relates_to")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub relates_to: Option<RelatesTo>,
|
||||
}
|
||||
|
||||
/// The payload of a video message.
|
||||
@ -213,8 +264,12 @@ pub struct VideoMessageEventContent {
|
||||
pub info: Option<VideoInfo>,
|
||||
/// The message type. Always *m.video*.
|
||||
pub msgtype: MessageType,
|
||||
/// The URL to the video clip.
|
||||
pub url: String,
|
||||
/// The URL to the video clip. Required if the file is unencrypted. The URL (typically
|
||||
/// [MXC URI](https://matrix.org/docs/spec/client_server/r0.5.0#mxc-uri)) to the video clip.
|
||||
pub url: Option<String>,
|
||||
/// Required if video clip is encrypted. Information on the encrypted video clip.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub file: Option<EncryptedFile>,
|
||||
}
|
||||
|
||||
/// Metadata about a video.
|
||||
@ -236,15 +291,35 @@ pub struct VideoInfo {
|
||||
/// Metadata about an image.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_info: Option<ThumbnailInfo>,
|
||||
/// The URL to a thumbnail of the video clip.
|
||||
/// The URL (typically [MXC URI](https://matrix.org/docs/spec/client_server/r0.5.0#mxc-uri)) to
|
||||
/// an image thumbnail of the video clip. Only present if the thumbnail is unencrypted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_url: Option<String>,
|
||||
/// Information on the encrypted thumbnail file. Only present if the thumbnail is encrypted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_file: Option<EncryptedFile>,
|
||||
/// The width of the video in pixels.
|
||||
#[serde(rename = "w")]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub width: Option<u64>,
|
||||
}
|
||||
|
||||
/// Information about related messages for
|
||||
/// [rich replies](https://matrix.org/docs/spec/client_server/r0.5.0#rich-replies).
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct RelatesTo {
|
||||
/// Information about another message being replied to.
|
||||
#[serde(rename = "m.in_reply_to")]
|
||||
pub in_reply_to: InReplyTo,
|
||||
}
|
||||
|
||||
/// Information about the event a "rich reply" is replying to.
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct InReplyTo {
|
||||
/// The event being replied to.
|
||||
pub event_id: EventId,
|
||||
}
|
||||
|
||||
impl_enum! {
|
||||
MessageType {
|
||||
Audio => "m.audio",
|
||||
@ -374,7 +449,8 @@ mod tests {
|
||||
body: "test".to_string(),
|
||||
info: None,
|
||||
msgtype: MessageType::Audio,
|
||||
url: "http://example.com/audio.mp3".to_string(),
|
||||
url: Some("http://example.com/audio.mp3".to_string()),
|
||||
file: None,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
@ -389,7 +465,8 @@ mod tests {
|
||||
body: "test".to_string(),
|
||||
info: None,
|
||||
msgtype: MessageType::Audio,
|
||||
url: "http://example.com/audio.mp3".to_string(),
|
||||
url: Some("http://example.com/audio.mp3".to_string()),
|
||||
file: None,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
|
41
src/room/message/feedback.rs
Normal file
41
src/room/message/feedback.rs
Normal file
@ -0,0 +1,41 @@
|
||||
//! Types for the *m.room.message.feedback* event.
|
||||
|
||||
use ruma_identifiers::EventId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
room_event! {
|
||||
/// An acknowledgement of a message.
|
||||
///
|
||||
/// N.B.: Usage of this event is discouraged in favor of the receipts module. Most clients will
|
||||
/// not recognise this event.
|
||||
pub struct FeedbackEvent(FeedbackEventContent) {}
|
||||
}
|
||||
|
||||
/// The payload of an *m.room.message.feedback* event.
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct FeedbackEventContent {
|
||||
/// The event that this feedback is related to.
|
||||
pub target_event_id: EventId,
|
||||
/// The type of feedback.
|
||||
#[serde(rename = "type")]
|
||||
pub feedback_type: FeedbackType,
|
||||
}
|
||||
|
||||
/// A type of feedback.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub enum FeedbackType {
|
||||
/// Sent when a message is received.
|
||||
#[serde(rename = "delivered")]
|
||||
Delivered,
|
||||
|
||||
/// Sent when a message has been observed by the end user.
|
||||
#[serde(rename = "read")]
|
||||
Read,
|
||||
}
|
||||
|
||||
impl_enum! {
|
||||
FeedbackType {
|
||||
Delivered => "delivered",
|
||||
Read => "read",
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
//!
|
||||
//! This module also contains types shared by events in its child namespaces.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod aliases;
|
||||
@ -33,9 +35,12 @@ pub struct ImageInfo {
|
||||
/// Metadata about the image referred to in `thumbnail_url`.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_info: Option<ThumbnailInfo>,
|
||||
/// The URL to the thumbnail of the image.
|
||||
/// The URL to the thumbnail of the image. Only present if the thumbnail is unencrypted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_url: Option<String>,
|
||||
/// Information on the encrypted thumbnail image. Only present if the thumbnail is encrypted.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub thumbnail_file: Option<EncryptedFile>,
|
||||
/// The width of the image in pixels.
|
||||
#[serde(rename = "w")]
|
||||
pub width: u64,
|
||||
@ -55,3 +60,35 @@ pub struct ThumbnailInfo {
|
||||
#[serde(rename = "w")]
|
||||
pub width: u64,
|
||||
}
|
||||
|
||||
/// A file sent to a room with end-to-end encryption enabled.
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct EncryptedFile {
|
||||
/// The URL to the file.
|
||||
pub url: String,
|
||||
/// A [JSON Web Key](https://tools.ietf.org/html/rfc7517#appendix-A.3) object.
|
||||
pub key: JsonWebKey,
|
||||
/// The initialization vector used by AES-CTR, encoded as unpadded base64.
|
||||
pub iv: String,
|
||||
/// A map from an algorithm name to a hash of the ciphertext, encoded as unpadded base64.
|
||||
/// Clients should support the SHA-256 hash, which uses the key sha256.
|
||||
pub hashes: HashMap<String, String>,
|
||||
/// Version of the encrypted attachments protocol. Must be `v2`.
|
||||
pub v: String,
|
||||
}
|
||||
|
||||
/// A [JSON Web Key](https://tools.ietf.org/html/rfc7517#appendix-A.3) object.
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
||||
pub struct JsonWebKey {
|
||||
/// Key type. Must be `oct`.
|
||||
pub kty: String,
|
||||
/// Key operations. Must at least contain `encrypt` and `decrypt`.
|
||||
pub key_ops: Vec<String>,
|
||||
/// Required. Algorithm. Must be `A256CTR`.
|
||||
pub alg: String,
|
||||
/// The key, encoded as urlsafe unpadded base64.
|
||||
pub k: String,
|
||||
/// Extractable. Must be `true`. This is a
|
||||
/// [W3C extension](https://w3c.github.io/webcrypto/#iana-section-jwk).
|
||||
pub ext: bool,
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ state_event! {
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct NameEventContent {
|
||||
/// The name of the room. This MUST NOT exceed 255 bytes.
|
||||
/// Rooms with `name: None` should be treated the same as a room with no name.
|
||||
// The spec says “A room with an m.room.name event with an absent, null, or empty name field
|
||||
// should be treated the same as a room with no m.room.name event.”.
|
||||
// Serde maps null fields to None by default, serde(default) maps an absent field to None,
|
||||
|
@ -36,7 +36,7 @@ mod tests {
|
||||
content: content.clone(),
|
||||
event_id: EventId::new("example.com").unwrap(),
|
||||
event_type: EventType::RoomPinnedEvents,
|
||||
origin_server_ts: 1432804485886,
|
||||
origin_server_ts: 1_432_804_485_886,
|
||||
prev_content: None,
|
||||
room_id: Some(RoomId::new("example.com").unwrap()),
|
||||
sender: UserId::new("example.com").unwrap(),
|
||||
|
@ -52,6 +52,19 @@ pub struct PowerLevelsEventContent {
|
||||
/// The default power level for every user in the room.
|
||||
#[serde(default)]
|
||||
pub users_default: u64,
|
||||
|
||||
/// The power level requirements for specific notification types.
|
||||
///
|
||||
/// This is a mapping from `key` to power level for that notifications key.
|
||||
pub notifications: NotificationPowerLevels,
|
||||
}
|
||||
|
||||
/// The power level requirements for specific notification types.
|
||||
#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
|
||||
pub struct NotificationPowerLevels {
|
||||
/// The level required to trigger an `@room` notification.
|
||||
#[serde(default = "default_power_level")]
|
||||
pub room: u64,
|
||||
}
|
||||
|
||||
fn default_power_level() -> u64 {
|
||||
|
@ -338,7 +338,7 @@ mod tests {
|
||||
assert_eq!(event.sender.to_string(), "@example:localhost");
|
||||
}
|
||||
_ => {
|
||||
assert!(false);
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
|
||||
@ -350,7 +350,7 @@ mod tests {
|
||||
assert_eq!(event.sender.to_string(), "@example:localhost");
|
||||
}
|
||||
_ => {
|
||||
assert!(false);
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
|
||||
@ -362,14 +362,14 @@ mod tests {
|
||||
assert_eq!(image_info.width, 128);
|
||||
assert_eq!(image_info.mimetype, "image/jpeg");
|
||||
assert_eq!(image_info.size, 1024);
|
||||
assert_eq!(event.content.thumbnail_info.unwrap().size, 32);
|
||||
assert_eq!(image_info.thumbnail_info.unwrap().size, 32);
|
||||
assert_eq!(event.content.url, "https://domain.com/image.jpg");
|
||||
assert_eq!(event.event_type, EventType::RoomAvatar);
|
||||
assert_eq!(event.state_key, "");
|
||||
assert_eq!(event.sender.to_string(), "@example:localhost");
|
||||
}
|
||||
_ => {
|
||||
assert!(false);
|
||||
unreachable!();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -21,5 +21,5 @@ pub struct TagEventContent {
|
||||
pub struct TagInfo {
|
||||
/// Value to use for lexicographically ordering rooms with this tag.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub order: Option<String>,
|
||||
pub order: Option<f64>,
|
||||
}
|
||||
|
@ -7,11 +7,7 @@ event! {
|
||||
/// Informs the client of the list of users currently typing.
|
||||
pub struct TypingEvent(TypingEventContent) {
|
||||
/// The unique identifier for the room associated with this event.
|
||||
///
|
||||
/// This can be `None` if the event came from a context where there is
|
||||
/// no ambiguity which room it belongs to, like a `/sync` response for example.
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
pub room_id: Option<RoomId>
|
||||
pub room_id: RoomId
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user