feat(sdk): Add method to get a room permalink
Include routing for room IDs
This commit is contained in:
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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].
|
||||
|
||||
Reference in New Issue
Block a user