Update existing events for spec r0.5.0 and add m.fully_read and

m.room.message.feedback.
This commit is contained in:
Jimmy Cuadra 2019-06-12 16:20:10 -07:00
parent 9f43f37f41
commit ba2538dda9
20 changed files with 370 additions and 66 deletions

View File

@ -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",
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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
View 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,
}

View File

@ -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,

View File

@ -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,

View File

@ -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,
}

View File

@ -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,
}

View File

@ -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,
}

View File

@ -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>,

View File

@ -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!(

View 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",
}
}

View File

@ -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,
}

View File

@ -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,

View File

@ -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(),

View File

@ -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 {

View File

@ -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!();
}
};
}

View File

@ -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>,
}

View File

@ -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
}
}