mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-20 05:39: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
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -22,8 +22,8 @@ jobs:
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
submodules: true
|
submodules: true
|
||||||
- name: Install Rust (1.67 w/ clippy)
|
- name: Install Rust (1.70 w/ clippy)
|
||||||
uses: dtolnay/rust-toolchain@1.67
|
uses: dtolnay/rust-toolchain@1.70
|
||||||
with:
|
with:
|
||||||
components: clippy
|
components: clippy
|
||||||
- name: Install Rust (nightly w/ rustfmt)
|
- name: Install Rust (nightly w/ rustfmt)
|
||||||
|
|
1562
Cargo.lock
generated
1562
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -11,7 +11,7 @@ license = "Apache-2.0"
|
||||||
exclude = [".github", "CONTRIBUTING.md"]
|
exclude = [".github", "CONTRIBUTING.md"]
|
||||||
keywords = ["matrix", "chat", "tui", "vim"]
|
keywords = ["matrix", "chat", "tui", "vim"]
|
||||||
categories = ["command-line-utilities"]
|
categories = ["command-line-utilities"]
|
||||||
rust-version = "1.67"
|
rust-version = "1.70"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
@ -40,12 +40,15 @@ markup5ever_rcdom = "0.2.0"
|
||||||
mime = "^0.3.16"
|
mime = "^0.3.16"
|
||||||
mime_guess = "^2.0.4"
|
mime_guess = "^2.0.4"
|
||||||
open = "3.2.0"
|
open = "3.2.0"
|
||||||
|
rand = "0.8.5"
|
||||||
ratatui = "0.23"
|
ratatui = "0.23"
|
||||||
ratatui-image = { version = "0.4.3", features = ["serde"] }
|
ratatui-image = { version = "0.4.3", features = ["serde"] }
|
||||||
regex = "^1.5"
|
regex = "^1.5"
|
||||||
rpassword = "^7.2"
|
rpassword = "^7.2"
|
||||||
serde = "^1.0"
|
serde = "^1.0"
|
||||||
serde_json = "^1.0"
|
serde_json = "^1.0"
|
||||||
|
sled = "0.34.7"
|
||||||
|
temp-dir = "0.1.12"
|
||||||
thiserror = "^1.0.37"
|
thiserror = "^1.0.37"
|
||||||
tracing = "~0.1.36"
|
tracing = "~0.1.36"
|
||||||
tracing-appender = "~0.2.2"
|
tracing-appender = "~0.2.2"
|
||||||
|
@ -66,9 +69,9 @@ git = "https://github.com/ulyssa/modalkit"
|
||||||
rev = "cb8c8aeb9a499b9b16615ce144f9014d78036e01"
|
rev = "cb8c8aeb9a499b9b16615ce144f9014d78036e01"
|
||||||
|
|
||||||
[dependencies.matrix-sdk]
|
[dependencies.matrix-sdk]
|
||||||
version = "^0.6.2"
|
version = "0.7.1"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["e2e-encryption", "sled", "rustls-tls", "sso-login"]
|
features = ["e2e-encryption", "rustls-tls", "bundled-sqlite", "sso-login"]
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
version = "1.24.1"
|
version = "1.24.1"
|
||||||
|
|
|
@ -22,7 +22,7 @@ website, [iamb.chat].
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Install Rust (1.67.0 or above) and Cargo, and then run:
|
Install Rust (1.70.0 or above) and Cargo, and then run:
|
||||||
|
|
||||||
```
|
```
|
||||||
cargo install --locked iamb
|
cargo install --locked iamb
|
||||||
|
|
30
src/base.rs
30
src/base.rs
|
@ -32,17 +32,18 @@ use url::Url;
|
||||||
|
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
encryption::verification::SasVerification,
|
encryption::verification::SasVerification,
|
||||||
room::{Joined, Room as MatrixRoom},
|
room::Room as MatrixRoom,
|
||||||
ruma::{
|
ruma::{
|
||||||
events::{
|
events::{
|
||||||
reaction::ReactionEvent,
|
reaction::ReactionEvent,
|
||||||
|
relation::Replacement,
|
||||||
room::encrypted::RoomEncryptedEvent,
|
room::encrypted::RoomEncryptedEvent,
|
||||||
room::message::{
|
room::message::{
|
||||||
OriginalRoomMessageEvent,
|
OriginalRoomMessageEvent,
|
||||||
Relation,
|
Relation,
|
||||||
Replacement,
|
|
||||||
RoomMessageEvent,
|
RoomMessageEvent,
|
||||||
RoomMessageEventContent,
|
RoomMessageEventContent,
|
||||||
|
RoomMessageEventContentWithoutRelation,
|
||||||
},
|
},
|
||||||
tag::{TagName, Tags},
|
tag::{TagName, Tags},
|
||||||
MessageLikeEvent,
|
MessageLikeEvent,
|
||||||
|
@ -55,6 +56,7 @@ use matrix_sdk::{
|
||||||
RoomId,
|
RoomId,
|
||||||
UserId,
|
UserId,
|
||||||
},
|
},
|
||||||
|
RoomState as MatrixRoomState,
|
||||||
};
|
};
|
||||||
|
|
||||||
use modalkit::{
|
use modalkit::{
|
||||||
|
@ -581,6 +583,10 @@ pub enum IambError {
|
||||||
#[error("Cryptographic storage error: {0}")]
|
#[error("Cryptographic storage error: {0}")]
|
||||||
CryptoStore(#[from] matrix_sdk::encryption::CryptoStoreError),
|
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.
|
/// An HTTP error.
|
||||||
#[error("HTTP client error: {0}")]
|
#[error("HTTP client error: {0}")]
|
||||||
Http(#[from] matrix_sdk::HttpError),
|
Http(#[from] matrix_sdk::HttpError),
|
||||||
|
@ -809,9 +815,9 @@ impl RoomInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert an edit.
|
/// 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 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) {
|
let key = if let Some(EventLocation::Message(k)) = self.keys.get(&event_id) {
|
||||||
k
|
k
|
||||||
|
@ -827,10 +833,10 @@ impl RoomInfo {
|
||||||
|
|
||||||
match &mut msg.event {
|
match &mut msg.event {
|
||||||
MessageEvent::Original(orig) => {
|
MessageEvent::Original(orig) => {
|
||||||
orig.content.msgtype = new_content.msgtype;
|
orig.content.apply_replacement(new_msgtype);
|
||||||
},
|
},
|
||||||
MessageEvent::Local(_, content) => {
|
MessageEvent::Local(_, content) => {
|
||||||
content.msgtype = new_content.msgtype;
|
content.apply_replacement(new_msgtype);
|
||||||
},
|
},
|
||||||
MessageEvent::Redacted(_) |
|
MessageEvent::Redacted(_) |
|
||||||
MessageEvent::EncryptedOriginal(_) |
|
MessageEvent::EncryptedOriginal(_) |
|
||||||
|
@ -1182,8 +1188,16 @@ impl ChatStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a joined room.
|
/// Get a joined room.
|
||||||
pub fn get_joined_room(&self, room_id: &RoomId) -> Option<Joined> {
|
pub fn get_joined_room(&self, room_id: &RoomId) -> Option<MatrixRoom> {
|
||||||
self.worker.client.get_joined_room(room_id)
|
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.
|
/// 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::fmt;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::io::BufReader;
|
use std::io::{BufReader, BufWriter};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process;
|
use std::process;
|
||||||
|
|
||||||
use clap::Parser;
|
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::style::{Color, Modifier as StyleModifier, Style};
|
||||||
use ratatui::text::Span;
|
use ratatui::text::Span;
|
||||||
use ratatui_image::picker::ProtocolType;
|
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 tracing::Level;
|
||||||
use url::Url;
|
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 {
|
macro_rules! usage {
|
||||||
( $($args: tt)* ) => {
|
( $($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)]
|
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq)]
|
||||||
pub struct UserDisplayTunables {
|
pub struct UserDisplayTunables {
|
||||||
pub color: Option<UserColor>,
|
pub color: Option<UserColor>,
|
||||||
|
@ -421,14 +464,35 @@ impl Tunables {
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct DirectoryValues {
|
pub struct DirectoryValues {
|
||||||
pub cache: PathBuf,
|
pub cache: PathBuf,
|
||||||
|
pub data: PathBuf,
|
||||||
pub logs: PathBuf,
|
pub logs: PathBuf,
|
||||||
pub downloads: Option<PathBuf>,
|
pub downloads: Option<PathBuf>,
|
||||||
pub image_previews: 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)]
|
#[derive(Clone, Default, Deserialize)]
|
||||||
pub struct Directories {
|
pub struct Directories {
|
||||||
pub cache: Option<PathBuf>,
|
pub cache: Option<PathBuf>,
|
||||||
|
pub data: Option<PathBuf>,
|
||||||
pub logs: Option<PathBuf>,
|
pub logs: Option<PathBuf>,
|
||||||
pub downloads: Option<PathBuf>,
|
pub downloads: Option<PathBuf>,
|
||||||
pub image_previews: Option<PathBuf>,
|
pub image_previews: Option<PathBuf>,
|
||||||
|
@ -438,6 +502,7 @@ impl Directories {
|
||||||
fn merge(self, other: Self) -> Self {
|
fn merge(self, other: Self) -> Self {
|
||||||
Directories {
|
Directories {
|
||||||
cache: self.cache.or(other.cache),
|
cache: self.cache.or(other.cache),
|
||||||
|
data: self.data.or(other.data),
|
||||||
logs: self.logs.or(other.logs),
|
logs: self.logs.or(other.logs),
|
||||||
downloads: self.downloads.or(other.downloads),
|
downloads: self.downloads.or(other.downloads),
|
||||||
image_previews: self.image_previews.or(other.image_previews),
|
image_previews: self.image_previews.or(other.image_previews),
|
||||||
|
@ -454,6 +519,15 @@ impl Directories {
|
||||||
})
|
})
|
||||||
.expect("no dirs.cache value configured!");
|
.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 logs = self.logs.unwrap_or_else(|| {
|
||||||
let mut dir = cache.clone();
|
let mut dir = cache.clone();
|
||||||
dir.push("logs");
|
dir.push("logs");
|
||||||
|
@ -468,7 +542,7 @@ impl Directories {
|
||||||
dir
|
dir
|
||||||
});
|
});
|
||||||
|
|
||||||
DirectoryValues { cache, logs, downloads, image_previews }
|
DirectoryValues { cache, data, logs, downloads, image_previews }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -540,9 +614,11 @@ impl IambConfig {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ApplicationSettings {
|
pub struct ApplicationSettings {
|
||||||
pub matrix_dir: PathBuf,
|
|
||||||
pub layout_json: PathBuf,
|
pub layout_json: PathBuf,
|
||||||
pub session_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_name: String,
|
||||||
pub profile: ProfileConfig,
|
pub profile: ProfileConfig,
|
||||||
pub tunables: TunableValues,
|
pub tunables: TunableValues,
|
||||||
|
@ -602,17 +678,30 @@ impl ApplicationSettings {
|
||||||
let dirs = profile.dirs.take().unwrap_or_default().merge(dirs);
|
let dirs = profile.dirs.take().unwrap_or_default().merge(dirs);
|
||||||
let dirs = dirs.values();
|
let dirs = dirs.values();
|
||||||
|
|
||||||
|
// Create directories
|
||||||
|
dirs.create_dir_all()?;
|
||||||
|
|
||||||
// Set up paths that live inside the profile's data directory.
|
// Set up paths that live inside the profile's data directory.
|
||||||
let mut profile_dir = config_dir.clone();
|
let mut profile_dir = config_dir.clone();
|
||||||
profile_dir.push("profiles");
|
profile_dir.push("profiles");
|
||||||
profile_dir.push(profile_name.as_str());
|
profile_dir.push(profile_name.as_str());
|
||||||
|
|
||||||
let mut matrix_dir = profile_dir.clone();
|
let mut profile_data_dir = dirs.data.clone();
|
||||||
matrix_dir.push("matrix");
|
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");
|
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.
|
// Set up paths that live inside the profile's cache directory.
|
||||||
let mut cache_dir = dirs.cache.clone();
|
let mut cache_dir = dirs.cache.clone();
|
||||||
cache_dir.push("profiles");
|
cache_dir.push("profiles");
|
||||||
|
@ -622,9 +711,11 @@ impl ApplicationSettings {
|
||||||
layout_json.push("layout.json");
|
layout_json.push("layout.json");
|
||||||
|
|
||||||
let settings = ApplicationSettings {
|
let settings = ApplicationSettings {
|
||||||
matrix_dir,
|
sled_dir,
|
||||||
layout_json,
|
layout_json,
|
||||||
session_json,
|
session_json,
|
||||||
|
session_json_old,
|
||||||
|
sqlite_dir,
|
||||||
profile_name,
|
profile_name,
|
||||||
profile,
|
profile,
|
||||||
tunables,
|
tunables,
|
||||||
|
@ -635,6 +726,21 @@ impl ApplicationSettings {
|
||||||
Ok(settings)
|
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> {
|
pub fn get_user_char_span<'a>(&self, user_id: &'a UserId) -> Span<'a> {
|
||||||
let (color, c) = self
|
let (color, c) = self
|
||||||
.tunables
|
.tunables
|
||||||
|
|
210
src/main.rs
210
src/main.rs
|
@ -19,7 +19,7 @@ use std::collections::VecDeque;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::fs::{create_dir_all, File};
|
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::ops::DerefMut;
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
@ -27,7 +27,10 @@ use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use matrix_sdk::crypto::encrypt_room_key_export;
|
||||||
use matrix_sdk::ruma::OwnedUserId;
|
use matrix_sdk::ruma::OwnedUserId;
|
||||||
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
|
use temp_dir::TempDir;
|
||||||
use tokio::sync::Mutex as AsyncMutex;
|
use tokio::sync::Mutex as AsyncMutex;
|
||||||
use tracing_subscriber::FmtSubscriber;
|
use tracing_subscriber::FmtSubscriber;
|
||||||
|
|
||||||
|
@ -62,6 +65,7 @@ mod config;
|
||||||
mod keybindings;
|
mod keybindings;
|
||||||
mod message;
|
mod message;
|
||||||
mod preview;
|
mod preview;
|
||||||
|
mod sled_export;
|
||||||
mod util;
|
mod util;
|
||||||
mod windows;
|
mod windows;
|
||||||
mod worker;
|
mod worker;
|
||||||
|
@ -558,7 +562,7 @@ impl Application {
|
||||||
match action {
|
match action {
|
||||||
HomeserverAction::CreateRoom(alias, vis, flags) => {
|
HomeserverAction::CreateRoom(alias, vis, flags) => {
|
||||||
let client = &store.application.worker.client;
|
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 room = IambId::Room(room_id);
|
||||||
let target = OpenTarget::Application(room);
|
let target = OpenTarget::Application(room);
|
||||||
let action = WindowAction::Switch(target);
|
let action = WindowAction::Switch(target);
|
||||||
|
@ -659,36 +663,57 @@ impl Application {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn login(worker: Requester, settings: &ApplicationSettings) -> IambResult<()> {
|
fn gen_passphrase() -> String {
|
||||||
println!("Logging in for {}...", settings.profile.user_id);
|
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() {
|
if settings.session_json.is_file() {
|
||||||
let file = File::open(settings.session_json.as_path())?;
|
let session = settings.read_session(&settings.session_json)?;
|
||||||
let reader = BufReader::new(file);
|
worker.login(LoginStyle::SessionRestore(session.into()))?;
|
||||||
let session = serde_json::from_reader(reader).map_err(IambError::from)?;
|
|
||||||
|
|
||||||
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(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
println!("Please select login type: [p]assword / [s]ingle sign on");
|
let login_style =
|
||||||
|
match read_response("Please select login type: [p]assword / [s]ingle sign on")
|
||||||
let mut input = String::new();
|
.chars()
|
||||||
std::io::stdin().read_line(&mut input).unwrap();
|
.next()
|
||||||
|
.map(|c| c.to_ascii_lowercase())
|
||||||
let login_style = match input.chars().next().map(|c| c.to_ascii_lowercase()) {
|
{
|
||||||
None | Some('p') => {
|
None | Some('p') => {
|
||||||
let password = rpassword::prompt_password("Password: ")?;
|
let password = rpassword::prompt_password("Password: ")?;
|
||||||
LoginStyle::Password(password)
|
LoginStyle::Password(password)
|
||||||
},
|
},
|
||||||
Some('s') => LoginStyle::SingleSignOn,
|
Some('s') => LoginStyle::SingleSignOn,
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
println!("Failed to login. Please enter 'p' or 's'");
|
println!("Failed to login. Please enter 'p' or 's'");
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
match worker.login(login_style) {
|
match worker.login(login_style) {
|
||||||
Ok(info) => {
|
Ok(info) => {
|
||||||
|
@ -713,17 +738,142 @@ fn print_exit<T: Display, N>(v: T) -> N {
|
||||||
process::exit(2);
|
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<()> {
|
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.
|
// Set up the async worker thread and global store.
|
||||||
let worker = ClientWorker::spawn(settings.clone()).await;
|
let worker = ClientWorker::spawn(client.clone(), settings.clone()).await;
|
||||||
let client = worker.client.clone();
|
|
||||||
let store = ChatStore::new(worker.clone(), settings.clone());
|
let store = ChatStore::new(worker.clone(), settings.clone());
|
||||||
let store = Store::new(store);
|
let store = Store::new(store);
|
||||||
let store = Arc::new(AsyncMutex::new(store));
|
let store = Arc::new(AsyncMutex::new(store));
|
||||||
worker.init(store.clone());
|
worker.init(store.clone());
|
||||||
|
|
||||||
login(worker, &settings).await.unwrap_or_else(print_exit);
|
if let Some((keydir, pass)) = import_keys {
|
||||||
worker::do_first_sync(client, &store).await;
|
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() {
|
fn restore_tty() {
|
||||||
let _ = crossterm::terminal::disable_raw_mode();
|
let _ = crossterm::terminal::disable_raw_mode();
|
||||||
|
@ -767,10 +917,6 @@ fn main() -> IambResult<()> {
|
||||||
let log_prefix = format!("iamb-log-{}", settings.profile_name);
|
let log_prefix = format!("iamb-log-{}", settings.profile_name);
|
||||||
let log_dir = settings.dirs.logs.as_path();
|
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 = tracing_appender::rolling::daily(log_dir, log_prefix);
|
||||||
let (appender, guard) = tracing_appender::non_blocking(appender);
|
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 chrono::{DateTime, Local as LocalTz, NaiveDateTime, TimeZone};
|
||||||
use comrak::{markdown_to_html, ComrakOptions};
|
use comrak::{markdown_to_html, ComrakOptions};
|
||||||
|
use serde_json::json;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use matrix_sdk::ruma::{
|
use matrix_sdk::ruma::{
|
||||||
|
@ -33,7 +34,7 @@ use matrix_sdk::ruma::{
|
||||||
redaction::SyncRoomRedactionEvent,
|
redaction::SyncRoomRedactionEvent,
|
||||||
},
|
},
|
||||||
AnyMessageLikeEvent,
|
AnyMessageLikeEvent,
|
||||||
Redact,
|
RedactContent,
|
||||||
RedactedUnsigned,
|
RedactedUnsigned,
|
||||||
},
|
},
|
||||||
EventId,
|
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)]
|
#[derive(Clone)]
|
||||||
pub enum MessageEvent {
|
pub enum MessageEvent {
|
||||||
EncryptedOriginal(Box<OriginalRoomEncryptedEvent>),
|
EncryptedOriginal(Box<OriginalRoomEncryptedEvent>),
|
||||||
|
@ -419,7 +442,14 @@ impl MessageEvent {
|
||||||
MessageEvent::Redacted(_) => return,
|
MessageEvent::Redacted(_) => return,
|
||||||
MessageEvent::Local(_, _) => return,
|
MessageEvent::Local(_, _) => return,
|
||||||
MessageEvent::Original(ev) => {
|
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));
|
*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> {
|
fn body_cow_reason(unsigned: &RedactedUnsigned) -> Cow<'_, str> {
|
||||||
let reason = unsigned
|
let reason = unsigned.redacted_because.content.reason.as_ref();
|
||||||
.redacted_because
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|e| e.as_original())
|
|
||||||
.and_then(|r| r.content.reason.as_ref());
|
|
||||||
|
|
||||||
if let Some(r) = reason {
|
if let Some(r) = reason {
|
||||||
Cow::Owned(format!("[Redacted: {r:?}]"))
|
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 {
|
pub fn mock_dirs() -> DirectoryValues {
|
||||||
DirectoryValues {
|
DirectoryValues {
|
||||||
cache: PathBuf::new(),
|
cache: PathBuf::new(),
|
||||||
|
data: PathBuf::new(),
|
||||||
logs: PathBuf::new(),
|
logs: PathBuf::new(),
|
||||||
downloads: None,
|
downloads: None,
|
||||||
image_previews: PathBuf::new(),
|
image_previews: PathBuf::new(),
|
||||||
|
@ -202,9 +203,11 @@ pub fn mock_tunables() -> TunableValues {
|
||||||
|
|
||||||
pub fn mock_settings() -> ApplicationSettings {
|
pub fn mock_settings() -> ApplicationSettings {
|
||||||
ApplicationSettings {
|
ApplicationSettings {
|
||||||
matrix_dir: PathBuf::new(),
|
|
||||||
layout_json: PathBuf::new(),
|
layout_json: PathBuf::new(),
|
||||||
session_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_name: "test".into(),
|
||||||
profile: ProfileConfig {
|
profile: ProfileConfig {
|
||||||
|
|
|
@ -14,14 +14,16 @@ use url::Url;
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
attachment::AttachmentConfig,
|
attachment::AttachmentConfig,
|
||||||
media::{MediaFormat, MediaRequest},
|
media::{MediaFormat, MediaRequest},
|
||||||
room::{Joined, Room as MatrixRoom},
|
room::Room as MatrixRoom,
|
||||||
ruma::{
|
ruma::{
|
||||||
events::reaction::{ReactionEventContent, Relation as Reaction},
|
events::reaction::ReactionEventContent,
|
||||||
|
events::relation::{Annotation, Replacement},
|
||||||
events::room::message::{
|
events::room::message::{
|
||||||
|
AddMentions,
|
||||||
|
ForwardThread,
|
||||||
MessageType,
|
MessageType,
|
||||||
OriginalRoomMessageEvent,
|
OriginalRoomMessageEvent,
|
||||||
Relation,
|
Relation,
|
||||||
Replacement,
|
|
||||||
RoomMessageEventContent,
|
RoomMessageEventContent,
|
||||||
TextMessageEventContent,
|
TextMessageEventContent,
|
||||||
},
|
},
|
||||||
|
@ -29,6 +31,7 @@ use matrix_sdk::{
|
||||||
OwnedRoomId,
|
OwnedRoomId,
|
||||||
RoomId,
|
RoomId,
|
||||||
},
|
},
|
||||||
|
RoomState,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
|
@ -126,8 +129,16 @@ impl ChatState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_joined(&self, worker: &Requester) -> Result<Joined, IambError> {
|
fn get_joined(&self, worker: &Requester) -> Result<MatrixRoom, IambError> {
|
||||||
worker.client.get_joined_room(self.id()).ok_or(IambError::NotJoined)
|
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> {
|
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 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)
|
Ok(None)
|
||||||
},
|
},
|
||||||
|
@ -449,12 +460,7 @@ impl ChatState {
|
||||||
_: ProgramContext,
|
_: ProgramContext,
|
||||||
store: &mut ProgramStore,
|
store: &mut ProgramStore,
|
||||||
) -> IambResult<EditInfo> {
|
) -> IambResult<EditInfo> {
|
||||||
let room = store
|
let room = self.get_joined(&store.application.worker)?;
|
||||||
.application
|
|
||||||
.worker
|
|
||||||
.client
|
|
||||||
.get_joined_room(self.id())
|
|
||||||
.ok_or(IambError::NotJoined)?;
|
|
||||||
let info = store.application.rooms.get_or_default(self.id().to_owned());
|
let info = store.application.rooms.get_or_default(self.id().to_owned());
|
||||||
let mut show_echo = true;
|
let mut show_echo = true;
|
||||||
|
|
||||||
|
@ -475,18 +481,18 @@ impl ChatState {
|
||||||
if let Some((_, event_id)) = &self.editing {
|
if let Some((_, event_id)) = &self.editing {
|
||||||
msg.relates_to = Some(Relation::Replacement(Replacement::new(
|
msg.relates_to = Some(Relation::Replacement(Replacement::new(
|
||||||
event_id.clone(),
|
event_id.clone(),
|
||||||
Box::new(msg.clone()),
|
msg.msgtype.clone().into(),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
show_echo = false;
|
show_echo = false;
|
||||||
} else if let Some(m) = self.get_reply_to(info) {
|
} else if let Some(m) = self.get_reply_to(info) {
|
||||||
// XXX: Switch to RoomMessageEventContent::reply() once it's stable?
|
// 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.
|
// XXX: second parameter can be a locally unique transaction id.
|
||||||
// Useful for doing retries.
|
// 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;
|
let event_id = resp.event_id;
|
||||||
|
|
||||||
// Reset message bar state now that it's been sent.
|
// Reset message bar state now that it's been sent.
|
||||||
|
@ -506,7 +512,7 @@ impl ChatState {
|
||||||
let config = AttachmentConfig::new();
|
let config = AttachmentConfig::new();
|
||||||
|
|
||||||
let resp = room
|
let resp = room
|
||||||
.send_attachment(name.as_ref(), &mime, bytes.as_ref(), config)
|
.send_attachment(name.as_ref(), &mime, bytes, config)
|
||||||
.await
|
.await
|
||||||
.map_err(IambError::from)?;
|
.map_err(IambError::from)?;
|
||||||
|
|
||||||
|
@ -536,7 +542,7 @@ impl ChatState {
|
||||||
let config = AttachmentConfig::new();
|
let config = AttachmentConfig::new();
|
||||||
|
|
||||||
let resp = room
|
let resp = room
|
||||||
.send_attachment(name.as_ref(), &mime, bytes.as_ref(), config)
|
.send_attachment(name.as_ref(), &mime, bytes, config)
|
||||||
.await
|
.await
|
||||||
.map_err(IambError::from)?;
|
.map_err(IambError::from)?;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//! # Windows for Matrix rooms and spaces
|
//! # Windows for Matrix rooms and spaces
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
room::{Invited, Room as MatrixRoom},
|
room::Room as MatrixRoom,
|
||||||
ruma::{
|
ruma::{
|
||||||
events::{
|
events::{
|
||||||
room::{name::RoomNameEventContent, topic::RoomTopicEventContent},
|
room::{name::RoomNameEventContent, topic::RoomTopicEventContent},
|
||||||
|
@ -9,6 +9,7 @@ use matrix_sdk::{
|
||||||
RoomId,
|
RoomId,
|
||||||
},
|
},
|
||||||
DisplayName,
|
DisplayName,
|
||||||
|
RoomState as MatrixRoomState,
|
||||||
};
|
};
|
||||||
|
|
||||||
use ratatui::{
|
use ratatui::{
|
||||||
|
@ -114,7 +115,7 @@ impl RoomState {
|
||||||
|
|
||||||
fn draw_invite(
|
fn draw_invite(
|
||||||
&self,
|
&self,
|
||||||
invited: Invited,
|
invited: MatrixRoom,
|
||||||
area: Rect,
|
area: Rect,
|
||||||
buf: &mut Buffer,
|
buf: &mut Buffer,
|
||||||
store: &mut ProgramStore,
|
store: &mut ProgramStore,
|
||||||
|
@ -177,12 +178,12 @@ impl RoomState {
|
||||||
) -> IambResult<Vec<(Action<IambInfo>, ProgramContext)>> {
|
) -> IambResult<Vec<(Action<IambInfo>, ProgramContext)>> {
|
||||||
match act {
|
match act {
|
||||||
RoomAction::InviteAccept => {
|
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 = room.invite_details().await.map_err(IambError::from)?;
|
||||||
let details = details.invitee.event().original_content();
|
let details = details.invitee.event().original_content();
|
||||||
let is_direct = details.and_then(|ev| ev.is_direct).unwrap_or_default();
|
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 {
|
if is_direct {
|
||||||
room.set_is_direct(true).await.map_err(IambError::from)?;
|
room.set_is_direct(true).await.map_err(IambError::from)?;
|
||||||
|
@ -194,8 +195,8 @@ impl RoomState {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RoomAction::InviteReject => {
|
RoomAction::InviteReject => {
|
||||||
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()) {
|
||||||
room.reject_invitation().await.map_err(IambError::from)?;
|
room.leave().await.map_err(IambError::from)?;
|
||||||
|
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
} else {
|
} else {
|
||||||
|
@ -203,7 +204,7 @@ impl RoomState {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RoomAction::InviteSend(user) => {
|
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)?;
|
room.invite_user_by_id(user.as_ref()).await.map_err(IambError::from)?;
|
||||||
|
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
|
@ -212,7 +213,7 @@ impl RoomState {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
RoomAction::Leave(skip_confirm) => {
|
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 {
|
if skip_confirm {
|
||||||
room.leave().await.map_err(IambError::from)?;
|
room.leave().await.map_err(IambError::from)?;
|
||||||
|
|
||||||
|
@ -247,7 +248,7 @@ impl RoomState {
|
||||||
|
|
||||||
match field {
|
match field {
|
||||||
RoomField::Name => {
|
RoomField::Name => {
|
||||||
let ev = RoomNameEventContent::new(value.into());
|
let ev = RoomNameEventContent::new(value);
|
||||||
let _ = room.send_state_event(ev).await.map_err(IambError::from)?;
|
let _ = room.send_state_event(ev).await.map_err(IambError::from)?;
|
||||||
},
|
},
|
||||||
RoomField::Tag(tag) => {
|
RoomField::Tag(tag) => {
|
||||||
|
@ -272,7 +273,7 @@ impl RoomState {
|
||||||
|
|
||||||
match field {
|
match field {
|
||||||
RoomField::Name => {
|
RoomField::Name => {
|
||||||
let ev = RoomNameEventContent::new(None);
|
let ev = RoomNameEventContent::new("".into());
|
||||||
let _ = room.send_state_event(ev).await.map_err(IambError::from)?;
|
let _ = room.send_state_event(ev).await.map_err(IambError::from)?;
|
||||||
},
|
},
|
||||||
RoomField::Tag(tag) => {
|
RoomField::Tag(tag) => {
|
||||||
|
@ -381,12 +382,12 @@ impl TerminalCursor for RoomState {
|
||||||
|
|
||||||
impl WindowOps<IambInfo> for RoomState {
|
impl WindowOps<IambInfo> for RoomState {
|
||||||
fn draw(&mut self, area: Rect, buf: &mut Buffer, focused: bool, store: &mut ProgramStore) {
|
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);
|
self.refresh_room(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let MatrixRoom::Invited(invited) = self.room() {
|
if self.room().state() == MatrixRoomState::Invited {
|
||||||
self.draw_invite(invited.clone(), area, buf, store);
|
self.draw_invite(self.room().clone(), area, buf, store);
|
||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
|
218
src/worker.rs
218
src/worker.rs
|
@ -5,8 +5,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::fs::File;
|
use std::ops::Deref;
|
||||||
use std::io::BufWriter;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -17,13 +16,15 @@ use gethostname::gethostname;
|
||||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use matrix_sdk::{
|
use matrix_sdk::{
|
||||||
config::{RequestConfig, SyncSettings},
|
config::{RequestConfig, SyncSettings},
|
||||||
encryption::verification::{SasVerification, Verification},
|
encryption::verification::{SasVerification, Verification},
|
||||||
event_handler::Ctx,
|
event_handler::Ctx,
|
||||||
|
matrix_auth::MatrixSession,
|
||||||
reqwest,
|
reqwest,
|
||||||
room::{Invited, Messages, MessagesOptions, Room as MatrixRoom, RoomMember},
|
room::{Messages, MessagesOptions, Room as MatrixRoom, RoomMember},
|
||||||
ruma::{
|
ruma::{
|
||||||
api::client::{
|
api::client::{
|
||||||
filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
|
filter::{FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter},
|
||||||
|
@ -42,7 +43,8 @@ use matrix_sdk::{
|
||||||
},
|
},
|
||||||
presence::PresenceEvent,
|
presence::PresenceEvent,
|
||||||
reaction::ReactionEventContent,
|
reaction::ReactionEventContent,
|
||||||
receipt::{ReceiptEventContent, ReceiptType},
|
receipt::ReceiptType,
|
||||||
|
receipt::{ReceiptEventContent, ReceiptThread},
|
||||||
room::{
|
room::{
|
||||||
encryption::RoomEncryptionEventContent,
|
encryption::RoomEncryptionEventContent,
|
||||||
member::OriginalSyncRoomMemberEvent,
|
member::OriginalSyncRoomMemberEvent,
|
||||||
|
@ -73,8 +75,9 @@ use matrix_sdk::{
|
||||||
RoomVersionId,
|
RoomVersionId,
|
||||||
},
|
},
|
||||||
Client,
|
Client,
|
||||||
|
ClientBuildError,
|
||||||
DisplayName,
|
DisplayName,
|
||||||
Session,
|
RoomMemberships,
|
||||||
};
|
};
|
||||||
|
|
||||||
use modalkit::errors::UIError;
|
use modalkit::errors::UIError;
|
||||||
|
@ -106,9 +109,13 @@ fn initial_devname() -> String {
|
||||||
format!("{} on {}", IAMB_DEVICE_NAME, gethostname().to_string_lossy())
|
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(
|
pub async fn create_room(
|
||||||
client: &Client,
|
client: &Client,
|
||||||
room_alias_name: Option<&str>,
|
room_alias_name: Option<String>,
|
||||||
rt: CreateRoomType,
|
rt: CreateRoomType,
|
||||||
flags: CreateRoomFlags,
|
flags: CreateRoomFlags,
|
||||||
) -> IambResult<OwnedRoomId> {
|
) -> IambResult<OwnedRoomId> {
|
||||||
|
@ -154,8 +161,8 @@ pub async fn create_room(
|
||||||
let request = assign!(CreateRoomRequest::new(), {
|
let request = assign!(CreateRoomRequest::new(), {
|
||||||
room_alias_name,
|
room_alias_name,
|
||||||
creation_content,
|
creation_content,
|
||||||
initial_state: initial_state.as_slice(),
|
initial_state,
|
||||||
invite: invite.as_slice(),
|
invite,
|
||||||
is_direct,
|
is_direct,
|
||||||
visibility,
|
visibility,
|
||||||
preset,
|
preset,
|
||||||
|
@ -164,27 +171,31 @@ pub async fn create_room(
|
||||||
let resp = client.create_room(request).await.map_err(IambError::from)?;
|
let resp = client.create_room(request).await.map_err(IambError::from)?;
|
||||||
|
|
||||||
if is_direct {
|
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)?;
|
room.set_is_direct(true).await.map_err(IambError::from)?;
|
||||||
} else {
|
} else {
|
||||||
error!(
|
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"
|
"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) {
|
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,
|
Ok(receipts) => receipts,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!(?event_id, "failed to get event receipts: {e}");
|
tracing::warn!(?event_id, "failed to get event receipts: {e}");
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (user_id, _) in receipts {
|
for (user_id, _) in receipts {
|
||||||
info.set_receipt(user_id, event_id.to_owned());
|
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>> {
|
async fn members_load(client: &Client, room_id: &RoomId) -> IambResult<Vec<RoomMember>> {
|
||||||
if let Some(room) = client.get_room(room_id) {
|
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 {
|
} else {
|
||||||
Err(IambError::UnknownRoom(room_id.to_owned()).into())
|
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));
|
names.push((room.room_id().to_owned(), name));
|
||||||
|
|
||||||
if room.is_direct() {
|
if is_direct(&room).await {
|
||||||
dms.push(Arc::new((room.into(), tags)));
|
dms.push(Arc::new((room, tags)));
|
||||||
} else if room.is_space() {
|
} else if room.is_space() {
|
||||||
spaces.push(Arc::new((room.into(), tags)));
|
spaces.push(Arc::new((room, tags)));
|
||||||
} else {
|
} 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));
|
names.push((room.room_id().to_owned(), name));
|
||||||
|
|
||||||
if room.is_direct() {
|
if is_direct(&room).await {
|
||||||
dms.push(Arc::new((room.into(), tags)));
|
dms.push(Arc::new((room, tags)));
|
||||||
} else if room.is_space() {
|
} else if room.is_space() {
|
||||||
spaces.push(Arc::new((room.into(), tags)));
|
spaces.push(Arc::new((room, tags)));
|
||||||
} else {
|
} 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);
|
drop(locked);
|
||||||
|
|
||||||
for (room_id, new_receipt) in updates {
|
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;
|
continue;
|
||||||
};
|
};
|
||||||
match room.read_receipt(&new_receipt).await {
|
|
||||||
|
match room
|
||||||
|
.send_single_receipt(
|
||||||
|
ReceiptType::Read,
|
||||||
|
ReceiptThread::Unthreaded,
|
||||||
|
new_receipt.clone(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
sent.insert(room_id, new_receipt);
|
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.
|
// Perform an initial, lazily-loaded sync.
|
||||||
let mut room = RoomEventFilter::default();
|
let mut room = RoomEventFilter::default();
|
||||||
room.lazy_load_options = LazyLoadOptions::Enabled { include_redundant_members: false };
|
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.
|
// 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.
|
// Insert Need::Messages to fetch accurate recent timestamps in the background.
|
||||||
let mut locked = store.lock().await;
|
let mut locked = store.lock().await;
|
||||||
|
@ -509,7 +533,7 @@ pub async fn do_first_sync(client: Client, store: &AsyncProgramStore) {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum LoginStyle {
|
pub enum LoginStyle {
|
||||||
SessionRestore(Session),
|
SessionRestore(MatrixSession),
|
||||||
Password(String),
|
Password(String),
|
||||||
SingleSignOn,
|
SingleSignOn,
|
||||||
}
|
}
|
||||||
|
@ -543,7 +567,7 @@ pub enum WorkerTask {
|
||||||
Init(AsyncProgramStore, ClientReply<()>),
|
Init(AsyncProgramStore, ClientReply<()>),
|
||||||
Login(LoginStyle, ClientReply<IambResult<EditInfo>>),
|
Login(LoginStyle, ClientReply<IambResult<EditInfo>>),
|
||||||
Logout(String, 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>>),
|
GetRoom(OwnedRoomId, ClientReply<IambResult<FetchedRoom>>),
|
||||||
JoinRoom(String, ClientReply<IambResult<OwnedRoomId>>),
|
JoinRoom(String, ClientReply<IambResult<OwnedRoomId>>),
|
||||||
Members(OwnedRoomId, ClientReply<IambResult<Vec<RoomMember>>>),
|
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)]
|
#[derive(Clone)]
|
||||||
pub struct Requester {
|
pub struct Requester {
|
||||||
pub client: Client,
|
pub client: Client,
|
||||||
|
@ -649,7 +723,7 @@ impl Requester {
|
||||||
return response.recv();
|
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();
|
let (reply, response) = oneshot();
|
||||||
|
|
||||||
self.tx.send(WorkerTask::GetInviter(invite, reply)).unwrap();
|
self.tx.send(WorkerTask::GetInviter(invite, reply)).unwrap();
|
||||||
|
@ -719,40 +793,8 @@ pub struct ClientWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 (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 {
|
let mut worker = ClientWorker {
|
||||||
initialized: false,
|
initialized: false,
|
||||||
|
@ -872,13 +914,11 @@ impl ClientWorker {
|
||||||
store: Ctx<AsyncProgramStore>| {
|
store: Ctx<AsyncProgramStore>| {
|
||||||
async move {
|
async move {
|
||||||
if let SyncStateEvent::Original(ev) = ev {
|
if let SyncStateEvent::Original(ev) = ev {
|
||||||
if let Some(room_name) = ev.content.name {
|
let room_id = room.room_id().to_owned();
|
||||||
let room_id = room.room_id().to_owned();
|
let room_name = Some(ev.content.name);
|
||||||
let room_name = Some(room_name.to_string());
|
let mut locked = store.lock().await;
|
||||||
let mut locked = store.lock().await;
|
let info = locked.application.rooms.get_or_default(room_id.clone());
|
||||||
let info = locked.application.rooms.get_or_default(room_id.clone());
|
info.name = room_name;
|
||||||
info.name = room_name;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -980,7 +1020,11 @@ impl ClientWorker {
|
||||||
let mut locked = store.lock().await;
|
let mut locked = store.lock().await;
|
||||||
let info = locked.application.get_room_info(room_id.to_owned());
|
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,
|
None => return,
|
||||||
Some(EventLocation::Message(key)) => {
|
Some(EventLocation::Message(key)) => {
|
||||||
if let Some(msg) = info.messages.get_mut(key) {
|
if let Some(msg) = info.messages.get_mut(key) {
|
||||||
|
@ -990,10 +1034,10 @@ impl ClientWorker {
|
||||||
},
|
},
|
||||||
Some(EventLocation::Reaction(event_id)) => {
|
Some(EventLocation::Reaction(event_id)) => {
|
||||||
if let Some(reactions) = info.reactions.get_mut(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 {
|
match style {
|
||||||
LoginStyle::SessionRestore(session) => {
|
LoginStyle::SessionRestore(session) => {
|
||||||
client.restore_login(session).await.map_err(IambError::from)?;
|
client.restore_session(session).await.map_err(IambError::from)?;
|
||||||
},
|
},
|
||||||
LoginStyle::Password(password) => {
|
LoginStyle::Password(password) => {
|
||||||
let resp = client
|
let resp = client
|
||||||
|
.matrix_auth()
|
||||||
.login_username(&self.settings.profile.user_id, &password)
|
.login_username(&self.settings.profile.user_id, &password)
|
||||||
.initial_device_display_name(initial_devname().as_str())
|
.initial_device_display_name(initial_devname().as_str())
|
||||||
.send()
|
.send()
|
||||||
.await
|
.await
|
||||||
.map_err(IambError::from)?;
|
.map_err(IambError::from)?;
|
||||||
let file = File::create(self.settings.session_json.as_path())?;
|
let session = MatrixSession::from(&resp);
|
||||||
let writer = BufWriter::new(file);
|
self.settings.write_session(session)?;
|
||||||
let session = Session::from(resp);
|
|
||||||
serde_json::to_writer(writer, &session).map_err(IambError::from)?;
|
|
||||||
},
|
},
|
||||||
LoginStyle::SingleSignOn => {
|
LoginStyle::SingleSignOn => {
|
||||||
let resp = client
|
let resp = client
|
||||||
|
.matrix_auth()
|
||||||
.login_sso(|url| {
|
.login_sso(|url| {
|
||||||
let opened = format!(
|
let opened = format!(
|
||||||
"The following URL should have been opened in your browser:\n {url}"
|
"The following URL should have been opened in your browser:\n {url}"
|
||||||
|
@ -1197,10 +1241,8 @@ impl ClientWorker {
|
||||||
.await
|
.await
|
||||||
.map_err(IambError::from)?;
|
.map_err(IambError::from)?;
|
||||||
|
|
||||||
let file = File::create(self.settings.session_json.as_path())?;
|
let session = MatrixSession::from(&resp);
|
||||||
let writer = BufWriter::new(file);
|
self.settings.write_session(session)?;
|
||||||
let session = Session::from(resp);
|
|
||||||
serde_json::to_writer(writer, &session).map_err(IambError::from)?;
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1213,7 +1255,7 @@ impl ClientWorker {
|
||||||
})
|
})
|
||||||
.into();
|
.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> {
|
async fn logout(&mut self, user_id: String) -> IambResult<EditInfo> {
|
||||||
|
@ -1228,7 +1270,7 @@ impl ClientWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the logout request.
|
// 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 msg = format!("Failed to logout: {e}");
|
||||||
let err = UIError::Failure(msg);
|
let err = UIError::Failure(msg);
|
||||||
|
|
||||||
|
@ -1243,7 +1285,7 @@ impl ClientWorker {
|
||||||
|
|
||||||
async fn direct_message(&mut self, user: OwnedUserId) -> IambResult<OwnedRoomId> {
|
async fn direct_message(&mut self, user: OwnedUserId) -> IambResult<OwnedRoomId> {
|
||||||
for room in self.client.rooms() {
|
for room in self.client.rooms() {
|
||||||
if !room.is_direct() {
|
if !is_direct(&room).await {
|
||||||
continue;
|
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)?;
|
let details = invited.invite_details().await.map_err(IambError::from)?;
|
||||||
|
|
||||||
Ok(details.inviter)
|
Ok(details.inviter)
|
||||||
|
@ -1287,7 +1329,7 @@ impl ClientWorker {
|
||||||
async fn join_room(&mut self, name: String) -> IambResult<OwnedRoomId> {
|
async fn join_room(&mut self, name: String) -> IambResult<OwnedRoomId> {
|
||||||
if let Ok(alias_id) = OwnedRoomOrAliasId::from_str(name.as_str()) {
|
if let Ok(alias_id) = OwnedRoomOrAliasId::from_str(name.as_str()) {
|
||||||
match self.client.join_room_by_id_or_alias(&alias_id, &[]).await {
|
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) => {
|
Err(e) => {
|
||||||
let msg = e.to_string();
|
let msg = e.to_string();
|
||||||
let err = UIError::Failure(msg);
|
let err = UIError::Failure(msg);
|
||||||
|
@ -1307,14 +1349,14 @@ impl ClientWorker {
|
||||||
|
|
||||||
async fn members(&mut self, room_id: OwnedRoomId) -> IambResult<Vec<RoomMember>> {
|
async fn members(&mut self, room_id: OwnedRoomId) -> IambResult<Vec<RoomMember>> {
|
||||||
if let Some(room) = self.client.get_room(room_id.as_ref()) {
|
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 {
|
} else {
|
||||||
Err(IambError::UnknownRoom(room_id).into())
|
Err(IambError::UnknownRoom(room_id).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn space_members(&mut self, space: OwnedRoomId) -> IambResult<Vec<OwnedRoomId>> {
|
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.limit = Some(1000u32.into());
|
||||||
req.max_depth = Some(1u32.into());
|
req.max_depth = Some(1u32.into());
|
||||||
|
|
||||||
|
@ -1326,7 +1368,7 @@ impl ClientWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn typing_notice(&mut self, room_id: OwnedRoomId) {
|
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;
|
let _ = room.typing_notice(true).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue