/** * 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 */ 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, };