2023-10-06 22:35:27 -07:00
|
|
|
//! # Async Matrix Client Worker
|
|
|
|
//!
|
|
|
|
//! The worker thread handles asynchronous work, and can receive messages from the main thread that
|
|
|
|
//! block on a reply from the async worker.
|
2023-03-13 10:46:26 -07:00
|
|
|
use std::collections::HashMap;
|
2022-12-29 18:00:59 -08:00
|
|
|
use std::convert::TryFrom;
|
2023-01-10 19:59:30 -08:00
|
|
|
use std::fmt::{Debug, Formatter};
|
2024-03-06 23:49:35 -08:00
|
|
|
use std::ops::{Deref, DerefMut};
|
2022-12-29 18:00:59 -08:00
|
|
|
use std::str::FromStr;
|
2023-01-10 19:59:30 -08:00
|
|
|
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
2022-12-29 18:00:59 -08:00
|
|
|
use std::sync::Arc;
|
2023-03-13 10:46:26 -07:00
|
|
|
use std::time::{Duration, Instant};
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-07-05 15:25:42 -07:00
|
|
|
use futures::{stream::FuturesUnordered, StreamExt};
|
2022-12-29 18:00:59 -08:00
|
|
|
use gethostname::gethostname;
|
2023-01-10 19:59:30 -08:00
|
|
|
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
2024-03-06 23:49:35 -08:00
|
|
|
use tokio::sync::Semaphore;
|
2022-12-29 18:00:59 -08:00
|
|
|
use tokio::task::JoinHandle;
|
2023-03-13 10:46:26 -07:00
|
|
|
use tracing::{error, warn};
|
2024-03-02 15:00:29 -08:00
|
|
|
use url::Url;
|
2022-12-29 18:00:59 -08:00
|
|
|
|
|
|
|
use matrix_sdk::{
|
2023-03-20 16:01:02 -07:00
|
|
|
config::{RequestConfig, SyncSettings},
|
2022-12-29 18:00:59 -08:00
|
|
|
encryption::verification::{SasVerification, Verification},
|
2024-03-02 15:55:27 -08:00
|
|
|
encryption::{BackupDownloadStrategy, EncryptionSettings},
|
2022-12-29 18:00:59 -08:00
|
|
|
event_handler::Ctx,
|
2024-03-02 15:00:29 -08:00
|
|
|
matrix_auth::MatrixSession,
|
2022-12-29 18:00:59 -08:00
|
|
|
reqwest,
|
2024-03-02 15:00:29 -08:00
|
|
|
room::{Messages, MessagesOptions, Room as MatrixRoom, RoomMember},
|
2022-12-29 18:00:59 -08:00
|
|
|
ruma::{
|
|
|
|
api::client::{
|
2024-02-28 09:03:28 -08:00
|
|
|
filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
|
2023-03-04 12:23:17 -08:00
|
|
|
room::create_room::v3::{CreationContent, Request as CreateRoomRequest, RoomPreset},
|
2022-12-29 18:00:59 -08:00
|
|
|
room::Visibility,
|
|
|
|
space::get_hierarchy::v1::Request as SpaceHierarchyRequest,
|
|
|
|
},
|
2023-03-03 16:37:11 -08:00
|
|
|
assign,
|
2022-12-29 18:00:59 -08:00
|
|
|
events::{
|
|
|
|
key::verification::{
|
|
|
|
done::{OriginalSyncKeyVerificationDoneEvent, ToDeviceKeyVerificationDoneEvent},
|
|
|
|
key::{OriginalSyncKeyVerificationKeyEvent, ToDeviceKeyVerificationKeyEvent},
|
|
|
|
request::ToDeviceKeyVerificationRequestEvent,
|
|
|
|
start::{OriginalSyncKeyVerificationStartEvent, ToDeviceKeyVerificationStartEvent},
|
|
|
|
VerificationMethod,
|
|
|
|
},
|
2023-03-01 18:46:33 -08:00
|
|
|
presence::PresenceEvent,
|
2023-02-09 17:53:33 -08:00
|
|
|
reaction::ReactionEventContent,
|
2024-03-02 15:00:29 -08:00
|
|
|
receipt::ReceiptType,
|
|
|
|
receipt::{ReceiptEventContent, ReceiptThread},
|
2023-01-05 18:12:25 -08:00
|
|
|
room::{
|
2023-03-03 16:37:11 -08:00
|
|
|
encryption::RoomEncryptionEventContent,
|
2023-07-06 23:15:58 -07:00
|
|
|
member::OriginalSyncRoomMemberEvent,
|
2023-01-10 19:59:30 -08:00
|
|
|
message::{MessageType, RoomMessageEventContent},
|
2023-01-05 18:12:25 -08:00
|
|
|
name::RoomNameEventContent,
|
2024-03-23 19:20:06 -07:00
|
|
|
redaction::OriginalSyncRoomRedactionEvent,
|
2023-01-05 18:12:25 -08:00
|
|
|
},
|
2023-01-25 17:54:16 -08:00
|
|
|
tag::Tags,
|
2023-01-03 13:57:28 -08:00
|
|
|
typing::SyncTypingEvent,
|
2023-03-03 16:37:11 -08:00
|
|
|
AnyInitialStateEvent,
|
2023-03-13 10:46:26 -07:00
|
|
|
AnyMessageLikeEvent,
|
2022-12-29 18:00:59 -08:00
|
|
|
AnyTimelineEvent,
|
2023-03-03 16:37:11 -08:00
|
|
|
EmptyStateKey,
|
|
|
|
InitialStateEvent,
|
2023-10-15 18:12:39 -07:00
|
|
|
SyncEphemeralRoomEvent,
|
2022-12-29 18:00:59 -08:00
|
|
|
SyncMessageLikeEvent,
|
|
|
|
SyncStateEvent,
|
|
|
|
},
|
2023-03-04 12:23:17 -08:00
|
|
|
room::RoomType,
|
2023-03-03 16:37:11 -08:00
|
|
|
serde::Raw,
|
|
|
|
EventEncryptionAlgorithm,
|
2023-10-15 18:12:39 -07:00
|
|
|
EventId,
|
2023-05-12 17:42:25 -07:00
|
|
|
OwnedEventId,
|
2022-12-29 18:00:59 -08:00
|
|
|
OwnedRoomId,
|
|
|
|
OwnedRoomOrAliasId,
|
|
|
|
OwnedUserId,
|
2023-03-13 10:46:26 -07:00
|
|
|
RoomId,
|
2023-01-13 17:53:54 -08:00
|
|
|
RoomVersionId,
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
|
|
|
Client,
|
2024-03-02 15:00:29 -08:00
|
|
|
ClientBuildError,
|
2022-12-29 18:00:59 -08:00
|
|
|
DisplayName,
|
2024-03-23 19:09:11 -07:00
|
|
|
Error as MatrixError,
|
2024-03-02 15:00:29 -08:00
|
|
|
RoomMemberships,
|
2022-12-29 18:00:59 -08:00
|
|
|
};
|
|
|
|
|
2024-02-28 23:00:25 -08:00
|
|
|
use modalkit::errors::UIError;
|
|
|
|
use modalkit::prelude::{EditInfo, InfoMessage};
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-12-18 20:55:04 -08:00
|
|
|
use crate::base::Need;
|
2024-03-22 00:46:46 +00:00
|
|
|
use crate::notifications::register_notifications;
|
2022-12-29 18:00:59 -08:00
|
|
|
use crate::{
|
2023-03-03 16:37:11 -08:00
|
|
|
base::{
|
|
|
|
AsyncProgramStore,
|
2023-03-13 10:46:26 -07:00
|
|
|
ChatStore,
|
2023-03-03 16:37:11 -08:00
|
|
|
CreateRoomFlags,
|
|
|
|
CreateRoomType,
|
|
|
|
IambError,
|
|
|
|
IambResult,
|
2024-03-06 23:49:35 -08:00
|
|
|
ProgramStore,
|
2023-03-13 10:46:26 -07:00
|
|
|
RoomFetchStatus,
|
2023-10-15 18:12:39 -07:00
|
|
|
RoomInfo,
|
2023-03-03 16:37:11 -08:00
|
|
|
VerifyAction,
|
|
|
|
},
|
2022-12-29 18:00:59 -08:00
|
|
|
ApplicationSettings,
|
|
|
|
};
|
|
|
|
|
2024-03-02 15:55:27 -08:00
|
|
|
const DEFAULT_ENCRYPTION_SETTINGS: EncryptionSettings = EncryptionSettings {
|
|
|
|
auto_enable_cross_signing: true,
|
|
|
|
auto_enable_backups: true,
|
|
|
|
backup_download_strategy: BackupDownloadStrategy::AfterDecryptionFailure,
|
|
|
|
};
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
const IAMB_DEVICE_NAME: &str = "iamb";
|
|
|
|
const IAMB_USER_AGENT: &str = "iamb";
|
2023-03-13 10:46:26 -07:00
|
|
|
const MIN_MSG_LOAD: u32 = 50;
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2024-03-06 23:49:35 -08:00
|
|
|
type MessageFetchResult =
|
|
|
|
IambResult<(Option<String>, Vec<(AnyMessageLikeEvent, Vec<OwnedUserId>)>)>;
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
fn initial_devname() -> String {
|
|
|
|
format!("{} on {}", IAMB_DEVICE_NAME, gethostname().to_string_lossy())
|
|
|
|
}
|
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
async fn is_direct(room: &MatrixRoom) -> bool {
|
|
|
|
room.deref().is_direct().await.unwrap_or_default()
|
|
|
|
}
|
|
|
|
|
2023-03-03 16:37:11 -08:00
|
|
|
pub async fn create_room(
|
|
|
|
client: &Client,
|
2024-03-02 15:00:29 -08:00
|
|
|
room_alias_name: Option<String>,
|
2023-03-03 16:37:11 -08:00
|
|
|
rt: CreateRoomType,
|
|
|
|
flags: CreateRoomFlags,
|
|
|
|
) -> IambResult<OwnedRoomId> {
|
2023-03-04 12:23:17 -08:00
|
|
|
let mut creation_content = None;
|
2023-03-03 16:37:11 -08:00
|
|
|
let mut initial_state = vec![];
|
2023-03-04 12:23:17 -08:00
|
|
|
let mut is_direct = false;
|
|
|
|
let mut preset = None;
|
2023-03-03 16:37:11 -08:00
|
|
|
let mut invite = vec![];
|
|
|
|
|
|
|
|
let visibility = if flags.contains(CreateRoomFlags::PUBLIC) {
|
|
|
|
Visibility::Public
|
|
|
|
} else {
|
|
|
|
Visibility::Private
|
|
|
|
};
|
|
|
|
|
|
|
|
match rt {
|
|
|
|
CreateRoomType::Direct(user) => {
|
|
|
|
invite.push(user);
|
|
|
|
is_direct = true;
|
|
|
|
preset = Some(RoomPreset::TrustedPrivateChat);
|
|
|
|
},
|
2023-03-04 12:23:17 -08:00
|
|
|
CreateRoomType::Space => {
|
|
|
|
let mut cc = CreationContent::new();
|
|
|
|
cc.room_type = Some(RoomType::Space);
|
|
|
|
|
|
|
|
let raw_cc = Raw::new(&cc).map_err(IambError::from)?;
|
|
|
|
creation_content = Some(raw_cc);
|
|
|
|
},
|
|
|
|
CreateRoomType::Room => {},
|
2023-03-03 16:37:11 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set up encryption.
|
|
|
|
if flags.contains(CreateRoomFlags::ENCRYPTED) {
|
|
|
|
// XXX: Once matrix-sdk uses ruma 0.8, then this can skip the cast.
|
|
|
|
let algo = EventEncryptionAlgorithm::MegolmV1AesSha2;
|
|
|
|
let content = RoomEncryptionEventContent::new(algo);
|
|
|
|
let encr = InitialStateEvent { content, state_key: EmptyStateKey };
|
|
|
|
let encr_raw = Raw::new(&encr).map_err(IambError::from)?;
|
|
|
|
let encr_raw = encr_raw.cast::<AnyInitialStateEvent>();
|
|
|
|
initial_state.push(encr_raw);
|
|
|
|
}
|
|
|
|
|
|
|
|
let request = assign!(CreateRoomRequest::new(), {
|
|
|
|
room_alias_name,
|
|
|
|
creation_content,
|
2024-03-02 15:00:29 -08:00
|
|
|
initial_state,
|
|
|
|
invite,
|
2023-03-03 16:37:11 -08:00
|
|
|
is_direct,
|
|
|
|
visibility,
|
|
|
|
preset,
|
|
|
|
});
|
|
|
|
|
|
|
|
let resp = client.create_room(request).await.map_err(IambError::from)?;
|
|
|
|
|
|
|
|
if is_direct {
|
2024-03-02 15:00:29 -08:00
|
|
|
if let Some(room) = client.get_room(resp.room_id()) {
|
2023-03-03 16:37:11 -08:00
|
|
|
room.set_is_direct(true).await.map_err(IambError::from)?;
|
|
|
|
} else {
|
|
|
|
error!(
|
2024-03-02 15:00:29 -08:00
|
|
|
room_id = resp.room_id().as_str(),
|
2023-03-03 16:37:11 -08:00
|
|
|
"Couldn't set is_direct for new direct message room"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
return Ok(resp.room_id().to_owned());
|
2023-03-03 16:37:11 -08:00
|
|
|
}
|
|
|
|
|
2023-10-15 18:12:39 -07:00
|
|
|
async fn update_event_receipts(info: &mut RoomInfo, room: &MatrixRoom, event_id: &EventId) {
|
2024-03-02 15:00:29 -08:00
|
|
|
let receipts = match room
|
|
|
|
.load_event_receipts(ReceiptType::Read, ReceiptThread::Main, event_id)
|
|
|
|
.await
|
|
|
|
{
|
2023-10-15 18:12:39 -07:00
|
|
|
Ok(receipts) => receipts,
|
|
|
|
Err(e) => {
|
|
|
|
tracing::warn!(?event_id, "failed to get event receipts: {e}");
|
|
|
|
return;
|
|
|
|
},
|
|
|
|
};
|
2024-03-02 15:00:29 -08:00
|
|
|
|
2023-10-15 18:12:39 -07:00
|
|
|
for (user_id, _) in receipts {
|
|
|
|
info.set_receipt(user_id, event_id.to_owned());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-12-18 20:55:04 -08:00
|
|
|
#[derive(Debug)]
|
|
|
|
enum Plan {
|
|
|
|
Messages(OwnedRoomId, Option<String>),
|
|
|
|
Members(OwnedRoomId),
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn load_plans(store: &AsyncProgramStore) -> Vec<Plan> {
|
2023-03-13 10:46:26 -07:00
|
|
|
let mut locked = store.lock().await;
|
|
|
|
let ChatStore { need_load, rooms, .. } = &mut locked.application;
|
2024-03-06 23:49:35 -08:00
|
|
|
let mut plan = Vec::with_capacity(need_load.rooms() * 2);
|
2023-03-13 10:46:26 -07:00
|
|
|
|
2023-12-18 20:55:04 -08:00
|
|
|
for (room_id, mut need) in std::mem::take(need_load).into_iter() {
|
|
|
|
if need.contains(Need::MESSAGES) {
|
|
|
|
let info = rooms.get_or_default(room_id.clone());
|
2023-03-13 10:46:26 -07:00
|
|
|
|
2023-12-18 20:55:04 -08:00
|
|
|
if !info.recently_fetched() && !info.fetching {
|
|
|
|
info.fetch_last = Instant::now().into();
|
|
|
|
info.fetching = true;
|
2023-03-13 10:46:26 -07:00
|
|
|
|
2023-12-18 20:55:04 -08:00
|
|
|
let fetch_id = match &info.fetch_id {
|
|
|
|
RoomFetchStatus::Done => continue,
|
|
|
|
RoomFetchStatus::HaveMore(fetch_id) => Some(fetch_id.clone()),
|
|
|
|
RoomFetchStatus::NotStarted => None,
|
|
|
|
};
|
2023-03-13 10:46:26 -07:00
|
|
|
|
2023-12-18 20:55:04 -08:00
|
|
|
plan.push(Plan::Messages(room_id.to_owned(), fetch_id));
|
|
|
|
need.remove(Need::MESSAGES);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if need.contains(Need::MEMBERS) {
|
|
|
|
plan.push(Plan::Members(room_id.to_owned()));
|
|
|
|
need.remove(Need::MEMBERS);
|
|
|
|
}
|
|
|
|
if !need.is_empty() {
|
|
|
|
need_load.insert(room_id, need);
|
|
|
|
}
|
2023-03-13 10:46:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return plan;
|
|
|
|
}
|
|
|
|
|
2024-03-06 23:49:35 -08:00
|
|
|
async fn run_plan(client: &Client, store: &AsyncProgramStore, plan: Plan, permits: &Semaphore) {
|
|
|
|
let permit = permits.acquire().await;
|
2023-12-18 20:55:04 -08:00
|
|
|
match plan {
|
|
|
|
Plan::Messages(room_id, fetch_id) => {
|
|
|
|
let limit = MIN_MSG_LOAD;
|
|
|
|
let client = client.clone();
|
2024-03-06 23:49:35 -08:00
|
|
|
let store_clone = store.clone();
|
2023-12-18 20:55:04 -08:00
|
|
|
|
|
|
|
let res = load_older_one(&client, &room_id, fetch_id, limit).await;
|
2024-03-06 23:49:35 -08:00
|
|
|
let mut locked = store.lock().await;
|
|
|
|
load_insert(room_id, res, locked.deref_mut(), store_clone);
|
2023-12-18 20:55:04 -08:00
|
|
|
},
|
|
|
|
Plan::Members(room_id) => {
|
|
|
|
let res = members_load(client, &room_id).await;
|
2024-03-06 23:49:35 -08:00
|
|
|
let mut locked = store.lock().await;
|
|
|
|
members_insert(room_id, res, locked.deref_mut());
|
2023-12-18 20:55:04 -08:00
|
|
|
},
|
|
|
|
}
|
2024-03-06 23:49:35 -08:00
|
|
|
drop(permit);
|
2023-12-18 20:55:04 -08:00
|
|
|
}
|
|
|
|
|
2023-03-13 10:46:26 -07:00
|
|
|
async fn load_older_one(
|
2023-10-15 18:12:39 -07:00
|
|
|
client: &Client,
|
2023-03-13 10:46:26 -07:00
|
|
|
room_id: &RoomId,
|
|
|
|
fetch_id: Option<String>,
|
|
|
|
limit: u32,
|
|
|
|
) -> MessageFetchResult {
|
|
|
|
if let Some(room) = client.get_room(room_id) {
|
|
|
|
let mut opts = match &fetch_id {
|
|
|
|
Some(id) => MessagesOptions::backward().from(id.as_str()),
|
|
|
|
None => MessagesOptions::backward(),
|
|
|
|
};
|
|
|
|
opts.limit = limit.into();
|
|
|
|
|
|
|
|
let Messages { end, chunk, .. } = room.messages(opts).await.map_err(IambError::from)?;
|
|
|
|
|
2024-03-06 23:49:35 -08:00
|
|
|
let mut msgs = vec![];
|
|
|
|
|
|
|
|
for ev in chunk.into_iter() {
|
|
|
|
let msg = match ev.event.deserialize() {
|
|
|
|
Ok(AnyTimelineEvent::MessageLike(msg)) => msg,
|
|
|
|
Ok(AnyTimelineEvent::State(_)) => continue,
|
|
|
|
Err(_) => continue,
|
|
|
|
};
|
|
|
|
|
|
|
|
let event_id = msg.event_id();
|
|
|
|
let receipts = match room
|
|
|
|
.load_event_receipts(ReceiptType::Read, ReceiptThread::Main, event_id)
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
Ok(receipts) => receipts.into_iter().map(|(u, _)| u).collect(),
|
|
|
|
Err(e) => {
|
|
|
|
tracing::warn!(?event_id, "failed to get event receipts: {e}");
|
|
|
|
vec![]
|
|
|
|
},
|
|
|
|
};
|
2023-03-13 10:46:26 -07:00
|
|
|
|
2024-03-06 23:49:35 -08:00
|
|
|
msgs.push((msg, receipts));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok((end, msgs))
|
2023-03-13 10:46:26 -07:00
|
|
|
} else {
|
|
|
|
Err(IambError::UnknownRoom(room_id.to_owned()).into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-06 23:49:35 -08:00
|
|
|
fn load_insert(
|
|
|
|
room_id: OwnedRoomId,
|
|
|
|
res: MessageFetchResult,
|
|
|
|
locked: &mut ProgramStore,
|
|
|
|
store: AsyncProgramStore,
|
|
|
|
) {
|
2023-12-18 20:55:04 -08:00
|
|
|
let ChatStore { presences, rooms, worker, picker, settings, .. } = &mut locked.application;
|
2023-03-13 10:46:26 -07:00
|
|
|
let info = rooms.get_or_default(room_id.clone());
|
|
|
|
info.fetching = false;
|
2023-11-16 08:36:22 -08:00
|
|
|
let client = &worker.client;
|
2023-03-13 10:46:26 -07:00
|
|
|
|
|
|
|
match res {
|
|
|
|
Ok((fetch_id, msgs)) => {
|
2024-03-06 23:49:35 -08:00
|
|
|
for (msg, receipts) in msgs.into_iter() {
|
2023-03-13 10:46:26 -07:00
|
|
|
let sender = msg.sender().to_owned();
|
|
|
|
let _ = presences.get_or_default(sender);
|
|
|
|
|
2024-03-06 23:49:35 -08:00
|
|
|
for user_id in receipts {
|
|
|
|
info.set_receipt(user_id, msg.event_id().to_owned());
|
2023-10-15 18:12:39 -07:00
|
|
|
}
|
|
|
|
|
2023-03-13 10:46:26 -07:00
|
|
|
match msg {
|
2023-03-13 15:18:53 -07:00
|
|
|
AnyMessageLikeEvent::RoomEncrypted(msg) => {
|
|
|
|
info.insert_encrypted(msg);
|
|
|
|
},
|
2023-03-13 10:46:26 -07:00
|
|
|
AnyMessageLikeEvent::RoomMessage(msg) => {
|
2023-11-16 08:36:22 -08:00
|
|
|
info.insert_with_preview(
|
|
|
|
room_id.clone(),
|
|
|
|
store.clone(),
|
|
|
|
*picker,
|
|
|
|
msg,
|
|
|
|
settings,
|
|
|
|
client.media(),
|
|
|
|
);
|
2023-03-13 10:46:26 -07:00
|
|
|
},
|
|
|
|
AnyMessageLikeEvent::Reaction(ev) => {
|
|
|
|
info.insert_reaction(ev);
|
|
|
|
},
|
|
|
|
_ => continue,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
info.fetch_id = fetch_id.map_or(RoomFetchStatus::Done, RoomFetchStatus::HaveMore);
|
|
|
|
},
|
|
|
|
Err(e) => {
|
|
|
|
warn!(room_id = room_id.as_str(), err = e.to_string(), "Failed to load older messages");
|
|
|
|
|
|
|
|
// Wait and try again.
|
2023-12-18 20:55:04 -08:00
|
|
|
locked.application.need_load.insert(room_id, Need::MESSAGES);
|
2023-03-13 10:46:26 -07:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-05 15:25:42 -07:00
|
|
|
async fn load_older(client: &Client, store: &AsyncProgramStore) -> usize {
|
2024-03-06 23:49:35 -08:00
|
|
|
// This is an arbitrary limit on how much work we do in parallel to avoid
|
|
|
|
// spawning too many tasks at startup and overwhelming the client. We
|
|
|
|
// should normally only surpass this limit at startup when doing an initial.
|
|
|
|
// fetch for each room.
|
|
|
|
const LIMIT: usize = 15;
|
|
|
|
|
2023-12-18 20:55:04 -08:00
|
|
|
// Plans are run in parallel. Any room *may* have several plans.
|
2024-03-06 23:49:35 -08:00
|
|
|
let plans = load_plans(store).await;
|
|
|
|
let permits = Semaphore::new(LIMIT);
|
|
|
|
|
|
|
|
plans
|
2023-07-05 15:25:42 -07:00
|
|
|
.into_iter()
|
2024-03-06 23:49:35 -08:00
|
|
|
.map(|plan| run_plan(client, store, plan, &permits))
|
2023-07-05 15:25:42 -07:00
|
|
|
.collect::<FuturesUnordered<_>>()
|
|
|
|
.count()
|
|
|
|
.await
|
|
|
|
}
|
|
|
|
|
2023-12-18 20:55:04 -08:00
|
|
|
async fn members_load(client: &Client, room_id: &RoomId) -> IambResult<Vec<RoomMember>> {
|
|
|
|
if let Some(room) = client.get_room(room_id) {
|
2024-03-02 15:00:29 -08:00
|
|
|
Ok(room
|
|
|
|
.members_no_sync(RoomMemberships::all())
|
|
|
|
.await
|
|
|
|
.map_err(IambError::from)?)
|
2023-12-18 20:55:04 -08:00
|
|
|
} else {
|
|
|
|
Err(IambError::UnknownRoom(room_id.to_owned()).into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-06 23:49:35 -08:00
|
|
|
fn members_insert(
|
2023-12-18 20:55:04 -08:00
|
|
|
room_id: OwnedRoomId,
|
|
|
|
res: IambResult<Vec<RoomMember>>,
|
2024-03-06 23:49:35 -08:00
|
|
|
store: &mut ProgramStore,
|
2023-12-18 20:55:04 -08:00
|
|
|
) {
|
|
|
|
if let Ok(members) = res {
|
2024-03-06 23:49:35 -08:00
|
|
|
let ChatStore { rooms, .. } = &mut store.application;
|
2024-03-09 00:47:05 -08:00
|
|
|
let info = rooms.get_or_default(room_id);
|
2023-12-18 20:55:04 -08:00
|
|
|
|
|
|
|
for member in members {
|
|
|
|
let user_id = member.user_id();
|
|
|
|
let display_name =
|
|
|
|
member.display_name().map_or(user_id.to_string(), |str| str.to_string());
|
|
|
|
info.display_names.insert(user_id.to_owned(), display_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// else ???
|
|
|
|
}
|
|
|
|
|
2023-07-05 15:25:42 -07:00
|
|
|
async fn load_older_forever(client: &Client, store: &AsyncProgramStore) {
|
2023-12-18 20:55:04 -08:00
|
|
|
// Load any pending older messages or members every 2 seconds.
|
2023-07-05 15:25:42 -07:00
|
|
|
let mut interval = tokio::time::interval(Duration::from_secs(2));
|
|
|
|
|
|
|
|
loop {
|
|
|
|
interval.tick().await;
|
|
|
|
load_older(client, store).await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn refresh_rooms(client: &Client, store: &AsyncProgramStore) {
|
|
|
|
let mut names = vec![];
|
|
|
|
|
|
|
|
let mut spaces = vec![];
|
|
|
|
let mut rooms = vec![];
|
|
|
|
let mut dms = vec![];
|
|
|
|
|
|
|
|
for room in client.invited_rooms().into_iter() {
|
|
|
|
let name = room.display_name().await.unwrap_or(DisplayName::Empty).to_string();
|
2023-10-20 19:32:33 -07:00
|
|
|
let tags = room.tags().await.unwrap_or_default();
|
|
|
|
|
2023-07-05 15:25:42 -07:00
|
|
|
names.push((room.room_id().to_owned(), name));
|
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
if is_direct(&room).await {
|
|
|
|
dms.push(Arc::new((room, tags)));
|
2023-07-05 15:25:42 -07:00
|
|
|
} else if room.is_space() {
|
2024-03-02 15:00:29 -08:00
|
|
|
spaces.push(Arc::new((room, tags)));
|
2023-07-05 15:25:42 -07:00
|
|
|
} else {
|
2024-03-02 15:00:29 -08:00
|
|
|
rooms.push(Arc::new((room, tags)));
|
2023-07-05 15:25:42 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for room in client.joined_rooms().into_iter() {
|
|
|
|
let name = room.display_name().await.unwrap_or(DisplayName::Empty).to_string();
|
2023-10-20 19:32:33 -07:00
|
|
|
let tags = room.tags().await.unwrap_or_default();
|
|
|
|
|
2023-07-05 15:25:42 -07:00
|
|
|
names.push((room.room_id().to_owned(), name));
|
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
if is_direct(&room).await {
|
|
|
|
dms.push(Arc::new((room, tags)));
|
2023-07-05 15:25:42 -07:00
|
|
|
} else if room.is_space() {
|
2024-03-02 15:00:29 -08:00
|
|
|
spaces.push(Arc::new((room, tags)));
|
2023-07-05 15:25:42 -07:00
|
|
|
} else {
|
2024-03-02 15:00:29 -08:00
|
|
|
rooms.push(Arc::new((room, tags)));
|
2023-07-05 15:25:42 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut locked = store.lock().await;
|
|
|
|
locked.application.sync_info.spaces = spaces;
|
|
|
|
locked.application.sync_info.rooms = rooms;
|
|
|
|
locked.application.sync_info.dms = dms;
|
|
|
|
|
|
|
|
for (room_id, name) in names {
|
|
|
|
locked.application.set_room_name(&room_id, &name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn refresh_rooms_forever(client: &Client, store: &AsyncProgramStore) {
|
|
|
|
let mut interval = tokio::time::interval(Duration::from_secs(5));
|
|
|
|
|
|
|
|
loop {
|
|
|
|
refresh_rooms(client, store).await;
|
2024-02-28 09:03:28 -08:00
|
|
|
interval.tick().await;
|
2023-07-05 15:25:42 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-15 18:12:39 -07:00
|
|
|
async fn send_receipts_forever(client: &Client, store: &AsyncProgramStore) {
|
|
|
|
let mut interval = tokio::time::interval(Duration::from_secs(2));
|
2023-07-05 15:25:42 -07:00
|
|
|
let mut sent = HashMap::<OwnedRoomId, OwnedEventId>::default();
|
|
|
|
|
|
|
|
loop {
|
|
|
|
interval.tick().await;
|
|
|
|
|
2023-10-15 18:12:39 -07:00
|
|
|
let locked = store.lock().await;
|
2024-02-28 09:03:28 -08:00
|
|
|
let user_id = &locked.application.settings.profile.user_id;
|
2023-10-15 18:12:39 -07:00
|
|
|
let updates = client
|
|
|
|
.joined_rooms()
|
|
|
|
.into_iter()
|
|
|
|
.filter_map(|room| {
|
|
|
|
let room_id = room.room_id().to_owned();
|
|
|
|
let info = locked.application.rooms.get(&room_id)?;
|
2024-02-28 09:03:28 -08:00
|
|
|
let new_receipt = info.get_receipt(user_id)?;
|
2023-10-15 18:12:39 -07:00
|
|
|
let old_receipt = sent.get(&room_id);
|
|
|
|
if Some(new_receipt) != old_receipt {
|
|
|
|
Some((room_id, new_receipt.clone()))
|
|
|
|
} else {
|
|
|
|
None
|
2023-07-05 15:25:42 -07:00
|
|
|
}
|
2023-10-15 18:12:39 -07:00
|
|
|
})
|
|
|
|
.collect::<Vec<_>>();
|
|
|
|
drop(locked);
|
|
|
|
|
|
|
|
for (room_id, new_receipt) in updates {
|
2024-03-02 15:00:29 -08:00
|
|
|
use matrix_sdk::ruma::api::client::receipt::create_receipt::v3::ReceiptType;
|
|
|
|
|
|
|
|
let Some(room) = client.get_room(&room_id) else {
|
2023-10-15 18:12:39 -07:00
|
|
|
continue;
|
|
|
|
};
|
2024-03-02 15:00:29 -08:00
|
|
|
|
|
|
|
match room
|
|
|
|
.send_single_receipt(
|
|
|
|
ReceiptType::Read,
|
|
|
|
ReceiptThread::Unthreaded,
|
|
|
|
new_receipt.clone(),
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
{
|
2023-10-15 18:12:39 -07:00
|
|
|
Ok(()) => {
|
|
|
|
sent.insert(room_id, new_receipt);
|
|
|
|
},
|
|
|
|
Err(e) => tracing::warn!(?room_id, "Failed to set read receipt: {e}"),
|
2023-07-05 15:25:42 -07:00
|
|
|
}
|
|
|
|
}
|
2023-03-13 10:46:26 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-23 19:09:11 -07:00
|
|
|
pub async fn do_first_sync(client: &Client, store: &AsyncProgramStore) -> Result<(), MatrixError> {
|
2024-02-28 09:03:28 -08:00
|
|
|
// Perform an initial, lazily-loaded sync.
|
|
|
|
let mut room = RoomEventFilter::default();
|
|
|
|
room.lazy_load_options = LazyLoadOptions::Enabled { include_redundant_members: false };
|
|
|
|
|
|
|
|
let mut room_ev = RoomFilter::default();
|
|
|
|
room_ev.state = room;
|
|
|
|
|
|
|
|
let mut filter = FilterDefinition::default();
|
|
|
|
filter.room = room_ev;
|
|
|
|
|
|
|
|
let settings = SyncSettings::new().filter(filter.into());
|
|
|
|
|
2024-03-23 19:09:11 -07:00
|
|
|
client.sync_once(settings).await?;
|
2024-02-28 09:03:28 -08:00
|
|
|
|
|
|
|
// Populate sync_info with our initial set of rooms/dms/spaces.
|
2024-03-02 15:00:29 -08:00
|
|
|
refresh_rooms(client, store).await;
|
2024-02-28 09:03:28 -08:00
|
|
|
|
|
|
|
// Insert Need::Messages to fetch accurate recent timestamps in the background.
|
|
|
|
let mut locked = store.lock().await;
|
|
|
|
let ChatStore { sync_info, need_load, .. } = &mut locked.application;
|
|
|
|
|
|
|
|
for room in sync_info.rooms.iter() {
|
|
|
|
let room_id = room.as_ref().0.room_id().to_owned();
|
|
|
|
need_load.insert(room_id, Need::MESSAGES);
|
|
|
|
}
|
|
|
|
|
|
|
|
for room in sync_info.dms.iter() {
|
|
|
|
let room_id = room.as_ref().0.room_id().to_owned();
|
|
|
|
need_load.insert(room_id, Need::MESSAGES);
|
|
|
|
}
|
2024-03-23 19:09:11 -07:00
|
|
|
|
|
|
|
Ok(())
|
2024-02-28 09:03:28 -08:00
|
|
|
}
|
|
|
|
|
2023-01-10 19:59:30 -08:00
|
|
|
#[derive(Debug)]
|
2022-12-29 18:00:59 -08:00
|
|
|
pub enum LoginStyle {
|
2024-03-02 15:00:29 -08:00
|
|
|
SessionRestore(MatrixSession),
|
2022-12-29 18:00:59 -08:00
|
|
|
Password(String),
|
2023-11-04 17:39:17 -04:00
|
|
|
SingleSignOn,
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct ClientResponse<T>(Receiver<T>);
|
|
|
|
pub struct ClientReply<T>(SyncSender<T>);
|
|
|
|
|
|
|
|
impl<T> ClientResponse<T> {
|
|
|
|
fn recv(self) -> T {
|
|
|
|
self.0.recv().expect("failed to receive response from client thread")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> ClientReply<T> {
|
|
|
|
fn send(self, t: T) {
|
|
|
|
self.0.send(t).unwrap();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn oneshot<T>() -> (ClientReply<T>, ClientResponse<T>) {
|
|
|
|
let (tx, rx) = sync_channel(1);
|
|
|
|
let reply = ClientReply(tx);
|
|
|
|
let response = ClientResponse(rx);
|
|
|
|
|
|
|
|
return (reply, response);
|
|
|
|
}
|
|
|
|
|
2023-01-26 15:23:15 -08:00
|
|
|
pub type FetchedRoom = (MatrixRoom, DisplayName, Option<Tags>);
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
pub enum WorkerTask {
|
|
|
|
Init(AsyncProgramStore, ClientReply<()>),
|
|
|
|
Login(LoginStyle, ClientReply<IambResult<EditInfo>>),
|
2023-10-13 00:58:59 -05:00
|
|
|
Logout(String, ClientReply<IambResult<EditInfo>>),
|
2024-03-02 15:00:29 -08:00
|
|
|
GetInviter(MatrixRoom, ClientReply<IambResult<Option<RoomMember>>>),
|
2023-01-26 15:23:15 -08:00
|
|
|
GetRoom(OwnedRoomId, ClientReply<IambResult<FetchedRoom>>),
|
2022-12-29 18:00:59 -08:00
|
|
|
JoinRoom(String, ClientReply<IambResult<OwnedRoomId>>),
|
2023-01-04 12:51:33 -08:00
|
|
|
Members(OwnedRoomId, ClientReply<IambResult<Vec<RoomMember>>>),
|
2022-12-29 18:00:59 -08:00
|
|
|
SpaceMembers(OwnedRoomId, ClientReply<IambResult<Vec<OwnedRoomId>>>),
|
2023-01-03 13:57:28 -08:00
|
|
|
TypingNotice(OwnedRoomId),
|
2022-12-29 18:00:59 -08:00
|
|
|
Verify(VerifyAction, SasVerification, ClientReply<IambResult<EditInfo>>),
|
|
|
|
VerifyRequest(OwnedUserId, ClientReply<IambResult<EditInfo>>),
|
|
|
|
}
|
|
|
|
|
2023-01-10 19:59:30 -08:00
|
|
|
impl Debug for WorkerTask {
|
|
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
|
|
|
match self {
|
|
|
|
WorkerTask::Init(_, _) => {
|
|
|
|
f.debug_tuple("WorkerTask::Init")
|
|
|
|
.field(&format_args!("_"))
|
|
|
|
.field(&format_args!("_"))
|
|
|
|
.finish()
|
|
|
|
},
|
|
|
|
WorkerTask::Login(style, _) => {
|
|
|
|
f.debug_tuple("WorkerTask::Login")
|
|
|
|
.field(style)
|
|
|
|
.field(&format_args!("_"))
|
|
|
|
.finish()
|
|
|
|
},
|
2023-10-13 00:58:59 -05:00
|
|
|
WorkerTask::Logout(user_id, _) => {
|
|
|
|
f.debug_tuple("WorkerTask::Logout").field(user_id).finish()
|
|
|
|
},
|
2023-01-11 17:54:49 -08:00
|
|
|
WorkerTask::GetInviter(invite, _) => {
|
|
|
|
f.debug_tuple("WorkerTask::GetInviter").field(invite).finish()
|
|
|
|
},
|
2023-01-10 19:59:30 -08:00
|
|
|
WorkerTask::GetRoom(room_id, _) => {
|
|
|
|
f.debug_tuple("WorkerTask::GetRoom")
|
|
|
|
.field(room_id)
|
|
|
|
.field(&format_args!("_"))
|
|
|
|
.finish()
|
|
|
|
},
|
|
|
|
WorkerTask::JoinRoom(s, _) => {
|
|
|
|
f.debug_tuple("WorkerTask::JoinRoom")
|
|
|
|
.field(s)
|
|
|
|
.field(&format_args!("_"))
|
|
|
|
.finish()
|
|
|
|
},
|
|
|
|
WorkerTask::Members(room_id, _) => {
|
|
|
|
f.debug_tuple("WorkerTask::Members")
|
|
|
|
.field(room_id)
|
|
|
|
.field(&format_args!("_"))
|
|
|
|
.finish()
|
|
|
|
},
|
|
|
|
WorkerTask::SpaceMembers(room_id, _) => {
|
|
|
|
f.debug_tuple("WorkerTask::SpaceMembers")
|
|
|
|
.field(room_id)
|
|
|
|
.field(&format_args!("_"))
|
|
|
|
.finish()
|
|
|
|
},
|
|
|
|
WorkerTask::TypingNotice(room_id) => {
|
|
|
|
f.debug_tuple("WorkerTask::TypingNotice").field(room_id).finish()
|
|
|
|
},
|
|
|
|
WorkerTask::Verify(act, sasv1, _) => {
|
|
|
|
f.debug_tuple("WorkerTask::Verify")
|
|
|
|
.field(act)
|
|
|
|
.field(sasv1)
|
|
|
|
.field(&format_args!("_"))
|
|
|
|
.finish()
|
|
|
|
},
|
|
|
|
WorkerTask::VerifyRequest(user_id, _) => {
|
|
|
|
f.debug_tuple("WorkerTask::VerifyRequest")
|
|
|
|
.field(user_id)
|
|
|
|
.field(&format_args!("_"))
|
|
|
|
.finish()
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
async fn create_client_inner(
|
|
|
|
homeserver: &Option<Url>,
|
|
|
|
settings: &ApplicationSettings,
|
|
|
|
) -> Result<Client, ClientBuildError> {
|
|
|
|
let req_timeout = Duration::from_secs(settings.tunables.request_timeout);
|
|
|
|
|
|
|
|
// Set up the HTTP client.
|
|
|
|
let http = reqwest::Client::builder()
|
|
|
|
.user_agent(IAMB_USER_AGENT)
|
|
|
|
.timeout(req_timeout)
|
|
|
|
.pool_idle_timeout(Duration::from_secs(60))
|
|
|
|
.pool_max_idle_per_host(10)
|
|
|
|
.tcp_keepalive(Duration::from_secs(10))
|
|
|
|
.build()
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
let req_config = RequestConfig::new().timeout(req_timeout).retry_timeout(req_timeout);
|
|
|
|
|
|
|
|
// Set up the Matrix client for the selected profile.
|
|
|
|
let builder = Client::builder()
|
|
|
|
.http_client(http)
|
|
|
|
.sqlite_store(settings.sqlite_dir.as_path(), None)
|
2024-03-02 15:55:27 -08:00
|
|
|
.request_config(req_config)
|
2024-03-06 23:49:35 -08:00
|
|
|
.with_encryption_settings(DEFAULT_ENCRYPTION_SETTINGS);
|
2024-03-02 15:00:29 -08:00
|
|
|
|
|
|
|
let builder = if let Some(url) = homeserver {
|
|
|
|
// Use the explicitly specified homeserver.
|
|
|
|
builder.homeserver_url(url.as_str())
|
|
|
|
} else {
|
|
|
|
// Try to discover the homeserver from the user ID.
|
|
|
|
let account = &settings.profile;
|
|
|
|
builder.server_name(account.user_id.server_name())
|
|
|
|
};
|
|
|
|
|
|
|
|
builder.build().await
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn create_client(settings: &ApplicationSettings) -> Client {
|
|
|
|
let account = &settings.profile;
|
|
|
|
let res = match create_client_inner(&account.url, settings).await {
|
|
|
|
Err(ClientBuildError::AutoDiscovery(_)) => {
|
|
|
|
let url = format!("https://{}/", account.user_id.server_name().as_str());
|
|
|
|
let url = Url::parse(&url).unwrap();
|
|
|
|
create_client_inner(&Some(url), settings).await
|
|
|
|
},
|
|
|
|
res => res,
|
|
|
|
};
|
|
|
|
|
|
|
|
res.expect("Failed to instantiate client")
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Requester {
|
2023-01-10 19:59:30 -08:00
|
|
|
pub client: Client,
|
|
|
|
pub tx: UnboundedSender<WorkerTask>,
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Requester {
|
|
|
|
pub fn init(&self, store: AsyncProgramStore) {
|
|
|
|
let (reply, response) = oneshot();
|
|
|
|
|
|
|
|
self.tx.send(WorkerTask::Init(store, reply)).unwrap();
|
|
|
|
|
|
|
|
return response.recv();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn login(&self, style: LoginStyle) -> IambResult<EditInfo> {
|
|
|
|
let (reply, response) = oneshot();
|
|
|
|
|
|
|
|
self.tx.send(WorkerTask::Login(style, reply)).unwrap();
|
|
|
|
|
|
|
|
return response.recv();
|
|
|
|
}
|
|
|
|
|
2023-10-13 00:58:59 -05:00
|
|
|
pub fn logout(&self, user_id: String) -> IambResult<EditInfo> {
|
|
|
|
let (reply, response) = oneshot();
|
|
|
|
|
|
|
|
self.tx.send(WorkerTask::Logout(user_id, reply)).unwrap();
|
|
|
|
|
|
|
|
return response.recv();
|
|
|
|
}
|
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
pub fn get_inviter(&self, invite: MatrixRoom) -> IambResult<Option<RoomMember>> {
|
2023-01-11 17:54:49 -08:00
|
|
|
let (reply, response) = oneshot();
|
|
|
|
|
|
|
|
self.tx.send(WorkerTask::GetInviter(invite, reply)).unwrap();
|
|
|
|
|
|
|
|
return response.recv();
|
|
|
|
}
|
|
|
|
|
2023-01-26 15:23:15 -08:00
|
|
|
pub fn get_room(&self, room_id: OwnedRoomId) -> IambResult<FetchedRoom> {
|
2022-12-29 18:00:59 -08:00
|
|
|
let (reply, response) = oneshot();
|
|
|
|
|
|
|
|
self.tx.send(WorkerTask::GetRoom(room_id, reply)).unwrap();
|
|
|
|
|
|
|
|
return response.recv();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn join_room(&self, name: String) -> IambResult<OwnedRoomId> {
|
|
|
|
let (reply, response) = oneshot();
|
|
|
|
|
|
|
|
self.tx.send(WorkerTask::JoinRoom(name, reply)).unwrap();
|
|
|
|
|
|
|
|
return response.recv();
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:51:33 -08:00
|
|
|
pub fn members(&self, room_id: OwnedRoomId) -> IambResult<Vec<RoomMember>> {
|
|
|
|
let (reply, response) = oneshot();
|
|
|
|
|
|
|
|
self.tx.send(WorkerTask::Members(room_id, reply)).unwrap();
|
|
|
|
|
|
|
|
return response.recv();
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
pub fn space_members(&self, space: OwnedRoomId) -> IambResult<Vec<OwnedRoomId>> {
|
|
|
|
let (reply, response) = oneshot();
|
|
|
|
|
|
|
|
self.tx.send(WorkerTask::SpaceMembers(space, reply)).unwrap();
|
|
|
|
|
|
|
|
return response.recv();
|
|
|
|
}
|
|
|
|
|
2023-01-03 13:57:28 -08:00
|
|
|
pub fn typing_notice(&self, room_id: OwnedRoomId) {
|
|
|
|
self.tx.send(WorkerTask::TypingNotice(room_id)).unwrap();
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
pub fn verify(&self, act: VerifyAction, sas: SasVerification) -> IambResult<EditInfo> {
|
|
|
|
let (reply, response) = oneshot();
|
|
|
|
|
|
|
|
self.tx.send(WorkerTask::Verify(act, sas, reply)).unwrap();
|
|
|
|
|
|
|
|
return response.recv();
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn verify_request(&self, user_id: OwnedUserId) -> IambResult<EditInfo> {
|
|
|
|
let (reply, response) = oneshot();
|
|
|
|
|
|
|
|
self.tx.send(WorkerTask::VerifyRequest(user_id, reply)).unwrap();
|
|
|
|
|
|
|
|
return response.recv();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct ClientWorker {
|
|
|
|
initialized: bool,
|
|
|
|
settings: ApplicationSettings,
|
|
|
|
client: Client,
|
2023-03-13 10:46:26 -07:00
|
|
|
load_handle: Option<JoinHandle<()>>,
|
|
|
|
sync_handle: Option<JoinHandle<()>>,
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
impl ClientWorker {
|
2024-03-02 15:00:29 -08:00
|
|
|
pub async fn spawn(client: Client, settings: ApplicationSettings) -> Requester {
|
2023-01-10 19:59:30 -08:00
|
|
|
let (tx, rx) = unbounded_channel();
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-01-10 19:59:30 -08:00
|
|
|
let mut worker = ClientWorker {
|
|
|
|
initialized: false,
|
|
|
|
settings,
|
|
|
|
client: client.clone(),
|
2023-03-13 10:46:26 -07:00
|
|
|
load_handle: None,
|
|
|
|
sync_handle: None,
|
2023-01-10 19:59:30 -08:00
|
|
|
};
|
|
|
|
|
2023-01-30 13:51:32 -08:00
|
|
|
tokio::spawn(async move {
|
2022-12-29 18:00:59 -08:00
|
|
|
worker.work(rx).await;
|
|
|
|
});
|
|
|
|
|
2023-01-10 19:59:30 -08:00
|
|
|
return Requester { client, tx };
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
2023-01-10 19:59:30 -08:00
|
|
|
async fn work(&mut self, mut rx: UnboundedReceiver<WorkerTask>) {
|
2022-12-29 18:00:59 -08:00
|
|
|
loop {
|
2023-01-10 19:59:30 -08:00
|
|
|
let t = rx.recv().await;
|
2022-12-29 18:00:59 -08:00
|
|
|
|
|
|
|
match t {
|
2023-01-10 19:59:30 -08:00
|
|
|
Some(task) => self.run(task).await,
|
|
|
|
None => {
|
2022-12-29 18:00:59 -08:00
|
|
|
break;
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(handle) = self.sync_handle.take() {
|
|
|
|
handle.abort();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn run(&mut self, task: WorkerTask) {
|
|
|
|
match task {
|
|
|
|
WorkerTask::Init(store, reply) => {
|
|
|
|
assert_eq!(self.initialized, false);
|
|
|
|
self.init(store).await;
|
|
|
|
reply.send(());
|
|
|
|
},
|
|
|
|
WorkerTask::JoinRoom(room_id, reply) => {
|
|
|
|
assert!(self.initialized);
|
|
|
|
reply.send(self.join_room(room_id).await);
|
|
|
|
},
|
2023-01-11 17:54:49 -08:00
|
|
|
WorkerTask::GetInviter(invited, reply) => {
|
|
|
|
assert!(self.initialized);
|
|
|
|
reply.send(self.get_inviter(invited).await);
|
|
|
|
},
|
2022-12-29 18:00:59 -08:00
|
|
|
WorkerTask::GetRoom(room_id, reply) => {
|
|
|
|
assert!(self.initialized);
|
|
|
|
reply.send(self.get_room(room_id).await);
|
|
|
|
},
|
|
|
|
WorkerTask::Login(style, reply) => {
|
|
|
|
assert!(self.initialized);
|
|
|
|
reply.send(self.login_and_sync(style).await);
|
|
|
|
},
|
2023-10-13 00:58:59 -05:00
|
|
|
WorkerTask::Logout(user_id, reply) => {
|
|
|
|
assert!(self.initialized);
|
|
|
|
reply.send(self.logout(user_id).await);
|
|
|
|
},
|
2023-01-04 12:51:33 -08:00
|
|
|
WorkerTask::Members(room_id, reply) => {
|
|
|
|
assert!(self.initialized);
|
|
|
|
reply.send(self.members(room_id).await);
|
|
|
|
},
|
2022-12-29 18:00:59 -08:00
|
|
|
WorkerTask::SpaceMembers(space, reply) => {
|
|
|
|
assert!(self.initialized);
|
|
|
|
reply.send(self.space_members(space).await);
|
|
|
|
},
|
2023-01-03 13:57:28 -08:00
|
|
|
WorkerTask::TypingNotice(room_id) => {
|
|
|
|
assert!(self.initialized);
|
|
|
|
self.typing_notice(room_id).await;
|
|
|
|
},
|
2022-12-29 18:00:59 -08:00
|
|
|
WorkerTask::Verify(act, sas, reply) => {
|
|
|
|
assert!(self.initialized);
|
|
|
|
reply.send(self.verify(act, sas).await);
|
|
|
|
},
|
|
|
|
WorkerTask::VerifyRequest(user_id, reply) => {
|
|
|
|
assert!(self.initialized);
|
|
|
|
reply.send(self.verify_request(user_id).await);
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn init(&mut self, store: AsyncProgramStore) {
|
2023-01-26 15:40:16 -08:00
|
|
|
self.client.add_event_handler_context(store.clone());
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-01-03 13:57:28 -08:00
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: SyncTypingEvent, room: MatrixRoom, store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
let room_id = room.room_id().to_owned();
|
|
|
|
let mut locked = store.lock().await;
|
|
|
|
|
|
|
|
let users = ev
|
|
|
|
.content
|
|
|
|
.user_ids
|
|
|
|
.into_iter()
|
|
|
|
.filter(|u| u != &locked.application.settings.profile.user_id)
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
locked.application.get_room_info(room_id).set_typing(users);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-03-01 18:46:33 -08:00
|
|
|
let _ =
|
|
|
|
self.client
|
|
|
|
.add_event_handler(|ev: PresenceEvent, store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
let mut locked = store.lock().await;
|
|
|
|
locked.application.presences.insert(ev.sender, ev.content.presence);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: SyncStateEvent<RoomNameEventContent>,
|
|
|
|
room: MatrixRoom,
|
|
|
|
store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
if let SyncStateEvent::Original(ev) = ev {
|
2024-03-02 15:00:29 -08:00
|
|
|
let room_id = room.room_id().to_owned();
|
|
|
|
let room_name = Some(ev.content.name);
|
|
|
|
let mut locked = store.lock().await;
|
|
|
|
let info = locked.application.rooms.get_or_default(room_id.clone());
|
|
|
|
info.name = room_name;
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: SyncMessageLikeEvent<RoomMessageEventContent>,
|
|
|
|
room: MatrixRoom,
|
|
|
|
client: Client,
|
|
|
|
store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
let room_id = room.room_id();
|
|
|
|
|
|
|
|
if let Some(msg) = ev.as_original() {
|
|
|
|
if let MessageType::VerificationRequest(_) = msg.content.msgtype {
|
|
|
|
if let Some(request) = client
|
|
|
|
.encryption()
|
|
|
|
.get_verification_request(ev.sender(), ev.event_id())
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
request.accept().await.expect("Failed to accept request");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut locked = store.lock().await;
|
2023-03-01 18:46:33 -08:00
|
|
|
|
|
|
|
let sender = ev.sender().to_owned();
|
|
|
|
let _ = locked.application.presences.get_or_default(sender);
|
|
|
|
|
2023-11-16 08:36:22 -08:00
|
|
|
let ChatStore { rooms, picker, settings, .. } = &mut locked.application;
|
|
|
|
let info = rooms.get_or_default(room_id.to_owned());
|
|
|
|
|
2023-10-15 18:12:39 -07:00
|
|
|
update_event_receipts(info, &room, ev.event_id()).await;
|
2023-11-16 08:36:22 -08:00
|
|
|
|
|
|
|
let full_ev = ev.into_full_event(room_id.to_owned());
|
|
|
|
info.insert_with_preview(
|
|
|
|
room_id.to_owned(),
|
|
|
|
store.clone(),
|
|
|
|
*picker,
|
|
|
|
full_ev,
|
|
|
|
settings,
|
|
|
|
client.media(),
|
|
|
|
);
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-02-09 17:53:33 -08:00
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: SyncMessageLikeEvent<ReactionEventContent>,
|
|
|
|
room: MatrixRoom,
|
|
|
|
store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
let room_id = room.room_id();
|
|
|
|
|
|
|
|
let mut locked = store.lock().await;
|
2023-03-01 18:46:33 -08:00
|
|
|
|
|
|
|
let sender = ev.sender().to_owned();
|
|
|
|
let _ = locked.application.presences.get_or_default(sender);
|
|
|
|
|
2023-02-09 17:53:33 -08:00
|
|
|
let info = locked.application.get_room_info(room_id.to_owned());
|
2023-10-15 18:12:39 -07:00
|
|
|
update_event_receipts(info, &room, ev.event_id()).await;
|
2023-02-09 17:53:33 -08:00
|
|
|
info.insert_reaction(ev.into_full_event(room_id.to_owned()));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-10-15 18:12:39 -07:00
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: SyncEphemeralRoomEvent<ReceiptEventContent>,
|
|
|
|
room: MatrixRoom,
|
|
|
|
store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
let room_id = room.room_id();
|
|
|
|
|
|
|
|
let mut locked = store.lock().await;
|
|
|
|
|
|
|
|
let info = locked.application.get_room_info(room_id.to_owned());
|
|
|
|
for (event_id, receipts) in ev.content.0.into_iter() {
|
|
|
|
let Some(receipts) = receipts.get(&ReceiptType::Read) else {
|
|
|
|
continue;
|
|
|
|
};
|
|
|
|
for user_id in receipts.keys() {
|
|
|
|
info.set_receipt(user_id.to_owned(), event_id.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-01-13 17:53:54 -08:00
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: OriginalSyncRoomRedactionEvent,
|
|
|
|
room: MatrixRoom,
|
|
|
|
store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
let room_id = room.room_id();
|
|
|
|
let room_info = room.clone_info();
|
|
|
|
let room_version = room_info.room_version().unwrap_or(&RoomVersionId::V1);
|
|
|
|
|
|
|
|
let mut locked = store.lock().await;
|
|
|
|
let info = locked.application.get_room_info(room_id.to_owned());
|
2024-03-23 19:20:06 -07:00
|
|
|
info.redact(ev, room_version);
|
2023-01-13 17:53:54 -08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-07-06 23:15:58 -07:00
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: OriginalSyncRoomMemberEvent,
|
|
|
|
room: MatrixRoom,
|
|
|
|
client: Client,
|
|
|
|
store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
let room_id = room.room_id();
|
|
|
|
let user_id = ev.state_key;
|
|
|
|
|
|
|
|
let ambiguous_name =
|
|
|
|
ev.content.displayname.as_deref().unwrap_or_else(|| user_id.localpart());
|
|
|
|
let ambiguous = client
|
|
|
|
.store()
|
|
|
|
.get_users_with_display_name(room_id, ambiguous_name)
|
|
|
|
.await
|
|
|
|
.map(|users| users.len() > 1)
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
let mut locked = store.lock().await;
|
|
|
|
let info = locked.application.get_room_info(room_id.to_owned());
|
|
|
|
|
|
|
|
if ambiguous {
|
|
|
|
info.display_names.remove(&user_id);
|
|
|
|
} else if let Some(display) = ev.content.displayname {
|
|
|
|
info.display_names.insert(user_id, display);
|
|
|
|
} else {
|
|
|
|
info.display_names.remove(&user_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: OriginalSyncKeyVerificationStartEvent,
|
|
|
|
client: Client,
|
|
|
|
store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
let tx_id = ev.content.relates_to.event_id.as_ref();
|
|
|
|
|
|
|
|
if let Some(Verification::SasV1(sas)) =
|
|
|
|
client.encryption().get_verification(&ev.sender, tx_id).await
|
|
|
|
{
|
|
|
|
sas.accept().await.unwrap();
|
|
|
|
|
|
|
|
store.lock().await.application.insert_sas(sas)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: OriginalSyncKeyVerificationKeyEvent,
|
|
|
|
client: Client,
|
|
|
|
store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
let tx_id = ev.content.relates_to.event_id.as_ref();
|
|
|
|
|
|
|
|
if let Some(Verification::SasV1(sas)) =
|
|
|
|
client.encryption().get_verification(&ev.sender, tx_id).await
|
|
|
|
{
|
|
|
|
store.lock().await.application.insert_sas(sas);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: OriginalSyncKeyVerificationDoneEvent,
|
|
|
|
client: Client,
|
|
|
|
store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
let tx_id = ev.content.relates_to.event_id.as_ref();
|
|
|
|
|
|
|
|
if let Some(Verification::SasV1(sas)) =
|
|
|
|
client.encryption().get_verification(&ev.sender, tx_id).await
|
|
|
|
{
|
|
|
|
store.lock().await.application.insert_sas(sas);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: ToDeviceKeyVerificationRequestEvent, client: Client| {
|
|
|
|
async move {
|
|
|
|
let request = client
|
|
|
|
.encryption()
|
|
|
|
.get_verification_request(&ev.sender, &ev.content.transaction_id)
|
2023-05-01 21:33:12 -07:00
|
|
|
.await;
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-05-01 21:33:12 -07:00
|
|
|
if let Some(request) = request {
|
|
|
|
request.accept().await.unwrap();
|
|
|
|
}
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: ToDeviceKeyVerificationStartEvent,
|
|
|
|
client: Client,
|
|
|
|
store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
let tx_id = ev.content.transaction_id;
|
|
|
|
|
|
|
|
if let Some(Verification::SasV1(sas)) =
|
|
|
|
client.encryption().get_verification(&ev.sender, tx_id.as_ref()).await
|
|
|
|
{
|
|
|
|
sas.accept().await.unwrap();
|
|
|
|
|
|
|
|
store.lock().await.application.insert_sas(sas);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: ToDeviceKeyVerificationKeyEvent, client: Client, store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
let tx_id = ev.content.transaction_id;
|
|
|
|
|
|
|
|
if let Some(Verification::SasV1(sas)) =
|
|
|
|
client.encryption().get_verification(&ev.sender, tx_id.as_ref()).await
|
|
|
|
{
|
|
|
|
store.lock().await.application.insert_sas(sas);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let _ = self.client.add_event_handler(
|
|
|
|
|ev: ToDeviceKeyVerificationDoneEvent,
|
|
|
|
client: Client,
|
|
|
|
store: Ctx<AsyncProgramStore>| {
|
|
|
|
async move {
|
|
|
|
let tx_id = ev.content.transaction_id;
|
|
|
|
|
|
|
|
if let Some(Verification::SasV1(sas)) =
|
|
|
|
client.encryption().get_verification(&ev.sender, tx_id.as_ref()).await
|
|
|
|
{
|
|
|
|
store.lock().await.application.insert_sas(sas);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
2023-03-13 10:46:26 -07:00
|
|
|
self.load_handle = tokio::spawn({
|
|
|
|
let client = self.client.clone();
|
2024-03-22 00:46:46 +00:00
|
|
|
let settings = self.settings.clone();
|
2023-03-13 10:46:26 -07:00
|
|
|
|
|
|
|
async move {
|
2024-03-28 21:14:37 -07:00
|
|
|
while !client.logged_in() {
|
|
|
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
|
|
|
}
|
|
|
|
|
2023-07-05 15:25:42 -07:00
|
|
|
let load = load_older_forever(&client, &store);
|
2023-10-15 18:12:39 -07:00
|
|
|
let rcpt = send_receipts_forever(&client, &store);
|
2023-07-05 15:25:42 -07:00
|
|
|
let room = refresh_rooms_forever(&client, &store);
|
2024-03-22 00:46:46 +00:00
|
|
|
let notifications = register_notifications(&client, &settings, &store);
|
|
|
|
let ((), (), (), ()) = tokio::join!(load, rcpt, room, notifications);
|
2023-01-26 15:40:16 -08:00
|
|
|
}
|
2023-01-30 13:51:32 -08:00
|
|
|
})
|
|
|
|
.into();
|
2023-01-26 15:40:16 -08:00
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
self.initialized = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn login_and_sync(&mut self, style: LoginStyle) -> IambResult<EditInfo> {
|
|
|
|
let client = self.client.clone();
|
|
|
|
|
|
|
|
match style {
|
|
|
|
LoginStyle::SessionRestore(session) => {
|
2024-03-02 15:00:29 -08:00
|
|
|
client.restore_session(session).await.map_err(IambError::from)?;
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
|
|
|
LoginStyle::Password(password) => {
|
|
|
|
let resp = client
|
2024-03-02 15:00:29 -08:00
|
|
|
.matrix_auth()
|
2022-12-29 18:00:59 -08:00
|
|
|
.login_username(&self.settings.profile.user_id, &password)
|
|
|
|
.initial_device_display_name(initial_devname().as_str())
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.map_err(IambError::from)?;
|
2024-03-02 15:00:29 -08:00
|
|
|
let session = MatrixSession::from(&resp);
|
|
|
|
self.settings.write_session(session)?;
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
2023-11-04 17:39:17 -04:00
|
|
|
LoginStyle::SingleSignOn => {
|
|
|
|
let resp = client
|
2024-03-02 15:00:29 -08:00
|
|
|
.matrix_auth()
|
2023-11-04 17:39:17 -04:00
|
|
|
.login_sso(|url| {
|
|
|
|
let opened = format!(
|
|
|
|
"The following URL should have been opened in your browser:\n {url}"
|
|
|
|
);
|
|
|
|
|
|
|
|
async move {
|
|
|
|
tokio::task::spawn_blocking(move || open::that(url));
|
|
|
|
println!("\n{opened}\n");
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.initial_device_display_name(initial_devname().as_str())
|
|
|
|
.send()
|
|
|
|
.await
|
|
|
|
.map_err(IambError::from)?;
|
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
let session = MatrixSession::from(&resp);
|
|
|
|
self.settings.write_session(session)?;
|
2023-11-04 17:39:17 -04:00
|
|
|
},
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
2023-07-07 22:35:33 -07:00
|
|
|
self.sync_handle = tokio::spawn(async move {
|
2022-12-29 18:00:59 -08:00
|
|
|
loop {
|
|
|
|
let settings = SyncSettings::default();
|
|
|
|
|
|
|
|
let _ = client.sync(settings).await;
|
|
|
|
}
|
2023-07-07 22:35:33 -07:00
|
|
|
})
|
|
|
|
.into();
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
Ok(Some(InfoMessage::from("* Successfully logged in!")))
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
2023-10-13 00:58:59 -05:00
|
|
|
async fn logout(&mut self, user_id: String) -> IambResult<EditInfo> {
|
|
|
|
// Verify that the user is logging out of the correct profile.
|
2024-03-20 22:30:14 -07:00
|
|
|
let curr = self.settings.profile.user_id.as_str();
|
2023-10-13 00:58:59 -05:00
|
|
|
|
|
|
|
if user_id != curr {
|
|
|
|
let msg = format!("Incorrect user ID (currently logged in as {curr})");
|
|
|
|
let err = UIError::Failure(msg);
|
|
|
|
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Send the logout request.
|
2024-03-02 15:00:29 -08:00
|
|
|
if let Err(e) = self.client.matrix_auth().logout().await {
|
2023-10-13 00:58:59 -05:00
|
|
|
let msg = format!("Failed to logout: {e}");
|
|
|
|
let err = UIError::Failure(msg);
|
|
|
|
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the session.json file.
|
|
|
|
std::fs::remove_file(&self.settings.session_json)?;
|
|
|
|
|
|
|
|
Ok(Some(InfoMessage::from("Sucessfully logged out")))
|
|
|
|
}
|
|
|
|
|
2023-07-05 15:25:42 -07:00
|
|
|
async fn direct_message(&mut self, user: OwnedUserId) -> IambResult<OwnedRoomId> {
|
|
|
|
for room in self.client.rooms() {
|
2024-03-02 15:00:29 -08:00
|
|
|
if !is_direct(&room).await {
|
2023-07-05 15:25:42 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
if room.get_member(user.as_ref()).await.map_err(IambError::from)?.is_some() {
|
2023-07-05 15:25:42 -07:00
|
|
|
return Ok(room.room_id().to_owned());
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-03 16:37:11 -08:00
|
|
|
let rt = CreateRoomType::Direct(user.clone());
|
|
|
|
let flags = CreateRoomFlags::ENCRYPTED;
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-07-05 15:25:42 -07:00
|
|
|
create_room(&self.client, None, rt, flags).await.map_err(|e| {
|
|
|
|
error!(
|
|
|
|
user_id = user.as_str(),
|
|
|
|
err = e.to_string(),
|
|
|
|
"Failed to create direct message room"
|
|
|
|
);
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-07-05 15:25:42 -07:00
|
|
|
let msg = format!("Could not open a room with {user}");
|
|
|
|
UIError::Failure(msg)
|
|
|
|
})
|
2022-12-29 18:00:59 -08:00
|
|
|
}
|
|
|
|
|
2024-03-02 15:00:29 -08:00
|
|
|
async fn get_inviter(&mut self, invited: MatrixRoom) -> IambResult<Option<RoomMember>> {
|
2023-01-11 17:54:49 -08:00
|
|
|
let details = invited.invite_details().await.map_err(IambError::from)?;
|
|
|
|
|
|
|
|
Ok(details.inviter)
|
|
|
|
}
|
|
|
|
|
2023-01-26 15:23:15 -08:00
|
|
|
async fn get_room(&mut self, room_id: OwnedRoomId) -> IambResult<FetchedRoom> {
|
2022-12-29 18:00:59 -08:00
|
|
|
if let Some(room) = self.client.get_room(&room_id) {
|
|
|
|
let name = room.display_name().await.map_err(IambError::from)?;
|
2023-01-25 17:54:16 -08:00
|
|
|
let tags = room.tags().await.map_err(IambError::from)?;
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-01-25 17:54:16 -08:00
|
|
|
Ok((room, name, tags))
|
2022-12-29 18:00:59 -08:00
|
|
|
} else {
|
|
|
|
Err(IambError::UnknownRoom(room_id).into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn join_room(&mut self, name: String) -> IambResult<OwnedRoomId> {
|
|
|
|
if let Ok(alias_id) = OwnedRoomOrAliasId::from_str(name.as_str()) {
|
|
|
|
match self.client.join_room_by_id_or_alias(&alias_id, &[]).await {
|
2024-03-02 15:00:29 -08:00
|
|
|
Ok(resp) => Ok(resp.room_id().to_owned()),
|
2022-12-29 18:00:59 -08:00
|
|
|
Err(e) => {
|
|
|
|
let msg = e.to_string();
|
|
|
|
let err = UIError::Failure(msg);
|
|
|
|
|
|
|
|
return Err(err);
|
|
|
|
},
|
|
|
|
}
|
|
|
|
} else if let Ok(user) = OwnedUserId::try_from(name.as_str()) {
|
2023-07-05 15:25:42 -07:00
|
|
|
self.direct_message(user).await
|
2022-12-29 18:00:59 -08:00
|
|
|
} else {
|
|
|
|
let msg = format!("{:?} is not a valid room or user name", name.as_str());
|
|
|
|
let err = UIError::Failure(msg);
|
|
|
|
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:51:33 -08:00
|
|
|
async fn members(&mut self, room_id: OwnedRoomId) -> IambResult<Vec<RoomMember>> {
|
|
|
|
if let Some(room) = self.client.get_room(room_id.as_ref()) {
|
2024-03-02 15:00:29 -08:00
|
|
|
Ok(room.members(RoomMemberships::ACTIVE).await.map_err(IambError::from)?)
|
2023-01-04 12:51:33 -08:00
|
|
|
} else {
|
|
|
|
Err(IambError::UnknownRoom(room_id).into())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
async fn space_members(&mut self, space: OwnedRoomId) -> IambResult<Vec<OwnedRoomId>> {
|
2024-03-02 15:00:29 -08:00
|
|
|
let mut req = SpaceHierarchyRequest::new(space);
|
2022-12-29 18:00:59 -08:00
|
|
|
req.limit = Some(1000u32.into());
|
|
|
|
req.max_depth = Some(1u32.into());
|
|
|
|
|
|
|
|
let resp = self.client.send(req, None).await.map_err(IambError::from)?;
|
|
|
|
|
|
|
|
let rooms = resp.rooms.into_iter().map(|chunk| chunk.room_id).collect();
|
|
|
|
|
|
|
|
Ok(rooms)
|
|
|
|
}
|
|
|
|
|
2023-01-03 13:57:28 -08:00
|
|
|
async fn typing_notice(&mut self, room_id: OwnedRoomId) {
|
2024-03-02 15:00:29 -08:00
|
|
|
if let Some(room) = self.client.get_room(room_id.as_ref()) {
|
2023-01-03 13:57:28 -08:00
|
|
|
let _ = room.typing_notice(true).await;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:00:59 -08:00
|
|
|
async fn verify(&self, action: VerifyAction, sas: SasVerification) -> IambResult<EditInfo> {
|
|
|
|
match action {
|
|
|
|
VerifyAction::Accept => {
|
|
|
|
sas.accept().await.map_err(IambError::from)?;
|
|
|
|
|
|
|
|
Ok(Some(InfoMessage::from("Accepted verification request")))
|
|
|
|
},
|
|
|
|
VerifyAction::Confirm => {
|
|
|
|
if sas.is_done() || sas.is_cancelled() {
|
|
|
|
let msg = "Can only confirm in-progress verifications!";
|
|
|
|
let err = UIError::Failure(msg.into());
|
|
|
|
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
sas.confirm().await.map_err(IambError::from)?;
|
|
|
|
|
|
|
|
Ok(Some(InfoMessage::from("Confirmed verification")))
|
|
|
|
},
|
|
|
|
VerifyAction::Cancel => {
|
|
|
|
if sas.is_done() || sas.is_cancelled() {
|
|
|
|
let msg = "Can only cancel in-progress verifications!";
|
|
|
|
let err = UIError::Failure(msg.into());
|
|
|
|
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
sas.cancel().await.map_err(IambError::from)?;
|
|
|
|
|
|
|
|
Ok(Some(InfoMessage::from("Cancelled verification")))
|
|
|
|
},
|
|
|
|
VerifyAction::Mismatch => {
|
|
|
|
if sas.is_done() || sas.is_cancelled() {
|
|
|
|
let msg = "Can only cancel in-progress verifications!";
|
|
|
|
let err = UIError::Failure(msg.into());
|
|
|
|
|
|
|
|
return Err(err);
|
|
|
|
}
|
|
|
|
|
|
|
|
sas.mismatch().await.map_err(IambError::from)?;
|
|
|
|
|
|
|
|
Ok(Some(InfoMessage::from("Cancelled verification")))
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn verify_request(&self, user_id: OwnedUserId) -> IambResult<EditInfo> {
|
|
|
|
let enc = self.client.encryption();
|
|
|
|
|
|
|
|
match enc.get_user_identity(user_id.as_ref()).await.map_err(IambError::from)? {
|
|
|
|
Some(identity) => {
|
|
|
|
let methods = vec![VerificationMethod::SasV1];
|
|
|
|
let request = identity.request_verification_with_methods(methods);
|
|
|
|
let _req = request.await.map_err(IambError::from)?;
|
2023-01-30 13:51:32 -08:00
|
|
|
let info = format!("Sent verification request to {user_id}");
|
2022-12-29 18:00:59 -08:00
|
|
|
|
2023-04-28 16:52:33 -07:00
|
|
|
Ok(Some(InfoMessage::from(info)))
|
2022-12-29 18:00:59 -08:00
|
|
|
},
|
|
|
|
None => {
|
2023-01-30 13:51:32 -08:00
|
|
|
let msg = format!("Could not find identity information for {user_id}");
|
2022-12-29 18:00:59 -08:00
|
|
|
let err = UIError::Failure(msg);
|
|
|
|
|
|
|
|
Err(err)
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|