Files
matrix-rust-sdk/examples/command_bot/src/main.rs
Benjamin Kampmann e8278b5d68 feat: add Result return value to sync_* (#1037)
Inspired by the changes in #1013 I was thinking about the use case for `sync_*` and how we handle error cases. Most notably while we give the callback the option to stop the loop, we don't really give an indication to the outside, how to interpret that cancellation: was there a failure? should we restart?

Take e.g. a connectivity issue on the wire, we'd constantly loop and just `warn`, what you might or might not see. Even if you handle that in the `sync_with_result_callback` and thus break the loop, the outer caller now still doesn't know whether everything is honky dory or whether they should restart. 

This Changes reworks that area by having all the `sync` return `Result<(), Error>`, where `()` means it was ended by the inner callback (which in `sync()` never occurs) or `Error` is the error either the inner `result_callback` found or the that was coming from the `send` in the first place. Thus allowing us to e.g. back down to sync as it was a dead wire or restart it if there was only a temporary problem. Making all that a just a bit more "rust-y".
2022-09-26 17:14:15 +02:00

100 lines
3.2 KiB
Rust

use std::{env, process::exit};
use matrix_sdk::{
config::SyncSettings,
room::Room,
ruma::events::room::message::{
MessageType, OriginalSyncRoomMessageEvent, RoomMessageEventContent, TextMessageEventContent,
},
Client,
};
async fn on_room_message(event: OriginalSyncRoomMessageEvent, room: Room) {
if let Room::Joined(room) = room {
let msg_body = match event.content.msgtype {
MessageType::Text(TextMessageEventContent { body, .. }) => body,
_ => return,
};
if msg_body.contains("!party") {
let content = RoomMessageEventContent::text_plain("🎉🎊🥳 let's PARTY!! 🥳🎊🎉");
println!("sending");
// send our message to the room we found the "!party" command in
// the last parameter is an optional transaction id which we don't
// care about.
room.send(content, None).await.unwrap();
println!("message sent");
}
}
}
async fn login_and_sync(
homeserver_url: String,
username: String,
password: String,
) -> anyhow::Result<()> {
#[allow(unused_mut)]
let mut client_builder = Client::builder().homeserver_url(homeserver_url);
#[cfg(feature = "sled")]
{
// The location to save files to
let home = dirs::home_dir().expect("no home directory found").join("party_bot");
client_builder = client_builder.sled_store(home, None)?;
}
#[cfg(feature = "indexeddb")]
{
client_builder = client_builder.indexeddb_store("party_bot", None).await?;
}
let client = client_builder.build().await.unwrap();
client
.login_username(&username, &password)
.initial_device_display_name("command 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. If the `StateStore` finds saved state in the location given the
// initial sync will be skipped in favor of loading state from the store
client.sync_once(SyncSettings::default()).await.unwrap();
// add our CommandBot to be notified of incoming messages, we do this after the
// initial sync to avoid responding to messages before the bot was running.
client.add_event_handler(on_room_message);
// since we called `sync_once` before we entered our sync loop we must pass
// that sync token to `sync`
let settings = SyncSettings::default().token(client.sync_token().await.unwrap());
// this keeps state from the server streaming in to CommandBot via the
// EventHandler trait
client.sync(settings).await?;
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
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(())
}