diff --git a/Cargo.lock b/Cargo.lock index 91f198c..0dc1d87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,9 +81,9 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.59", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -92,9 +92,9 @@ version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.59", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -134,9 +134,9 @@ checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" -version = "0.21.1" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f1e31e207a6b8fb791a38ea3105e6cb541f55e4d029902d3039a4ad07cc4105" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bitflags" @@ -224,8 +224,8 @@ checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.59", + "quote 1.0.28", "strsim", "syn 1.0.109", ] @@ -237,7 +237,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core", - "quote 1.0.27", + "quote 1.0.28", "syn 1.0.109", ] @@ -270,8 +270,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f91d4cfa921f1c05904dc3c57b4a32c38aed3340cce209f3a6fd1478babafc4" dependencies = [ "darling", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.59", + "quote 1.0.28", "syn 1.0.109", ] @@ -381,9 +381,9 @@ version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.59", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -677,12 +677,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "lru" @@ -806,14 +803,13 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "eebffdb73fe72e917997fad08bdbf31ac50b0fa91cec93e69a0662e4264d454c" dependencies = [ "libc", - "log", "wasi", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -832,8 +828,8 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.59", + "quote 1.0.28", "syn 1.0.109", ] @@ -941,9 +937,9 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.59", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -985,9 +981,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.58" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa1fb82fc0c281dd9671101b66b771ebbe1eaf967b96ac8740dcba4b70005ca8" +checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b" dependencies = [ "unicode-ident", ] @@ -1003,11 +999,11 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.27" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ - "proc-macro2 1.0.58", + "proc-macro2 1.0.59", ] [[package]] @@ -1166,9 +1162,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a59b5d8e97dee33696bf13c5ba8ab85341c002922fba050069326b9c498974" +checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390" dependencies = [ "aho-corasick", "memchr", @@ -1187,7 +1183,7 @@ version = "0.11.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" dependencies = [ - "base64 0.21.1", + "base64 0.21.2", "bytes", "encoding_rs", "futures-core", @@ -1324,8 +1320,8 @@ checksum = "0f82e91eb61cd86d9287303133ee55b54618eccb75a522cc22a42c15f5bda340" dependencies = [ "once_cell", "proc-macro-crate", - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.59", + "quote 1.0.28", "ruma-identifiers-validation", "serde", "syn 1.0.109", @@ -1350,7 +1346,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" dependencies = [ - "base64 0.21.1", + "base64 0.21.2", ] [[package]] @@ -1400,9 +1396,9 @@ version = "1.0.163" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.59", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -1491,19 +1487,19 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.59", + "quote 1.0.28", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.16" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6f671d4b5ffdb8eadec19c0ae67fe2639df8684bd7bc4b83d986b8db549cf01" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", + "proc-macro2 1.0.59", + "quote 1.0.28", "unicode-ident", ] @@ -1522,9 +1518,9 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.59", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -1544,9 +1540,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.1" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aa32867d44e6f2ce3385e89dceb990188b8bb0fb25b0cf576647a6f98ac5105" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg 1.1.0", "bytes", @@ -1582,9 +1578,9 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.59", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -1628,9 +1624,9 @@ checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" [[package]] name = "toml_edit" -version = "0.19.9" +version = "0.19.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d964908cec0d030b812013af25a0e57fddfadb1e066ecc6681d86253129d4f" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" dependencies = [ "indexmap", "toml_datetime", @@ -1661,9 +1657,9 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.59", + "quote 1.0.28", + "syn 2.0.18", ] [[package]] @@ -1689,9 +1685,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -1784,9 +1780,9 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.59", + "quote 1.0.28", + "syn 2.0.18", "wasm-bindgen-shared", ] @@ -1808,7 +1804,7 @@ version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ - "quote 1.0.27", + "quote 1.0.28", "wasm-bindgen-macro-support", ] @@ -1818,9 +1814,9 @@ version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.59", + "quote 1.0.28", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2064,9 +2060,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.3.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" dependencies = [ "zeroize_derive", ] @@ -2077,7 +2073,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.58", - "quote 1.0.27", - "syn 2.0.16", + "proc-macro2 1.0.59", + "quote 1.0.28", + "syn 2.0.18", ] diff --git a/src/heartbeat.rs b/src/heartbeat.rs new file mode 100644 index 0000000..36e7c42 --- /dev/null +++ b/src/heartbeat.rs @@ -0,0 +1,94 @@ +use js_int::UInt; +use matrix_sdk::{ + room::Joined, + ruma::{ + events::EmptyStateKey, exports::ruma_macros::EventContent, OwnedRoomId, OwnedServerName, + UserId, + }, +}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; +use std::{collections::HashMap, env, process::exit, sync::Arc}; +use tokio::sync::RwLock; + +type HeartBeatStats = Arc>>>; + +#[derive(Clone, Debug, Deserialize, Serialize, EventContent)] +#[ruma_event(type = "xyz.dandellion.matrix-heartbeat.settings", kind = State, state_key_type = EmptyStateKey)] +pub struct HeartBeatSettingsContent { + pub periode: UInt, // in seconds between each heartbeat +} + +#[derive(Clone, Debug, Deserialize, Serialize, EventContent)] +#[ruma_event(type = "xyz.dandellion.matrix-heartbeat.notification-settings", kind = State, state_key_type = String)] +pub struct NotificationSettingsContent { + // Bot to notify: Bot to count: How many counters to miss before sending a notification + /* { + '@heartbeat:dodsorf.as': { '@bot_heartbeat:pvv.ntnu.no': 5 }, + '@bot_heartbeat:pvv.ntnu.no': { '@heartbeat:dodsorf.as': 5 }, + } */ + // This way you can get an alert when incoming or outgoing federation is broken. + // Or you can montitor a different server than your own + notification_rules: HashMap>, +} + +#[derive(Clone, Debug, Deserialize, Serialize, EventContent)] +#[ruma_event(type = "xyz.dandellion.matrix-heartbeat.heartbeat", kind = MessageLike)] +pub struct HeartbeatContent { + // Server to different pong stats from last heartbeat + pongs: HashMap, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct Millis { + pub last: UInt, +} + +pub async fn get_heartbeat_settings( + room: &Joined, +) -> anyhow::Result> { + let settings = match room + .get_state_event_static::() + .await? + { + Some(s) => s, + None => return Ok(None), + }; + let settings = match settings.deserialize() { + Ok(r) => r, + Err(e) => return Err(anyhow::Error::new(e)), + }; + let settings = match settings { + matrix_sdk::ruma::events::SyncStateEvent::Original(s) => s, + matrix_sdk::ruma::events::SyncStateEvent::Redacted(_) => return Ok(None), + }; + let settings = settings.content; + Ok(Some(settings)) +} + +pub async fn start_loop(room: Joined, d: Duration, heartbeat_stats: HeartBeatStats) { + let mut interval = tokio::time::interval(d); + println!("Sending pings in {} every {:?}", &room.room_id(), d); + loop { + let heartbeat_stats = heartbeat_stats.clone(); + send_heartbeat(&room, heartbeat_stats).await; + interval.tick().await; + } +} + +async fn send_heartbeat(room: &Joined, heartbeat_stats: HeartBeatStats) -> anyhow::Result<()> { + let room_id = room.room_id().to_owned(); + println!("Sending ping in {}", &room_id); + let default_pong = HashMap::new(); + { + let heartbeat_stats_r = heartbeat_stats.read().await; + let pongs = heartbeat_stats_r.get(&room_id).unwrap_or(&default_pong); + let heartbeat = HeartbeatContent { + pongs: pongs.clone(), + }; + room.send(heartbeat, None).await.unwrap(); + } + let mut heartbeat_stats_w = heartbeat_stats.write().await; + heartbeat_stats_w.insert(room_id, default_pong); + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 65a6a0e..47da85e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,27 @@ // Parts of this file was lifted from https://github.com/matrix-org/matrix-rust-sdk/commit/3db90fbe026c222167000fcfdee8b35b44ae5694 // And are thus licensed under Apache +use heartbeat::HeartBeatSettingsContent; use matrix_sdk::{ config::SyncSettings, room::Room, ruma::events::room::message::{MessageType, OriginalSyncRoomMessageEvent}, - ruma::events::{macros::EventContent, room::member::StrippedRoomMemberEvent}, + ruma::{ + api::client::membership::join_room_by_id_or_alias, + events::{macros::EventContent, room::member::StrippedRoomMemberEvent}, + OwnedRoomId, OwnedServerName, OwnedServerSigningKeyId, + }, Client, }; use serde::{Deserialize, Serialize}; -use std::{env, process::exit}; -use tokio::time::{sleep, Duration}; +use std::{collections::HashMap, env, process::exit, sync::Arc}; +use tokio::sync::RwLock; +use tokio::time::{self, sleep, Duration}; +mod heartbeat; mod ping; +use crate::heartbeat::start_loop; +use crate::heartbeat::{get_heartbeat_settings, Millis}; async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) { if let Room::Joined(room) = room { @@ -20,7 +29,7 @@ async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) { return; }; - if text_content.body.starts_with("!ping ") { + if text_content.body.starts_with("!ping") { ping::ping(event, room).await; } } @@ -89,9 +98,68 @@ async fn login_and_sync( println!("Finished initial sync"); + let heartbeat_senders = Arc::new(RwLock::new(HashMap::new())); + let heartbeat_stats = Arc::new(RwLock::new(HashMap::< + OwnedRoomId, + HashMap, + >::new())); + + let joined_rooms = client.joined_rooms(); + for room in joined_rooms { + let hbs = get_heartbeat_settings(&room).await; + match hbs { + Ok(Some(a)) => { + let room_id = room.room_id().to_owned(); + let mut heartbeat_senders = heartbeat_senders.write().await; + heartbeat_senders.insert( + room_id, + tokio::spawn({ + let heartbeat_stats = heartbeat_stats.clone(); + async move { + start_loop( + room, + std::time::Duration::from_secs(a.periode.into()), + heartbeat_stats.clone(), + ) + .await + } + }), + ); + } + Err(e) => println!("{}", e), + Ok(None) => {} + }; + } + client.add_event_handler(on_room_message); client.add_event_handler(on_stripped_state_member); + client.add_event_handler({ + let heartbeat_stats = heartbeat_stats.clone(); + move |event: heartbeat::OriginalSyncHeartbeat, room: Room| { + let heartbeat_stats = heartbeat_stats.clone(); + async move { + let room_id = room.room_id().to_owned(); + println!("Recieved a heartbeat in {}", &room_id); + let mut heartbeat_stats = heartbeat_stats.write().await; + + let millis = Millis { + last: ping::duration_to_jsint(&ping::get_event_time_delta(&event)), + }; + + let sender: OwnedServerName = event.sender.server_name().to_owned(); + + if let Some(hm) = heartbeat_stats.get_mut(&room_id) { + hm.insert(sender, millis); + } else { + let mut hm = HashMap::new(); + hm.insert(sender, millis); + heartbeat_stats.insert(room_id, hm); + } + } + } + }); + // since we called `sync_once` before we entered our sync loop we must pass // that sync token to `sync` let settings = SyncSettings::default().token(response.next_batch); diff --git a/src/ping.rs b/src/ping.rs index 5edfb1c..47666af 100644 --- a/src/ping.rs +++ b/src/ping.rs @@ -3,8 +3,8 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use matrix_sdk::{ room::Joined, ruma::{ - events::room::message::OriginalSyncRoomMessageEvent, exports::ruma_macros::EventContent, - OwnedEventId, UInt, + events::room::message::OriginalSyncRoomMessageEvent, events::OriginalSyncMessageLikeEvent, + exports::ruma_macros::EventContent, OwnedEventId, UInt, }, }; use serde::{Deserialize, Serialize}; @@ -42,20 +42,24 @@ struct PongMixin { impl PongMixin { fn new(event: &OriginalSyncRoomMessageEvent, diff: &Duration) -> Self { - let millis = diff.as_millis(); - let clamped_ms: u64 = if millis < js_int::MAX_SAFE_UINT as u128 { - millis as u64 - } else { - js_int::MAX_SAFE_UINT - }; PongMixin { ping: event.event_id.to_owned(), from: event.sender.server_name().to_string(), - ms: UInt::new_saturating(clamped_ms), + ms: duration_to_jsint(diff), } } } +pub fn duration_to_jsint(d: &Duration) -> UInt { + let millis = d.as_millis(); + let clamped_ms: u64 = if millis < js_int::MAX_SAFE_UINT as u128 { + millis as u64 + } else { + js_int::MAX_SAFE_UINT + }; + UInt::new_saturating(clamped_ms) +} + #[derive(Clone, Debug, Deserialize, Serialize, EventContent)] #[ruma_event(type = "m.room.message", kind = MessageLike)] struct AckEventContent { @@ -79,7 +83,9 @@ impl AckEventContent { } } -pub async fn ping(event: OriginalSyncRoomMessageEvent, room: Joined) { +pub fn get_event_time_delta( + event: &OriginalSyncMessageLikeEvent, +) -> Duration { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("timetravel"); @@ -89,9 +95,12 @@ pub async fn ping(event: OriginalSyncRoomMessageEvent, room: Joined) { .expect("timestamp was not a real time") .duration_since(UNIX_EPOCH) .expect("timetravel"); - let diff = now - then; + now - then +} + +pub async fn ping(event: OriginalSyncRoomMessageEvent, room: Joined) { + let diff = get_event_time_delta(&event); let content = AckEventContent::new(&event, &diff); - room.send(content, None).await.unwrap(); }