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

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();
}