fix: improve pager message display and encryption classification

Three issues caused POCSAG messages to be incorrectly hidden or
misclassified in the Device Intelligence panel:

1. detectEncryption used a narrow character class ([a-zA-Z0-9\s.,!?-])
   to measure "printable ratio". Messages containing common printable
   ASCII characters like : = / + @ fell below the 0.8 threshold and
   returned null ("Unknown") instead of false ("Plaintext"). Simplified
   to check all printable ASCII (\x20-\x7E) which correctly classifies
   base64, structured data, and punctuation-heavy content.

2. The default hideToneOnly filter was true, hiding all address-only
   (Tone) pager messages. When RF conditions cause multimon-ng to decode
   the address but not the message content, the resulting Tone card was
   silently filtered. Changed default to false so users see all traffic
   and can opt-in to filtering.

3. The multimon-ng output parser only recognized "Alpha" and "Numeric"
   content type labels. Added a catch-all pattern to capture any
   additional content type labels that future multimon-ng versions or
   forks might emit, rather than dropping them to raw output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
ribs
2026-02-28 14:11:04 -08:00
parent 325dafacbc
commit 3de5e68e68
2 changed files with 26 additions and 11 deletions

View File

@@ -55,6 +55,20 @@ def parse_multimon_output(line: str) -> dict[str, str] | None:
'message': pocsag_match.group(5).strip() or '[No Message]' 'message': pocsag_match.group(5).strip() or '[No Message]'
} }
# POCSAG parsing - other content types (catch-all for non-Alpha/Numeric labels)
pocsag_other_match = re.match(
r'(POCSAG\d+):\s*Address:\s*(\d+)\s+Function:\s*(\d+)\s+(\w+):\s*(.*)',
line
)
if pocsag_other_match:
return {
'protocol': pocsag_other_match.group(1),
'address': pocsag_other_match.group(2),
'function': pocsag_other_match.group(3),
'msg_type': pocsag_other_match.group(4),
'message': pocsag_other_match.group(5).strip() or '[No Message]'
}
# POCSAG parsing - address only (no message content) # POCSAG parsing - address only (no message content)
pocsag_addr_match = re.match( pocsag_addr_match = re.match(
r'(POCSAG\d+):\s*Address:\s*(\d+)\s+Function:\s*(\d+)\s*$', r'(POCSAG\d+):\s*Address:\s*(\d+)\s+Function:\s*(\d+)\s*$',

View File

@@ -3611,7 +3611,7 @@
// Pager message filter settings // Pager message filter settings
let pagerFilters = { let pagerFilters = {
hideToneOnly: true, hideToneOnly: false,
keywords: [] keywords: []
}; };
@@ -6911,20 +6911,21 @@
return null; // Can't determine return null; // Can't determine
} }
// Check for high entropy (random-looking data) // Check for non-printable characters (outside printable ASCII range)
const printableRatio = (message.match(/[a-zA-Z0-9\s.,!?-]/g) || []).length / message.length;
// Check for common encrypted patterns (hex strings, base64-like)
const hexPattern = /^[0-9A-Fa-f\s]+$/;
const hasNonPrintable = /[^\x20-\x7E]/.test(message); const hasNonPrintable = /[^\x20-\x7E]/.test(message);
if (printableRatio > 0.8 && !hasNonPrintable) { // Check for common encrypted patterns (hex strings)
return false; // Likely plaintext const hexPattern = /^[0-9A-Fa-f\s]+$/;
} else if (hexPattern.test(message.replace(/\s/g, '')) || hasNonPrintable) {
return true; // Likely encrypted or encoded if (hasNonPrintable) {
return true; // Contains non-printable chars — likely encrypted or encoded
}
if (hexPattern.test(message.replace(/\s/g, ''))) {
return true; // Pure hex data — likely encoded
} }
return null; // Unknown // All printable ASCII (covers base64, structured data, punctuation, etc.)
return false; // Likely plaintext
} }
// Generate device fingerprint // Generate device fingerprint