@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 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 & Intel |
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
- sudo wxecho keys
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 (requires sudo) |
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) running with `sudo`; (2) the app has been re-signed; (3) the app is running and logged in.
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
- exec node "$NPM_PREFIX/lib/node_modules/wxecho/dist/cli.js" "$@"
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 "$ROOT_DIR/dist/cli.js" ]; then
17
- exec node "$ROOT_DIR/dist/cli.js" "$@"
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 __filename = fileURLToPath(import.meta.url);
16
- var __dirname = dirname(__filename);
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(cmd) {
39
+ function runExport(name) {
41
40
  const args = [];
42
- const opts = cmd.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: execSync2 } = await import("child_process");
193
+ const { execSync: execSync3 } = await import("child_process");
196
194
  const safePath = outPath.replace(/'/g, "'\\''");
197
- const output = execSync2(
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
- var __filename2 = fileURLToPath3(import.meta.url);
346
- var __dirname2 = dirname4(__filename2);
347
- var PY_DIR2 = path3.resolve(__dirname2, "../../py");
348
- async function runKeys(options) {
349
- const outputFile = options.output || "all_keys.json";
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 compile = spawn2("cc", [
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 = execSync(check.cmd, { stdio: "pipe" }).toString().trim();
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 = execSync(check.cmd, { stdio: "pipe" }).toString().trim();
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.0",
3
+ "version": "1.0.2",
4
4
  "description": "macOS native chat history export tool with local database decryption support",
5
- "keywords": ["macos", "chat-export", "decrypt", "sqlite", "wxecho", "database", "cli"],
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.21.0",
47
+ "esbuild": "^0.28.0",
40
48
  "tsx": "^4.15.0",
41
49
  "typescript": "^5.4.0"
42
50
  },
Binary file