@walkerch/wxecho 1.0.0 → 1.0.2
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/README.md +4 -5
- package/bin/wxecho +5 -4
- package/dist/cli.js +32 -19
- package/package.json +11 -3
- package/py/find_all_keys_macos +0 -0
package/README.md
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
| 📤 **Multi-format Export** | Export to TXT / CSV / JSON formats |
|
|
38
38
|
| 🔍 **Fuzzy Search** | Search contacts by nickname or remarks |
|
|
39
39
|
| 💬 **Group Chat Support** | Full support for group conversations |
|
|
40
|
-
| 🍎 **Native macOS** | Built with Mach VM API, runs natively on Apple Silicon
|
|
40
|
+
| 🍎 **Native macOS** | Built with Mach VM API, runs natively on Apple Silicon |
|
|
41
41
|
|
|
42
42
|
</div>
|
|
43
43
|
|
|
@@ -52,7 +52,6 @@
|
|
|
52
52
|
- macOS 11+ on **Apple Silicon** (M1/M2/M3/M4...)
|
|
53
53
|
- Desktop app 4.x (logged in, chat history synced)
|
|
54
54
|
- Xcode Command Line Tools: `xcode-select --install`
|
|
55
|
-
- Python 3.8+: `pip install pycryptodome`
|
|
56
55
|
|
|
57
56
|
</div>
|
|
58
57
|
|
|
@@ -79,7 +78,7 @@ sudo codesign --force --deep --sign - /Applications/WeChat.app
|
|
|
79
78
|
# Re-open and log in
|
|
80
79
|
|
|
81
80
|
# Step 2: Extract Keys
|
|
82
|
-
|
|
81
|
+
wxecho keys
|
|
83
82
|
|
|
84
83
|
# Step 3: Decrypt Databases
|
|
85
84
|
wxecho decrypt
|
|
@@ -112,7 +111,7 @@ The app uses [WCDB](https://github.com/nicklockwood/wcdb) (based on SQLCipher 4)
|
|
|
112
111
|
|
|
113
112
|
| Command | Description |
|
|
114
113
|
|---------|-------------|
|
|
115
|
-
| `wxecho keys` | Extract database keys from running process
|
|
114
|
+
| `wxecho keys` | Extract database keys from running process |
|
|
116
115
|
| `wxecho decrypt` | Decrypt local databases |
|
|
117
116
|
| `wxecho export [options]` | Export chat history |
|
|
118
117
|
| `wxecho doctor` | Check environment dependencies |
|
|
@@ -163,7 +162,7 @@ Messages for each contact/group are stored in tables named `Msg_<md5(username)>`
|
|
|
163
162
|
<h2 align="center">❓ FAQ</h2>
|
|
164
163
|
|
|
165
164
|
**Q: `task_for_pid failed` — what to do?**
|
|
166
|
-
Make sure: (1)
|
|
165
|
+
Make sure: (1) the app has been re-signed with ad-hoc signature; (2) the app is running and logged in.
|
|
167
166
|
|
|
168
167
|
**Q: Does this work after an app update?**
|
|
169
168
|
Updates restore the original code signature. Re-run the re-sign step.
|
package/bin/wxecho
CHANGED
|
@@ -7,14 +7,15 @@ ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
|
|
7
7
|
|
|
8
8
|
# Try npm global prefix approach (most reliable for global installs)
|
|
9
9
|
if NPM_PREFIX=$(npm prefix -g 2>/dev/null); then
|
|
10
|
-
if [ -f "$NPM_PREFIX/lib/node_modules/wxecho/dist/cli.js" ]; then
|
|
11
|
-
|
|
10
|
+
if [ -f "$NPM_PREFIX/lib/node_modules/@walkerch/wxecho/dist/cli.js" ]; then
|
|
11
|
+
PKG_ROOT="$NPM_PREFIX/lib/node_modules/@walkerch/wxecho"
|
|
12
|
+
exec env WXECHO_ROOT="$PKG_ROOT" node "$PKG_ROOT/dist/cli.js" "$@"
|
|
12
13
|
fi
|
|
13
14
|
fi
|
|
14
15
|
|
|
15
16
|
# For local development, check if pre-built dist exists
|
|
16
|
-
if [ -f "$
|
|
17
|
-
exec node "$
|
|
17
|
+
if [ -f "$SCRIPT_DIR/../dist/cli.js" ]; then
|
|
18
|
+
exec env WXECHO_ROOT="$SCRIPT_DIR" node "$SCRIPT_DIR/../dist/cli.js" "$@"
|
|
18
19
|
fi
|
|
19
20
|
|
|
20
21
|
# Fallback: use npx tsx to run TypeScript directly
|
package/dist/cli.js
CHANGED
|
@@ -12,9 +12,8 @@ import { spawn } from "child_process";
|
|
|
12
12
|
import path from "path";
|
|
13
13
|
import { fileURLToPath } from "url";
|
|
14
14
|
import { dirname } from "path";
|
|
15
|
-
var
|
|
16
|
-
var
|
|
17
|
-
var PY_DIR = path.resolve(__dirname, "../../py");
|
|
15
|
+
var PKG_ROOT = process.env.WXECHO_ROOT ? path.resolve(process.env.WXECHO_ROOT) : path.resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
16
|
+
var PY_DIR = path.join(PKG_ROOT, "py");
|
|
18
17
|
function runPythonScript(scriptName, args = []) {
|
|
19
18
|
return new Promise((resolve2, reject) => {
|
|
20
19
|
const scriptPath = path.join(PY_DIR, scriptName);
|
|
@@ -37,10 +36,9 @@ function runPythonScript(scriptName, args = []) {
|
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
// src/commands/export.ts
|
|
40
|
-
function runExport(
|
|
39
|
+
function runExport(name) {
|
|
41
40
|
const args = [];
|
|
42
|
-
const opts =
|
|
43
|
-
const name = cmd.args[0];
|
|
41
|
+
const opts = this.opts();
|
|
44
42
|
if (opts.list) {
|
|
45
43
|
args.push("-l");
|
|
46
44
|
if (opts.top) args.push("--top", opts.top);
|
|
@@ -192,9 +190,9 @@ function getKeyInfo(keys, relPath) {
|
|
|
192
190
|
}
|
|
193
191
|
async function verifySqlite(outPath) {
|
|
194
192
|
try {
|
|
195
|
-
const { execSync:
|
|
193
|
+
const { execSync: execSync3 } = await import("child_process");
|
|
196
194
|
const safePath = outPath.replace(/'/g, "'\\''");
|
|
197
|
-
const output =
|
|
195
|
+
const output = execSync3(
|
|
198
196
|
`python3 -c "import sqlite3; conn=sqlite3.connect('${safePath}'); print(','.join([r[0] for r in conn.execute('SELECT name FROM sqlite_master WHERE type=\\'table\\'').fetchall()]))"`,
|
|
199
197
|
{ encoding: "utf-8" }
|
|
200
198
|
).trim();
|
|
@@ -337,16 +335,17 @@ async function runDecryptCmd() {
|
|
|
337
335
|
}
|
|
338
336
|
|
|
339
337
|
// src/commands/keys.ts
|
|
340
|
-
import { spawn as spawn2 } from "child_process";
|
|
338
|
+
import { spawn as spawn2, execSync } from "child_process";
|
|
341
339
|
import path3 from "path";
|
|
342
340
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
343
341
|
import { dirname as dirname4 } from "path";
|
|
344
342
|
import fs2 from "fs";
|
|
345
|
-
|
|
346
|
-
var
|
|
347
|
-
var PY_DIR2 = path3.
|
|
348
|
-
async function runKeys(
|
|
349
|
-
const
|
|
343
|
+
import os from "os";
|
|
344
|
+
var PKG_ROOT2 = process.env.WXECHO_ROOT ? path3.resolve(process.env.WXECHO_ROOT) : path3.resolve(dirname4(fileURLToPath3(import.meta.url)), "../..");
|
|
345
|
+
var PY_DIR2 = path3.join(PKG_ROOT2, "py");
|
|
346
|
+
async function runKeys() {
|
|
347
|
+
const opts = this.opts();
|
|
348
|
+
const outputFile = opts.output || path3.join(PY_DIR2, "all_keys.json");
|
|
350
349
|
const binaryPath = path3.join(PY_DIR2, "find_all_keys_macos");
|
|
351
350
|
console.log("\u6B63\u5728\u4ECE\u5FAE\u4FE1\u8FDB\u7A0B\u63D0\u53D6\u5BC6\u94A5...\n");
|
|
352
351
|
console.log("\u6CE8\u610F\uFF1A\u6B64\u64CD\u4F5C\u9700\u8981\uFF1A");
|
|
@@ -358,9 +357,21 @@ async function runKeys(options) {
|
|
|
358
357
|
}
|
|
359
358
|
await extractKeys(binaryPath, outputFile);
|
|
360
359
|
}
|
|
360
|
+
function findCC() {
|
|
361
|
+
try {
|
|
362
|
+
execSync("cc --version", { stdio: "pipe" });
|
|
363
|
+
const ccPath = execSync("xcrun --find cc", { encoding: "utf8" }).trim();
|
|
364
|
+
return fs2.realpathSync(ccPath);
|
|
365
|
+
} catch {
|
|
366
|
+
throw new Error(
|
|
367
|
+
"\u672A\u627E\u5230 C \u7F16\u8BD1\u5668 (cc)\u3002\n\n\u8BF7\u5148\u5B89\u88C5 Xcode Command Line Tools:\n xcode-select --install\n\n\u5B89\u88C5\u5B8C\u6210\u540E\u91CD\u65B0\u8FD0\u884C wxecho keys"
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
361
371
|
function compileBinary() {
|
|
362
372
|
return new Promise((resolve2, reject) => {
|
|
363
|
-
const
|
|
373
|
+
const ccPath = findCC();
|
|
374
|
+
const compile = spawn2(ccPath, [
|
|
364
375
|
"-O2",
|
|
365
376
|
"-o",
|
|
366
377
|
path3.join(PY_DIR2, "find_all_keys_macos"),
|
|
@@ -381,9 +392,11 @@ function compileBinary() {
|
|
|
381
392
|
}
|
|
382
393
|
function extractKeys(binaryPath, outputFile) {
|
|
383
394
|
return new Promise((resolve2, reject) => {
|
|
395
|
+
const realHome = os.userInfo().homedir;
|
|
384
396
|
const child = spawn2("sudo", [binaryPath], {
|
|
385
397
|
cwd: PY_DIR2,
|
|
386
|
-
stdio: "inherit"
|
|
398
|
+
stdio: "inherit",
|
|
399
|
+
env: { ...process.env, HOME: realHome, SUDO_USER: path3.basename(realHome) }
|
|
387
400
|
});
|
|
388
401
|
child.on("close", (code) => {
|
|
389
402
|
if (code === 0) {
|
|
@@ -399,7 +412,7 @@ function extractKeys(binaryPath, outputFile) {
|
|
|
399
412
|
}
|
|
400
413
|
|
|
401
414
|
// src/utils/doctor.ts
|
|
402
|
-
import { execSync } from "child_process";
|
|
415
|
+
import { execSync as execSync2 } from "child_process";
|
|
403
416
|
async function doctor() {
|
|
404
417
|
console.log("\u6B63\u5728\u68C0\u6D4B\u73AF\u5883\u4F9D\u8D56...\n");
|
|
405
418
|
const checks = [
|
|
@@ -414,7 +427,7 @@ async function doctor() {
|
|
|
414
427
|
let allPassed = true;
|
|
415
428
|
for (const check of checks) {
|
|
416
429
|
try {
|
|
417
|
-
const output =
|
|
430
|
+
const output = execSync2(check.cmd, { stdio: "pipe" }).toString().trim();
|
|
418
431
|
console.log(`\u2713 ${check.name}: ${output}`);
|
|
419
432
|
} catch {
|
|
420
433
|
if (check.required) {
|
|
@@ -427,7 +440,7 @@ async function doctor() {
|
|
|
427
440
|
}
|
|
428
441
|
for (const check of infoChecks) {
|
|
429
442
|
try {
|
|
430
|
-
const output =
|
|
443
|
+
const output = execSync2(check.cmd, { stdio: "pipe" }).toString().trim();
|
|
431
444
|
console.log(`\u2139 ${check.name}: ${output}`);
|
|
432
445
|
} catch {
|
|
433
446
|
console.log(`\u2139 ${check.name}: \u65E0\u6CD5\u68C0\u6D4B`);
|
package/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@walkerch/wxecho",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "macOS native chat history export tool with local database decryption support",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"macos",
|
|
7
|
+
"chat-export",
|
|
8
|
+
"decrypt",
|
|
9
|
+
"sqlite",
|
|
10
|
+
"wxecho",
|
|
11
|
+
"database",
|
|
12
|
+
"cli"
|
|
13
|
+
],
|
|
6
14
|
"license": "MIT",
|
|
7
15
|
"author": "Xinhai Chang <changxinhai@pku.edu.cn>",
|
|
8
16
|
"repository": {
|
|
@@ -36,7 +44,7 @@
|
|
|
36
44
|
},
|
|
37
45
|
"devDependencies": {
|
|
38
46
|
"@types/node": "^20.14.0",
|
|
39
|
-
"esbuild": "^0.
|
|
47
|
+
"esbuild": "^0.28.0",
|
|
40
48
|
"tsx": "^4.15.0",
|
|
41
49
|
"typescript": "^5.4.0"
|
|
42
50
|
},
|
package/py/find_all_keys_macos
DELETED
|
Binary file
|