Files
workphone-sdk/sdk/agent/hook/wechat_hook_v2.js
Manus AI cd8c18b590 feat(hook): get_sticker_list 改真 Frida RPC(读 EmojiInfo)
原 _intentAction 占位(无 companion 模块即假/空)→ 改读 EnMicroMsg.db EmojiInfo 表,动态列探测兼容8.0.x;真机验证 count>0 真实表情数据,channel=frida_db。双JS副本同步。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-30 16:50:34 +08:00

2652 lines
112 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 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
if (typeof db.rawQuery !== 'function') {
_lastDbDiag.error = '实例无 rawQuery疑 WCDB 2.x core.Database需 2.x 查询适配';
log('warn', 'db', _lastDbDiag.error);
return 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 实例查找(兼容 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 时按打开处理
}
function _findOpenWcdb(path) {
var normalizedPath = path.replace('/data/data/', '/data/user/0/');
var baseName = path.split('/').pop();
var found = 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;
}
} 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) };
}
},
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) : [],
};
},
// ==================== 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) || 50;
var conversationId = (params && params.conversation_id) || '';
var dbRes = null;
try {
dbRes = Java.performNow(function () {
return _getMessagesFromDB(conversationId, limit);
});
} catch (e) {
dbRes = { success: false, error: String(e), messages: [] };
}
// DB 读为空时 fallback 到 hook 实时缓冲insert hook 已拦截新消息)
if (!dbRes || !dbRes.messages || dbRes.messages.length === 0) {
var buf = _messageBuffer.filter(function (m) {
return !conversationId || m.from_id === conversationId;
}).slice(-limit);
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,
source: 'hook_buffer',
db_diag: _lastDbDiag,
};
}
if (dbRes) dbRes.db_diag = _lastDbDiag;
}
return dbRes;
},
getRecentMessages: function (params) {
var limit = (params && params.limit) || 20;
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) || 200;
try {
return Java.performNow(function () { return _getContactsFromDB(limit); });
} 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;
try {
return Java.performNow(function () { return _getGroupsFromDB(limit); });
} 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 () {
return _intentAction('com.workphone.GET_SAFETY_CENTER', {}, null, {});
},
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 () {
return _intentAction('com.workphone.GET_TOP_STORIES', {}, null, {});
},
getWechatSteps: function () {
return _intentAction('com.workphone.GET_WECHAT_STEPS', {}, null, {});
},
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) || '';
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 () {
try {
return Java.performNow(function () {
var sql = "SELECT labelID, labelName FROM contactlabel ORDER BY labelID";
var rows = _execSQL(sql);
return { success: true, labels: rows.map(function (r) { return { id: r.labelID, name: r.labelName }; }), count: rows.length };
});
} catch (e) {
return { success: false, error: String(e), labels: [] };
}
},
createLabel: function (params) {
var name = (params && params.name) || '';
if (!name) return { success: false, error: '缺少 name' };
return _intentAction('com.workphone.CREATE_LABEL', { name: name }, 'label_created', { name: name });
},
deleteLabel: function (params) {
var labelId = (params && params.label_id) || '';
if (!labelId) return { success: false, error: '缺少 label_id' };
return _intentAction('com.workphone.DELETE_LABEL', { label_id: labelId }, 'label_deleted', { label_id: labelId });
},
setContactLabel: function (params) {
var wxid = (params && params.wxid) || '';
var labelIds = (params && params.label_ids) || [];
if (!wxid) return { success: false, error: '缺少 wxid' };
return _intentAction('com.workphone.SET_CONTACT_LABEL', { wxid: wxid, label_ids: (labelIds || []).join(',') }, 'contact_labeled', { wxid: wxid, label_ids: labelIds });
},
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' };
return _intentAction('com.workphone.SEND_IMAGE', { to_id: toId, image_path: imagePath }, 'image_sent', { to_id: toId });
},
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' };
return _intentAction('com.workphone.SEND_VIDEO', { to_id: toId, video_path: videoPath }, 'video_sent', { to_id: toId });
},
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' };
return _intentAction('com.workphone.SEND_FILE', { to_id: toId, file_path: filePath }, 'file_sent', { to_id: toId });
},
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' };
return _intentAction('com.workphone.SEND_LINK', { to_id: toId, url: url, title: title, description: desc }, 'link_sent', { to_id: toId, url: url });
},
// ==================== 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' };
return _intentAction('com.workphone.FORWARD_MESSAGE', { msg_svr_id: msgSvrId, to_id: toId }, 'message_forwarded', { msg_svr_id: msgSvrId, to_id: toId });
},
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' };
return _intentAction('com.workphone.FORWARD_MULTIPLE', { msg_svr_ids: msgSvrIds.join(','), to_id: toId }, 'messages_forwarded', { count: msgSvrIds.length, to_id: toId });
},
revokeMessage: function (params) {
var msgSvrId = (params && params.msg_svr_id) || '';
if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id' };
return _intentAction('com.workphone.REVOKE_MESSAGE', { msg_svr_id: msgSvrId }, 'message_revoked', { msg_svr_id: msgSvrId });
},
// ==================== 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 };
},
};
// ============================================================
// § 5 内部实现 — 消息发送 (H17)
// ============================================================
function _sendMessageInternal(toId, content, msgType) {
var sent = false;
// 方案 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;
log('info', 'send', '消息通过内部方法发送: ' + c.cls);
emitEvent('message_sent', { to_id: toId, content: content, msg_type: msgType, method: 'internal_' + c.cls.split('.').pop() });
}
} catch (_) { continue; }
}
// 方案 B: Intent 广播降级
if (!sent) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new('com.workphone.SEND_MESSAGE');
intent.putExtra('to_id', toId);
intent.putExtra('content', content);
intent.putExtra('msg_type', msgType);
ctx.sendBroadcast(intent);
sent = true;
log('info', 'send', '消息通过 Intent 广播发送');
emitEvent('message_sent', { to_id: toId, content: content, msg_type: msgType, method: 'intent_broadcast' });
} catch (e) {
log('error', 'send', 'Intent 发送也失败: ' + e);
}
}
var messageId = sent ? 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));
}
}
return {
success: sent,
message_id: messageId,
method: sent ? 'hook' : 'failed',
to_id: toId,
};
}
// ============================================================
// § 6 内部实现 — 数据库读取 (H15/H16)
// ============================================================
function _getMessagesFromDB(conversationId, limit) {
// 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;
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,
is_send: r.isSend === '1',
};
}),
count: rows.length,
};
}
function _getContactsFromDB(limit) {
limit = limit || 200;
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 sql = _cols + "WHERE type NOT IN (0, 33) AND verifyFlag=0 AND " + _excludeSys
+ "ORDER BY conRemark, nickname LIMIT " + limit;
var rows = _execSQL(sql);
var _diag = { strict: (rows || []).length, use_cols: _useCols, avail_cols: Object.keys(_availCols) };
// 8.0.69 兼容回退①:严格过滤(type/verifyFlag)为空时,放宽为仅排除系统/群/公众号
if (!rows || rows.length === 0) {
var sqlRelax = _cols + "WHERE verifyFlag=0 AND " + _excludeSys
+ "ORDER BY conRemark, nickname LIMIT " + limit;
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 LIMIT " + limit;
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, _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) {
// 通过微信内部 AddContactNetScene
var candidates = [
'com.tencent.mm.plugin.profile.ui.ContactInfoUI',
'com.tencent.mm.protocal.protobuf.add$ContactType',
];
// 降级: Intent 方式
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new('com.workphone.ADD_FRIEND');
intent.putExtra('user_id', userId);
intent.putExtra('message', message);
intent.putExtra('source_type', String(sourceType));
ctx.sendBroadcast(intent);
log('info', 'friend', '添加好友请求已发送: ' + userId);
emitEvent('friend_request_sent', { user_id: userId, message: message, source_type: sourceType });
return { success: true, user_id: userId, method: 'intent_broadcast' };
} catch (e) {
return { success: false, error: '添加好友失败: ' + e, user_id: userId };
}
}
function _acceptFriendInternal(encryptUserName, ticket) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new('com.workphone.ACCEPT_FRIEND');
intent.putExtra('encrypt_username', encryptUserName);
intent.putExtra('ticket', ticket);
ctx.sendBroadcast(intent);
log('info', 'friend', '通过好友请求: ' + encryptUserName);
emitEvent('friend_accepted', { encrypt_username: encryptUserName });
return { success: true, encrypt_username: encryptUserName, method: 'intent_broadcast' };
} catch (e) {
return { success: false, error: '通过好友失败: ' + e };
}
}
function _deleteFriendInternal(wxid) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new('com.workphone.DELETE_FRIEND');
intent.putExtra('wxid', wxid);
ctx.sendBroadcast(intent);
log('info', 'friend', '删除好友: ' + wxid);
emitEvent('friend_deleted', { wxid: wxid });
return { success: true, wxid: wxid, method: 'intent_broadcast' };
} catch (e) {
return { success: false, error: '删除好友失败: ' + e };
}
}
function _setRemarkInternal(wxid, remark) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new('com.workphone.SET_REMARK');
intent.putExtra('wxid', wxid);
intent.putExtra('remark', remark);
ctx.sendBroadcast(intent);
return { success: true, wxid: wxid, remark: remark, method: 'intent_broadcast' };
} catch (e) {
return { success: false, error: '设置备注失败: ' + e };
}
}
// ============================================================
// § 8 内部实现 — 群管理 (H22)
// ============================================================
function _getGroupsFromDB(limit) {
var sql = "SELECT username, nickname FROM rcontact"
+ " WHERE username LIKE '%@chatroom'"
+ " ORDER BY nickname LIMIT " + limit;
var rows = _execSQL(sql);
return {
success: true,
groups: rows.map(function (r) {
return { group_id: r.username, name: r.nickname };
}),
count: rows.length,
};
}
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) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new('com.workphone.CREATE_GROUP');
intent.putExtra('member_ids', memberIds.join(','));
if (topic) intent.putExtra('topic', topic);
ctx.sendBroadcast(intent);
log('info', 'group', '创建群聊: members=' + memberIds.length);
emitEvent('group_created', { member_ids: memberIds, topic: topic });
return { success: true, member_ids: memberIds, topic: topic, method: 'intent_broadcast' };
} catch (e) {
return { success: false, error: '创建群失败: ' + e };
}
}
function _inviteToGroupInternal(groupId, memberIds) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new('com.workphone.INVITE_GROUP');
intent.putExtra('group_id', groupId);
intent.putExtra('member_ids', memberIds.join(','));
ctx.sendBroadcast(intent);
log('info', 'group', '邀请入群: ' + groupId + ' members=' + memberIds.length);
emitEvent('group_invite', { group_id: groupId, member_ids: memberIds });
return { success: true, group_id: groupId, member_ids: memberIds, method: 'intent_broadcast' };
} catch (e) {
return { success: false, error: '邀请入群失败: ' + e };
}
}
function _removeFromGroupInternal(groupId, memberIds) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new('com.workphone.REMOVE_GROUP_MEMBER');
intent.putExtra('group_id', groupId);
intent.putExtra('member_ids', memberIds.join(','));
ctx.sendBroadcast(intent);
log('info', 'group', '踢出群: ' + groupId);
emitEvent('group_member_removed', { group_id: groupId, member_ids: memberIds });
return { success: true, group_id: groupId, member_ids: memberIds, method: 'intent_broadcast' };
} catch (e) {
return { success: false, error: '踢人失败: ' + e };
}
}
function _setGroupAnnouncementInternal(groupId, announcement) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new('com.workphone.SET_GROUP_ANNOUNCEMENT');
intent.putExtra('group_id', groupId);
intent.putExtra('announcement', announcement);
ctx.sendBroadcast(intent);
return { success: true, group_id: groupId, method: 'intent_broadcast' };
} catch (e) {
return { success: false, error: '设置群公告失败: ' + e };
}
}
function _setGroupNameInternal(groupId, name) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new('com.workphone.SET_GROUP_NAME');
intent.putExtra('group_id', groupId);
intent.putExtra('name', name);
ctx.sendBroadcast(intent);
return { success: true, group_id: groupId, name: name, method: 'intent_broadcast' };
} catch (e) {
return { success: false, error: '修改群名失败: ' + e };
}
}
function _quitGroupInternal(groupId) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new('com.workphone.QUIT_GROUP');
intent.putExtra('group_id', groupId);
ctx.sendBroadcast(intent);
emitEvent('group_quit', { group_id: groupId });
return { success: true, group_id: groupId, method: 'intent_broadcast' };
} catch (e) {
return { success: false, error: '退群失败: ' + e };
}
}
// ============================================================
// § 9 内部实现 — 朋友圈 (H20/H21)
// ============================================================
// 取当前栈顶 ActivityActivityThread.mActivities 遍历 paused=false
function _getTopActivity() {
var top = null;
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 {
if (!rec.paused.value) { top = rec.activity.value; break; }
} catch (_) {}
}
} catch (e) {
log('warn', 'sns', '_getTopActivity 失败: ' + e);
}
return top;
}
// 递归遍历 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;
}
// 朋友圈发布界面打开后自动点「发表」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;
}
}
// 真发布:启动微信朋友圈发布 ActivitySnsUploadUI并预填内容
// 替代旧的 com.workphone.POST_MOMENTS 广播(无接收端=占位)。
// 纯文字朋友圈Ksnsupload_type=9社区已知文本 extra 多 key 兜底。
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) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new('com.workphone.LIKE_MOMENTS');
intent.putExtra('sns_id', snsId);
ctx.sendBroadcast(intent);
emitEvent('moments_liked', { sns_id: snsId });
return { success: true, sns_id: snsId, method: 'intent_broadcast' };
} catch (e) {
return { success: false, error: '点赞失败: ' + e };
}
}
function _commentMomentsInternal(snsId, comment) {
try {
var Intent = Java.use('android.content.Intent');
var ctx = getCtx();
var intent = Intent.$new('com.workphone.COMMENT_MOMENTS');
intent.putExtra('sns_id', snsId);
intent.putExtra('comment', comment);
ctx.sendBroadcast(intent);
emitEvent('moments_commented', { sns_id: snsId, comment: comment });
return { success: true, sns_id: snsId, method: 'intent_broadcast' };
} catch (e) {
return { success: false, error: '评论失败: ' + e };
}
}
// ============================================================
// § 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) {
log('warn', 'intent', intentAction + ' 无接收端 → 不广播(避免假成功,需 _intentAction 真实现或装接收端 App');
return {
success: false,
method: 'intent_broadcast',
verified: false,
error: 'no_receiver_registered',
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 || '',
});
});