From 2337d7b59259b43638c64db845c5680aa37c23cb Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Thu, 19 Mar 2026 17:44:51 -0700 Subject: [PATCH] Remove Apprise duplicate names. Closes #88. --- app/fanout/AGENTS_fanout.md | 2 ++ app/fanout/apprise_mod.py | 4 ++-- app/fanout/base.py | 27 ++++++++++++++++++++++++ tests/test_fanout.py | 42 +++++++++++++++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 2 deletions(-) diff --git a/app/fanout/AGENTS_fanout.md b/app/fanout/AGENTS_fanout.md index 3c5d1b3..30a4069 100644 --- a/app/fanout/AGENTS_fanout.md +++ b/app/fanout/AGENTS_fanout.md @@ -65,6 +65,7 @@ Wraps bot code execution via `app/fanout/bot_exec.py`. Config blob: - `code` — Python bot function source code - Executes in a thread pool with timeout and semaphore concurrency control - Rate-limits outgoing messages for repeater compatibility +- Channel `message_text` passed to bot code is normalized for human readability by stripping a leading `"{sender_name}: "` prefix when it matches the payload sender. ### webhook (webhook.py) HTTP webhook delivery. Config blob: @@ -78,6 +79,7 @@ Push notifications via Apprise library. Config blob: - `urls` — newline-separated Apprise notification service URLs - `preserve_identity` — suppress Discord webhook name/avatar override - `include_path` — include routing path in notification body +- Channel notifications normalize stored message text by stripping a leading `"{sender_name}: "` prefix when it matches the payload sender so alerts do not duplicate the name. ### sqs (sqs.py) Amazon SQS delivery. Config blob: diff --git a/app/fanout/apprise_mod.py b/app/fanout/apprise_mod.py index 1453d85..c463aee 100644 --- a/app/fanout/apprise_mod.py +++ b/app/fanout/apprise_mod.py @@ -6,7 +6,7 @@ import asyncio import logging from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit -from app.fanout.base import FanoutModule +from app.fanout.base import FanoutModule, get_fanout_message_text from app.path_utils import split_path_hex logger = logging.getLogger(__name__) @@ -39,7 +39,7 @@ def _normalize_discord_url(url: str) -> str: def _format_body(data: dict, *, include_path: bool) -> str: """Build a human-readable notification body from message data.""" msg_type = data.get("type", "") - text = data.get("text", "") + text = get_fanout_message_text(data) sender_name = data.get("sender_name") or "Unknown" via = "" diff --git a/app/fanout/base.py b/app/fanout/base.py index f0af94c..3ad269f 100644 --- a/app/fanout/base.py +++ b/app/fanout/base.py @@ -33,3 +33,30 @@ class FanoutModule: def status(self) -> str: """Return 'connected', 'disconnected', or 'error'.""" raise NotImplementedError + + +def get_fanout_message_text(data: dict) -> str: + """Return the best human-readable message body for fanout consumers. + + Channel messages are stored with the rendered sender label embedded in the + text (for example ``"Alice: hello"``). Human-facing integrations that also + carry ``sender_name`` should strip that duplicated prefix when it matches + the payload sender exactly. + """ + + text = data.get("text", "") + if not isinstance(text, str): + return "" + + if data.get("type") != "CHAN": + return text + + sender_name = data.get("sender_name") + if not isinstance(sender_name, str) or not sender_name: + return text + + prefix, separator, remainder = text.partition(": ") + if separator and prefix == sender_name: + return remainder + + return text diff --git a/tests/test_fanout.py b/tests/test_fanout.py index 12a165c..ad656d7 100644 --- a/tests/test_fanout.py +++ b/tests/test_fanout.py @@ -781,6 +781,20 @@ class TestAppriseFormatBody: ) assert body == "**#general:** Bob: hi" + def test_channel_format_strips_stored_sender_prefix(self): + from app.fanout.apprise_mod import _format_body + + body = _format_body( + { + "type": "CHAN", + "text": "Bob: hi", + "sender_name": "Bob", + "channel_name": "#general", + }, + include_path=False, + ) + assert body == "**#general:** Bob: hi" + def test_dm_with_path(self): from app.fanout.apprise_mod import _format_body @@ -888,6 +902,34 @@ class TestAppriseNormalizeDiscordUrl: result = _normalize_discord_url("https://discord.com/api/webhooks/123/abc") assert "avatar=no" in result + +class TestFanoutMessageText: + def test_channel_text_strips_matching_sender_prefix(self): + from app.fanout.base import get_fanout_message_text + + text = get_fanout_message_text( + { + "type": "CHAN", + "text": "Alice: hello world", + "sender_name": "Alice", + } + ) + + assert text == "hello world" + + def test_channel_text_keeps_nonmatching_prefix(self): + from app.fanout.base import get_fanout_message_text + + text = get_fanout_message_text( + { + "type": "CHAN", + "text": "Alice: hello world", + "sender_name": "Bob", + } + ) + + assert text == "Alice: hello world" + def test_non_discord_unchanged(self): from app.fanout.apprise_mod import _normalize_discord_url