Initial commit of Collapse
This commit is contained in:
commit
cef0ce7615
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/target
|
||||||
|
|
||||||
|
/.idea
|
||||||
|
*.swp
|
3200
Cargo.lock
generated
Normal file
3200
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[package]
|
||||||
|
name = "collapse"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
appdirs = "0.2.0"
|
||||||
|
eyre = "0.6.8"
|
||||||
|
matrix-sdk = { version = "0.6.2", features = ["eyre"] }
|
||||||
|
serde = "1.0.147"
|
||||||
|
serde_json = "1.0.87"
|
||||||
|
notify-rust = "4.5.10"
|
||||||
|
tokio = { version = "1.21.2", features = ["full"] }
|
169
src/bin/collapse.rs
Normal file
169
src/bin/collapse.rs
Normal file
@ -0,0 +1,169 @@
|
|||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use eyre::{Context, ContextCompat};
|
||||||
|
use matrix_sdk::{Client, Session};
|
||||||
|
use matrix_sdk::config::SyncSettings;
|
||||||
|
use matrix_sdk::room::Room;
|
||||||
|
use matrix_sdk::ruma::events::room::message::MessageType;
|
||||||
|
use matrix_sdk::ruma::UserId;
|
||||||
|
use matrix_sdk::ruma::events::room::message::OriginalSyncRoomMessageEvent;
|
||||||
|
use notify_rust::{Hint, Notification, Timeout};
|
||||||
|
use tokio::io::{AsyncBufReadExt, BufReader, stdin};
|
||||||
|
use tokio::task::spawn_blocking;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn main() -> eyre::Result<()> {
|
||||||
|
let appdir = appdirs::user_data_dir(Some("collapse_matrix"), None, true)
|
||||||
|
.unwrap();
|
||||||
|
if ! appdir.exists() {
|
||||||
|
tokio::fs::create_dir(&appdir).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let session_path = appdir.join("session.json");
|
||||||
|
let store_path = appdir.join("matrix-sdk-sled");
|
||||||
|
let client = if session_path.exists() {
|
||||||
|
let session_json = tokio::fs::read(&session_path).await.context("Failed to read session")?;
|
||||||
|
let session: Session = serde_json::from_slice(&session_json).context("Failed to deserialise session")?;
|
||||||
|
let client = Client::builder()
|
||||||
|
.server_name(session.user_id.server_name())
|
||||||
|
.sled_store(&store_path, None)
|
||||||
|
.context("Failed to make/open Matrix Store")?
|
||||||
|
.build().await
|
||||||
|
.context("Failed to create new client")?;
|
||||||
|
|
||||||
|
client.restore_login(session).await.context("Failed to restore session")?;
|
||||||
|
|
||||||
|
println!("Restored login.");
|
||||||
|
client
|
||||||
|
} else {
|
||||||
|
let mut br = BufReader::new(stdin());
|
||||||
|
let mut buf = String::new();
|
||||||
|
|
||||||
|
println!("You need to log in to use Collapse.");
|
||||||
|
println!("MXID: ");
|
||||||
|
br.read_line(&mut buf).await?;
|
||||||
|
let user_id = <&UserId>::try_from(buf.trim()).context("Bad MXID")?.to_owned();
|
||||||
|
|
||||||
|
buf.clear();
|
||||||
|
println!("Password: ");
|
||||||
|
br.read_line(&mut buf).await?;
|
||||||
|
let password = buf.trim().to_owned();
|
||||||
|
|
||||||
|
let client = Client::builder()
|
||||||
|
.server_name(user_id.server_name())
|
||||||
|
.sled_store(&store_path, None)
|
||||||
|
.context("Failed to make/open Matrix Store")?
|
||||||
|
.build().await
|
||||||
|
.context("Failed to create new client")?;
|
||||||
|
|
||||||
|
client.login_username(&user_id, &password)
|
||||||
|
.initial_device_display_name("Collapse")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.context("Login failed")?;
|
||||||
|
|
||||||
|
let session_json = serde_json::to_vec(&client.session().context("No session after login")?).context("Failed to serialise session.")?;
|
||||||
|
tokio::fs::write(session_path, &session_json).await.context("Failed to write session")?;
|
||||||
|
|
||||||
|
client
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Initial syncing now...");
|
||||||
|
// Sync once so we don't process old messages
|
||||||
|
for attempt in 0..3 {
|
||||||
|
match client.sync_once(SyncSettings::default()).await
|
||||||
|
.context("Failed initial sync") {
|
||||||
|
Ok(_) => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
if attempt == 2 {
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
eprintln!("{err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Initial sync completed.");
|
||||||
|
|
||||||
|
client.add_event_handler(on_room_message);
|
||||||
|
|
||||||
|
let mut last_fail = None;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if let Err(err) = client.sync(SyncSettings::default()).await {
|
||||||
|
eprintln!("Sync failed: {:?}", err);
|
||||||
|
let this_fail = Instant::now();
|
||||||
|
if let Some(last_fail) = last_fail {
|
||||||
|
if this_fail.duration_since(last_fail) < Duration::from_secs(3) {
|
||||||
|
Notification::new()
|
||||||
|
.appname("Collapse")
|
||||||
|
.summary("Collapse has collapsed!")
|
||||||
|
.body(&format!("Sync failed multiple times: {:?}", err))
|
||||||
|
.timeout(Timeout::Never)
|
||||||
|
.icon("error")
|
||||||
|
.show()
|
||||||
|
.context("Failed to show notification")?
|
||||||
|
.wait_for_action(|_| {});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last_fail = Some(this_fail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) -> eyre::Result<()> {
|
||||||
|
let Room::Joined(room) = room else { return Ok(()); };
|
||||||
|
let MessageType::Text(text) = event.content.msgtype else { return Ok(()); };
|
||||||
|
|
||||||
|
if ! text.body.contains("reivilibre") { return Ok(()); }
|
||||||
|
|
||||||
|
let escaped_body = text.body.replace("&", "&").replace("<", "<").replace(">", ">")
|
||||||
|
.replace("reivilibre", "<b>reivilibre</b>");
|
||||||
|
|
||||||
|
let room_name = room.display_name().await.context("Failed to calculate name of room.")?.to_string();
|
||||||
|
let member = room.get_member(&event.sender).await?.context("No room member")?;
|
||||||
|
let user_name = member.name();
|
||||||
|
let notif_name = format!("{user_name} ({room_name})");
|
||||||
|
|
||||||
|
|
||||||
|
let notif = Notification::new()
|
||||||
|
.appname("Collapse")
|
||||||
|
.summary(¬if_name)
|
||||||
|
.body(&escaped_body)
|
||||||
|
.hint(Hint::Category("chat".to_owned()))
|
||||||
|
.timeout(10_000)
|
||||||
|
.show()
|
||||||
|
.context("Couldn't show notification")?;
|
||||||
|
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let action = spawn_blocking(move || {
|
||||||
|
let mut action = None;
|
||||||
|
notif.wait_for_action(|action_arg| {
|
||||||
|
action = Some(action_arg.to_owned());
|
||||||
|
});
|
||||||
|
action
|
||||||
|
}).await;
|
||||||
|
match action {
|
||||||
|
Ok(Some(ref action)) => {
|
||||||
|
match action.as_str() {
|
||||||
|
"__close" => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
eprintln!("action: {:?}", other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Ok(None) => return,
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Error: {err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user