From 52257759cb5d4f1f10106dc59f5365b460f8be6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Christian=20Gr=C3=BCnhage?= Date: Thu, 15 Oct 2020 23:49:58 +0200 Subject: [PATCH] feat: reply to commands Reply to commands in addition to executing them. --- Cargo.lock | 45 +++++++++++++++++++++---- Cargo.toml | 4 +-- src/bot.rs | 91 +++++++++++++++++++++++++++++++++++++++------------ src/config.rs | 4 +-- 4 files changed, 112 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d45ddb7..17cbc3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,14 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b476ce7103678b0c6d3d395dbbae31d48ff910bd28be979ba5d48c6351131d0d" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -565,14 +574,15 @@ dependencies = [ "chrono", "clap", "fern", + "lazy_static", "log", "matrix-sdk", + "regex", "serde", "tokio", "toml", "tracing", "url", - "wole", ] [[package]] @@ -834,6 +844,24 @@ version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" +[[package]] +name = "regex" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8963b85b8ce3074fecffde43b4b0dded83ce2f367dc8d363afc56679f3ee820b" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cab7a364d15cde1e505267766a2d3c4e22a843e1a601f0fa7564c0f82ced11c" + [[package]] name = "remove_dir_all" version = "0.5.3" @@ -1171,6 +1199,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + [[package]] name = "time" version = "0.1.44" @@ -1532,12 +1569,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "wole" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "130532c58c60bb816f30c3975734d9c4cb78981213b6dbcf19ba86539ed36e80" - [[package]] name = "ws2_32-sys" version = "0.2.1" diff --git a/Cargo.toml b/Cargo.toml index 3bd92ce..9373398 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ license = "AGPL-3.0-only" [dependencies] matrix-sdk = { version = "0.1.0", default_features = false, features = ["messages"] } -wole = "0.1.3" fern = "0.6.0" chrono = "0.4.19" tracing = { version = "0.1.21", features = ["log"] } @@ -21,4 +20,5 @@ toml = "0.5.7" serde = { version = "1.0.116", features = ["derive"] } anyhow = "1.0.33" clap = "2.33.3" -matrix-wol = { path = "." } +regex = "1.4.1" +lazy_static = "1.4.0" diff --git a/src/bot.rs b/src/bot.rs index 46d7867..cca9705 100644 --- a/src/bot.rs +++ b/src/bot.rs @@ -1,29 +1,33 @@ use crate::{config::Host, wol}; -use std::time::Duration; +use std::{collections::HashMap, str::FromStr, time::Duration}; use tracing::{error, info}; use async_trait::async_trait; use matrix_sdk::{ self, + identifiers::RoomId, events::{ room::{ member::MemberEventContent, - message::{MessageEvent, MessageEventContent, TextMessageEventContent}, + message::{MessageEvent, MessageEventContent, TextMessageEventContent, NoticeMessageEventContent}, }, stripped::StrippedStateEvent, }, Client, EventEmitter, SyncRoom, }; +use lazy_static::lazy_static; +use regex::Regex; + pub(crate) struct WakeOnLanBot { pub(crate) client: Client, - pub(crate) hosts: Vec, + pub(crate) hosts: HashMap, } impl WakeOnLanBot { - pub fn new(client: Client, hosts: Vec) -> Self { + pub fn new(client: Client, hosts: HashMap) -> Self { Self { client, hosts } } } @@ -66,7 +70,7 @@ impl EventEmitter for WakeOnLanBot { } async fn on_room_message(&self, room: SyncRoom, event: &MessageEvent) { match room { - SyncRoom::Joined(_room) => { + SyncRoom::Joined(room) => { let msg_body = if let MessageEvent { content: MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }), @@ -78,25 +82,70 @@ impl EventEmitter for WakeOnLanBot { String::new() }; - for host in &self.hosts { - //TODO: pull the host name out of the message and check it. If the hostname is - //invalid, complain - if msg_body == format!("!wake {}", host.name) { - if host.users.contains(&event.sender.to_string()) { - info!("Waking host {}", host.name); - //TODO: reply here - match wol::wake(host.mac_addr) { - Ok(()) => info!("Magic packet sent to {} successfully", host.name), - Err(e) => error!( - "Couldn't send magic packet to {}, error: {}", - host.name, e - ), - } - } - } + if let Ok(command) = Command::from_str(&msg_body) { + handle_command(command, &self.client, &self.hosts, event, &room.read().await.room_id.clone()).await; + } else { + //TODO: give help text } } _ => {} } } } + +async fn handle_command(command: Command, client: &Client, hosts: &HashMap, event: &MessageEvent, room: &RoomId) { + match command { + Command::Wake { host } => { + if let Some(host_conf) = hosts.get(&host) { + if host_conf.users.contains(&event.sender.to_string()) { + info!("Waking host {}", host); + match wol::wake(host_conf.mac_addr) { + Ok(()) => { + info!("Magic packet sent to {} successfully", host); + send_message(client, &format!("Successfully send magic packet to {}", host), room).await; + } + Err(e) => { + error!("Couldn't send magic packet to {}, error: {}", host, e); + send_message(client, &format!("Failed to send magic packet to {}", host), room).await; + } + } + } else { + send_message(client, &format!("No permission to wake up {}!", host), room).await; + } + } else { + send_message(client, &format!("Host {} not found!", host), room).await; + } + } + } +} + +async fn send_message(client: &Client, message: &str, room: &RoomId) { + client.room_send(room, MessageEventContent::Notice(NoticeMessageEventContent { + body: message.to_owned(), + format: None, + formatted_body: None, + relates_to: None, + }), None).await.unwrap(); //TODO error handling here +} + +enum Command { + Wake { host: String }, +} + +impl FromStr for Command { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + lazy_static! { + static ref RE_WAKE: Regex = Regex::new("!wake (?P.*)").unwrap(); + } + + if let Some(captures) = RE_WAKE.captures(s) { + return Ok(Self::Wake { + host: captures["host"].to_string(), + }); + } else { + return Err("Invalid command"); + } + } +} diff --git a/src/config.rs b/src/config.rs index 4569a46..fddba5f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result}; use clap::{clap_app, crate_authors, crate_description, crate_name, crate_version}; use serde::Deserialize; +use std::collections::HashMap; use tracing::info; #[derive(Deserialize)] @@ -8,12 +9,11 @@ pub(crate) struct Config { pub(crate) hs_url: String, pub(crate) username: String, pub(crate) password: String, - pub(crate) hosts: Vec, + pub(crate) hosts: HashMap, } #[derive(Deserialize)] pub(crate) struct Host { - pub(crate) name: String, pub(crate) mac_addr: [u8; 6], pub(crate) users: Vec, }