Add first and last interaction timeline events for WhatsApp chats

Extract one record per ZWACHATSESSION with the first and last stored
message dates, the session's own last-message date, the group creation
date and message counts. Each chat produces chat_first_message and
chat_last_message timeline events, and groups a group_created event.
The session last-message date is preferred over the newest stored
message because it survives message deletion.
This commit is contained in:
Donncha Ó Cearbhaill
2026-08-19 12:02:14 +02:00
parent 6b0c439d1c
commit 07066feba9
5 changed files with 187 additions and 7 deletions
+54
View File
@@ -6,8 +6,62 @@
import logging
from mvt.common.indicators import Indicators
from mvt.common.module import run_module
from mvt.ios.modules.mixed.whatsapp import Whatsapp
from ..utils import get_ios_backup_folder
def test_extraction():
m = Whatsapp(target_path=get_ios_backup_folder())
run_module(m)
messages = [r for r in m.results if "ZTEXT" in r]
sessions = [r for r in m.results if r.get("record_type") == "chat_session"]
assert len(messages) == 3
assert len(sessions) == 2
linked = next(r for r in messages if r.get("links"))
assert linked["links"] == ["https://example.org/news"]
alice = next(s for s in sessions if s["partner_name"] == "Alice Example")
assert alice["contact_jid"] == "14155550100@s.whatsapp.net"
assert alice["first_stored_message_date"] == "2025-08-27 15:06:40.000000"
assert alice["last_message_date"] == "2025-08-28 18:53:20.000000"
assert alice["group_creation_date"] is None
assert alice["stored_message_count"] == 2
group = next(s for s in sessions if s["partner_name"] == "Example Group")
assert group["group_creation_date"] == "2025-08-21 20:13:20.000000"
assert group["first_stored_message_date"] == "2025-08-29 22:40:00.000000"
# The last stored message predates the session's own last-message date:
# the newest message in this chat was deleted.
assert group["last_stored_message_date"] == "2025-08-29 22:40:00.000000"
assert group["last_message_date"] == "2025-08-31 02:26:40.000000"
# 3 message events plus first/last per chat and the group creation.
assert len(m.timeline) == 8
events = {
(entry["event"], entry["timestamp"]): entry["data"]
for entry in m.timeline
}
assert events[("chat_first_message", "2025-08-27 15:06:40.000000")] == (
"First stored message in WhatsApp chat with "
"'Alice Example' (14155550100@s.whatsapp.net)"
)
assert events[("chat_last_message", "2025-08-28 18:53:20.000000")] == (
"Last message in WhatsApp chat with "
"'Alice Example' (14155550100@s.whatsapp.net)"
)
assert events[("group_created", "2025-08-21 20:13:20.000000")] == (
"WhatsApp group chat 'Example Group' "
"(120000000000000001@g.us) was created"
)
assert ("chat_first_message", "2025-08-29 22:40:00.000000") in events
assert ("chat_last_message", "2025-08-31 02:26:40.000000") in events
assert len(m.alertstore.alerts) == 0
def test_collect_url_results_includes_expansion():
module = Whatsapp(
+4 -4
View File
@@ -15,8 +15,8 @@ class TestFilesystem:
def test_filesystem(self):
m = Filesystem(target_path=get_ios_backup_folder())
run_module(m)
assert len(m.results) == 19
assert len(m.timeline) == 19
assert len(m.results) == 21
assert len(m.timeline) == 21
assert len(m.alertstore.alerts) == 0
def test_detection(self, indicator_file):
@@ -29,6 +29,6 @@ class TestFilesystem:
)
m.indicators = ind
run_module(m)
assert len(m.results) == 19
assert len(m.timeline) == 19
assert len(m.results) == 21
assert len(m.timeline) == 21
assert len(m.alertstore.alerts) == 1