From 03f4963966cdcc439605e24b845347487bcd9ec3 Mon Sep 17 00:00:00 2001 From: Jack Kingsman Date: Wed, 4 Mar 2026 20:15:44 -0800 Subject: [PATCH] Guard flood scope and be better about blocking --- app/models.py | 1 + app/packet_processor.py | 2 ++ app/radio.py | 12 +++++++++--- app/repository/messages.py | 1 + frontend/src/App.tsx | 12 ++++++++++-- frontend/src/test/integration.test.ts | 6 ++++++ frontend/src/test/messageCache.test.ts | 1 + frontend/src/test/searchView.test.tsx | 1 + .../src/test/useConversationMessages.race.test.ts | 1 + frontend/src/test/useConversationMessages.test.ts | 1 + frontend/src/types.ts | 1 + tests/test_event_handlers.py | 1 + 12 files changed, 35 insertions(+), 5 deletions(-) diff --git a/app/models.py b/app/models.py index b365909..f2e1755 100644 --- a/app/models.py +++ b/app/models.py @@ -192,6 +192,7 @@ class Message(BaseModel): ) txt_type: int = 0 signature: str | None = None + sender_key: str | None = None outgoing: bool = False acked: int = 0 sender_name: str | None = None diff --git a/app/packet_processor.py b/app/packet_processor.py index eb092ec..5b5a4c8 100644 --- a/app/packet_processor.py +++ b/app/packet_processor.py @@ -206,6 +206,8 @@ async def create_message_from_decrypted( sender_timestamp=timestamp, received_at=received, paths=paths, + sender_name=sender, + sender_key=resolved_sender_key, ).model_dump(), ) diff --git a/app/radio.py b/app/radio.py index 4f0100a..078dd1e 100644 --- a/app/radio.py +++ b/app/radio.py @@ -258,13 +258,19 @@ class RadioManager: # Sync radio clock with system time await sync_radio_time(mc) - # Apply flood scope from settings + # Apply flood scope from settings (best-effort; older firmware + # may not support set_flood_scope) from app.repository import AppSettingsRepository app_settings = await AppSettingsRepository.get() scope = app_settings.flood_scope - await mc.commands.set_flood_scope(scope if scope else "") - logger.info("Applied flood_scope=%r", scope or "(disabled)") + try: + await mc.commands.set_flood_scope(scope if scope else "") + logger.info("Applied flood_scope=%r", scope or "(disabled)") + except Exception as exc: + logger.warning( + "set_flood_scope failed (firmware may not support it): %s", exc + ) # Sync contacts/channels from radio to DB and clear radio logger.info("Syncing and offloading radio data...") diff --git a/app/repository/messages.py b/app/repository/messages.py index 28a7003..31364ea 100644 --- a/app/repository/messages.py +++ b/app/repository/messages.py @@ -154,6 +154,7 @@ class MessageRepository: paths=MessageRepository._parse_paths(row["paths"]), txt_type=row["txt_type"], signature=row["signature"], + sender_key=row["sender_key"], outgoing=bool(row["outgoing"]), acked=row["acked"], sender_name=row["sender_name"], diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d514084..4d192fe 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -270,14 +270,22 @@ export function App() { if (!msg.outgoing) { const bKeys = blockedKeysRef.current; const bNames = blockedNamesRef.current; - // Block DMs by key + // Block DMs by sender key if ( bKeys.length > 0 && msg.type === 'PRIV' && bKeys.includes(msg.conversation_key.toLowerCase()) ) return; - // Block by sender name (works for channel messages) + // Block channel messages by sender key + if ( + bKeys.length > 0 && + msg.type === 'CHAN' && + msg.sender_key && + bKeys.includes(msg.sender_key.toLowerCase()) + ) + return; + // Block by sender name (works for both DMs and channel messages) if (bNames.length > 0 && msg.sender_name && bNames.includes(msg.sender_name)) return; } diff --git a/frontend/src/test/integration.test.ts b/frontend/src/test/integration.test.ts index 032d316..d537595 100644 --- a/frontend/src/test/integration.test.ts +++ b/frontend/src/test/integration.test.ts @@ -193,6 +193,7 @@ describe('Integration: No phantom unreads from mesh echoes (hitlist #8 regressio paths: null, txt_type: 0, signature: null, + sender_key: null, outgoing: false, acked: 0, sender_name: null, @@ -214,6 +215,7 @@ describe('Integration: No phantom unreads from mesh echoes (hitlist #8 regressio paths: null, txt_type: 0, signature: null, + sender_key: null, outgoing: false, acked: 0, sender_name: null, @@ -354,6 +356,7 @@ describe('Integration: ACK + messageCache propagation', () => { paths: null, txt_type: 0, signature: null, + sender_key: null, outgoing: true, acked: 0, sender_name: null, @@ -378,6 +381,7 @@ describe('Integration: ACK + messageCache propagation', () => { paths: [{ path: 'aa', received_at: 1700000001 }], txt_type: 0, signature: null, + sender_key: null, outgoing: true, acked: 1, sender_name: null, @@ -406,6 +410,7 @@ describe('Integration: ACK + messageCache propagation', () => { paths: null, txt_type: 0, signature: null, + sender_key: null, outgoing: true, acked: 5, sender_name: null, @@ -430,6 +435,7 @@ describe('Integration: ACK + messageCache propagation', () => { paths: null, txt_type: 0, signature: null, + sender_key: null, outgoing: true, acked: 0, sender_name: null, diff --git a/frontend/src/test/messageCache.test.ts b/frontend/src/test/messageCache.test.ts index 2f3da25..4978f85 100644 --- a/frontend/src/test/messageCache.test.ts +++ b/frontend/src/test/messageCache.test.ts @@ -18,6 +18,7 @@ function createMessage(overrides: Partial = {}): Message { paths: null, txt_type: 0, signature: null, + sender_key: null, outgoing: false, acked: 0, sender_name: null, diff --git a/frontend/src/test/searchView.test.tsx b/frontend/src/test/searchView.test.tsx index ad5fcb5..d5eb73c 100644 --- a/frontend/src/test/searchView.test.tsx +++ b/frontend/src/test/searchView.test.tsx @@ -24,6 +24,7 @@ function createSearchResult(overrides: Partial = {}): Message { paths: null, txt_type: 0, signature: null, + sender_key: null, outgoing: false, acked: 0, sender_name: 'Alice', diff --git a/frontend/src/test/useConversationMessages.race.test.ts b/frontend/src/test/useConversationMessages.race.test.ts index 88a8953..5afa4a0 100644 --- a/frontend/src/test/useConversationMessages.race.test.ts +++ b/frontend/src/test/useConversationMessages.race.test.ts @@ -35,6 +35,7 @@ function createMessage(overrides: Partial = {}): Message { paths: null, txt_type: 0, signature: null, + sender_key: null, outgoing: true, acked: 0, sender_name: null, diff --git a/frontend/src/test/useConversationMessages.test.ts b/frontend/src/test/useConversationMessages.test.ts index efe85e6..c3e8fbf 100644 --- a/frontend/src/test/useConversationMessages.test.ts +++ b/frontend/src/test/useConversationMessages.test.ts @@ -19,6 +19,7 @@ function createMessage(overrides: Partial = {}): Message { paths: null, txt_type: 0, signature: null, + sender_key: null, outgoing: false, acked: 0, sender_name: null, diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 6260250..d5fcea6 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -151,6 +151,7 @@ export interface Message { paths: MessagePath[] | null; txt_type: number; signature: string | null; + sender_key: string | null; outgoing: boolean; /** ACK count: 0 = not acked, 1+ = number of acks/flood echoes received */ acked: number; diff --git a/tests/test_event_handlers.py b/tests/test_event_handlers.py index 25b5c80..891e20b 100644 --- a/tests/test_event_handlers.py +++ b/tests/test_event_handlers.py @@ -320,6 +320,7 @@ class TestContactMessageCLIFiltering: "paths", "txt_type", "signature", + "sender_key", "outgoing", "acked", "sender_name",