events: Unstable support for MSC 3489 live location sharing
This commit is contained in:
parent
99b30fb9d4
commit
f60c79727a
@ -3,6 +3,8 @@
|
||||
Improvements:
|
||||
|
||||
- Add support for encrypted stickers as sent by several bridges under the flag `compat-encrypted-stickers`
|
||||
- Add unstable support for MSC3489 `m.beacon` & `m.beacon_info` events
|
||||
(unstable types `org.matrix.msc3489.beacon` & `org.matrix.msc3489.beacon_info`)
|
||||
|
||||
Breaking changes:
|
||||
|
||||
|
@ -33,6 +33,7 @@ unstable-msc3291 = []
|
||||
unstable-msc3381 = ["unstable-msc1767"]
|
||||
unstable-msc3401 = []
|
||||
unstable-msc3488 = ["unstable-msc1767"]
|
||||
unstable-msc3489 = ["unstable-msc3488"]
|
||||
unstable-msc3551 = ["unstable-msc3956"]
|
||||
unstable-msc3552 = ["unstable-msc3551"]
|
||||
unstable-msc3553 = ["unstable-msc3552"]
|
||||
|
43
crates/ruma-events/src/beacon.rs
Normal file
43
crates/ruma-events/src/beacon.rs
Normal file
@ -0,0 +1,43 @@
|
||||
//! Types for the `org.matrix.msc3489.beacon` event, the unstable version of
|
||||
//! `m.beacon` ([MSC3489]).
|
||||
//!
|
||||
//! [MSC3489]: https://github.com/matrix-org/matrix-spec-proposals/pull/3489
|
||||
|
||||
use ruma_common::{MilliSecondsSinceUnixEpoch, OwnedEventId};
|
||||
use ruma_events::{location::LocationContent, relation::Reference};
|
||||
use ruma_macros::EventContent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// The content of a beacon.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, EventContent)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_event(type = "org.matrix.msc3672.beacon", alias = "m.beacon", kind = MessageLike)]
|
||||
pub struct BeaconEventContent {
|
||||
/// The beacon_info event id this relates to.
|
||||
#[serde(rename = "m.relates_to")]
|
||||
pub relates_to: Reference,
|
||||
|
||||
/// The location of the beacon.
|
||||
#[serde(rename = "org.matrix.msc3488.location")]
|
||||
pub location: LocationContent,
|
||||
|
||||
/// The timestamp of the event.
|
||||
#[serde(rename = "org.matrix.msc3488.ts")]
|
||||
pub ts: MilliSecondsSinceUnixEpoch,
|
||||
}
|
||||
|
||||
impl BeaconEventContent {
|
||||
/// Creates a new `BeaconEventContent` with the given beacon_info event id, geo uri and
|
||||
/// optional ts. If ts is None, the current time will be used.
|
||||
pub fn new(
|
||||
beacon_info_event_id: OwnedEventId,
|
||||
geo_uri: String,
|
||||
ts: Option<MilliSecondsSinceUnixEpoch>,
|
||||
) -> Self {
|
||||
Self {
|
||||
relates_to: Reference::new(beacon_info_event_id),
|
||||
location: LocationContent::new(geo_uri),
|
||||
ts: ts.unwrap_or_else(MilliSecondsSinceUnixEpoch::now),
|
||||
}
|
||||
}
|
||||
}
|
83
crates/ruma-events/src/beacon_info.rs
Normal file
83
crates/ruma-events/src/beacon_info.rs
Normal file
@ -0,0 +1,83 @@
|
||||
//! Types for the `org.matrix.msc3489.beacon_info` state event, the unstable version of
|
||||
//! `m.beacon_info` ([MSC3489]).
|
||||
//!
|
||||
//! [MSC3489]: https://github.com/matrix-org/matrix-spec-proposals/pull/3489
|
||||
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use ruma_common::{MilliSecondsSinceUnixEpoch, OwnedUserId};
|
||||
use ruma_macros::EventContent;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::location::AssetContent;
|
||||
|
||||
/// The content of a beacon_info state.
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_event(
|
||||
type = "org.matrix.msc3672.beacon_info", alias = "m.beacon_info", kind = State, state_key_type = OwnedUserId
|
||||
)]
|
||||
pub struct BeaconInfoEventContent {
|
||||
/// The description of the location.
|
||||
///
|
||||
/// It should be used to label the location on a map.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<String>,
|
||||
|
||||
/// Whether the user starts sharing their location.
|
||||
pub live: bool,
|
||||
|
||||
/// The time when location sharing started.
|
||||
#[serde(rename = "org.matrix.msc3488.ts")]
|
||||
pub ts: MilliSecondsSinceUnixEpoch,
|
||||
|
||||
/// The duration that the location sharing will be live.
|
||||
///
|
||||
/// Meaning that the location will stop being shared at `ts + timeout`.
|
||||
#[serde(default, with = "ruma_common::serde::duration::ms")]
|
||||
pub timeout: Duration,
|
||||
|
||||
/// The asset that this message refers to.
|
||||
#[serde(default, rename = "org.matrix.msc3488.asset")]
|
||||
pub asset: AssetContent,
|
||||
}
|
||||
|
||||
impl BeaconInfoEventContent {
|
||||
/// Creates a new `BeaconInfoEventContent` with the given description, live, timeout and
|
||||
/// optional ts. If ts is None, the current time will be used.
|
||||
pub fn new(
|
||||
description: Option<String>,
|
||||
timeout: Duration,
|
||||
live: bool,
|
||||
ts: Option<MilliSecondsSinceUnixEpoch>,
|
||||
) -> Self {
|
||||
Self {
|
||||
description,
|
||||
live,
|
||||
ts: ts.unwrap_or_else(MilliSecondsSinceUnixEpoch::now),
|
||||
timeout,
|
||||
asset: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Starts the beacon_info being live.
|
||||
pub fn start(&mut self) {
|
||||
self.live = true;
|
||||
}
|
||||
|
||||
/// Stops the beacon_info from being live.
|
||||
pub fn stop(&mut self) {
|
||||
self.live = false;
|
||||
}
|
||||
|
||||
/// Start time plus its timeout, it returns `false`, indicating that the beacon is not live.
|
||||
/// Otherwise, it returns `true`.
|
||||
pub fn is_live(&self) -> bool {
|
||||
self.live
|
||||
&& self
|
||||
.ts
|
||||
.to_system_time()
|
||||
.and_then(|t| t.checked_add(self.timeout))
|
||||
.is_some_and(|t| t > SystemTime::now())
|
||||
}
|
||||
}
|
@ -88,6 +88,9 @@ event_enum! {
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
#[ruma_enum(ident = UnstablePollEnd)]
|
||||
"org.matrix.msc3381.poll.end" => super::poll::unstable_end,
|
||||
#[cfg(feature = "unstable-msc3489")]
|
||||
#[ruma_enum(alias = "m.beacon")]
|
||||
"org.matrix.msc3672.beacon" => super::beacon,
|
||||
"m.reaction" => super::reaction,
|
||||
"m.room.encrypted" => super::room::encrypted,
|
||||
"m.room.message" => super::room::message,
|
||||
@ -127,6 +130,9 @@ event_enum! {
|
||||
"m.room.topic" => super::room::topic,
|
||||
"m.space.child" => super::space::child,
|
||||
"m.space.parent" => super::space::parent,
|
||||
#[cfg(feature = "unstable-msc3489")]
|
||||
#[ruma_enum(alias = "m.beacon_info")]
|
||||
"org.matrix.msc3672.beacon_info" => super::beacon_info,
|
||||
#[cfg(feature = "unstable-msc3401")]
|
||||
#[ruma_enum(alias = "m.call.member")]
|
||||
"org.matrix.msc3401.call.member" => super::call::member,
|
||||
@ -307,6 +313,8 @@ impl AnyMessageLikeEventContent {
|
||||
/// This is a helper function intended for encryption. There should not be a reason to access
|
||||
/// `m.relates_to` without first destructuring an `AnyMessageLikeEventContent` otherwise.
|
||||
pub fn relation(&self) -> Option<encrypted::Relation> {
|
||||
#[cfg(feature = "unstable-msc3489")]
|
||||
use super::beacon::BeaconEventContent;
|
||||
use super::key::verification::{
|
||||
accept::KeyVerificationAcceptEventContent, cancel::KeyVerificationCancelEventContent,
|
||||
done::KeyVerificationDoneEventContent, key::KeyVerificationKeyEventContent,
|
||||
@ -359,6 +367,10 @@ impl AnyMessageLikeEventContent {
|
||||
| Self::UnstablePollEnd(UnstablePollEndEventContent { relates_to, .. }) => {
|
||||
Some(encrypted::Relation::Reference(relates_to.clone()))
|
||||
}
|
||||
#[cfg(feature = "unstable-msc3489")]
|
||||
Self::Beacon(BeaconEventContent { relates_to, .. }) => {
|
||||
Some(encrypted::Relation::Reference(relates_to.clone()))
|
||||
}
|
||||
#[cfg(feature = "unstable-msc3381")]
|
||||
Self::PollStart(_) | Self::UnstablePollStart(_) => None,
|
||||
#[cfg(feature = "unstable-msc4075")]
|
||||
|
@ -139,6 +139,10 @@ pub mod macros {
|
||||
|
||||
#[cfg(feature = "unstable-msc3927")]
|
||||
pub mod audio;
|
||||
#[cfg(feature = "unstable-msc3489")]
|
||||
pub mod beacon;
|
||||
#[cfg(feature = "unstable-msc3489")]
|
||||
pub mod beacon_info;
|
||||
pub mod call;
|
||||
pub mod direct;
|
||||
pub mod dummy;
|
||||
|
81
crates/ruma-events/tests/it/beacon.rs
Normal file
81
crates/ruma-events/tests/it/beacon.rs
Normal file
@ -0,0 +1,81 @@
|
||||
#![cfg(feature = "unstable-msc3489")]
|
||||
|
||||
use assert_matches2::assert_matches;
|
||||
use js_int::uint;
|
||||
use ruma_common::{
|
||||
owned_event_id, room_id, serde::CanBeEmpty, user_id, MilliSecondsSinceUnixEpoch,
|
||||
};
|
||||
use ruma_events::{
|
||||
beacon::BeaconEventContent, relation::Reference, AnyMessageLikeEvent, MessageLikeEvent,
|
||||
};
|
||||
use serde_json::{
|
||||
from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue,
|
||||
};
|
||||
|
||||
fn get_beacon_event_content() -> BeaconEventContent {
|
||||
BeaconEventContent::new(
|
||||
owned_event_id!("$beacon_info_event_id:example.com"),
|
||||
"geo:51.5008,0.1247;u=35".to_owned(),
|
||||
Some(MilliSecondsSinceUnixEpoch(uint!(1_636_829_458))),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_beacon_event_content_json() -> JsonValue {
|
||||
json!({
|
||||
"m.relates_to": {
|
||||
"rel_type": "m.reference",
|
||||
"event_id": "$beacon_info_event_id:example.com"
|
||||
},
|
||||
"org.matrix.msc3488.location": {
|
||||
"uri": "geo:51.5008,0.1247;u=35",
|
||||
},
|
||||
"org.matrix.msc3488.ts": 1_636_829_458
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beacon_event_content_serialization() {
|
||||
let event_content = get_beacon_event_content();
|
||||
|
||||
assert_eq!(to_json_value(&event_content).unwrap(), get_beacon_event_content_json());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beacon_event_content_deserialization() {
|
||||
let json_data = get_beacon_event_content_json();
|
||||
|
||||
let event_content: BeaconEventContent =
|
||||
from_json_value::<BeaconEventContent>(json_data).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
event_content.relates_to.event_id,
|
||||
owned_event_id!("$beacon_info_event_id:example.com")
|
||||
);
|
||||
assert_eq!(event_content.location.uri, "geo:51.5008,0.1247;u=35");
|
||||
assert_eq!(event_content.ts, MilliSecondsSinceUnixEpoch(uint!(1_636_829_458)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn message_event_deserialization() {
|
||||
let json_data = json!({
|
||||
"content": get_beacon_event_content_json(),
|
||||
"event_id": "$beacon_event_id:example.com",
|
||||
"origin_server_ts": 1_636_829_458,
|
||||
"room_id": "!roomid:example.com",
|
||||
"type": "org.matrix.msc3672.beacon",
|
||||
"sender": "@example:example.com"
|
||||
});
|
||||
|
||||
let event = from_json_value::<AnyMessageLikeEvent>(json_data).unwrap();
|
||||
|
||||
assert_matches!(event, AnyMessageLikeEvent::Beacon(MessageLikeEvent::Original(ev)));
|
||||
assert_eq!(ev.content.location.uri, "geo:51.5008,0.1247;u=35");
|
||||
assert_eq!(ev.content.ts, MilliSecondsSinceUnixEpoch(uint!(1_636_829_458)));
|
||||
assert_matches!(ev.content.relates_to, Reference { event_id, .. });
|
||||
assert_eq!(event_id, owned_event_id!("$beacon_info_event_id:example.com"));
|
||||
|
||||
assert_eq!(ev.sender, user_id!("@example:example.com"));
|
||||
assert_eq!(ev.room_id, room_id!("!roomid:example.com"));
|
||||
assert_eq!(ev.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(1_636_829_458)));
|
||||
assert!(ev.unsigned.is_empty());
|
||||
}
|
161
crates/ruma-events/tests/it/beacon_info.rs
Normal file
161
crates/ruma-events/tests/it/beacon_info.rs
Normal file
@ -0,0 +1,161 @@
|
||||
#![cfg(feature = "unstable-msc3489")]
|
||||
|
||||
use std::time::Duration;
|
||||
|
||||
use assert_matches2::assert_matches;
|
||||
use js_int::uint;
|
||||
use ruma_common::{event_id, room_id, serde::CanBeEmpty, user_id, MilliSecondsSinceUnixEpoch};
|
||||
use ruma_events::{
|
||||
beacon_info::BeaconInfoEventContent, location::AssetType, AnyStateEvent, StateEvent,
|
||||
};
|
||||
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
||||
|
||||
fn get_beacon_info_event_content(
|
||||
duration: Option<Duration>,
|
||||
ts: Option<MilliSecondsSinceUnixEpoch>,
|
||||
) -> BeaconInfoEventContent {
|
||||
let description = Some("Kylie's live location".to_owned());
|
||||
let duration_or = duration.unwrap_or(Duration::from_secs(60));
|
||||
let ts_or = Some(ts.unwrap_or(MilliSecondsSinceUnixEpoch::now()));
|
||||
|
||||
BeaconInfoEventContent::new(description, duration_or, true, ts_or)
|
||||
}
|
||||
|
||||
fn get_beacon_info_json() -> serde_json::Value {
|
||||
json!({
|
||||
"org.matrix.msc3488.ts": 1_636_829_458,
|
||||
"org.matrix.msc3488.asset": {
|
||||
"type": "m.self"
|
||||
},
|
||||
"timeout": 60_000,
|
||||
"description": "Kylie's live location",
|
||||
"live": true
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beacon_info_is_live() {
|
||||
let event_content = get_beacon_info_event_content(None, None);
|
||||
|
||||
assert!(event_content.is_live());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beacon_info_is_not_live() {
|
||||
let duration = Some(Duration::from_nanos(1));
|
||||
let event_content = get_beacon_info_event_content(duration, None);
|
||||
|
||||
assert!(!event_content.is_live());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beacon_info_stop_event() {
|
||||
let ts = Some(MilliSecondsSinceUnixEpoch(1_636_829_458_u64.try_into().unwrap()));
|
||||
|
||||
let mut event_content = get_beacon_info_event_content(None, ts);
|
||||
|
||||
event_content.stop();
|
||||
|
||||
assert_eq!(
|
||||
to_json_value(&event_content).unwrap(),
|
||||
json!({
|
||||
"org.matrix.msc3488.ts": 1_636_829_458,
|
||||
"org.matrix.msc3488.asset": {
|
||||
"type": "m.self"
|
||||
},
|
||||
"timeout": 60_000,
|
||||
"description": "Kylie's live location",
|
||||
"live": false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beacon_info_start_event() {
|
||||
let ts = Some(MilliSecondsSinceUnixEpoch(1_636_829_458_u64.try_into().unwrap()));
|
||||
|
||||
let mut event_content = BeaconInfoEventContent::new(
|
||||
Some("Kylie's live location".to_owned()),
|
||||
Duration::from_secs(60),
|
||||
false,
|
||||
ts,
|
||||
);
|
||||
|
||||
event_content.start();
|
||||
|
||||
assert_eq!(
|
||||
to_json_value(&event_content).unwrap(),
|
||||
json!({
|
||||
"org.matrix.msc3488.ts": 1_636_829_458,
|
||||
"org.matrix.msc3488.asset": {
|
||||
"type": "m.self"
|
||||
},
|
||||
"timeout": 60_000,
|
||||
"description": "Kylie's live location",
|
||||
"live": true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beacon_info_start_event_content_serialization() {
|
||||
let ts = Some(MilliSecondsSinceUnixEpoch(1_636_829_458_u64.try_into().unwrap()));
|
||||
|
||||
let event_content = get_beacon_info_event_content(None, ts);
|
||||
|
||||
assert_eq!(
|
||||
to_json_value(&event_content).unwrap(),
|
||||
json!({
|
||||
"org.matrix.msc3488.ts": 1_636_829_458,
|
||||
"org.matrix.msc3488.asset": {
|
||||
"type": "m.self"
|
||||
},
|
||||
"timeout": 60_000,
|
||||
"description": "Kylie's live location",
|
||||
"live": true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn beacon_info_start_event_content_deserialization() {
|
||||
let json_data = get_beacon_info_json();
|
||||
|
||||
let event_content: BeaconInfoEventContent = serde_json::from_value(json_data).unwrap();
|
||||
|
||||
assert_eq!(event_content.description, Some("Kylie's live location".to_owned()));
|
||||
assert!(event_content.live);
|
||||
assert_eq!(event_content.ts, MilliSecondsSinceUnixEpoch(uint!(1_636_829_458)));
|
||||
assert_eq!(event_content.timeout, Duration::from_secs(60));
|
||||
assert_eq!(event_content.asset.type_, AssetType::Self_);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn state_event_deserialization() {
|
||||
let json_data = json!({
|
||||
"content": get_beacon_info_json(),
|
||||
"event_id": "$beacon_event_id:example.com",
|
||||
"origin_server_ts": 1_636_829_458,
|
||||
"room_id": "!roomid:example.com",
|
||||
"type": "org.matrix.msc3672.beacon_info",
|
||||
"sender": "@example:example.com",
|
||||
"state_key": "@example:example.com"
|
||||
});
|
||||
|
||||
let event = from_json_value::<AnyStateEvent>(json_data).unwrap();
|
||||
|
||||
assert_matches!(event, AnyStateEvent::BeaconInfo(StateEvent::Original(ev)));
|
||||
|
||||
assert_eq!(ev.content.description, Some("Kylie's live location".to_owned()));
|
||||
assert_eq!(ev.content.ts, MilliSecondsSinceUnixEpoch(uint!(1_636_829_458)));
|
||||
assert_eq!(ev.content.timeout, Duration::from_secs(60));
|
||||
assert_eq!(ev.content.asset.type_, AssetType::Self_);
|
||||
assert!(ev.content.live);
|
||||
|
||||
assert_eq!(ev.event_id, event_id!("$beacon_event_id:example.com"));
|
||||
assert_eq!(ev.origin_server_ts, MilliSecondsSinceUnixEpoch(uint!(1_636_829_458)));
|
||||
assert_eq!(ev.room_id, room_id!("!roomid:example.com"));
|
||||
assert_eq!(ev.sender, user_id!("@example:example.com"));
|
||||
assert_eq!(ev.state_key, "@example:example.com");
|
||||
assert!(ev.unsigned.is_empty());
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
mod audio;
|
||||
mod beacon;
|
||||
mod beacon_info;
|
||||
mod call;
|
||||
mod encrypted;
|
||||
mod enums;
|
||||
|
@ -207,6 +207,7 @@ unstable-msc3291 = ["ruma-events?/unstable-msc3291"]
|
||||
unstable-msc3381 = ["ruma-events?/unstable-msc3381"]
|
||||
unstable-msc3401 = ["ruma-events?/unstable-msc3401"]
|
||||
unstable-msc3488 = ["ruma-client-api?/unstable-msc3488", "ruma-events?/unstable-msc3488"]
|
||||
unstable-msc3489 = ["ruma-events?/unstable-msc3489"]
|
||||
unstable-msc3551 = ["ruma-events?/unstable-msc3551"]
|
||||
unstable-msc3552 = ["ruma-events?/unstable-msc3552"]
|
||||
unstable-msc3553 = ["ruma-events?/unstable-msc3553"]
|
||||
@ -262,6 +263,7 @@ __ci = [
|
||||
"unstable-msc3381",
|
||||
"unstable-msc3401",
|
||||
"unstable-msc3488",
|
||||
"unstable-msc3489",
|
||||
"unstable-msc3551",
|
||||
"unstable-msc3552",
|
||||
"unstable-msc3553",
|
||||
|
Loading…
x
Reference in New Issue
Block a user