* fix: parse Claude.ai privacy export with messages key and sender field (#677) The privacy-export branch in _try_claude_ai_json only checked for the "chat_messages" key, missing exports that use "messages" instead. It also only read the "role" field while real privacy exports use "sender". Both gaps caused the file to fall through to plain-text, producing a single giant drawer. Changes: - Accept "messages" alongside "chat_messages" in the conversation-object guard and inner extraction. - Accept "sender" alongside "role" as the author field. - Fall back to a top-level "text" key when content blocks are empty. - Produce one transcript per conversation instead of concatenating all conversations into a single blob. - Extract shared logic into _collect_claude_messages helper. - Add 6 regression tests covering each variant. * style: apply ruff format to normalize.py * fix: guard against null text field in Claude.ai export parsing item.get("text", "").strip() crashes when "text" is explicitly null in the JSON (legal and observed in some exports). Use (item.get("text") or "").strip() and add a regression test. --------- Co-authored-by: Igor Lins e Silva <4753812+igorls@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
e200ce2c8a
commit
a2432a3245
@@ -524,6 +524,119 @@ def test_claude_ai_privacy_export_non_dict_items():
|
||||
assert result is not None
|
||||
|
||||
|
||||
def test_claude_ai_privacy_export_messages_key():
|
||||
"""Privacy export using 'messages' key instead of 'chat_messages'."""
|
||||
data = [
|
||||
{
|
||||
"uuid": "abc-123",
|
||||
"name": "Test convo",
|
||||
"messages": [
|
||||
{"role": "human", "content": "Q1"},
|
||||
{"role": "ai", "content": "A1"},
|
||||
],
|
||||
}
|
||||
]
|
||||
result = _try_claude_ai_json(data)
|
||||
assert result is not None
|
||||
assert "> Q1" in result
|
||||
|
||||
|
||||
def test_claude_ai_privacy_export_sender_field():
|
||||
"""Privacy export using 'sender' instead of 'role'."""
|
||||
data = [
|
||||
{
|
||||
"chat_messages": [
|
||||
{"sender": "human", "content": "Q1"},
|
||||
{"sender": "assistant", "content": "A1"},
|
||||
]
|
||||
}
|
||||
]
|
||||
result = _try_claude_ai_json(data)
|
||||
assert result is not None
|
||||
assert "> Q1" in result
|
||||
|
||||
|
||||
def test_claude_ai_privacy_export_text_fallback():
|
||||
"""Privacy export where content is empty but text field has the message."""
|
||||
data = [
|
||||
{
|
||||
"chat_messages": [
|
||||
{"sender": "human", "text": "Q1", "content": []},
|
||||
{"sender": "assistant", "text": "A1", "content": []},
|
||||
]
|
||||
}
|
||||
]
|
||||
result = _try_claude_ai_json(data)
|
||||
assert result is not None
|
||||
assert "> Q1" in result
|
||||
|
||||
|
||||
def test_claude_ai_privacy_export_null_text():
|
||||
"""Privacy export where text field is explicitly null must not crash."""
|
||||
data = [
|
||||
{
|
||||
"chat_messages": [
|
||||
{"sender": "human", "text": None, "content": "Q1"},
|
||||
{"sender": "assistant", "text": None, "content": "A1"},
|
||||
]
|
||||
}
|
||||
]
|
||||
result = _try_claude_ai_json(data)
|
||||
assert result is not None
|
||||
assert "> Q1" in result
|
||||
|
||||
|
||||
def test_claude_ai_privacy_export_per_conversation():
|
||||
"""Multiple conversations produce separate transcripts."""
|
||||
data = [
|
||||
{
|
||||
"uuid": "convo-1",
|
||||
"chat_messages": [
|
||||
{"role": "human", "content": "Q1"},
|
||||
{"role": "ai", "content": "A1"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"uuid": "convo-2",
|
||||
"chat_messages": [
|
||||
{"role": "human", "content": "Q2"},
|
||||
{"role": "ai", "content": "A2"},
|
||||
],
|
||||
},
|
||||
]
|
||||
result = _try_claude_ai_json(data)
|
||||
assert result is not None
|
||||
assert "> Q1" in result
|
||||
assert "> Q2" in result
|
||||
# each conversation is a separate transcript block
|
||||
parts = result.split("\n\n")
|
||||
q1_parts = [p for p in parts if "> Q1" in p]
|
||||
q2_parts = [p for p in parts if "> Q2" in p]
|
||||
assert len(q1_parts) >= 1
|
||||
assert len(q2_parts) >= 1
|
||||
|
||||
|
||||
def test_claude_ai_privacy_export_skips_empty_conversations():
|
||||
"""Conversations with <2 messages are skipped."""
|
||||
data = [
|
||||
{
|
||||
"chat_messages": [
|
||||
{"role": "human", "content": "lonely message"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"chat_messages": [
|
||||
{"role": "human", "content": "Q1"},
|
||||
{"role": "ai", "content": "A1"},
|
||||
],
|
||||
},
|
||||
]
|
||||
result = _try_claude_ai_json(data)
|
||||
assert result is not None
|
||||
assert "lonely message" not in result
|
||||
assert "> Q1" in result
|
||||
|
||||
|
||||
# ── _try_chatgpt_json ─────────────────────────────────────────────────
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user