feat(signal): add require_mention filter for group chats

Add a configurable mention filter to the Signal adapter so the bot
only responds in groups when it is explicitly @mentioned.

Changes:
- gateway/platforms/signal.py: read require_mention from adapter
  extra config or SIGNAL_REQUIRE_MENTION env var; skip group messages
  that don't mention the bot account (checked in rendered text and
  raw mention metadata)
- gateway/config.py: map signal.require_mention YAML key to the
  SIGNAL_REQUIRE_MENTION env var (env var takes precedence)

Config example:
  signal:
    require_mention: true

Or via env var:
  SIGNAL_REQUIRE_MENTION=true
This commit is contained in:
jdelmerico 2026-05-18 23:58:59 -07:00 committed by Teknium
parent 6dd0b357c4
commit 7f40767393
2 changed files with 31 additions and 0 deletions

View file

@ -1118,6 +1118,12 @@ def load_gateway_config() -> GatewayConfig:
gaf = ",".join(str(v) for v in gaf)
os.environ["WHATSAPP_GROUP_ALLOWED_USERS"] = str(gaf)
# Signal settings → env vars (env vars take precedence)
signal_cfg = yaml_cfg.get("signal", {})
if isinstance(signal_cfg, dict):
if "require_mention" in signal_cfg and not os.getenv("SIGNAL_REQUIRE_MENTION"):
os.environ["SIGNAL_REQUIRE_MENTION"] = str(signal_cfg["require_mention"]).lower()
# DingTalk settings → env vars (env vars take precedence)
dingtalk_cfg = yaml_cfg.get("dingtalk", {})
if isinstance(dingtalk_cfg, dict):

View file

@ -192,6 +192,14 @@ class SignalAdapter(BasePlatformAdapter):
group_allowed_str = os.getenv("SIGNAL_GROUP_ALLOWED_USERS", "")
self.group_allow_from = set(_parse_comma_list(group_allowed_str))
# Mention filter — only respond in groups when the bot account is @mentioned.
# Read from config extra first, then SIGNAL_REQUIRE_MENTION env var.
_rm_cfg = extra.get("require_mention")
if _rm_cfg is not None:
self.require_mention = bool(_rm_cfg)
else:
self.require_mention = os.getenv("SIGNAL_REQUIRE_MENTION", "false").lower() in ("true", "1", "yes", "on")
# DM allowlist — mirrors SIGNAL_ALLOWED_USERS checked by run.py.
# Stored here so the reaction hooks can skip unauthorized senders
# (reactions fire before run.py's auth gate, so without this check
@ -528,6 +536,23 @@ class SignalAdapter(BasePlatformAdapter):
if text and mentions:
text = _render_mentions(text, mentions)
# Mention filter: in groups, only process messages that @mention the bot account
if is_group and self.require_mention:
account_norm = self._account_normalized
# Check rendered mention tags OR raw mention metadata
mentioned_in_text = account_norm and (
f"@{account_norm}" in (text or "")
)
mentioned_in_metadata = any(
m.get("number") == account_norm or m.get("uuid") == account_norm
for m in (data_message.get("mentions") or [])
)
if not mentioned_in_text and not mentioned_in_metadata:
logger.debug(
"Signal: ignoring group message (require_mention=true, bot not mentioned)"
)
return
# Extract quote (reply-to) context from Signal dataMessage
quote_data = data_message.get("quote") or {}
reply_to_id = str(quote_data.get("id")) if quote_data.get("id") else None