From 3f9243a3261faeddf5d0619ecfb5c36dcf4b93f4 Mon Sep 17 00:00:00 2001 From: Devin R Date: Wed, 15 Apr 2020 07:52:29 -0400 Subject: [PATCH] command-bot: add comments, use timestamp to filter old messages --- examples/command_bot.rs | 31 ++++++++++++++++++++++--------- examples/login.rs | 3 ++- src/async_client.rs | 2 ++ src/event_emitter/mod.rs | 23 ++++++++++++----------- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/examples/command_bot.rs b/examples/command_bot.rs index 56925416..2f2213e5 100644 --- a/examples/command_bot.rs +++ b/examples/command_bot.rs @@ -1,23 +1,31 @@ use std::sync::Arc; +use std::time::{SystemTime, UNIX_EPOCH}; use std::{env, process::exit}; -use url::Url; - +use js_int::UInt; use matrix_sdk::{ self, events::room::message::{MessageEvent, MessageEventContent, TextMessageEventContent}, AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings, }; use tokio::sync::{Mutex, RwLock}; +use url::Url; struct CommandBot { + /// This clone of the `AsyncClient` will send requests to the server, + /// while the other keeps us in sync with the server using `sync_forever`. client: Mutex, + /// A timestamp so we only respond to messages sent after the bot is running. + start_time: UInt, } impl CommandBot { pub fn new(client: AsyncClient) -> Self { + let now = SystemTime::now(); + let timestamp = now.duration_since(UNIX_EPOCH).unwrap().as_millis(); Self { client: Mutex::new(client), + start_time: UInt::new(timestamp as u64).unwrap(), } } } @@ -25,30 +33,34 @@ impl CommandBot { #[async_trait::async_trait] impl EventEmitter for CommandBot { async fn on_room_message(&self, room: Arc>, event: &MessageEvent) { - let msg_body = if let MessageEvent { + let (msg_body, timestamp) = if let MessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), + origin_server_ts, .. } = event { - msg_body.clone() + (msg_body.clone(), *origin_server_ts) } else { - String::new() + (String::new(), UInt::min_value()) }; - if msg_body.contains("!party") { + if msg_body.contains("!party") && timestamp > self.start_time { let content = MessageEventContent::Text(TextMessageEventContent { body: "🎉🎊🥳 let's PARTY!! 🥳🎊🎉".to_string(), format: None, formatted_body: None, relates_to: None, }); - let room_id = { room.read().await.room_id.clone() }; + // we clone here to hold the lock for as little time as possible. + let room_id = room.read().await.room_id.clone(); println!("sending"); self.client .lock() .await + // send our message to the room we found the "!party" command in + // the last parameter is an optional Uuid which we don't care about. .room_send(&room_id, content, None) .await .unwrap(); @@ -58,7 +70,6 @@ impl EventEmitter for CommandBot { } } -#[allow(clippy::for_loop_over_option)] async fn login_and_sync( homeserver_url: String, username: String, @@ -69,8 +80,9 @@ async fn login_and_sync( .disable_ssl_verification(); let homeserver_url = Url::parse(&homeserver_url)?; + // create a new AsyncClient with the given homeserver url and config let mut client = AsyncClient::new_with_config(homeserver_url, None, client_config).unwrap(); - + // add our CommandBot to be notified of incoming messages client .add_event_emitter(Box::new(CommandBot::new(client.clone()))) .await; @@ -86,6 +98,7 @@ async fn login_and_sync( println!("logged in as {}", username); + // this keeps state from the server streaming in to CommandBot via the EventEmitter trait client.sync_forever(SyncSettings::new(), |_| async {}).await; Ok(()) diff --git a/examples/login.rs b/examples/login.rs index 703fd2e0..150684c7 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -21,7 +21,8 @@ impl EventEmitter for EventCallback { } = event { let name = { - // any reads or + // any reads should be held for the shortest time possible to + // avoid dead locks let room = room.read().await; let member = room.members.get(&sender).unwrap(); member diff --git a/src/async_client.rs b/src/async_client.rs index d52b724c..86a9e139 100644 --- a/src/async_client.rs +++ b/src/async_client.rs @@ -53,6 +53,8 @@ const DEFAULT_SYNC_TIMEOUT: Duration = Duration::from_secs(30); #[derive(Clone)] /// An async/await enabled Matrix client. +/// +/// All of the state is held in an `Arc` so the `AsyncClient` can be cloned freely. pub struct AsyncClient { /// The URL of the homeserver to connect to. homeserver: Url, diff --git a/src/event_emitter/mod.rs b/src/event_emitter/mod.rs index f3afd392..2d675521 100644 --- a/src/event_emitter/mod.rs +++ b/src/event_emitter/mod.rs @@ -50,28 +50,29 @@ use tokio::sync::RwLock; /// }, /// AsyncClient, AsyncClientConfig, EventEmitter, Room, SyncSettings, /// }; -/// use tokio::sync::Mutex; +/// use tokio::sync::RwLock; /// /// struct EventCallback; /// /// #[async_trait::async_trait] /// impl EventEmitter for EventCallback { -/// async fn on_room_message(&self, room: &Room, event: &MessageEvent) { +/// async fn on_room_message(&self, room: Arc>, event: &MessageEvent) { /// if let MessageEvent { /// content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), /// sender, /// .. /// } = event /// { -/// let member = room.members.get(&sender).unwrap(); -/// println!( -/// "{}: {}", -/// member -/// .display_name -/// .as_ref() -/// .unwrap_or(&sender.to_string()), -/// msg_body -/// ); +/// let name = { +/// let room = room.read().await; +/// let member = room.members.get(&sender).unwrap(); +/// member +/// .display_name +/// .as_ref() +/// .map(ToString::to_string) +/// .unwrap_or(sender.to_string()) +/// }; +/// println!("{}: {}", name, msg_body); /// } /// } /// }