feat(sdk): Add method to get a room permalink

Include routing for room IDs
This commit is contained in:
Kévin Commaille
2022-06-26 13:32:44 +02:00
parent ebc7177438
commit f0e0194ff2
2 changed files with 383 additions and 3 deletions
+320 -1
View File
@@ -2216,7 +2216,7 @@ pub(crate) mod tests {
},
mxc_uri, room_id, thirdparty, uint, user_id, TransactionId, UserId,
};
use serde_json::json;
use serde_json::{json, Value as JsonValue};
use url::Url;
use super::{Client, ClientBuilder, Session};
@@ -3960,4 +3960,323 @@ pub(crate) mod tests {
mocked_messages.assert();
mocked_messages_2.assert();
}
#[async_test]
async fn room_permalink() {
fn sync_response(index: u8, room_timeline_events: &[JsonValue]) -> JsonValue {
json!({
"device_one_time_keys_count": {},
"next_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index + 1),
"device_lists": {
"changed": [],
"left": []
},
"account_data": {
"events": []
},
"rooms": {
"invite": {},
"join": {
"!test_room:127.0.0.1": {
"summary": {},
"account_data": {
"events": []
},
"ephemeral": {
"events": []
},
"state": {
"events": []
},
"timeline": {
"events": room_timeline_events,
"limited": false,
"prev_batch": format!("s526_47314_0_7_1_1_1_11444_{}", index - 1),
},
"unread_notifications": {
"highlight_count": 0,
"notification_count": 0,
}
}
},
"leave": {}
},
"to_device": {
"events": []
},
"presence": {
"events": []
}
})
}
fn room_member_events(nb: usize, server: &str) -> Vec<JsonValue> {
let mut events = Vec::with_capacity(nb);
for i in 0..nb {
let id = format!("${server}{i}");
let user = format!("@user{i}:{server}");
events.push(json!({
"content": {
"membership": "join",
},
"event_id": id,
"origin_server_ts": 151800140,
"sender": user,
"state_key": user,
"type": "m.room.member",
}))
}
events
}
let client = logged_in_client().await;
let sync_settings = SyncSettings::new();
// Without elligible server
let mut sync_index = 1;
let res = sync_response(
sync_index,
&[
json!({
"content": {
"creator": "@creator:127.0.0.1",
"room_version": "6",
},
"event_id": "$151957878228ekrDs",
"origin_server_ts": 15195787,
"sender": "@creator:localhost",
"state_key": "",
"type": "m.room.create",
}),
json!({
"content": {
"membership": "join",
},
"event_id": "$151800140517rfvjc",
"origin_server_ts": 151800140,
"sender": "@creator:127.0.0.1",
"state_key": "@creator:127.0.0.1",
"type": "m.room.member",
}),
],
);
let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned()))
.with_status(200)
.with_body(res.to_string())
.match_header("authorization", "Bearer 1234")
.create();
client.sync_once(sync_settings.clone()).await.unwrap();
let room = client.get_room(room_id!("!test_room:127.0.0.1")).unwrap();
assert_eq!(room.permalink().await.unwrap(), "https://matrix.to/#/%21test_room%3A127.0.0.1");
// With a single elligible server
sync_index += 1;
let res = sync_response(
sync_index,
&[json!({
"content": {
"membership": "join",
},
"event_id": "$151800140517rfvjc",
"origin_server_ts": 151800140,
"sender": "@example:localhost",
"state_key": "@example:localhost",
"type": "m.room.member",
})],
);
let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned()))
.with_status(200)
.with_body(res.to_string())
.match_header("authorization", "Bearer 1234")
.create();
client.sync_once(sync_settings.clone()).await.unwrap();
assert_eq!(
room.permalink().await.unwrap(),
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost"
);
// With two elligible servers
sync_index += 1;
let res = sync_response(sync_index, &room_member_events(15, "notarealhs"));
let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned()))
.with_status(200)
.with_body(res.to_string())
.match_header("authorization", "Bearer 1234")
.create();
client.sync_once(sync_settings.clone()).await.unwrap();
assert_eq!(
room.permalink().await.unwrap(),
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=localhost"
);
// With three elligible servers
sync_index += 1;
let res = sync_response(sync_index, &room_member_events(5, "mymatrix"));
let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned()))
.with_status(200)
.with_body(res.to_string())
.match_header("authorization", "Bearer 1234")
.create();
client.sync_once(sync_settings.clone()).await.unwrap();
assert_eq!(
room.permalink().await.unwrap(),
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=mymatrix&via=localhost"
);
// With four elligible servers
sync_index += 1;
let res = sync_response(sync_index, &room_member_events(10, "yourmatrix"));
let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned()))
.with_status(200)
.with_body(res.to_string())
.match_header("authorization", "Bearer 1234")
.create();
client.sync_once(sync_settings.clone()).await.unwrap();
assert_eq!(
room.permalink().await.unwrap(),
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=notarealhs&via=yourmatrix&via=mymatrix"
);
// With power levels
sync_index += 1;
let res = sync_response(
sync_index,
&[json!({
"content": {
"users": {
"@example:localhost": 50,
},
},
"event_id": "$15139375512JaHAW",
"origin_server_ts": 151393755,
"sender": "@creator:127.0.0.1",
"state_key": "",
"type": "m.room.power_levels",
})],
);
let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned()))
.with_status(200)
.with_body(res.to_string())
.match_header("authorization", "Bearer 1234")
.create();
client.sync_once(sync_settings.clone()).await.unwrap();
assert_eq!(
room.permalink().await.unwrap(),
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=localhost&via=notarealhs&via=yourmatrix"
);
// With higher power levels
sync_index += 1;
let res = sync_response(
sync_index,
&[json!({
"content": {
"users": {
"@example:localhost": 50,
"@user0:mymatrix": 70,
},
},
"event_id": "$15139375512JaHAZ",
"origin_server_ts": 151393755,
"sender": "@creator:127.0.0.1",
"state_key": "",
"type": "m.room.power_levels",
})],
);
let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned()))
.with_status(200)
.with_body(res.to_string())
.match_header("authorization", "Bearer 1234")
.create();
client.sync_once(sync_settings.clone()).await.unwrap();
assert_eq!(
room.permalink().await.unwrap(),
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=notarealhs&via=yourmatrix"
);
// With server ACLs
sync_index += 1;
let res = sync_response(
sync_index,
&[json!({
"content": {
"allow": ["*"],
"allow_ip_literals": true,
"deny": ["notarealhs"],
},
"event_id": "$143273582443PhrSn",
"origin_server_ts": 1432735824,
"sender": "@creator:127.0.0.1",
"state_key": "",
"type": "m.room.server_acl",
})],
);
let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned()))
.with_status(200)
.with_body(res.to_string())
.match_header("authorization", "Bearer 1234")
.create();
client.sync_once(sync_settings.clone()).await.unwrap();
assert_eq!(
room.permalink().await.unwrap(),
"https://matrix.to/#/%21test_room%3A127.0.0.1?via=mymatrix&via=yourmatrix&via=localhost"
);
// With an alternative alias
sync_index += 1;
let res = sync_response(
sync_index,
&[json!({
"content": {
"alt_aliases": ["#alias:localhost"],
},
"event_id": "$15139375513VdeRF",
"origin_server_ts": 151393755,
"sender": "@example:localhost",
"state_key": "",
"type": "m.room.canonical_alias",
})],
);
let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned()))
.with_status(200)
.with_body(res.to_string())
.match_header("authorization", "Bearer 1234")
.create();
client.sync_once(sync_settings.clone()).await.unwrap();
assert_eq!(room.permalink().await.unwrap(), "https://matrix.to/#/%23alias%3Alocalhost");
// With a canonical alias
sync_index += 1;
let res = sync_response(
sync_index,
&[json!({
"content": {
"alias": "#canonical:localhost",
"alt_aliases": ["#alias:localhost"],
},
"event_id": "$15139375513VdeRF",
"origin_server_ts": 151393755,
"sender": "@example:localhost",
"state_key": "",
"type": "m.room.canonical_alias",
})],
);
let _sync = mock("GET", Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_owned()))
.with_status(200)
.with_body(res.to_string())
.match_header("authorization", "Bearer 1234")
.create();
client.sync_once(sync_settings).await.unwrap();
assert_eq!(room.permalink().await.unwrap(), "https://matrix.to/#/%23canonical%3Alocalhost");
}
}
+63 -2
View File
@@ -28,7 +28,10 @@ use ruma::{
assign,
events::{
direct::DirectEvent,
room::{history_visibility::HistoryVisibility, MediaSource},
room::{
history_visibility::HistoryVisibility, server_acl::RoomServerAclEventContent,
MediaSource,
},
tag::{TagInfo, TagName},
AnyRoomAccountDataEvent, AnyStateEvent, AnySyncStateEvent, GlobalAccountDataEventType,
RedactContent, RedactedEventContent, RoomAccountDataEvent, RoomAccountDataEventContent,
@@ -36,7 +39,7 @@ use ruma::{
SyncStateEvent,
},
serde::Raw,
uint, EventId, RoomId, UInt, UserId,
uint, EventId, RoomId, ServerName, UInt, UserId,
};
use crate::{
@@ -931,6 +934,64 @@ impl Common {
Err(Error::NoOlmMachine)
}
}
/// Get a permalink to this room.
///
/// If this room has an alias, we use it. Otherwise, we try to use the
/// synced members in the room for [routing] the room ID.
///
/// This currently returns a `matrix.to` URI but the format of the permalink
/// might change without notice so don't rely on it.
///
/// [routing]: https://spec.matrix.org/v1.3/appendices/#routing
pub async fn permalink(&self) -> Result<String> {
if let Some(alias) = self.canonical_alias().or_else(|| self.alt_aliases().pop()) {
return Ok(alias.matrix_to_uri().to_string());
}
let acl_ev = self
.get_state_event_static::<RoomServerAclEventContent>("")
.await?
.and_then(|ev| ev.deserialize().ok());
let acl = acl_ev.as_ref().and_then(|ev| ev.as_original()).map(|ev| &ev.content);
// Filter out server names that:
// - Are blocked due to server ACLs
// - Are IP addresses
let members: Vec<_> = self
.joined_members_no_sync()
.await?
.into_iter()
.filter(|member| {
let server = member.user_id().server_name();
acl.filter(|acl| !acl.is_allowed(server)).is_none() && !server.is_ip_literal()
})
.collect();
// Get the server of the highest power level user in the room, provided
// they are at least power level 50.
let max = members
.iter()
.max_by_key(|member| member.power_level())
.filter(|max| max.power_level() >= 50)
.map(|member| member.user_id().server_name());
// Sort the servers by population.
let servers = members
.iter()
.map(|member| member.user_id().server_name())
.filter(|server| max.filter(|max| max == server).is_none())
.fold(BTreeMap::<&ServerName, u32>::new(), |mut servers, server| {
*servers.entry(server).or_default() += 1;
servers
});
let mut servers: Vec<_> = servers.into_iter().collect();
servers.sort_unstable_by(|(_, count_a), (_, count_b)| count_b.cmp(count_a));
let via = max.into_iter().chain(servers.into_iter().map(|(name, _)| name)).take(3);
Ok(self.room_id().matrix_to_uri(via).to_string())
}
}
/// Options for [`messages`][Common::messages].