reap_orphan_reports.py
4.99 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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/data/data/com.termux/files/usr/bin/python3
"""Orphan report reaper — import report_result_*.md files whose callback never fired.
Root cause: Flask restart kills daemon threads → _on_report_done never calls
report_add(). The result file remains in /tmp but never makes it to daily_reports.
This reaper runs via watchdog every 5 minutes.
"""
import os, re, sys, glob
TMP_DIR = "/data/data/com.termux/files/home/tmp"
APP_DIR = "/data/data/com.termux/files/home/wechat-viewer"
sys.path.insert(0, APP_DIR)
from local_db import report_add, report_list
def main():
pattern = os.path.join(TMP_DIR, "report_result_*.md")
result_files = glob.glob(pattern)
if not result_files:
return # nothing to do, silent
# Load existing reports once
existing = report_list()
existing_keys = set()
for r in existing:
existing_keys.add((r.get("chatroom_id", ""), r.get("report_date", "")))
imported = 0
for rf in result_files:
basename = os.path.basename(rf)
# Extract task_id: report_result_<12-char hex>.md
m = re.match(r"report_result_([a-f0-9]{12})\.md", basename)
if not m:
print(f" Skip (bad name): {basename}")
continue
task_id = m.group(1)
task_file = os.path.join(TMP_DIR, f"report_task_{task_id}.json")
if not os.path.exists(task_file):
print(f" Skip (no task file): {basename}")
# Can't determine chatroom_id without task file — leave for manual
continue
# Read task metadata
import json
try:
with open(task_file) as f:
task = json.load(f)
except Exception as e:
print(f" Skip (task file corrupt): {basename} — {e}")
continue
chatroom_id = task.get("chatroom_id", "")
date_str = task.get("date", "")
if not chatroom_id or not date_str:
print(f" Skip (missing fields): {basename}")
continue
# Check if already imported
if (chatroom_id, date_str) in existing_keys:
print(f" Already imported: {basename}")
# Clean up orphan files
try:
os.remove(rf)
os.remove(task_file)
except OSError:
pass
continue
# Read report content
try:
with open(rf, encoding="utf-8", errors="replace") as f:
content = f.read()
except Exception as e:
print(f" Skip (unreadable): {basename} — {e}")
continue
if len(content.strip()) < 100:
print(f" Skip (too short): {basename} ({len(content)} bytes)")
continue
# Resolve chatroom name
chatroom_name = resolve_chatroom_name(chatroom_id)
# Parse counts from report content
msg_count = 0
image_count = 0
article_count = 0
mc = re.search(r'消息总数[::]?\s*(\d+)', content)
ic = re.search(r'图片\s*(\d+)', content)
ac = re.search(r'文章\s*(\d+)', content)
if mc:
msg_count = int(mc.group(1))
if ic:
image_count = int(ic.group(1))
if ac:
article_count = int(ac.group(1))
# Import into daily_reports
try:
rid = report_add(chatroom_id, chatroom_name, date_str, content,
msg_count, image_count, article_count)
print(f" Imported: {chatroom_name} ({date_str}) → id={rid}")
imported += 1
except Exception as e:
print(f" Import failed: {basename} — {e}")
continue
# Clean up on success
try:
os.remove(rf)
os.remove(task_file)
except OSError:
pass
# Update cache
existing_keys.add((chatroom_id, date_str))
gate_trace = (
f"[node=reaper,attempt=1,gate=ORPHAN_REPORTS,"
f"action={'CONTINUE' if imported > 0 else 'PASS'}] "
f"scanned {len(result_files)} files, imported {imported}"
)
if imported:
print(f"Done: imported {imported} orphan reports.")
print(gate_trace)
def resolve_chatroom_name(chatroom_id: str) -> str:
"""Resolve chatroom_id to display name, fallback to chatroom_id."""
try:
# Try reading from rcontact via WeChat DB
import subprocess
result = subprocess.run(
["su", "-c",
f"sqlcipher /data/data/com.tencent.mm/MicroMsg/*/EnMicroMsg.db "
f"\"PRAGMA key='x'; SELECT nickname FROM rcontact WHERE username='{chatroom_id}';\" "
f"2>/dev/null"],
capture_output=True, text=True, timeout=10)
# sqlcipher output format: ok\n<NICKNAME>
output = result.stdout.strip()
if output and output != "ok":
lines = output.split("\n")
if len(lines) > 1 and lines[1].strip():
return lines[1].strip()
except Exception:
pass
return chatroom_id
if __name__ == "__main__":
main()