use async_trait::async_trait;
use indexmap::IndexMap;
#[cfg(feature = "e2e-encryption")]
use matrix_sdk::{deserialized_responses::TimelineEvent, Result};
use matrix_sdk::{event_cache::paginator::PaginableRoom, Room};
use matrix_sdk_base::latest_event::LatestEvent;
#[cfg(feature = "e2e-encryption")]
use ruma::{events::AnySyncTimelineEvent, serde::Raw};
use ruma::{
events::{
fully_read::FullyReadEventContent,
receipt::{Receipt, ReceiptThread, ReceiptType},
},
push::{PushConditionRoomCtx, Ruleset},
EventId, OwnedEventId, OwnedUserId, RoomVersionId, UserId,
};
use tracing::{debug, error};
use super::{Profile, TimelineBuilder};
use crate::timeline::{self, Timeline};
#[async_trait]
pub trait RoomExt {
async fn timeline(&self) -> Result<Timeline, timeline::Error>;
fn timeline_builder(&self) -> TimelineBuilder;
}
#[async_trait]
impl RoomExt for Room {
async fn timeline(&self) -> Result<Timeline, timeline::Error> {
self.timeline_builder().build().await
}
fn timeline_builder(&self) -> TimelineBuilder {
Timeline::builder(self).track_read_marker_and_receipts()
}
}
#[async_trait]
pub(super) trait RoomDataProvider: Clone + Send + Sync + 'static + PaginableRoom {
fn own_user_id(&self) -> &UserId;
fn room_version(&self) -> RoomVersionId;
async fn profile_from_user_id(&self, user_id: &UserId) -> Option<Profile>;
async fn profile_from_latest_event(&self, latest_event: &LatestEvent) -> Option<Profile>;
async fn load_user_receipt(
&self,
receipt_type: ReceiptType,
thread: ReceiptThread,
user_id: &UserId,
) -> Option<(OwnedEventId, Receipt)>;
async fn load_event_receipts(&self, event_id: &EventId) -> IndexMap<OwnedUserId, Receipt>;
async fn load_fully_read_marker(&self) -> Option<OwnedEventId>;
async fn push_rules_and_context(&self) -> Option<(Ruleset, PushConditionRoomCtx)>;
}
#[async_trait]
impl RoomDataProvider for Room {
fn own_user_id(&self) -> &UserId {
(**self).own_user_id()
}
fn room_version(&self) -> RoomVersionId {
(**self).clone_info().room_version_or_default()
}
async fn profile_from_user_id(&self, user_id: &UserId) -> Option<Profile> {
match self.get_member_no_sync(user_id).await {
Ok(Some(member)) => Some(Profile {
display_name: member.display_name().map(ToOwned::to_owned),
display_name_ambiguous: member.name_ambiguous(),
avatar_url: member.avatar_url().map(ToOwned::to_owned),
}),
Ok(None) if self.are_members_synced() => Some(Profile::default()),
Ok(None) => None,
Err(e) => {
error!(%user_id, "Failed to fetch room member information: {e}");
None
}
}
}
async fn profile_from_latest_event(&self, latest_event: &LatestEvent) -> Option<Profile> {
if !latest_event.has_sender_profile() {
return None;
}
Some(Profile {
display_name: latest_event.sender_display_name().map(ToOwned::to_owned),
display_name_ambiguous: latest_event.sender_name_ambiguous().unwrap_or(false),
avatar_url: latest_event.sender_avatar_url().map(ToOwned::to_owned),
})
}
async fn load_user_receipt(
&self,
receipt_type: ReceiptType,
thread: ReceiptThread,
user_id: &UserId,
) -> Option<(OwnedEventId, Receipt)> {
match self.load_user_receipt(receipt_type.clone(), thread.clone(), user_id).await {
Ok(receipt) => receipt,
Err(e) => {
error!(
?receipt_type,
?thread,
?user_id,
"Failed to get read receipt for user: {e}"
);
None
}
}
}
async fn load_event_receipts(&self, event_id: &EventId) -> IndexMap<OwnedUserId, Receipt> {
let mut unthreaded_receipts = match self
.load_event_receipts(ReceiptType::Read, ReceiptThread::Unthreaded, event_id)
.await
{
Ok(receipts) => receipts.into_iter().collect(),
Err(e) => {
error!(?event_id, "Failed to get unthreaded read receipts for event: {e}");
IndexMap::new()
}
};
let main_thread_receipts = match self
.load_event_receipts(ReceiptType::Read, ReceiptThread::Main, event_id)
.await
{
Ok(receipts) => receipts,
Err(e) => {
error!(?event_id, "Failed to get main thread read receipts for event: {e}");
Vec::new()
}
};
unthreaded_receipts.extend(main_thread_receipts);
unthreaded_receipts
}
async fn push_rules_and_context(&self) -> Option<(Ruleset, PushConditionRoomCtx)> {
match self.push_context().await {
Ok(Some(push_context)) => match self.client().account().push_rules().await {
Ok(push_rules) => Some((push_rules, push_context)),
Err(e) => {
error!("Could not get push rules: {e}");
None
}
},
Ok(None) => {
debug!("Could not aggregate push context");
None
}
Err(e) => {
error!("Could not get push context: {e}");
None
}
}
}
async fn load_fully_read_marker(&self) -> Option<OwnedEventId> {
match self.account_data_static::<FullyReadEventContent>().await {
Ok(Some(fully_read)) => match fully_read.deserialize() {
Ok(fully_read) => Some(fully_read.content.event_id),
Err(e) => {
error!("Failed to deserialize fully-read account data: {e}");
None
}
},
Err(e) => {
error!("Failed to get fully-read account data from the store: {e}");
None
}
_ => None,
}
}
}
#[cfg(feature = "e2e-encryption")]
#[async_trait]
pub(super) trait Decryptor: Clone + Send + Sync + 'static {
async fn decrypt_event_impl(&self, raw: &Raw<AnySyncTimelineEvent>) -> Result<TimelineEvent>;
}
#[cfg(feature = "e2e-encryption")]
#[async_trait]
impl Decryptor for Room {
async fn decrypt_event_impl(&self, raw: &Raw<AnySyncTimelineEvent>) -> Result<TimelineEvent> {
self.decrypt_event(raw.cast_ref()).await
}
}
#[cfg(all(test, feature = "e2e-encryption"))]
#[async_trait]
impl Decryptor for (matrix_sdk_base::crypto::OlmMachine, ruma::OwnedRoomId) {
async fn decrypt_event_impl(&self, raw: &Raw<AnySyncTimelineEvent>) -> Result<TimelineEvent> {
let (olm_machine, room_id) = self;
let event = olm_machine.decrypt_room_event(raw.cast_ref(), room_id).await?;
Ok(event)
}
}