-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparse_export.py
More file actions
135 lines (105 loc) · 4.08 KB
/
Copy pathparse_export.py
File metadata and controls
135 lines (105 loc) · 4.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#!/usr/bin/env python3
"""
STEP 1 — Parse Instagram DM export → conversation.jsonl
"""
from __future__ import annotations
import json
import re
from pathlib import Path
from tqdm import tqdm
from utils import (
ensure_dir,
extract_message_text,
fix_value,
has_media_only,
load_config,
write_jsonl,
)
def discover_message_files(thread_path: Path) -> list[Path]:
files = list(thread_path.glob("message_*.json"))
if not files:
raise FileNotFoundError(f"No message_*.json files found in {thread_path}")
def sort_key(path: Path) -> int:
match = re.search(r"message_(\d+)\.json$", path.name)
return int(match.group(1)) if match else 0
return sorted(files, key=sort_key)
def load_thread_messages(thread_path: Path) -> list[dict]:
all_messages: list[dict] = []
for path in discover_message_files(thread_path):
with open(path, encoding="utf-8") as f:
data = fix_value(json.load(f))
participants = [p.get("name", "") for p in data.get("participants", [])]
for raw in data.get("messages", []):
raw["_source_file"] = path.name
raw["_participants"] = participants
all_messages.append(raw)
all_messages.sort(key=lambda m: m.get("timestamp_ms", 0))
return all_messages
def parse_messages(raw_messages: list[dict], include_shares: bool) -> tuple[list[dict], dict]:
cleaned: list[dict] = []
stats = {
"total_raw": len(raw_messages),
"skipped_system": 0,
"skipped_media_only": 0,
"skipped_empty": 0,
"kept": 0,
}
for raw in raw_messages:
if has_media_only(raw):
stats["skipped_media_only"] += 1
continue
text = extract_message_text(raw, include_shares=include_shares)
if text is None:
if (raw.get("content") or "").strip():
stats["skipped_system"] += 1
else:
stats["skipped_empty"] += 1
continue
sender = fix_value(raw.get("sender_name", "")).strip()
cleaned.append(
{
"sender": sender,
"text": text,
"timestamp": raw.get("timestamp_ms", 0),
}
)
stats["kept"] += 1
return cleaned, stats
def main() -> None:
cfg = load_config()
thread_path = Path(cfg["thread_path"])
output_dir = ensure_dir(cfg["output_dir"])
output_path = output_dir / "conversation.jsonl"
print(f"Loading Instagram export from {thread_path} ...")
raw_messages = load_thread_messages(thread_path)
cleaned, stats = parse_messages(raw_messages, include_shares=cfg.get("include_shares", False))
write_jsonl(output_path, cleaned)
senders: dict[str, int] = {}
for msg in cleaned:
senders[msg["sender"]] = senders.get(msg["sender"], 0) + 1
if cleaned:
ts_start = min(m["timestamp"] for m in cleaned)
ts_end = max(m["timestamp"] for m in cleaned)
from datetime import datetime, timezone
d0 = datetime.fromtimestamp(ts_start / 1000, tz=timezone.utc).strftime("%Y-%m-%d")
d1 = datetime.fromtimestamp(ts_end / 1000, tz=timezone.utc).strftime("%Y-%m-%d")
date_range = f"{d0} to {d1}"
else:
date_range = "n/a"
print(f"\nParsed {stats['kept']:,} messages -> {output_path}")
print(f" Raw messages: {stats['total_raw']:,}")
print(f" Skipped (system): {stats['skipped_system']:,}")
print(f" Skipped (media): {stats['skipped_media_only']:,}")
print(f" Skipped (empty): {stats['skipped_empty']:,}")
print(f" Date range: {date_range}")
print(f" Senders:")
for name, count in sorted(senders.items(), key=lambda x: -x[1]):
print(f" {name}: {count:,}")
target = cfg.get("target_sender", "")
mine = cfg.get("my_sender", "")
if target and target not in senders:
print(f"\nWARNING: target_sender '{target}' not found in parsed messages.")
if mine and mine not in senders:
print(f"WARNING: my_sender '{mine}' not found in parsed messages.")
if __name__ == "__main__":
main()