From 39b51da9562fa4976055ba4a451367d7377f1ff4 Mon Sep 17 00:00:00 2001 From: Slavasil Date: Fri, 20 Mar 2026 23:12:52 +0300 Subject: [PATCH] implement /wol --- src/handler.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 3 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index bead3e2..3261d3c 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,12 +1,13 @@ use std::{collections::HashSet, sync::Arc}; -use anyhow::Result as AnyhowResult; +use anyhow::{Context as _, Result as AnyhowResult}; use deltachat::{ chat::{self, ChatId}, contact::ContactId, context::Context, message::Message, }; +use eui48::MacAddress; use tokio::sync::Mutex; use crate::config::BotConfig; @@ -90,7 +91,83 @@ pub async fn wol_command( return Ok(()); } - // TODO + let mac = match args.first() { + None => { + match ctx_lock.config.machines.values().find(|m| m.is_default) { + Some(machine) => machine.mac, + None => { + let usage = wol_usage(&ctx_lock.config); + chat::send_text_msg(&dchat_ctx_lock, chat_id, usage).await?; + return Ok(()); + } + } + } + Some(arg) => { + if let Some(machine) = ctx_lock.config.machines.get(*arg) { + machine.mac + } else if let Ok(mac) = MacAddress::parse_str(arg) { + mac + } else { + let usage = wol_usage(&ctx_lock.config); + chat::send_text_msg(&dchat_ctx_lock, chat_id, usage).await?; + return Ok(()); + } + } + }; + + let reply = match send_magic_packet(mac).await { + Ok(()) => format!("Magic packet sent to {}.", mac_to_string(mac)), + Err(e) => format!("Failed to send magic packet: {e:#}"), + }; + chat::send_text_msg(&dchat_ctx_lock, chat_id, reply).await?; + + Ok(()) +} + +fn wol_usage(config: &BotConfig) -> String { + let mut forms: Vec = Vec::new(); + + if config.machines.values().any(|m| m.is_default) { + forms.push("/wol".to_owned()); + } + + let mut names: Vec<&str> = config.machines.keys().map(String::as_str).collect(); + names.sort(); + if !names.is_empty() { + forms.push(format!("/wol <{}>", names.join("|"))); + } + + forms.push("/wol ".to_owned()); + + format!("Usage: {}", forms.join(" OR ")) +} + +fn mac_to_string(mac: MacAddress) -> String { + let b = mac.as_bytes(); + format!( + "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + b[0], b[1], b[2], b[3], b[4], b[5] + ) +} + +async fn send_magic_packet(mac: MacAddress) -> AnyhowResult<()> { + let mut packet = [0u8; 102]; + packet[..6].fill(0xFF); + let mac_bytes = mac.as_bytes(); + for i in 0..16 { + packet[6 + i * 6..6 + (i + 1) * 6].copy_from_slice(mac_bytes); + } + + let socket = tokio::net::UdpSocket::bind("0.0.0.0:0") + .await + .context("Failed to bind UDP socket for WoL")?; + socket + .set_broadcast(true) + .context("Failed to enable broadcast on UDP socket")?; + socket + .send_to(&packet, "255.255.255.255:9") + .await + .context("Failed to send magic packet")?; Ok(()) } @@ -111,7 +188,7 @@ pub async fn ssh_unlock_disk_command( return Ok(()); } - // TODO + chat::send_text_msg(&dchat_ctx_lock, chat_id, format!("*ssh unlock disk* {:?}", args)).await?; Ok(()) }