wechat-chat-attitude 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- wechat_chat_attitude-0.4.0/PKG-INFO +230 -0
- wechat_chat_attitude-0.4.0/README.md +220 -0
- wechat_chat_attitude-0.4.0/mcp_server.py +8 -0
- wechat_chat_attitude-0.4.0/native/__init__.py +1 -0
- wechat_chat_attitude-0.4.0/native/common/__init__.py +1 -0
- wechat_chat_attitude-0.4.0/native/common/file_permissions.py +19 -0
- wechat_chat_attitude-0.4.0/native/common/wechat_key_matcher.py +133 -0
- wechat_chat_attitude-0.4.0/native/macos/__init__.py +1 -0
- wechat_chat_attitude-0.4.0/native/macos/wechat_key_scanner.c +495 -0
- wechat_chat_attitude-0.4.0/native/windows/__init__.py +1 -0
- wechat_chat_attitude-0.4.0/native/windows/wechat_key_scanner.py +203 -0
- wechat_chat_attitude-0.4.0/pyproject.toml +33 -0
- wechat_chat_attitude-0.4.0/scripts/__init__.py +1 -0
- wechat_chat_attitude-0.4.0/scripts/chat_sampling.py +451 -0
- wechat_chat_attitude-0.4.0/scripts/compare_chat_exports.py +343 -0
- wechat_chat_attitude-0.4.0/scripts/extract_target_chat.py +529 -0
- wechat_chat_attitude-0.4.0/scripts/prepare_chat_evidence.py +491 -0
- wechat_chat_attitude-0.4.0/scripts/validate_review_ledger.py +128 -0
- wechat_chat_attitude-0.4.0/scripts/wechat_chat_pipeline.py +159 -0
- wechat_chat_attitude-0.4.0/setup.cfg +4 -0
- wechat_chat_attitude-0.4.0/tests/test_acquisition.py +398 -0
- wechat_chat_attitude-0.4.0/tests/test_database_resources.py +106 -0
- wechat_chat_attitude-0.4.0/tests/test_decrypt_snapshot.py +156 -0
- wechat_chat_attitude-0.4.0/tests/test_install_mcp.py +163 -0
- wechat_chat_attitude-0.4.0/tests/test_macos_key_scanner.py +126 -0
- wechat_chat_attitude-0.4.0/tests/test_mcp_server.py +403 -0
- wechat_chat_attitude-0.4.0/tests/test_package_layout.py +57 -0
- wechat_chat_attitude-0.4.0/tests/test_release_manifest.py +50 -0
- wechat_chat_attitude-0.4.0/tests/test_windows_key_scanner.py +127 -0
- wechat_chat_attitude-0.4.0/wechat_chat_attitude.egg-info/PKG-INFO +230 -0
- wechat_chat_attitude-0.4.0/wechat_chat_attitude.egg-info/SOURCES.txt +39 -0
- wechat_chat_attitude-0.4.0/wechat_chat_attitude.egg-info/dependency_links.txt +1 -0
- wechat_chat_attitude-0.4.0/wechat_chat_attitude.egg-info/entry_points.txt +2 -0
- wechat_chat_attitude-0.4.0/wechat_chat_attitude.egg-info/requires.txt +3 -0
- wechat_chat_attitude-0.4.0/wechat_chat_attitude.egg-info/top_level.txt +5 -0
- wechat_chat_attitude-0.4.0/wechat_mcp/__init__.py +5 -0
- wechat_chat_attitude-0.4.0/wechat_mcp/acquisition.py +646 -0
- wechat_chat_attitude-0.4.0/wechat_mcp/backend.py +527 -0
- wechat_chat_attitude-0.4.0/wechat_mcp/decrypt_snapshot.py +206 -0
- wechat_chat_attitude-0.4.0/wechat_mcp/server.py +556 -0
- wechat_chat_attitude-0.4.0/windows_key_capture.py +8 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wechat-chat-attitude
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Local WeChat direct-chat evidence preparation MCP server
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: mcp==2.1.1
|
|
8
|
+
Requires-Dist: cryptography<51,>=46
|
|
9
|
+
Requires-Dist: zstandard<1,>=0.23
|
|
10
|
+
|
|
11
|
+
# WeChat Chat Attitude
|
|
12
|
+
|
|
13
|
+
把你和某个微信联系人的单聊整理成可核对的证据,再交给你选择的模型逐条阅读,分析:
|
|
14
|
+
|
|
15
|
+
- 对方的回复方式和态度变化;
|
|
16
|
+
- 谁在主动推进话题、邀约和后续安排;
|
|
17
|
+
- 可从聊天中观察到的沟通风格与行为倾向;
|
|
18
|
+
- 下一句怎样聊得自然、低压力。
|
|
19
|
+
|
|
20
|
+
它不是“关键词统计器”,也不是一个自带模型的聊天机器人。程序负责在本机寻找或导入数据、隔离指定联系人、按聊天总长度抽取近期和历史证据;真正的语义分析由你连接的 MCP 模型完成。
|
|
21
|
+
|
|
22
|
+
## 先说结论:它能不能像其他 MCP 一样自动下载?
|
|
23
|
+
|
|
24
|
+
可以。这个项目同时支持两种连接方式:
|
|
25
|
+
|
|
26
|
+
1. **自动下载包(推荐给已经发布的版本)**:客户端启动 `uvx`,自动下载并运行 `wechat-chat-attitude` 包。
|
|
27
|
+
2. **本地运行(包尚未发布或离线环境)**:安装器在项目目录创建 Python 环境,配置直接指向本地文件。
|
|
28
|
+
|
|
29
|
+
MCP 协议本身不会替客户端修改配置。Codex 可以由安装器自动登记;其他客户端需要把一段 JSON 粘贴进去,或者把配置合并到你指定的 JSON 文件。合并时只更新 `wechat-chat-attitude` 这一项,并先生成备份。
|
|
30
|
+
|
|
31
|
+
## 你需要准备什么
|
|
32
|
+
|
|
33
|
+
- macOS 或 Windows;
|
|
34
|
+
- Python 3.10+(只使用本地运行方式时需要);
|
|
35
|
+
- 支持 MCP 的模型客户端,例如 Codex、Claude Desktop、Cursor、Trae 等;
|
|
36
|
+
- 你自己的微信账号和聊天数据。
|
|
37
|
+
|
|
38
|
+
本机自动获取目前针对 macOS/Windows 微信 4.x 的 `xwechat_files/.../db_storage` 布局。其他版本可以先导出单聊 JSON,或提供稳定的已解密数据库。Linux 等不支持直接捕获微信进程密钥,但仍可使用导入方式。
|
|
39
|
+
|
|
40
|
+
## 最省事的安装方式
|
|
41
|
+
|
|
42
|
+
### Codex
|
|
43
|
+
|
|
44
|
+
下载并解压本项目,在项目目录双击:
|
|
45
|
+
|
|
46
|
+
- macOS:`install.command`;如果系统拦截,右键选择“打开”;
|
|
47
|
+
- Windows:`install.bat`。
|
|
48
|
+
|
|
49
|
+
选择 **Codex**。安装器会创建项目自己的环境,安装依赖,并用 Codex 命令只刷新名为 `wechat-chat-attitude` 的 MCP 配置;其他 MCP 不会被删除。
|
|
50
|
+
|
|
51
|
+
也可以手动运行:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
python3 install_mcp.py --client codex
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Windows:
|
|
58
|
+
|
|
59
|
+
```powershell
|
|
60
|
+
py -3 install_mcp.py --client codex
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
重新打开 Codex 后即可使用。
|
|
64
|
+
|
|
65
|
+
### 其他 MCP 客户端:自动下载模式
|
|
66
|
+
|
|
67
|
+
此模式适用于 `wechat-chat-attitude` 已发布到包源之后。双击安装器选择“自动下载配置”,或运行:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
python3 install_mcp.py --client generic --config-mode package
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
复制输出的 JSON 到客户端的 MCP/工具/开发者设置,然后重启客户端。配置的核心是:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"mcpServers": {
|
|
78
|
+
"wechat-chat-attitude": {
|
|
79
|
+
"command": "uvx",
|
|
80
|
+
"args": [
|
|
81
|
+
"--python", "3.11",
|
|
82
|
+
"--from", "wechat-chat-attitude", "wechat-chat-attitude-mcp"
|
|
83
|
+
],
|
|
84
|
+
"description": "本地微信聊天证据准备与逐条分析 MCP 服务"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
如果你能访问源码仓库,也可以下载 [自动下载配置模板](configs/wechat-chat-attitude.package.json)。拿不到仓库文件时,直接复制上面的 JSON 即可;`uvx` 第一次启动时会取得包,之后使用本机的隔离环境运行。
|
|
91
|
+
|
|
92
|
+
如果客户端提示找不到 `uvx`,先安装 Astral 的 uv,再重启客户端:<https://docs.astral.sh/uv/getting-started/installation/>。配置固定使用 Python 3.11;本机没有时,uv 会按其默认策略取得一个隔离运行时。
|
|
93
|
+
|
|
94
|
+
> 如果包还没有发布到 PyPI,请使用下面的本地模式。发布者完成 [发布步骤](docs/publishing.md) 后,其他用户即可使用上面的 `uvx` 配置,不需要访问这个仓库。
|
|
95
|
+
|
|
96
|
+
### 发布后,别人怎么直接安装
|
|
97
|
+
|
|
98
|
+
发布者只需在 PyPI 配置一次 Trusted Publisher,然后给仓库打一个与 `pyproject.toml` 版本对应的 `v*` 标签;GitHub Actions 会自动测试、构建并发布包。用户只需安装一次 `uv`,粘贴上面的 MCP JSON,重启模型/Agent,首次调用会自动下载 `wechat-chat-attitude`。完整的发布者清单见 [`docs/publishing.md`](docs/publishing.md)。
|
|
99
|
+
|
|
100
|
+
### 其他 MCP 客户端:本地模式
|
|
101
|
+
|
|
102
|
+
在项目目录双击安装器并选择“本地配置”,或运行:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
python3 install_mcp.py --client generic --config-mode local
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
安装器会创建 `.venv` 并输出一段包含绝对路径的配置。把它粘贴到客户端后重启即可。只想查看模板、不安装依赖时:
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
python3 install_mcp.py --client generic --config-mode local --config-only
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 直接生成或修改配置文件
|
|
115
|
+
|
|
116
|
+
如果客户端允许导入 JSON,可以让安装器直接生成文件:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
python3 install_mcp.py \
|
|
120
|
+
--client generic \
|
|
121
|
+
--config-mode package \
|
|
122
|
+
--config-output "$HOME/wechat-chat-attitude.mcp.json"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Windows PowerShell:
|
|
126
|
+
|
|
127
|
+
```powershell
|
|
128
|
+
py -3 install_mcp.py --client generic --config-mode package `
|
|
129
|
+
--config-output "$env:USERPROFILE\wechat-chat-attitude.mcp.json"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
如果你已经有客户端配置文件,可以让安装器合并进去:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
python3 install_mcp.py \
|
|
136
|
+
--client generic \
|
|
137
|
+
--config-mode package \
|
|
138
|
+
--merge-config "/path/to/your/mcp.json"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Windows PowerShell 只需把最后一个参数换成配置文件的绝对路径,例如:
|
|
142
|
+
|
|
143
|
+
```powershell
|
|
144
|
+
py -3 install_mcp.py --client generic --config-mode package `
|
|
145
|
+
--merge-config "$env:APPDATA\SomeMcpClient\config.json"
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
已有文件会先保存成同目录下的 `.bak`(若已存在则使用编号后缀),原来的其他 `mcpServers` 会保留。安装器不会猜测 Claude、Cursor 或其他客户端的配置路径,所以合并时必须由你明确提供目标文件。
|
|
149
|
+
|
|
150
|
+
## 第一次分析
|
|
151
|
+
|
|
152
|
+
完成 MCP 连接后,直接对模型说:
|
|
153
|
+
|
|
154
|
+
> 使用 wechat-chat-attitude,分析我和“阎亦琛 01.1.26”的全部微信聊天,重点分析她对我的态度、回复变化、沟通风格和可观察到的性格倾向,并告诉我接下来怎么自然地聊。
|
|
155
|
+
|
|
156
|
+
模型会按这个顺序工作:
|
|
157
|
+
|
|
158
|
+
1. 发现本机微信账户和进程,只返回选择项,不读取聊天正文;
|
|
159
|
+
2. 说明即将进行的本机授权,等你确认后才捕获所选进程的数据库密钥;
|
|
160
|
+
3. 如果 macOS/Windows 要求权限,只给你一条系统终端命令;管理员密码只输入系统终端,不要发给模型;
|
|
161
|
+
4. 提醒你完全退出微信,建立稳定快照;
|
|
162
|
+
5. 精确匹配一个联系人。重名或多个账户时,模型必须让你选择,不能猜;
|
|
163
|
+
6. 按总聊天长度保留最近完整消息和分层历史样本;
|
|
164
|
+
7. 逐块、逐条读取实际证据,登记读过的消息 ID,再给出结论。
|
|
165
|
+
|
|
166
|
+
“全部聊天”指程序会以全部导出记录计算范围和抽样;默认逐条交给模型阅读的是最近 1000 条加按总长度增长的历史样本,不会把超大聊天未经筛选地塞进上下文。需要更深的历史基线时,可以对模型说“重新准备并使用 deep 模式”。模型必须在报告开头区分 `raw_rows`、`selected_rows`、`manual_reviewed_rows` 和未逐条阅读的行数。
|
|
167
|
+
|
|
168
|
+
## 获取最新记录和刷新分析
|
|
169
|
+
|
|
170
|
+
不要把旧的 `run_id` 直接当成最新数据。要刷新时明确说:
|
|
171
|
+
|
|
172
|
+
> 重新获取我和“阎亦琛 01.1.26”的最新微信记录,不要复用旧 run;完成后告诉我数据截止时间、最新消息 ID、与上次相比新增了哪些内容,再结合近期内容和回复间隔分析我应该怎么聊。
|
|
173
|
+
|
|
174
|
+
模型应重新发现微信、按提示授权并建立新快照;如果微信还在运行,先按提示退出。完成后可以继续追问“她更像礼貌回应还是主动维持关系”“最近回复间隔有没有变化”等。
|
|
175
|
+
|
|
176
|
+
## 如果没有自动获取条件
|
|
177
|
+
|
|
178
|
+
可以导入已经隔离的单聊 JSON。先连接本地模式,然后对模型说:
|
|
179
|
+
|
|
180
|
+
> 使用 wechat-chat-attitude。导入 `/绝对路径/单聊.json`,联系人是“张三”,本人标签是“我”。先说明会写入哪些本机文件,得到允许后再准备证据;按顺序逐条读完所有分块,分析态度、沟通风格和可观察到的性格倾向,并给出自然的聊天建议。
|
|
181
|
+
|
|
182
|
+
单聊 JSON 示例见 [examples/example-chat.json](examples/example-chat.json)。已解密数据库、加密数据库和受保护密钥 JSON 的导入方式见 [本机获取说明](references/local-acquisition.md)。
|
|
183
|
+
|
|
184
|
+
## 隐私和安全
|
|
185
|
+
|
|
186
|
+
- MCP 服务只在本机通过 `stdio` 运行,不开放监听端口;
|
|
187
|
+
- 原始聊天只在明确读取分块时返回,不写入服务日志;
|
|
188
|
+
- 密钥只保存在本机私有目录,工具不会返回密钥;
|
|
189
|
+
- 程序不会修改微信数据库、微信应用或账户文件;
|
|
190
|
+
- 使用云模型时,逐条读取的消息会进入该模型的上下文,仍受模型服务商的数据政策约束;
|
|
191
|
+
- 不要把管理员密码、数据库密钥或密钥 JSON 内容发到聊天中。
|
|
192
|
+
|
|
193
|
+
## 常见问题
|
|
194
|
+
|
|
195
|
+
### “配置成功但客户端找不到 MCP”
|
|
196
|
+
|
|
197
|
+
保存 JSON 后完全退出并重新打开客户端。自动下载模式还要确认 `uvx` 在系统 PATH 中;包尚未发布时请改用本地模式。
|
|
198
|
+
|
|
199
|
+
### “要求输入密码”
|
|
200
|
+
|
|
201
|
+
只在系统的 Terminal/管理员终端输入。模型和 MCP 不需要、也不应该收到这个密码。
|
|
202
|
+
|
|
203
|
+
### “请退出微信”
|
|
204
|
+
|
|
205
|
+
这是为了避免复制正在写入的数据库。退出后确认微信进程已经结束,再让模型重试;不要用正在变化的数据库继续解密。
|
|
206
|
+
|
|
207
|
+
### “找不到联系人或有多个候选”
|
|
208
|
+
|
|
209
|
+
提供精确备注、昵称、微信号或让模型列出候选后选择,不要让模型猜。
|
|
210
|
+
|
|
211
|
+
### “自动获取不支持我的微信版本”
|
|
212
|
+
|
|
213
|
+
使用单聊 JSON 或稳定的已解密数据库导入。程序会明确停止,不会假装成功或全盘猜路径。
|
|
214
|
+
|
|
215
|
+
## 命令行和开发文档
|
|
216
|
+
|
|
217
|
+
不使用 MCP 也可以运行 `scripts/wechat_chat_pipeline.py` 处理 JSON/已解密数据库。抽样、刷新、账本和数据格式说明分别见:
|
|
218
|
+
|
|
219
|
+
- [MCP 使用设计](docs/mcp-first-mvp.md)
|
|
220
|
+
- [本机自动获取设计](docs/foolproof-acquisition-design.md)
|
|
221
|
+
- [分析合同](references/analysis-contract.md)
|
|
222
|
+
- [刷新流程](references/refresh-workflow.md)
|
|
223
|
+
|
|
224
|
+
开发者运行测试:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
PYTHONPATH=. .venv/bin/python -m unittest discover -s tests -v
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
发行包不包含真实聊天、数据库密钥或第三方预编译扫描器。
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# WeChat Chat Attitude
|
|
2
|
+
|
|
3
|
+
把你和某个微信联系人的单聊整理成可核对的证据,再交给你选择的模型逐条阅读,分析:
|
|
4
|
+
|
|
5
|
+
- 对方的回复方式和态度变化;
|
|
6
|
+
- 谁在主动推进话题、邀约和后续安排;
|
|
7
|
+
- 可从聊天中观察到的沟通风格与行为倾向;
|
|
8
|
+
- 下一句怎样聊得自然、低压力。
|
|
9
|
+
|
|
10
|
+
它不是“关键词统计器”,也不是一个自带模型的聊天机器人。程序负责在本机寻找或导入数据、隔离指定联系人、按聊天总长度抽取近期和历史证据;真正的语义分析由你连接的 MCP 模型完成。
|
|
11
|
+
|
|
12
|
+
## 先说结论:它能不能像其他 MCP 一样自动下载?
|
|
13
|
+
|
|
14
|
+
可以。这个项目同时支持两种连接方式:
|
|
15
|
+
|
|
16
|
+
1. **自动下载包(推荐给已经发布的版本)**:客户端启动 `uvx`,自动下载并运行 `wechat-chat-attitude` 包。
|
|
17
|
+
2. **本地运行(包尚未发布或离线环境)**:安装器在项目目录创建 Python 环境,配置直接指向本地文件。
|
|
18
|
+
|
|
19
|
+
MCP 协议本身不会替客户端修改配置。Codex 可以由安装器自动登记;其他客户端需要把一段 JSON 粘贴进去,或者把配置合并到你指定的 JSON 文件。合并时只更新 `wechat-chat-attitude` 这一项,并先生成备份。
|
|
20
|
+
|
|
21
|
+
## 你需要准备什么
|
|
22
|
+
|
|
23
|
+
- macOS 或 Windows;
|
|
24
|
+
- Python 3.10+(只使用本地运行方式时需要);
|
|
25
|
+
- 支持 MCP 的模型客户端,例如 Codex、Claude Desktop、Cursor、Trae 等;
|
|
26
|
+
- 你自己的微信账号和聊天数据。
|
|
27
|
+
|
|
28
|
+
本机自动获取目前针对 macOS/Windows 微信 4.x 的 `xwechat_files/.../db_storage` 布局。其他版本可以先导出单聊 JSON,或提供稳定的已解密数据库。Linux 等不支持直接捕获微信进程密钥,但仍可使用导入方式。
|
|
29
|
+
|
|
30
|
+
## 最省事的安装方式
|
|
31
|
+
|
|
32
|
+
### Codex
|
|
33
|
+
|
|
34
|
+
下载并解压本项目,在项目目录双击:
|
|
35
|
+
|
|
36
|
+
- macOS:`install.command`;如果系统拦截,右键选择“打开”;
|
|
37
|
+
- Windows:`install.bat`。
|
|
38
|
+
|
|
39
|
+
选择 **Codex**。安装器会创建项目自己的环境,安装依赖,并用 Codex 命令只刷新名为 `wechat-chat-attitude` 的 MCP 配置;其他 MCP 不会被删除。
|
|
40
|
+
|
|
41
|
+
也可以手动运行:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
python3 install_mcp.py --client codex
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Windows:
|
|
48
|
+
|
|
49
|
+
```powershell
|
|
50
|
+
py -3 install_mcp.py --client codex
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
重新打开 Codex 后即可使用。
|
|
54
|
+
|
|
55
|
+
### 其他 MCP 客户端:自动下载模式
|
|
56
|
+
|
|
57
|
+
此模式适用于 `wechat-chat-attitude` 已发布到包源之后。双击安装器选择“自动下载配置”,或运行:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
python3 install_mcp.py --client generic --config-mode package
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
复制输出的 JSON 到客户端的 MCP/工具/开发者设置,然后重启客户端。配置的核心是:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"wechat-chat-attitude": {
|
|
69
|
+
"command": "uvx",
|
|
70
|
+
"args": [
|
|
71
|
+
"--python", "3.11",
|
|
72
|
+
"--from", "wechat-chat-attitude", "wechat-chat-attitude-mcp"
|
|
73
|
+
],
|
|
74
|
+
"description": "本地微信聊天证据准备与逐条分析 MCP 服务"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
如果你能访问源码仓库,也可以下载 [自动下载配置模板](configs/wechat-chat-attitude.package.json)。拿不到仓库文件时,直接复制上面的 JSON 即可;`uvx` 第一次启动时会取得包,之后使用本机的隔离环境运行。
|
|
81
|
+
|
|
82
|
+
如果客户端提示找不到 `uvx`,先安装 Astral 的 uv,再重启客户端:<https://docs.astral.sh/uv/getting-started/installation/>。配置固定使用 Python 3.11;本机没有时,uv 会按其默认策略取得一个隔离运行时。
|
|
83
|
+
|
|
84
|
+
> 如果包还没有发布到 PyPI,请使用下面的本地模式。发布者完成 [发布步骤](docs/publishing.md) 后,其他用户即可使用上面的 `uvx` 配置,不需要访问这个仓库。
|
|
85
|
+
|
|
86
|
+
### 发布后,别人怎么直接安装
|
|
87
|
+
|
|
88
|
+
发布者只需在 PyPI 配置一次 Trusted Publisher,然后给仓库打一个与 `pyproject.toml` 版本对应的 `v*` 标签;GitHub Actions 会自动测试、构建并发布包。用户只需安装一次 `uv`,粘贴上面的 MCP JSON,重启模型/Agent,首次调用会自动下载 `wechat-chat-attitude`。完整的发布者清单见 [`docs/publishing.md`](docs/publishing.md)。
|
|
89
|
+
|
|
90
|
+
### 其他 MCP 客户端:本地模式
|
|
91
|
+
|
|
92
|
+
在项目目录双击安装器并选择“本地配置”,或运行:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
python3 install_mcp.py --client generic --config-mode local
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
安装器会创建 `.venv` 并输出一段包含绝对路径的配置。把它粘贴到客户端后重启即可。只想查看模板、不安装依赖时:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python3 install_mcp.py --client generic --config-mode local --config-only
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 直接生成或修改配置文件
|
|
105
|
+
|
|
106
|
+
如果客户端允许导入 JSON,可以让安装器直接生成文件:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
python3 install_mcp.py \
|
|
110
|
+
--client generic \
|
|
111
|
+
--config-mode package \
|
|
112
|
+
--config-output "$HOME/wechat-chat-attitude.mcp.json"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Windows PowerShell:
|
|
116
|
+
|
|
117
|
+
```powershell
|
|
118
|
+
py -3 install_mcp.py --client generic --config-mode package `
|
|
119
|
+
--config-output "$env:USERPROFILE\wechat-chat-attitude.mcp.json"
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
如果你已经有客户端配置文件,可以让安装器合并进去:
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
python3 install_mcp.py \
|
|
126
|
+
--client generic \
|
|
127
|
+
--config-mode package \
|
|
128
|
+
--merge-config "/path/to/your/mcp.json"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Windows PowerShell 只需把最后一个参数换成配置文件的绝对路径,例如:
|
|
132
|
+
|
|
133
|
+
```powershell
|
|
134
|
+
py -3 install_mcp.py --client generic --config-mode package `
|
|
135
|
+
--merge-config "$env:APPDATA\SomeMcpClient\config.json"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
已有文件会先保存成同目录下的 `.bak`(若已存在则使用编号后缀),原来的其他 `mcpServers` 会保留。安装器不会猜测 Claude、Cursor 或其他客户端的配置路径,所以合并时必须由你明确提供目标文件。
|
|
139
|
+
|
|
140
|
+
## 第一次分析
|
|
141
|
+
|
|
142
|
+
完成 MCP 连接后,直接对模型说:
|
|
143
|
+
|
|
144
|
+
> 使用 wechat-chat-attitude,分析我和“阎亦琛 01.1.26”的全部微信聊天,重点分析她对我的态度、回复变化、沟通风格和可观察到的性格倾向,并告诉我接下来怎么自然地聊。
|
|
145
|
+
|
|
146
|
+
模型会按这个顺序工作:
|
|
147
|
+
|
|
148
|
+
1. 发现本机微信账户和进程,只返回选择项,不读取聊天正文;
|
|
149
|
+
2. 说明即将进行的本机授权,等你确认后才捕获所选进程的数据库密钥;
|
|
150
|
+
3. 如果 macOS/Windows 要求权限,只给你一条系统终端命令;管理员密码只输入系统终端,不要发给模型;
|
|
151
|
+
4. 提醒你完全退出微信,建立稳定快照;
|
|
152
|
+
5. 精确匹配一个联系人。重名或多个账户时,模型必须让你选择,不能猜;
|
|
153
|
+
6. 按总聊天长度保留最近完整消息和分层历史样本;
|
|
154
|
+
7. 逐块、逐条读取实际证据,登记读过的消息 ID,再给出结论。
|
|
155
|
+
|
|
156
|
+
“全部聊天”指程序会以全部导出记录计算范围和抽样;默认逐条交给模型阅读的是最近 1000 条加按总长度增长的历史样本,不会把超大聊天未经筛选地塞进上下文。需要更深的历史基线时,可以对模型说“重新准备并使用 deep 模式”。模型必须在报告开头区分 `raw_rows`、`selected_rows`、`manual_reviewed_rows` 和未逐条阅读的行数。
|
|
157
|
+
|
|
158
|
+
## 获取最新记录和刷新分析
|
|
159
|
+
|
|
160
|
+
不要把旧的 `run_id` 直接当成最新数据。要刷新时明确说:
|
|
161
|
+
|
|
162
|
+
> 重新获取我和“阎亦琛 01.1.26”的最新微信记录,不要复用旧 run;完成后告诉我数据截止时间、最新消息 ID、与上次相比新增了哪些内容,再结合近期内容和回复间隔分析我应该怎么聊。
|
|
163
|
+
|
|
164
|
+
模型应重新发现微信、按提示授权并建立新快照;如果微信还在运行,先按提示退出。完成后可以继续追问“她更像礼貌回应还是主动维持关系”“最近回复间隔有没有变化”等。
|
|
165
|
+
|
|
166
|
+
## 如果没有自动获取条件
|
|
167
|
+
|
|
168
|
+
可以导入已经隔离的单聊 JSON。先连接本地模式,然后对模型说:
|
|
169
|
+
|
|
170
|
+
> 使用 wechat-chat-attitude。导入 `/绝对路径/单聊.json`,联系人是“张三”,本人标签是“我”。先说明会写入哪些本机文件,得到允许后再准备证据;按顺序逐条读完所有分块,分析态度、沟通风格和可观察到的性格倾向,并给出自然的聊天建议。
|
|
171
|
+
|
|
172
|
+
单聊 JSON 示例见 [examples/example-chat.json](examples/example-chat.json)。已解密数据库、加密数据库和受保护密钥 JSON 的导入方式见 [本机获取说明](references/local-acquisition.md)。
|
|
173
|
+
|
|
174
|
+
## 隐私和安全
|
|
175
|
+
|
|
176
|
+
- MCP 服务只在本机通过 `stdio` 运行,不开放监听端口;
|
|
177
|
+
- 原始聊天只在明确读取分块时返回,不写入服务日志;
|
|
178
|
+
- 密钥只保存在本机私有目录,工具不会返回密钥;
|
|
179
|
+
- 程序不会修改微信数据库、微信应用或账户文件;
|
|
180
|
+
- 使用云模型时,逐条读取的消息会进入该模型的上下文,仍受模型服务商的数据政策约束;
|
|
181
|
+
- 不要把管理员密码、数据库密钥或密钥 JSON 内容发到聊天中。
|
|
182
|
+
|
|
183
|
+
## 常见问题
|
|
184
|
+
|
|
185
|
+
### “配置成功但客户端找不到 MCP”
|
|
186
|
+
|
|
187
|
+
保存 JSON 后完全退出并重新打开客户端。自动下载模式还要确认 `uvx` 在系统 PATH 中;包尚未发布时请改用本地模式。
|
|
188
|
+
|
|
189
|
+
### “要求输入密码”
|
|
190
|
+
|
|
191
|
+
只在系统的 Terminal/管理员终端输入。模型和 MCP 不需要、也不应该收到这个密码。
|
|
192
|
+
|
|
193
|
+
### “请退出微信”
|
|
194
|
+
|
|
195
|
+
这是为了避免复制正在写入的数据库。退出后确认微信进程已经结束,再让模型重试;不要用正在变化的数据库继续解密。
|
|
196
|
+
|
|
197
|
+
### “找不到联系人或有多个候选”
|
|
198
|
+
|
|
199
|
+
提供精确备注、昵称、微信号或让模型列出候选后选择,不要让模型猜。
|
|
200
|
+
|
|
201
|
+
### “自动获取不支持我的微信版本”
|
|
202
|
+
|
|
203
|
+
使用单聊 JSON 或稳定的已解密数据库导入。程序会明确停止,不会假装成功或全盘猜路径。
|
|
204
|
+
|
|
205
|
+
## 命令行和开发文档
|
|
206
|
+
|
|
207
|
+
不使用 MCP 也可以运行 `scripts/wechat_chat_pipeline.py` 处理 JSON/已解密数据库。抽样、刷新、账本和数据格式说明分别见:
|
|
208
|
+
|
|
209
|
+
- [MCP 使用设计](docs/mcp-first-mvp.md)
|
|
210
|
+
- [本机自动获取设计](docs/foolproof-acquisition-design.md)
|
|
211
|
+
- [分析合同](references/analysis-contract.md)
|
|
212
|
+
- [刷新流程](references/refresh-workflow.md)
|
|
213
|
+
|
|
214
|
+
开发者运行测试:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
PYTHONPATH=. .venv/bin/python -m unittest discover -s tests -v
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
发行包不包含真实聊天、数据库密钥或第三方预编译扫描器。
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Native acquisition helpers shipped as reviewed source."""
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Cross-platform key matching primitives."""
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Small cross-platform helpers for best-effort private file permissions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def chmod_fd(file_descriptor: int, mode: int) -> None:
|
|
9
|
+
"""Apply a file-descriptor mode when the host operating system supports it."""
|
|
10
|
+
|
|
11
|
+
fchmod = getattr(os, "fchmod", None)
|
|
12
|
+
if fchmod is None:
|
|
13
|
+
return
|
|
14
|
+
try:
|
|
15
|
+
fchmod(file_descriptor, mode)
|
|
16
|
+
except (AttributeError, NotImplementedError, OSError):
|
|
17
|
+
# Windows does not expose POSIX descriptor permissions. The caller
|
|
18
|
+
# still applies os.chmod after the atomic replace where available.
|
|
19
|
+
return
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import hmac
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import struct
|
|
9
|
+
import tempfile
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from .file_permissions import chmod_fd
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
PAGE_SIZE = 4096
|
|
17
|
+
SALT_SIZE = 16
|
|
18
|
+
KEY_SIZE = 32
|
|
19
|
+
RESERVE_SIZE = 80
|
|
20
|
+
HEX_PATTERN = re.compile(rb"x'([0-9a-fA-F]{64,192})'")
|
|
21
|
+
MESSAGE_NAME = re.compile(r"message_[0-9]+\.db")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True)
|
|
25
|
+
class DatabaseEntry:
|
|
26
|
+
relative_path: str
|
|
27
|
+
path: Path
|
|
28
|
+
salt_hex: str
|
|
29
|
+
first_page: bytes
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _database_entry(db_root: Path, relative_path: str) -> DatabaseEntry | None:
|
|
33
|
+
path = db_root / Path(relative_path)
|
|
34
|
+
if not path.is_file() or path.is_symlink() or path.stat().st_size < PAGE_SIZE:
|
|
35
|
+
return None
|
|
36
|
+
with path.open("rb") as handle:
|
|
37
|
+
first_page = handle.read(PAGE_SIZE)
|
|
38
|
+
if len(first_page) != PAGE_SIZE or first_page.startswith(b"SQLite format 3"):
|
|
39
|
+
return None
|
|
40
|
+
return DatabaseEntry(
|
|
41
|
+
relative_path=relative_path,
|
|
42
|
+
path=path,
|
|
43
|
+
salt_hex=first_page[:SALT_SIZE].hex(),
|
|
44
|
+
first_page=first_page,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def collect_allowlisted_databases(db_root: Path) -> list[DatabaseEntry]:
|
|
49
|
+
root = Path(db_root).expanduser().resolve()
|
|
50
|
+
if not root.is_dir() or root.is_symlink():
|
|
51
|
+
raise ValueError("db_root must be a regular directory")
|
|
52
|
+
relative_paths = ["contact/contact.db", "session/session.db"]
|
|
53
|
+
message_dir = root / "message"
|
|
54
|
+
if message_dir.is_dir() and not message_dir.is_symlink():
|
|
55
|
+
for path in sorted(message_dir.iterdir()):
|
|
56
|
+
if path.name == "message_resource.db" or MESSAGE_NAME.fullmatch(path.name):
|
|
57
|
+
relative_paths.append(f"message/{path.name}")
|
|
58
|
+
entries = []
|
|
59
|
+
for relative in relative_paths:
|
|
60
|
+
entry = _database_entry(root, relative)
|
|
61
|
+
if entry is not None:
|
|
62
|
+
entries.append(entry)
|
|
63
|
+
return entries
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def verify_key(key: bytes, database: DatabaseEntry) -> bool:
|
|
67
|
+
if len(key) != KEY_SIZE:
|
|
68
|
+
return False
|
|
69
|
+
salt = database.first_page[:SALT_SIZE]
|
|
70
|
+
mac_salt = bytes(value ^ 0x3A for value in salt)
|
|
71
|
+
mac_key = hashlib.pbkdf2_hmac("sha512", key, mac_salt, 2, dklen=KEY_SIZE)
|
|
72
|
+
authenticated = database.first_page[SALT_SIZE : PAGE_SIZE - RESERVE_SIZE + 16]
|
|
73
|
+
stored = database.first_page[PAGE_SIZE - 64 : PAGE_SIZE]
|
|
74
|
+
digest = hmac.new(mac_key, authenticated, hashlib.sha512)
|
|
75
|
+
digest.update(struct.pack("<I", 1))
|
|
76
|
+
return hmac.compare_digest(digest.digest(), stored)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def scan_bytes(data: bytes, databases: list[DatabaseEntry]) -> dict[str, str]:
|
|
80
|
+
matches: dict[str, str] = {}
|
|
81
|
+
by_salt: dict[str, list[DatabaseEntry]] = {}
|
|
82
|
+
for database in databases:
|
|
83
|
+
by_salt.setdefault(database.salt_hex, []).append(database)
|
|
84
|
+
for match in HEX_PATTERN.finditer(data):
|
|
85
|
+
raw = match.group(1).decode("ascii").lower()
|
|
86
|
+
if len(raw) % 2:
|
|
87
|
+
continue
|
|
88
|
+
candidates: list[tuple[str, str | None]] = []
|
|
89
|
+
if len(raw) == 64:
|
|
90
|
+
candidates.append((raw, None))
|
|
91
|
+
elif len(raw) >= 96:
|
|
92
|
+
candidates.append((raw[:64], raw[-32:]))
|
|
93
|
+
for key_hex, salt_hex in candidates:
|
|
94
|
+
key = bytes.fromhex(key_hex)
|
|
95
|
+
possible = by_salt.get(salt_hex, []) if salt_hex else databases
|
|
96
|
+
for database in possible:
|
|
97
|
+
if database.relative_path in matches:
|
|
98
|
+
continue
|
|
99
|
+
if verify_key(key, database):
|
|
100
|
+
matches[database.relative_path] = key_hex
|
|
101
|
+
return matches
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def write_protected_key_file(path: Path, matches: dict[str, str]) -> None:
|
|
105
|
+
destination = Path(path).expanduser().absolute()
|
|
106
|
+
if destination.exists() and destination.is_symlink():
|
|
107
|
+
raise ValueError("key output must not be a symlink")
|
|
108
|
+
destination.parent.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
try:
|
|
110
|
+
os.chmod(destination.parent, 0o700)
|
|
111
|
+
except OSError:
|
|
112
|
+
pass
|
|
113
|
+
descriptor, temporary_name = tempfile.mkstemp(
|
|
114
|
+
prefix=f".{destination.name}.", dir=destination.parent
|
|
115
|
+
)
|
|
116
|
+
try:
|
|
117
|
+
payload = {
|
|
118
|
+
relative: {"enc_key": key_hex}
|
|
119
|
+
for relative, key_hex in sorted(matches.items())
|
|
120
|
+
}
|
|
121
|
+
with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
|
|
122
|
+
json.dump(payload, handle, ensure_ascii=False, indent=2)
|
|
123
|
+
handle.write("\n")
|
|
124
|
+
handle.flush()
|
|
125
|
+
chmod_fd(handle.fileno(), 0o600)
|
|
126
|
+
os.replace(temporary_name, destination)
|
|
127
|
+
try:
|
|
128
|
+
os.chmod(destination, 0o600)
|
|
129
|
+
except OSError:
|
|
130
|
+
pass
|
|
131
|
+
finally:
|
|
132
|
+
if os.path.exists(temporary_name):
|
|
133
|
+
os.unlink(temporary_name)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Bundled source for the macOS WeChat key scanner."""
|