Compare commits

..

3 Commits

Author SHA1 Message Date
projectmoon b3c4d8a38c Centralize plain text formatting at point of message sending.
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
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 <p> tags.
2021-01-31 14:12:09 +00:00
projectmoon a4e66a0ca6 Basic output for multiple command failures.
continuous-integration/drone/pr Build is failing Details
continuous-integration/drone/push Build is passing Details
Also replace newlines with <br/>s in HTML output.
2021-01-31 08:15:22 +00:00
projectmoon d0c6ca3de8 Print out how many commands failed in a multi-command scenario.
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/pr Build is passing Details
The number of failing commands are now printed out when at least one
command in a multi-command execution fails. This commit does not
introduce printing out WHICH commands failed, nor their error
messages.

There was also some minor refactoring to move command response
handling into their own functions (one for single response, one for
multiple) so that the code is more readable.
2021-01-30 22:13:06 +00:00
12 changed files with 329 additions and 117 deletions

223
Cargo.lock generated
View File

@ -275,13 +275,14 @@ dependencies = [
"dirs", "dirs",
"env_logger", "env_logger",
"futures", "futures",
"html2text",
"indoc", "indoc",
"itertools", "itertools",
"log", "log",
"matrix-sdk", "matrix-sdk",
"memmem", "memmem",
"nom", "nom",
"phf", "phf 0.7.24",
"rand 0.7.3", "rand 0.7.3",
"serde", "serde",
"sled", "sled",
@ -542,6 +543,16 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 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]] [[package]]
name = "futures" name = "futures"
version = "0.3.12" version = "0.3.12"
@ -767,6 +778,31 @@ dependencies = [
"digest", "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]] [[package]]
name = "http" name = "http"
version = "0.2.3" version = "0.2.3"
@ -884,7 +920,7 @@ checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"js-sys", "js-sys",
"time", "time 0.2.25",
"wasm-bindgen", "wasm-bindgen",
"web-sys", "web-sys",
] ]
@ -971,12 +1007,47 @@ dependencies = [
"cfg-if 1.0.0", "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]] [[package]]
name = "maplit" name = "maplit"
version = "1.0.2" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" 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]] [[package]]
name = "matches" name = "matches"
version = "0.1.8" version = "0.1.8"
@ -1135,6 +1206,12 @@ dependencies = [
"tempfile", "tempfile",
] ]
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]] [[package]]
name = "nom" name = "nom"
version = "5.1.2" version = "5.1.2"
@ -1285,7 +1362,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18"
dependencies = [ dependencies = [
"phf_macros", "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]] [[package]]
@ -1294,18 +1390,28 @@ version = "0.7.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662"
dependencies = [ dependencies = [
"phf_shared", "phf_shared 0.7.24",
"rand 0.6.5", "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]] [[package]]
name = "phf_macros" name = "phf_macros"
version = "0.7.24" version = "0.7.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bdb45e833315153371697760dad1831da99ce41884162320305e4f123ca3fe37" checksum = "bdb45e833315153371697760dad1831da99ce41884162320305e4f123ca3fe37"
dependencies = [ dependencies = [
"phf_generator", "phf_generator 0.7.24",
"phf_shared", "phf_shared 0.7.24",
"proc-macro2 0.4.30", "proc-macro2 0.4.30",
"quote 0.6.13", "quote 0.6.13",
"syn 0.15.44", "syn 0.15.44",
@ -1317,7 +1423,16 @@ version = "0.7.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0"
dependencies = [ 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]] [[package]]
@ -1405,6 +1520,12 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "0.1.5" version = "0.1.5"
@ -1482,7 +1603,7 @@ dependencies = [
"rand_isaac", "rand_isaac",
"rand_jitter", "rand_jitter",
"rand_os", "rand_os",
"rand_pcg", "rand_pcg 0.1.2",
"rand_xorshift", "rand_xorshift",
"winapi", "winapi",
] ]
@ -1498,6 +1619,7 @@ dependencies = [
"rand_chacha 0.2.2", "rand_chacha 0.2.2",
"rand_core 0.5.1", "rand_core 0.5.1",
"rand_hc 0.2.0", "rand_hc 0.2.0",
"rand_pcg 0.2.1",
] ]
[[package]] [[package]]
@ -1646,6 +1768,15 @@ dependencies = [
"rand_core 0.4.2", "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]] [[package]]
name = "rand_xorshift" name = "rand_xorshift"
version = "0.1.1" version = "0.1.1"
@ -2082,6 +2213,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
[[package]]
name = "siphasher"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.2" version = "0.4.2"
@ -2185,6 +2322,31 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" 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]] [[package]]
name = "subtle" name = "subtle"
version = "2.4.0" version = "2.4.0"
@ -2239,6 +2401,17 @@ dependencies = [
"winapi", "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]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.1.2" version = "1.1.2"
@ -2277,6 +2450,16 @@ dependencies = [
"once_cell", "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]] [[package]]
name = "time" name = "time"
version = "0.2.25" version = "0.2.25"
@ -2485,6 +2668,12 @@ dependencies = [
"tinyvec", "tinyvec",
] ]
[[package]]
name = "unicode-width"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.1.0" version = "0.1.0"
@ -2525,6 +2714,12 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "utf-8"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "0.8.2" version = "0.8.2"
@ -2687,6 +2882,18 @@ dependencies = [
"winapi", "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]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.3.0" version = "0.3.0"

View File

@ -29,6 +29,7 @@ byteorder = "1.3"
futures = "0.3" futures = "0.3"
memmem = "0.1" memmem = "0.1"
bincode = "1.3" bincode = "1.3"
html2text = "0.2"
phf = { version = "0.7", features = ["macros"] } phf = { version = "0.7", features = ["macros"] }
matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk", branch = "master" } matrix-sdk = { git = "https://github.com/matrix-org/matrix-rust-sdk", branch = "master" }

View File

@ -25,9 +25,8 @@ async fn main() -> Result<(), BotError> {
message_body: &input, message_body: &input,
}; };
println!( let message = command.execute(&context).await.message_html("fakeuser");
"{}", let message = html2text::from_read(message.as_bytes(), 80);
command.execute(&context).await.message_plain("fakeuser") println!("{}", message.trim());
);
Ok(()) Ok(())
} }

View File

@ -1,20 +1,13 @@
use crate::commands::execute_command; use crate::commands::{execute_command, CommandResult, ExecutionError, ResponseExtractor};
use crate::config::*; use crate::config::*;
use crate::context::{Context, RoomContext}; use crate::context::{Context, RoomContext};
use crate::db::Database; use crate::db::Database;
use crate::error::BotError; use crate::error::BotError;
use crate::matrix;
use crate::state::DiceBotState; use crate::state::DiceBotState;
use dirs; use dirs;
use log::{error, info}; use log::info;
use matrix_sdk::Error as MatrixError; use matrix_sdk::{self, identifiers::RoomId, Client, ClientConfig, JoinedRoom, SyncSettings};
use matrix_sdk::{
self,
events::{
room::message::{MessageEventContent, NoticeMessageEventContent},
AnyMessageEventContent,
},
Client, ClientConfig, JoinedRoom, SyncSettings,
};
//use matrix_sdk_common_macros::async_trait; //use matrix_sdk_common_macros::async_trait;
use std::clone::Clone; use std::clone::Clone;
use std::path::PathBuf; use std::path::PathBuf;
@ -55,13 +48,54 @@ fn create_client(config: &Config) -> Result<Client, BotError> {
Ok(Client::new_with_config(homeserver_url, client_config)?) Ok(Client::new_with_config(homeserver_url, client_config)?)
} }
/// Extracts more detailed error messages out of a matrix SDK error. /// Handle responding to a single command being executed. Wil print
fn extract_error_message(error: MatrixError) -> String { /// out the full result of that command.
use matrix_sdk::Error::RumaResponse; async fn handle_single_result(
match error { client: &Client,
RumaResponse(ruma_error) => ruma_error.to_string(), cmd_result: &CommandResult,
_ => error.to_string(), respond_to: &str,
room_id: &RoomId,
) {
let html = cmd_result.message_html(respond_to);
matrix::send_message(client, room_id, &html).await;
} }
/// Handle responding to multiple commands being executed. Will print
/// out how many commands succeeded and failed (if any failed).
async fn handle_multiple_results(
client: &Client,
results: &[(&str, CommandResult)],
respond_to: &str,
room_id: &RoomId,
) {
let errors: Vec<(&str, &ExecutionError)> = results
.into_iter()
.filter_map(|(cmd, result)| match result {
Err(e) => Some((*cmd, e)),
_ => None,
})
.collect();
let message = if errors.len() == 0 {
format!("{}: Executed {} commands", respond_to, results.len())
} else {
let failures: String = errors
.iter()
.map(|&(cmd, err)| format!("<strong>{}:</strong> {}", cmd, err))
.collect::<Vec<_>>()
.join("\n");
format!(
"{}: Executed {} commands ({} failed)\n\nFailures:\n{}",
respond_to,
results.len(),
errors.len(),
failures
)
.replace("\n", "<br/>")
};
matrix::send_message(client, room_id, &message).await;
} }
impl DiceBot { impl DiceBot {
@ -122,7 +156,7 @@ impl DiceBot {
let room_name = room.display_name().await.ok().unwrap_or_default(); let room_name = room.display_name().await.ok().unwrap_or_default();
let room_id = room.room_id().clone(); let room_id = room.room_id().clone();
let mut results = Vec::with_capacity(msg_body.lines().count()); let mut results: Vec<(&str, CommandResult)> = Vec::with_capacity(msg_body.lines().count());
let commands = msg_body.trim().lines().filter(|line| line.starts_with("!")); let commands = msg_body.trim().lines().filter(|line| line.starts_with("!"));
@ -136,37 +170,14 @@ impl DiceBot {
}; };
let cmd_result = execute_command(&ctx).await; let cmd_result = execute_command(&ctx).await;
results.push(cmd_result); results.push((&command, cmd_result));
} }
use crate::commands::ResponseExtractor;
if results.len() >= 1 { if results.len() >= 1 {
if results.len() == 1 { if results.len() == 1 {
let cmd_result = &results[0]; handle_single_result(&self.client, &results[0].1, sender_username, &room_id).await;
let response = AnyMessageEventContent::RoomMessage(MessageEventContent::Notice(
NoticeMessageEventContent::html(
cmd_result.message_plain(&sender_username),
cmd_result.message_html(&sender_username),
),
));
let result = self.client.room_send(&room_id, response, None).await;
if let Err(e) = result {
let message = extract_error_message(e);
error!("Error sending message: {}", message);
};
} else if results.len() > 1 { } else if results.len() > 1 {
let message = format!("{}: Executed {} commands", sender_username, results.len()); handle_multiple_results(&self.client, &results, sender_username, &room_id).await;
let response = AnyMessageEventContent::RoomMessage(MessageEventContent::Notice(
NoticeMessageEventContent::html(&message, &message),
));
let result = self.client.room_send(&room_id, response, None).await;
if let Err(e) = result {
let message = extract_error_message(e);
error!("Error sending message: {}", message);
};
} }
info!("[{}] {} executed: {}", room_name, sender_username, msg_body); info!("[{}] {} executed: {}", room_name, sender_username, msg_body);

View File

@ -23,22 +23,16 @@ pub enum CommandError {
} }
/// A successfully executed command returns a message to be sent back /// 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 /// to the user in HTML (plain text used as a fallback by message
/// displayed in the user's client depending on its capabilities. /// formatter).
#[derive(Debug)] #[derive(Debug)]
pub struct Execution { pub struct Execution {
plain: String,
html: String, html: String,
} }
impl Execution { impl Execution {
pub fn new(plain: String, html: String) -> CommandResult { pub fn new(html: String) -> CommandResult {
Ok(Execution { plain, html }) Ok(Execution { html })
}
/// Response message in plain text.
pub fn plain(&self) -> String {
self.plain.clone()
} }
/// Response message in HTML. /// Response message in HTML.
@ -47,10 +41,9 @@ impl Execution {
} }
} }
/// Wraps a command execution failure. Provides plain-text and HTML /// Wraps a command execution failure. Provides HTML formatting for
/// formatting for any error message from the BotError type, similar /// any error message from the BotError type, similar to how Execution
/// to how Response provides formatting for successfully executed /// provides formatting for successfully executed commands.
/// commands.
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("{0}")] #[error("{0}")]
pub struct ExecutionError(#[from] BotError); pub struct ExecutionError(#[from] BotError);
@ -62,11 +55,6 @@ impl From<crate::db::errors::DataError> for ExecutionError {
} }
impl ExecutionError { impl ExecutionError {
/// Error message in plain text.
pub fn plain(&self) -> String {
format!("{}", self.0)
}
/// Error message in bolded HTML. /// Error message in bolded HTML.
pub fn html(&self) -> String { pub fn html(&self) -> String {
format!("<p><strong>{}</strong></p>", self.0) format!("<p><strong>{}</strong></p>", self.0)
@ -80,29 +68,17 @@ pub type CommandResult = Result<Execution, ExecutionError>;
/// Extract response messages out of a type, whether it is success or /// Extract response messages out of a type, whether it is success or
/// failure. /// failure.
pub trait ResponseExtractor { 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 /// HTML representation of the message, directly mentioning the
/// username. /// username.
fn message_html(&self, username: &str) -> String; fn message_html(&self, username: &str) -> String;
} }
impl ResponseExtractor for CommandResult { 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. /// Error message in bolded HTML.
fn message_html(&self, username: &str) -> String { fn message_html(&self, username: &str) -> String {
match self { match self {
Ok(resp) => format!("<p>{}</p>\n{}", username, resp.html), Ok(resp) => format!("<p>{}</p><p>{}</p>", username, resp.html).replace("\n", "<br/>"),
Err(e) => format!("<p>{}</p>\n{}", username, e.html()), Err(e) => format!("<p>{}</p><p>{}</p>", username, e.html()).replace("\n", "<br/>"),
} }
} }
} }

View File

@ -14,12 +14,11 @@ impl Command for RollCommand {
async fn execute(&self, _ctx: &Context<'_>) -> CommandResult { async fn execute(&self, _ctx: &Context<'_>) -> CommandResult {
let roll = self.0.roll(); let roll = self.0.roll();
let plain = format!("Dice: {}\nResult: {}", self.0, roll);
let html = format!( let html = format!(
"<p><strong>Dice:</strong> {}</p><p><strong>Result</strong>: {}</p>", "<p><strong>Dice:</strong> {}</p><p><strong>Result</strong>: {}</p>",
self.0, roll self.0, roll
); );
Execution::new(plain, html) Execution::new(html)
} }
} }

View File

@ -15,12 +15,11 @@ impl Command for PoolRollCommand {
let pool_with_ctx = DicePoolWithContext(&self.0, ctx); let pool_with_ctx = DicePoolWithContext(&self.0, ctx);
let rolled_pool = roll_pool(&pool_with_ctx).await?; let rolled_pool = roll_pool(&pool_with_ctx).await?;
let plain = format!("Pool: {}\nResult: {}", rolled_pool, rolled_pool.roll);
let html = format!( let html = format!(
"<p><strong>Pool:</strong> {}</p><p><strong>Result</strong>: {}</p>", "<p><strong>Pool:</strong> {}</p><p><strong>Result</strong>: {}</p>",
rolled_pool, rolled_pool.roll rolled_pool, rolled_pool.roll
); );
Execution::new(plain, html) Execution::new(html)
} }
} }

