events: Unstable support for MSC 3489 live location sharing

This commit is contained in:
torrybr 2024-06-10 17:45:42 -04:00 committed by GitHub
parent 99b30fb9d4
commit f60c79727a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 391 additions and 0 deletions

View File

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

View File

@ -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"]

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

View 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())
}
}

View File

@ -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")]

View File

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

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

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

View File

@ -1,4 +1,6 @@
mod audio;
mod beacon;
mod beacon_info;
mod call;
mod encrypted;
mod enums;

View File

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