weflow-cli 1.5.0 → 1.6.1
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.
- package/ARCHITECTURE.md +105 -0
- package/CHANGELOG.md +121 -0
- package/CONTRIBUTING.md +45 -0
- package/LICENSE +21 -21
- package/OPERATIONS.md +272 -0
- package/README.en.md +281 -0
- package/README.md +334 -149
- package/SECURITY.md +47 -0
- package/bin/weflow-cli.ts +4050 -581
- package/dist/bin/weflow-cli.js +4310 -588
- package/dist/bin/weflow-cli.js.map +1 -1
- package/dist/mcp-server/index.js +178 -236
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/src/core/dbPathService.d.ts +15 -1
- package/dist/src/core/dbPathService.d.ts.map +1 -1
- package/dist/src/core/dbPathService.js +177 -19
- package/dist/src/core/dbPathService.js.map +1 -1
- package/dist/src/core/keyService.d.ts +8 -0
- package/dist/src/core/keyService.d.ts.map +1 -1
- package/dist/src/core/keyService.js +127 -22
- package/dist/src/core/keyService.js.map +1 -1
- package/dist/src/core/ntCore.d.ts +59 -1
- package/dist/src/core/ntCore.d.ts.map +1 -1
- package/dist/src/core/ntCore.js +201 -65
- package/dist/src/core/ntCore.js.map +1 -1
- package/dist/src/core/sqlcipherCore.d.ts.map +1 -1
- package/dist/src/core/sqlcipherCore.js +18 -1
- package/dist/src/core/sqlcipherCore.js.map +1 -1
- package/dist/src/core/wcdbCore.d.ts.map +1 -1
- package/dist/src/core/wcdbCore.js +3 -6
- package/dist/src/core/wcdbCore.js.map +1 -1
- package/dist/src/services/assistantDaemon.d.ts +19 -0
- package/dist/src/services/assistantDaemon.d.ts.map +1 -0
- package/dist/src/services/assistantDaemon.js +117 -0
- package/dist/src/services/assistantDaemon.js.map +1 -0
- package/dist/src/services/assistantMemory.d.ts +36 -0
- package/dist/src/services/assistantMemory.d.ts.map +1 -0
- package/dist/src/services/assistantMemory.js +182 -0
- package/dist/src/services/assistantMemory.js.map +1 -0
- package/dist/src/services/assistantPrivacy.d.ts +25 -0
- package/dist/src/services/assistantPrivacy.d.ts.map +1 -0
- package/dist/src/services/assistantPrivacy.js +77 -0
- package/dist/src/services/assistantPrivacy.js.map +1 -0
- package/dist/src/services/assistantRouting.d.ts +18 -0
- package/dist/src/services/assistantRouting.d.ts.map +1 -0
- package/dist/src/services/assistantRouting.js +68 -0
- package/dist/src/services/assistantRouting.js.map +1 -0
- package/dist/src/services/assistantService.d.ts +37 -0
- package/dist/src/services/assistantService.d.ts.map +1 -0
- package/dist/src/services/assistantService.js +284 -0
- package/dist/src/services/assistantService.js.map +1 -0
- package/dist/src/services/assistantTools.d.ts +24 -0
- package/dist/src/services/assistantTools.d.ts.map +1 -0
- package/dist/src/services/assistantTools.js +582 -0
- package/dist/src/services/assistantTools.js.map +1 -0
- package/dist/src/services/chatService.d.ts +44 -0
- package/dist/src/services/chatService.d.ts.map +1 -1
- package/dist/src/services/chatService.js +105 -4
- package/dist/src/services/chatService.js.map +1 -1
- package/dist/src/services/configService.d.ts +55 -0
- package/dist/src/services/configService.d.ts.map +1 -1
- package/dist/src/services/configService.js +147 -6
- package/dist/src/services/configService.js.map +1 -1
- package/dist/src/services/evidenceService.d.ts +30 -0
- package/dist/src/services/evidenceService.d.ts.map +1 -0
- package/dist/src/services/evidenceService.js +98 -0
- package/dist/src/services/evidenceService.js.map +1 -0
- package/dist/src/services/exportService.d.ts +13 -20
- package/dist/src/services/exportService.d.ts.map +1 -1
- package/dist/src/services/exportService.js +113 -47
- package/dist/src/services/exportService.js.map +1 -1
- package/dist/src/services/initKeyService.d.ts +19 -0
- package/dist/src/services/initKeyService.d.ts.map +1 -0
- package/dist/src/services/initKeyService.js +80 -0
- package/dist/src/services/initKeyService.js.map +1 -0
- package/dist/src/services/messageContract.d.ts +23 -0
- package/dist/src/services/messageContract.d.ts.map +1 -0
- package/dist/src/services/messageContract.js +49 -0
- package/dist/src/services/messageContract.js.map +1 -0
- package/dist/src/services/messageQuery.d.ts +7 -0
- package/dist/src/services/messageQuery.d.ts.map +1 -0
- package/dist/src/services/messageQuery.js +30 -0
- package/dist/src/services/messageQuery.js.map +1 -0
- package/dist/src/services/wechat-formatter.js +14 -14
- package/dist/src/services/wechatMessageService.d.ts.map +1 -1
- package/dist/src/services/wechatMessageService.js +31 -3
- package/dist/src/services/wechatMessageService.js.map +1 -1
- package/dist/src/services/whitelistService.d.ts +52 -4
- package/dist/src/services/whitelistService.d.ts.map +1 -1
- package/dist/src/services/whitelistService.js +183 -17
- package/dist/src/services/whitelistService.js.map +1 -1
- package/dist/src/types.d.ts +20 -0
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/utils/dateRange.d.ts +15 -0
- package/dist/src/utils/dateRange.d.ts.map +1 -0
- package/dist/src/utils/dateRange.js +48 -0
- package/dist/src/utils/dateRange.js.map +1 -0
- package/dist/src/utils/mcpSecurity.d.ts +5 -0
- package/dist/src/utils/mcpSecurity.d.ts.map +1 -0
- package/dist/src/utils/mcpSecurity.js +39 -0
- package/dist/src/utils/mcpSecurity.js.map +1 -0
- package/dist/src/utils/packageRoot.d.ts +2 -0
- package/dist/src/utils/packageRoot.d.ts.map +1 -0
- package/dist/src/utils/packageRoot.js +16 -0
- package/dist/src/utils/packageRoot.js.map +1 -0
- package/dist/src/utils/python.d.ts +2 -0
- package/dist/src/utils/python.d.ts.map +1 -0
- package/dist/src/utils/python.js +72 -0
- package/dist/src/utils/python.js.map +1 -0
- package/dist/src/utils/pythonProcessEnv.d.ts +3 -0
- package/dist/src/utils/pythonProcessEnv.d.ts.map +1 -0
- package/dist/src/utils/pythonProcessEnv.js +46 -0
- package/dist/src/utils/pythonProcessEnv.js.map +1 -0
- package/dist/src/utils/talkerUtils.d.ts +4 -1
- package/dist/src/utils/talkerUtils.d.ts.map +1 -1
- package/dist/src/utils/talkerUtils.js +9 -9
- package/dist/src/utils/talkerUtils.js.map +1 -1
- package/dist/src/utils/wechatEmoji.d.ts +5 -0
- package/dist/src/utils/wechatEmoji.d.ts.map +1 -0
- package/dist/src/utils/wechatEmoji.js +35 -0
- package/dist/src/utils/wechatEmoji.js.map +1 -0
- package/docs/AI_INTERFACE.md +189 -0
- package/docs/BRANCHES.md +87 -0
- package/docs/DATA_CONTRACT.md +74 -0
- package/docs/DECISIONS.md +270 -0
- package/docs/DEEPSEEK_V4_FLASH/346/226/275/345/267/245/346/226/207/346/241/243.md +234 -0
- package/docs/EVIDENCE_GUIDE.md +96 -0
- package/docs/MCP.md +95 -0
- package/docs/PARTNERS.md +41 -0
- package/docs/PROJECT_STATE.md +96 -0
- package/docs/RELEASING.md +124 -0
- package/docs/ROADMAP.md +191 -0
- package/docs/SETUP.md +103 -0
- package/docs/images/weflow-architecture-gpt-image-2.png +0 -0
- package/docs/images/weflow-architecture.png +0 -0
- package/docs/images/weflow-architecture.svg +65 -0
- package/mcp-server/index.ts +169 -269
- package/package.json +81 -72
- package/requirements-3x.txt +4 -0
- package/requirements-voice.txt +15 -0
- package/requirements.txt +9 -0
- package/resources/js/marked.min.js +69 -0
- package/resources/wechat-emoji/666.png +0 -0
- package/resources/wechat-emoji/Awesome.png +0 -0
- package/resources/wechat-emoji/Concerned.png +0 -0
- package/resources/wechat-emoji/Cry.png +0 -0
- package/resources/wechat-emoji/Emm.png +0 -0
- package/resources/wechat-emoji/Facepalm.png +0 -0
- package/resources/wechat-emoji/Grin.png +0 -0
- package/resources/wechat-emoji/OK.png +0 -0
- package/resources/wechat-emoji/Respect.png +0 -0
- package/resources/wechat-emoji/Sick.png +0 -0
- package/resources/wechat-emoji/Sleep.png +0 -0
- package/resources/wechat-emoji/Smile.png +0 -0
- package/resources/wechat-emoji//344/272/262/344/272/262.png +0 -0
- package/resources/wechat-emoji//344/276/277/344/276/277.png +0 -0
- package/resources/wechat-emoji//345/201/267/347/254/221.png +0 -0
- package/resources/wechat-emoji//345/202/262/346/205/242.png +0 -0
- package/resources/wechat-emoji//345/206/215/350/247/201.png +0 -0
- package/resources/wechat-emoji//345/207/213/350/260/242.png +0 -0
- package/resources/wechat-emoji//345/212/240/346/262/271.png +0 -0
- package/resources/wechat-emoji//345/213/276/345/274/225.png +0 -0
- package/resources/wechat-emoji//345/217/221/345/221/206.png +0 -0
- package/resources/wechat-emoji//345/217/221/346/200/222.png +0 -0
- package/resources/wechat-emoji//345/217/221/346/212/226.png +0 -0
- package/resources/wechat-emoji//345/217/257/346/200/234.png +0 -0
- package/resources/wechat-emoji//345/217/263/345/223/274/345/223/274.png +0 -0
- package/resources/wechat-emoji//345/217/271/346/260/224.png +0 -0
- package/resources/wechat-emoji//345/220/203/347/223/234.png +0 -0
- package/resources/wechat-emoji//345/220/210/345/215/201.png +0 -0
- package/resources/wechat-emoji//345/220/220.png +0 -0
- package/resources/wechat-emoji//345/221/262/347/211/231.png +0 -0
- package/resources/wechat-emoji//345/222/222/351/252/202.png +0 -0
- package/resources/wechat-emoji//345/222/226/345/225/241.png +0 -0
- package/resources/wechat-emoji//345/223/207.png +0 -0
- package/resources/wechat-emoji//345/225/244/351/205/222.png +0 -0
- package/resources/wechat-emoji//345/230/230.png +0 -0
- package/resources/wechat-emoji//345/230/264/345/224/207.png +0 -0
- package/resources/wechat-emoji//345/230/277/345/223/210.png +0 -0
- package/resources/wechat-emoji//345/233/247.png +0 -0
- package/resources/wechat-emoji//345/233/260.png +0 -0
- package/resources/wechat-emoji//345/235/217/347/254/221.png +0 -0
- package/resources/wechat-emoji//345/244/247/345/223/255.png +0 -0
- package/resources/wechat-emoji//345/244/251/345/225/212.png +0 -0
- package/resources/wechat-emoji//345/244/252/351/230/263.png +0 -0
- package/resources/wechat-emoji//345/244/261/346/234/233.png +0 -0
- package/resources/wechat-emoji//345/245/270/347/254/221.png +0 -0
- package/resources/wechat-emoji//345/245/275/347/232/204.png +0 -0
- package/resources/wechat-emoji//345/247/224/345/261/210.png +0 -0
- package/resources/wechat-emoji//345/256/263/347/276/236.png +0 -0
- package/resources/wechat-emoji//345/260/264/345/260/254.png +0 -0
- package/resources/wechat-emoji//345/272/206/347/245/235.png +0 -0
- package/resources/wechat-emoji//345/274/261.png +0 -0
- package/resources/wechat-emoji//345/274/272.png +0 -0
- package/resources/wechat-emoji//345/276/227/346/204/217.png +0 -0
- package/resources/wechat-emoji//345/276/256/347/254/221.png +0 -0
- package/resources/wechat-emoji//345/277/203/347/242/216.png +0 -0
- package/resources/wechat-emoji//345/277/253/345/223/255/344/272/206.png +0 -0
- package/resources/wechat-emoji//346/201/220/346/203/247.png +0 -0
- package/resources/wechat-emoji//346/202/240/351/227/262.png +0 -0
- package/resources/wechat-emoji//346/203/212/346/201/220.png +0 -0
- package/resources/wechat-emoji//346/203/212/350/256/266.png +0 -0
- package/resources/wechat-emoji//346/204/211/345/277/253.png +0 -0
- package/resources/wechat-emoji//346/206/250/347/254/221.png +0 -0
- package/resources/wechat-emoji//346/211/223/350/204/270.png +0 -0
- package/resources/wechat-emoji//346/212/223/347/213/202.png +0 -0
- package/resources/wechat-emoji//346/212/240/351/274/273.png +0 -0
- package/resources/wechat-emoji//346/212/261/346/213/263.png +0 -0
- package/resources/wechat-emoji//346/213/245/346/212/261.png +0 -0
- package/resources/wechat-emoji//346/213/263/345/244/264.png +0 -0
- package/resources/wechat-emoji//346/215/202/350/204/270.png +0 -0
- package/resources/wechat-emoji//346/217/241/346/211/213.png +0 -0
- package/resources/wechat-emoji//346/222/207/345/230/264.png +0 -0
- package/resources/wechat-emoji//346/223/246/346/261/227.png +0 -0
- package/resources/wechat-emoji//346/225/262/346/211/223.png +0 -0
- package/resources/wechat-emoji//346/227/240/350/257/255.png +0 -0
- package/resources/wechat-emoji//346/227/272/346/237/264.png +0 -0
- package/resources/wechat-emoji//346/231/225.png +0 -0
- package/resources/wechat-emoji//346/234/210/344/272/256.png +0 -0
- package/resources/wechat-emoji//346/234/272/346/231/272.png +0 -0
- package/resources/wechat-emoji//346/261/227.png +0 -0
- package/resources/wechat-emoji//346/265/201/346/263/252.png +0 -0
- package/resources/wechat-emoji//347/202/270/345/274/271.png +0 -0
- package/resources/wechat-emoji//347/203/237/350/212/261.png +0 -0
- package/resources/wechat-emoji//347/210/206/347/253/271.png +0 -0
- package/resources/wechat-emoji//347/210/261/345/277/203.png +0 -0
- package/resources/wechat-emoji//347/214/252/345/244/264.png +0 -0
- package/resources/wechat-emoji//347/216/253/347/221/260.png +0 -0
- package/resources/wechat-emoji//347/224/237/347/227/205.png +0 -0
- package/resources/wechat-emoji//347/226/221/351/227/256.png +0 -0
- package/resources/wechat-emoji//347/231/274.png +0 -0
- package/resources/wechat-emoji//347/231/275/347/234/274.png +0 -0
- package/resources/wechat-emoji//347/232/261/347/234/211.png +0 -0
- package/resources/wechat-emoji//347/235/241.png +0 -0
- package/resources/wechat-emoji//347/240/264/346/266/225/344/270/272/347/254/221.png +0 -0
- package/resources/wechat-emoji//347/244/274/347/211/251.png +0 -0
- package/resources/wechat-emoji//347/244/276/344/274/232/347/244/276/344/274/232.png +0 -0
- package/resources/wechat-emoji//347/246/217.png +0 -0
- package/resources/wechat-emoji//347/254/221/350/204/270.png +0 -0
- package/resources/wechat-emoji//347/272/242/345/214/205.png +0 -0
- package/resources/wechat-emoji//347/277/273/347/231/275/347/234/274.png +0 -0
- package/resources/wechat-emoji//350/200/266.png +0 -0
- package/resources/wechat-emoji//350/203/234/345/210/251.png +0 -0
- package/resources/wechat-emoji//350/204/270/347/272/242.png +0 -0
- package/resources/wechat-emoji//350/211/262.png +0 -0
- package/resources/wechat-emoji//350/213/246/346/266/251.png +0 -0
- package/resources/wechat-emoji//350/217/234/345/210/200.png +0 -0
- package/resources/wechat-emoji//350/233/213/347/263/225.png +0 -0
- package/resources/wechat-emoji//350/241/260.png +0 -0
- package/resources/wechat-emoji//350/243/202/345/274/200.png +0 -0
- package/resources/wechat-emoji//350/256/251/346/210/221/347/234/213/347/234/213.png +0 -0
- package/resources/wechat-emoji//350/260/203/347/232/256.png +0 -0
- package/resources/wechat-emoji//350/267/263/350/267/263.png +0 -0
- package/resources/wechat-emoji//350/275/254/345/234/210.png +0 -0
- package/resources/wechat-emoji//351/204/231/350/247/206.png +0 -0
- package/resources/wechat-emoji//351/227/255/345/230/264.png +0 -0
- package/resources/wechat-emoji//351/230/264/351/231/251.png +0 -0
- package/resources/wechat-emoji//351/232/276/350/277/207.png +0 -0
- package/resources/wechat-emoji//351/252/267/351/253/205.png +0 -0
- package/resources/wechat-emoji//351/274/223/346/216/214.png +0 -0
- package/scripts/_batch_link_sources.py +73 -73
- package/scripts/_utils.py +29 -0
- package/scripts/annual_report.py +796 -796
- package/scripts/auto_tag.py +128 -128
- package/scripts/biz_daily.py +210 -53
- package/scripts/chat_report.py +10 -0
- package/scripts/chat_stats.py +702 -702
- package/scripts/classify_daily.py +17 -11
- package/scripts/create_reading_notes.py +370 -370
- package/scripts/daily_stats.py +132 -0
- package/scripts/enrich_backlinks.py +163 -163
- package/scripts/export_chat_html.py +1704 -107
- package/scripts/extract_todos.py +445 -404
- package/scripts/fav_server.py +119 -12
- package/scripts/fix_topics.py +111 -111
- package/scripts/generate_ai_report.py +1 -1
- package/scripts/generate_html.py +597 -277
- package/scripts/inspection_records.py +354 -0
- package/scripts/mcp_bridge.py +398 -398
- package/scripts/nt_decrypt.py +702 -48
- package/scripts/pipeline.py +27 -19
- package/scripts/promote_all.py +312 -310
- package/scripts/promote_ideas.py +299 -297
- package/scripts/rag_chat.py +194 -191
- package/scripts/semantic_search.py +435 -431
- package/scripts/sync_fav.py +201 -146
- package/scripts/sync_weread.py +192 -192
- package/scripts/vault_rag.py +140 -139
- package/scripts/vault_search.py +141 -141
- package/scripts/wechat_emoji.py +132 -0
- package/scripts/wechat_emoticon.py +433 -0
- package/scripts/wechat_image.py +258 -0
- package/scripts/wechat_voice.py +327 -0
- package/src/core/dbPathService.ts +163 -18
- package/src/core/keyService.ts +137 -25
- package/src/core/ntCore.ts +215 -62
- package/src/core/sqlcipherCore.ts +17 -1
- package/src/core/wcdbCore.ts +3 -7
- package/src/services/assistantDaemon.ts +111 -0
- package/src/services/assistantMemory.ts +193 -0
- package/src/services/assistantPrivacy.ts +87 -0
- package/src/services/assistantRouting.ts +92 -0
- package/src/services/assistantService.ts +295 -0
- package/src/services/assistantTools.ts +549 -0
- package/src/services/chatService.ts +133 -4
- package/src/services/configService.ts +194 -6
- package/src/services/evidenceService.ts +129 -0
- package/src/services/exportService.ts +113 -48
- package/src/services/initKeyService.ts +83 -0
- package/src/services/messageContract.ts +77 -0
- package/src/services/messageQuery.ts +38 -0
- package/src/services/wechat-formatter.ts +348 -348
- package/src/services/wechatMessageService.ts +25 -3
- package/src/services/wereadService.ts +308 -308
- package/src/services/whitelistService.ts +209 -22
- package/src/types.ts +20 -0
- package/src/utils/dateRange.ts +58 -0
- package/src/utils/mcpSecurity.ts +39 -0
- package/src/utils/packageRoot.ts +14 -0
- package/src/utils/python.ts +73 -0
- package/src/utils/pythonProcessEnv.ts +45 -0
- package/src/utils/talkerUtils.ts +14 -2
- package/dist/src/utils/errors.d.ts +0 -25
- package/dist/src/utils/errors.d.ts.map +0 -1
- package/dist/src/utils/errors.js +0 -43
- package/dist/src/utils/errors.js.map +0 -1
- package/dist/src/utils/pythonRunner.d.ts +0 -27
- package/dist/src/utils/pythonRunner.d.ts.map +0 -1
- package/dist/src/utils/pythonRunner.js +0 -62
- package/dist/src/utils/pythonRunner.js.map +0 -1
- package/src/utils/errors.ts +0 -42
- package/src/utils/pythonRunner.ts +0 -81
|
@@ -2,35 +2,50 @@ import { join, basename, dirname } from 'path'
|
|
|
2
2
|
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
|
3
3
|
import { execFile } from 'child_process'
|
|
4
4
|
import { promisify } from 'util'
|
|
5
|
-
import { fileURLToPath } from 'url'
|
|
6
5
|
import { chatService } from './chatService.js'
|
|
7
6
|
import { configService } from './configService.js'
|
|
7
|
+
import { getPythonCommand } from '../utils/python.js'
|
|
8
|
+
import { createPythonProcessEnv, safeSubprocessError } from '../utils/pythonProcessEnv.js'
|
|
8
9
|
import type { Message } from '../types.js'
|
|
10
|
+
import { createWeFlowEnvelope } from './messageContract.js'
|
|
11
|
+
import { parseLocalDateOrIso } from '../utils/dateRange.js'
|
|
12
|
+
import { resolvePackageRoot } from '../utils/packageRoot.js'
|
|
13
|
+
|
|
14
|
+
export type ExportContract = 'raw' | 'weflow-v1'
|
|
15
|
+
export interface ExportResult { success: boolean; path?: string; count?: number; error?: string }
|
|
9
16
|
|
|
10
17
|
const execFileAsync = promisify(execFile)
|
|
11
|
-
|
|
12
|
-
|
|
18
|
+
function getPackageRoot(): string {
|
|
19
|
+
return resolvePackageRoot(import.meta.url)
|
|
20
|
+
}
|
|
13
21
|
|
|
14
22
|
export class ExportService {
|
|
15
|
-
async exportJson(talker: string, outputDir: string, limit = 0
|
|
23
|
+
async exportJson(talker: string, outputDir: string, limit = 0, from?: number, to?: number, contract: ExportContract = 'raw'): Promise<ExportResult> {
|
|
16
24
|
try {
|
|
17
|
-
const messages = await chatService.
|
|
25
|
+
const messages = await chatService.getMessagesInRange(talker, limit, from, to)
|
|
18
26
|
if (messages.length === 0) {
|
|
19
27
|
return { success: false, error: '未找到消息' }
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
const dir = this.ensureOutputDir(outputDir)
|
|
23
31
|
const filePath = join(dir, `${talker}_messages.json`)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
const payload = contract === 'weflow-v1'
|
|
33
|
+
? createWeFlowEnvelope(messages, new Date().toISOString(), {
|
|
34
|
+
requestedFrom: from,
|
|
35
|
+
requestedTo: to,
|
|
36
|
+
requestedLimit: limit,
|
|
37
|
+
})
|
|
38
|
+
: messages
|
|
39
|
+
writeFileSync(filePath, JSON.stringify(payload, null, 2), 'utf8')
|
|
40
|
+
return { success: true, path: filePath, count: messages.length }
|
|
41
|
+
} catch {
|
|
42
|
+
return { success: false, error: 'JSON 导出失败' }
|
|
28
43
|
}
|
|
29
44
|
}
|
|
30
45
|
|
|
31
|
-
async exportTxt(talker: string, outputDir: string, limit = 0
|
|
46
|
+
async exportTxt(talker: string, outputDir: string, limit = 0, from?: number, to?: number): Promise<ExportResult> {
|
|
32
47
|
try {
|
|
33
|
-
const messages = await chatService.
|
|
48
|
+
const messages = await chatService.getMessagesInRange(talker, limit, from, to)
|
|
34
49
|
if (messages.length === 0) {
|
|
35
50
|
return { success: false, error: '未找到消息' }
|
|
36
51
|
}
|
|
@@ -45,32 +60,48 @@ export class ExportService {
|
|
|
45
60
|
const dir = this.ensureOutputDir(outputDir)
|
|
46
61
|
const filePath = join(dir, `${talker}_messages.txt`)
|
|
47
62
|
writeFileSync(filePath, lines.join('\n'), 'utf8')
|
|
48
|
-
return { success: true, path: filePath }
|
|
49
|
-
} catch
|
|
50
|
-
return { success: false, error:
|
|
63
|
+
return { success: true, path: filePath, count: messages.length }
|
|
64
|
+
} catch {
|
|
65
|
+
return { success: false, error: '文本导出失败' }
|
|
51
66
|
}
|
|
52
67
|
}
|
|
53
68
|
|
|
54
|
-
async exportHtml(talker: string, outputDir: string, limit = 0
|
|
69
|
+
async exportHtml(talker: string, outputDir: string, limit = 0, date = '', from?: number, to?: number, quiet = false, options: { fullImages?: boolean } = {}): Promise<ExportResult> {
|
|
55
70
|
try {
|
|
71
|
+
if (from !== undefined || to !== undefined) {
|
|
72
|
+
return this.exportHtmlBasic(talker, outputDir, limit, from, to)
|
|
73
|
+
}
|
|
56
74
|
// Use Python export_chat_html.py for rich HTML with images
|
|
57
75
|
const cfg = configService.getAll()
|
|
58
76
|
const db = cfg.ntDbPath
|
|
59
77
|
const key = cfg.ntKey
|
|
60
78
|
const salt = cfg.ntSalt
|
|
79
|
+
// Derives each message shard's key (WeChat 4.1.12.26+). favPassphrase is
|
|
80
|
+
// the dedicated field, but decryptKey holds the same value and is what
|
|
81
|
+
// `init` writes - without this fallback the shard merge silently stays
|
|
82
|
+
// dormant and exports stop at the first shard's last message.
|
|
83
|
+
const passphrase = configService.get('favPassphrase') || cfg.decryptKey || ''
|
|
61
84
|
|
|
62
85
|
if (!db || !key || !salt) {
|
|
63
86
|
// Fallback: basic HTML
|
|
64
|
-
|
|
87
|
+
let fallbackFrom: number | undefined = from
|
|
88
|
+
let fallbackTo: number | undefined = to
|
|
89
|
+
if (date && fallbackFrom === undefined && fallbackTo === undefined) {
|
|
90
|
+
fallbackFrom = parseLocalDateOrIso(date)
|
|
91
|
+
fallbackTo = parseLocalDateOrIso(date, true)
|
|
92
|
+
}
|
|
93
|
+
return this.exportHtmlBasic(talker, outputDir, limit, fallbackFrom, fallbackTo)
|
|
65
94
|
}
|
|
66
95
|
|
|
67
96
|
// Resolve Python script path (dist/src/services → package root)
|
|
68
|
-
const
|
|
69
|
-
const script = join(pkgRoot, 'scripts', 'export_chat_html.py')
|
|
97
|
+
const script = join(getPackageRoot(), 'scripts', 'export_chat_html.py')
|
|
70
98
|
|
|
71
99
|
// Cache dir for image thumbnails
|
|
72
|
-
const cacheDir = cfg.
|
|
73
|
-
? join(dirname(dirname(cfg.
|
|
100
|
+
const cacheDir = cfg.ntDbPath
|
|
101
|
+
? join(dirname(dirname(dirname(cfg.ntDbPath))), 'cache')
|
|
102
|
+
: ''
|
|
103
|
+
const accountDir = cfg.ntDbPath
|
|
104
|
+
? dirname(dirname(dirname(cfg.ntDbPath)))
|
|
74
105
|
: ''
|
|
75
106
|
|
|
76
107
|
// Resolve display name for filename (prefer remark/nickname over wxid)
|
|
@@ -85,48 +116,82 @@ export class ExportService {
|
|
|
85
116
|
|
|
86
117
|
const args: string[] = [
|
|
87
118
|
script,
|
|
88
|
-
|
|
89
|
-
'--
|
|
90
|
-
'--salt', salt,
|
|
91
|
-
'--talker', talker,
|
|
92
|
-
'--out', outputDir || `./output`,
|
|
93
|
-
'--single',
|
|
94
|
-
'--parts', '1',
|
|
119
|
+
// Paginate: a whole year of chat in one file makes the browser crawl.
|
|
120
|
+
'--per-page', '100',
|
|
95
121
|
]
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const { stdout } = await execFileAsync('python', args, {
|
|
105
|
-
timeout: 300_000,
|
|
122
|
+
const ownWxid = String(cfg.wxid || '').trim()
|
|
123
|
+
const exportCacheDir = cacheDir && existsSync(cacheDir) ? cacheDir : undefined
|
|
124
|
+
const exportAccountDir = accountDir && existsSync(accountDir) ? accountDir : undefined
|
|
125
|
+
if (!quiet) console.log(` Exporting HTML via Python...`)
|
|
126
|
+
const { stdout } = await execFileAsync(getPythonCommand(), args, {
|
|
127
|
+
// Headroom for the opt-in --full-images path, which resizes hundreds
|
|
128
|
+
// of multi-MB originals and can take several minutes.
|
|
129
|
+
timeout: 900_000,
|
|
106
130
|
maxBuffer: 50 * 1024 * 1024,
|
|
131
|
+
env: createPythonProcessEnv({
|
|
132
|
+
WEFLOW_DB_PATH: db,
|
|
133
|
+
WEFLOW_NT_KEY: key,
|
|
134
|
+
WEFLOW_NT_SALT: salt,
|
|
135
|
+
WEFLOW_TALKER: talker,
|
|
136
|
+
WEFLOW_OWN_WXID: ownWxid || undefined,
|
|
137
|
+
WEFLOW_NT_PASSPHRASE: passphrase || undefined,
|
|
138
|
+
WEFLOW_EXPORT_NAME: displayName || undefined,
|
|
139
|
+
WEFLOW_EXPORT_OUTPUT: outputDir || './output',
|
|
140
|
+
WEFLOW_EXPORT_CACHE_DIR: exportCacheDir,
|
|
141
|
+
WEFLOW_EXPORT_ACCOUNT_DIR: exportAccountDir,
|
|
142
|
+
// Lets custom stickers be decrypted from WeChat's local cache.
|
|
143
|
+
WEFLOW_EMOTICON_SEED: String(cfg.emoticonSeed || '') || undefined,
|
|
144
|
+
// Group rows identify their sender only by wxid; the contact
|
|
145
|
+
// database is what turns that into a name.
|
|
146
|
+
WEFLOW_CONTACT_DB_PATH: cfg.contactDbPath || undefined,
|
|
147
|
+
WEFLOW_CONTACT_KEY: cfg.contactKey || undefined,
|
|
148
|
+
WEFLOW_CONTACT_SALT: cfg.contactSalt || undefined,
|
|
149
|
+
// Off by default: originals are 5-24MB each and downscaling a few
|
|
150
|
+
// hundred of them costs minutes in PIL.
|
|
151
|
+
WEFLOW_FULL_IMAGES: options.fullImages ? '1' : undefined,
|
|
152
|
+
WEFLOW_EXPORT_DATE: date || undefined,
|
|
153
|
+
}),
|
|
107
154
|
})
|
|
108
155
|
|
|
156
|
+
// Surface Python's progress and diagnostics. Only the trailing JSON
|
|
157
|
+
// summary is machine-read; everything else used to be discarded, so a
|
|
158
|
+
// multi-minute export showed nothing between "Exporting..." and the
|
|
159
|
+
// result - and the sticker seed hint never reached the user at all.
|
|
160
|
+
if (!quiet) {
|
|
161
|
+
for (const raw of stdout.split('\n')) {
|
|
162
|
+
const line = raw.trim()
|
|
163
|
+
if (!line || line.startsWith('{') || line.startsWith('Total messages:')) continue
|
|
164
|
+
if (/^Fetched \d+\/\d+/.test(line)) continue
|
|
165
|
+
console.log(` ${line}`)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
109
169
|
// Parse JSON result from Python output
|
|
110
170
|
const lines = stdout.split('\n').filter((l: string) => l.trim())
|
|
111
171
|
for (let i = lines.length - 1; i >= 0; i--) {
|
|
112
172
|
try {
|
|
113
173
|
const result = JSON.parse(lines[i])
|
|
114
174
|
if (result.success && result.files?.length > 0) {
|
|
115
|
-
|
|
175
|
+
const total = Number(result.total)
|
|
176
|
+
return { success: true, path: result.files[0], count: Number.isFinite(total) ? total : undefined }
|
|
116
177
|
}
|
|
117
178
|
} catch {}
|
|
118
179
|
}
|
|
119
180
|
|
|
120
181
|
return { success: false, error: 'Python export succeeded but no output found' }
|
|
121
182
|
} catch (e: any) {
|
|
122
|
-
|
|
183
|
+
const failure = safeSubprocessError(e, 'Python export failed')
|
|
184
|
+
if (!quiet) console.error(` ${failure}`)
|
|
185
|
+
if (date) {
|
|
186
|
+
return { success: false, error: `指定日期导出失败: ${failure}` }
|
|
187
|
+
}
|
|
123
188
|
// Fallback to basic HTML
|
|
124
|
-
return this.exportHtmlBasic(talker, outputDir, limit)
|
|
189
|
+
return this.exportHtmlBasic(talker, outputDir, limit, from, to)
|
|
125
190
|
}
|
|
126
191
|
}
|
|
127
192
|
|
|
128
|
-
private async exportHtmlBasic(talker: string, outputDir: string, limit = 0
|
|
129
|
-
const messages = await chatService.
|
|
193
|
+
private async exportHtmlBasic(talker: string, outputDir: string, limit = 0, from?: number, to?: number): Promise<ExportResult> {
|
|
194
|
+
const messages = await chatService.getMessagesInRange(talker, limit, from, to)
|
|
130
195
|
if (messages.length === 0) {
|
|
131
196
|
return { success: false, error: '未找到消息' }
|
|
132
197
|
}
|
|
@@ -135,13 +200,13 @@ export class ExportService {
|
|
|
135
200
|
const dir = this.ensureOutputDir(outputDir)
|
|
136
201
|
const filePath = join(dir, `${talker}_messages.html`)
|
|
137
202
|
writeFileSync(filePath, html, 'utf8')
|
|
138
|
-
return { success: true, path: filePath }
|
|
203
|
+
return { success: true, path: filePath, count: messages.length }
|
|
139
204
|
}
|
|
140
205
|
|
|
141
|
-
async exportExcel(talker: string, outputDir: string, limit = 0
|
|
206
|
+
async exportExcel(talker: string, outputDir: string, limit = 0, from?: number, to?: number): Promise<ExportResult> {
|
|
142
207
|
try {
|
|
143
208
|
const ExcelJS = await import('exceljs')
|
|
144
|
-
const messages = await chatService.
|
|
209
|
+
const messages = await chatService.getMessagesInRange(talker, limit, from, to)
|
|
145
210
|
if (messages.length === 0) {
|
|
146
211
|
return { success: false, error: '未找到消息' }
|
|
147
212
|
}
|
|
@@ -170,9 +235,9 @@ export class ExportService {
|
|
|
170
235
|
const dir = this.ensureOutputDir(outputDir)
|
|
171
236
|
const filePath = join(dir, `${talker}_messages.xlsx`)
|
|
172
237
|
await workbook.xlsx.writeFile(filePath)
|
|
173
|
-
return { success: true, path: filePath }
|
|
174
|
-
} catch
|
|
175
|
-
return { success: false, error:
|
|
238
|
+
return { success: true, path: filePath, count: messages.length }
|
|
239
|
+
} catch {
|
|
240
|
+
return { success: false, error: 'Excel 导出失败' }
|
|
176
241
|
}
|
|
177
242
|
}
|
|
178
243
|
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* init 密钥派生服务 — 较新微信版本(实测 4.1.12.55)密钥体系适配。
|
|
3
|
+
*
|
|
4
|
+
* 新版微信进程内存中不再出现 x'<key><salt>' 文本, 内存扫描匹配不到任何密钥;
|
|
5
|
+
* 但 DLL hook 仍能拿到全库 passphrase, 各库密钥改为本地派生:
|
|
6
|
+
* raw_key = PBKDF2-HMAC-SHA512(passphrase, 库文件前16字节盐, 256000轮, 32字节)
|
|
7
|
+
* 派生后逐库用 sqlcipher 真实打开验证, 通过才写入配置。
|
|
8
|
+
*/
|
|
9
|
+
import chalk from 'chalk'
|
|
10
|
+
import { existsSync } from 'fs'
|
|
11
|
+
import { NtCore } from '../core/ntCore.js'
|
|
12
|
+
import { configService } from './configService.js'
|
|
13
|
+
|
|
14
|
+
export interface NtDbEntry {
|
|
15
|
+
path: string
|
|
16
|
+
name: string
|
|
17
|
+
salt: string
|
|
18
|
+
size: number
|
|
19
|
+
wxid: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type Log = (line: string) => void
|
|
23
|
+
|
|
24
|
+
/** 从已配置的 NT 主库路径推导 favorite.db 路径 */
|
|
25
|
+
export function detectFavDbPath(): string | null {
|
|
26
|
+
const ntDbPath = configService.get('ntDbPath')
|
|
27
|
+
if (!ntDbPath) return null
|
|
28
|
+
const parts = ntDbPath.replace(/\\/g, '/').split('/')
|
|
29
|
+
const idx = parts.lastIndexOf('db_storage')
|
|
30
|
+
if (idx < 0) return null
|
|
31
|
+
const favPath = [...parts.slice(0, idx), 'db_storage', 'favorite', 'favorite.db'].join('/')
|
|
32
|
+
return existsSync(favPath) ? favPath : null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 派生各 NT 库密钥并验证写入配置 (4.1.12+ 派生模式)。
|
|
37
|
+
* @returns message_0.db 是否成功配置 (聊天主路径可用)
|
|
38
|
+
*/
|
|
39
|
+
export async function applyDerivedNtKeys(passphrase: string, databases: NtDbEntry[], log: Log = () => {}): Promise<boolean> {
|
|
40
|
+
const targets = [
|
|
41
|
+
{ name: 'message/message_0.db', pathKey: 'ntDbPath', keyKey: 'ntKey', saltKey: 'ntSalt', label: '聊天主库' },
|
|
42
|
+
{ name: 'contact/contact.db', pathKey: 'contactDbPath', keyKey: 'contactKey', saltKey: 'contactSalt', label: '联系人库' },
|
|
43
|
+
{ name: 'sns/sns.db', pathKey: 'snsDbPath', keyKey: 'snsKey', saltKey: 'snsSalt', label: '朋友圈库' },
|
|
44
|
+
] as const
|
|
45
|
+
let messageSaved = false
|
|
46
|
+
let anySaved = false
|
|
47
|
+
for (const t of targets) {
|
|
48
|
+
const db = databases.find((d) => d.name === t.name)
|
|
49
|
+
if (!db) continue
|
|
50
|
+
const derivedKey = NtCore.deriveRawKey(passphrase, db.salt)
|
|
51
|
+
const verify = await NtCore.verifyDbKey(db.path, derivedKey, db.salt)
|
|
52
|
+
if (verify.success || verify.cannotVerify) {
|
|
53
|
+
// cannotVerify = Python/sqlcipher3 环境问题而非密钥错误: 公式确定性派生, 值仍写入
|
|
54
|
+
configService.set(t.pathKey, db.path)
|
|
55
|
+
configService.set(t.keyKey, derivedKey)
|
|
56
|
+
configService.set(t.saltKey, db.salt)
|
|
57
|
+
const note = verify.success ? '派生密钥已验证' : `已写入但未能验证 (${verify.error})`
|
|
58
|
+
log(chalk.green(` ✓ ${t.label}: ${db.name} (${(db.size / 1024 / 1024).toFixed(1)}MB, ${note})`))
|
|
59
|
+
if (t.name === 'message/message_0.db') messageSaved = true
|
|
60
|
+
anySaved = true
|
|
61
|
+
} else {
|
|
62
|
+
log(chalk.red(` ✗ ${t.label}: 派生密钥验证失败 (${verify.error || '密钥不匹配'})`))
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (anySaved) await enableFavorites(passphrase, databases, log)
|
|
66
|
+
return messageSaved
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** 启用收藏功能: 验证 passphrase 能派生打开 favorite.db 后写入配置 */
|
|
70
|
+
export async function enableFavorites(passphrase: string, databases: NtDbEntry[], log: Log = () => {}): Promise<void> {
|
|
71
|
+
if (configService.get('favPassphrase')) return
|
|
72
|
+
const favDb = databases.find((d) => d.name === 'favorite/favorite.db')
|
|
73
|
+
const favPath = favDb?.path || detectFavDbPath()
|
|
74
|
+
if (!favPath) return
|
|
75
|
+
if (favDb) {
|
|
76
|
+
const derived = NtCore.deriveRawKey(passphrase, favDb.salt)
|
|
77
|
+
const v = await NtCore.verifyDbKey(favPath, derived, favDb.salt)
|
|
78
|
+
if (!v.success && !v.cannotVerify) return
|
|
79
|
+
}
|
|
80
|
+
configService.set('favDbPath', favPath)
|
|
81
|
+
configService.set('favPassphrase', passphrase)
|
|
82
|
+
log(chalk.green(' ✓ 收藏功能已启用 (favorite.db passphrase 已配置)'))
|
|
83
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Message } from '../types.js'
|
|
2
|
+
|
|
3
|
+
export type WeFlowMessageType =
|
|
4
|
+
| 'text' | 'image' | 'voice' | 'card' | 'video' | 'emoji'
|
|
5
|
+
| 'location' | 'link' | 'call' | 'system' | 'quote' | 'other'
|
|
6
|
+
|
|
7
|
+
const MESSAGE_TYPE_NAMES: Record<number, WeFlowMessageType> = {
|
|
8
|
+
1: 'text',
|
|
9
|
+
3: 'image',
|
|
10
|
+
34: 'voice',
|
|
11
|
+
42: 'card',
|
|
12
|
+
43: 'video',
|
|
13
|
+
47: 'emoji',
|
|
14
|
+
48: 'location',
|
|
15
|
+
49: 'link',
|
|
16
|
+
50: 'call',
|
|
17
|
+
10000: 'system',
|
|
18
|
+
10002: 'quote',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface WeFlowMessageEnvelope {
|
|
22
|
+
schema: 'weflow-message/v1'
|
|
23
|
+
source: 'weflow-cli'
|
|
24
|
+
generatedAt: string
|
|
25
|
+
coverage?: WeFlowMessageCoverage
|
|
26
|
+
messages: Array<Message & { messageType: WeFlowMessageType }>
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface WeFlowMessageCoverage {
|
|
30
|
+
requestedFrom?: number
|
|
31
|
+
requestedTo?: number
|
|
32
|
+
requestedLimit: number
|
|
33
|
+
returned: number
|
|
34
|
+
mayHaveMore: boolean
|
|
35
|
+
oldestCreateTime?: number
|
|
36
|
+
newestCreateTime?: number
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function toWeFlowMessages(messages: Message[]): WeFlowMessageEnvelope['messages'] {
|
|
40
|
+
return messages.map((message) => ({
|
|
41
|
+
...message,
|
|
42
|
+
messageType: MESSAGE_TYPE_NAMES[message.localType] || 'other',
|
|
43
|
+
}))
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function createWeFlowEnvelope(
|
|
47
|
+
messages: Message[],
|
|
48
|
+
generatedAt = new Date().toISOString(),
|
|
49
|
+
coverage?: Omit<WeFlowMessageCoverage, 'returned' | 'oldestCreateTime' | 'newestCreateTime' | 'mayHaveMore'>,
|
|
50
|
+
): WeFlowMessageEnvelope {
|
|
51
|
+
let oldestCreateTime: number | undefined
|
|
52
|
+
let newestCreateTime: number | undefined
|
|
53
|
+
for (const message of messages) {
|
|
54
|
+
const timestamp = Number(message.createTime)
|
|
55
|
+
if (!Number.isFinite(timestamp)) continue
|
|
56
|
+
oldestCreateTime = oldestCreateTime === undefined ? timestamp : Math.min(oldestCreateTime, timestamp)
|
|
57
|
+
newestCreateTime = newestCreateTime === undefined ? timestamp : Math.max(newestCreateTime, timestamp)
|
|
58
|
+
}
|
|
59
|
+
const result: WeFlowMessageEnvelope = {
|
|
60
|
+
schema: 'weflow-message/v1',
|
|
61
|
+
source: 'weflow-cli',
|
|
62
|
+
generatedAt,
|
|
63
|
+
messages: toWeFlowMessages(messages),
|
|
64
|
+
}
|
|
65
|
+
if (coverage) {
|
|
66
|
+
result.coverage = {
|
|
67
|
+
...coverage,
|
|
68
|
+
returned: messages.length,
|
|
69
|
+
mayHaveMore: coverage.requestedLimit > 0 && messages.length >= coverage.requestedLimit,
|
|
70
|
+
...(oldestCreateTime !== undefined ? {
|
|
71
|
+
oldestCreateTime,
|
|
72
|
+
newestCreateTime,
|
|
73
|
+
} : {}),
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return result
|
|
77
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { Message } from '../types.js'
|
|
2
|
+
|
|
3
|
+
export interface MessageTimeRange {
|
|
4
|
+
from?: number
|
|
5
|
+
to?: number
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function collectMessagesInRange(
|
|
9
|
+
fetchPage: (limit: number, offset: number) => Promise<Message[]>,
|
|
10
|
+
limit: number,
|
|
11
|
+
range: MessageTimeRange,
|
|
12
|
+
pageSize = 500,
|
|
13
|
+
): Promise<Message[]> {
|
|
14
|
+
const result: Message[] = []
|
|
15
|
+
let offset = 0
|
|
16
|
+
|
|
17
|
+
while (true) {
|
|
18
|
+
const page = await fetchPage(pageSize, offset)
|
|
19
|
+
if (page.length === 0) break
|
|
20
|
+
|
|
21
|
+
let oldestTimestamp = Number.POSITIVE_INFINITY
|
|
22
|
+
for (const message of page) {
|
|
23
|
+
const timestamp = Number(message.createTime)
|
|
24
|
+
if (!Number.isFinite(timestamp)) continue
|
|
25
|
+
oldestTimestamp = Math.min(oldestTimestamp, timestamp)
|
|
26
|
+
if (range.from !== undefined && timestamp < range.from) continue
|
|
27
|
+
if (range.to !== undefined && timestamp > range.to) continue
|
|
28
|
+
result.push(message)
|
|
29
|
+
if (limit > 0 && result.length >= limit) return result
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
offset += page.length
|
|
33
|
+
if (page.length < pageSize) break
|
|
34
|
+
if (range.from !== undefined && oldestTimestamp < range.from) break
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return result
|
|
38
|
+
}
|