View File

@ -15,13 +15,12 @@ impl Command for CthRoll {
let roll_with_ctx = DiceRollWithContext(&self.0, ctx); let roll_with_ctx = DiceRollWithContext(&self.0, ctx);
let executed_roll = regular_roll(&roll_with_ctx).await?; let executed_roll = regular_roll(&roll_with_ctx).await?;
let plain = format!("Roll: {}\nResult: {}", executed_roll, executed_roll.roll);
let html = format!( let html = format!(
"<p><strong>Roll:</strong> {}</p><p><strong>Result</strong>: {}</p>", "<p><strong>Roll:</strong> {}</p><p><strong>Result</strong>: {}</p>",
executed_roll, executed_roll.roll 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 { async fn execute(&self, _ctx: &Context<'_>) -> CommandResult {
//TODO this will be converted to a result when supporting variables. //TODO this will be converted to a result when supporting variables.
let roll = self.0.roll(); let roll = self.0.roll();
let plain = format!("Roll: {}\nResult: {}", self.0, roll);
let html = format!( let html = format!(
"<p><strong>Roll:</strong> {}</p><p><strong>Result</strong>: {}</p>", "<p><strong>Roll:</strong> {}</p><p><strong>Result</strong>: {}</p>",
self.0, roll self.0, roll
); );
Execution::new(plain, html) Execution::new(html)
} }
} }

View File

@ -25,9 +25,7 @@ impl Command for ResyncCommand {
) )
.await?; .await?;
let plain = "Room information resynced".to_string(); let message = "Room information resynced.".to_string();
let html = "<p>Room information resynced.</p>".to_string(); Execution::new(message)
Execution::new(plain, html)
} }
} }

