原 _intentAction 占位(无 companion 模块即假/空)→ 改读 EnMicroMsg.db EmojiInfo 表,动态列探测兼容8.0.x;真机验证 count>0 真实表情数据,channel=frida_db。双JS副本同步。 Co-authored-by: Cursor <cursoragent@cursor.com>
2652 lines
112 KiB
JavaScript
2652 lines
112 KiB
JavaScript
/**
|
||
* 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.x,SnsMicroMsg=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)
|
||
// ============================================================
|
||
|
||
// 取当前栈顶 Activity(ActivityThread.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;
|
||
}
|
||
}
|
||
|
||
// 真发布:启动微信朋友圈发布 Activity(SnsUploadUI)并预填内容
|
||
// 替代旧的 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 || '',
|
||
});
|
||
});
|