matrix-wol/src/bot.rs

188 lines
5.8 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,
events::{
room::{
member::MemberEventContent,
message::{
MessageEvent, MessageEventContent, NoticeMessageEventContent,
TextMessageEventContent,
},
},
AnyMessageEventContent, StrippedStateEvent, SyncMessageEvent,
},
identifiers::RoomId,
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: &SyncMessageEvent<MessageEventContent>) {
match room {
SyncRoom::Joined(room) => {
let msg_body = if let SyncMessageEvent {
content:
MessageEventContent::Text(TextMessageEventContent { body: msg_body, .. }),
..
} = event
{
msg_body.clone()
} else {
String::new()
};
if let Ok(command) = Command::from_str(&msg_body) {
let room_id = room.read().await.room_id.clone();
handle_command(
command,
&self.client,
&self.hosts,
event,
&room_id,
)
.await;
} else {
//TODO: give help text
}
}
_ => {}
}
}
}
async fn handle_command(
command: Command,
client: &Client,
hosts: &HashMap<String, Host>,
event: &SyncMessageEvent<MessageEventContent>,
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,
AnyMessageEventContent::RoomMessage(MessageEventContent::Notice(
NoticeMessageEventContent {
body: message.to_owned(),
formatted: 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");
}
}
}