imessage-exporter
imessage-exporter copied to clipboard
Support Mentions
iMessage has a feature where you can mention chat participants:
There is only one column in the database that seems related to mentions: has_unseen_mention
. It is just a boolean column, though, so it contains no data on what or who was mentioned. The binary blob columns also do not seem to have any information out of the ordinary. Thus, I am not sure how to proceed with this feature.
The attributedBody data contains no delimiters in the body text:

There is an additional field called __kIMMentionConfirmedMention
, but it only contains the handle's number/email, not the location of the mention within the message body.
Just a quick thought, but cross-referencing the number/email with the user's contacts and then searching for the contact's name in the message seems to be the only way to find the mention in the message - albeit with quite a lot of effort required to make it work.
See #62, this software will not target features outside of the iMessage database.
Also, you can type contact names without transforming them into mentions; a string search doesn't have enough context to handle that case.
The attributedBody does contain the range of text that should be annotated with the mention. If parsing the attributedBody, you should see a list of message parts, and this will contain a part for the mention as well with a corrseponding range. For example, if the text is:
Hi ReagentX, here's an example for issue 18!
The attributedBody will have three parts inside it:
1st part: Hi
2nd part: ReagentX
3rd part: , here's an example for issue 18!
And these three will have NSRanges. This also works with multi-part messages.
And these three will have NSRanges
Where? I don't see any delimiters in the attributedBody
. I'm also not sure how much of the streamtyped
data I can parse without reaching into NSUnarchiver
, which I am not sure I can even do in Rust.
"attributedBody": [{
"string": "Heya every part of this message that you can still see was sent from macOS Ventura Tanay mandatory mention",
"runs": [{
"range": [0, 4],
"attributes": {
"__kIMBaseWritingDirectionAttributeName": -1,
"__kIMMessagePartAttributeName": 0
}
}, {
"range": [4, 1],
"attributes": {
"__kIMFileTransferGUIDAttributeName": "624F228F-8EE7-4FBD-9AD8-CA87FEDC0D9F",
"__kIMBaseWritingDirectionAttributeName": -1,
"__kIMMessagePartAttributeName": 1
}
}, {
"range": [5, 1],
"attributes": {
"__kIMFileTransferGUIDAttributeName": "29B4410B-10EC-42B4-8AD6-83BF7E94599F",
"__kIMBaseWritingDirectionAttributeName": -1,
"__kIMMessagePartAttributeName": 3
}
}, {
"range": [6, 77],
"attributes": {
"__kIMBaseWritingDirectionAttributeName": -1,
"__kIMMessagePartAttributeName": 4
}
}, {
"range": [83, 1],
"attributes": {
"__kIMFileTransferGUIDAttributeName": "C4D638E6-76C0-4BAE-B637-AD9E96F44489",
"__kIMBaseWritingDirectionAttributeName": -1,
"__kIMMessagePartAttributeName": 5
}
}, {
"range": [84, 5],
"attributes": {
"__kIMBaseWritingDirectionAttributeName": -1,
"__kIMMessagePartAttributeName": 6,
"__kIMMentionConfirmedMention": "<address>"
}
}, {
"range": [89, 18],
"attributes": {
"__kIMBaseWritingDirectionAttributeName": -1,
"__kIMMessagePartAttributeName": 6
}
}]
}],
Here is an example of an actual deserialized attributedBody, notice that the second to last part is the mention and has NSRange(84,5)
I'm part of the bluebubbles team (https://github.com/BlueBubblesApp). Our server uses node and one of our users / contributors made a node package to deserialize these with NSKeyedUnarchiver - https://www.npmjs.com/package/node-typedstream
Works great for attributedBody, messageSummaryInfo, and payloadData
Got it, thanks, this is extremely helpful.
Tracking support for this and a custom attributedBody
parser in Rust in the feat/cs/attributedBody-parse
branch, which will also be required for #273. The existing Python/Node libs were helpful, but not what I wanted, so I ended up writing my own parser.
An attributedBody like:
Gets parsed as:
At long last, we have come full circle: