From 1ff2586a276fc8bb5be5115c9e88641025749da2 Mon Sep 17 00:00:00 2001 From: Chris Beck Date: Fri, 5 Dec 2025 04:19:51 -0700 Subject: [PATCH] group-aware messaging * reply in group if admin command from group * allow alerts to be sent to a group instead of individual admins --- signal-gateway/src/gateway/mod.rs | 31 ++++++++++++++++++++++++++----- signal-gateway/src/jsonrpc.rs | 25 ++++++++++++++++++++++--- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/signal-gateway/src/gateway/mod.rs b/signal-gateway/src/gateway/mod.rs index 006bd93..b5c3456 100644 --- a/signal-gateway/src/gateway/mod.rs +++ b/signal-gateway/src/gateway/mod.rs @@ -1,6 +1,6 @@ use crate::{ alertmanager::AlertPost, - jsonrpc::{Envelope, RpcClient, RpcClientError, SignalMessage, connect_tcp}, + jsonrpc::{Envelope, MessageTarget, RpcClient, RpcClientError, SignalMessage, connect_tcp}, log_message::{LogMessage, Origin}, message_handler::{AdminMessageResponse, MessageHandler, MessageHandlerResult}, prometheus::{Prometheus, PrometheusConfig}, @@ -52,6 +52,9 @@ pub struct GatewayConfig { pub signal_account: String, #[conf(repeat, long, env)] pub admin_uuid: Vec, + /// If set, alerts are sent to this group instead of individual admins + #[conf(long, env)] + pub alert_group_id: Option, #[conf(flatten)] pub prometheus: Option, #[conf(flatten)] @@ -256,9 +259,15 @@ impl Gateway { msg.text }; let attachments = msg.attachment_paths.into_iter().map(|p| p.to_str().unwrap().to_owned()).collect(); + // Send to group if configured, otherwise to individual admins + let target = if let Some(group_id) = &self.config.alert_group_id { + MessageTarget::Group(group_id.clone()) + } else { + MessageTarget::Recipients(self.config.admin_uuid.clone()) + }; SignalMessage { sender: self.config.signal_account.clone(), - recipient: self.config.admin_uuid.clone(), + target, message, attachments, }.send(signal_cli).await?; @@ -280,11 +289,16 @@ impl Gateway { } Some(Ok(msg)) => { //info!("Signal Rx: {msg:?}"); - if msg.envelope.data_message.is_none() { + let Some(data_message) = &msg.envelope.data_message else { debug!("Ignoring message which was not a data message: {msg:?}"); continue; - } + }; + // Determine if this message came from a group + let from_group = data_message.group_info.as_ref().map(|g| g.group_id.clone()); + + // For group messages, check if sender is an admin + // For direct messages, check if sender is an admin if !self.config.admin_uuid.contains(&msg.envelope.source_uuid) { warn!("Ignoring message from non-admin: {msg:?}"); continue; @@ -307,9 +321,16 @@ impl Gateway { let attachments = resp.attachments.into_iter().map(|p| p.to_str().expect("attachments must have utf8 paths").to_owned()).collect(); + // Reply to group if message came from a group, otherwise reply to sender + let target = if let Some(group_id) = from_group { + MessageTarget::Group(group_id) + } else { + MessageTarget::Recipients(vec![msg.envelope.source_uuid.clone()]) + }; + SignalMessage { sender: self.config.signal_account.clone(), - recipient: vec![msg.envelope.source_uuid.clone()], + target, message: resp.text, attachments, }.send(signal_cli).await?; diff --git a/signal-gateway/src/jsonrpc.rs b/signal-gateway/src/jsonrpc.rs index d6172f2..09393aa 100644 --- a/signal-gateway/src/jsonrpc.rs +++ b/signal-gateway/src/jsonrpc.rs @@ -430,6 +430,14 @@ pub struct Envelope { pub struct DataMessage { pub timestamp: u64, pub message: String, + #[serde(default)] + pub group_info: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GroupInfo { + pub group_id: String, } /// Connect to signal-cli over tcp socket @@ -472,10 +480,19 @@ impl Envelope { } } +/// Target for a SignalMessage - either individual recipients or a group +#[derive(Clone, Debug)] +pub enum MessageTarget { + /// Send to individual recipients + Recipients(Vec), + /// Send to a group + Group(String), +} + /// Helper for invoking send, which has way too many parameters pub struct SignalMessage { pub sender: String, - pub recipient: Vec, + pub target: MessageTarget, pub message: String, pub attachments: Vec, } @@ -512,8 +529,10 @@ impl SignalMessage { editTimestamp: Option, */ let account = Some(self.sender); - let recipients = self.recipient; - let groupIds = vec![]; + let (recipients, groupIds) = match self.target { + MessageTarget::Recipients(r) => (r, vec![]), + MessageTarget::Group(g) => (vec![], vec![g]), + }; let noteToSelf = false; let endSession = false; let message = self.message;