52257759cb
Reply to commands in addition to executing them.
152 lines
4.9 KiB
Rust
152 lines
4.9 KiB
Rust
use crate::{config::Host, wol};
|
|
|
|
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, 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: HashMap<String, Host>,
|
|
}
|
|
|
|
impl WakeOnLanBot {
|
|
pub fn new(client: Client, hosts: HashMap<String, Host>) -> Self {
|
|
Self { client, hosts }
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl EventEmitter for WakeOnLanBot {
|
|
async fn on_stripped_state_member(
|
|
&self,
|
|
room: SyncRoom,
|
|
_room_member: &StrippedStateEvent<MemberEventContent>,
|
|
_: Option<MemberEventContent>,
|
|
) {
|
|
match room {
|
|
SyncRoom::Invited(room) => {
|
|
let room = room.read().await;
|
|
info!("Autojoining room {}", room.room_id);
|
|
let mut delay = 2;
|
|
|
|
while let Err(err) = self.client.join_room_by_id(&room.room_id).await {
|
|
// retry autojoin due to synapse sending invites, before the
|
|
// invited user can join for more information see
|
|
// https://github.com/matrix-org/synapse/issues/4345
|
|
info!(
|
|
"Failed to join room {} ({:?}), retrying in {}s",
|
|
room.room_id, err, delay
|
|
);
|
|
|
|
tokio::time::delay_for(Duration::from_secs(delay)).await;
|
|
delay *= 2;
|
|
|
|
if delay > 3600 {
|
|
error!("Can't join room {} ({:?})", room.room_id, err);
|
|
break;
|
|
}
|
|
}
|
|
info!("Successfully joined room {}", room.room_id);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
async fn on_room_message(&self, room: SyncRoom, event: &MessageEvent) {
|
|
match room {
|
|
SyncRoom::Joined(room) => {
|
|
let msg_body = if let MessageEvent {
|
|
content:
|
|
MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }),
|
|
..
|
|
} = event
|
|
{
|
|
msg_body.clone()
|
|
} else {
|
|
String::new()
|
|
};
|
|
|
|
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<String, Host>, 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<Self, Self::Err> {
|
|
lazy_static! {
|
|
static ref RE_WAKE: Regex = Regex::new("!wake (?P<host>.*)").unwrap();
|
|
}
|
|
|
|
if let Some(captures) = RE_WAKE.captures(s) {
|
|
return Ok(Self::Wake {
|
|
host: captures["host"].to_string(),
|
|
});
|
|
} else {
|
|
return Err("Invalid command");
|
|
}
|
|
}
|
|
}
|