simple ping bot functionality

This commit is contained in:
2023-05-23 21:48:38 +02:00
parent 9ab64354de
commit 7438471f84
4 changed files with 417 additions and 5 deletions

197
Cargo.lock generated
View File

@@ -22,6 +22,21 @@ dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
[[package]]
name = "anymap2"
version = "0.13.0"
@@ -153,6 +168,18 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
dependencies = [
"iana-time-zone",
"num-integer",
"num-traits",
"winapi",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
@@ -162,6 +189,23 @@ dependencies = [
"bitflags",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "cron"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ff76b51e4c068c52bfd2866e1567bee7c567ae8f24ada09fd4307019e25eab7"
dependencies = [
"chrono",
"nom",
"once_cell",
]
[[package]]
name = "darling"
version = "0.14.4"
@@ -437,6 +481,15 @@ dependencies = [
"ahash",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "http"
version = "0.2.9"
@@ -508,6 +561,29 @@ dependencies = [
"tokio-rustls",
]
[[package]]
name = "iana-time-zone"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "ident_case"
version = "1.0.1"
@@ -627,8 +703,12 @@ checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
name = "matrix-federation-notifier"
version = "0.1.0"
dependencies = [
"anyhow",
"js_int",
"matrix-sdk",
"serde",
"tokio",
"tokio-cron-scheduler",
]
[[package]]
@@ -637,6 +717,7 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbeafb4809f33f377165f2fbcf10e0613053ad206762194c3050a727fd3abcb2"
dependencies = [
"anyhow",
"anymap2",
"async-once-cell",
"async-stream",
@@ -717,6 +798,12 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "mio"
version = "0.8.6"
@@ -729,6 +816,56 @@ dependencies = [
"windows-sys 0.45.0",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-derive"
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",
"syn 1.0.109",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg 1.1.0",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg 1.1.0",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.17.1"
@@ -746,6 +883,16 @@ dependencies = [
"parking_lot_core 0.8.6",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core 0.9.7",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
@@ -1281,6 +1428,15 @@ dependencies = [
"serde",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "slab"
version = "0.4.8"
@@ -1396,11 +1552,41 @@ dependencies = [
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot 0.12.1",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-cron-scheduler"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de2c1fd54a857b29c6cd1846f31903d0ae8e28175615c14a277aed45c58d8e27"
dependencies = [
"chrono",
"cron",
"num-derive",
"num-traits",
"tokio",
"tracing",
"uuid 1.3.3",
]
[[package]]
name = "tokio-macros"
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",
]
[[package]]
name = "tokio-rustls"
version = "0.24.0"
@@ -1653,7 +1839,7 @@ checksum = "be0ecb0db480561e9a7642b5d3e4187c128914e58aa84330b9493e3eb68c5e7f"
dependencies = [
"futures",
"js-sys",
"parking_lot",
"parking_lot 0.11.2",
"pin-utils",
"wasm-bindgen",
"wasm-bindgen-futures",
@@ -1717,6 +1903,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [
"windows-targets 0.48.0",
]
[[package]]
name = "windows-sys"
version = "0.45.0"

View File

@@ -6,5 +6,9 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
matrix-sdk = { version = "0.6.2", default-features = false, features = ["rustls-tls"] }
tokio = "1.28.1"
anyhow = "1.0.71"
js_int = { version = "0.2.2", features = ["serde"] }
matrix-sdk = { version = "0.6.2", default-features = false, features = ["rustls-tls", "anyhow"] }
serde = { version = "1.0.163", features = ["derive"] }
tokio = { version = "1.28.1", features = ["full"] }
tokio-cron-scheduler = "0.9.4"

View File

@@ -1,3 +1,119 @@
fn main() {
println!("Hello, world!");
// Parts of this file was lifted from https://github.com/matrix-org/matrix-rust-sdk/commit/3db90fbe026c222167000fcfdee8b35b44ae5694
// And are thus licensed under Apache
use matrix_sdk::{
config::SyncSettings,
room::Room,
ruma::events::room::message::{MessageType, OriginalSyncRoomMessageEvent},
ruma::events::{macros::EventContent, room::member::StrippedRoomMemberEvent},
Client,
};
use serde::{Deserialize, Serialize};
use std::{env, process::exit};
use tokio::time::{sleep, Duration};
mod ping;
async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) {
if let Room::Joined(room) = room {
let MessageType::Text(text_content) = &event.content.msgtype else {
return;
};
if text_content.body.starts_with("!ping ") {
ping::ping(event, room).await;
}
}
}
async fn on_stripped_state_member(
room_member: StrippedRoomMemberEvent,
client: Client,
room: Room,
) {
if room_member.state_key != client.user_id().unwrap() {
return;
}
if let Room::Invited(room) = room {
tokio::spawn(async move {
println!("Autojoining room {}", room.room_id());
let mut delay = 2;
while let Err(err) = room.accept_invitation().await {
// retry autojoin due to synapse sending invites, before the
// invited user can join for more information see
// https://github.com/matrix-org/synapse/issues/4345
eprintln!(
"Failed to join room {} ({err:?}), retrying in {delay}s",
room.room_id()
);
sleep(Duration::from_secs(delay)).await;
delay *= 2;
if delay > 3600 {
eprintln!("Can't join room {} ({err:?})", room.room_id());
break;
}
}
println!("Successfully joined room {}", room.room_id());
});
}
}
async fn login_and_sync(
homeserver_url: String,
username: String,
password: String,
) -> anyhow::Result<()> {
// Note that when encryption is enabled, you should use a persistent store to be
// able to restore the session with a working encryption setup.
// See the `persist_session` example.
let client = Client::builder()
.homeserver_url(homeserver_url)
.build()
.await
.unwrap();
client
.login_username(&username, &password)
.initial_device_display_name("ping bot")
.send()
.await?;
println!("logged in as {username}");
// An initial sync to set up state and so our bot doesn't respond to old
// messages.
let response = client.sync_once(SyncSettings::default()).await.unwrap();
println!("Finished initial sync");
client.add_event_handler(on_room_message);
client.add_event_handler(on_stripped_state_member);
// 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);
client.sync(settings).await?;
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let (homeserver_url, username, password) =
match (env::args().nth(1), env::args().nth(2), env::args().nth(3)) {
(Some(a), Some(b), Some(c)) => (a, b, c),
_ => {
eprintln!(
"Usage: {} <homeserver_url> <username> <password>",
env::args().next().unwrap()
);
exit(1)
}
};
login_and_sync(homeserver_url, username, password).await?;
Ok(())
}

97
src/ping.rs Normal file
View File

@@ -0,0 +1,97 @@
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use matrix_sdk::{
room::Joined,
ruma::{
events::room::message::OriginalSyncRoomMessageEvent, exports::ruma_macros::EventContent,
OwnedEventId, UInt,
},
};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "rel_type", rename = "xyz.maubot.pong")]
struct PongRelation {
event_id: OwnedEventId,
from: String,
ms: UInt,
}
impl PongRelation {
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
};
PongRelation {
event_id: event.event_id.to_owned(),
from: event.sender.server_name().to_string(),
ms: UInt::new_saturating(clamped_ms),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct PongMixin {
from: String,
ms: UInt,
ping: OwnedEventId,
}
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),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize, EventContent)]
#[ruma_event(type = "m.room.message", kind = MessageLike)]
struct AckEventContent {
body: String,
msgtype: String,
#[serde(rename = "m.relates_to")]
relates_to: PongRelation,
pong: PongMixin,
}
impl AckEventContent {
fn new(event: &OriginalSyncRoomMessageEvent, diff: &Duration) -> Self {
let plain = format!("ping took {:?} to arrive", &diff);
Self {
body: plain,
msgtype: "m.notice".to_string(),
relates_to: PongRelation::new(&event, &diff),
pong: PongMixin::new(&event, &diff),
}
}
}
pub async fn ping(event: OriginalSyncRoomMessageEvent, room: Joined) {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("timetravel");
let then = event
.origin_server_ts
.to_system_time()
.expect("timestamp was not a real time")
.duration_since(UNIX_EPOCH)
.expect("timetravel");
let diff = now - then;
let content = AckEventContent::new(&event, &diff);
room.send(content, None).await.unwrap();
}