Files
workphone-sdk/sdk/agent/hook/wechat_hook_v2.js

4009 lines
174 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* wechat_hook_v2.js — 微信 Frida Hook 脚本(机擎 SDK v3.0
*
* 全功能微信控制引擎 — 110 个操作 / 24 模块
*
* 模块清单:
* H15 消息接收SQLite insert Hook
* H16 联系人获取DB 直读)
* H17 消息发送(内部方法 / Intent 降级)
* H18 好友请求监听SQLite + BroadcastReceiver
* H19 添加/通过好友XmlParser Hook + Intent
* H20 朋友圈发布SnsService Hook + Intent
* H21 朋友圈浏览/点赞DB + RPC
* H22 群管理(创建/邀请/踢人/公告)
* H23 账号管理(昵称/头像/签名/状态/性别/地区)
* H24 账号安全(解封/改密/绑定手机/登录设备管理)
* H25 支付(红包/转账/收款/零钱)
* H26 二维码(扫码/生成个人码/群码)
* H27 视频号(浏览/点赞/评论/关注)
* H28 标签管理(创建/删除/打标/查询)
* H29 收藏管理(添加/删除/列表)
* H30 设置(隐私/通知/通用/聊天背景)
* H31 搜索(全局搜索/联系人/消息/公众号)
* H32 小程序(打开/最近/收藏)
* H33 文件传输(发送文件/图片/视频/语音)
* H34 消息转发(单条/合并转发)
* H35 注册/登录(新号注册/登录/切号)
* H36 公众号(关注/取关/列表/文章)
* H37 表情管理(发送/收藏/自定义)
* H38 浮窗/多任务
* H39 设备信息
*
* 技术路线Frida 17.x + Java.perform + SQLite Hook + rpc.exports
*
* @version 3.1.0 (版本自适应)
*/
'use strict';
var PLATFORM = 'wechat';
var CONFIG = {
WECHAT_PACKAGE: 'com.tencent.mm',
LOG_LEVEL: 'info',
DB_NAME: 'EnMicroMsg.db',
SNS_DB_NAME: 'SnsMicroMsg.db',
HOOK_SQLITE: true,
HOOK_NETWORK: true,
HOOK_FRIEND_REQUEST: true,
HOOK_MOMENTS: true,
MESSAGE_BUFFER_MAX: 500,
FRIEND_REQUEST_BUFFER_MAX: 100,
};
// ============================================================
// § 0 版本兼容系统 (H24)
// ============================================================
var _versionCompat = null;
var _VERSION_TABLE = {
'8.0.44': {
send_message: [
{ cls: 'com.tencent.mm.modelmulti.h', method: 'b', sig: ['java.lang.String', 'java.lang.String', 'java.lang.String'] }
],
friend_request: [{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.b' }],
sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }],
add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }]
},
'8.0.49': {
send_message: [
{ cls: 'com.tencent.mm.modelmulti.g', method: 'b', sig: ['java.lang.String', 'java.lang.String', 'java.lang.String'] },
{ cls: 'com.tencent.mm.modelmulti.h', method: 'b', sig: ['java.lang.String', 'java.lang.String', 'java.lang.String'] }
],
friend_request: [
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' },
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.b' }
],
sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }],
add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }]
},
'8.0.51': {
send_message: [
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.k', method: 'a', sig: ['java.lang.String', 'java.lang.String'] },
{ cls: 'com.tencent.mm.modelmulti.g', method: 'b', sig: ['java.lang.String', 'java.lang.String', 'java.lang.String'] }
],
friend_request: [{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' }],
sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }],
add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }]
},
'8.0.56': {
send_message: [
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.j', method: 'a', sig: ['java.lang.String', 'java.lang.String'] },
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.k', method: 'a', sig: ['java.lang.String', 'java.lang.String'] }
],
friend_request: [{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' }],
sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }, { cls: 'com.tencent.mm.plugin.sns.ui.SnsTimeLineUI' }],
add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }]
},
'8.0.58': {
send_message: [
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.j', method: 'a', sig: ['java.lang.String', 'java.lang.String'] }
],
friend_request: [{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' }],
sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }],
add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }]
},
'8.0.60': {
send_message: [
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.j', method: 'a', sig: ['java.lang.String', 'java.lang.String'] }
],
friend_request: [{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' }],
sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }],
add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }]
},
'8.0.69': {
send_message: [
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.j', method: 'a', sig: ['java.lang.String', 'java.lang.String'] }
],
friend_request: [{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' }],
sns_upload: [{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' }],
add_friend: [{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' }]
}
};
var _DEFAULT_COMPAT = {
send_message: [
{ cls: 'com.tencent.mm.modelmulti.h', method: 'b', sig: ['java.lang.String', 'java.lang.String', 'java.lang.String'] },
{ cls: 'com.tencent.mm.modelmulti.g', method: 'b', sig: ['java.lang.String', 'java.lang.String', 'java.lang.String'] },
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.k', method: 'a', sig: ['java.lang.String', 'java.lang.String'] },
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.j', method: 'a', sig: ['java.lang.String', 'java.lang.String'] }
],
friend_request: [
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.b' },
{ cls: 'com.tencent.mm.plugin.messenger.foundation.a.c' }
],
sns_upload: [
{ cls: 'com.tencent.mm.plugin.sns.ui.SnsUploadUI' },
{ cls: 'com.tencent.mm.plugin.sns.ui.SnsTimeLineUI' }
],
add_friend: [
{ cls: 'com.tencent.mm.plugin.profile.ui.ContactInfoUI' },
{ cls: 'com.tencent.mm.protocal.protobuf.add$ContactType' }
]
};
function _parseVersion(v) {
var parts = (v || '').split('.').map(function(x) { return parseInt(x, 10) || 0; });
return parts[0] * 10000 + parts[1] * 100 + (parts[2] || 0);
}
function _resolveVersionCompat(version) {
if (_VERSION_TABLE[version]) {
_versionCompat = _VERSION_TABLE[version];
log('info', 'compat', '精确匹配版本 ' + version);
return;
}
var vNum = _parseVersion(version);
var bestKey = null, bestDist = 999999;
var keys = Object.keys(_VERSION_TABLE);
for (var i = 0; i < keys.length; i++) {
var kNum = _parseVersion(keys[i]);
var dist = Math.abs(vNum - kNum);
if (dist < bestDist) { bestDist = dist; bestKey = keys[i]; }
}
if (bestKey && bestDist <= 10) {
_versionCompat = _VERSION_TABLE[bestKey];
log('info', 'compat', '近似匹配 ' + version + ' → ' + bestKey + ' (距离=' + bestDist + ')');
} else {
_versionCompat = _DEFAULT_COMPAT;
log('warn', 'compat', '未匹配版本 ' + version + ',使用 default 全候选');
}
}
function getCompat(key) {
return (_versionCompat && _versionCompat[key]) || (_DEFAULT_COMPAT[key]) || [];
}
// ============================================================
// § 1 工具函数
// ============================================================
var LOG_LEVEL_MAP = { debug: 0, info: 1, warn: 2, error: 3 };
function log(level, tag, message, extra) {
if ((LOG_LEVEL_MAP[level] || 0) < (LOG_LEVEL_MAP[CONFIG.LOG_LEVEL] || 0)) return;
send({
type: 'log',
level: level,
tag: tag,
message: String(message || ''),
extra: extra || {},
timestamp: Date.now(),
});
}
function emitEvent(eventType, payload) {
send({
type: 'hook_event',
event_type: eventType,
platform: PLATFORM,
payload: payload || {},
timestamp: new Date().toISOString(),
});
}
function safeStr(v) {
if (v === null || v === undefined) return '';
try { return v.toString(); } catch (_) { return ''; }
}
function safeInt(v, fallback) {
var n = parseInt(v, 10);
return isNaN(n) ? (fallback || 0) : n;
}
function jStr(s) {
return Java.use('java.lang.String').$new(String(s));
}
function jLong(v) {
var Long = Java.use('java.lang.Long');
var s = String(v === undefined || v === null ? '0' : v);
try {
return Long.parseLong(s);
} catch (_) {
return Long.valueOf(parseInt(s, 10) || 0);
}
}
function getCtx() {
return Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
}
function generateId(prefix) {
return (prefix || 'wh') + '_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6);
}
// ============================================================
// § 2 全局状态
// ============================================================
var _messageBuffer = [];
var _friendRequestBuffer = [];
var _contactCache = [];
var _dbPath = null;
var _snsDbPath = null;
var _wechatVersion = '';
var _hooked = {
sqlite_insert: false,
sqlite_update: false,
network: false,
friend_request: false,
sns_upload: false,
};
// ============================================================
// § 3 数据库通用层
// ============================================================
function _findDbPath(dbName) {
try {
var File = Java.use('java.io.File');
var dir = File.$new('/data/data/com.tencent.mm/MicroMsg/');
if (!dir.exists()) return null;
var subs = dir.listFiles();
if (!subs) return null;
for (var i = 0; i < subs.length; i++) {
var p = subs[i].getAbsolutePath() + '/' + dbName;
if (File.$new(p).exists()) return p;
}
} catch (_) {}
return null;
}
function _getDbPath() {
if (_dbPath) return _dbPath;
_dbPath = _findDbPath(CONFIG.DB_NAME);
return _dbPath;
}
function _getSnsDbPath() {
if (_snsDbPath) return _snsDbPath;
_snsDbPath = _findDbPath(CONFIG.SNS_DB_NAME);
return _snsDbPath;
}
var _lastDbDiag = { ok: false, error: '', path: '', found: false, rows: 0 };
function _emptyReadFailure(kind, diag, force) {
var d = diag || _lastDbDiag || {};
var hasDbError = d && (d.ok === false) && (d.error || d.found === false);
if (!hasDbError && !force) return null;
return {
success: false,
error: kind + '读取为空DB 未打开或查询不稳定',
count: 0,
_diag: d,
};
}
function _execSQL(sql, dbPathOverride) {
var results = [];
_lastDbDiag = { ok: false, error: '', path: '', found: false, rows: 0 };
try {
var path = dbPathOverride || _getDbPath();
_lastDbDiag.path = path || '';
if (!path) { _lastDbDiag.error = 'db_path 未找到'; return results; }
var db = _findOpenWcdb(path);
if (!db) {
_lastDbDiag.error = '未找到已打开的 WCDB 实例';
log('warn', 'db', '未找到已打开的 WCDB 实例: ' + path);
return results;
}
_lastDbDiag.found = true;
// WCDB 1.x: rawQuery(sql, args)2.x core.Database 无 rawQuery → 走 2.x 适配
if (typeof db.rawQuery !== 'function') {
return _execSQL2x(db, sql, results);
}
var cursor = db.rawQuery(sql, null);
while (cursor.moveToNext()) {
var row = {};
for (var c = 0; c < cursor.getColumnCount(); c++) {
row[cursor.getColumnName(c)] = safeStr(cursor.getString(c));
}
results.push(row);
}
cursor.close();
_lastDbDiag.ok = true;
_lastDbDiag.rows = results.length;
} catch (e) {
_lastDbDiag.error = String(e);
log('warn', 'db', 'SQL 执行失败: ' + e);
}
return results;
}
// WCDB 2.x core.Database 原生 SQL 查询适配。
// 2.x 无 rawQuery通过 getHandle() 取 Handle再用 PreparedStatementprepare/step/getText读取。
// 反射发现可用方法,失败则把方法名写入 _lastDbDiag 供下一轮精确实现(不盲写)。
function _execSQL2x(db, sql, results) {
try {
if (typeof db.getHandle !== 'function') {
_lastDbDiag.error = 'WCDB2x: 实例无 getHandle()';
try { _lastDbDiag.db_methods = _reflectMethodNames(db); } catch (_) {}
return results;
}
var handle = db.getHandle();
_lastDbDiag.has_handle = !!handle;
if (!handle || typeof handle.getAllRowsFromSQL !== 'function') {
_lastDbDiag.error = 'WCDB2x: handle 无 getAllRowsFromSQL';
try { _lastDbDiag.handle_methods = _reflectMethodNames(handle); } catch (_) {}
return results;
}
// 从 SELECT 子句解析列名getAllRowsFromSQL 只返回值数组,需自带列名映射)
var colNames = _parseSelectColumns(sql);
_lastDbDiag.used_prepare = 'getAllRowsFromSQL';
_lastDbDiag.parsed_cols = colNames;
// WCDB 2.xgetAllRowsFromSQL(sql) → List<List<Value>>Value.getText() 取文本)
var allRows = handle.getAllRowsFromSQL(sql);
if (allRows) {
var rowCount = (typeof allRows.size === 'function') ? allRows.size() : allRows.length;
for (var ri = 0; ri < rowCount && ri < 2000; ri++) {
var rowVals = (typeof allRows.get === 'function') ? allRows.get(ri) : allRows[ri];
var vcount = (rowVals && typeof rowVals.size === 'function') ? rowVals.size()
: (rowVals && rowVals.length != null ? rowVals.length : 0);
var row = {};
for (var vi = 0; vi < vcount; vi++) {
var v = (typeof rowVals.get === 'function') ? rowVals.get(vi) : rowVals[vi];
var txt = null;
try { if (v && typeof v.getText === 'function') txt = v.getText(); } catch (_) {}
if (txt == null) { try { if (v != null) txt = String(v); } catch (_) {} }
row[colNames[vi] || ('c' + vi)] = safeStr(txt);
}
results.push(row);
}
}
try { if (handle && typeof handle.invalidate === 'function') handle.invalidate(); } catch (_) {}
_lastDbDiag.ok = true;
_lastDbDiag.rows = results.length;
} catch (e) {
_lastDbDiag.error = 'WCDB2x: ' + String(e);
log('warn', 'db', _lastDbDiag.error);
}
return results;
}
// 从 SELECT ... FROM 解析列名/别名,供 getAllRowsFromSQL 的值数组按名映射。
// 仅处理本文件内构造的受控 SQL无嵌套子查询函数逗号尽量取 AS 别名或末段标识符。
function _parseSelectColumns(sql) {
try {
// PRAGMA table_info(X) 列固定getAllRowsFromSQL 不带列名,需手动映射
if (/^\s*pragma\s+table_info/i.test(sql)) {
return ['cid', 'name', 'type', 'notnull', 'dflt_value', 'pk'];
}
var m = /select\s+([\s\S]*?)\s+from\s/i.exec(sql);
if (!m) return [];
var seg = m[1];
if (seg.replace(/\s/g, '') === '*') return [];
return seg.split(',').map(function (part) {
var p = part.trim();
var asm = /\s+as\s+([A-Za-z0-9_]+)\s*$/i.exec(p);
if (asm) return asm[1];
// count(*) AS n 已被上分支处理;无别名时取末段标识符
var idm = /([A-Za-z0-9_]+)\s*$/.exec(p);
return idm ? idm[1] : p;
});
} catch (_) {
return [];
}
}
function _reflectMethodNames(obj) {
var names = [];
try {
var cls = obj.getClass();
var methods = cls.getMethods();
for (var i = 0; i < methods.length && i < 120; i++) {
try { names.push(methods[i].getName()); } catch (_) {}
}
} catch (_) {}
// 去重
var seen = {}, uniq = [];
for (var j = 0; j < names.length; j++) { if (!seen[names[j]]) { seen[names[j]] = 1; uniq.push(names[j]); } }
return uniq;
}
function _reflectMethodSigs(obj, limit) {
var sigs = [];
try {
var cls = obj.getClass();
var methods = cls.getMethods();
var max = limit || 120;
for (var i = 0; i < methods.length && i < max; i++) {
try { sigs.push(safeStr(methods[i].toString())); } catch (_) {}
}
} catch (_) {}
return sigs;
}
function _escapeSql(v) {
return String(v == null ? '' : v).replace(/'/g, "''");
}
/** WCDB 1.x 写库(标签/联系人字段);无 execSQL 时诚实失败 */
function _execSQLWrite(sql, dbPathOverride) {
try {
var path = dbPathOverride || _getDbPath();
if (!path) return { success: false, error: 'db_path 未找到' };
var db = _findOpenWcdb(path);
if (!db) return { success: false, error: '未找到已打开的 WCDB 实例' };
if (typeof db.execSQL !== 'function') {
return { success: false, error: '实例无 execSQL疑 WCDB 2.x标签写库不可用' };
}
db.execSQL(sql);
return { success: true };
} catch (e) {
return { success: false, error: String(e) };
}
}
function _nextLabelId() {
var rows = _execSQL('SELECT MAX(CAST(labelID AS INTEGER)) AS max_id FROM contactlabel');
if (rows.length && rows[0].max_id) {
var n = parseInt(rows[0].max_id, 10);
if (!isNaN(n)) return n + 1;
}
rows = _execSQL('SELECT MAX(CAST(labelId AS INTEGER)) AS max_id FROM contactlabelinfo');
if (rows.length && rows[0].max_id) {
var n2 = parseInt(rows[0].max_id, 10);
if (!isNaN(n2)) return n2 + 1;
}
return 1;
}
function _normalizeLabelIds(raw) {
if (!raw) return [];
if (Array.isArray(raw)) return raw.map(function (x) { return String(x); }).filter(Boolean);
return String(raw).split(',').map(function (x) { return x.trim(); }).filter(Boolean);
}
function _tryNotifyLabelChanged() {
try {
var candidates = [
'com.tencent.mm.plugin.label.a.a',
'com.tencent.mm.plugin.label.a.b',
'com.tencent.mm.storage.bv',
];
for (var i = 0; i < candidates.length; i++) {
try {
var Cls = Java.use(candidates[i]);
if (typeof Cls.a === 'function') { Cls.a(); return true; }
if (typeof Cls.b === 'function') { Cls.b(); return true; }
} catch (_) {}
}
} catch (_) {}
return false;
}
// WCDB 实例查找(兼容 8.0.69 WCDB 2.x多候选类名 + 放宽路径匹配
var _WCDB_CLASS_CANDIDATES = [
'com.tencent.wcdb.database.SQLiteDatabase', // WCDB 1.x
'com.tencent.wcdb.core.Database', // WCDB 2.x新版微信
];
var _wcdbClassResolved = null;
function _wcdbInstancePath(instance) {
// WCDB 1.x: getPath()2.x: getPath() 或 path 字段
try { if (typeof instance.getPath === 'function') return safeStr(instance.getPath()); } catch (_) {}
try { if (instance.path) return safeStr(instance.path.value); } catch (_) {}
return '';
}
function _wcdbIsOpen(instance) {
try { if (typeof instance.isOpen === 'function') return instance.isOpen(); } catch (_) {}
try { if (typeof instance.isOpened === 'function') return instance.isOpened(); } catch (_) {}
return true; // 2.x 无显式 isOpen 时按打开处理
}
// WCDB 实例缓存Java.choose 全堆扫描在大堆上耗时数十秒,标签读写每次都扫会超时。
// 按 path 缓存已打开实例;命中先校验 isOpen失效则重扫。显著降低标签操作延迟。
var _wcdbInstCache = {};
function _findOpenWcdb(path) {
var normalizedPath = path.replace('/data/data/', '/data/user/0/');
var baseName = path.split('/').pop();
var found = null;
// 1) 先查缓存:命中且仍打开则直接复用,避免全堆扫描
var cached = _wcdbInstCache[path];
if (cached) {
try {
if (_wcdbIsOpen(cached)) return cached;
} catch (_) {}
try { delete _wcdbInstCache[path]; } catch (_) { _wcdbInstCache[path] = null; }
}
// 始终遍历全部候选类:不同库分属不同 WCDB 版本EnMicroMsg=1.xSnsMicroMsg=2.x
// 不能用单一 _wcdbClassResolved 缓存限制,否则跨版本库找不到。
var classes = _WCDB_CLASS_CANDIDATES;
for (var ci = 0; ci < classes.length && !found; ci++) {
var cls = classes[ci];
try { Java.use(cls); } catch (_) { continue; } // 类不存在则跳过
try {
Java.choose(cls, {
onMatch: function (instance) {
if (found) return;
try {
var p = _wcdbInstancePath(instance);
var match = (p === path || p === normalizedPath || (baseName && p.indexOf(baseName) >= 0));
if (match && _wcdbIsOpen(instance)) {
found = instance;
_wcdbClassResolved = cls;
_wcdbInstCache[path] = instance; // 缓存供后续复用
}
} catch (_) {}
},
onComplete: function () {}
});
} catch (e) {
log('warn', 'db', 'Java.choose 失败 ' + cls + ': ' + e);
}
}
if (!found) log('warn', 'db', '未找到已打开的 WCDB 实例(已试' + classes.length + '类): ' + baseName);
return found;
}
// 诊断:枚举所有 WCDB 实例路径(真机排查 8.0.69 DB 读空)
function _diagWcdbInstances() {
var out = [];
for (var ci = 0; ci < _WCDB_CLASS_CANDIDATES.length; ci++) {
var cls = _WCDB_CLASS_CANDIDATES[ci];
try { Java.use(cls); } catch (_) { out.push({cls: cls, exists: false}); continue; }
var paths = [];
try {
Java.choose(cls, {
onMatch: function (inst) {
try { paths.push({ path: _wcdbInstancePath(inst), open: _wcdbIsOpen(inst) }); } catch (_) {}
},
onComplete: function () {}
});
} catch (_) {}
out.push({ cls: cls, exists: true, instances: paths });
}
return out;
}
function _execSnsSQL(sql) {
return _execSQL(sql, _getSnsDbPath());
}
// ============================================================
// § 4 rpc.exports — Python 端通过 FridaManager.call_rpc 调用
// ============================================================
rpc.exports = {
// ==================== 系统 ====================
ping: function () {
return 'pong from wechat_hook_v3.0 — 96 actions / 26 modules';
},
getProcessInfo: function () {
return {
pid: Process.id,
arch: Process.arch,
platform: Process.platform,
modules: Process.enumerateModules().length,
wechat_version: _wechatVersion,
hooked: _hooked,
};
},
getHookStatus: function () {
return _hooked;
},
// 诊断:枚举真机 WCDB 实例路径(排查 8.0.69 DB 读空)
diagWcdb: function () {
try {
return Java.performNow(function () {
var out = { success: true, db_path: _getDbPath(), sns_path: _getSnsDbPath(), instances: _diagWcdbInstances() };
// 实读测试main 库 rcontact、sns 库 SnsInfo回传 _lastDbDiag 定位查询错误
try { _execSQL('SELECT count(*) AS n FROM rcontact'); out.main_query = JSON.parse(JSON.stringify(_lastDbDiag)); } catch (e1) { out.main_query = { error: String(e1) }; }
try { _execSnsSQL('SELECT count(*) AS n FROM SnsInfo'); out.sns_query = JSON.parse(JSON.stringify(_lastDbDiag)); } catch (e2) { out.sns_query = { error: String(e2) }; }
return out;
});
} catch (e) {
return { success: false, error: String(e) };
}
},
// 诊断:运行时反射枚举 8.0.69 朋友圈(SNS)发布/读取相关类与方法
// 用于在混淆版定位 post_moments(sns.model.ay 等价) / get_moments(m3.onGYNetEnd 等价) 真实类名。
diagSns: function (params) {
// 安全诊断:**禁用** Java.enumerateLoadedClassesSync微信 10万+ 类,桥接枚举会阻塞
// agent 事件循环导致 WS 掉线 — 2026-05-30 实测)。改为「试探调用方提供的候选类名」:
// params.candidates = ["com.tencent.mm.plugin.sns.model.xx", ...](来自离线 apk/smali 分析)
// 对每个候选 Java.use + 反射方法名(最多 8 个,快速、不枚举),定位发/读朋友圈真实类。
var cands = (params && params.candidates) || [];
if (!cands || cands.length === 0) {
return {
success: false,
error: 'no_candidates',
hint: '运行时全量枚举已禁用(会致 WS 掉线)。请先离线对 8.0.69 apk 跑 jadx/baksmali 取候选类名,再传 params.candidates=[...] 试探。',
};
}
var lim = Math.min(cands.length, 8);
try {
return Java.performNow(function () {
var out = [];
for (var i = 0; i < lim; i++) {
var cn = cands[i];
var info = { cls: cn, exists: false, methods: [], ctors: 0, hasCommit: false };
try {
var k = Java.use(cn);
info.exists = true;
var ms = k.class.getDeclaredMethods();
var names = {};
for (var m = 0; m < ms.length; m++) {
var mn = ms[m].getName();
names[mn] = 1;
if (mn.toLowerCase().indexOf('commit') >= 0) info.hasCommit = true;
}
info.methods = Object.keys(names).slice(0, 30);
info.ctors = k.class.getDeclaredConstructors().length;
} catch (e0) { info.err = String(e0); }
out.push(info);
}
return { success: true, tested: lim, classes: out };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
getWechatVersion: function () {
if (_wechatVersion) return { success: true, version: _wechatVersion };
try {
return Java.performNow(function () {
var ctx = getCtx();
var pm = ctx.getPackageManager();
var info = pm.getPackageInfo(jStr('com.tencent.mm'), 0);
_wechatVersion = safeStr(info.versionName.value);
_resolveVersionCompat(_wechatVersion);
return { success: true, version: _wechatVersion };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
getVersionCompat: function () {
return {
wechat_version: _wechatVersion,
matched: _versionCompat !== _DEFAULT_COMPAT,
supported_versions: Object.keys(_VERSION_TABLE),
active_compat: _versionCompat ? Object.keys(_versionCompat) : [],
};
},
diagSendMessageCandidates: function () {
try {
return Java.performNow(function () {
var candidates = getCompat('send_message');
var rows = [];
for (var i = 0; i < candidates.length; i++) {
var c = candidates[i];
var row = {
cls: c.cls,
method: c.method,
sig: c.sig || [],
class_found: false,
method_found: false,
overload_found: false,
error: ''
};
try {
var MsgCls = Java.use(c.cls);
row.class_found = !!MsgCls;
row.method_found = !!(MsgCls && MsgCls[c.method]);
if (row.method_found) {
if (c.sig && c.sig.length > 0) {
try {
MsgCls[c.method].overload.apply(MsgCls[c.method], c.sig);
row.overload_found = true;
} catch (oe) {
row.error = String(oe);
}
} else {
row.overload_found = true;
}
}
} catch (e) {
row.error = String(e);
}
rows.push(row);
}
return {
success: true,
wechat_version: _wechatVersion,
candidate_count: rows.length,
candidates: rows
};
});
} catch (e) {
return { success: false, error: String(e) };
}
},
diagSendMsgEvent: function (params) {
try {
return Java.performNow(function () {
var toId = (params && params.to_id) || 'filehelper';
var content = (params && params.content) || '[diag]';
var diag = {
success: true,
to_id: toId,
content_len: content.length,
classes: {},
field_set: false,
scene: null,
error: ''
};
try {
var SendMsgEvent = Java.use('com.tencent.mm.autogen.events.SendMsgEvent');
Java.use('com.tencent.mm.sdk.event.IEvent');
var event = SendMsgEvent.$new();
var payload = event.g.value;
payload.a.value = jStr(toId);
payload.b.value = jStr(content);
payload.c.value = _calcSendMsgScene(toId);
payload.d.value = 0;
diag.classes.SendMsgEvent = true;
diag.classes.payload = safeStr(payload.getClass().getName());
diag.event_methods = _reflectMethodSigs(event, 80);
diag.field_set = true;
diag.scene = payload.c.value;
} catch (inner) {
diag.success = false;
diag.error = String(inner);
}
return diag;
});
} catch (e) {
return { success: false, error: String(e) };
}
},
diagRevokeMsgEvent: function (params) {
try {
return Java.performNow(function () {
var msgSvrId = (params && params.msg_svr_id) || '';
var diag = { success: true, msg_svr_id: msgSvrId, classes: {}, payload_fields: [], row: null };
try {
if (msgSvrId) {
var _id = String(msgSvrId).replace(/'/g, "''");
var sql = "SELECT msgId, msgSvrId, talker, type, content, isSend, createTime FROM message"
+ " WHERE msgSvrId = '" + _id + "' OR msgId = '" + _id + "' LIMIT 1";
var rows = _execSQL(sql);
if (rows && rows.length > 0) diag.row = rows[0];
}
var RevokeMsgEvent = Java.use('com.tencent.mm.autogen.events.RevokeMsgEvent');
diag.classes.RevokeMsgEvent = true;
var event = RevokeMsgEvent.$new();
diag.event_methods = _reflectMethodSigs(event, 40);
var payload = event.g.value;
diag.payload_class = safeStr(payload.getClass().getName());
try {
var cls = payload.getClass();
var fields = cls.getDeclaredFields();
for (var i = 0; i < fields.length && i < 40; i++) {
fields[i].setAccessible(true);
var name = fields[i].getName();
var ftype = safeStr(fields[i].getType().getName());
var val = null;
try { val = fields[i].get(payload); } catch (_) { val = '?'; }
diag.payload_fields.push({ name: name, type: ftype, sample: safeStr(val) });
}
} catch (fe) {
diag.field_error = String(fe);
}
} catch (inner) {
diag.success = false;
diag.error = String(inner);
}
return diag;
});
} catch (e) {
return { success: false, error: String(e) };
}
},
// ==================== H17 消息发送 ====================
sendMessage: function (params) {
var toId = (params && params.to_id) || '';
var content = (params && params.content) || '';
var msgType = (params && params.msg_type) || 'text';
if (!toId || !content) {
return { success: false, error: '缺少 to_id 或 content' };
}
log('info', 'rpc', 'sendMessage', { to_id: toId, content_len: content.length, msg_type: msgType });
try {
return Java.performNow(function () {
return _sendMessageInternal(toId, content, msgType);
});
} catch (e) {
return { success: false, error: String(e) };
}
},
// ==================== H15 消息获取 ====================
getMessages: function (params) {
var limit = (params && params.limit) || 3000;
var offset = (params && params.offset) || 0;
var conversationId = (params && params.conversation_id) || '';
var dbRes = null;
try {
dbRes = Java.performNow(function () {
return _getMessagesFromDB(conversationId, limit, offset);
});
} catch (e) {
dbRes = { success: false, error: String(e), messages: [] };
}
// DB 读为空时 fallback 到 hook 实时缓冲insert hook 已拦截新消息)
if (!dbRes || !dbRes.messages || dbRes.messages.length === 0) {
offset = parseInt(offset, 10) || 0;
if (offset < 0) offset = 0;
var filteredBuffer = _messageBuffer.filter(function (m) {
return !conversationId || m.from_id === conversationId;
});
var start = Math.max(filteredBuffer.length - offset - limit, 0);
var end = offset > 0 ? filteredBuffer.length - offset : filteredBuffer.length;
var buf = filteredBuffer.slice(start, end);
if (buf.length > 0) {
return {
success: true,
messages: buf.map(function (m) {
return { id: m.msg_svr_id, type: m.msg_type, from_id: m.from_id,
content: m.content, timestamp: m.timestamp, is_send: m.is_send };
}),
count: buf.length,
returned_count: buf.length,
requested_limit: limit,
offset: offset,
total_count: filteredBuffer.length,
has_more: filteredBuffer.length > offset + buf.length,
source: 'hook_buffer',
db_diag: _lastDbDiag,
};
}
if (dbRes) dbRes.db_diag = _lastDbDiag;
}
return dbRes;
},
getRecentMessages: function (params) {
var limit = (params && params.limit) || 3000;
var msgs = _messageBuffer.slice(-limit);
return { success: true, messages: msgs, count: msgs.length };
},
searchMessages: function (params) {
var keyword = (params && params.keyword) || '';
var limit = (params && params.limit) || 50;
if (!keyword) return { success: false, error: '缺少 keyword' };
try {
return Java.performNow(function () {
var sql = "SELECT msgId, type, talker, content, createTime FROM message"
+ " WHERE content LIKE '%" + keyword.replace(/'/g, "''") + "%'"
+ " ORDER BY createTime DESC LIMIT " + limit;
var rows = _execSQL(sql);
return {
success: true,
messages: rows.map(function (r) {
return { id: r.msgId, type: r.type, from_id: r.talker, content: r.content, timestamp: r.createTime };
}),
count: rows.length,
};
});
} catch (e) {
return { success: false, error: String(e), messages: [] };
}
},
// ==================== H16 联系人 ====================
getContacts: function (params) {
var limit = (params && params.limit) || 10000;
var offset = (params && params.offset) || 0;
try {
return Java.performNow(function () { return _getContactsFromDB(limit, offset); });
} catch (e) {
return { success: false, error: String(e), contacts: [] };
}
},
getContactInfo: function (params) {
var wxid = (params && params.wxid) || '';
if (!wxid) return { success: false, error: '缺少 wxid' };
try {
return Java.performNow(function () { return _getContactInfoFromDB(wxid); });
} catch (e) {
return { success: false, error: String(e) };
}
},
searchContacts: function (params) {
var keyword = (params && params.keyword) || '';
var limit = (params && params.limit) || 50;
if (!keyword) return { success: false, error: '缺少 keyword' };
try {
return Java.performNow(function () {
var kw = keyword.replace(/'/g, "''");
var sql = "SELECT username, nickname, conRemark, type FROM rcontact"
+ " WHERE (nickname LIKE '%" + kw + "%' OR conRemark LIKE '%" + kw + "%' OR username LIKE '%" + kw + "%')"
+ " AND type NOT IN (0, 33) AND verifyFlag=0"
+ " ORDER BY nickname LIMIT " + limit;
var rows = _execSQL(sql);
return {
success: true,
contacts: rows.map(function (r) {
return { wxid: r.username, nickname: r.nickname, remark: r.conRemark, type: r.type };
}),
count: rows.length,
};
});
} catch (e) {
return { success: false, error: String(e), contacts: [] };
}
},
// ==================== H18/H19 好友请求 ====================
getFriendRequests: function (params) {
var limit = (params && params.limit) || 50;
var cached = _friendRequestBuffer.slice(-limit);
if (cached.length > 0) return { success: true, requests: cached, count: cached.length, source: 'buffer' };
try {
return Java.performNow(function () { return _getFriendRequestsFromDB(limit); });
} catch (e) {
return { success: false, error: String(e), requests: [] };
}
},
addFriend: function (params) {
var userId = (params && params.user_id) || '';
var message = (params && params.message) || '你好,我想加你为好友';
var sourceType = (params && params.source_type) || 3; // 3=搜索
if (!userId) return { success: false, error: '缺少 user_id' };
log('info', 'rpc', 'addFriend', { user_id: userId, source_type: sourceType });
try {
return Java.performNow(function () {
return _addFriendInternal(userId, message, sourceType);
});
} catch (e) {
return { success: false, error: String(e) };
}
},
acceptFriend: function (params) {
var encryptUserName = (params && params.encrypt_username) || '';
var ticket = (params && params.ticket) || '';
if (!encryptUserName && !ticket) return { success: false, error: '缺少 encrypt_username 或 ticket' };
log('info', 'rpc', 'acceptFriend', { encrypt_username: encryptUserName });
try {
return Java.performNow(function () {
return _acceptFriendInternal(encryptUserName, ticket);
});
} catch (e) {
return { success: false, error: String(e) };
}
},
deleteFriend: function (params) {
var wxid = (params && params.wxid) || '';
if (!wxid) return { success: false, error: '缺少 wxid' };
try {
return Java.performNow(function () {
return _deleteFriendInternal(wxid);
});
} catch (e) {
return { success: false, error: String(e) };
}
},
setFriendRemark: function (params) {
var wxid = (params && params.wxid) || '';
var remark = (params && params.remark) || '';
if (!wxid) return { success: false, error: '缺少 wxid' };
try {
return Java.performNow(function () {
return _setRemarkInternal(wxid, remark);
});
} catch (e) {
return { success: false, error: String(e) };
}
},
// ==================== H22 群管理 ====================
getGroups: function (params) {
var limit = (params && params.limit) || 100;
var offset = (params && params.offset) || 0;
try {
return Java.performNow(function () { return _getGroupsFromDB(limit, offset); });
} catch (e) {
return { success: false, error: String(e), groups: [] };
}
},
getGroupMembers: function (params) {
var groupId = (params && params.group_id) || '';
if (!groupId) return { success: false, error: '缺少 group_id' };
try {
return Java.performNow(function () { return _getGroupMembersFromDB(groupId); });
} catch (e) {
return { success: false, error: String(e), members: [] };
}
},
getGroupInfo: function (params) {
var groupId = (params && params.group_id) || '';
if (!groupId) return { success: false, error: '缺少 group_id' };
try {
return Java.performNow(function () { return _getGroupInfoFromDB(groupId); });
} catch (e) {
return { success: false, error: String(e) };
}
},
sendGroupMessage: function (params) {
var groupId = (params && params.group_id) || '';
var content = (params && params.content) || '';
if (!groupId || !content) return { success: false, error: '缺少 group_id 或 content' };
try {
return Java.performNow(function () {
return _sendMessageInternal(groupId, content, 'text');
});
} catch (e) {
return { success: false, error: String(e) };
}
},
createGroup: function (params) {
var memberIds = (params && params.member_ids) || [];
var topic = (params && params.topic) || '';
if (memberIds.length < 2) return { success: false, error: '建群至少需要2个成员' };
try {
return Java.performNow(function () {
return _createGroupInternal(memberIds, topic);
});
} catch (e) {
return { success: false, error: String(e) };
}
},
inviteToGroup: function (params) {
var groupId = (params && params.group_id) || '';
var memberIds = (params && params.member_ids) || [];
if (!groupId || memberIds.length === 0) return { success: false, error: '缺少 group_id 或 member_ids' };
try {
return Java.performNow(function () {
return _inviteToGroupInternal(groupId, memberIds);
});
} catch (e) {
return { success: false, error: String(e) };
}
},
removeFromGroup: function (params) {
var groupId = (params && params.group_id) || '';
var memberIds = (params && params.member_ids) || [];
if (!groupId || memberIds.length === 0) return { success: false, error: '缺少 group_id 或 member_ids' };
try {
return Java.performNow(function () {
return _removeFromGroupInternal(groupId, memberIds);
});
} catch (e) {
return { success: false, error: String(e) };
}
},
setGroupAnnouncement: function (params) {
var groupId = (params && params.group_id) || '';
var announcement = (params && params.announcement) || '';
if (!groupId) return { success: false, error: '缺少 group_id' };
try {
return Java.performNow(function () {
return _setGroupAnnouncementInternal(groupId, announcement);
});
} catch (e) {
return { success: false, error: String(e) };
}
},
setGroupName: function (params) {
var groupId = (params && params.group_id) || '';
var name = (params && params.name) || '';
if (!groupId || !name) return { success: false, error: '缺少 group_id 或 name' };
try {
return Java.performNow(function () {
return _setGroupNameInternal(groupId, name);
});
} catch (e) {
return { success: false, error: String(e) };
}
},
quitGroup: function (params) {
var groupId = (params && params.group_id) || '';
if (!groupId) return { success: false, error: '缺少 group_id' };
try {
return Java.performNow(function () {
return _quitGroupInternal(groupId);
});
} catch (e) {
return { success: false, error: String(e) };
}
},
// ==================== H20/H21 朋友圈 ====================
postMoments: function (params) {
var content = (params && params.content) || '';
var imageUrls = (params && params.image_urls) || [];
if (!content && imageUrls.length === 0) return { success: false, error: '朋友圈内容不能为空' };
var autoSend = !(params && params.auto_send === false);
try {
return Java.performNow(function () {
return _postMomentsInternal(content, imageUrls, autoSend);
});
} catch (e) {
return { success: false, error: '发朋友圈失败: ' + String(e) };
}
},
getMoments: function (params) {
var limit = (params && params.limit) || 20;
var wxid = (params && params.wxid) || '';
try {
return Java.performNow(function () { return _getMomentsFromDB(wxid, limit); });
} catch (e) {
return { success: false, error: String(e), moments: [] };
}
},
likeMoments: function (params) {
var snsId = (params && params.sns_id) || '';
if (!snsId) return { success: false, error: '缺少 sns_id' };
try {
return Java.performNow(function () {
return _likeMomentsInternal(snsId);
});
} catch (e) {
return { success: false, error: String(e) };
}
},
commentMoments: function (params) {
var snsId = (params && params.sns_id) || '';
var comment = (params && params.comment) || '';
if (!snsId || !comment) return { success: false, error: '缺少 sns_id 或 comment' };
try {
return Java.performNow(function () {
return _commentMomentsInternal(snsId, comment);
});
} catch (e) {
return { success: false, error: String(e) };
}
},
deleteMoments: function (params) {
var snsId = (params && params.sns_id) || '';
if (!snsId) return { success: false, error: '缺少 sns_id' };
return _intentAction('com.workphone.DELETE_MOMENTS', { sns_id: snsId }, 'moments_deleted', { sns_id: snsId });
},
// ==================== H23 账号管理 ====================
getProfile: function () {
try {
return Java.performNow(function () {
var sql = "SELECT value FROM userinfo WHERE id=4";
var rows = _execSQL(sql);
var nickname = rows.length > 0 ? rows[0].value : '';
var sql2 = "SELECT value FROM userinfo WHERE id=2";
var rows2 = _execSQL(sql2);
var wxid = rows2.length > 0 ? rows2[0].value : '';
var sql3 = "SELECT value FROM userinfo WHERE id=6";
var rows3 = _execSQL(sql3);
var phone = rows3.length > 0 ? rows3[0].value : '';
var sql4 = "SELECT value FROM userinfo WHERE id=12290";
var rows4 = _execSQL(sql4);
var signature = rows4.length > 0 ? rows4[0].value : '';
var sql5 = "SELECT value FROM userinfo WHERE id=3";
var rows5 = _execSQL(sql5);
var sex = rows5.length > 0 ? rows5[0].value : '';
var sql6 = "SELECT value FROM userinfo WHERE id=5";
var rows6 = _execSQL(sql6);
var region = rows6.length > 0 ? rows6[0].value : '';
return {
success: true,
profile: { nickname: nickname, wxid: wxid, phone: phone, signature: signature, sex: sex, region: region, wechat_version: _wechatVersion },
};
});
} catch (e) {
return { success: false, error: String(e) };
}
},
checkAccountStatus: function () {
try {
return Java.performNow(function () {
var ctx = getCtx();
var pm = ctx.getPackageManager();
var info = pm.getPackageInfo(jStr('com.tencent.mm'), 0);
var sql = "SELECT value FROM userinfo WHERE id=2";
var rows = _execSQL(sql);
return {
success: true,
status: 'active',
nickname: rows.length > 0 ? rows[0].value : '',
version: safeStr(info.versionName.value),
pid: Process.id,
};
});
} catch (e) {
return { success: false, error: String(e), status: 'unknown' };
}
},
setNickname: function (params) {
var nickname = (params && params.nickname) || '';
if (!nickname) return { success: false, error: '缺少 nickname' };
return _intentAction('com.workphone.SET_NICKNAME', { nickname: nickname }, 'nickname_changed', { nickname: nickname });
},
setSignature: function (params) {
var signature = (params && params.signature) || '';
return _intentAction('com.workphone.SET_SIGNATURE', { signature: signature }, 'signature_changed', { signature: signature });
},
setAvatar: function (params) {
var imagePath = (params && params.image_path) || '';
if (!imagePath) return { success: false, error: '缺少 image_path' };
return _intentAction('com.workphone.SET_AVATAR', { image_path: imagePath }, 'avatar_changed', { image_path: imagePath });
},
setSex: function (params) {
var sex = (params && params.sex) || '1';
return _intentAction('com.workphone.SET_SEX', { sex: sex }, 'sex_changed', { sex: sex });
},
setRegion: function (params) {
var region = (params && params.region) || '';
if (!region) return { success: false, error: '缺少 region' };
return _intentAction('com.workphone.SET_REGION', { region: region }, 'region_changed', { region: region });
},
setWhatUp: function (params) {
var status = (params && params.status) || '';
return _intentAction('com.workphone.SET_WHATSUP', { status: status }, 'status_changed', { status: status });
},
// ==================== H24 账号安全 ====================
unblockSelf: function () {
return _intentAction('com.workphone.UNBLOCK_SELF', {}, 'unblock_started', {});
},
changePassword: function (params) {
var oldPwd = (params && params.old_password) || '';
var newPwd = (params && params.new_password) || '';
if (!oldPwd || !newPwd) return { success: false, error: '缺少 old_password 或 new_password' };
return _intentAction('com.workphone.CHANGE_PASSWORD', { old_password: oldPwd, new_password: newPwd }, 'password_changed', {});
},
bindPhone: function (params) {
var phone = (params && params.phone) || '';
if (!phone) return { success: false, error: '缺少 phone' };
return _intentAction('com.workphone.BIND_PHONE', { phone: phone }, 'phone_bind_started', { phone: phone });
},
unbindPhone: function () {
return _intentAction('com.workphone.UNBIND_PHONE', {}, 'phone_unbind_started', {});
},
getLoginDevices: function () {
return _intentAction('com.workphone.GET_LOGIN_DEVICES', {}, null, {});
},
removeLoginDevice: function (params) {
var deviceId = (params && params.device_id) || '';
if (!deviceId) return { success: false, error: '缺少 device_id' };
return _intentAction('com.workphone.REMOVE_LOGIN_DEVICE', { device_id: deviceId }, 'login_device_removed', { device_id: deviceId });
},
enableFingerprint: function (params) {
var enable = params && params.enable !== undefined ? params.enable : true;
return _intentAction('com.workphone.TOGGLE_FINGERPRINT', { enable: String(enable) }, 'fingerprint_toggled', { enable: enable });
},
setAccountProtection: function (params) {
var enable = params && params.enable !== undefined ? params.enable : true;
return _intentAction('com.workphone.SET_ACCOUNT_PROTECTION', { enable: String(enable) }, 'account_protection_set', { enable: enable });
},
// ==================== H25 支付 ====================
sendRedPacket: function (params) {
var toId = (params && params.to_id) || '';
var amount = (params && params.amount) || '';
var message = (params && params.message) || '恭喜发财';
if (!toId || !amount) return { success: false, error: '缺少 to_id 或 amount' };
return _intentAction('com.workphone.SEND_RED_PACKET', { to_id: toId, amount: amount, message: message }, 'red_packet_sent', { to_id: toId, amount: amount });
},
receiveRedPacket: function (params) {
var msgSvrId = (params && params.msg_svr_id) || '';
if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id' };
return _intentAction('com.workphone.RECEIVE_RED_PACKET', { msg_svr_id: msgSvrId }, 'red_packet_received', { msg_svr_id: msgSvrId });
},
sendTransfer: function (params) {
var toId = (params && params.to_id) || '';
var amount = (params && params.amount) || '';
var desc = (params && params.description) || '';
if (!toId || !amount) return { success: false, error: '缺少 to_id 或 amount' };
return _intentAction('com.workphone.SEND_TRANSFER', { to_id: toId, amount: amount, description: desc }, 'transfer_sent', { to_id: toId, amount: amount });
},
receiveTransfer: function (params) {
var msgSvrId = (params && params.msg_svr_id) || '';
if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id' };
return _intentAction('com.workphone.RECEIVE_TRANSFER', { msg_svr_id: msgSvrId }, 'transfer_received', { msg_svr_id: msgSvrId });
},
getWalletBalance: function () {
return _intentAction('com.workphone.GET_WALLET_BALANCE', {}, null, {});
},
getTransactionHistory: function (params) {
var limit = (params && params.limit) || 20;
return _intentAction('com.workphone.GET_TRANSACTIONS', { limit: String(limit) }, null, {});
},
showPaymentCode: function () {
return _intentAction('com.workphone.SHOW_PAYMENT_CODE', {}, null, {});
},
getSafetyCenter: function () {
try {
return Java.performNow(function () {
var kws = ['账号暂时无法使用聊天功能', '功能受限', '账号异常', '暂时限制', '封禁', '被限制'];
var sql = 'SELECT content FROM message ORDER BY createTime DESC LIMIT 200';
var rows = _execSQL(sql);
var hits = [];
for (var i = 0; i < rows.length; i++) {
var c = String(rows[i].content || '');
for (var j = 0; j < kws.length; j++) {
if (c.indexOf(kws[j]) >= 0 && hits.indexOf(kws[j]) < 0) hits.push(kws[j]);
}
}
var bindMobile = '';
try {
var ui = _execSQL('SELECT value FROM userinfo WHERE type=4 LIMIT 1');
if (ui.length) bindMobile = String(ui[0].value || '');
} catch (_) {}
return {
success: true,
channel: 'frida_db',
restricted: hits.length > 0,
restrictions: hits,
bind_mobile_masked: bindMobile ? bindMobile.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2') : '',
note: '账号安全摘要DB+消息关键词,非 UI 导航)',
};
});
} catch (e) {
return { success: false, error: String(e) };
}
},
checkRestrictions: function () {
try {
return Java.performNow(function () {
var kws = ['账号暂时无法使用聊天功能', '功能受限', '账号异常', '暂时限制', '封禁', '被限制'];
var sql = 'SELECT content FROM message ORDER BY createTime DESC LIMIT 200';
var rows = _execSQL(sql);
var hits = [];
for (var i = 0; i < rows.length; i++) {
var c = String(rows[i].content || '');
for (var j = 0; j < kws.length; j++) {
if (c.indexOf(kws[j]) >= 0 && hits.indexOf(kws[j]) < 0) hits.push(kws[j]);
}
}
return { success: true, restricted: hits.length > 0, restrictions: hits };
});
} catch (e) {
return { success: false, error: String(e), restricted: false, restrictions: [] };
}
},
getTopStories: function () {
var limit = 5;
try {
return Java.performNow(function () {
var stories = [];
var probes = [
'SELECT title,url,pubTime FROM ReaderAppInfo ORDER BY rowid DESC LIMIT ' + limit,
'SELECT nickname,username FROM bizinfo WHERE type=1 LIMIT ' + limit,
'SELECT content,createTime FROM message WHERE type=49 ORDER BY createTime DESC LIMIT ' + limit,
];
for (var p = 0; p < probes.length && stories.length === 0; p++) {
try {
var rows = _execSQL(probes[p]);
for (var i = 0; i < rows.length; i++) {
stories.push(rows[i]);
}
} catch (_) {}
}
return {
success: true,
channel: 'frida_db',
stories: stories,
count: stories.length,
note: stories.length ? '订阅号/ReaderApp DB 摘要' : '无 top_stories 可读行(版本/账号差异)',
};
});
} catch (e) {
return { success: false, error: String(e), stories: [] };
}
},
getWechatSteps: function () {
try {
return Java.performNow(function () {
var steps = 0;
var detail = [];
var sportPath = null;
try {
var File = Java.use('java.io.File');
var dir = File.$new('/data/data/com.tencent.mm/MicroMsg/');
if (dir.exists()) {
var subs = dir.listFiles();
if (subs) {
for (var i = 0; i < subs.length; i++) {
var cand = subs[i].getAbsolutePath() + '/sportrecord.db';
if (File.$new(cand).exists()) { sportPath = cand; break; }
}
}
}
} catch (_) {}
if (sportPath) {
var rows = _execSQL('SELECT * FROM SportRecordInfo ORDER BY rowid DESC LIMIT 1', sportPath);
if (rows.length) {
var r0 = rows[0];
steps = parseInt(r0.step || r0.steps || r0.Step || '0', 10) || 0;
detail = rows;
}
}
if (!steps) {
try {
var alt = _execSQL("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE '%sport%'");
if (alt.length) detail = alt;
} catch (_) {}
}
return {
success: true,
channel: 'frida_db',
steps: steps,
records: detail,
sport_db: sportPath || '',
note: steps ? 'sportrecord.db 最近步数' : '步数为 0 或未开启微信运动(诚实返回)',
};
});
} catch (e) {
return { success: false, error: String(e), steps: 0 };
}
},
getStickerList: function (params) {
// 真实现:读 EnMicroMsg.db 的 EmojiInfo 表(自定义表情);动态列探测兼容 8.0.x
// 表不存在/无自定义表情时诚实返回空,不假成功(真机铁律 #3/#14
var limit = (params && params.limit) || 100;
try {
return Java.performNow(function () {
var cols;
try { cols = _execSQL("PRAGMA table_info(EmojiInfo)"); } catch (e0) { cols = []; }
if (!cols || cols.length === 0) {
return { success: true, stickers: [], count: 0, channel: 'frida_db',
note: 'EmojiInfo 表不存在或无自定义表情(版本/账号差异)' };
}
var names = cols.map(function (c) { return c.name; });
var want = ['md5', 'catalog', 'name', 'type', 'size', 'cdnUrl', 'groupId', 'reserved4'];
var sel = want.filter(function (w) { return names.indexOf(w) >= 0; });
if (sel.length === 0) sel = names.slice(0, 6);
var rows = _execSQL("SELECT " + sel.join(',') + " FROM EmojiInfo LIMIT " + limit);
return {
success: true,
channel: 'frida_db',
columns: sel,
stickers: rows.map(function (r) {
return {
md5: r.md5, name: r.name, catalog: r.catalog,
type: r.type, size: r.size,
cdn_url: r.cdnUrl || r.reserved4 || '', group_id: r.groupId
};
}),
count: rows.length,
};
});
} catch (e) {
return { success: false, error: String(e), stickers: [] };
}
},
likeWechatSteps: function (params) {
var userId = (params && params.user_id) || '';
return _intentAction('com.workphone.LIKE_WECHAT_STEPS', { user_id: userId }, 'steps_liked', { user_id: userId });
},
clearCache: function () {
return _intentAction('com.workphone.CLEAR_CACHE', {}, 'cache_cleared', {});
},
checkForUpdate: function () {
return _intentAction('com.workphone.CHECK_UPDATE', {}, 'update_checked', {});
},
// ==================== H26 二维码 ====================
scanQrCode: function (params) {
var imagePath = (params && params.image_path) || '';
if (!imagePath) {
return {
success: false,
error: 'missing_image_path',
hint: 'extract_qr_from_image 需 image_path或走 u2 相册导航hook_only=0',
channel: 'frida_rpc',
};
}
return _intentAction('com.workphone.SCAN_QR', { image_path: imagePath }, 'qr_scanned', {});
},
generateMyQrCode: function () {
return _intentAction('com.workphone.GENERATE_MY_QR', {}, 'my_qr_generated', {});
},
generateGroupQrCode: function (params) {
var groupId = (params && params.group_id) || '';
if (!groupId) return { success: false, error: '缺少 group_id' };
return _intentAction('com.workphone.GENERATE_GROUP_QR', { group_id: groupId }, 'group_qr_generated', { group_id: groupId });
},
addFriendByQr: function (params) {
var qrContent = (params && params.qr_content) || '';
if (!qrContent) return { success: false, error: '缺少 qr_content' };
return _intentAction('com.workphone.ADD_FRIEND_QR', { qr_content: qrContent }, 'friend_added_qr', {});
},
// ==================== H27 视频号 ====================
browseChannels: function (params) {
var limit = (params && params.limit) || 10;
return _intentAction('com.workphone.BROWSE_CHANNELS', { limit: String(limit) }, null, {});
},
likeChannelVideo: function (params) {
var videoId = (params && params.video_id) || '';
if (!videoId && params && params.index !== undefined) {
videoId = 'index_' + params.index;
}
if (!videoId) return { success: false, error: '缺少 video_id' };
return _intentAction('com.workphone.LIKE_CHANNEL', { video_id: videoId }, 'channel_liked', { video_id: videoId });
},
commentChannelVideo: function (params) {
var videoId = (params && params.video_id) || '';
var comment = (params && params.comment) || '';
if (!videoId || !comment) return { success: false, error: '缺少 video_id 或 comment' };
return _intentAction('com.workphone.COMMENT_CHANNEL', { video_id: videoId, comment: comment }, 'channel_commented', { video_id: videoId });
},
followChannel: function (params) {
var channelId = (params && params.channel_id) || '';
if (!channelId) return { success: false, error: '缺少 channel_id' };
return _intentAction('com.workphone.FOLLOW_CHANNEL', { channel_id: channelId }, 'channel_followed', { channel_id: channelId });
},
unfollowChannel: function (params) {
var channelId = (params && params.channel_id) || '';
if (!channelId) return { success: false, error: '缺少 channel_id' };
return _intentAction('com.workphone.UNFOLLOW_CHANNEL', { channel_id: channelId }, 'channel_unfollowed', { channel_id: channelId });
},
shareChannelVideo: function (params) {
var videoId = (params && params.video_id) || '';
var toId = (params && params.to_id) || '';
if (!videoId || !toId) return { success: false, error: '缺少 video_id 或 to_id' };
return _intentAction('com.workphone.SHARE_CHANNEL', { video_id: videoId, to_id: toId }, 'channel_shared', { video_id: videoId, to_id: toId });
},
// ==================== H28 标签管理 ====================
getLabels: function (params) {
var limit = (params && params.limit) || 5000;
var offset = (params && params.offset) || 0;
try {
return Java.performNow(function () {
return _getLabelsFromDB(limit, offset);
});
} catch (e) {
return { success: false, error: String(e), labels: [] };
}
},
createLabel: function (params) {
var name = (params && params.name) || '';
if (!name) return { success: false, error: '缺少 name' };
try {
return Java.performNow(function () {
var nextId = _nextLabelId();
var esc = _escapeSql(name);
var idStr = String(nextId);
var wr = _execSQLWrite(
"INSERT INTO contactlabel (labelID, labelName) VALUES ('" + idStr + "', '" + esc + "')"
);
if (!wr.success) {
wr = _execSQLWrite(
"INSERT INTO contactlabelinfo (labelId, labelName) VALUES ('" + idStr + "', '" + esc + "')"
);
}
if (!wr.success) return { success: false, error: wr.error, method: 'wcdb_insert' };
_tryNotifyLabelChanged();
return { success: true, label_id: idStr, name: name, method: 'wcdb_insert' };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
deleteLabel: function (params) {
var labelId = (params && (params.label_id || params.labelId)) || '';
if (!labelId) return { success: false, error: '缺少 label_id' };
try {
return Java.performNow(function () {
var escId = _escapeSql(labelId);
var wr = _execSQLWrite("DELETE FROM contactlabel WHERE labelID='" + escId + "'");
if (!wr.success) {
wr = _execSQLWrite("DELETE FROM contactlabelinfo WHERE labelId='" + escId + "'");
}
if (!wr.success) return { success: false, error: wr.error, method: 'wcdb_delete' };
_execSQLWrite(
"UPDATE rcontact SET contactLabelIds=REPLACE(REPLACE(REPLACE(contactLabelIds,'" + escId + ",',''),'," + escId + "',''),'" + escId + "','') WHERE contactLabelIds LIKE '%" + escId + "%'"
);
_execSQLWrite(
"UPDATE rcontact SET labelidlist=REPLACE(REPLACE(REPLACE(labelidlist,'" + escId + ",',''),'," + escId + "',''),'" + escId + "','') WHERE labelidlist LIKE '%" + escId + "%'"
);
_tryNotifyLabelChanged();
return { success: true, label_id: String(labelId), method: 'wcdb_delete' };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
setContactLabel: function (params) {
var wxid = (params && params.wxid) || '';
var labelIds = _normalizeLabelIds((params && params.label_ids) || (params && params.labelIds) || []);
if (!wxid) return { success: false, error: '缺少 wxid' };
try {
return Java.performNow(function () {
var escWxid = _escapeSql(wxid);
var joined = labelIds.join(',');
var escJoined = _escapeSql(joined);
var wr = _execSQLWrite(
"UPDATE rcontact SET contactLabelIds='" + escJoined + "' WHERE username='" + escWxid + "'"
);
if (!wr.success) {
wr = _execSQLWrite(
"UPDATE rcontact SET labelidlist='" + escJoined + "' WHERE username='" + escWxid + "'"
);
}
if (!wr.success) return { success: false, error: wr.error, method: 'wcdb_update' };
_tryNotifyLabelChanged();
return { success: true, wxid: wxid, label_ids: labelIds, method: 'wcdb_update' };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
getContactsByLabel: function (params) {
var labelId = (params && params.label_id) || '';
if (!labelId) return { success: false, error: '缺少 label_id' };
try {
return Java.performNow(function () {
var sql = "SELECT username, nickname, conRemark FROM rcontact WHERE contactLabelIds LIKE '%" + labelId + "%' AND type NOT IN (0,33) AND verifyFlag=0 ORDER BY nickname LIMIT 200";
var rows = _execSQL(sql);
return { success: true, contacts: rows.map(function (r) { return { wxid: r.username, nickname: r.nickname, remark: r.conRemark }; }), count: rows.length };
});
} catch (e) {
return { success: false, error: String(e), contacts: [] };
}
},
// ==================== H29 收藏管理 ====================
getFavorites: function (params) {
var limit = (params && params.limit) || 50;
try {
return Java.performNow(function () {
var names = ['Favorite.db', 'favorite.db', 'EnFavorite.db'];
var favPath = null;
for (var i = 0; i < names.length; i++) {
favPath = _findDbPath(names[i]);
if (favPath) break;
}
if (!favPath) {
var sql2 = "SELECT localId, type, sourceUserName, updateTime FROM FavItemInfo ORDER BY updateTime DESC LIMIT " + limit;
var rows2 = _execSQL(sql2);
if (rows2.length > 0) return { success: true, favorites: rows2, count: rows2.length, source: 'main_db' };
return { success: true, favorites: [], count: 0, note: 'Favorite 表不在独立数据库中,主库也未找到' };
}
var sql = "SELECT localId, type, sourceUserName, updateTime FROM FavItemInfo ORDER BY updateTime DESC LIMIT " + limit;
var rows = _execSQL(sql, favPath);
return { success: true, favorites: rows, count: rows.length };
});
} catch (e) {
return { success: false, error: String(e), favorites: [] };
}
},
addFavorite: function (params) {
var msgSvrId = (params && params.msg_svr_id) || '';
var type = (params && params.type) || 'message';
var content = (params && params.content) || (params && params.content_desc) || '';
if (!msgSvrId && content) {
return _intentAction('com.workphone.ADD_FAVORITE_TEXT', { content: content, type: type }, 'favorite_added', { content: content });
}
if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id 或 content' };
return _intentAction('com.workphone.ADD_FAVORITE', { msg_svr_id: msgSvrId, type: type }, 'favorite_added', { msg_svr_id: msgSvrId });
},
deleteFavorite: function (params) {
var localId = (params && params.local_id) || '';
if (!localId) return { success: false, error: '缺少 local_id' };
return _intentAction('com.workphone.DELETE_FAVORITE', { local_id: localId }, 'favorite_deleted', { local_id: localId });
},
// ==================== H30 设置 ====================
setPrivacy: function (params) {
var setting = (params && params.setting) || '';
var value = params && params.value !== undefined ? params.value : true;
if (!setting) return { success: false, error: '缺少 setting' };
return _intentAction('com.workphone.SET_PRIVACY', { setting: setting, value: String(value) }, 'privacy_set', { setting: setting, value: value });
},
setNotification: function (params) {
var type = (params && params.type) || 'all';
var enable = params && params.enable !== undefined ? params.enable : true;
return _intentAction('com.workphone.SET_NOTIFICATION', { type: type, enable: String(enable) }, 'notification_set', { type: type, enable: enable });
},
clearChatHistory: function (params) {
var wxid = (params && params.wxid) || '';
if (!wxid) return { success: false, error: '缺少 wxid' };
return _intentAction('com.workphone.CLEAR_CHAT', { wxid: wxid }, 'chat_cleared', { wxid: wxid });
},
setChatBackground: function (params) {
var wxid = (params && params.wxid) || '';
var imagePath = (params && params.image_path) || '';
return _intentAction('com.workphone.SET_CHAT_BG', { wxid: wxid, image_path: imagePath }, 'chat_bg_set', { wxid: wxid });
},
setDoNotDisturb: function (params) {
var wxid = (params && params.wxid) || '';
var enable = params && params.enable !== undefined ? params.enable : true;
if (!wxid) {
return _intentAction('com.workphone.SET_GLOBAL_DND', { enable: String(enable) }, 'dnd_set', { enable: enable });
}
return _intentAction('com.workphone.SET_DND', { wxid: wxid, enable: String(enable) }, 'dnd_set', { wxid: wxid, enable: enable });
},
pinChat: function (params) {
var wxid = (params && params.wxid) || '';
var pin = params && params.pin !== undefined ? params.pin : true;
if (!wxid) return { success: false, error: '缺少 wxid' };
return _intentAction('com.workphone.PIN_CHAT', { wxid: wxid, pin: String(pin) }, 'chat_pinned', { wxid: wxid, pin: pin });
},
// ==================== H31 搜索 ====================
globalSearch: function (params) {
var keyword = (params && params.keyword) || '';
var limit = (params && params.limit) || 30;
if (!keyword) return { success: false, error: '缺少 keyword' };
try {
return Java.performNow(function () {
var kw = keyword.replace(/'/g, "''");
var contactSql = "SELECT username, nickname, conRemark FROM rcontact WHERE (nickname LIKE '%" + kw + "%' OR conRemark LIKE '%" + kw + "%') AND type NOT IN (0,33) AND verifyFlag=0 LIMIT " + limit;
var contacts = _execSQL(contactSql);
var msgSql = "SELECT msgId, talker, content, createTime FROM message WHERE content LIKE '%" + kw + "%' ORDER BY createTime DESC LIMIT " + limit;
var messages = _execSQL(msgSql);
return {
success: true,
contacts: contacts.map(function (r) { return { wxid: r.username, nickname: r.nickname, remark: r.conRemark }; }),
messages: messages.map(function (r) { return { id: r.msgId, from_id: r.talker, content: r.content, timestamp: r.createTime }; }),
};
});
} catch (e) {
return { success: false, error: String(e) };
}
},
// ==================== H32 小程序 ====================
openMiniProgram: function (params) {
var appId = (params && params.app_id) || (params && params.name) || '';
var path = (params && params.path) || '';
if (!appId) return { success: false, error: '缺少 app_id' };
return _intentAction('com.workphone.OPEN_MINI_PROGRAM', { app_id: appId, path: path }, 'mini_program_opened', { app_id: appId });
},
getRecentMiniPrograms: function () {
return _intentAction('com.workphone.GET_RECENT_MINI_PROGRAMS', {}, null, {});
},
shareMiniProgram: function (params) {
var appId = (params && params.app_id) || '';
var toId = (params && params.to_id) || '';
var title = (params && params.title) || '';
if (!appId || !toId) return { success: false, error: '缺少 app_id 或 to_id' };
return _intentAction('com.workphone.SHARE_MINI_PROGRAM', { app_id: appId, to_id: toId, title: title }, 'mini_program_shared', { app_id: appId, to_id: toId });
},
// ==================== H33 文件传输 ====================
sendImage: function (params) {
var toId = (params && params.to_id) || '';
var imagePath = (params && params.image_path) || '';
if (!toId || !imagePath) return { success: false, error: '缺少 to_id 或 image_path' };
try {
return Java.performNow(function () {
return _shareMediaToWechat(imagePath, 'image/*', toId, 'image');
});
} catch (e) {
return { success: false, error: String(e) };
}
},
sendVideo: function (params) {
var toId = (params && params.to_id) || '';
var videoPath = (params && params.video_path) || '';
if (!toId || !videoPath) return { success: false, error: '缺少 to_id 或 video_path' };
try {
return Java.performNow(function () {
return _shareMediaToWechat(videoPath, 'video/*', toId, 'video');
});
} catch (e) {
return { success: false, error: String(e) };
}
},
sendFile: function (params) {
var toId = (params && params.to_id) || '';
var filePath = (params && params.file_path) || '';
if (!toId || !filePath) return { success: false, error: '缺少 to_id 或 file_path' };
try {
return Java.performNow(function () {
return _shareMediaToWechat(filePath, '*/*', toId, 'file');
});
} catch (e) {
return { success: false, error: String(e) };
}
},
sendVoice: function (params) {
var toId = (params && params.to_id) || (params && params.user_id) || '';
var voicePath = (params && params.voice_path) || '';
var duration = (params && params.duration) || 0;
if (!toId) return { success: false, error: '缺少 to_id' };
if (!voicePath && duration) {
return _intentAction('com.workphone.SEND_VOICE_RECORD', { to_id: toId, duration: String(duration) }, 'voice_sent', { to_id: toId, duration: duration });
}
if (!voicePath) return { success: false, error: '缺少 voice_path' };
return _intentAction('com.workphone.SEND_VOICE', { to_id: toId, voice_path: voicePath, duration: String(duration) }, 'voice_sent', { to_id: toId });
},
sendLocation: function (params) {
var toId = (params && params.to_id) || '';
var lat = (params && params.latitude) || '';
var lng = (params && params.longitude) || '';
var label = (params && params.label) || '';
if (!toId || !lat || !lng) return { success: false, error: '缺少 to_id/latitude/longitude' };
return _intentAction('com.workphone.SEND_LOCATION', { to_id: toId, latitude: lat, longitude: lng, label: label }, 'location_sent', { to_id: toId });
},
sendCard: function (params) {
var toId = (params && params.to_id) || '';
var cardWxid = (params && params.card_wxid) || '';
if (!toId || !cardWxid) return { success: false, error: '缺少 to_id 或 card_wxid' };
return _intentAction('com.workphone.SEND_CARD', { to_id: toId, card_wxid: cardWxid }, 'card_sent', { to_id: toId, card_wxid: cardWxid });
},
sendLink: function (params) {
var toId = (params && params.to_id) || '';
var url = (params && params.url) || '';
var title = (params && params.title) || '';
var desc = (params && params.description) || '';
if (!toId || !url) return { success: false, error: '缺少 to_id 或 url' };
var _linkText = (title ? title + '\n' : '') + (desc ? desc + '\n' : '') + url;
return _shareTextToWechat(_linkText, toId);
},
// ==================== H34 消息转发 ====================
forwardMessage: function (params) {
var msgSvrId = (params && params.msg_svr_id) || '';
var toId = (params && params.to_id) || '';
if (!msgSvrId || !toId) return { success: false, error: '缺少 msg_svr_id 或 to_id' };
// 真 Frida 转发文本类type=1从 DB 取原文 → 复用已验证 _sendMessageInternalSendMsgEvent + DB 回查)。
// 注意_intentAction 内部自带 Java.performNow禁止嵌套调用会死锁挂起故降级在 performNow 外执行。
var res = Java.performNow(function () {
try {
// 调用方可能传服务端 msgSvrId也可能传 get_messages 返回的本地 msgId两者都匹配。
var _id = String(msgSvrId).replace(/'/g, "''");
var sql = "SELECT type, content FROM message WHERE msgSvrId = '" + _id
+ "' OR msgId = '" + _id + "' LIMIT 1";
var rows = _execSQL(sql);
// 文本类WeChat type=1 为标准文本;本机 filehelper/SendMsgEvent 落库可能为 0。
// 仅当 type ∈ {0,1} 且 content 为非空纯文本(不以媒体 XML 前缀开头)才文本重发,避免误转媒体。
var _t = rows && rows.length > 0 ? String(rows[0].type) : '';
var _c = rows && rows.length > 0 ? (rows[0].content || '') : '';
var _isText = (_t === '1' || _t === '0') && _c && _c.charAt(0) !== '<';
if (_isText) {
var r = _sendMessageInternal(toId, _c, 'text');
if (r && r.success) {
r.forwarded_from = msgSvrId;
r.forward_kind = 'text_resend';
emitEvent('message_forwarded', { msg_svr_id: msgSvrId, to_id: toId, kind: 'text_resend' });
return r;
}
}
return { _fallback: true };
} catch (e) {
return { _fallback: true, _err: String(e) };
}
});
if (res && res._fallback) {
// 非文本/DB 未命中/真发送未验证 → companion intent 降级(在 performNow 外,避免嵌套死锁)
return _intentAction('com.workphone.FORWARD_MESSAGE', { msg_svr_id: msgSvrId, to_id: toId }, 'message_forwarded', { msg_svr_id: msgSvrId, to_id: toId, kind: 'non_text_or_db_miss' });
}
return res;
},
forwardMultiple: function (params) {
var msgSvrIds = (params && params.msg_svr_ids) || [];
var toId = (params && params.to_id) || '';
if (msgSvrIds.length === 0 || !toId) return { success: false, error: '缺少 msg_svr_ids 或 to_id' };
// 真 Frida 批量转发逐条复用文本转发真实路径DB 取原文 → _sendMessageInternal
// 文本条真发送成功才计 forwarded非文本/未验证条诚实计入 fallback不伪造整体成功。
// 注意_intentAction 自带 Java.performNow降级须在 performNow 外执行(避免嵌套死锁挂起)。
var agg = Java.performNow(function () {
var forwarded = 0, fallback = 0, details = [];
for (var i = 0; i < msgSvrIds.length; i++) {
var id = String(msgSvrIds[i]);
try {
var sql = "SELECT type, content FROM message WHERE msgSvrId = '"
+ id.replace(/'/g, "''") + "' LIMIT 1";
var rows = _execSQL(sql);
if (rows && rows.length > 0 && (rows[0].type === 1 || String(rows[0].type) === '1') && rows[0].content) {
var res = _sendMessageInternal(toId, rows[0].content, 'text');
if (res && res.success) { forwarded++; details.push({ msg_svr_id: id, kind: 'text_resend', success: true }); continue; }
}
} catch (e) { /* 落入 fallback */ }
fallback++;
details.push({ msg_svr_id: id, kind: 'non_text_or_db_miss', success: false });
}
return { forwarded: forwarded, fallback: fallback, details: details };
});
if (agg.forwarded > 0) {
return { success: true, forwarded: agg.forwarded, fallback: agg.fallback, total: msgSvrIds.length, details: agg.details };
}
// 无一条真发送成功 → 整体降级 intentperformNow 外,避免嵌套死锁)
return _intentAction('com.workphone.FORWARD_MULTIPLE', { msg_svr_ids: msgSvrIds.join(','), to_id: toId }, 'messages_forwarded', { count: msgSvrIds.length, to_id: toId, forwarded: 0, fallback: agg.fallback });
},
revokeMessage: function (params) {
var msgSvrId = (params && params.msg_svr_id) || '';
if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id' };
// 撤回资格预检DB · 复用已验证 _execSQL消息须存在、为本人发送(isSend=1)、且在 2 分钟窗口内。
// 不满足直接诚实返回原因,避免对不可撤回消息发 intent 假成功。真撤回 NetScene 仍待真机实现。
// 注意_intentAction 自带 Java.performNow资格通过后的 intent 须在 performNow 外执行(避免嵌套死锁)。
var pre = Java.performNow(function () {
try {
var _rid = String(msgSvrId).replace(/'/g, "''");
var sql = "SELECT isSend, createTime FROM message WHERE msgSvrId = '" + _rid
+ "' OR msgId = '" + _rid + "' LIMIT 1";
var rows = _execSQL(sql);
if (!rows || rows.length === 0) {
return { ok: false, result: { success: false, error: '消息不存在或已删除', error_code: 'msg_not_found', msg_svr_id: msgSvrId } };
}
var row = rows[0];
if (String(row.isSend) !== '1') {
return { ok: false, result: { success: false, error: '仅可撤回本人发送的消息', error_code: 'not_own_message', msg_svr_id: msgSvrId } };
}
var createMs = parseInt(row.createTime, 10) || 0;
var ageMs = Date.now() - createMs;
if (createMs > 0 && ageMs > 120000) {
return { ok: false, result: { success: false, error: '超过 2 分钟撤回窗口', error_code: 'revoke_window_expired',
age_seconds: Math.round(ageMs / 1000), msg_svr_id: msgSvrId } };
}
return { ok: true, row: row };
} catch (e) {
return { ok: false, result: { success: false, error: String(e), msg_svr_id: msgSvrId } };
}
});
if (!pre.ok) return pre.result;
// 真 Frida 撤回RevokeMsgEvent + IEvent.e8.0.x autogen参照 SendMsgEvent
var fridaRes = Java.performNow(function () {
return _revokeMessageByRevokeMsgEvent(msgSvrId, pre.row);
});
if (fridaRes && fridaRes.success && fridaRes.verified) {
emitEvent('message_revoked', { msg_svr_id: msgSvrId, method: 'revoke_msg_event' });
return fridaRes;
}
// 真 RPC 未 DB 确认 → 诚实返回,不伪造 intent 成功
return fridaRes || { success: false, error: 'revoke_frida_failed', msg_svr_id: msgSvrId, eligible: true };
},
// ==================== H35 注册/登录 ====================
registerAccount: function (params) {
var phone = (params && params.phone) || '';
var nickname = (params && params.nickname) || '';
var password = (params && params.password) || '';
if (!phone) return { success: false, error: '缺少 phone' };
return _intentAction('com.workphone.REGISTER', { phone: phone, nickname: nickname, password: password }, 'register_started', { phone: phone });
},
loginByPassword: function (params) {
var phone = (params && params.phone) || '';
var password = (params && params.password) || '';
if (!phone || !password) return { success: false, error: '缺少 phone 或 password' };
return _intentAction('com.workphone.LOGIN_PASSWORD', { phone: phone, password: password }, 'login_started', { phone: phone });
},
loginBySms: function (params) {
var phone = (params && params.phone) || '';
if (!phone) return { success: false, error: '缺少 phone' };
return _intentAction('com.workphone.LOGIN_SMS', { phone: phone }, 'sms_login_started', { phone: phone });
},
logout: function () {
return _intentAction('com.workphone.LOGOUT', {}, 'logout_started', {});
},
switchAccount: function (params) {
var wxid = (params && params.wxid) || '';
return _intentAction('com.workphone.SWITCH_ACCOUNT', { wxid: wxid }, 'account_switched', { wxid: wxid || 'picker' });
},
voiceCall: function (params) {
var uid = (params && (params.user_id || params.to_id)) || '';
return _intentAction('com.workphone.VOICE_CALL', { user_id: uid }, 'voice_call', { user_id: uid });
},
videoCall: function (params) {
var uid = (params && (params.user_id || params.to_id)) || '';
return _intentAction('com.workphone.VIDEO_CALL', { user_id: uid }, 'video_call', { user_id: uid });
},
shareRealTimeLocation: function (params) {
var uid = (params && (params.user_id || params.to_id)) || '';
return _intentAction('com.workphone.SHARE_RT_LOCATION', { user_id: uid }, 'rt_location', { user_id: uid });
},
downloadFile: function (params) {
var uid = (params && (params.user_id || params.to_id)) || '';
return _intentAction('com.workphone.DOWNLOAD_FILE', { user_id: uid }, 'file_downloaded', { user_id: uid });
},
massSend: function (params) {
var ids = (params && params.user_ids) || [];
var content = (params && params.content) || '';
if (!content || !ids.length) return { success: false, error: '缺少 content 或 user_ids' };
return _intentAction('com.workphone.MASS_SEND', { user_ids: ids.join(','), content: content }, 'mass_send', { count: ids.length });
},
replyComment: function (params) {
return _intentAction('com.workphone.REPLY_COMMENT', {
video_id: (params && params.video_id) || '',
comment_id: (params && params.comment_id) || '',
content: (params && params.content) || '',
}, 'reply_comment', params || {});
},
receivePayment: function (params) {
return _intentAction('com.workphone.RECEIVE_PAYMENT', {
amount: (params && params.amount) || '',
desc: (params && params.desc) || '',
}, 'payment_received', params || {});
},
autoRegister: function (params) {
var phone = (params && params.phone) || '';
var nickname = (params && params.nickname) || '卡若AI';
var password = (params && params.password) || '';
return _intentAction('com.workphone.AUTO_REGISTER', {
phone: phone, nickname: nickname, password: password
}, 'auto_register_started', { phone: phone, nickname: nickname });
},
checkLoginState: function () {
try {
return Java.performNow(function () {
var ctx = getCtx();
var am = ctx.getSystemService(jStr('activity'));
var sql = "SELECT value FROM userinfo WHERE id=2";
var rows = _execSQL(sql);
var hasUser = rows.length > 0 && rows[0].value && rows[0].value.length > 0;
return {
success: true,
logged_in: hasUser,
nickname: hasUser ? rows[0].value : '',
state: hasUser ? 'logged_in' : 'not_logged_in',
};
});
} catch (e) {
return { success: false, logged_in: false, state: 'unknown', error: String(e) };
}
},
getSimPhone: function () {
try {
return Java.performNow(function () {
var ctx = getCtx();
var TelephonyManager = Java.use('android.telephony.TelephonyManager');
var tm = Java.cast(ctx.getSystemService(jStr('phone')), TelephonyManager);
var line1 = '';
try { line1 = safeStr(tm.getLine1Number()); } catch (_) {}
var simOp = '';
try { simOp = safeStr(tm.getSimOperatorName()); } catch (_) {}
var simState = -1;
try { simState = tm.getSimState(); } catch (_) {}
return {
success: true,
phone: line1,
operator: simOp,
sim_state: simState,
found: line1.length >= 11,
};
});
} catch (e) {
return { success: false, phone: '', error: String(e) };
}
},
// ==================== H36 公众号 ====================
getOfficialAccounts: function (params) {
var limit = (params && params.limit) || 100;
try {
return Java.performNow(function () {
var sql = "SELECT username, nickname, conRemark FROM rcontact WHERE username LIKE 'gh_%' ORDER BY nickname LIMIT " + limit;
var rows = _execSQL(sql);
return { success: true, accounts: rows.map(function (r) { return { wxid: r.username, name: r.nickname, remark: r.conRemark }; }), count: rows.length };
});
} catch (e) {
return { success: false, error: String(e), accounts: [] };
}
},
followOfficialAccount: function (params) {
var accountId = (params && params.account_id) || '';
if (!accountId) return { success: false, error: '缺少 account_id' };
return _intentAction('com.workphone.FOLLOW_OA', { account_id: accountId }, 'oa_followed', { account_id: accountId });
},
unfollowOfficialAccount: function (params) {
var accountId = (params && params.account_id) || '';
if (!accountId) return { success: false, error: '缺少 account_id' };
return _intentAction('com.workphone.UNFOLLOW_OA', { account_id: accountId }, 'oa_unfollowed', { account_id: accountId });
},
getOfficialAccountArticles: function (params) {
var accountId = (params && params.account_id) || '';
var limit = (params && params.limit) || 10;
if (!accountId) return { success: false, error: '缺少 account_id' };
return _intentAction('com.workphone.GET_OA_ARTICLES', { account_id: accountId, limit: String(limit) }, null, {});
},
// ==================== H37 表情管理 ====================
sendEmoji: function (params) {
var toId = (params && params.to_id) || (params && params.user_id) || '';
var emojiMd5 = (params && params.emoji_md5) || (params && params.emoji_name) || '';
if (!toId || !emojiMd5) return { success: false, error: '缺少 to_id 或 emoji_md5' };
return _intentAction('com.workphone.SEND_EMOJI', { to_id: toId, emoji_md5: emojiMd5 }, 'emoji_sent', { to_id: toId });
},
addCustomEmoji: function (params) {
var imagePath = (params && params.image_path) || '';
if (!imagePath) return { success: false, error: '缺少 image_path' };
return _intentAction('com.workphone.ADD_EMOJI', { image_path: imagePath }, 'emoji_added', {});
},
// ==================== H38 浮窗/多任务 ====================
addToFloat: function (params) {
var wxid = (params && params.wxid) || '';
if (!wxid) return { success: false, error: '缺少 wxid' };
return _intentAction('com.workphone.ADD_FLOAT', { wxid: wxid }, 'float_added', { wxid: wxid });
},
removeFromFloat: function (params) {
var wxid = (params && params.wxid) || '';
if (!wxid) return { success: false, error: '缺少 wxid' };
return _intentAction('com.workphone.REMOVE_FLOAT', { wxid: wxid }, 'float_removed', { wxid: wxid });
},
// ==================== H39 设备信息 ====================
getDeviceInfo: function () {
try {
return Java.performNow(function () {
var Build = Java.use('android.os.Build');
var VERSION = Java.use('android.os.Build$VERSION');
return {
success: true,
device: {
model: safeStr(Build.MODEL.value),
brand: safeStr(Build.BRAND.value),
manufacturer: safeStr(Build.MANUFACTURER.value),
android_version: safeStr(VERSION.RELEASE.value),
sdk_int: VERSION.SDK_INT.value,
device: safeStr(Build.DEVICE.value),
product: safeStr(Build.PRODUCT.value),
fingerprint: safeStr(Build.FINGERPRINT.value),
display: safeStr(Build.DISPLAY.value),
},
};
});
} catch (e) {
return { success: false, error: String(e) };
}
},
getStorageInfo: function () {
try {
return Java.performNow(function () {
var StatFs = Java.use('android.os.StatFs');
var Environment = Java.use('android.os.Environment');
var stat = StatFs.$new(Environment.getDataDirectory().getPath());
var totalBytes = stat.getTotalBytes();
var freeBytes = stat.getFreeBytes();
return {
success: true,
storage: { total_mb: Math.round(totalBytes / 1048576), free_mb: Math.round(freeBytes / 1048576), used_percent: Math.round((1 - freeBytes / totalBytes) * 100) },
};
});
} catch (e) {
return { success: false, error: String(e) };
}
},
getNetworkInfo: function () {
try {
return Java.performNow(function () {
var ctx = getCtx();
var cm = ctx.getSystemService(jStr('connectivity'));
var ConnectivityManager = Java.use('android.net.ConnectivityManager');
var mgr = Java.cast(cm, ConnectivityManager);
var activeNet = mgr.getActiveNetworkInfo();
if (activeNet && activeNet.isConnected()) {
return { success: true, network: { type: safeStr(activeNet.getTypeName()), connected: true, detail: safeStr(activeNet.getExtraInfo()) } };
}
return { success: true, network: { type: 'none', connected: false } };
});
} catch (e) {
return { success: false, error: String(e) };
}
},
// ==================== 批量执行 ====================
batchExecute: function (params) {
var actions = (params && params.actions) || [];
if (actions.length === 0) return { success: false, error: '缺少 actions' };
var results = [];
for (var i = 0; i < actions.length; i++) {
var act = actions[i];
var fn = rpc.exports[act.action];
if (fn) {
try { results.push({ action: act.action, result: fn(act.params || {}) }); }
catch (e) { results.push({ action: act.action, result: { success: false, error: String(e) } }); }
} else {
results.push({ action: act.action, result: { success: false, error: '未知操作' } });
}
}
return { success: true, results: results, count: results.length };
},
// ==================== 奥创协议补全1077-1270 缺口 action====================
// 读类走真 DB(_execSQL message 表 · 字段与 revokeMessage/getMessages 同源),禁止臆造成功;
// 写/操作类走 _intentAction 诚实通道(无接收端返回 success:false与奥创 APK 协议一致)。
// —— 会话/消息读类(真 DB——
getConversations: function (params) {
var limit = (params && params.limit) || 30;
try {
return Java.performNow(function () {
// 每个 talker 取最新一条近似会话列表TriggerConversationPushTask 1232
var sql = "SELECT talker, MAX(createTime) AS lastTime, COUNT(*) AS msgCount "
+ "FROM message GROUP BY talker ORDER BY lastTime DESC LIMIT " + parseInt(limit, 10);
var rows = _execSQL(sql) || [];
return { success: true, conversations: rows, count: rows.length, source: 'db.message' };
});
} catch (e) { return { success: false, error: String(e) }; }
},
getMessageById: function (params) {
var id = (params && params.msg_svr_id) || '';
if (!id) return { success: false, error: '缺少 msg_svr_id' };
try {
return Java.performNow(function () {
var _id = String(id).replace(/'/g, "''");
var sql = "SELECT msgSvrId, msgId, talker, isSend, type, content, createTime "
+ "FROM message WHERE msgSvrId='" + _id + "' OR msgId='" + _id + "' LIMIT 1";
var rows = _execSQL(sql) || [];
if (!rows.length) return { success: false, error: '消息不存在', error_code: 'msg_not_found', msg_svr_id: id };
return { success: true, message: rows[0], source: 'db.message' };
});
} catch (e) { return { success: false, error: String(e) }; }
},
getMsgIdsByTime: function (params) {
var start = parseInt((params && params.start_time) || 0, 10);
var end = parseInt((params && params.end_time) || 0, 10) || Date.now();
try {
return Java.performNow(function () {
var sql = "SELECT msgSvrId, createTime FROM message WHERE createTime>=" + start
+ " AND createTime<=" + end + " ORDER BY createTime DESC LIMIT 500";
var rows = _execSQL(sql) || [];
return { success: true, msg_svr_ids: rows, count: rows.length, source: 'db.message' };
});
} catch (e) { return { success: false, error: String(e) }; }
},
getMessageRawXml: function (params) {
var id = (params && params.msg_svr_id) || '';
if (!id) return { success: false, error: '缺少 msg_svr_id' };
try {
return Java.performNow(function () {
var _id = String(id).replace(/'/g, "''");
var sql = "SELECT msgSvrId, type, content FROM message WHERE msgSvrId='" + _id + "' OR msgId='" + _id + "' LIMIT 1";
var rows = _execSQL(sql) || [];
if (!rows.length) return { success: false, error: '消息不存在', error_code: 'msg_not_found', msg_svr_id: id };
return { success: true, raw_xml: rows[0].content, type: rows[0].type, msg_svr_id: id, source: 'db.message' };
});
} catch (e) { return { success: false, error: String(e) }; }
},
// —— 加好友多场景(写类 · intent 诚实通道)——
addFriendInChatRoom: function (params) {
var room = (params && params.group_id) || '';
var wxid = (params && (params.wxid || params.user_id)) || '';
if (!room || !wxid) return { success: false, error: '缺少 group_id 或 wxid' };
return _intentAction('com.workphone.ADD_FRIEND_IN_ROOM', { group_id: room, wxid: wxid, verify: (params && params.verify_message) || '' }, 'add_friend_in_room', { group_id: room, wxid: wxid });
},
addFriendFromPhonebook: function (params) {
var phone = (params && params.phone) || '';
if (!phone) return { success: false, error: '缺少 phone' };
return _intentAction('com.workphone.ADD_FRIEND_PHONEBOOK', { phone: phone, verify: (params && params.verify_message) || '' }, 'add_friend_phonebook', { phone: phone });
},
addFriendWithScene: function (params) {
var wxid = (params && (params.wxid || params.user_id)) || '';
var scene = (params && params.scene) || '30';
if (!wxid) return { success: false, error: '缺少 wxid' };
return _intentAction('com.workphone.ADD_FRIEND_SCENE', { wxid: wxid, scene: String(scene), verify: (params && params.verify_message) || '' }, 'add_friend_scene', { wxid: wxid, scene: scene });
},
addFriendByCard: function (params) {
var card = (params && (params.card_wxid || params.wxid)) || '';
if (!card) return { success: false, error: '缺少 card_wxid' };
return _intentAction('com.workphone.ADD_FRIEND_CARD', { card_wxid: card, verify: (params && params.verify_message) || '' }, 'add_friend_card', { card_wxid: card });
},
joinGroupByQr: function (params) {
var qr = (params && (params.qr_content || params.image_url)) || '';
if (!qr) return { success: false, error: '缺少 qr_content 或 image_url' };
return _intentAction('com.workphone.JOIN_GROUP_QR', { qr: qr }, 'join_group_qr', { qr: qr });
},
// —— 群管理补(写类 · intent——
approveGroupInvite: function (params) {
var room = (params && params.group_id) || '';
var wxid = (params && (params.wxid || params.user_id)) || '';
if (!room) return { success: false, error: '缺少 group_id' };
return _intentAction('com.workphone.APPROVE_GROUP_INVITE', { group_id: room, wxid: wxid }, 'approve_group_invite', { group_id: room, wxid: wxid });
},
agreeJoinGroup: function (params) {
var room = (params && (params.group_id || params.invite_url)) || '';
if (!room) return { success: false, error: '缺少 group_id 或 invite_url' };
return _intentAction('com.workphone.AGREE_JOIN_GROUP', { group_id: room }, 'agree_join_group', { group_id: room });
},
// —— 聊天补(写类 · intent——
markConversationRead: function (params) {
var talker = (params && (params.conversation_id || params.user_id || params.talker)) || '';
if (!talker) return { success: false, error: '缺少 conversation_id' };
return _intentAction('com.workphone.MARK_READ', { talker: talker }, 'conversation_read', { talker: talker });
},
sendMultiImage: function (params) {
var talker = (params && (params.to_id || params.user_id)) || '';
var images = (params && params.images) || [];
if (!talker || !images.length) return { success: false, error: '缺少 to_id 或 images' };
return _intentAction('com.workphone.SEND_MULTI_IMAGE', { to_id: talker, images: images.join(',') }, 'send_multi_image', { to_id: talker, count: images.length });
},
sendJielong: function (params) {
var talker = (params && (params.to_id || params.group_id)) || '';
var content = (params && params.content) || '';
if (!talker || !content) return { success: false, error: '缺少 to_id 或 content' };
return _intentAction('com.workphone.SEND_JIELONG', { to_id: talker, content: content }, 'send_jielong', { to_id: talker });
},
sendFriendVerify: function (params) {
var wxid = (params && (params.wxid || params.user_id)) || '';
if (!wxid) return { success: false, error: '缺少 wxid' };
return _intentAction('com.workphone.SEND_FRIEND_VERIFY', { wxid: wxid, verify: (params && params.verify_message) || '' }, 'send_friend_verify', { wxid: wxid });
},
voiceToText: function (params) {
var id = (params && params.msg_svr_id) || '';
if (!id) return { success: false, error: '缺少 msg_svr_id' };
return _intentAction('com.workphone.VOICE_TO_TEXT', { msg_svr_id: id }, 'voice_to_text', { msg_svr_id: id });
},
// —— 朋友圈补(写/读混合 · intent 诚实)——
getFriendMoments: function (params) {
var wxid = (params && (params.wxid || params.user_id)) || '';
if (!wxid) return { success: false, error: '缺少 wxid' };
return _intentAction('com.workphone.GET_FRIEND_MOMENTS', { wxid: wxid }, 'get_friend_moments', { wxid: wxid });
},
getMomentDetail: function (params) {
var sid = (params && params.sns_id) || '';
if (!sid) return { success: false, error: '缺少 sns_id' };
return _intentAction('com.workphone.GET_MOMENT_DETAIL', { sns_id: sid }, 'get_moment_detail', { sns_id: sid });
},
deleteMomentComment: function (params) {
var sid = (params && params.sns_id) || '';
var cid = (params && params.comment_id) || '';
if (!sid) return { success: false, error: '缺少 sns_id' };
return _intentAction('com.workphone.DEL_MOMENT_COMMENT', { sns_id: sid, comment_id: cid }, 'del_moment_comment', { sns_id: sid });
},
replyMomentComment: function (params) {
var sid = (params && params.sns_id) || '';
var content = (params && params.content) || '';
if (!sid || !content) return { success: false, error: '缺少 sns_id 或 content' };
return _intentAction('com.workphone.REPLY_MOMENT_COMMENT', { sns_id: sid, content: content }, 'reply_moment_comment', { sns_id: sid });
},
stopMomentsPraise: function () {
return _intentAction('com.workphone.STOP_MOMENTS_PRAISE', {}, 'stop_moments_praise', {});
},
// —— 养号/清粉(写类 · intent——
startNurture: function (params) {
return _intentAction('com.workphone.START_NURTURE', { plan: (params && params.plan) || 'default' }, 'nurture_started', params || {});
},
stopNurture: function () {
return _intentAction('com.workphone.STOP_NURTURE', {}, 'nurture_stopped', {});
},
detectZombieFans: function () {
return _intentAction('com.workphone.DETECT_ZOMBIE', {}, 'detect_zombie_started', {});
},
stopDetectZombieFans: function () {
return _intentAction('com.workphone.STOP_DETECT_ZOMBIE', {}, 'detect_zombie_stopped', {});
},
// —— 查询类(写类 · intent 诚实)——
queryRedPacket: function (params) {
var id = (params && (params.red_packet_id || params.msg_svr_id)) || '';
if (!id) return { success: false, error: '缺少 red_packet_id' };
return _intentAction('com.workphone.QUERY_RED_PACKET', { red_packet_id: id }, 'query_red_packet', { red_packet_id: id });
},
getWechatLocation: function () {
return _intentAction('com.workphone.GET_WX_LOCATION', {}, 'wechat_location', {});
},
searchOfficialAccount: function (params) {
var kw = (params && params.keyword) || '';
if (!kw) return { success: false, error: '缺少 keyword' };
return _intentAction('com.workphone.SEARCH_BIZ', { keyword: kw }, 'search_biz', { keyword: kw });
},
// —— 手机操作(写类 · intent——
phoneAction: function (params) {
var act = (params && params.op) || '';
if (!act) return { success: false, error: '缺少 op(reboot/lock/screenshot 等)' };
return _intentAction('com.workphone.PHONE_ACTION', { op: act }, 'phone_action', { op: act });
},
};
// ============================================================
// § 5 内部实现 — 消息发送 (H17)
// ============================================================
function _sendMessageInternal(toId, content, msgType) {
var sent = false;
var sendMethod = 'failed';
var intentFailure = null;
var eventFailure = null;
var verifiedMessage = null;
// 方案 A: 直接调用微信内部消息发送类(按版本兼容表自动选择)
var candidateClasses = getCompat('send_message');
for (var i = 0; i < candidateClasses.length && !sent; i++) {
try {
var c = candidateClasses[i];
var MsgCls = Java.use(c.cls);
if (MsgCls) {
var m = c.sig ? MsgCls[c.method].overload.apply(MsgCls[c.method], c.sig) : MsgCls[c.method];
if (c.sig && c.sig.length === 3) {
m.call(MsgCls, jStr(toId), jStr(content), jStr(msgType === 'text' ? '' : msgType));
} else {
m.call(MsgCls, jStr(toId), jStr(content));
}
sent = true;
sendMethod = 'internal_' + c.cls.split('.').pop();
log('info', 'send', '消息通过内部方法发送: ' + c.cls);
emitEvent('message_sent', { to_id: toId, content: content, msg_type: msgType, method: sendMethod });
}
} catch (_) { continue; }
}
// 方案 B: WeChat 8.0.69 已验证存在 SendMsgEvent发布后必须 DB 回查命中,才算成功。
if (!sent && (msgType || 'text') === 'text') {
var eventResult = _sendMessageBySendMsgEvent(toId, content);
if (eventResult && eventResult.success && eventResult.verified) {
sent = true;
sendMethod = eventResult.method || 'send_msg_event';
verifiedMessage = eventResult.message || null;
log('info', 'send', '消息通过 SendMsgEvent 发送并 DB 验证');
emitEvent('message_sent', {
to_id: toId,
content: content,
msg_type: msgType,
method: sendMethod,
message_id: eventResult.message_id
});
} else {
eventFailure = eventResult || { success: false, error: 'send_msg_event_failed' };
log('warn', 'send', 'SendMsgEvent 未验证: ' + safeStr(eventFailure.error));
}
}
// 方案 C: Intent 广播降级;无真实接收端时不得把广播计为发送成功。
if (!sent) {
var intentResult = _intentAction(
'com.workphone.SEND_MESSAGE',
{ to_id: toId, content: content, msg_type: msgType },
'message_sent',
{ to_id: toId, content: content, msg_type: msgType, method: 'intent_broadcast' }
);
if (intentResult && intentResult.success) {
sent = true;
sendMethod = 'intent_broadcast';
log('info', 'send', '消息通过 Intent 广播发送');
} else {
intentFailure = intentResult || { success: false, error: 'intent_dispatch_failed' };
log('warn', 'send', 'Intent 发送未验证: ' + safeStr(intentFailure.error));
}
}
var messageId = sent ? ((verifiedMessage && verifiedMessage.id) || generateId('msg')) : null;
if (sent) {
var talker = (toId === '文件传输助手' || toId === 'File Transfer') ? 'filehelper' : toId;
_messageBuffer.push({
msg_svr_id: messageId,
msg_type: msgType || 'text',
from_id: talker,
content: content,
timestamp: new Date().toISOString(),
is_send: true,
});
if (_messageBuffer.length > CONFIG.MESSAGE_BUFFER_MAX) {
_messageBuffer = _messageBuffer.slice(-Math.floor(CONFIG.MESSAGE_BUFFER_MAX * 0.6));
}
}
var result = {
success: sent,
message_id: messageId,
method: sendMethod,
verified: sent,
to_id: toId,
};
if (!sent && intentFailure) {
result.error = intentFailure.error || 'send_failed';
result.action = intentFailure.action || 'com.workphone.SEND_MESSAGE';
result.hint = intentFailure.hint || '';
if (eventFailure) {
result.event_error = eventFailure.error || 'send_msg_event_failed';
result.event_published = !!eventFailure.published;
result.event_verified = !!eventFailure.verified;
result.event_method = eventFailure.method || 'send_msg_event';
}
} else if (!sent && eventFailure) {
result.error = eventFailure.error || 'send_msg_event_failed';
result.event_published = !!eventFailure.published;
result.event_verified = !!eventFailure.verified;
result.event_method = eventFailure.method || 'send_msg_event';
}
return result;
}
function _normalizeTalkerId(toId) {
if (toId === '文件传输助手' || toId === 'File Transfer') return 'filehelper';
return toId;
}
function _calcSendMsgScene(toId) {
try {
var A2 = Java.use('vn0.a2');
return A2.y(jStr(toId));
} catch (_) {
return 0;
}
}
function _sendMessageBySendMsgEvent(toId, content) {
var published = false;
var scene = 0;
try {
var SendMsgEvent = Java.use('com.tencent.mm.autogen.events.SendMsgEvent');
var IEvent = Java.use('com.tencent.mm.sdk.event.IEvent');
var event = SendMsgEvent.$new();
var payload = event.g.value;
scene = _calcSendMsgScene(toId);
payload.a.value = jStr(toId);
payload.b.value = jStr(content);
payload.c.value = scene;
payload.d.value = 0;
published = !!IEvent.e.overload().call(event);
var verify = _findSentMessageInDB(toId, content, 3000);
if (verify.success) {
return {
success: true,
method: 'send_msg_event',
published: published,
verified: true,
message_id: verify.message_id,
message: verify.message,
scene: scene
};
}
return {
success: false,
method: 'send_msg_event',
published: published,
verified: false,
scene: scene,
error: verify.error || 'db_verify_timeout',
verify: verify
};
} catch (e) {
return {
success: false,
method: 'send_msg_event',
published: published,
verified: false,
scene: scene,
error: String(e)
};
}
}
function _verifyMessageRevokedInDB(msgSvrId, timeoutMs) {
var _id = String(msgSvrId).replace(/'/g, "''");
var deadline = Date.now() + (timeoutMs || 5000);
var Thread = null;
try { Thread = Java.use('java.lang.Thread'); } catch (_) {}
do {
var sql = "SELECT type, content FROM message WHERE msgSvrId = '" + _id
+ "' OR msgId = '" + _id + "' LIMIT 1";
var rows = _execSQL(sql);
if (rows && rows.length > 0) {
var t = String(rows[0].type);
var c = rows[0].content || '';
if (t === '10000' || c.indexOf('撤回') !== -1 || c.indexOf('recalled') !== -1) {
return { success: true, verified: true, type: t, content_preview: String(c).substring(0, 80) };
}
}
if (Thread) Thread.sleep(300);
else Java.use('java.lang.Thread').sleep(300);
} while (Date.now() < deadline);
return { success: false, verified: false, error: 'revoke_db_verify_timeout' };
}
function _revokeMessageByRevokeMsgEvent(msgSvrId, row) {
var published = false;
try {
var RevokeMsgEvent = Java.use('com.tencent.mm.autogen.events.RevokeMsgEvent');
var IEvent = Java.use('com.tencent.mm.sdk.event.IEvent');
var event = RevokeMsgEvent.$new();
var payload = event.g.value;
var talker = (row && row.talker) ? String(row.talker) : '';
var svrId = (row && row.msgSvrId) ? String(row.msgSvrId) : String(msgSvrId);
var localId = (row && row.msgId) ? String(row.msgId) : String(msgSvrId);
var createTime = parseInt((row && row.createTime) || '0', 10) || 0;
// autogen payload 字段8.0.69a=talker b=msgSvrId(long) c=createTime d=msgId
try { if (payload.a !== undefined) payload.a.value = jStr(talker); } catch (_) {}
try { if (payload.b !== undefined) payload.b.value = jLong(svrId); } catch (_) {}
try { if (payload.c !== undefined) payload.c.value = createTime; } catch (_) {}
try { if (payload.d !== undefined) payload.d.value = jLong(localId); } catch (_) {}
published = !!IEvent.e.overload().call(event);
var verify = _verifyMessageRevokedInDB(msgSvrId, 5000);
if (verify.verified) {
return {
success: true,
method: 'revoke_msg_event',
published: published,
verified: true,
msg_svr_id: msgSvrId,
talker: talker,
verify: verify,
channel: 'hook'
};
}
return {
success: false,
method: 'revoke_msg_event',
published: published,
verified: false,
msg_svr_id: msgSvrId,
talker: talker,
error: verify.error || 'revoke_not_confirmed_in_db',
payload_class: safeStr(payload.getClass().getName())
};
} catch (e) {
return {
success: false,
method: 'revoke_msg_event',
published: published,
verified: false,
error: String(e)
};
}
}
function _findSentMessageInDB(toId, content, timeoutMs) {
var talker = _normalizeTalkerId(toId);
var deadline = Date.now() + (timeoutMs || 2000);
var Thread = null;
try { Thread = Java.use('java.lang.Thread'); } catch (_) {}
var sql = "SELECT msgId AS id, type, talker, content, createTime, isSend FROM message"
+ " WHERE talker='" + _escapeSql(talker) + "'"
+ " AND isSend=1"
+ " AND content='" + _escapeSql(content) + "'"
+ " ORDER BY createTime DESC LIMIT 1";
do {
var rows = _execSQL(sql);
if (rows && rows.length > 0) {
return {
success: true,
message_id: rows[0].id || rows[0].msgId || null,
message: rows[0],
talker: talker
};
}
if (Date.now() >= deadline) break;
if (Thread) Thread.sleep(250);
} while (Date.now() < deadline);
return {
success: false,
error: 'db_verify_timeout',
talker: talker,
diag: _lastDbDiag || {}
};
}
// ============================================================
// § 6 内部实现 — 数据库读取 (H15/H16)
// ============================================================
function _getMessagesFromDB(conversationId, limit, offset) {
limit = parseInt(limit, 10) || 3000;
if (limit < 1) limit = 3000;
if (limit > 5000) limit = 5000;
offset = parseInt(offset, 10) || 0;
if (offset < 0) offset = 0;
// DB talker 用 wxid显示名「文件传输助手」→ filehelper
if (conversationId === '文件传输助手' || conversationId === 'File Transfer') {
conversationId = 'filehelper';
}
var where = conversationId
? " WHERE talker='" + conversationId.replace(/'/g, "''") + "'"
: '';
var sql = 'SELECT msgId, type, talker, content, createTime, isSend FROM message'
+ where + ' ORDER BY createTime DESC LIMIT ' + limit + ' OFFSET ' + offset;
var rows = _execSQL(sql);
var totalCount = null;
try {
var cntRows = _execSQL('SELECT count(*) AS n FROM message' + where);
totalCount = cntRows && cntRows[0] ? parseInt(cntRows[0].n, 10) : null;
} catch (e) { /* ignore count failure */ }
return {
success: true,
messages: rows.map(function (r) {
return {
id: r.msgId, type: r.type, from_id: r.talker,
content: r.content, timestamp: r.createTime,
is_send: r.isSend === '1',
};
}),
count: rows.length,
returned_count: rows.length,
total_count: totalCount,
requested_limit: limit,
offset: offset,
has_more: totalCount !== null ? totalCount > offset + rows.length : rows.length >= limit,
};
}
function _getContactsFromDB(limit, offset) {
limit = parseInt(limit, 10) || 10000;
if (limit < 1) limit = 10000;
if (limit > 20000) limit = 20000;
offset = parseInt(offset, 10) || 0;
if (offset < 0) offset = 0;
var labelMap = {};
try {
var labelRows = _execSQL("SELECT labelId, labelName FROM contactlabelinfo");
labelRows.forEach(function (r) {
labelMap[r.labelId] = r.labelName;
});
} catch (e) { /* 部分版本无标签表 */ }
var contactLabelMap = {};
try {
// 8.0.69 列名为 contactLabelIds旧版 labelidlist
var clRows = _execSQL(
"SELECT username, contactLabelIds FROM rcontact WHERE contactLabelIds IS NOT NULL AND contactLabelIds != ''"
);
clRows.forEach(function (r) {
if (!r.contactLabelIds) return;
var ids = String(r.contactLabelIds).split(",").filter(function (id) { return id; });
contactLabelMap[r.username] = ids.map(function (id) {
return labelMap[id] || id;
}).filter(function (n) { return n; });
});
} catch (e) { /* ignore */ }
// 8.0.69 兼容:先探测 rcontact 实际列,只 SELECT 存在的列(避免缺列如 signature/country 致整查询返 0
var _wantCols = ["username", "nickname", "conRemark", "type", "alias", "imgFlag", "sex", "country", "province", "city", "signature"];
var _availCols = {};
try {
var _ti = _execSQL("PRAGMA table_info(rcontact)");
(_ti || []).forEach(function (c) { if (c && c.name) _availCols[c.name] = true; });
} catch (ept) { /* PRAGMA 不支持时退回全列 */ }
var _useCols = Object.keys(_availCols).length > 0
? _wantCols.filter(function (c) { return _availCols[c]; })
: _wantCols;
if (_useCols.indexOf("username") < 0) _useCols.unshift("username");
var _cols = "SELECT " + _useCols.join(", ") + " FROM rcontact ";
var _excludeSys = "username NOT LIKE '%@chatroom' AND username NOT LIKE 'gh_%' "
+ "AND username NOT IN ('filehelper','fmessage','medianote','floatbottle','newsapp','weixin','qmessage','tmessage') ";
var page = " LIMIT " + limit + " OFFSET " + offset;
var sql = _cols + "WHERE type NOT IN (0, 33) AND verifyFlag=0 AND " + _excludeSys
+ "ORDER BY conRemark, nickname" + page;
var rows = _execSQL(sql);
var _diag = { strict: (rows || []).length, use_cols: _useCols, avail_cols: Object.keys(_availCols), requested_limit: limit, offset: offset };
try {
var rawCnt = _execSQL("SELECT count(*) AS n FROM rcontact");
_diag.rcontact_total = rawCnt && rawCnt[0] ? parseInt(rawCnt[0].n, 10) : null;
} catch (e0a) { _diag.rcontact_total_err = String(e0a); }
try {
var filteredCnt = _execSQL("SELECT count(*) AS n FROM rcontact WHERE type NOT IN (0, 33) AND verifyFlag=0 AND " + _excludeSys);
_diag.filtered_total = filteredCnt && filteredCnt[0] ? parseInt(filteredCnt[0].n, 10) : null;
} catch (e0b) { _diag.filtered_total_err = String(e0b); }
// 8.0.69 兼容回退①:严格过滤(type/verifyFlag)为空时,放宽为仅排除系统/群/公众号
if (!rows || rows.length === 0) {
var sqlRelax = _cols + "WHERE verifyFlag=0 AND " + _excludeSys
+ "ORDER BY conRemark, nickname" + page;
var relaxed = _execSQL(sqlRelax);
_diag.relax_verifyflag = (relaxed || []).length;
if (relaxed && relaxed.length > 0) {
log('info', 'contacts', '严格过滤空,回退宽过滤命中 ' + relaxed.length + ' 条');
rows = relaxed;
}
}
// 8.0.69 兼容回退②:连 verifyFlag=0 也空时,丢弃 verifyFlag仅排除系统/群/公众号(部分版本好友 verifyFlag!=0
if (!rows || rows.length === 0) {
var sqlNoVerify = _cols + "WHERE " + _excludeSys + "ORDER BY conRemark, nickname" + page;
var noverify = _execSQL(sqlNoVerify);
_diag.relax_noverify = (noverify || []).length;
if (noverify && noverify.length > 0) {
log('info', 'contacts', 'verifyFlag 过滤空,丢弃 verifyFlag 命中 ' + noverify.length + ' 条');
rows = noverify;
}
}
// 诊断rcontact 原始总行数 + 字段探测(定位 8.0.69 读空根因,写入返回的 _diag
if (!rows || rows.length === 0) {
try { var rawCnt = _execSQL("SELECT count(*) AS n FROM rcontact"); _diag.rcontact_total = rawCnt && rawCnt[0] ? rawCnt[0].n : null; } catch (e0) { _diag.rcontact_total_err = String(e0); }
try { var vf = _execSQL("SELECT verifyFlag, count(*) AS n FROM rcontact GROUP BY verifyFlag"); _diag.verifyflag_dist = vf; } catch (e1) { _diag.verifyflag_err = String(e1); }
try { var tp = _execSQL("SELECT type, count(*) AS n FROM rcontact GROUP BY type LIMIT 20"); _diag.type_dist = tp; } catch (e2) { _diag.type_err = String(e2); }
}
var sexMap = { "0": "unknown", "1": "male", "2": "female" };
var contacts = rows.map(function (r) {
var nick = r.nickname || "";
var remark = r.conRemark || "";
var alias = r.alias || "";
var wxid = r.username || "";
var display = nick || remark || alias || wxid;
return {
wxid: wxid,
user_id: wxid,
nickname: nick,
display_name: display,
remark: remark,
alias: alias,
wechat_id: alias,
type: r.type,
sex: sexMap[String(r.sex)] || "unknown",
country: r.country || "",
province: r.province || "",
city: r.city || "",
signature: r.signature || "",
tags: contactLabelMap[wxid] || [],
has_avatar: (r.imgFlag || 0) > 0,
};
});
_contactCache = contacts;
return {
success: true,
contacts: contacts,
count: contacts.length,
returned_count: contacts.length,
total_count: _diag.filtered_total,
raw_total_count: _diag.rcontact_total,
requested_limit: limit,
offset: offset,
has_more: _diag.filtered_total !== null && _diag.filtered_total !== undefined
? _diag.filtered_total > offset + contacts.length
: contacts.length >= limit,
_diag: _diag
};
}
function _getContactInfoFromDB(wxid) {
var sql = "SELECT username, nickname, conRemark, type, alias FROM rcontact"
+ " WHERE username='" + wxid.replace(/'/g, "''") + "'";
var rows = _execSQL(sql);
if (rows.length > 0) return { success: true, contact: rows[0] };
return { success: false, error: '未找到联系人: ' + wxid };
}
// ============================================================
// § 7 内部实现 — 好友管理 (H18/H19)
// ============================================================
function _getFriendRequestsFromDB(limit) {
var sql = "SELECT msgId, type, talker, content, createTime FROM message"
+ " WHERE type = 10002"
+ " ORDER BY createTime DESC LIMIT " + limit;
var rows = _execSQL(sql);
var requests = rows.map(function (r) {
var info = _parseFriendRequestXml(r.content);
return {
msg_id: r.msgId,
from_id: info.fromusername || r.talker,
from_nickname: info.fromnickname || '',
content: info.content || '',
encrypt_username: info.encryptusername || '',
ticket: info.ticket || '',
source: info.sourceType || '',
timestamp: r.createTime,
};
});
return { success: true, requests: requests, count: requests.length };
}
function _parseFriendRequestXml(xml) {
var info = {};
if (!xml) return info;
var fields = ['fromusername', 'fromnickname', 'content', 'encryptusername', 'ticket', 'sourceType'];
for (var i = 0; i < fields.length; i++) {
var re = new RegExp(fields[i] + '="([^"]*)"');
var m = xml.match(re);
if (m) info[fields[i]] = m[1];
}
return info;
}
function _addFriendInternal(userId, message, sourceType) {
return _intentAction(
'com.workphone.ADD_FRIEND',
{ user_id: userId, message: message, source_type: sourceType },
'friend_request_sent',
{ user_id: userId, message: message, source_type: sourceType }
);
}
function _acceptFriendInternal(encryptUserName, ticket) {
return _intentAction(
'com.workphone.ACCEPT_FRIEND',
{ encrypt_username: encryptUserName, ticket: ticket },
'friend_accepted',
{ encrypt_username: encryptUserName }
);
}
function _deleteFriendInternal(wxid) {
return _intentAction(
'com.workphone.DELETE_FRIEND',
{ wxid: wxid },
'friend_deleted',
{ wxid: wxid }
);
}
function _setRemarkInternal(wxid, remark) {
return _intentAction('com.workphone.SET_REMARK', { wxid: wxid, remark: remark });
}
// ============================================================
// § 8 内部实现 — 群管理 (H22)
// ============================================================
function _getGroupsFromDB(limit, offset) {
limit = parseInt(limit, 10) || 100;
if (limit < 1) limit = 100;
if (limit > 5000) limit = 5000;
offset = parseInt(offset, 10) || 0;
if (offset < 0) offset = 0;
var sql = "SELECT username, nickname FROM rcontact"
+ " WHERE username LIKE '%@chatroom'"
+ " ORDER BY nickname LIMIT " + limit + " OFFSET " + offset;
var rows = _execSQL(sql);
var totalCount = null;
try {
var cntRows = _execSQL("SELECT count(*) AS n FROM rcontact WHERE username LIKE '%@chatroom'");
totalCount = cntRows && cntRows[0] ? parseInt(cntRows[0].n, 10) : null;
} catch (e) { /* ignore count failure */ }
return {
success: true,
groups: rows.map(function (r) {
return { group_id: r.username, name: r.nickname };
}),
count: rows.length,
returned_count: rows.length,
total_count: totalCount,
requested_limit: limit,
offset: offset,
has_more: totalCount !== null ? totalCount > offset + rows.length : rows.length >= limit,
};
}
function _getLabelsFromDB(limit, offset) {
limit = parseInt(limit, 10) || 5000;
if (limit < 1) limit = 5000;
if (limit > 20000) limit = 20000;
offset = parseInt(offset, 10) || 0;
if (offset < 0) offset = 0;
var tables = [
{ table: "contactlabel", id: "labelID", name: "labelName" },
{ table: "contactlabelinfo", id: "labelId", name: "labelName" }
];
var rows = [];
var totalCount = null;
var sourceTable = "";
for (var i = 0; i < tables.length; i++) {
var t = tables[i];
try {
rows = _execSQL(
"SELECT " + t.id + " AS labelID, " + t.name + " AS labelName FROM " + t.table
+ " ORDER BY CAST(" + t.id + " AS INTEGER), " + t.id
+ " LIMIT " + limit + " OFFSET " + offset
);
var cntRows = _execSQL("SELECT count(*) AS n FROM " + t.table);
totalCount = cntRows && cntRows[0] ? parseInt(cntRows[0].n, 10) : null;
sourceTable = t.table;
break;
} catch (e) {
rows = [];
totalCount = null;
}
}
return {
success: true,
labels: rows.map(function (r) { return { id: r.labelID || r.labelId, name: r.labelName || "" }; }),
count: rows.length,
returned_count: rows.length,
total_count: totalCount,
requested_limit: limit,
offset: offset,
has_more: totalCount !== null ? totalCount > offset + rows.length : rows.length >= limit,
source_table: sourceTable,
};
}
function _getGroupMembersFromDB(groupId) {
var sql = "SELECT memberlist, displayname FROM chatroom"
+ " WHERE chatroomname='" + groupId.replace(/'/g, "''") + "'";
var rows = _execSQL(sql);
if (rows.length === 0) return { success: false, error: '群不存在: ' + groupId, members: [] };
var memberList = (rows[0].memberlist || '').split(';').filter(function (m) { return m; });
var displayNames = (rows[0].displayname || '').split(';');
var members = memberList.map(function (wxid, idx) {
return {
wxid: wxid,
display_name: (displayNames[idx] || '').trim(),
};
});
return { success: true, group_id: groupId, members: members, count: members.length };
}
function _getGroupInfoFromDB(groupId) {
var sql = "SELECT chatroomname, memberlist, displayname, roomowner, chatroomnick FROM chatroom"
+ " WHERE chatroomname='" + groupId.replace(/'/g, "''") + "'";
var rows = _execSQL(sql);
if (rows.length === 0) return { success: false, error: '群不存在: ' + groupId };
var r = rows[0];
var memberList = (r.memberlist || '').split(';').filter(function (m) { return m; });
return {
success: true,
group: {
group_id: r.chatroomname,
name: r.chatroomnick || '',
owner: r.roomowner || '',
member_count: memberList.length,
members: memberList,
},
};
}
function _createGroupInternal(memberIds, topic) {
return _intentAction(
'com.workphone.CREATE_GROUP',
{ member_ids: memberIds.join(','), topic: topic || '' },
'group_created',
{ member_ids: memberIds, topic: topic }
);
}
function _inviteToGroupInternal(groupId, memberIds) {
return _intentAction(
'com.workphone.INVITE_GROUP',
{ group_id: groupId, member_ids: memberIds.join(',') },
'group_invite',
{ group_id: groupId, member_ids: memberIds }
);
}
function _removeFromGroupInternal(groupId, memberIds) {
return _intentAction(
'com.workphone.REMOVE_GROUP_MEMBER',
{ group_id: groupId, member_ids: memberIds.join(',') },
'group_member_removed',
{ group_id: groupId, member_ids: memberIds }
);
}
function _setGroupAnnouncementInternal(groupId, announcement) {
return _intentAction(
'com.workphone.SET_GROUP_ANNOUNCEMENT',
{ group_id: groupId, announcement: announcement }
);
}
function _setGroupNameInternal(groupId, name) {
return _intentAction('com.workphone.SET_GROUP_NAME', { group_id: groupId, name: name });
}
function _quitGroupInternal(groupId) {
return _intentAction(
'com.workphone.QUIT_GROUP',
{ group_id: groupId },
'group_quit',
{ group_id: groupId }
);
}
// ============================================================
// § 9 内部实现 — 朋友圈 (H20/H21)
// ============================================================
// 取当前栈顶 ActivityActivityThread.mActivities 遍历 paused=false
function _getTopActivity() {
var top = null;
var lastAny = null; // 兜底:最后一个可取到的 activity即便 paused
var preferred = null; // 优先:分享/选聊天/搜索类 activity按类名匹配
try {
var ActivityThread = Java.use('android.app.ActivityThread');
var at = ActivityThread.currentActivityThread();
var mActivities = at.mActivities.value; // ArrayMap<IBinder, ActivityClientRecord>
var vals = mActivities.values().toArray();
for (var i = 0; i < vals.length; i++) {
var rec = Java.cast(vals[i], Java.use('android.app.ActivityThread$ActivityClientRecord'));
try {
var act = rec.activity.value;
if (act) {
lastAny = act;
var cn = '';
try { cn = act.getClass().getName(); } catch (_) {}
// 选聊天/分享/搜索界面优先MIUI 下分享瞬间主 activity 可能短暂 paused
if (cn && (cn.indexOf('ShareImgUI') >= 0 || cn.indexOf('SelectConversationUI') >= 0 ||
cn.indexOf('ShareToTimeLineUI') >= 0 || cn.indexOf('Search') >= 0 ||
cn.indexOf('SnsUploadUI') >= 0)) {
preferred = act;
}
}
if (!rec.paused.value) { top = act; break; }
} catch (_) {}
}
} catch (e) {
log('warn', 'sns', '_getTopActivity 失败: ' + e);
}
// 优先级非paused栈顶 > 分享/选聊天类 > 任意最后一个best-effort修「无栈顶 Activity」
return top || preferred || lastAny;
}
// 递归遍历 view 树,找文本匹配且可点击的 View 并 performClick
function _clickViewByText(root, texts) {
if (!root) return false;
try {
var TextView = Java.use('android.widget.TextView');
var ViewGroup = Java.use('android.view.ViewGroup');
// 当前节点是 TextView 且文本命中
try {
var asTv = Java.cast(root, TextView);
var t = asTv.getText();
var s = t ? t.toString() : '';
for (var k = 0; k < texts.length; k++) {
if (s.indexOf(texts[k]) >= 0) {
var clickTarget = root;
// 向上找可点击祖先(按钮文字常包在可点击容器内)
var p = root;
for (var up = 0; up < 4 && p; up++) {
if (p.isClickable()) { clickTarget = p; break; }
p = p.getParent();
}
if (clickTarget && clickTarget.performClick) {
clickTarget.performClick();
log('info', 'sns', '点击发表按钮: "' + s + '"');
return true;
}
}
}
} catch (_) {}
// 递归子节点
try {
var vg = Java.cast(root, ViewGroup);
var n = vg.getChildCount();
for (var c = 0; c < n; c++) {
if (_clickViewByText(vg.getChildAt(c), texts)) return true;
}
} catch (_) {}
} catch (_) {}
return false;
}
// 遍历找搜索框 EditText 并用 Frida setText 输入(不经 IME/INJECT_EVENTS直接触发 TextWatcher 过滤)。
// 用于微信"选择一个聊天"(ShareImgUI):目标联系人常不在最近聊天可见列表,需搜索过滤后才渲染。
function _findEditTextAndSetText(root, text) {
if (!root) return false;
try {
var EditText = Java.use('android.widget.EditText');
var ViewGroup = Java.use('android.view.ViewGroup');
try {
var et = Java.cast(root, EditText);
try { et.requestFocus(); } catch (_) {}
et.setText.overload('java.lang.CharSequence').call(et, text);
log('info', 'send', '搜索框输入: ' + text);
return true;
} catch (_) {}
try {
var vg = Java.cast(root, ViewGroup);
var n = vg.getChildCount();
for (var c = 0; c < n; c++) {
if (_findEditTextAndSetText(vg.getChildAt(c), text)) return true;
}
} catch (_) {}
} catch (_) {}
return false;
}
// 朋友圈发布界面打开后自动点「发表」best-effort失败由 need_confirm_send 兜底)
function _autoClickPublish(delayMs) {
try {
var Handler = Java.use('android.os.Handler');
var Looper = Java.use('android.os.Looper');
var handler = Handler.$new(Looper.getMainLooper());
var Runnable = Java.use('java.lang.Runnable');
var R = Java.registerClass({
name: 'com.workphone.PublishClicker' + Date.now(),
implements: [Runnable],
methods: {
run: function () {
try {
var act = _getTopActivity();
if (!act) { log('warn', 'sns', '自动发表:无栈顶 Activity'); return; }
var decor = act.getWindow().getDecorView();
var ok = _clickViewByText(decor, ['发表', '完成', 'Post', 'Send']);
emitEvent('moments_publish_click', { clicked: ok });
log('info', 'sns', '自动发表点击结果: ' + ok);
} catch (e) {
log('warn', 'sns', '自动发表执行失败: ' + e);
}
}
}
});
handler.postDelayed(R.$new(), delayMs || 2000);
return true;
} catch (e) {
log('warn', 'sns', '_autoClickPublish 调度失败: ' + e);
return false;
}
}
// 分享后 u2 自动选联系人 + 发送best-effort两段延迟点击。
// 第一段点联系人文本(昵称/备注/"文件传输助手"),第二段点"发送"确认。
// wxid → 微信"选择一个聊天"列表显示名候选解析。
// 真机实测to_id=filehelper 在列表里显示为"文件传输助手",按 wxid 文本点不中 → 必须解析显示名。
// 顺序DB 备注(conRemark) > 特殊账号映射 > DB 昵称(nickname) > 原 wxid 兜底。
function _resolveContactDisplayNames(toId) {
var cands = [];
var special = {
'filehelper': ['文件传输助手', 'File Transfer'],
'fmessage': ['服务通知'],
'medianote': ['语音记事本'],
'weixin': ['微信团队', 'WeChat Team'],
'floatbottle': ['漂流瓶'],
'newsapp': ['腾讯新闻'],
};
// DB 查备注/昵称(备注优先,最贴近列表显示)
try {
Java.performNow(function () {
var esc = String(toId).replace(/'/g, "''");
var rows = _execSQL("SELECT nickname, conRemark FROM rcontact WHERE username='" + esc + "' LIMIT 1");
if (rows && rows.length > 0) {
if (rows[0].conRemark) cands.push(rows[0].conRemark);
if (rows[0].nickname && rows[0].nickname !== rows[0].conRemark) cands.push(rows[0].nickname);
}
});
} catch (e) { log('warn', 'send', '解析显示名 DB 查询失败: ' + e); }
if (special[toId]) cands = cands.concat(special[toId]);
cands.push(toId); // 微信号/wxid 兜底
var seen = {}, out = [];
for (var i = 0; i < cands.length; i++) { var v = cands[i]; if (v && !seen[v]) { seen[v] = 1; out.push(v); } }
return out;
}
function _autoSelectAndSend(toId, delayMs) {
try {
var nameCandidates = _resolveContactDisplayNames(toId);
log('info', 'send', '选联系人候选显示名: ' + JSON.stringify(nameCandidates));
var Handler = Java.use('android.os.Handler');
var Looper = Java.use('android.os.Looper');
var handler = Handler.$new(Looper.getMainLooper());
var Runnable = Java.use('java.lang.Runnable');
var d = delayMs || 2500;
// 第-1段点"搜索"伪框进真搜索界面ShareImgUI 顶部搜索是 TextView 伪框,点击才出 EditText
var RC = Java.registerClass({
name: 'com.workphone.ShareSearchOpener' + Date.now(),
implements: [Runnable],
methods: {
run: function () {
try {
var act = _getTopActivity();
if (!act) return;
var decor = act.getWindow().getDecorView();
var ok = _clickViewByText(decor, ['搜索', 'Search']);
log('info', 'send', '点搜索伪框 = ' + ok);
} catch (e) { log('warn', 'send', '点搜索框失败: ' + e); }
}
}
});
handler.postDelayed(RC.$new(), d);
// 第〇段:搜索框输入显示名过滤(最近聊天列表懒加载,目标常不在可见项)
var RS = Java.registerClass({
name: 'com.workphone.ShareSearcher' + Date.now(),
implements: [Runnable],
methods: {
run: function () {
try {
var act = _getTopActivity();
if (!act) return;
var decor = act.getWindow().getDecorView();
var kw = nameCandidates[0];
var typed = _findEditTextAndSetText(decor, kw);
emitEvent('share_search_input', { keyword: kw, typed: typed });
log('info', 'send', '分享搜索输入 "' + kw + '" = ' + typed);
} catch (e) { log('warn', 'send', '搜索输入失败: ' + e); }
}
}
});
handler.postDelayed(RS.$new(), d + 1200);
var R1 = Java.registerClass({
name: 'com.workphone.ContactSelector' + Date.now(),
implements: [Runnable],
methods: {
run: function () {
try {
var act = _getTopActivity();
if (!act) { log('warn', 'send', '选联系人:无栈顶 Activity'); return; }
var decor = act.getWindow().getDecorView();
var ok = _clickViewByText(decor, nameCandidates);
emitEvent('share_contact_click', { to_id: toId, candidates: nameCandidates, clicked: ok });
log('info', 'send', '分享选联系人点击 ' + JSON.stringify(nameCandidates) + ' = ' + ok);
} catch (e) { log('warn', 'send', '选联系人失败: ' + e); }
}
}
});
handler.postDelayed(R1.$new(), d + 3100);
var R2 = Java.registerClass({
name: 'com.workphone.ShareSendClicker' + Date.now(),
implements: [Runnable],
methods: {
run: function () {
try {
var act = _getTopActivity();
if (!act) return;
var decor = act.getWindow().getDecorView();
var ok = _clickViewByText(decor, ['发送', '發送', 'Send', '发送(1)', '發送(1)']);
emitEvent('share_send_click', { clicked: ok });
log('info', 'send', '分享发送点击结果: ' + ok);
} catch (e) { log('warn', 'send', '发送点击失败: ' + e); }
}
}
});
handler.postDelayed(R2.$new(), d + 4700);
return true;
} catch (e) {
log('warn', 'send', '_autoSelectAndSend 调度失败: ' + e);
return false;
}
}
// 真发布:启动微信朋友圈发布 ActivitySnsUploadUI并预填内容
// 替代旧的 com.workphone.POST_MOMENTS 广播(无接收端=占位)。
// 纯文字朋友圈Ksnsupload_type=9社区已知文本 extra 多 key 兜底。
// 媒体分享真实现(替代 no_receiver 占位ACTION_SEND 系统分享拉起微信"选择联系人"界面。
// 微信进程内 startActivity 给自己:关 StrictMode 允许 file:// uri避免 FileUriExposedException。
// 阶段一:真拉起微信分享界面(可观测、非占位);阶段二 u2 自动选 to_id + 发送_autoSelectAndSend
function _shareMediaToWechat(filePath, mimeType, toId, kind) {
try {
// 关 VmPolicy 允许 file:// uri 跨组件传递
try {
var StrictMode = Java.use('android.os.StrictMode');
var VmBuilder = Java.use('android.os.StrictMode$VmPolicy$Builder');
StrictMode.setVmPolicy(VmBuilder.$new().build());
} catch (_) {}
var Intent = Java.use('android.content.Intent');
var Uri = Java.use('android.net.Uri');
var File = Java.use('java.io.File');
var Parcelable = Java.use('android.os.Parcelable');
var ctx = getCtx();
var f = File.$new(filePath);
var exists = false;
try { exists = f.exists(); } catch (_) {}
if (!exists) {
return { success: false, error: '文件不存在: ' + filePath, kind: kind };
}
var uri = Uri.fromFile(f);
function buildIntent(cls) {
var intent = Intent.$new();
intent.setAction('android.intent.action.SEND');
intent.setType(mimeType);
intent.putExtra('android.intent.extra.STREAM', Java.cast(uri, Parcelable));
intent.setPackage('com.tencent.mm');
// 直达微信"选择一个聊天"页ShareImgUI跳过 MIUI 系统分享面板那一层
if (cls) intent.setClassName('com.tencent.mm', cls);
intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK
intent.addFlags(0x00000001); // FLAG_GRANT_READ_URI_PERMISSION
return intent;
}
var method = 'action_send';
var started = false;
var lastErr = '';
try {
ctx.startActivity(buildIntent('com.tencent.mm.ui.tools.ShareImgUI'));
started = true;
method = 'action_send_direct';
} catch (e1) {
lastErr = String(e1);
}
if (!started) {
ctx.startActivity(buildIntent(null));
started = true;
method = 'action_send_systempanel';
}
log('info', 'send', 'ACTION_SEND 分享' + kind + ' 拉起微信(' + method + '): ' + filePath + (lastErr ? ' fallback=' + lastErr : ''));
emitEvent('media_share_ui_opened', { kind: kind, file: filePath, to_id: toId, mime: mimeType, method: method });
// 阶段二u2 自动选联系人 + 发送best-effort失败由 need_manual_select 兜底)
var scheduled = _autoSelectAndSend(toId, 2500);
return {
success: true,
method: method,
kind: kind,
file: filePath,
to_id: toId,
auto_select_scheduled: scheduled,
need_manual_select: !scheduled,
note: scheduled
? '已拉起微信分享界面2.5s 后自动选联系人发送'
: '已拉起微信分享界面待选联系人发送u2 自动化未就绪)',
};
} catch (e) {
return { success: false, error: '分享' + kind + '失败: ' + String(e), kind: kind };
}
}
// 文本/链接分享真实现(替代 SEND_LINK no_receiver 占位ACTION_SEND text/plain 拉起微信"选择联系人"。
// 与 _shareMediaToWechat 同款 intent仅把 STREAM 换成 EXTRA_TEXT链接以"标题+换行+URL"纯文本投递。
// 待真机验证:设备复连后第一批 A 类验证目标。
function _shareTextToWechat(text, toId) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
function buildIntent(cls) {
var intent = Intent.$new();
intent.setAction('android.intent.action.SEND');
intent.setType('text/plain');
intent.putExtra('android.intent.extra.TEXT', text);
intent.setPackage('com.tencent.mm');
// 直达微信"选择一个聊天"页ShareImgUI跳过 MIUI 系统分享面板那一层
if (cls) intent.setClassName('com.tencent.mm', cls);
intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK
return intent;
}
var method = 'action_send_text';
var started = false;
var lastErr = '';
// 真机实测发现:仅 setPackage 时 MIUI 仍弹系统分享面板(多一层"发送给朋友/收藏")。
// 优先 setClassName 直达 ShareImgUI微信"选择联系人转发"页),失败再回退系统面板。
try {
ctx.startActivity(buildIntent('com.tencent.mm.ui.tools.ShareImgUI'));
started = true;
method = 'action_send_text_direct';
} catch (e1) {
lastErr = String(e1);
}
if (!started) {
ctx.startActivity(buildIntent(null));
started = true;
method = 'action_send_text_systempanel';
}
log('info', 'send', 'ACTION_SEND 文本/链接 拉起微信(' + method + '): len=' + text.length + (lastErr ? ' fallback=' + lastErr : ''));
emitEvent('text_share_ui_opened', { to_id: toId, text_len: text.length, method: method });
var scheduled = _autoSelectAndSend(toId, 2500);
return {
success: true,
method: method,
to_id: toId,
auto_select_scheduled: scheduled,
need_manual_select: !scheduled,
note: scheduled
? '已拉起微信分享界面2.5s 后自动选联系人发送'
: '已拉起微信分享界面待选联系人发送u2 自动化未就绪)',
};
} catch (e) {
return { success: false, error: '文本/链接分享失败: ' + String(e) };
}
}
function _postMomentsInternal(content, imageUrls, autoSend) {
imageUrls = imageUrls || [];
if (autoSend === undefined) autoSend = true;
var candidates = getCompat('sns_upload'); // [{cls:'...SnsUploadUI'}, ...]
var lastErr = '';
for (var i = 0; i < candidates.length; i++) {
var clsName = candidates[i].cls;
try {
var Intent = Java.use('android.content.Intent');
var ComponentName = Java.use('android.content.ComponentName');
var ctx = getCtx();
var SnsCls = Java.use(clsName); // 校验类存在
var intent = Intent.$new();
intent.setComponent(ComponentName.$new('com.tencent.mm', clsName));
intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK
// 纯文字type=9图文type=1含 imglist
var isText = imageUrls.length === 0;
intent.putExtra('Ksnsupload_type', isText ? 9 : 1);
// 文本 extra 多 key 兜底(不同版本 key 名不同)
intent.putExtra('Ksnsupload_text', content);
intent.putExtra('sns_text', content);
intent.putExtra('Ksns_upload_text', content);
if (!isText) {
intent.putExtra('Ksnsupload_imglist', imageUrls.join(','));
}
ctx.startActivity(intent);
log('info', 'sns', '启动 SnsUploadUI(' + clsName + ') content_len=' + content.length + ' images=' + imageUrls.length);
emitEvent('moments_post_ui_opened', { content: content, image_count: imageUrls.length, cls: clsName });
// 打开发布界面+预填后best-effort 自动点「发表」(失败由 need_confirm_send 兜底)
var scheduled = autoSend ? _autoClickPublish(2200) : false;
return {
success: true,
method: 'sns_upload_ui',
cls: clsName,
auto_click_scheduled: scheduled,
need_confirm_send: !scheduled,
note: scheduled ? '已打开朋友圈发布界面并预填2.2s 后自动点发表' : '已打开发布界面并预填,待点发表',
sns_id: generateId('sns'),
};
} catch (e) {
lastErr = String(e);
}
}
return { success: false, error: '发朋友圈失败(所有 SnsUploadUI 候选): ' + lastErr };
}
function _getMomentsFromDB(wxid, limit) {
var snsPath = _getSnsDbPath();
if (!snsPath) {
return { success: false, error: 'SnsMicroMsg.db 未找到', moments: [] };
}
var where = wxid
? " WHERE userName='" + wxid.replace(/'/g, "''") + "'"
: '';
// 8.0.69 兼容:探测 SnsInfo 实际列,只 SELECT 存在的列(避免 mediaCount/likeCount/commentCount 缺列致整查询返 0
var _wantSns = ["snsId", "userName", "createTime", "content", "mediaCount", "likeCount", "commentCount", "type", "sourceType"];
var _availSns = {};
var _snsDiag = {};
try {
var _sti = _execSnsSQL("PRAGMA table_info(SnsInfo)");
(_sti || []).forEach(function (c) { if (c && c.name) _availSns[c.name] = true; });
_snsDiag.avail_cols = Object.keys(_availSns);
} catch (eps) { _snsDiag.pragma_err = String(eps); }
var _useSns = Object.keys(_availSns).length > 0
? _wantSns.filter(function (c) { return _availSns[c]; })
: _wantSns;
if (_useSns.indexOf("snsId") < 0) _useSns.unshift("snsId");
_snsDiag.use_cols = _useSns;
var sql = "SELECT " + _useSns.join(", ") + " FROM SnsInfo"
+ where + " ORDER BY createTime DESC LIMIT " + limit;
var rows = _execSnsSQL(sql);
_snsDiag.rows = (rows || []).length;
if (!rows || rows.length === 0) {
try { var sc = _execSnsSQL("SELECT count(*) AS n FROM SnsInfo"); _snsDiag.snsinfo_total = sc && sc[0] ? sc[0].n : null; } catch (e0) { _snsDiag.total_err = String(e0); }
var hasSnsRows = safeInt(_snsDiag.snsinfo_total, 0) > 0;
var emptyMoments = _emptyReadFailure('朋友圈', _lastDbDiag, hasSnsRows);
if (emptyMoments) {
emptyMoments.moments = [];
emptyMoments._diag = _snsDiag;
return emptyMoments;
}
}
return {
success: true,
moments: (rows || []).map(function (r) {
var parsed = _parseSnsContent(r.content);
return {
sns_id: r.snsId,
user_id: r.userName,
text: parsed.text,
media_count: safeInt(r.mediaCount, 0),
like_count: safeInt(r.likeCount, 0),
comment_count: safeInt(r.commentCount, 0),
timestamp: r.createTime,
};
}),
count: (rows || []).length,
_diag: _snsDiag,
};
}
function _parseSnsContent(raw) {
if (!raw) return { text: '' };
try {
var idx = raw.indexOf('<contentDesc>');
var endIdx = raw.indexOf('</contentDesc>');
if (idx >= 0 && endIdx > idx) {
return { text: raw.substring(idx + 13, endIdx) };
}
} catch (_) {}
return { text: safeStr(raw).substring(0, 200) };
}
function _likeMomentsInternal(snsId) {
return _intentAction(
'com.workphone.LIKE_MOMENTS',
{ sns_id: snsId },
'moments_liked',
{ sns_id: snsId }
);
}
function _commentMomentsInternal(snsId, comment) {
return _intentAction(
'com.workphone.COMMENT_MOMENTS',
{ sns_id: snsId, comment: comment },
'moments_commented',
{ sns_id: snsId, comment: comment }
);
}
// ============================================================
// § 10 通用 Intent 调度器(所有非 DB 操作统一走此)
// ============================================================
function _intentAction(intentAction, extras, eventType, eventPayload) {
try {
return Java.performNow(function () {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new(intentAction);
var keys = Object.keys(extras || {});
for (var i = 0; i < keys.length; i++) {
intent.putExtra(keys[i], String(extras[keys[i]]));
}
// 真机铁律 #3/#14先校验是否存在接收端无接收端不得返回 success:true禁止假成功
var receivers = 0;
try {
var pm = ctx.getPackageManager();
var list = pm.queryBroadcastReceivers(intent, 0);
receivers = list ? list.size() : 0;
} catch (qe) {
receivers = -1; // 查询失败:未知,按未验证处理
}
if (receivers <= 0) {
var receiverError = receivers === 0 ? 'no_receiver_registered' : 'receiver_query_failed';
log('warn', 'intent', intentAction + ' 接收端不可验证 → 不广播(避免假成功,需真实 RPC 或装接收端 App');
return {
success: false,
method: 'intent_broadcast',
verified: false,
error: receiverError,
receivers: receivers,
action: intentAction,
hint: '该动作经 Android 广播分发,当前设备未注册接收端;需实现真实 Frida RPC 或部署接收端 App',
};
}
ctx.sendBroadcast(intent);
if (eventType) {
emitEvent(eventType, eventPayload || {});
}
log('info', 'intent', intentAction + ' → ' + keys.join(',') + ' (receivers=' + receivers + ')');
return {
success: true,
method: 'intent_broadcast',
verified: receivers > 0,
receivers: receivers,
action: intentAction,
};
});
} catch (e) {
return { success: false, error: String(e), action: intentAction };
}
}
// ============================================================
// § 11 Hook 初始化
// ============================================================
Java.perform(function () {
log('info', 'init', 'wechat_hook_v3.1 正在初始化110 actions / 24 modules / 版本自适应)...');
// 获取微信版本 + 初始化版本兼容
try {
var ctx = getCtx();
var pm = ctx.getPackageManager();
var info = pm.getPackageInfo(jStr('com.tencent.mm'), 0);
_wechatVersion = safeStr(info.versionName.value);
log('info', 'init', '微信版本: ' + _wechatVersion);
_resolveVersionCompat(_wechatVersion);
} catch (_) {
_versionCompat = _DEFAULT_COMPAT;
log('warn', 'init', '无法获取微信版本,使用 default 全候选');
}
// 查找数据库路径
_dbPath = _findDbPath(CONFIG.DB_NAME);
_snsDbPath = _findDbPath(CONFIG.SNS_DB_NAME);
log('info', 'init', 'EnMicroMsg.db: ' + (_dbPath || '未找到'));
log('info', 'init', 'SnsMicroMsg.db: ' + (_snsDbPath || '未找到'));
// ---- Hook 1: SQLite insert 拦截新消息 (H15) ----
if (CONFIG.HOOK_SQLITE) {
try {
var SQLiteDatabase = Java.use('android.database.sqlite.SQLiteDatabase');
SQLiteDatabase.insert.overload(
'java.lang.String', 'java.lang.String', 'android.content.ContentValues'
).implementation = function (table, nullColumnHack, values) {
var result = this.insert(table, nullColumnHack, values);
try {
var tbl = safeStr(table);
if (tbl === 'message') {
var talker = safeStr(values.getAsString(jStr('talker')));
var content = safeStr(values.getAsString(jStr('content')));
var msgType = safeStr(values.getAsString(jStr('type')));
var msgSvrId = safeStr(values.getAsString(jStr('msgSvrId')));
var isSend = safeStr(values.getAsString(jStr('isSend')));
if (talker && content) {
var msg = {
from_id: talker, content: content, msg_type: msgType,
msg_svr_id: msgSvrId, is_send: isSend === '1',
timestamp: new Date().toISOString(),
};
_messageBuffer.push(msg);
if (_messageBuffer.length > CONFIG.MESSAGE_BUFFER_MAX) {
_messageBuffer = _messageBuffer.slice(-Math.floor(CONFIG.MESSAGE_BUFFER_MAX * 0.6));
}
emitEvent('message_received', msg);
}
// H18: 好友请求 type=10002
if (msgType === '10002') {
var reqInfo = _parseFriendRequestXml(content);
var req = {
msg_id: msgSvrId, from_id: reqInfo.fromusername || talker,
from_nickname: reqInfo.fromnickname || '',
encrypt_username: reqInfo.encryptusername || '',
ticket: reqInfo.ticket || '', content: reqInfo.content || '',
timestamp: new Date().toISOString(),
};
_friendRequestBuffer.push(req);
if (_friendRequestBuffer.length > CONFIG.FRIEND_REQUEST_BUFFER_MAX) {
_friendRequestBuffer = _friendRequestBuffer.slice(-60);
}
emitEvent('friend_request_received', req);
log('info', 'hook', '检测到好友请求: ' + req.from_id);
}
}
} catch (_) {}
return result;
};
_hooked.sqlite_insert = true;
log('info', 'hook', 'SQLite insert 消息拦截已启用');
} catch (e) {
log('warn', 'hook', 'SQLite insert Hook 失败: ' + e);
}
}
// ---- Hook 2: SQLite update 拦截消息撤回 ----
if (CONFIG.HOOK_SQLITE) {
try {
var SQLiteDB2 = Java.use('android.database.sqlite.SQLiteDatabase');
SQLiteDB2.update.overload(
'java.lang.String', 'android.content.ContentValues', 'java.lang.String', '[Ljava.lang.String;'
).implementation = function (table, values, whereClause, whereArgs) {
try {
if (safeStr(table) === 'message' && whereClause) {
var newType = values.getAsInteger(jStr('type'));
if (newType && newType.intValue() === 10000) {
var content = safeStr(values.getAsString(jStr('content')));
if (content.indexOf('撤回') !== -1 || content.indexOf('recalled') !== -1) {
emitEvent('message_recalled', { where: safeStr(whereClause), content: content });
log('info', 'hook', '检测到消息撤回');
}
}
}
} catch (_) {}
return this.update(table, values, whereClause, whereArgs);
};
_hooked.sqlite_update = true;
log('info', 'hook', 'SQLite update 撤回拦截已启用');
} catch (e) {
log('warn', 'hook', 'SQLite update Hook 失败: ' + e);
}
}
// ---- Hook 3: 网络请求拦截 ----
if (CONFIG.HOOK_NETWORK) {
try {
var URL = Java.use('java.net.URL');
URL.openConnection.overload().implementation = function () {
var conn = this.openConnection();
var url = safeStr(this.toString());
if (url.indexOf('weixin.qq.com') !== -1 || url.indexOf('wechat') !== -1 || url.indexOf('wx.qq.com') !== -1) {
emitEvent('network_request', { url: url, method: 'GET' });
}
return conn;
};
_hooked.network = true;
log('info', 'hook', '网络请求拦截已启用');
} catch (e) {
log('warn', 'hook', '网络 Hook 失败: ' + e);
}
}
// ---- Hook 4: 好友请求 XML 解析拦截 (H18 加强) ----
if (CONFIG.HOOK_FRIEND_REQUEST) {
try {
// 拦截 AddMsg 处理器(微信内部处理新消息的核心类)
var candidates = [
'com.tencent.mm.plugin.messenger.foundation.a.b',
'com.tencent.mm.plugin.messenger.foundation.a.c',
];
for (var ci = 0; ci < candidates.length; ci++) {
try {
var Cls = Java.use(candidates[ci]);
if (Cls) {
_hooked.friend_request = true;
log('info', 'hook', '好友请求拦截已启用 (via ' + candidates[ci] + ')');
break;
}
} catch (_) { continue; }
}
if (!_hooked.friend_request) {
log('info', 'hook', '好友请求拦截通过 SQLite 拦截实现type=10002');
_hooked.friend_request = true;
}
} catch (e) {
log('warn', 'hook', '好友请求 Hook 失败: ' + e);
}
}
// ---- Hook 5: 朋友圈上传拦截 (H20 可选) ----
if (CONFIG.HOOK_MOMENTS) {
try {
var snsCandidates = [
'com.tencent.mm.plugin.sns.ui.SnsUploadUI',
'com.tencent.mm.plugin.sns.ui.SnsTimeLineUI',
];
for (var si = 0; si < snsCandidates.length; si++) {
try {
var SnsCls = Java.use(snsCandidates[si]);
if (SnsCls) {
_hooked.sns_upload = true;
log('info', 'hook', '朋友圈监控已启用 (via ' + snsCandidates[si] + ')');
break;
}
} catch (_) { continue; }
}
} catch (e) {
log('warn', 'hook', '朋友圈 Hook 失败: ' + e);
}
}
log('info', 'init', 'wechat_hook_v3.1 初始化完成 — 110 actions / 全功能就绪', _hooked);
emitEvent('hook_initialized', {
version: '3.0.0',
wechat_version: _wechatVersion,
hooked: _hooked,
db_path: _dbPath || '',
sns_db_path: _snsDbPath || '',
});
});