View File

@ -17,8 +17,7 @@ impl Command for HelpCommand {
_ => "There is no help for this topic", _ => "There is no help for this topic",
}; };
let plain = format!("Help: {}", help); let html = format!("<strong>Help:</strong> {}", help.replace("\n", "<br/>"));
let html = format!("<p><strong>Help:</strong> {}", help.replace("\n", "<br/>")); Execution::new(html)
Execution::new(plain, html)
} }
} }

View File

@ -24,12 +24,11 @@ impl Command for GetAllVariablesCommand {
variable_list.sort(); variable_list.sort();
let value = variable_list.join("\n"); let value = variable_list.join("\n");
let plain = format!("Variables:\n{}", value);
let html = format!( let html = format!(
"<p><strong>Variables:</strong><br/>{}", "<strong>Variables:</strong><br/>{}",
value.replace("\n", "<br/>") value.replace("\n", "<br/>")
); );
Execution::new(plain, html) Execution::new(html)
} }
} }
@ -52,9 +51,8 @@ impl Command for GetVariableCommand {
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}; };
let plain = format!("Variable: {}", value); let html = format!("<strong>Variable:</strong> {}", value);
let html = format!("<p><strong>Variable:</strong> {}", value); Execution::new(html)
Execution::new(plain, html)
} }
} }
@ -74,9 +72,8 @@ impl Command for SetVariableCommand {
ctx.db.variables.set_user_variable(&key, name, value)?; ctx.db.variables.set_user_variable(&key, name, value)?;
let content = format!("{} = {}", name, value); let content = format!("{} = {}", name, value);
let plain = format!("Set Variable: {}", content); let html = format!("<strong>Set Variable:</strong> {}", content);
let html = format!("<p><strong>Set Variable:</strong> {}", content); Execution::new(html)
Execution::new(plain, html)
} }
} }
@ -99,8 +96,7 @@ impl Command for DeleteVariableCommand {
Err(e) => return Err(e.into()), Err(e) => return Err(e.into()),
}; };
let plain = format!("Remove Variable: {}", value); let html = format!("<strong>Remove Variable:</strong> {}", value);
let html = format!("<p><strong>Remove Variable:</strong> {}", value); Execution::new(html)
Execution::new(plain, html)
} }
} }

View File

@ -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}; 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. /// Retrieve a list of users in a given room.
pub async fn get_users_in_room(client: &Client, room_id: &RoomId) -> Vec<String> { pub async fn get_users_in_room(client: &Client, room_id: &RoomId) -> Vec<String> {
if let Some(joined_room) = client.get_joined_room(room_id) { 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<String>
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);
};
}