wechat-sender.js
3.73 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
/**
* wechat-sender.js — 通过 LSPosed 模块发送微信消息
*
* 依赖:手机已安装 LSPosed 模块 + HermesWeChatSendMsg + 微信作用域已启用
* 原理:adb shell content insert → 模块 Provider → 广播 → WeChat 内部 API
*/
const { spawn } = require('child_process');
const path = require('path');
// adb 路径:优先使用项目内平台工具,否则从 PATH 查找
const ADB = process.env.ADB_PATH || (() => {
try {
// 尝试使用 lemonPCRM 项目里的 adb
const projectAdb = path.resolve(__dirname, '../../../../platform-tools/adb');
require('fs').accessSync(projectAdb, require('fs').constants.X_OK);
return projectAdb;
} catch {
return 'adb';
}
})();
const PROVIDER_URI = 'content://com.hermes.wechat.xposed.provider/send';
const STATUS_URI = 'content://com.hermes.wechat.xposed.provider/status';
/**
* 执行 adb shell 命令,返回 Promise<string>
*/
function adbShell(cmd, timeoutMs = 15000) {
return new Promise((resolve, reject) => {
const args = cmd.startsWith('shell ') ? cmd.split(' ') : ['shell', ...cmd.split(' ')];
const proc = spawn(ADB, args, { timeout: timeoutMs });
let stdout = '';
let stderr = '';
proc.stdout.on('data', d => stdout += d.toString());
proc.stderr.on('data', d => stderr += d.toString());
proc.on('close', code => {
if (code !== 0) {
reject(new Error(`adb exit ${code}: ${stderr.trim() || stdout.trim()}`));
} else {
resolve(stdout.trim());
}
});
proc.on('error', reject);
});
}
/**
* 发送文本消息
* @param {string} toUser — 接收者 wxid / filehelper / 群聊ID
* @param {string} content — 消息内容
* @returns {Promise<{ok: boolean, uri?: string, error?: string}>}
*/
async function sendTextMessage(toUser, content) {
if (!toUser || !content) {
return { ok: false, error: 'toUser and content are required' };
}
// 转义单引号防止 shell 注入
const safeToUser = toUser.replace(/'/g, "'\\''");
const safeContent = content.replace(/'/g, "'\\''");
const cmd = `content insert --uri ${PROVIDER_URI} --bind toUser:s:'${safeToUser}' --bind content:s:'${safeContent}'`;
try {
const out = await adbShell(cmd, 10000);
// adb content insert 成功时输出类似:content://com.hermes.wechat.xposed.provider/send/0
if (out.includes(PROVIDER_URI)) {
return { ok: true, uri: out };
}
return { ok: false, error: `unexpected output: ${out}` };
} catch (e) {
return { ok: false, error: e.message };
}
}
/**
* 查询最近一次发送结果(简单轮询用)
* @returns {Promise<{status: string}>}
*/
async function queryStatus() {
try {
const out = await adbShell(`content query --uri ${STATUS_URI}`, 5000);
// 输出格式:Row: status=pending / true / false
const m = out.match(/status=(\w+)/);
return { status: m ? m[1] : 'unknown' };
} catch (e) {
return { status: 'error', error: e.message };
}
}
/**
* 批量发送(带间隔,避免被风控)
* @param {Array<{toUser: string, content: string}>} messages
* @param {number} delayMs — 每条消息间隔,默认 2000ms
*/
async function sendBatch(messages, delayMs = 2000) {
const results = [];
for (const msg of messages) {
const r = await sendTextMessage(msg.toUser, msg.content);
results.push({ ...msg, ...r });
if (delayMs > 0) await sleep(delayMs);
}
return results;
}
function sleep(ms) {
return new Promise(r => setTimeout(r, ms));
}
module.exports = {
sendTextMessage,
queryStatus,
sendBatch,
adbShell,
ADB,
};