mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-14 19:16:02 -04:00
Merge branch 'main' into ganfra/kotlin_binding_scripts
This commit is contained in:
@@ -13,7 +13,7 @@ use matrix_sdk::ruma::{
|
||||
v4::RoomSubscription as RumaRoomSubscription,
|
||||
UnreadNotificationsCount as RumaUnreadNotificationsCount,
|
||||
},
|
||||
assign, IdParseError, OwnedRoomId, UInt,
|
||||
assign, IdParseError, OwnedRoomId, RoomId, UInt,
|
||||
};
|
||||
pub use matrix_sdk::{
|
||||
room::timeline::Timeline, ruma::api::client::sync::sync_events::v4::SyncRequestListFilters,
|
||||
@@ -642,7 +642,8 @@ impl SlidingSync {
|
||||
|
||||
pub fn get_room(&self, room_id: String) -> anyhow::Result<Option<Arc<SlidingSyncRoom>>> {
|
||||
let runner = self.inner.clone();
|
||||
Ok(self.inner.get_room(OwnedRoomId::try_from(room_id)?).map(|inner| {
|
||||
|
||||
Ok(self.inner.get_room(<&RoomId>::try_from(room_id.as_str())?).map(|inner| {
|
||||
Arc::new(SlidingSyncRoom {
|
||||
inner,
|
||||
runner,
|
||||
|
||||
@@ -463,7 +463,7 @@ impl Message {
|
||||
|
||||
// This event ID string will be replaced by something more useful later.
|
||||
pub fn in_reply_to(&self) -> Option<String> {
|
||||
self.0.in_reply_to().map(ToString::to_string)
|
||||
self.0.in_reply_to().map(|r| r.event_id.to_string())
|
||||
}
|
||||
|
||||
pub fn is_edited(&self) -> bool {
|
||||
|
||||
@@ -241,6 +241,11 @@ pub enum Error {
|
||||
#[error(transparent)]
|
||||
SlidingSync(#[from] crate::sliding_sync::Error),
|
||||
|
||||
/// An error occurred in the timeline.
|
||||
#[cfg(feature = "experimental-timeline")]
|
||||
#[error(transparent)]
|
||||
Timeline(#[from] crate::room::timeline::Error),
|
||||
|
||||
/// The client is in inconsistent state. This happens when we set a room to
|
||||
/// a specific type, but then cannot get it in this type.
|
||||
#[error("The internal client state is inconsistent.")]
|
||||
|
||||
@@ -46,8 +46,9 @@ use super::{
|
||||
MemberProfileChange, OtherState, Profile, RemoteEventTimelineItem, RoomMembershipChange,
|
||||
Sticker,
|
||||
},
|
||||
find_read_marker, rfind_event_by_id, rfind_event_item, EventTimelineItem, Message,
|
||||
ReactionGroup, TimelineInnerMetadata, TimelineItem, TimelineItemContent, VirtualTimelineItem,
|
||||
find_read_marker, rfind_event_by_id, rfind_event_item, EventTimelineItem, InReplyToDetails,
|
||||
Message, ReactionGroup, TimelineInnerMetadata, TimelineItem, TimelineItemContent,
|
||||
VirtualTimelineItem,
|
||||
};
|
||||
use crate::{events::SyncTimelineEventWithoutContent, room::timeline::MembershipChange};
|
||||
|
||||
@@ -821,10 +822,7 @@ impl NewEventTimelineItem {
|
||||
let edited = relations.replace.is_some();
|
||||
let content = TimelineItemContent::Message(Message {
|
||||
msgtype: c.msgtype,
|
||||
in_reply_to: c.relates_to.and_then(|rel| match rel {
|
||||
message::Relation::Reply { in_reply_to } => Some(in_reply_to.event_id),
|
||||
_ => None,
|
||||
}),
|
||||
in_reply_to: c.relates_to.and_then(InReplyToDetails::from_relation),
|
||||
edited,
|
||||
});
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
use std::{fmt, ops::Deref, sync::Arc};
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use matrix_sdk_base::deserialized_responses::EncryptionInfo;
|
||||
use matrix_sdk_base::deserialized_responses::{EncryptionInfo, TimelineEvent};
|
||||
use ruma::{
|
||||
events::{
|
||||
policy::rule::{
|
||||
@@ -33,7 +33,7 @@ use ruma::{
|
||||
history_visibility::RoomHistoryVisibilityEventContent,
|
||||
join_rules::RoomJoinRulesEventContent,
|
||||
member::{Change, RoomMemberEventContent},
|
||||
message::MessageType,
|
||||
message::{self, MessageType, Relation},
|
||||
name::RoomNameEventContent,
|
||||
pinned_events::RoomPinnedEventsEventContent,
|
||||
power_levels::RoomPowerLevelsEventContent,
|
||||
@@ -44,15 +44,16 @@ use ruma::{
|
||||
},
|
||||
space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
|
||||
sticker::StickerEventContent,
|
||||
AnyFullStateEventContent, AnySyncTimelineEvent, FullStateEventContent,
|
||||
MessageLikeEventType, StateEventType,
|
||||
AnyFullStateEventContent, AnyMessageLikeEventContent, AnySyncTimelineEvent,
|
||||
AnyTimelineEvent, FullStateEventContent, MessageLikeEventType, StateEventType,
|
||||
},
|
||||
serde::Raw,
|
||||
EventId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedEventId, OwnedMxcUri,
|
||||
OwnedTransactionId, OwnedUserId, TransactionId, UserId,
|
||||
};
|
||||
|
||||
use crate::Error;
|
||||
use super::inner::ProfileProvider;
|
||||
use crate::{Error, Result};
|
||||
|
||||
/// An item in the timeline that represents at least one event.
|
||||
///
|
||||
@@ -295,6 +296,11 @@ impl RemoteEventTimelineItem {
|
||||
Self { reactions, ..self.clone() }
|
||||
}
|
||||
|
||||
/// Clone the current event item, and update its `content`.
|
||||
pub(super) fn with_content(&self, content: TimelineItemContent) -> Self {
|
||||
Self { content, ..self.clone() }
|
||||
}
|
||||
|
||||
/// Clone the current event item, change its `content` to
|
||||
/// [`TimelineItemContent::RedactedMessage`], and reset its `reactions`.
|
||||
pub(super) fn to_redacted(&self) -> Self {
|
||||
@@ -364,6 +370,9 @@ pub enum TimelineDetails<T> {
|
||||
|
||||
/// The details are available.
|
||||
Ready(T),
|
||||
|
||||
/// An error occurred when fetching the details.
|
||||
Error(Arc<Error>),
|
||||
}
|
||||
|
||||
/// The content of an [`EventTimelineItem`].
|
||||
@@ -436,10 +445,7 @@ impl TimelineItemContent {
|
||||
#[derive(Clone)]
|
||||
pub struct Message {
|
||||
pub(super) msgtype: MessageType,
|
||||
// TODO: Add everything required to display the replied-to event, plus a
|
||||
// 'loading' state that is entered at first, until the user requests the
|
||||
// reply to be loaded.
|
||||
pub(super) in_reply_to: Option<OwnedEventId>,
|
||||
pub(super) in_reply_to: Option<InReplyToDetails>,
|
||||
pub(super) edited: bool,
|
||||
}
|
||||
|
||||
@@ -456,15 +462,19 @@ impl Message {
|
||||
self.msgtype.body()
|
||||
}
|
||||
|
||||
/// Get the event ID of the event this message is replying to, if any.
|
||||
pub fn in_reply_to(&self) -> Option<&EventId> {
|
||||
self.in_reply_to.as_deref()
|
||||
/// Get the event this message is replying to, if any.
|
||||
pub fn in_reply_to(&self) -> Option<&InReplyToDetails> {
|
||||
self.in_reply_to.as_ref()
|
||||
}
|
||||
|
||||
/// Get the edit state of this message (has been edited: `true` / `false`).
|
||||
pub fn is_edited(&self) -> bool {
|
||||
self.edited
|
||||
}
|
||||
|
||||
pub(super) fn with_in_reply_to(&self, in_reply_to: InReplyToDetails) -> Self {
|
||||
Self { in_reply_to: Some(in_reply_to), ..self.clone() }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Message {
|
||||
@@ -475,6 +485,85 @@ impl fmt::Debug for Message {
|
||||
}
|
||||
}
|
||||
|
||||
/// Details about an event being replied to.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct InReplyToDetails {
|
||||
/// The ID of the event.
|
||||
pub event_id: OwnedEventId,
|
||||
|
||||
/// The details of the event.
|
||||
///
|
||||
/// Use [`Timeline::fetch_item_details`] to fetch the data if it is
|
||||
/// unavailable. The `replies_nesting_level` field in
|
||||
/// [`TimelineDetailsSettings`] decides if this should be fetched.
|
||||
///
|
||||
/// [`Timeline::fetch_item_details`]: super::Timeline::fetch_item_details
|
||||
/// [`TimelineDetailsSettings`]: super::TimelineDetailsSettings
|
||||
pub details: TimelineDetails<Box<RepliedToEvent>>,
|
||||
}
|
||||
|
||||
impl InReplyToDetails {
|
||||
pub(super) fn from_relation<C>(relation: Relation<C>) -> Option<Self> {
|
||||
match relation {
|
||||
message::Relation::Reply { in_reply_to } => {
|
||||
Some(Self { event_id: in_reply_to.event_id, details: TimelineDetails::Unavailable })
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An event that is replied to.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RepliedToEvent {
|
||||
pub(super) message: Message,
|
||||
pub(super) sender: OwnedUserId,
|
||||
pub(super) sender_profile: Profile,
|
||||
}
|
||||
|
||||
impl RepliedToEvent {
|
||||
/// Get the message of this event.
|
||||
pub fn message(&self) -> &Message {
|
||||
&self.message
|
||||
}
|
||||
|
||||
/// Get the sender of this event.
|
||||
pub fn sender(&self) -> &UserId {
|
||||
&self.sender
|
||||
}
|
||||
|
||||
/// Get the profile of the sender.
|
||||
pub fn sender_profile(&self) -> &Profile {
|
||||
&self.sender_profile
|
||||
}
|
||||
|
||||
pub(super) async fn try_from_timeline_event<P: ProfileProvider>(
|
||||
timeline_event: TimelineEvent,
|
||||
profile_provider: &P,
|
||||
) -> Result<Self> {
|
||||
let event = match timeline_event.event.deserialize() {
|
||||
Ok(AnyTimelineEvent::MessageLike(event)) => event,
|
||||
_ => {
|
||||
return Err(super::Error::UnsupportedEvent.into());
|
||||
}
|
||||
};
|
||||
|
||||
let Some(AnyMessageLikeEventContent::RoomMessage(c)) = event.original_content() else {
|
||||
return Err(super::Error::UnsupportedEvent.into());
|
||||
};
|
||||
|
||||
let message = Message {
|
||||
msgtype: c.msgtype,
|
||||
in_reply_to: c.relates_to.and_then(InReplyToDetails::from_relation),
|
||||
edited: event.relations().replace.is_some(),
|
||||
};
|
||||
let sender = event.sender().to_owned();
|
||||
let sender_profile = profile_provider.profile(&sender).await;
|
||||
|
||||
Ok(Self { message, sender, sender_profile })
|
||||
}
|
||||
}
|
||||
|
||||
/// Metadata about an `m.room.encrypted` event that could not be decrypted.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum EncryptedMessage {
|
||||
|
||||
@@ -31,11 +31,13 @@ use super::{
|
||||
update_read_marker, Flow, HandleEventResult, TimelineEventHandler, TimelineEventKind,
|
||||
TimelineEventMetadata, TimelineItemPosition,
|
||||
},
|
||||
rfind_event_item, EventSendState, EventTimelineItem, Profile, TimelineItem,
|
||||
rfind_event_by_id, rfind_event_item, EventSendState, EventTimelineItem, InReplyToDetails,
|
||||
Message, Profile, RepliedToEvent, TimelineDetails, TimelineItem, TimelineItemContent,
|
||||
};
|
||||
use crate::{
|
||||
events::SyncTimelineEventWithoutContent,
|
||||
room::{self, timeline::event_item::RemoteEventTimelineItem},
|
||||
Result,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -368,12 +370,93 @@ impl<P: ProfileProvider> TimelineInner<P> {
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
fn update_event_item(&self, index: usize, event_item: EventTimelineItem) {
|
||||
self.items.lock_mut().set_cloned(index, Arc::new(TimelineItem::Event(event_item)))
|
||||
}
|
||||
}
|
||||
|
||||
impl TimelineInner {
|
||||
pub(super) fn room(&self) -> &room::Common {
|
||||
&self.profile_provider
|
||||
}
|
||||
|
||||
pub(super) async fn fetch_in_reply_to_details(
|
||||
&self,
|
||||
index: usize,
|
||||
mut item: RemoteEventTimelineItem,
|
||||
) -> Result<RemoteEventTimelineItem> {
|
||||
let TimelineItemContent::Message(message) = item.content.clone() else {
|
||||
return Ok(item);
|
||||
};
|
||||
let Some(in_reply_to) = message.in_reply_to() else {
|
||||
return Ok(item);
|
||||
};
|
||||
|
||||
let details =
|
||||
self.fetch_replied_to_event(index, &item, &message, &in_reply_to.event_id).await;
|
||||
|
||||
// We need to be sure to have the latest position of the event as it might have
|
||||
// changed while waiting for the request.
|
||||
let (index, _) = rfind_event_by_id(&self.items(), &item.event_id)
|
||||
.ok_or(super::Error::RemoteEventNotInTimeline)?;
|
||||
|
||||
item = item.with_content(TimelineItemContent::Message(message.with_in_reply_to(
|
||||
InReplyToDetails { event_id: in_reply_to.event_id.clone(), details },
|
||||
)));
|
||||
self.update_event_item(index, item.clone().into());
|
||||
|
||||
Ok(item)
|
||||
}
|
||||
|
||||
async fn fetch_replied_to_event(
|
||||
&self,
|
||||
index: usize,
|
||||
item: &RemoteEventTimelineItem,
|
||||
message: &Message,
|
||||
in_reply_to: &EventId,
|
||||
) -> TimelineDetails<Box<RepliedToEvent>> {
|
||||
if let Some((_, item)) = rfind_event_by_id(&self.items(), in_reply_to) {
|
||||
let details = match item.content() {
|
||||
TimelineItemContent::Message(message) => {
|
||||
TimelineDetails::Ready(Box::new(RepliedToEvent {
|
||||
message: message.clone(),
|
||||
sender: item.sender().to_owned(),
|
||||
sender_profile: item.sender_profile().clone(),
|
||||
}))
|
||||
}
|
||||
_ => TimelineDetails::Error(Arc::new(super::Error::UnsupportedEvent.into())),
|
||||
};
|
||||
|
||||
return details;
|
||||
};
|
||||
|
||||
self.update_event_item(
|
||||
index,
|
||||
item.with_content(TimelineItemContent::Message(message.with_in_reply_to(
|
||||
InReplyToDetails {
|
||||
event_id: in_reply_to.to_owned(),
|
||||
details: TimelineDetails::Pending,
|
||||
},
|
||||
)))
|
||||
.into(),
|
||||
);
|
||||
|
||||
match self.room().event(in_reply_to).await {
|
||||
Ok(timeline_event) => {
|
||||
match RepliedToEvent::try_from_timeline_event(
|
||||
timeline_event,
|
||||
&self.profile_provider,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(event) => TimelineDetails::Ready(Box::new(event)),
|
||||
Err(e) => TimelineDetails::Error(Arc::new(e)),
|
||||
}
|
||||
}
|
||||
Err(e) => TimelineDetails::Error(Arc::new(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -28,6 +28,7 @@ use ruma::{
|
||||
events::{fully_read::FullyReadEventContent, AnyMessageLikeEventContent},
|
||||
EventId, MilliSecondsSinceUnixEpoch, TransactionId,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use tracing::{error, instrument, warn};
|
||||
|
||||
use super::Joined;
|
||||
@@ -50,8 +51,9 @@ mod virtual_item;
|
||||
pub use self::{
|
||||
event_item::{
|
||||
AnyOtherFullStateEventContent, BundledReactions, EncryptedMessage, EventSendState,
|
||||
EventTimelineItem, MemberProfileChange, MembershipChange, Message, OtherState, Profile,
|
||||
ReactionGroup, RoomMembershipChange, Sticker, TimelineDetails, TimelineItemContent,
|
||||
EventTimelineItem, InReplyToDetails, MemberProfileChange, MembershipChange, Message,
|
||||
OtherState, Profile, ReactionGroup, RepliedToEvent, RoomMembershipChange, Sticker,
|
||||
TimelineDetails, TimelineItemContent,
|
||||
},
|
||||
pagination::{PaginationOptions, PaginationOutcome},
|
||||
virtual_item::VirtualTimelineItem,
|
||||
@@ -389,6 +391,36 @@ impl Timeline {
|
||||
};
|
||||
self.inner.update_event_send_state(&txn_id, send_state);
|
||||
}
|
||||
|
||||
/// Fetch unavailable details about the event with the given ID.
|
||||
///
|
||||
/// This method only works for IDs of [`RemoteEventTimelineItem`]s, to
|
||||
/// prevent losing details when a local echo is replaced by its remote
|
||||
/// echo.
|
||||
///
|
||||
/// This method tries to make all the requests it can. If an error is
|
||||
/// encountered for a given request, it is forwarded with the
|
||||
/// [`TimelineDetails::Error`] variant.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `event_id` - The event ID of the event to fetch details for.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the identifier doesn't match any event with a remote
|
||||
/// echo in the timeline, or if the event is removed from the timeline
|
||||
/// before all requests are handled.
|
||||
#[instrument(skip(self), fields(room_id = ?self.room().room_id()))]
|
||||
pub async fn fetch_event_details(&self, event_id: &EventId) -> Result<()> {
|
||||
let (index, item) = rfind_event_by_id(&self.inner.items(), event_id)
|
||||
.and_then(|(pos, item)| item.as_remote().map(|item| (pos, item.clone())))
|
||||
.ok_or(Error::RemoteEventNotInTimeline)?;
|
||||
|
||||
self.inner.fetch_in_reply_to_details(index, item).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// A single entry in timeline.
|
||||
@@ -473,3 +505,16 @@ fn rfind_event_by_id<'a>(
|
||||
fn find_read_marker(items: &[Arc<TimelineItem>]) -> Option<usize> {
|
||||
items.iter().rposition(|item| item.is_read_marker())
|
||||
}
|
||||
|
||||
/// Errors specific to the timeline.
|
||||
#[derive(Error, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
/// The requested event with a remote echo is not in the timeline.
|
||||
#[error("Event with remote echo not found in timeline")]
|
||||
RemoteEventNotInTimeline,
|
||||
|
||||
/// The event is currently unsupported for this use case.
|
||||
#[error("Unsupported event")]
|
||||
UnsupportedEvent,
|
||||
}
|
||||
|
||||
@@ -814,8 +814,8 @@ impl SlidingSync {
|
||||
}
|
||||
|
||||
/// Lookup a specific room
|
||||
pub fn get_room(&self, room_id: OwnedRoomId) -> Option<SlidingSyncRoom> {
|
||||
self.rooms.lock_ref().get(&room_id).cloned()
|
||||
pub fn get_room(&self, room_id: &RoomId) -> Option<SlidingSyncRoom> {
|
||||
self.rooms.lock_ref().get(room_id).cloned()
|
||||
}
|
||||
|
||||
fn update_to_device_since(&self, since: String) {
|
||||
|
||||
@@ -8,10 +8,11 @@ use futures_util::StreamExt;
|
||||
use matrix_sdk::{
|
||||
config::SyncSettings,
|
||||
room::timeline::{
|
||||
AnyOtherFullStateEventContent, EventSendState, PaginationOptions, TimelineItemContent,
|
||||
VirtualTimelineItem,
|
||||
AnyOtherFullStateEventContent, Error as TimelineError, EventSendState, PaginationOptions,
|
||||
TimelineDetails, TimelineItemContent, VirtualTimelineItem,
|
||||
},
|
||||
ruma::MilliSecondsSinceUnixEpoch,
|
||||
Error,
|
||||
};
|
||||
use matrix_sdk_common::executor::spawn;
|
||||
use matrix_sdk_test::{
|
||||
@@ -553,3 +554,163 @@ async fn read_marker() {
|
||||
assert_matches!(timeline_stream.next().await, Some(VecDiff::Push { value }) => value);
|
||||
assert_matches!(marker.as_virtual().unwrap(), VirtualTimelineItem::ReadMarker);
|
||||
}
|
||||
|
||||
#[async_test]
|
||||
async fn in_reply_to_details() {
|
||||
let room_id = room_id!("!a98sd12bjh:example.org");
|
||||
let (client, server) = logged_in_client().await;
|
||||
let sync_settings = SyncSettings::new().timeout(Duration::from_millis(3000));
|
||||
|
||||
let mut ev_builder = EventBuilder::new();
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id));
|
||||
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
|
||||
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
let room = client.get_room(room_id).unwrap();
|
||||
let timeline = room.timeline().await;
|
||||
let mut timeline_stream = timeline.signal().to_stream();
|
||||
|
||||
// The event doesn't exist.
|
||||
assert_matches!(
|
||||
timeline.fetch_event_details(event_id!("$fakeevent")).await,
|
||||
Err(Error::Timeline(TimelineError::RemoteEventNotInTimeline))
|
||||
);
|
||||
|
||||
ev_builder.add_joined_room(
|
||||
JoinedRoomBuilder::new(room_id)
|
||||
.add_timeline_event(TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"body": "hello",
|
||||
"msgtype": "m.text",
|
||||
},
|
||||
"event_id": "$event1",
|
||||
"origin_server_ts": 152037280,
|
||||
"sender": "@alice:example.org",
|
||||
"type": "m.room.message",
|
||||
})))
|
||||
.add_timeline_event(TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"body": "hello to you too",
|
||||
"msgtype": "m.text",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$event1",
|
||||
},
|
||||
},
|
||||
},
|
||||
"event_id": "$event2",
|
||||
"origin_server_ts": 152045456,
|
||||
"sender": "@bob:example.org",
|
||||
"type": "m.room.message",
|
||||
}))),
|
||||
);
|
||||
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
|
||||
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
let _day_divider =
|
||||
assert_matches!(timeline_stream.next().await, Some(VecDiff::Push { value }) => value);
|
||||
let first =
|
||||
assert_matches!(timeline_stream.next().await, Some(VecDiff::Push { value }) => value);
|
||||
assert_matches!(first.as_event().unwrap().content(), TimelineItemContent::Message(_));
|
||||
let second =
|
||||
assert_matches!(timeline_stream.next().await, Some(VecDiff::Push { value }) => value);
|
||||
let second_event = second.as_event().unwrap().as_remote().unwrap();
|
||||
let message =
|
||||
assert_matches!(&second_event.content, TimelineItemContent::Message(message) => message);
|
||||
let in_reply_to = message.in_reply_to().unwrap();
|
||||
assert_eq!(in_reply_to.event_id, event_id!("$event1"));
|
||||
assert_matches!(in_reply_to.details, TimelineDetails::Unavailable);
|
||||
|
||||
// Fetch details locally first.
|
||||
timeline.fetch_event_details(&second_event.event_id).await.unwrap();
|
||||
|
||||
let second = assert_matches!(timeline_stream.next().await, Some(VecDiff::UpdateAt { index: 2, value }) => value);
|
||||
let message = assert_matches!(second.as_event().unwrap().content(), TimelineItemContent::Message(message) => message);
|
||||
assert_matches!(message.in_reply_to().unwrap().details, TimelineDetails::Ready(_));
|
||||
|
||||
ev_builder.add_joined_room(JoinedRoomBuilder::new(room_id).add_timeline_event(
|
||||
TimelineTestEvent::Custom(json!({
|
||||
"content": {
|
||||
"body": "you were right",
|
||||
"msgtype": "m.text",
|
||||
"m.relates_to": {
|
||||
"m.in_reply_to": {
|
||||
"event_id": "$remoteevent",
|
||||
},
|
||||
},
|
||||
},
|
||||
"event_id": "$event3",
|
||||
"origin_server_ts": 152046694,
|
||||
"sender": "@bob:example.org",
|
||||
"type": "m.room.message",
|
||||
})),
|
||||
));
|
||||
|
||||
mock_sync(&server, ev_builder.build_json_sync_response(), None).await;
|
||||
let _response = client.sync_once(sync_settings.clone()).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
let third =
|
||||
assert_matches!(timeline_stream.next().await, Some(VecDiff::Push { value }) => value);
|
||||
let third_event = third.as_event().unwrap().as_remote().unwrap();
|
||||
let message =
|
||||
assert_matches!(&third_event.content, TimelineItemContent::Message(message) => message);
|
||||
let in_reply_to = message.in_reply_to().unwrap();
|
||||
assert_eq!(in_reply_to.event_id, event_id!("$remoteevent"));
|
||||
assert_matches!(in_reply_to.details, TimelineDetails::Unavailable);
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/event/\$remoteevent"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(404).set_body_json(json!({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Event not found.",
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
// Fetch details remotely if we can't find them locally.
|
||||
timeline.fetch_event_details(&third_event.event_id).await.unwrap();
|
||||
server.reset().await;
|
||||
|
||||
let third = assert_matches!(timeline_stream.next().await, Some(VecDiff::UpdateAt { index: 3, value }) => value);
|
||||
let message = assert_matches!(third.as_event().unwrap().content(), TimelineItemContent::Message(message) => message);
|
||||
assert_matches!(message.in_reply_to().unwrap().details, TimelineDetails::Pending);
|
||||
|
||||
let third = assert_matches!(timeline_stream.next().await, Some(VecDiff::UpdateAt { index: 3, value }) => value);
|
||||
let message = assert_matches!(third.as_event().unwrap().content(), TimelineItemContent::Message(message) => message);
|
||||
assert_matches!(message.in_reply_to().unwrap().details, TimelineDetails::Error(_));
|
||||
|
||||
Mock::given(method("GET"))
|
||||
.and(path_regex(r"^/_matrix/client/r0/rooms/.*/event/\$remoteevent"))
|
||||
.and(header("authorization", "Bearer 1234"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"content": {
|
||||
"body": "Alice is gonna arrive soon",
|
||||
"msgtype": "m.text",
|
||||
},
|
||||
"room_id": room_id,
|
||||
"event_id": "$event0",
|
||||
"origin_server_ts": 152024004,
|
||||
"sender": "@admin:example.org",
|
||||
"type": "m.room.message",
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
|
||||
timeline.fetch_event_details(&third_event.event_id).await.unwrap();
|
||||
|
||||
let third = assert_matches!(timeline_stream.next().await, Some(VecDiff::UpdateAt { index: 3, value }) => value);
|
||||
let message = assert_matches!(third.as_event().unwrap().content(), TimelineItemContent::Message(message) => message);
|
||||
assert_matches!(message.in_reply_to().unwrap().details, TimelineDetails::Pending);
|
||||
|
||||
let third = assert_matches!(timeline_stream.next().await, Some(VecDiff::UpdateAt { index: 3, value }) => value);
|
||||
let message = assert_matches!(third.as_event().unwrap().content(), TimelineItemContent::Message(message) => message);
|
||||
assert_matches!(message.in_reply_to().unwrap().details, TimelineDetails::Ready(_));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user