mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-20 13:49:52 -07:00
Update to matrix-sdk@0.7.1 (#200)
This commit is contained in:
parent
1948d80ec8
commit
9732971fc2
13 changed files with 1579 additions and 754 deletions
30
src/base.rs
30
src/base.rs
|
@ -32,17 +32,18 @@ use url::Url;
|
|||
|
||||
use matrix_sdk::{
|
||||
encryption::verification::SasVerification,
|
||||
room::{Joined, Room as MatrixRoom},
|
||||
room::Room as MatrixRoom,
|
||||
ruma::{
|
||||
events::{
|
||||
reaction::ReactionEvent,
|
||||
relation::Replacement,
|
||||
room::encrypted::RoomEncryptedEvent,
|
||||
room::message::{
|
||||
OriginalRoomMessageEvent,
|
||||
Relation,
|
||||
Replacement,
|
||||
RoomMessageEvent,
|
||||
RoomMessageEventContent,
|
||||
RoomMessageEventContentWithoutRelation,
|
||||
},
|
||||
tag::{TagName, Tags},
|
||||
MessageLikeEvent,
|
||||
|
@ -55,6 +56,7 @@ use matrix_sdk::{
|
|||
RoomId,
|
||||
UserId,
|
||||
},
|
||||
RoomState as MatrixRoomState,
|
||||
};
|
||||
|
||||
use modalkit::{
|
||||
|
@ -581,6 +583,10 @@ pub enum IambError {
|
|||
#[error("Cryptographic storage error: {0}")]
|
||||
CryptoStore(#[from] matrix_sdk::encryption::CryptoStoreError),
|
||||
|
||||
/// A failure related to the cryptographic store.
|
||||
#[error("Cannot export keys from sled: {0}")]
|
||||
UpgradeSled(#[from] crate::sled_export::SledMigrationError),
|
||||
|
||||
/// An HTTP error.
|
||||
#[error("HTTP client error: {0}")]
|
||||
Http(#[from] matrix_sdk::HttpError),
|
||||
|
@ -809,9 +815,9 @@ impl RoomInfo {
|
|||
}
|
||||
|
||||
/// Insert an edit.
|
||||
pub fn insert_edit(&mut self, msg: Replacement) {
|
||||
pub fn insert_edit(&mut self, msg: Replacement<RoomMessageEventContentWithoutRelation>) {
|
||||
let event_id = msg.event_id;
|
||||
let new_content = msg.new_content;
|
||||
let new_msgtype = msg.new_content;
|
||||
|
||||
let key = if let Some(EventLocation::Message(k)) = self.keys.get(&event_id) {
|
||||
k
|
||||
|
@ -827,10 +833,10 @@ impl RoomInfo {
|
|||
|
||||
match &mut msg.event {
|
||||
MessageEvent::Original(orig) => {
|
||||
orig.content.msgtype = new_content.msgtype;
|
||||
orig.content.apply_replacement(new_msgtype);
|
||||
},
|
||||
MessageEvent::Local(_, content) => {
|
||||
content.msgtype = new_content.msgtype;
|
||||
content.apply_replacement(new_msgtype);
|
||||
},
|
||||
MessageEvent::Redacted(_) |
|
||||
MessageEvent::EncryptedOriginal(_) |
|
||||
|
@ -1182,8 +1188,16 @@ impl ChatStore {
|
|||
}
|
||||
|
||||
/// Get a joined room.
|
||||
pub fn get_joined_room(&self, room_id: &RoomId) -> Option<Joined> {
|
||||
self.worker.client.get_joined_room(room_id)
|
||||
pub fn get_joined_room(&self, room_id: &RoomId) -> Option<MatrixRoom> {
|
||||
let Some(room) = self.worker.client.get_room(room_id) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if room.state() == MatrixRoomState::Joined {
|
||||
Some(room)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the title for a room.
|
||||
|
|
126
src/config.rs
126
src/config.rs
|
@ -5,20 +5,29 @@ use std::collections::HashMap;
|
|||
use std::fmt;
|
||||
use std::fs::File;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::io::BufReader;
|
||||
use std::io::{BufReader, BufWriter};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
|
||||
use clap::Parser;
|
||||
use matrix_sdk::ruma::{OwnedRoomAliasId, OwnedRoomId, OwnedUserId, UserId};
|
||||
use matrix_sdk::matrix_auth::MatrixSession;
|
||||
use matrix_sdk::ruma::{OwnedDeviceId, OwnedRoomAliasId, OwnedRoomId, OwnedUserId, UserId};
|
||||
use ratatui::style::{Color, Modifier as StyleModifier, Style};
|
||||
use ratatui::text::Span;
|
||||
use ratatui_image::picker::ProtocolType;
|
||||
use serde::{de::Error as SerdeError, de::Visitor, Deserialize, Deserializer};
|
||||
use serde::{de::Error as SerdeError, de::Visitor, Deserialize, Deserializer, Serialize};
|
||||
use tracing::Level;
|
||||
use url::Url;
|
||||
|
||||
use super::base::{IambId, RoomInfo, SortColumn, SortFieldRoom, SortFieldUser, SortOrder};
|
||||
use super::base::{
|
||||
IambError,
|
||||
IambId,
|
||||
RoomInfo,
|
||||
SortColumn,
|
||||
SortFieldRoom,
|
||||
SortFieldUser,
|
||||
SortOrder,
|
||||
};
|
||||
|
||||
macro_rules! usage {
|
||||
( $($args: tt)* ) => {
|
||||
|
@ -215,6 +224,40 @@ impl<'de> Deserialize<'de> for UserColor {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
pub struct Session {
|
||||
access_token: String,
|
||||
refresh_token: Option<String>,
|
||||
user_id: OwnedUserId,
|
||||
device_id: OwnedDeviceId,
|
||||
}
|
||||
|
||||
impl From<Session> for MatrixSession {
|
||||
fn from(session: Session) -> Self {
|
||||
MatrixSession {
|
||||
tokens: matrix_sdk::matrix_auth::MatrixSessionTokens {
|
||||
access_token: session.access_token,
|
||||
refresh_token: session.refresh_token,
|
||||
},
|
||||
meta: matrix_sdk::SessionMeta {
|
||||
user_id: session.user_id,
|
||||
device_id: session.device_id,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MatrixSession> for Session {
|
||||
fn from(session: MatrixSession) -> Self {
|
||||
Session {
|
||||
access_token: session.tokens.access_token,
|
||||
refresh_token: session.tokens.refresh_token,
|
||||
user_id: session.meta.user_id,
|
||||
device_id: session.meta.device_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||
pub struct UserDisplayTunables {
|
||||
pub color: Option<UserColor>,
|
||||
|
@ -421,14 +464,35 @@ impl Tunables {
|
|||
#[derive(Clone)]
|
||||
pub struct DirectoryValues {
|
||||
pub cache: PathBuf,
|
||||
pub data: PathBuf,
|
||||
pub logs: PathBuf,
|
||||
pub downloads: Option<PathBuf>,
|
||||
pub image_previews: PathBuf,
|
||||
}
|
||||
|
||||
impl DirectoryValues {
|
||||
fn create_dir_all(&self) -> std::io::Result<()> {
|
||||
use std::fs::create_dir_all;
|
||||
|
||||
let Self { cache, data, logs, downloads, image_previews } = self;
|
||||
|
||||
create_dir_all(cache)?;
|
||||
create_dir_all(data)?;
|
||||
create_dir_all(logs)?;
|
||||
create_dir_all(image_previews)?;
|
||||
|
||||
if let Some(downloads) = downloads {
|
||||
create_dir_all(downloads)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, Deserialize)]
|
||||
pub struct Directories {
|
||||
pub cache: Option<PathBuf>,
|
||||
pub data: Option<PathBuf>,
|
||||
pub logs: Option<PathBuf>,
|
||||
pub downloads: Option<PathBuf>,
|
||||
pub image_previews: Option<PathBuf>,
|
||||
|
@ -438,6 +502,7 @@ impl Directories {
|
|||
fn merge(self, other: Self) -> Self {
|
||||
Directories {
|
||||
cache: self.cache.or(other.cache),
|
||||
data: self.data.or(other.data),
|
||||
logs: self.logs.or(other.logs),
|
||||
downloads: self.downloads.or(other.downloads),
|
||||
image_previews: self.image_previews.or(other.image_previews),
|
||||
|
@ -454,6 +519,15 @@ impl Directories {
|
|||
})
|
||||
.expect("no dirs.cache value configured!");
|
||||
|
||||
let data = self
|
||||
.data
|
||||
.or_else(|| {
|
||||
let mut dir = dirs::data_dir()?;
|
||||
dir.push("iamb");
|
||||
dir.into()
|
||||
})
|
||||
.expect("no dirs.data value configured!");
|
||||
|
||||
let logs = self.logs.unwrap_or_else(|| {
|
||||
let mut dir = cache.clone();
|
||||
dir.push("logs");
|
||||
|
@ -468,7 +542,7 @@ impl Directories {
|
|||
dir
|
||||
});
|
||||
|
||||
DirectoryValues { cache, logs, downloads, image_previews }
|
||||
DirectoryValues { cache, data, logs, downloads, image_previews }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -540,9 +614,11 @@ impl IambConfig {
|
|||
|
||||
#[derive(Clone)]
|
||||
pub struct ApplicationSettings {
|
||||
pub matrix_dir: PathBuf,
|
||||
pub layout_json: PathBuf,
|
||||
pub session_json: PathBuf,
|
||||
pub session_json_old: PathBuf,
|
||||
pub sled_dir: PathBuf,
|
||||
pub sqlite_dir: PathBuf,
|
||||
pub profile_name: String,
|
||||
pub profile: ProfileConfig,
|
||||
pub tunables: TunableValues,
|
||||
|
@ -602,17 +678,30 @@ impl ApplicationSettings {
|
|||
let dirs = profile.dirs.take().unwrap_or_default().merge(dirs);
|
||||
let dirs = dirs.values();
|
||||
|
||||
// Create directories
|
||||
dirs.create_dir_all()?;
|
||||
|
||||
// Set up paths that live inside the profile's data directory.
|
||||
let mut profile_dir = config_dir.clone();
|
||||
profile_dir.push("profiles");
|
||||
profile_dir.push(profile_name.as_str());
|
||||
|
||||
let mut matrix_dir = profile_dir.clone();
|
||||
matrix_dir.push("matrix");
|
||||
let mut profile_data_dir = dirs.data.clone();
|
||||
profile_data_dir.push("profiles");
|
||||
profile_data_dir.push(profile_name.as_str());
|
||||
|
||||
let mut session_json = profile_dir;
|
||||
let mut sled_dir = profile_dir.clone();
|
||||
sled_dir.push("matrix");
|
||||
|
||||
let mut sqlite_dir = profile_data_dir.clone();
|
||||
sqlite_dir.push("sqlite");
|
||||
|
||||
let mut session_json = profile_data_dir.clone();
|
||||
session_json.push("session.json");
|
||||
|
||||
let mut session_json_old = profile_dir;
|
||||
session_json_old.push("session.json");
|
||||
|
||||
// Set up paths that live inside the profile's cache directory.
|
||||
let mut cache_dir = dirs.cache.clone();
|
||||
cache_dir.push("profiles");
|
||||
|
@ -622,9 +711,11 @@ impl ApplicationSettings {
|
|||
layout_json.push("layout.json");
|
||||
|
||||
let settings = ApplicationSettings {
|
||||
matrix_dir,
|
||||
sled_dir,
|
||||
layout_json,
|
||||
session_json,
|
||||
session_json_old,
|
||||
sqlite_dir,
|
||||
profile_name,
|
||||
profile,
|
||||
tunables,
|
||||
|
@ -635,6 +726,21 @@ impl ApplicationSettings {
|
|||
Ok(settings)
|
||||
}
|
||||
|
||||
pub fn read_session(&self, path: impl AsRef<Path>) -> Result<Session, IambError> {
|
||||
let file = File::open(path)?;
|
||||
let reader = BufReader::new(file);
|
||||
let session = serde_json::from_reader(reader).map_err(IambError::from)?;
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
pub fn write_session(&self, session: MatrixSession) -> Result<(), IambError> {
|
||||
let file = File::create(self.session_json.as_path())?;
|
||||
let writer = BufWriter::new(file);
|
||||
let session = Session::from(session);
|
||||
serde_json::to_writer(writer, &session).map_err(IambError::from)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_user_char_span<'a>(&self, user_id: &'a UserId) -> Span<'a> {
|
||||
let (color, c) = self
|
||||
.tunables
|
||||
|
|
210
src/main.rs
210
src/main.rs
|
@ -19,7 +19,7 @@ use std::collections::VecDeque;
|
|||
use std::convert::TryFrom;
|
||||
use std::fmt::Display;
|
||||
use std::fs::{create_dir_all, File};
|
||||
use std::io::{stdout, BufReader, BufWriter, Stdout};
|
||||
use std::io::{stdout, BufWriter, Stdout};
|
||||
use std::ops::DerefMut;
|
||||
use std::process;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
@ -27,7 +27,10 @@ use std::sync::Arc;
|
|||
use std::time::Duration;
|
||||
|
||||
use clap::Parser;
|
||||
use matrix_sdk::crypto::encrypt_room_key_export;
|
||||
use matrix_sdk::ruma::OwnedUserId;
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use temp_dir::TempDir;
|
||||
use tokio::sync::Mutex as AsyncMutex;
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
|
||||
|
@ -62,6 +65,7 @@ mod config;
|
|||
mod keybindings;
|
||||
mod message;
|
||||
mod preview;
|
||||
mod sled_export;
|
||||
mod util;
|
||||
mod windows;
|
||||
mod worker;
|
||||
|
@ -558,7 +562,7 @@ impl Application {
|
|||
match action {
|
||||
HomeserverAction::CreateRoom(alias, vis, flags) => {
|
||||
let client = &store.application.worker.client;
|
||||
let room_id = create_room(client, alias.as_deref(), vis, flags).await?;
|
||||
let room_id = create_room(client, alias, vis, flags).await?;
|
||||
let room = IambId::Room(room_id);
|
||||
let target = OpenTarget::Application(room);
|
||||
let action = WindowAction::Switch(target);
|
||||
|
@ -659,36 +663,57 @@ impl Application {
|
|||
}
|
||||
}
|
||||
|
||||
async fn login(worker: Requester, settings: &ApplicationSettings) -> IambResult<()> {
|
||||
println!("Logging in for {}...", settings.profile.user_id);
|
||||
fn gen_passphrase() -> String {
|
||||
rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(20)
|
||||
.map(char::from)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn read_response(question: &str) -> String {
|
||||
println!("{question}");
|
||||
let mut input = String::new();
|
||||
let _ = std::io::stdin().read_line(&mut input);
|
||||
input
|
||||
}
|
||||
|
||||
fn read_yesno(question: &str) -> Option<char> {
|
||||
read_response(question).chars().next().map(|c| c.to_ascii_lowercase())
|
||||
}
|
||||
|
||||
async fn login(worker: &Requester, settings: &ApplicationSettings) -> IambResult<()> {
|
||||
if settings.session_json.is_file() {
|
||||
let file = File::open(settings.session_json.as_path())?;
|
||||
let reader = BufReader::new(file);
|
||||
let session = serde_json::from_reader(reader).map_err(IambError::from)?;
|
||||
let session = settings.read_session(&settings.session_json)?;
|
||||
worker.login(LoginStyle::SessionRestore(session.into()))?;
|
||||
|
||||
worker.login(LoginStyle::SessionRestore(session))?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if settings.session_json_old.is_file() && !settings.sled_dir.is_dir() {
|
||||
let session = settings.read_session(&settings.session_json_old)?;
|
||||
worker.login(LoginStyle::SessionRestore(session.into()))?;
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
loop {
|
||||
println!("Please select login type: [p]assword / [s]ingle sign on");
|
||||
|
||||
let mut input = String::new();
|
||||
std::io::stdin().read_line(&mut input).unwrap();
|
||||
|
||||
let login_style = match input.chars().next().map(|c| c.to_ascii_lowercase()) {
|
||||
None | Some('p') => {
|
||||
let password = rpassword::prompt_password("Password: ")?;
|
||||
LoginStyle::Password(password)
|
||||
},
|
||||
Some('s') => LoginStyle::SingleSignOn,
|
||||
Some(_) => {
|
||||
println!("Failed to login. Please enter 'p' or 's'");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
let login_style =
|
||||
match read_response("Please select login type: [p]assword / [s]ingle sign on")
|
||||
.chars()
|
||||
.next()
|
||||
.map(|c| c.to_ascii_lowercase())
|
||||
{
|
||||
None | Some('p') => {
|
||||
let password = rpassword::prompt_password("Password: ")?;
|
||||
LoginStyle::Password(password)
|
||||
},
|
||||
Some('s') => LoginStyle::SingleSignOn,
|
||||
Some(_) => {
|
||||
println!("Failed to login. Please enter 'p' or 's'");
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
match worker.login(login_style) {
|
||||
Ok(info) => {
|
||||
|
@ -713,17 +738,142 @@ fn print_exit<T: Display, N>(v: T) -> N {
|
|||
process::exit(2);
|
||||
}
|
||||
|
||||
// We can't access the OlmMachine directly, so write the keys to a temporary
|
||||
// file first, and then import them later.
|
||||
async fn check_import_keys(
|
||||
settings: &ApplicationSettings,
|
||||
) -> IambResult<Option<(temp_dir::TempDir, String)>> {
|
||||
let do_import = settings.sled_dir.is_dir() && !settings.sqlite_dir.is_dir();
|
||||
|
||||
if !do_import {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let question = format!(
|
||||
"Found old sled store in {}. Would you like to export room keys from it? [y]es/[n]o",
|
||||
settings.sled_dir.display()
|
||||
);
|
||||
|
||||
loop {
|
||||
match read_yesno(&question) {
|
||||
Some('y') => {
|
||||
break;
|
||||
},
|
||||
Some('n') => {
|
||||
return Ok(None);
|
||||
},
|
||||
Some(_) | None => {
|
||||
continue;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let keys = sled_export::export_room_keys(&settings.sled_dir).await?;
|
||||
let passphrase = gen_passphrase();
|
||||
|
||||
println!("* Encrypting {} room keys with the passphrase {passphrase:?}...", keys.len());
|
||||
|
||||
let encrypted = match encrypt_room_key_export(&keys, &passphrase, 500000) {
|
||||
Ok(encrypted) => encrypted,
|
||||
Err(e) => {
|
||||
format!("* Failed to encrypt room keys during export: {e}");
|
||||
process::exit(2);
|
||||
},
|
||||
};
|
||||
|
||||
let tmpdir = TempDir::new()?;
|
||||
let exported = tmpdir.child("keys");
|
||||
|
||||
println!("* Writing encrypted room keys to {}...", exported.display());
|
||||
tokio::fs::write(&exported, &encrypted).await?;
|
||||
|
||||
Ok(Some((tmpdir, passphrase)))
|
||||
}
|
||||
|
||||
async fn login_upgrade(
|
||||
keydir: TempDir,
|
||||
passphrase: String,
|
||||
worker: &Requester,
|
||||
settings: &ApplicationSettings,
|
||||
store: &AsyncProgramStore,
|
||||
) -> IambResult<()> {
|
||||
println!(
|
||||
"Please log in for {} to import the room keys into a new session",
|
||||
settings.profile.user_id
|
||||
);
|
||||
|
||||
login(worker, settings).await?;
|
||||
|
||||
println!("* Importing room keys...");
|
||||
|
||||
let exported = keydir.child("keys");
|
||||
let imported = worker.client.encryption().import_room_keys(exported, &passphrase).await;
|
||||
|
||||
match imported {
|
||||
Ok(res) => {
|
||||
println!(
|
||||
"* Successfully imported {} out of {} keys",
|
||||
res.imported_count, res.total_count
|
||||
);
|
||||
let _ = keydir.cleanup();
|
||||
},
|
||||
Err(e) => {
|
||||
println!(
|
||||
"Failed to import room keys from {}/keys: {e}\n\n\
|
||||
They have been encrypted with the passphrase {passphrase:?}.\
|
||||
Please save them and try importing them manually instead\n",
|
||||
keydir.path().display()
|
||||
);
|
||||
|
||||
loop {
|
||||
match read_yesno("Would you like to continue logging in? [y]es/[n]o") {
|
||||
Some('y') => break,
|
||||
Some('n') => print_exit("* Exiting..."),
|
||||
Some(_) | None => continue,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
println!("* Syncing...");
|
||||
worker::do_first_sync(&worker.client, store).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn login_normal(
|
||||
worker: &Requester,
|
||||
settings: &ApplicationSettings,
|
||||
store: &AsyncProgramStore,
|
||||
) -> IambResult<()> {
|
||||
println!("* Logging in for {}...", settings.profile.user_id);
|
||||
login(worker, settings).await?;
|
||||
worker::do_first_sync(&worker.client, store).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn run(settings: ApplicationSettings) -> IambResult<()> {
|
||||
// Get old keys the first time we run w/ the upgraded SDK.
|
||||
let import_keys = check_import_keys(&settings).await?;
|
||||
|
||||
// Set up client state.
|
||||
create_dir_all(settings.sqlite_dir.as_path())?;
|
||||
let client = worker::create_client(&settings).await;
|
||||
|
||||
// Set up the async worker thread and global store.
|
||||
let worker = ClientWorker::spawn(settings.clone()).await;
|
||||
let client = worker.client.clone();
|
||||
let worker = ClientWorker::spawn(client.clone(), settings.clone()).await;
|
||||
let store = ChatStore::new(worker.clone(), settings.clone());
|
||||
let store = Store::new(store);
|
||||
let store = Arc::new(AsyncMutex::new(store));
|
||||
worker.init(store.clone());
|
||||
|
||||
login(worker, &settings).await.unwrap_or_else(print_exit);
|
||||
worker::do_first_sync(client, &store).await;
|
||||
if let Some((keydir, pass)) = import_keys {
|
||||
login_upgrade(keydir, pass, &worker, &settings, &store)
|
||||
.await
|
||||
.unwrap_or_else(print_exit);
|
||||
} else {
|
||||
login_normal(&worker, &settings, &store).await.unwrap_or_else(print_exit);
|
||||
}
|
||||
|
||||
fn restore_tty() {
|
||||
let _ = crossterm::terminal::disable_raw_mode();
|
||||
|
@ -767,10 +917,6 @@ fn main() -> IambResult<()> {
|
|||
let log_prefix = format!("iamb-log-{}", settings.profile_name);
|
||||
let log_dir = settings.dirs.logs.as_path();
|
||||
|
||||
create_dir_all(settings.matrix_dir.as_path())?;
|
||||
create_dir_all(settings.dirs.image_previews.as_path())?;
|
||||
create_dir_all(log_dir)?;
|
||||
|
||||
let appender = tracing_appender::rolling::daily(log_dir, log_prefix);
|
||||
let (appender, guard) = tracing_appender::non_blocking(appender);
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ use std::hash::{Hash, Hasher};
|
|||
|
||||
use chrono::{DateTime, Local as LocalTz, NaiveDateTime, TimeZone};
|
||||
use comrak::{markdown_to_html, ComrakOptions};
|
||||
use serde_json::json;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use matrix_sdk::ruma::{
|
||||
|
@ -33,7 +34,7 @@ use matrix_sdk::ruma::{
|
|||
redaction::SyncRoomRedactionEvent,
|
||||
},
|
||||
AnyMessageLikeEvent,
|
||||
Redact,
|
||||
RedactContent,
|
||||
RedactedUnsigned,
|
||||
},
|
||||
EventId,
|
||||
|
@ -345,6 +346,28 @@ impl PartialOrd for MessageCursor {
|
|||
}
|
||||
}
|
||||
|
||||
fn redaction_reason(ev: &SyncRoomRedactionEvent) -> Option<&str> {
|
||||
let SyncRoomRedactionEvent::Original(ev) = ev else {
|
||||
return None;
|
||||
};
|
||||
|
||||
return ev.content.reason.as_deref();
|
||||
}
|
||||
|
||||
fn redaction_unsigned(ev: SyncRoomRedactionEvent) -> RedactedUnsigned {
|
||||
let reason = redaction_reason(&ev);
|
||||
let redacted_because = json!({
|
||||
"content": {
|
||||
"reason": reason
|
||||
},
|
||||
"event_id": ev.event_id(),
|
||||
"sender": ev.sender(),
|
||||
"origin_server_ts": ev.origin_server_ts(),
|
||||
"unsigned": {},
|
||||
});
|
||||
RedactedUnsigned::new(serde_json::from_value(redacted_because).unwrap())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum MessageEvent {
|
||||
EncryptedOriginal(Box<OriginalRoomEncryptedEvent>),
|
||||
|
@ -419,7 +442,14 @@ impl MessageEvent {
|
|||
MessageEvent::Redacted(_) => return,
|
||||
MessageEvent::Local(_, _) => return,
|
||||
MessageEvent::Original(ev) => {
|
||||
let redacted = ev.clone().redact(redaction, version);
|
||||
let redacted = RedactedRoomMessageEvent {
|
||||
content: ev.content.clone().redact(version),
|
||||
event_id: ev.event_id.clone(),
|
||||
sender: ev.sender.clone(),
|
||||
origin_server_ts: ev.origin_server_ts,
|
||||
room_id: ev.room_id.clone(),
|
||||
unsigned: redaction_unsigned(redaction),
|
||||
};
|
||||
*self = MessageEvent::Redacted(Box::new(redacted));
|
||||
},
|
||||
}
|
||||
|
@ -455,11 +485,7 @@ fn body_cow_content(content: &RoomMessageEventContent) -> Cow<'_, str> {
|
|||
}
|
||||
|
||||
fn body_cow_reason(unsigned: &RedactedUnsigned) -> Cow<'_, str> {
|
||||
let reason = unsigned
|
||||
.redacted_because
|
||||
.as_ref()
|
||||
.and_then(|e| e.as_original())
|
||||
.and_then(|r| r.content.reason.as_ref());
|
||||
let reason = unsigned.redacted_because.content.reason.as_ref();
|
||||
|
||||
if let Some(r) = reason {
|
||||
Cow::Owned(format!("[Redacted: {r:?}]"))
|
||||
|
|
58
src/sled_export.rs
Normal file
58
src/sled_export.rs
Normal file
|
@ -0,0 +1,58 @@
|
|||
//! # sled -> sqlite migration code
|
||||
//!
|
||||
//! Before the 0.0.9 release, iamb used matrix-sdk@0.6.2, which used [sled]
|
||||
//! for storing information, including room keys. In matrix-sdk@0.7.0,
|
||||
//! the SDK switched to using SQLite. This module takes care of opening
|
||||
//! sled, exporting the inbound group sessions used for decryption,
|
||||
//! and importing them into SQLite.
|
||||
//!
|
||||
//! This code will eventually be removed once people have been given enough
|
||||
//! time to upgrade off of pre-0.0.9 versions.
|
||||
//!
|
||||
//! [sled]: https://docs.rs/sled/0.34.7/sled/index.html
|
||||
use sled::{Config, IVec};
|
||||
use std::path::Path;
|
||||
|
||||
use crate::base::IambError;
|
||||
use matrix_sdk::crypto::olm::{ExportedRoomKey, InboundGroupSession, PickledInboundGroupSession};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum SledMigrationError {
|
||||
#[error("sled failure: {0}")]
|
||||
Sled(#[from] sled::Error),
|
||||
|
||||
#[error("deserialization failure: {0}")]
|
||||
Deserialize(#[from] serde_json::Error),
|
||||
}
|
||||
|
||||
fn group_session_from_slice(
|
||||
(_, bytes): (IVec, IVec),
|
||||
) -> Result<PickledInboundGroupSession, SledMigrationError> {
|
||||
serde_json::from_slice(&bytes).map_err(SledMigrationError::from)
|
||||
}
|
||||
|
||||
async fn export_room_keys_priv(
|
||||
sled_dir: &Path,
|
||||
) -> Result<Vec<ExportedRoomKey>, SledMigrationError> {
|
||||
let path = sled_dir.join("matrix-sdk-state");
|
||||
let store = Config::new().temporary(false).path(&path).open()?;
|
||||
let inbound_groups = store.open_tree("inbound_group_sessions")?;
|
||||
|
||||
let mut exported = vec![];
|
||||
let sessions = inbound_groups
|
||||
.iter()
|
||||
.map(|p| p.map_err(SledMigrationError::from).and_then(group_session_from_slice))
|
||||
.collect::<Result<Vec<_>, _>>()?
|
||||
.into_iter()
|
||||
.filter_map(|p| InboundGroupSession::from_pickle(p).ok());
|
||||
|
||||
for session in sessions {
|
||||
exported.push(session.export().await);
|
||||
}
|
||||
|
||||
Ok(exported)
|
||||
}
|
||||
|
||||
pub async fn export_room_keys(sled_dir: &Path) -> Result<Vec<ExportedRoomKey>, IambError> {
|
||||
export_room_keys_priv(sled_dir).await.map_err(IambError::from)
|
||||
}
|
|
@ -169,6 +169,7 @@ pub fn mock_room() -> RoomInfo {
|
|||
pub fn mock_dirs() -> DirectoryValues {
|
||||
DirectoryValues {
|
||||
cache: PathBuf::new(),
|
||||
data: PathBuf::new(),
|
||||
logs: PathBuf::new(),
|
||||
downloads: None,
|
||||
image_previews: PathBuf::new(),
|
||||
|
@ -202,9 +203,11 @@ pub fn mock_tunables() -> TunableValues {
|
|||
|
||||
pub fn mock_settings() -> ApplicationSettings {
|
||||
ApplicationSettings {
|
||||
matrix_dir: PathBuf::new(),
|
||||
layout_json: PathBuf::new(),
|
||||
session_json: PathBuf::new(),
|
||||
session_json_old: PathBuf::new(),
|
||||
sled_dir: PathBuf::new(),
|
||||
sqlite_dir: PathBuf::new(),
|
||||
|
||||
profile_name: "test".into(),
|
||||
profile: ProfileConfig {
|
||||
|
|
|
@ -14,14 +14,16 @@ use url::Url;
|
|||
use matrix_sdk::{
|
||||
attachment::AttachmentConfig,
|
||||
media::{MediaFormat, MediaRequest},
|
||||
room::{Joined, Room as MatrixRoom},
|
||||
room::Room as MatrixRoom,
|
||||
ruma::{
|
||||
events::reaction::{ReactionEventContent, Relation as Reaction},
|
||||
events::reaction::ReactionEventContent,
|
||||
events::relation::{Annotation, Replacement},
|
||||
events::room::message::{
|
||||
AddMentions,
|
||||
ForwardThread,
|
||||
MessageType,
|
||||
OriginalRoomMessageEvent,
|
||||
Relation,
|
||||
Replacement,
|
||||
RoomMessageEventContent,
|
||||
TextMessageEventContent,
|
||||
},
|
||||
|
@ -29,6 +31,7 @@ use matrix_sdk::{
|
|||
OwnedRoomId,
|
||||
RoomId,
|
||||
},
|
||||
RoomState,
|
||||
};
|
||||
|
||||
use ratatui::{
|
||||
|
@ -126,8 +129,16 @@ impl ChatState {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_joined(&self, worker: &Requester) -> Result<Joined, IambError> {
|
||||
worker.client.get_joined_room(self.id()).ok_or(IambError::NotJoined)
|
||||
fn get_joined(&self, worker: &Requester) -> Result<MatrixRoom, IambError> {
|
||||
let Some(room) = worker.client.get_room(self.id()) else {
|
||||
return Err(IambError::NotJoined);
|
||||
};
|
||||
|
||||
if room.state() == RoomState::Joined {
|
||||
Ok(room)
|
||||
} else {
|
||||
Err(IambError::NotJoined)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_reply_to<'a>(&self, info: &'a RoomInfo) -> Option<&'a OriginalRoomMessageEvent> {
|
||||
|
@ -356,9 +367,9 @@ impl ChatState {
|
|||
},
|
||||
};
|
||||
|
||||
let reaction = Reaction::new(event_id, emoji);
|
||||
let reaction = Annotation::new(event_id, emoji);
|
||||
let msg = ReactionEventContent::new(reaction);
|
||||
let _ = room.send(msg, None).await.map_err(IambError::from)?;
|
||||
let _ = room.send(msg).await.map_err(IambError::from)?;
|
||||
|
||||
Ok(None)
|
||||
},
|
||||
|
@ -449,12 +460,7 @@ impl ChatState {
|
|||
_: ProgramContext,
|
||||
store: &mut ProgramStore,
|
||||
) -> IambResult<EditInfo> {
|
||||
let room = store
|
||||
.application
|
||||
.worker
|
||||
.client
|
||||
.get_joined_room(self.id())
|
||||
.ok_or(IambError::NotJoined)?;
|
||||
let room = self.get_joined(&store.application.worker)?;
|
||||
let info = store.application.rooms.get_or_default(self.id().to_owned());
|
||||
let mut show_echo = true;
|
||||
|
||||
|
@ -475,18 +481,18 @@ impl ChatState {
|
|||
if let Some((_, event_id)) = &self.editing {
|
||||
msg.relates_to = Some(Relation::Replacement(Replacement::new(
|
||||
event_id.clone(),
|
||||
Box::new(msg.clone()),
|
||||
msg.msgtype.clone().into(),
|
||||
)));
|
||||
|
||||
show_echo = false;
|
||||
} else if let Some(m) = self.get_reply_to(info) {
|
||||
// XXX: Switch to RoomMessageEventContent::reply() once it's stable?
|
||||
msg = msg.make_reply_to(m);
|
||||
msg = msg.make_reply_to(m, ForwardThread::Yes, AddMentions::No);
|
||||
}
|
||||
|
||||
// XXX: second parameter can be a locally unique transaction id.
|
||||
// Useful for doing retries.
|
||||
let resp = room.send(msg.clone(), None).await.map_err(IambError::from)?;
|
||||
let resp = room.send(msg.clone()).await.map_err(IambError::from)?;
|
||||
let event_id = resp.event_id;
|
||||
|
||||
// Reset message bar state now that it's been sent.
|
||||
|
@ -506,7 +512,7 @@ impl ChatState {
|
|||
let config = AttachmentConfig::new();
|
||||
|
||||
let resp = room
|
||||
.send_attachment(name.as_ref(), &mime, bytes.as_ref(), config)
|
||||
.send_attachment(name.as_ref(), &mime, bytes, config)
|
||||
.await
|
||||
.map_err(IambError::from)?;
|
||||
|
||||
|
@ -536,7 +542,7 @@ impl ChatState {
|
|||
let config = AttachmentConfig::new();
|
||||
|
||||
let resp = room
|
||||
.send_attachment(name.as_ref(), &mime, bytes.as_ref(), config)
|
||||
.send_attachment(name.as_ref(), &mime, bytes, config)
|
||||
.await
|
||||
.map_err(IambError::from)?;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! # Windows for Matrix rooms and spaces
|
||||
use matrix_sdk::{
|
||||
room::{Invited, Room as MatrixRoom},
|
||||
room::Room as MatrixRoom,
|
||||
ruma::{
|
||||
events::{
|
||||
room::{name::RoomNameEventContent, topic::RoomTopicEventContent},
|
||||
|
@ -9,6 +9,7 @@ use matrix_sdk::{
|
|||
RoomId,
|
||||
},
|
||||
DisplayName,
|
||||
RoomState as MatrixRoomState,
|
||||
};
|
||||
|
||||
use ratatui::{
|
||||
|
@ -114,7 +115,7 @@ impl RoomState {
|
|||
|
||||
fn draw_invite(
|
||||
&self,
|
||||
invited: Invited,
|
||||
invited: MatrixRoom,
|
||||
area: Rect,
|
||||
buf: &mut Buffer,
|
||||
store: &mut ProgramStore,
|
||||
|
@ -177,12 +178,12 @@ impl RoomState {
|
|||
) -> IambResult<Vec<(Action<IambInfo>, ProgramContext)>> {
|
||||
match act {
|
||||
RoomAction::InviteAccept => {
|
||||
if let Some(room) = store.application.worker.client.get_invited_room(self.id()) {
|
||||
if let Some(room) = store.application.worker.client.get_room(self.id()) {
|
||||
let details = room.invite_details().await.map_err(IambError::from)?;
|
||||
let details = details.invitee.event().original_content();
|
||||
let is_direct = details.and_then(|ev| ev.is_direct).unwrap_or_default();
|
||||
|
||||
room.accept_invitation().await.map_err(IambError::from)?;
|
||||
room.join().await.map_err(IambError::from)?;
|
||||
|
||||
if is_direct {
|
||||
room.set_is_direct(true).await.map_err(IambError::from)?;
|
||||
|
@ -194,8 +195,8 @@ impl RoomState {
|
|||
}
|
||||
},
|
||||
RoomAction::InviteReject => {
|
||||
if let Some(room) = store.application.worker.client.get_invited_room(self.id()) {
|
||||
room.reject_invitation().await.map_err(IambError::from)?;
|
||||
if let Some(room) = store.application.worker.client.get_room(self.id()) {
|
||||
room.leave().await.map_err(IambError::from)?;
|
||||
|
||||
Ok(vec![])
|
||||
} else {
|
||||
|
@ -203,7 +204,7 @@ impl RoomState {
|
|||
}
|
||||
},
|
||||
RoomAction::InviteSend(user) => {
|
||||
if let Some(room) = store.application.worker.client.get_joined_room(self.id()) {
|
||||
if let Some(room) = store.application.worker.client.get_room(self.id()) {
|
||||
room.invite_user_by_id(user.as_ref()).await.map_err(IambError::from)?;
|
||||
|
||||
Ok(vec![])
|
||||
|
@ -212,7 +213,7 @@ impl RoomState {
|
|||
}
|
||||
},
|
||||
RoomAction::Leave(skip_confirm) => {
|
||||
if let Some(room) = store.application.worker.client.get_joined_room(self.id()) {
|
||||
if let Some(room) = store.application.worker.client.get_room(self.id()) {
|
||||
if skip_confirm {
|
||||
room.leave().await.map_err(IambError::from)?;
|
||||
|
||||
|
@ -247,7 +248,7 @@ impl RoomState {
|
|||
|
||||
match field {
|
||||
RoomField::Name => {
|
||||
let ev = RoomNameEventContent::new(value.into());
|
||||
let ev = RoomNameEventContent::new(value);
|
||||
let _ = room.send_state_event(ev).await.map_err(IambError::from)?;
|
||||
},
|
||||
RoomField::Tag(tag) => {
|
||||
|
@ -272,7 +273,7 @@ impl RoomState {
|
|||
|
||||
match field {
|
||||
RoomField::Name => {
|
||||
let ev = RoomNameEventContent::new(None);
|
||||
let ev = RoomNameEventContent::new("".into());
|
||||
let _ = room.send_state_event(ev).await.map_err(IambError::from)?;
|
||||
},
|
||||
RoomField::Tag(tag) => {
|
||||
|
@ -381,12 +382,12 @@ impl TerminalCursor for RoomState {
|
|||
|
||||
impl WindowOps<IambInfo> for RoomState {
|
||||
fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut ProgramStore) {
|
||||
if let MatrixRoom::Invited(_) = self.room() {
|
||||
if self.room().state() == MatrixRoomState::Invited {
|
||||
self.refresh_room(store);
|
||||
}
|
||||
|
||||
if let MatrixRoom::Invited(invited) = self.room() {
|
||||
self.draw_invite(invited.clone(), area, buf, store);
|
||||
if self.room().state() == MatrixRoomState::Invited {
|
||||
self.draw_invite(self.room().clone(), area, buf, store);
|
||||
}
|
||||
|
||||
match self {
|
||||
|
|
218
src/worker.rs
218
src/worker.rs
|
@ -5,8 +5,7 @@
|
|||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::fs::File;
|
||||
use std::io::BufWriter;
|
||||
use std::ops::Deref;
|
||||
use std::str::FromStr;
|
||||
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
||||
use std::sync::Arc;
|
||||
|
@ -17,13 +16,15 @@ use gethostname::gethostname;
|
|||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::{error, warn};
|
||||
use url::Url;
|
||||
|
||||
use matrix_sdk::{
|
||||
config::{RequestConfig, SyncSettings},
|
||||
encryption::verification::{SasVerification, Verification},
|
||||
event_handler::Ctx,
|
||||
matrix_auth::MatrixSession,
|
||||
reqwest,
|
||||
room::{Invited, Messages, MessagesOptions, Room as MatrixRoom, RoomMember},
|
||||
room::{Messages, MessagesOptions, Room as MatrixRoom, RoomMember},
|
||||
ruma::{
|
||||
api::client::{
|
||||
filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
|
||||
|
@ -42,7 +43,8 @@ use matrix_sdk::{
|
|||
},
|
||||
presence::PresenceEvent,
|
||||
reaction::ReactionEventContent,
|
||||
receipt::{ReceiptEventContent, ReceiptType},
|
||||
receipt::ReceiptType,
|
||||
receipt::{ReceiptEventContent, ReceiptThread},
|
||||
room::{
|
||||
encryption::RoomEncryptionEventContent,
|
||||
member::OriginalSyncRoomMemberEvent,
|
||||
|
@ -73,8 +75,9 @@ use matrix_sdk::{
|
|||
RoomVersionId,
|
||||
},
|
||||
Client,
|
||||
ClientBuildError,
|
||||
DisplayName,
|
||||
Session,
|
||||
RoomMemberships,
|
||||
};
|
||||
|
||||
use modalkit::errors::UIError;
|
||||
|
@ -106,9 +109,13 @@ fn initial_devname() -> String {
|
|||
format!("{} on {}", IAMB_DEVICE_NAME, gethostname().to_string_lossy())
|
||||
}
|
||||
|
||||
async fn is_direct(room: &MatrixRoom) -> bool {
|
||||
room.deref().is_direct().await.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub async fn create_room(
|
||||
client: &Client,
|
||||
room_alias_name: Option<&str>,
|
||||
room_alias_name: Option<String>,
|
||||
rt: CreateRoomType,
|
||||
flags: CreateRoomFlags,
|
||||
) -> IambResult<OwnedRoomId> {
|
||||
|
@ -154,8 +161,8 @@ pub async fn create_room(
|
|||
let request = assign!(CreateRoomRequest::new(), {
|
||||
room_alias_name,
|
||||
creation_content,
|
||||
initial_state: initial_state.as_slice(),
|
||||
invite: invite.as_slice(),
|
||||
initial_state,
|
||||
invite,
|
||||
is_direct,
|
||||
visibility,
|
||||
preset,
|
||||
|
@ -164,27 +171,31 @@ pub async fn create_room(
|
|||
let resp = client.create_room(request).await.map_err(IambError::from)?;
|
||||
|
||||
if is_direct {
|
||||
if let Some(room) = client.get_room(&resp.room_id) {
|
||||
if let Some(room) = client.get_room(resp.room_id()) {
|
||||
room.set_is_direct(true).await.map_err(IambError::from)?;
|
||||
} else {
|
||||
error!(
|
||||
room_id = resp.room_id.as_str(),
|
||||
room_id = resp.room_id().as_str(),
|
||||
"Couldn't set is_direct for new direct message room"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(resp.room_id);
|
||||
return Ok(resp.room_id().to_owned());
|
||||
}
|
||||
|
||||
async fn update_event_receipts(info: &mut RoomInfo, room: &MatrixRoom, event_id: &EventId) {
|
||||
let receipts = match room.event_read_receipts(event_id).await {
|
||||
let receipts = match room
|
||||
.load_event_receipts(ReceiptType::Read, ReceiptThread::Main, event_id)
|
||||
.await
|
||||
{
|
||||
Ok(receipts) => receipts,
|
||||
Err(e) => {
|
||||
tracing::warn!(?event_id, "failed to get event receipts: {e}");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
for (user_id, _) in receipts {
|
||||
info.set_receipt(user_id, event_id.to_owned());
|
||||
}
|
||||
|
@ -339,7 +350,10 @@ async fn load_older(client: &Client, store: &AsyncProgramStore) -> usize {
|
|||
|
||||
async fn members_load(client: &Client, room_id: &RoomId) -> IambResult<Vec<RoomMember>> {
|
||||
if let Some(room) = client.get_room(room_id) {
|
||||
Ok(room.members_no_sync().await.map_err(IambError::from)?)
|
||||
Ok(room
|
||||
.members_no_sync(RoomMemberships::all())
|
||||
.await
|
||||
.map_err(IambError::from)?)
|
||||
} else {
|
||||
Err(IambError::UnknownRoom(room_id.to_owned()).into())
|
||||
}
|
||||
|
@ -388,12 +402,12 @@ async fn refresh_rooms(client: &Client, store: &AsyncProgramStore) {
|
|||
|
||||
names.push((room.room_id().to_owned(), name));
|
||||
|
||||
if room.is_direct() {
|
||||
dms.push(Arc::new((room.into(), tags)));
|
||||
if is_direct(&room).await {
|
||||
dms.push(Arc::new((room, tags)));
|
||||
} else if room.is_space() {
|
||||
spaces.push(Arc::new((room.into(), tags)));
|
||||
spaces.push(Arc::new((room, tags)));
|
||||
} else {
|
||||
rooms.push(Arc::new((room.into(), tags)));
|
||||
rooms.push(Arc::new((room, tags)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -403,12 +417,12 @@ async fn refresh_rooms(client: &Client, store: &AsyncProgramStore) {
|
|||
|
||||
names.push((room.room_id().to_owned(), name));
|
||||
|
||||
if room.is_direct() {
|
||||
dms.push(Arc::new((room.into(), tags)));
|
||||
if is_direct(&room).await {
|
||||
dms.push(Arc::new((room, tags)));
|
||||
} else if room.is_space() {
|
||||
spaces.push(Arc::new((room.into(), tags)));
|
||||
spaces.push(Arc::new((room, tags)));
|
||||
} else {
|
||||
rooms.push(Arc::new((room.into(), tags)));
|
||||
rooms.push(Arc::new((room, tags)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -458,10 +472,20 @@ async fn send_receipts_forever(client: &Client, store: &AsyncProgramStore) {
|
|||
drop(locked);
|
||||
|
||||
for (room_id, new_receipt) in updates {
|
||||
let Some(room) = client.get_joined_room(&room_id) else {
|
||||
use matrix_sdk::ruma::api::client::receipt::create_receipt::v3::ReceiptType;
|
||||
|
||||
let Some(room) = client.get_room(&room_id) else {
|
||||
continue;
|
||||
};
|
||||
match room.read_receipt(&new_receipt).await {
|
||||
|
||||
match room
|
||||
.send_single_receipt(
|
||||
ReceiptType::Read,
|
||||
ReceiptThread::Unthreaded,
|
||||
new_receipt.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(()) => {
|
||||
sent.insert(room_id, new_receipt);
|
||||
},
|
||||
|
@ -471,7 +495,7 @@ async fn send_receipts_forever(client: &Client, store: &AsyncProgramStore) {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn do_first_sync(client: Client, store: &AsyncProgramStore) {
|
||||
pub async fn do_first_sync(client: &Client, store: &AsyncProgramStore) {
|
||||
// Perform an initial, lazily-loaded sync.
|
||||
let mut room = RoomEventFilter::default();
|
||||
room.lazy_load_options = LazyLoadOptions::Enabled { include_redundant_members: false };
|
||||
|
@ -490,7 +514,7 @@ pub async fn do_first_sync(client: Client, store: &AsyncProgramStore) {
|
|||
}
|
||||
|
||||
// Populate sync_info with our initial set of rooms/dms/spaces.
|
||||
refresh_rooms(&client, store).await;
|
||||
refresh_rooms(client, store).await;
|
||||
|
||||
// Insert Need::Messages to fetch accurate recent timestamps in the background.
|
||||
let mut locked = store.lock().await;
|
||||
|
@ -509,7 +533,7 @@ pub async fn do_first_sync(client: Client, store: &AsyncProgramStore) {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum LoginStyle {
|
||||
SessionRestore(Session),
|
||||
SessionRestore(MatrixSession),
|
||||
Password(String),
|
||||
SingleSignOn,
|
||||
}
|
||||
|
@ -543,7 +567,7 @@ pub enum WorkerTask {
|
|||
Init(AsyncProgramStore, ClientReply<()>),
|
||||
Login(LoginStyle, ClientReply<IambResult<EditInfo>>),
|
||||
Logout(String, ClientReply<IambResult<EditInfo>>),
|
||||
GetInviter(Invited, ClientReply<IambResult<Option<RoomMember>>>),
|
||||
GetInviter(MatrixRoom, ClientReply<IambResult<Option<RoomMember>>>),
|
||||
GetRoom(OwnedRoomId, ClientReply<IambResult<FetchedRoom>>),
|
||||
JoinRoom(String, ClientReply<IambResult<OwnedRoomId>>),
|
||||
Members(OwnedRoomId, ClientReply<IambResult<Vec<RoomMember>>>),
|
||||
|
@ -618,6 +642,56 @@ impl Debug for WorkerTask {
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
.request_config(req_config);
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Requester {
|
||||
pub client: Client,
|
||||
|
@ -649,7 +723,7 @@ impl Requester {
|
|||
return response.recv();
|
||||
}
|
||||
|
||||
pub fn get_inviter(&self, invite: Invited) -> IambResult<Option<RoomMember>> {
|
||||
pub fn get_inviter(&self, invite: MatrixRoom) -> IambResult<Option<RoomMember>> {
|
||||
let (reply, response) = oneshot();
|
||||
|
||||
self.tx.send(WorkerTask::GetInviter(invite, reply)).unwrap();
|
||||
|
@ -719,40 +793,8 @@ pub struct ClientWorker {
|
|||
}
|
||||
|
||||
impl ClientWorker {
|
||||
pub async fn spawn(settings: ApplicationSettings) -> Requester {
|
||||
pub async fn spawn(client: Client, settings: ApplicationSettings) -> Requester {
|
||||
let (tx, rx) = unbounded_channel();
|
||||
let account = &settings.profile;
|
||||
|
||||
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(Arc::new(http))
|
||||
.sled_store(settings.matrix_dir.as_path(), None)
|
||||
.expect("Failed to setup up sled store for Matrix SDK")
|
||||
.request_config(req_config);
|
||||
|
||||
let builder = if let Some(url) = account.url.as_ref() {
|
||||
// Use the explicitly specified homeserver.
|
||||
builder.homeserver_url(url.as_str())
|
||||
} else {
|
||||
// Try to discover the homeserver from the user ID.
|
||||
builder.server_name(account.user_id.server_name())
|
||||
};
|
||||
|
||||
let client = builder.build().await.expect("Failed to instantiate Matrix client");
|
||||
|
||||
let mut worker = ClientWorker {
|
||||
initialized: false,
|
||||
|
@ -872,13 +914,11 @@ impl ClientWorker {
|
|||
store: Ctx<AsyncProgramStore>| {
|
||||
async move {
|
||||
if let SyncStateEvent::Original(ev) = ev {
|
||||
if let Some(room_name) = ev.content.name {
|
||||
let room_id = room.room_id().to_owned();
|
||||
let room_name = Some(room_name.to_string());
|
||||
let mut locked = store.lock().await;
|
||||
let info = locked.application.rooms.get_or_default(room_id.clone());
|
||||
info.name = room_name;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -980,7 +1020,11 @@ impl ClientWorker {
|
|||
let mut locked = store.lock().await;
|
||||
let info = locked.application.get_room_info(room_id.to_owned());
|
||||
|
||||
match info.keys.get(&ev.redacts) {
|
||||
let Some(redacts) = &ev.redacts else {
|
||||
return;
|
||||
};
|
||||
|
||||
match info.keys.get(redacts) {
|
||||
None => return,
|
||||
Some(EventLocation::Message(key)) => {
|
||||
if let Some(msg) = info.messages.get_mut(key) {
|
||||
|
@ -990,10 +1034,10 @@ impl ClientWorker {
|
|||
},
|
||||
Some(EventLocation::Reaction(event_id)) => {
|
||||
if let Some(reactions) = info.reactions.get_mut(event_id) {
|
||||
reactions.remove(&ev.redacts);
|
||||
reactions.remove(redacts);
|
||||
}
|
||||
|
||||
info.keys.remove(&ev.redacts);
|
||||
info.keys.remove(redacts);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1165,22 +1209,22 @@ impl ClientWorker {
|
|||
|
||||
match style {
|
||||
LoginStyle::SessionRestore(session) => {
|
||||
client.restore_login(session).await.map_err(IambError::from)?;
|
||||
client.restore_session(session).await.map_err(IambError::from)?;
|
||||
},
|
||||
LoginStyle::Password(password) => {
|
||||
let resp = client
|
||||
.matrix_auth()
|
||||
.login_username(&self.settings.profile.user_id, &password)
|
||||
.initial_device_display_name(initial_devname().as_str())
|
||||
.send()
|
||||
.await
|
||||
.map_err(IambError::from)?;
|
||||
let file = File::create(self.settings.session_json.as_path())?;
|
||||
let writer = BufWriter::new(file);
|
||||
let session = Session::from(resp);
|
||||
serde_json::to_writer(writer, &session).map_err(IambError::from)?;
|
||||
let session = MatrixSession::from(&resp);
|
||||
self.settings.write_session(session)?;
|
||||
},
|
||||
LoginStyle::SingleSignOn => {
|
||||
let resp = client
|
||||
.matrix_auth()
|
||||
.login_sso(|url| {
|
||||
let opened = format!(
|
||||
"The following URL should have been opened in your browser:\n {url}"
|
||||
|
@ -1197,10 +1241,8 @@ impl ClientWorker {
|
|||
.await
|
||||
.map_err(IambError::from)?;
|
||||
|
||||
let file = File::create(self.settings.session_json.as_path())?;
|
||||
let writer = BufWriter::new(file);
|
||||
let session = Session::from(resp);
|
||||
serde_json::to_writer(writer, &session).map_err(IambError::from)?;
|
||||
let session = MatrixSession::from(&resp);
|
||||
self.settings.write_session(session)?;
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -1213,7 +1255,7 @@ impl ClientWorker {
|
|||
})
|
||||
.into();
|
||||
|
||||
Ok(Some(InfoMessage::from("Successfully logged in!")))
|
||||
Ok(Some(InfoMessage::from("* Successfully logged in!")))
|
||||
}
|
||||
|
||||
async fn logout(&mut self, user_id: String) -> IambResult<EditInfo> {
|
||||
|
@ -1228,7 +1270,7 @@ impl ClientWorker {
|
|||
}
|
||||
|
||||
// Send the logout request.
|
||||
if let Err(e) = self.client.logout().await {
|
||||
if let Err(e) = self.client.matrix_auth().logout().await {
|
||||
let msg = format!("Failed to logout: {e}");
|
||||
let err = UIError::Failure(msg);
|
||||
|
||||
|
@ -1243,7 +1285,7 @@ impl ClientWorker {
|
|||
|
||||
async fn direct_message(&mut self, user: OwnedUserId) -> IambResult<OwnedRoomId> {
|
||||
for room in self.client.rooms() {
|
||||
if !room.is_direct() {
|
||||
if !is_direct(&room).await {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1267,7 +1309,7 @@ impl ClientWorker {
|
|||
})
|
||||
}
|
||||
|
||||
async fn get_inviter(&mut self, invited: Invited) -> IambResult<Option<RoomMember>> {
|
||||
async fn get_inviter(&mut self, invited: MatrixRoom) -> IambResult<Option<RoomMember>> {
|
||||
let details = invited.invite_details().await.map_err(IambError::from)?;
|
||||
|
||||
Ok(details.inviter)
|
||||
|
@ -1287,7 +1329,7 @@ impl ClientWorker {
|
|||
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 {
|
||||
Ok(resp) => Ok(resp.room_id),
|
||||
Ok(resp) => Ok(resp.room_id().to_owned()),
|
||||
Err(e) => {
|
||||
let msg = e.to_string();
|
||||
let err = UIError::Failure(msg);
|
||||
|
@ -1307,14 +1349,14 @@ impl ClientWorker {
|
|||
|
||||
async fn members(&mut self, room_id: OwnedRoomId) -> IambResult<Vec<RoomMember>> {
|
||||
if let Some(room) = self.client.get_room(room_id.as_ref()) {
|
||||
Ok(room.active_members().await.map_err(IambError::from)?)
|
||||
Ok(room.members(RoomMemberships::ACTIVE).await.map_err(IambError::from)?)
|
||||
} else {
|
||||
Err(IambError::UnknownRoom(room_id).into())
|
||||
}
|
||||
}
|
||||
|
||||
async fn space_members(&mut self, space: OwnedRoomId) -> IambResult<Vec<OwnedRoomId>> {
|
||||
let mut req = SpaceHierarchyRequest::new(&space);
|
||||
let mut req = SpaceHierarchyRequest::new(space);
|
||||
req.limit = Some(1000u32.into());
|
||||
req.max_depth = Some(1u32.into());
|
||||
|
||||
|
@ -1326,7 +1368,7 @@ impl ClientWorker {
|
|||
}
|
||||
|
||||
async fn typing_notice(&mut self, room_id: OwnedRoomId) {
|
||||
if let Some(room) = self.client.get_joined_room(room_id.as_ref()) {
|
||||
if let Some(room) = self.client.get_room(room_id.as_ref()) {
|
||||
let _ = room.typing_notice(true).await;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue