make actually work with messages

This commit is contained in:
Taylor C. Richberger 2020-04-21 22:19:15 -06:00
parent 81b8b2c0cc
commit 6d180734d5
3 changed files with 68 additions and 9 deletions

View File

@ -1,4 +1,5 @@
use crate::matrix::{Event, MessageContent, RoomEvent, SyncCommand}; use crate::matrix::{Event, MessageContent, RoomEvent, SyncCommand, NoticeMessage};
use crate::commands::parse_command;
use reqwest::{Client, Url}; use reqwest::{Client, Url};
use serde::{self, Deserialize, Serialize}; use serde::{self, Deserialize, Serialize};
use std::fs; use std::fs;
@ -15,6 +16,8 @@ pub struct MatrixConfig {
/// The next batch to grab. This should be set automatically /// The next batch to grab. This should be set automatically
pub next_batch: Option<String>, pub next_batch: Option<String>,
#[serde(default)]
pub txn_id: u64,
pub login: toml::Value, pub login: toml::Value,
} }
@ -34,6 +37,7 @@ pub struct DiceBot {
next_batch: Option<String>, next_batch: Option<String>,
client: Client, client: Client,
home_server: Url, home_server: Url,
txn_id: u64,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
@ -60,6 +64,7 @@ impl DiceBot {
.await?; .await?;
let body: LoginResponse = serde_json::from_str(&response.text().await?)?; let body: LoginResponse = serde_json::from_str(&response.text().await?)?;
let next_batch = config.matrix.next_batch.clone(); let next_batch = config.matrix.next_batch.clone();
let txn_id = config.matrix.txn_id;
Ok(DiceBot { Ok(DiceBot {
home_server, home_server,
config_path, config_path,
@ -67,6 +72,7 @@ impl DiceBot {
config, config,
access_token: body.access_token, access_token: body.access_token,
next_batch, next_batch,
txn_id,
}) })
} }
@ -84,9 +90,9 @@ impl DiceBot {
/// Build a url using the current home server and the given path, as well as appending the /// Build a url using the current home server and the given path, as well as appending the
/// access token /// access token
fn url(&self, path: &str, query: &[(&str, &str)]) -> Url { fn url<S: AsRef<str>>(&self, path: S, query: &[(&str, &str)]) -> Url {
let mut url = self.home_server.clone(); let mut url = self.home_server.clone();
url.set_path(path); url.set_path(path.as_ref());
{ {
let mut query_pairs = url.query_pairs_mut(); let mut query_pairs = url.query_pairs_mut();
query_pairs.append_pair("access_token", &self.access_token); query_pairs.append_pair("access_token", &self.access_token);
@ -118,7 +124,7 @@ impl DiceBot {
let sync: SyncCommand = serde_json::from_str(&body).unwrap(); let sync: SyncCommand = serde_json::from_str(&body).unwrap();
// First join invited rooms // First join invited rooms
for room in sync.rooms.invite.keys() { for room in sync.rooms.invite.keys() {
let join_url = self.url(&format!("/_matrix/client/r0/rooms/{}/join", room), &[]); let join_url = self.url(format!("/_matrix/client/r0/rooms/{}/join", room), &[]);
self.client self.client
.post(join_url) .post(join_url)
.header("user-agent", USER_AGENT) .header("user-agent", USER_AGENT)
@ -126,16 +132,45 @@ impl DiceBot {
.await?; .await?;
} }
for (_room_id, room) in sync.rooms.join.iter() { for (room_id, room) in sync.rooms.join.iter() {
for event in &room.timeline.events { for event in &room.timeline.events {
if let Event::Room(RoomEvent { if let Event::Room(RoomEvent {
sender,
event_id,
content: MessageContent::Text(message), content: MessageContent::Text(message),
.. ..
}) = event }) = event
{ {
// TODO: create command parser (maybe command.rs) to parse !roll/!r commands let (plain, html): (String, String) = match parse_command(message.body()) {
// and reply Ok(Some(command)) => {
println!("Body: {}", message.body()); let command = command.execute();
(command.plain().into(), command.html().into())
},
Ok(None) => continue,
Err(e) => {
let message = format!("Error parsing command: {}", e);
let html_message = format!("<p><strong>{}</strong></p>", message);
(message, html_message)
},
};
let plain = format!("{}\n{}", sender, plain);
let html = format!("<p>{}</p>\n{}", sender, plain);
let message = NoticeMessage {
body: plain,
format: Some("org.matrix.custom.html".into()),
formatted_body: Some(html),
};
self.txn_id += 1;
let mut send_url = self.url(format!("/_matrix/client/r0/rooms/{}/send/m.room.message/{}", room_id, self.txn_id), &[]);
self.client
.put(send_url)
.header("user-agent", USER_AGENT)
.body(serde_json::to_string(&message)?)
.send()
.await?;
} }
} }
} }
@ -155,6 +190,7 @@ impl DiceBot {
.await?; .await?;
self.config.matrix.next_batch = self.next_batch; self.config.matrix.next_batch = self.next_batch;
self.config.matrix.txn_id = self.txn_id;
if let Some(config_path) = self.config_path { if let Some(config_path) = self.config_path {
let config = toml::to_string_pretty(&self.config)?; let config = toml::to_string_pretty(&self.config)?;

View File

@ -29,13 +29,16 @@ impl Command for RollCommand {
let roll = self.0.roll(); let roll = self.0.roll();
let plain = format!("Dice: {}\nResult: {}", self.0, roll); let plain = format!("Dice: {}\nResult: {}", self.0, roll);
let html = format!( let html = format!(
"<strong>Dice:</strong> {}<br><strong>Result</strong>: {}", "<p><strong>Dice:</strong> {}</p><p><strong>Result</strong>: {}</p>",
self.0, roll self.0, roll
); );
Execution { plain, html } Execution { plain, html }
} }
} }
/// Parse a command string into a dynamic command execution trait object.
/// Returns an error if a command was recognized but not parsed correctly. Returns None if no
/// command was recognized.
pub fn parse_command(s: &str) -> Result<Option<Box<dyn Command>>, String> { pub fn parse_command(s: &str) -> Result<Option<Box<dyn Command>>, String> {
// Ignore trailing input, if any. // Ignore trailing input, if any.
match parser::parse_command(s) { match parser::parse_command(s) {

View File

@ -1,11 +1,30 @@
use serde::{self, Deserialize, Serialize}; use serde::{self, Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
#[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "msgtype")]
#[serde(rename = "m.notice")]
pub struct NoticeMessage {
pub body: String,
#[serde(default)]
pub format: Option<String>,
#[serde(default)]
pub formatted_body: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
#[serde(tag = "msgtype")] #[serde(tag = "msgtype")]
#[serde(rename = "m.text")] #[serde(rename = "m.text")]
pub struct TextMessage { pub struct TextMessage {
body: String, body: String,
#[serde(default)]
format: Option<String>,
#[serde(default)]
formatted_body: Option<String>,
} }
impl TextMessage { impl TextMessage {
@ -26,6 +45,7 @@ pub enum MessageContent {
pub struct RoomEvent { pub struct RoomEvent {
pub content: MessageContent, pub content: MessageContent,
pub event_id: String, pub event_id: String,
pub sender: String,
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]