Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2.5 KiB
Signal envelope shapes
Reference for the two JSON frames signal-cli-rest-api pushes down
ws://…/v1/receive/{number}. signal_bot/models.py flattens both into one
IncomingMessage; these are the shapes it flattens.
From another account: dataMessage
{
"envelope": {
"source": "NAME",
"sourceNumber": "PHONE_NUMBER",
"sourceUuid": "SOURCE_ID",
"sourceName": "USER_DEFINED_NAME",
"sourceDevice": 69,
"timestamp": 1719177917717,
"dataMessage": {
"timestamp": 1719177917717,
"message": "MESSAGE_CONTENT",
"expiresInSeconds": 0,
"viewOnce": false,
"sticker": { "packId": "166dd1b5…", "stickerId": 6 },
"groupInfo": { "groupId": "ID_OF_GROUP", "type": "DELIVER" }
}
},
"account": "BOT_ACCOUNT_PHONE_NUMBER"
}
From the bot's own account: syncMessage.sentMessage
Because the bot runs as a linked device, the account's own outgoing messages come back as sync messages. Same fields, one level deeper.
{
"envelope": {
"sourceUuid": "SOURCE_ID",
"sourceName": "USER_DEFINED_NAME",
"timestamp": 1719177728639,
"syncMessage": {
"sentMessage": {
"destination": "DEST_NAME",
"destinationNumber": "DEST_PHONE_NUMBER",
"timestamp": 1719177728639,
"message": "MESSAGE_CONTENT",
"sticker": { "packId": "166dd1b5…", "stickerId": 6 },
"groupInfo": { "groupId": "ID_OF_GROUP", "type": "DELIVER" }
}
}
},
"account": "BOT_ACCOUNT_PHONE_NUMBER"
}
The trap this caused
The original bot read envelope.dataMessage.sticker even when the frame was a
syncMessage. Its own stickers therefore never matched the sticker filter and
were counted as messages. Four consecutive commits (f1931b8, b325126,
62352e5, d35aa59) fought the symptom.
models.py resolves the payload first — dataMessage or
syncMessage.sentMessage — and reads sticker, reaction, message and
groupInfo off whichever it found. tests/test_models.py asserts both shapes.
No groupInfo means a direct message
A frame without groupInfo arrived in a one-to-one conversation. A linked
device receives every one of those on the account, so counting.watched_group
rejects them explicitly rather than relying on a falsy comparison. See the
scoping scenario in fake_signal/scenarios/.
Frames the bot ignores
Receipts, typing indicators and anything else with no dataMessage and no
syncMessage.sentMessage parse to None and are dropped.