Merge branch 'master' into state-store
commit
0749e59af8
|
@ -697,6 +697,13 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
RoomEvent::RoomTombstone(tomb) => {
|
||||||
|
if let Some(ee) = &self.event_emitter {
|
||||||
|
if let Some(room) = self.get_room(&room_id) {
|
||||||
|
ee.on_room_tombstone(Arc::clone(&room), &tomb).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -753,6 +760,13 @@ impl Client {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
StateEvent::RoomTombstone(tomb) => {
|
||||||
|
if let Some(ee) = &self.event_emitter {
|
||||||
|
if let Some(room) = self.get_room(&room_id) {
|
||||||
|
ee.on_room_tombstone(Arc::clone(&room), &tomb).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ use crate::events::{
|
||||||
name::NameEvent,
|
name::NameEvent,
|
||||||
power_levels::PowerLevelsEvent,
|
power_levels::PowerLevelsEvent,
|
||||||
redaction::RedactionEvent,
|
redaction::RedactionEvent,
|
||||||
|
tombstone::TombstoneEvent,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use crate::models::Room;
|
use crate::models::Room;
|
||||||
|
@ -98,6 +99,8 @@ pub trait EventEmitter: Send + Sync {
|
||||||
async fn on_room_redaction(&self, _: Arc<RwLock<Room>>, _: &RedactionEvent) {}
|
async fn on_room_redaction(&self, _: Arc<RwLock<Room>>, _: &RedactionEvent) {}
|
||||||
/// Fires when `AsyncClient` receives a `RoomEvent::RoomPowerLevels` event.
|
/// Fires when `AsyncClient` receives a `RoomEvent::RoomPowerLevels` event.
|
||||||
async fn on_room_power_levels(&self, _: Arc<RwLock<Room>>, _: &PowerLevelsEvent) {}
|
async fn on_room_power_levels(&self, _: Arc<RwLock<Room>>, _: &PowerLevelsEvent) {}
|
||||||
|
/// Fires when `AsyncClient` receives a `RoomEvent::Tombstone` event.
|
||||||
|
async fn on_room_tombstone(&self, _: Arc<RwLock<Room>>, _: &TombstoneEvent) {}
|
||||||
|
|
||||||
// `RoomEvent`s from `IncomingState`
|
// `RoomEvent`s from `IncomingState`
|
||||||
/// Fires when `AsyncClient` receives a `StateEvent::RoomMember` event.
|
/// Fires when `AsyncClient` receives a `StateEvent::RoomMember` event.
|
||||||
|
@ -167,6 +170,9 @@ mod test {
|
||||||
async fn on_room_power_levels(&self, _: Arc<RwLock<Room>>, _: &PowerLevelsEvent) {
|
async fn on_room_power_levels(&self, _: Arc<RwLock<Room>>, _: &PowerLevelsEvent) {
|
||||||
self.0.lock().await.push("power".to_string())
|
self.0.lock().await.push("power".to_string())
|
||||||
}
|
}
|
||||||
|
async fn on_room_tombstone(&self, _: Arc<RwLock<Room>>, _: &TombstoneEvent) {
|
||||||
|
self.0.lock().await.push("tombstone".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
async fn on_state_member(&self, _: Arc<RwLock<Room>>, _: &MemberEvent) {
|
async fn on_state_member(&self, _: Arc<RwLock<Room>>, _: &MemberEvent) {
|
||||||
self.0.lock().await.push("state member".to_string())
|
self.0.lock().await.push("state member".to_string())
|
||||||
|
|
|
@ -28,6 +28,7 @@ use crate::events::room::{
|
||||||
member::{MemberEvent, MembershipChange},
|
member::{MemberEvent, MembershipChange},
|
||||||
name::NameEvent,
|
name::NameEvent,
|
||||||
power_levels::{NotificationPowerLevels, PowerLevelsEvent, PowerLevelsEventContent},
|
power_levels::{NotificationPowerLevels, PowerLevelsEvent, PowerLevelsEventContent},
|
||||||
|
tombstone::TombstoneEvent,
|
||||||
};
|
};
|
||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
use crate::identifiers::{RoomAliasId, RoomId, UserId};
|
use crate::identifiers::{RoomAliasId, RoomId, UserId};
|
||||||
|
@ -82,8 +83,16 @@ pub struct PowerLevels {
|
||||||
pub notifications: Int,
|
pub notifications: Int,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct Tombstone {
|
||||||
|
/// A server-defined message.
|
||||||
|
body: String,
|
||||||
|
/// The room that is now active.
|
||||||
|
replacement: RoomId,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||||
/// A Matrix rooom.
|
/// A Matrix room.
|
||||||
pub struct Room {
|
pub struct Room {
|
||||||
/// The unique id of the room.
|
/// The unique id of the room.
|
||||||
pub room_id: RoomId,
|
pub room_id: RoomId,
|
||||||
|
@ -106,6 +115,8 @@ pub struct Room {
|
||||||
pub unread_highlight: Option<UInt>,
|
pub unread_highlight: Option<UInt>,
|
||||||
/// Number of unread notifications.
|
/// Number of unread notifications.
|
||||||
pub unread_notifications: Option<UInt>,
|
pub unread_notifications: Option<UInt>,
|
||||||
|
/// The tombstone state of this room.
|
||||||
|
pub tombstone: Option<Tombstone>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoomName {
|
impl RoomName {
|
||||||
|
@ -128,23 +139,50 @@ impl RoomName {
|
||||||
// https://matrix.org/docs/spec/client_server/latest#calculating-the-display-name-for-a-room.
|
// https://matrix.org/docs/spec/client_server/latest#calculating-the-display-name-for-a-room.
|
||||||
// the order in which we check for a name ^^
|
// the order in which we check for a name ^^
|
||||||
if let Some(name) = &self.name {
|
if let Some(name) = &self.name {
|
||||||
name.clone()
|
let name = name.trim();
|
||||||
|
name.to_string()
|
||||||
} else if let Some(alias) = &self.canonical_alias {
|
} else if let Some(alias) = &self.canonical_alias {
|
||||||
alias.alias().to_string()
|
let alias = alias.alias().trim();
|
||||||
} else if !self.aliases.is_empty() {
|
alias.to_string()
|
||||||
self.aliases[0].alias().to_string()
|
} else if !self.aliases.is_empty() && !self.aliases[0].alias().is_empty() {
|
||||||
|
self.aliases[0].alias().trim().to_string()
|
||||||
} else {
|
} else {
|
||||||
let joined = self.joined_member_count.unwrap_or(UInt::max_value());
|
let joined = self.joined_member_count.unwrap_or(UInt::min_value());
|
||||||
let invited = self.invited_member_count.unwrap_or(UInt::max_value());
|
let invited = self.invited_member_count.unwrap_or(UInt::min_value());
|
||||||
let heroes = UInt::new(self.heroes.len() as u64).unwrap();
|
let heroes = UInt::new(self.heroes.len() as u64).unwrap();
|
||||||
let one = UInt::new(1).unwrap();
|
let one = UInt::new(1).unwrap();
|
||||||
|
|
||||||
if heroes >= (joined + invited - one) {
|
let invited_joined = if invited + joined == UInt::min_value() {
|
||||||
let mut names = self.heroes.iter().take(3).cloned().collect::<Vec<String>>();
|
UInt::min_value()
|
||||||
|
} else {
|
||||||
|
invited + joined - one
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO this should use `self.heroes but it is always empty??
|
||||||
|
if heroes >= invited_joined {
|
||||||
|
let mut names = members
|
||||||
|
.values()
|
||||||
|
.take(3)
|
||||||
|
.map(|mem| {
|
||||||
|
mem.display_name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or(mem.user_id.localpart().to_string())
|
||||||
|
.to_string()
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
// stabilize ordering
|
||||||
names.sort();
|
names.sort();
|
||||||
names.join(", ")
|
names.join(", ")
|
||||||
} else if heroes < (joined + invited - one) && invited + joined > one {
|
} else if heroes < invited_joined && invited + joined > one {
|
||||||
let mut names = self.heroes.iter().take(3).cloned().collect::<Vec<String>>();
|
let mut names = members
|
||||||
|
.values()
|
||||||
|
.take(3)
|
||||||
|
.map(|mem| {
|
||||||
|
mem.display_name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or(mem.user_id.localpart().to_string())
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>();
|
||||||
names.sort();
|
names.sort();
|
||||||
// TODO what is the length the spec wants us to use here and in the `else`
|
// TODO what is the length the spec wants us to use here and in the `else`
|
||||||
format!("{}, and {} others", names.join(", "), (joined + invited))
|
format!("{}, and {} others", names.join(", "), (joined + invited))
|
||||||
|
@ -175,6 +213,7 @@ impl Room {
|
||||||
encrypted: false,
|
encrypted: false,
|
||||||
unread_highlight: None,
|
unread_highlight: None,
|
||||||
unread_notifications: None,
|
unread_notifications: None,
|
||||||
|
tombstone: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,7 +255,7 @@ impl Room {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_name_room(&mut self, name: &str) -> bool {
|
fn set_room_name(&mut self, name: &str) -> bool {
|
||||||
self.room_name.set_name(name);
|
self.room_name.set_name(name);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -308,7 +347,7 @@ impl Room {
|
||||||
/// Returns true if the room name changed, false otherwise.
|
/// Returns true if the room name changed, false otherwise.
|
||||||
pub fn handle_room_name(&mut self, event: &NameEvent) -> bool {
|
pub fn handle_room_name(&mut self, event: &NameEvent) -> bool {
|
||||||
match event.content.name() {
|
match event.content.name() {
|
||||||
Some(name) => self.set_name_room(name),
|
Some(name) => self.set_room_name(name),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -335,6 +374,14 @@ impl Room {
|
||||||
updated
|
updated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_tombstone(&mut self, event: &TombstoneEvent) -> bool {
|
||||||
|
self.tombstone = Some(Tombstone {
|
||||||
|
body: event.content.body.clone(),
|
||||||
|
replacement: event.content.replacement_room.clone(),
|
||||||
|
});
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_encryption_event(&mut self, _event: &EncryptionEvent) -> bool {
|
fn handle_encryption_event(&mut self, _event: &EncryptionEvent) -> bool {
|
||||||
self.encrypted = true;
|
self.encrypted = true;
|
||||||
true
|
true
|
||||||
|
@ -357,6 +404,7 @@ impl Room {
|
||||||
RoomEvent::RoomAliases(a) => self.handle_room_aliases(a),
|
RoomEvent::RoomAliases(a) => self.handle_room_aliases(a),
|
||||||
// power levels of the room members
|
// power levels of the room members
|
||||||
RoomEvent::RoomPowerLevels(p) => self.handle_power_level(p),
|
RoomEvent::RoomPowerLevels(p) => self.handle_power_level(p),
|
||||||
|
RoomEvent::RoomTombstone(t) => self.handle_tombstone(t),
|
||||||
RoomEvent::RoomEncryption(e) => self.handle_encryption_event(e),
|
RoomEvent::RoomEncryption(e) => self.handle_encryption_event(e),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
@ -376,6 +424,7 @@ impl Room {
|
||||||
StateEvent::RoomCanonicalAlias(ca) => self.handle_canonical(ca),
|
StateEvent::RoomCanonicalAlias(ca) => self.handle_canonical(ca),
|
||||||
StateEvent::RoomAliases(a) => self.handle_room_aliases(a),
|
StateEvent::RoomAliases(a) => self.handle_room_aliases(a),
|
||||||
StateEvent::RoomPowerLevels(p) => self.handle_power_level(p),
|
StateEvent::RoomPowerLevels(p) => self.handle_power_level(p),
|
||||||
|
StateEvent::RoomTombstone(t) => self.handle_tombstone(t),
|
||||||
StateEvent::RoomEncryption(e) => self.handle_encryption_event(e),
|
StateEvent::RoomEncryption(e) => self.handle_encryption_event(e),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
@ -487,4 +536,74 @@ mod test {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(admin.power_level.unwrap(), js_int::Int::new(100).unwrap());
|
assert_eq!(admin.power_level.unwrap(), js_int::Int::new(100).unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn calculate_aliases() {
|
||||||
|
let rid = RoomId::try_from("!roomid:room.com").unwrap();
|
||||||
|
let uid = UserId::try_from("@example:localhost").unwrap();
|
||||||
|
|
||||||
|
let mut bld = EventBuilder::default()
|
||||||
|
.add_state_event_from_file("./tests/data/events/aliases.json", StateEvent::RoomAliases)
|
||||||
|
.build_room_runner(&rid, &uid);
|
||||||
|
|
||||||
|
let room = bld.to_room();
|
||||||
|
|
||||||
|
assert_eq!("tutorial", room.calculate_name());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn calculate_alias() {
|
||||||
|
let rid = RoomId::try_from("!roomid:room.com").unwrap();
|
||||||
|
let uid = UserId::try_from("@example:localhost").unwrap();
|
||||||
|
|
||||||
|
let mut bld = EventBuilder::default()
|
||||||
|
.add_state_event_from_file(
|
||||||
|
"./tests/data/events/alias.json",
|
||||||
|
StateEvent::RoomCanonicalAlias,
|
||||||
|
)
|
||||||
|
.build_room_runner(&rid, &uid);
|
||||||
|
|
||||||
|
let room = bld.to_room();
|
||||||
|
|
||||||
|
assert_eq!("tutorial", room.calculate_name());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn calculate_name() {
|
||||||
|
let rid = RoomId::try_from("!roomid:room.com").unwrap();
|
||||||
|
let uid = UserId::try_from("@example:localhost").unwrap();
|
||||||
|
|
||||||
|
let mut bld = EventBuilder::default()
|
||||||
|
.add_state_event_from_file("./tests/data/events/name.json", StateEvent::RoomName)
|
||||||
|
.build_room_runner(&rid, &uid);
|
||||||
|
|
||||||
|
let room = bld.to_room();
|
||||||
|
|
||||||
|
assert_eq!("room name", room.calculate_name());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn calculate_room_names_from_summary() {
|
||||||
|
let homeserver = Url::from_str(&mockito::server_url()).unwrap();
|
||||||
|
|
||||||
|
let mut bld = EventBuilder::default().build_with_response(
|
||||||
|
// this sync has no room.name or room.alias events so only relies on summary
|
||||||
|
"tests/data/sync_with_summary.json",
|
||||||
|
"GET",
|
||||||
|
Matcher::Regex(r"^/_matrix/client/r0/sync\?.*$".to_string()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let session = Session {
|
||||||
|
access_token: "1234".to_owned(),
|
||||||
|
user_id: UserId::try_from("@example:localhost").unwrap(),
|
||||||
|
device_id: "DEVICEID".to_owned(),
|
||||||
|
};
|
||||||
|
let client = AsyncClient::new(homeserver, Some(session)).unwrap();
|
||||||
|
let client = bld.set_client(client).to_client().await.unwrap();
|
||||||
|
|
||||||
|
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
||||||
|
let _response = client.sync(sync_settings).await.unwrap();
|
||||||
|
|
||||||
|
assert_eq!(vec!["example, example2"], client.get_room_names().await);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,7 +123,8 @@ mod test {
|
||||||
"power_levels": null,
|
"power_levels": null,
|
||||||
"encrypted": false,
|
"encrypted": false,
|
||||||
"unread_highlight": null,
|
"unread_highlight": null,
|
||||||
"unread_notifications": null
|
"unread_notifications": null,
|
||||||
|
"tombstone": null
|
||||||
}
|
}
|
||||||
}"#,
|
}"#,
|
||||||
serde_json::to_string_pretty(&joined_rooms).unwrap()
|
serde_json::to_string_pretty(&joined_rooms).unwrap()
|
||||||
|
|
|
@ -165,6 +165,33 @@ impl EventBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Consumes `ResponseBuilder and returns a `TestRunner`.
|
||||||
|
///
|
||||||
|
/// The `TestRunner` responds to requests made by the `AsyncClient`.
|
||||||
|
pub fn build_with_response<M, P>(mut self, path: P, method: &str, matcher: M) -> MockTestRunner
|
||||||
|
where
|
||||||
|
M: Into<mockito::Matcher>,
|
||||||
|
P: AsRef<Path>,
|
||||||
|
{
|
||||||
|
let body = fs::read_to_string(path.as_ref())
|
||||||
|
.expect(&format!("file not found {:?}", path.as_ref()));
|
||||||
|
let mock = Some(
|
||||||
|
mock(method, matcher)
|
||||||
|
.with_status(200)
|
||||||
|
.with_body(body)
|
||||||
|
.create(),
|
||||||
|
);
|
||||||
|
MockTestRunner {
|
||||||
|
client: None,
|
||||||
|
ephemeral: Vec::new(),
|
||||||
|
account_data: Vec::new(),
|
||||||
|
room_events: Vec::new(),
|
||||||
|
presence_events: Vec::new(),
|
||||||
|
state_events: Vec::new(),
|
||||||
|
mock,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Consumes `ResponseBuilder and returns a `TestRunner`.
|
/// Consumes `ResponseBuilder and returns a `TestRunner`.
|
||||||
///
|
///
|
||||||
/// The `TestRunner` streams the events to the client and holds methods to make assertions
|
/// The `TestRunner` streams the events to the client and holds methods to make assertions
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"aliases": [
|
||||||
|
"#tutorial:localhost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"event_id": "$15139375516NUgtD:localhost",
|
||||||
|
"origin_server_ts": 1513937551720,
|
||||||
|
"sender": "@example:localhost",
|
||||||
|
"state_key": "localhost",
|
||||||
|
"type": "m.room.aliases",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 7034220174
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"name": "#tutorial:localhost"
|
"name": "room name"
|
||||||
},
|
},
|
||||||
"event_id": "$15139375513VdeRF:localhost",
|
"event_id": "$15139375513VdeRF:localhost",
|
||||||
"origin_server_ts": 1513937551461,
|
"origin_server_ts": 1513937551461,
|
||||||
|
|
|
@ -0,0 +1,256 @@
|
||||||
|
{
|
||||||
|
"device_one_time_keys_count": {},
|
||||||
|
"next_batch": "s526_47314_0_7_1_1_1_11444_1",
|
||||||
|
"device_lists": {
|
||||||
|
"changed": [
|
||||||
|
"@example:example.org"
|
||||||
|
],
|
||||||
|
"left": []
|
||||||
|
},
|
||||||
|
"rooms": {
|
||||||
|
"invite": {},
|
||||||
|
"join": {
|
||||||
|
"!SVkFJHzfwvuaIEawgC:localhost": {
|
||||||
|
"summary": {
|
||||||
|
"m.heroes": [
|
||||||
|
"@alice:example.com",
|
||||||
|
"@bob:example.com"
|
||||||
|
],
|
||||||
|
"m.joined_member_count": 2,
|
||||||
|
"m.invited_member_count": 0
|
||||||
|
},
|
||||||
|
"account_data": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"event_id": "$someplace:example.org"
|
||||||
|
},
|
||||||
|
"room_id": "!roomid:room.com",
|
||||||
|
"type": "m.fully_read"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"ignored_users": {
|
||||||
|
"@someone:example.org": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "m.ignored_user_list"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ephemeral": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"$151680659217152dPKjd:localhost": {
|
||||||
|
"m.read": {
|
||||||
|
"@example:localhost": {
|
||||||
|
"ts": 1516809890615
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "m.receipt"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"join_rule": "public"
|
||||||
|
},
|
||||||
|
"event_id": "$15139375514WsgmR:localhost",
|
||||||
|
"origin_server_ts": 1513937551539,
|
||||||
|
"sender": "@example:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"type": "m.room.join_rules",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 7034220355
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"avatar_url": null,
|
||||||
|
"displayname": "example",
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "$151800140517rfvjc:localhost",
|
||||||
|
"membership": "join",
|
||||||
|
"origin_server_ts": 1518001405556,
|
||||||
|
"sender": "@example:localhost",
|
||||||
|
"state_key": "@example:localhost",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 2970366338,
|
||||||
|
"replaces_state": "$151800111315tsynI:localhost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"history_visibility": "shared"
|
||||||
|
},
|
||||||
|
"event_id": "$15139375515VaJEY:localhost",
|
||||||
|
"origin_server_ts": 1513937551613,
|
||||||
|
"sender": "@example:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"type": "m.room.history_visibility",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 7034220281
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"creator": "@example:localhost"
|
||||||
|
},
|
||||||
|
"event_id": "$15139375510KUZHi:localhost",
|
||||||
|
"origin_server_ts": 1513937551203,
|
||||||
|
"sender": "@example:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"type": "m.room.create",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 7034220691
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"topic": "\ud83d\ude00"
|
||||||
|
},
|
||||||
|
"event_id": "$151957878228ssqrJ:localhost",
|
||||||
|
"origin_server_ts": 1519578782185,
|
||||||
|
"sender": "@example:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"type": "m.room.topic",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1392989709,
|
||||||
|
"prev_content": {
|
||||||
|
"topic": "test"
|
||||||
|
},
|
||||||
|
"prev_sender": "@example:localhost",
|
||||||
|
"replaces_state": "$151957069225EVYKm:localhost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"ban": 50,
|
||||||
|
"events": {
|
||||||
|
"m.room.avatar": 50,
|
||||||
|
"m.room.canonical_alias": 50,
|
||||||
|
"m.room.history_visibility": 100,
|
||||||
|
"m.room.name": 50,
|
||||||
|
"m.room.power_levels": 100
|
||||||
|
},
|
||||||
|
"events_default": 0,
|
||||||
|
"invite": 0,
|
||||||
|
"kick": 50,
|
||||||
|
"redact": 50,
|
||||||
|
"state_default": 50,
|
||||||
|
"users": {
|
||||||
|
"@example:localhost": 100
|
||||||
|
},
|
||||||
|
"users_default": 0
|
||||||
|
},
|
||||||
|
"event_id": "$15139375512JaHAW:localhost",
|
||||||
|
"origin_server_ts": 1513937551359,
|
||||||
|
"sender": "@example:localhost",
|
||||||
|
"state_key": "",
|
||||||
|
"type": "m.room.power_levels",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 7034220535
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"avatar_url": null,
|
||||||
|
"displayname": "example2",
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "$152034824468gOeNB:localhost",
|
||||||
|
"membership": "join",
|
||||||
|
"origin_server_ts": 1520348244605,
|
||||||
|
"sender": "@example2:localhost",
|
||||||
|
"state_key": "@example2:localhost",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 623527289,
|
||||||
|
"prev_content": {
|
||||||
|
"membership": "leave"
|
||||||
|
},
|
||||||
|
"prev_sender": "@example:localhost",
|
||||||
|
"replaces_state": "$152034819067QWJxM:localhost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"membership": "leave",
|
||||||
|
"reason": "offline",
|
||||||
|
"avatar_url": "avatar.com",
|
||||||
|
"displayname": "example"
|
||||||
|
},
|
||||||
|
"event_id": "$1585345508297748AIUBh:matrix.org",
|
||||||
|
"origin_server_ts": 1585345508223,
|
||||||
|
"sender": "@example:localhost",
|
||||||
|
"state_key": "@example:localhost",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"unsigned": {
|
||||||
|
"replaces_state": "$1585345354296486IGZfp:localhost",
|
||||||
|
"prev_content": {
|
||||||
|
"avatar_url": "avatar.com",
|
||||||
|
"displayname": "example",
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"prev_sender": "@example2:localhost",
|
||||||
|
"age": 6992
|
||||||
|
},
|
||||||
|
"room_id": "!roomid:room.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "baba",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "<strong>baba</strong>",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$152037280074GZeOm:localhost",
|
||||||
|
"origin_server_ts": 1520372800469,
|
||||||
|
"sender": "@example:localhost",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 598971425
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limited": true,
|
||||||
|
"prev_batch": "t392-516_47314_0_7_1_1_1_11444_1"
|
||||||
|
},
|
||||||
|
"unread_notifications": {
|
||||||
|
"highlight_count": 0,
|
||||||
|
"notification_count": 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"leave": {}
|
||||||
|
},
|
||||||
|
"to_device": {
|
||||||
|
"events": []
|
||||||
|
},
|
||||||
|
"presence": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"avatar_url": "mxc://localhost:wefuiwegh8742w",
|
||||||
|
"currently_active": false,
|
||||||
|
"last_active_ago": 1,
|
||||||
|
"presence": "online",
|
||||||
|
"status_msg": "Making cupcakes"
|
||||||
|
},
|
||||||
|
"sender": "@example:localhost",
|
||||||
|
"type": "m.presence"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue