From b3c4d8a38c5766ad336b5d63254a1d5ee7174dfd Mon Sep 17 00:00:00 2001 From: projectmoon Date: Sun, 31 Jan 2021 14:06:25 +0000 Subject: [PATCH] Centralize plain text formatting at point of message sending. Instead of relying on all parts of the application to construct both HTML and plain-text responses, we now construct only HTML responses, and convert the HTML to plain text right before sending the message to Matrix. This is a first iteration, because the plain text has a few extra newlines than it should, created by use of nested

tags. --- Cargo.lock | 223 ++++++++++++++++++++++++++++++++-- Cargo.toml | 1 + src/bin/dicebot-cmd.rs | 7 +- src/bot.rs | 47 ++----- src/commands.rs | 42 ++----- src/commands/basic_rolling.rs | 3 +- src/commands/cofd.rs | 3 +- src/commands/cthulhu.rs | 6 +- src/commands/management.rs | 6 +- src/commands/misc.rs | 5 +- src/commands/variables.rs | 20 ++- src/matrix.rs | 29 +++++ 12 files changed, 280 insertions(+), 112 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c197ca5..78c1a2d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -275,13 +275,14 @@ dependencies = [ "dirs", "env_logger", "futures", + "html2text", "indoc", "itertools", "log", "matrix-sdk", "memmem", "nom", - "phf", + "phf 0.7.24", "rand 0.7.3", "serde", "sled", @@ -542,6 +543,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +[[package]] +name = "futf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c9c1ce3fa9336301af935ab852c437817d14cd33690446569392e65170aac3b" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.12" @@ -767,6 +778,31 @@ dependencies = [ "digest", ] +[[package]] +name = "html2text" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26379dcb715e237b96102a12b505c553e2bffa74bae2e54658748d298660ef1" +dependencies = [ + "html5ever", + "markup5ever_rcdom", + "unicode-width", +] + +[[package]] +name = "html5ever" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafcf38a1a36118242d29b92e1b08ef84e67e4a5ed06e0a80be20e6a32bfed6b" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2 1.0.24", + "quote 1.0.8", + "syn 1.0.60", +] + [[package]] name = "http" version = "0.2.3" @@ -884,7 +920,7 @@ checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" dependencies = [ "cfg-if 1.0.0", "js-sys", - "time", + "time 0.2.25", "wasm-bindgen", "web-sys", ] @@ -971,12 +1007,47 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "markup5ever" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae38d669396ca9b707bfc3db254bc382ddb94f57cc5c235f34623a669a01dab" +dependencies = [ + "log", + "phf 0.8.0", + "phf_codegen", + "serde", + "serde_derive", + "serde_json", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f015da43bcd8d4f144559a3423f4591d69b8ce0652c905374da7205df336ae2b" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + [[package]] name = "matches" version = "0.1.8" @@ -1135,6 +1206,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "nom" version = "5.1.2" @@ -1285,7 +1362,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" dependencies = [ "phf_macros", - "phf_shared", + "phf_shared 0.7.24", +] + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", ] [[package]] @@ -1294,18 +1390,28 @@ version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" dependencies = [ - "phf_shared", + "phf_shared 0.7.24", "rand 0.6.5", ] +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + [[package]] name = "phf_macros" version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdb45e833315153371697760dad1831da99ce41884162320305e4f123ca3fe37" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.7.24", + "phf_shared 0.7.24", "proc-macro2 0.4.30", "quote 0.6.13", "syn 0.15.44", @@ -1317,7 +1423,16 @@ version = "0.7.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" dependencies = [ - "siphasher", + "siphasher 0.2.3", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.3", ] [[package]] @@ -1405,6 +1520,12 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -1482,7 +1603,7 @@ dependencies = [ "rand_isaac", "rand_jitter", "rand_os", - "rand_pcg", + "rand_pcg 0.1.2", "rand_xorshift", "winapi", ] @@ -1498,6 +1619,7 @@ dependencies = [ "rand_chacha 0.2.2", "rand_core 0.5.1", "rand_hc 0.2.0", + "rand_pcg 0.2.1", ] [[package]] @@ -1646,6 +1768,15 @@ dependencies = [ "rand_core 0.4.2", ] +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rand_xorshift" version = "0.1.1" @@ -2082,6 +2213,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" +[[package]] +name = "siphasher" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" + [[package]] name = "slab" version = "0.4.2" @@ -2185,6 +2322,31 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +[[package]] +name = "string_cache" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb1139b5353f96e429e1a5e19fbaf663bddedaa06d1dbd49f82e352601209a" +dependencies = [ + "lazy_static", + "new_debug_unreachable", + "phf_shared 0.8.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro2 1.0.24", + "quote 1.0.8", +] + [[package]] name = "subtle" version = "2.4.0" @@ -2239,6 +2401,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "tendril" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9ef557cb397a4f0a5a3a628f06515f78563f2209e64d47055d9dc6052bf5e33" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -2277,6 +2450,16 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "time" version = "0.2.25" @@ -2485,6 +2668,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + [[package]] name = "unicode-xid" version = "0.1.0" @@ -2525,6 +2714,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" + [[package]] name = "uuid" version = "0.8.2" @@ -2687,6 +2882,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "xml5ever" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b1b52e6e8614d4a58b8e70cf51ec0cc21b256ad8206708bcff8139b5bbd6a59" +dependencies = [ + "log", + "mac", + "markup5ever", + "time 0.1.43", +] + [[package]] name = "zerocopy" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 7c28ab4..a1d18bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ byteorder = "1.3" futures = "0.3" memmem = "0.1" bincode = "1.3" +html2text = "0.2" phf = { version = "0.7", features = ["macros"] } matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk", branch = "master" } diff --git a/src/bin/dicebot-cmd.rs b/src/bin/dicebot-cmd.rs index d5d4166..1c51489 100644 --- a/src/bin/dicebot-cmd.rs +++ b/src/bin/dicebot-cmd.rs @@ -25,9 +25,8 @@ async fn main() -> Result<(), BotError> { message_body: &input, }; - println!( - "{}", - command.execute(&context).await.message_plain("fakeuser") - ); + let message = command.execute(&context).await.message_html("fakeuser"); + let message = html2text::from_read(message.as_bytes(), 80); + println!("{}", message.trim()); Ok(()) } diff --git a/src/bot.rs b/src/bot.rs index f708282..23b9d85 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -3,19 +3,11 @@ use crate::config::*; use crate::context::{Context, RoomContext}; use crate::db::Database; use crate::error::BotError; +use crate::matrix; use crate::state::DiceBotState; use dirs; -use log::{error, info}; -use matrix_sdk::Error as MatrixError; -use matrix_sdk::{ - self, - events::{ - room::message::{MessageEventContent::Notice, NoticeMessageEventContent}, - AnyMessageEventContent::RoomMessage, - }, - identifiers::RoomId, - Client, ClientConfig, JoinedRoom, SyncSettings, -}; +use log::info; +use matrix_sdk::{self, identifiers::RoomId, Client, ClientConfig, JoinedRoom, SyncSettings}; //use matrix_sdk_common_macros::async_trait; use std::clone::Clone; use std::path::PathBuf; @@ -56,15 +48,6 @@ fn create_client(config: &Config) -> Result { Ok(Client::new_with_config(homeserver_url, client_config)?) } -/// Extracts more detailed error messages out of a matrix SDK error. -fn extract_error_message(error: MatrixError) -> String { - use matrix_sdk::Error::RumaResponse; - match error { - RumaResponse(ruma_error) => ruma_error.to_string(), - _ => error.to_string(), - } -} - /// Handle responding to a single command being executed. Wil print /// out the full result of that command. async fn handle_single_result( @@ -73,16 +56,8 @@ async fn handle_single_result( respond_to: &str, room_id: &RoomId, ) { - let plain = cmd_result.message_plain(respond_to); let html = cmd_result.message_html(respond_to); - - let response = RoomMessage(Notice(NoticeMessageEventContent::html(plain, html))); - - let result = client.room_send(&room_id, response, None).await; - if let Err(e) = result { - let message = extract_error_message(e); - error!("Error sending message: {}", message); - }; + matrix::send_message(client, room_id, &html).await; } /// Handle responding to multiple commands being executed. Will print @@ -106,7 +81,7 @@ async fn handle_multiple_results( } else { let failures: String = errors .iter() - .map(|&(cmd, err)| format!("{}: {}", cmd, err)) + .map(|&(cmd, err)| format!("{}: {}", cmd, err)) .collect::>() .join("\n"); @@ -117,18 +92,10 @@ async fn handle_multiple_results( errors.len(), failures ) + .replace("\n", "
") }; - // TODO Need separate code that handles message formatting and - // sending so we aren't littering codebase with replace calls. - let html = message.replace("\n", "
"); - let response = RoomMessage(Notice(NoticeMessageEventContent::html(&message, &html))); - - let result = client.room_send(&room_id, response, None).await; - if let Err(e) = result { - let message = extract_error_message(e); - error!("Error sending message: {}", message); - }; + matrix::send_message(client, room_id, &message).await; } impl DiceBot { diff --git a/src/commands.rs b/src/commands.rs index 654a2d0..c2aa26a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -23,22 +23,16 @@ pub enum CommandError { } /// A successfully executed command returns a message to be sent back -/// to the user in both plain text and HTML, one of which will be -/// displayed in the user's client depending on its capabilities. +/// to the user in HTML (plain text used as a fallback by message +/// formatter). #[derive(Debug)] pub struct Execution { - plain: String, html: String, } impl Execution { - pub fn new(plain: String, html: String) -> CommandResult { - Ok(Execution { plain, html }) - } - - /// Response message in plain text. - pub fn plain(&self) -> String { - self.plain.clone() + pub fn new(html: String) -> CommandResult { + Ok(Execution { html }) } /// Response message in HTML. @@ -47,10 +41,9 @@ impl Execution { } } -/// Wraps a command execution failure. Provides plain-text and HTML -/// formatting for any error message from the BotError type, similar -/// to how Response provides formatting for successfully executed -/// commands. +/// Wraps a command execution failure. Provides HTML formatting for +/// any error message from the BotError type, similar to how Execution +/// provides formatting for successfully executed commands. #[derive(Error, Debug)] #[error("{0}")] pub struct ExecutionError(#[from] BotError); @@ -62,11 +55,6 @@ impl From for ExecutionError { } impl ExecutionError { - /// Error message in plain text. - pub fn plain(&self) -> String { - format!("{}", self.0) - } - /// Error message in bolded HTML. pub fn html(&self) -> String { format!("

{}

", self.0) @@ -80,29 +68,17 @@ pub type CommandResult = Result; /// Extract response messages out of a type, whether it is success or /// failure. pub trait ResponseExtractor { - /// Plain-text representation of the message, directly mentioning - /// the username. - fn message_plain(&self, username: &str) -> String; - /// HTML representation of the message, directly mentioning the /// username. fn message_html(&self, username: &str) -> String; } impl ResponseExtractor for CommandResult { - /// Error message in plain text. - fn message_plain(&self, username: &str) -> String { - match self { - Ok(resp) => format!("{}\n{}", username, resp.plain()), - Err(e) => format!("{}\n{}", username, e.plain()), - } - } - /// Error message in bolded HTML. fn message_html(&self, username: &str) -> String { match self { - Ok(resp) => format!("

{}

\n{}", username, resp.html).replace("\n", "
"), - Err(e) => format!("

{}

\n{}", username, e.html()).replace("\n", "
"), + Ok(resp) => format!("

{}

{}

", username, resp.html).replace("\n", "
"), + Err(e) => format!("

{}

{}

", username, e.html()).replace("\n", "
"), } } } diff --git a/src/commands/basic_rolling.rs b/src/commands/basic_rolling.rs index e104f6d..26489b6 100644 --- a/src/commands/basic_rolling.rs +++ b/src/commands/basic_rolling.rs @@ -14,12 +14,11 @@ impl Command for RollCommand { async fn execute(&self, _ctx: &Context<'_>) -> CommandResult { let roll = self.0.roll(); - let plain = format!("Dice: {}\nResult: {}", self.0, roll); let html = format!( "

Dice: {}

Result: {}

", self.0, roll ); - Execution::new(plain, html) + Execution::new(html) } } diff --git a/src/commands/cofd.rs b/src/commands/cofd.rs index 5e09697..7ac3a2a 100644 --- a/src/commands/cofd.rs +++ b/src/commands/cofd.rs @@ -15,12 +15,11 @@ impl Command for PoolRollCommand { let pool_with_ctx = DicePoolWithContext(&self.0, ctx); let rolled_pool = roll_pool(&pool_with_ctx).await?; - let plain = format!("Pool: {}\nResult: {}", rolled_pool, rolled_pool.roll); let html = format!( "

Pool: {}

Result: {}

", rolled_pool, rolled_pool.roll ); - Execution::new(plain, html) + Execution::new(html) } } diff --git a/src/commands/cthulhu.rs b/src/commands/cthulhu.rs index f862d9c..c6d63a1 100644 --- a/src/commands/cthulhu.rs +++ b/src/commands/cthulhu.rs @@ -15,13 +15,12 @@ impl Command for CthRoll { let roll_with_ctx = DiceRollWithContext(&self.0, ctx); let executed_roll = regular_roll(&roll_with_ctx).await?; - let plain = format!("Roll: {}\nResult: {}", executed_roll, executed_roll.roll); let html = format!( "

Roll: {}

Result: {}

", executed_roll, executed_roll.roll ); - Execution::new(plain, html) + Execution::new(html) } } @@ -36,12 +35,11 @@ impl Command for CthAdvanceRoll { async fn execute(&self, _ctx: &Context<'_>) -> CommandResult { //TODO this will be converted to a result when supporting variables. let roll = self.0.roll(); - let plain = format!("Roll: {}\nResult: {}", self.0, roll); let html = format!( "

Roll: {}

Result: {}

", self.0, roll ); - Execution::new(plain, html) + Execution::new(html) } } diff --git a/src/commands/management.rs b/src/commands/management.rs index fc76a19..8f0bbc1 100644 --- a/src/commands/management.rs +++ b/src/commands/management.rs @@ -25,9 +25,7 @@ impl Command for ResyncCommand { ) .await?; - let plain = "Room information resynced".to_string(); - let html = "

Room information resynced.

".to_string(); - - Execution::new(plain, html) + let message = "Room information resynced.".to_string(); + Execution::new(message) } } diff --git a/src/commands/misc.rs b/src/commands/misc.rs index 2e0c70c..aede558 100644 --- a/src/commands/misc.rs +++ b/src/commands/misc.rs @@ -17,8 +17,7 @@ impl Command for HelpCommand { _ => "There is no help for this topic", }; - let plain = format!("Help: {}", help); - let html = format!("

Help: {}", help.replace("\n", "
")); - Execution::new(plain, html) + let html = format!("Help: {}", help.replace("\n", "
")); + Execution::new(html) } } diff --git a/src/commands/variables.rs b/src/commands/variables.rs index 0f49cae..91d12d5 100644 --- a/src/commands/variables.rs +++ b/src/commands/variables.rs @@ -24,12 +24,11 @@ impl Command for GetAllVariablesCommand { variable_list.sort(); let value = variable_list.join("\n"); - let plain = format!("Variables:\n{}", value); let html = format!( - "

Variables:
{}", + "Variables:
{}", value.replace("\n", "
") ); - Execution::new(plain, html) + Execution::new(html) } } @@ -52,9 +51,8 @@ impl Command for GetVariableCommand { Err(e) => return Err(e.into()), }; - let plain = format!("Variable: {}", value); - let html = format!("

Variable: {}", value); - Execution::new(plain, html) + let html = format!("Variable: {}", value); + Execution::new(html) } } @@ -74,9 +72,8 @@ impl Command for SetVariableCommand { ctx.db.variables.set_user_variable(&key, name, value)?; let content = format!("{} = {}", name, value); - let plain = format!("Set Variable: {}", content); - let html = format!("

Set Variable: {}", content); - Execution::new(plain, html) + let html = format!("Set Variable: {}", content); + Execution::new(html) } } @@ -99,8 +96,7 @@ impl Command for DeleteVariableCommand { Err(e) => return Err(e.into()), }; - let plain = format!("Remove Variable: {}", value); - let html = format!("

Remove Variable: {}", value); - Execution::new(plain, html) + let html = format!("Remove Variable: {}", value); + Execution::new(html) } } diff --git a/src/matrix.rs b/src/matrix.rs index 94e3244..6dfec76 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -1,5 +1,20 @@ +use log::error; +use matrix_sdk::events::{ + room::message::{MessageEventContent::Notice, NoticeMessageEventContent}, + AnyMessageEventContent::RoomMessage, +}; +use matrix_sdk::Error as MatrixError; use matrix_sdk::{identifiers::RoomId, Client}; +/// Extracts more detailed error messages out of a matrix SDK error. +fn extract_error_message(error: MatrixError) -> String { + use matrix_sdk::Error::RumaResponse; + match error { + RumaResponse(ruma_error) => ruma_error.to_string(), + _ => error.to_string(), + } +} + /// Retrieve a list of users in a given room. pub async fn get_users_in_room(client: &Client, room_id: &RoomId) -> Vec { if let Some(joined_room) = client.get_joined_room(room_id) { @@ -21,3 +36,17 @@ pub async fn get_users_in_room(client: &Client, room_id: &RoomId) -> Vec vec![] } } + +pub async fn send_message(client: &Client, room_id: &RoomId, message: &str) { + let plain = html2text::from_read(message.as_bytes(), message.len()); + let response = RoomMessage(Notice(NoticeMessageEventContent::html( + plain.trim(), + message, + ))); + + let result = client.room_send(&room_id, response, None).await; + if let Err(e) = result { + let message = extract_error_message(e); + error!("Error sending message: {}", message); + }; +}