/**
 * 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 → 走 2.x 适配
        if (typeof db.rawQuery !== 'function') {
            return _execSQL2x(db, sql, results);
        }
        var cursor = db.rawQuery(sql, null);
        while (cursor.moveToNext()) {
            var row = {};
            for (var c = 0; c < cursor.getColumnCount(); c++) {
                row[cursor.getColumnName(c)] = safeStr(cursor.getString(c));
            }
            results.push(row);
        }
        cursor.close();
        _lastDbDiag.ok = true;
        _lastDbDiag.rows = results.length;
    } catch (e) {
        _lastDbDiag.error = String(e);
        log('warn', 'db', 'SQL 执行失败: ' + e);
    }
    return results;
}

// WCDB 2.x core.Database 原生 SQL 查询适配。
// 2.x 无 rawQuery；通过 getHandle() 取 Handle，再用 PreparedStatement（prepare/step/getText）读取。
// 反射发现可用方法，失败则把方法名写入 _lastDbDiag 供下一轮精确实现（不盲写）。
function _execSQL2x(db, sql, results) {
    try {
        if (typeof db.getHandle !== 'function') {
            _lastDbDiag.error = 'WCDB2x: 实例无 getHandle()';
            try { _lastDbDiag.db_methods = _reflectMethodNames(db); } catch (_) {}
            return results;
        }
        var handle = db.getHandle();
        _lastDbDiag.has_handle = !!handle;
        if (!handle || typeof handle.getAllRowsFromSQL !== 'function') {
            _lastDbDiag.error = 'WCDB2x: handle 无 getAllRowsFromSQL';
            try { _lastDbDiag.handle_methods = _reflectMethodNames(handle); } catch (_) {}
            return results;
        }
        // 从 SELECT 子句解析列名（getAllRowsFromSQL 只返回值数组，需自带列名映射）
        var colNames = _parseSelectColumns(sql);
        _lastDbDiag.used_prepare = 'getAllRowsFromSQL';
        _lastDbDiag.parsed_cols = colNames;
        // WCDB 2.x：getAllRowsFromSQL(sql) → List<List<Value>>（Value.getText() 取文本）
        var allRows = handle.getAllRowsFromSQL(sql);
        if (allRows) {
            var rowCount = (typeof allRows.size === 'function') ? allRows.size() : allRows.length;
            for (var ri = 0; ri < rowCount && ri < 2000; ri++) {
                var rowVals = (typeof allRows.get === 'function') ? allRows.get(ri) : allRows[ri];
                var vcount = (rowVals && typeof rowVals.size === 'function') ? rowVals.size()
                    : (rowVals && rowVals.length != null ? rowVals.length : 0);
                var row = {};
                for (var vi = 0; vi < vcount; vi++) {
                    var v = (typeof rowVals.get === 'function') ? rowVals.get(vi) : rowVals[vi];
                    var txt = null;
                    try { if (v && typeof v.getText === 'function') txt = v.getText(); } catch (_) {}
                    if (txt == null) { try { if (v != null) txt = String(v); } catch (_) {} }
                    row[colNames[vi] || ('c' + vi)] = safeStr(txt);
                }
                results.push(row);
            }
        }
        try { if (handle && typeof handle.invalidate === 'function') handle.invalidate(); } catch (_) {}
        _lastDbDiag.ok = true;
        _lastDbDiag.rows = results.length;
    } catch (e) {
        _lastDbDiag.error = 'WCDB2x: ' + String(e);
        log('warn', 'db', _lastDbDiag.error);
    }
    return results;
}

// 从 SELECT ... FROM 解析列名/别名，供 getAllRowsFromSQL 的值数组按名映射。
// 仅处理本文件内构造的受控 SQL（无嵌套子查询函数逗号）；尽量取 AS 别名或末段标识符。
function _parseSelectColumns(sql) {
    try {
        // PRAGMA table_info(X) 列固定，getAllRowsFromSQL 不带列名，需手动映射
        if (/^\s*pragma\s+table_info/i.test(sql)) {
            return ['cid', 'name', 'type', 'notnull', 'dflt_value', 'pk'];
        }
        var m = /select\s+([\s\S]*?)\s+from\s/i.exec(sql);
        if (!m) return [];
        var seg = m[1];
        if (seg.replace(/\s/g, '') === '*') return [];
        return seg.split(',').map(function (part) {
            var p = part.trim();
            var asm = /\s+as\s+([A-Za-z0-9_]+)\s*$/i.exec(p);
            if (asm) return asm[1];
            // count(*) AS n 已被上分支处理；无别名时取末段标识符
            var idm = /([A-Za-z0-9_]+)\s*$/.exec(p);
            return idm ? idm[1] : p;
        });
    } catch (_) {
        return [];
    }
}

function _reflectMethodNames(obj) {
    var names = [];
    try {
        var cls = obj.getClass();
        var methods = cls.getMethods();
        for (var i = 0; i < methods.length && i < 120; i++) {
            try { names.push(methods[i].getName()); } catch (_) {}
        }
    } catch (_) {}
    // 去重
    var seen = {}, uniq = [];
    for (var j = 0; j < names.length; j++) { if (!seen[names[j]]) { seen[names[j]] = 1; uniq.push(names[j]); } }
    return uniq;
}

function _escapeSql(v) {
    return String(v == null ? '' : v).replace(/'/g, "''");
}

/** WCDB 1.x 写库（标签/联系人字段）；无 execSQL 时诚实失败 */
function _execSQLWrite(sql, dbPathOverride) {
    try {
        var path = dbPathOverride || _getDbPath();
        if (!path) return { success: false, error: 'db_path 未找到' };
        var db = _findOpenWcdb(path);
        if (!db) return { success: false, error: '未找到已打开的 WCDB 实例' };
        if (typeof db.execSQL !== 'function') {
            return { success: false, error: '实例无 execSQL（疑 WCDB 2.x），标签写库不可用' };
        }
        db.execSQL(sql);
        return { success: true };
    } catch (e) {
        return { success: false, error: String(e) };
    }
}

function _nextLabelId() {
    var rows = _execSQL('SELECT MAX(CAST(labelID AS INTEGER)) AS max_id FROM contactlabel');
    if (rows.length && rows[0].max_id) {
        var n = parseInt(rows[0].max_id, 10);
        if (!isNaN(n)) return n + 1;
    }
    rows = _execSQL('SELECT MAX(CAST(labelId AS INTEGER)) AS max_id FROM contactlabelinfo');
    if (rows.length && rows[0].max_id) {
        var n2 = parseInt(rows[0].max_id, 10);
        if (!isNaN(n2)) return n2 + 1;
    }
    return 1;
}

function _normalizeLabelIds(raw) {
    if (!raw) return [];
    if (Array.isArray(raw)) return raw.map(function (x) { return String(x); }).filter(Boolean);
    return String(raw).split(',').map(function (x) { return x.trim(); }).filter(Boolean);
}

function _tryNotifyLabelChanged() {
    try {
        var candidates = [
            'com.tencent.mm.plugin.label.a.a',
            'com.tencent.mm.plugin.label.a.b',
            'com.tencent.mm.storage.bv',
        ];
        for (var i = 0; i < candidates.length; i++) {
            try {
                var Cls = Java.use(candidates[i]);
                if (typeof Cls.a === 'function') { Cls.a(); return true; }
                if (typeof Cls.b === 'function') { Cls.b(); return true; }
            } catch (_) {}
        }
    } catch (_) {}
    return false;
}

// WCDB 实例查找（兼容 8.0.69 WCDB 2.x）：多候选类名 + 放宽路径匹配
var _WCDB_CLASS_CANDIDATES = [
    'com.tencent.wcdb.database.SQLiteDatabase',  // WCDB 1.x
    'com.tencent.wcdb.core.Database',            // WCDB 2.x（新版微信）
];
var _wcdbClassResolved = null;

function _wcdbInstancePath(instance) {
    // WCDB 1.x: getPath()；2.x: getPath() 或 path 字段
    try { if (typeof instance.getPath === 'function') return safeStr(instance.getPath()); } catch (_) {}
    try { if (instance.path) return safeStr(instance.path.value); } catch (_) {}
    return '';
}

function _wcdbIsOpen(instance) {
    try { if (typeof instance.isOpen === 'function') return instance.isOpen(); } catch (_) {}
    try { if (typeof instance.isOpened === 'function') return instance.isOpened(); } catch (_) {}
    return true;  // 2.x 无显式 isOpen 时按打开处理
}

// WCDB 实例缓存：Java.choose 全堆扫描在大堆上耗时数十秒，标签读写每次都扫会超时。
// 按 path 缓存已打开实例；命中先校验 isOpen，失效则重扫。显著降低标签操作延迟。
var _wcdbInstCache = {};

function _findOpenWcdb(path) {
    var normalizedPath = path.replace('/data/data/', '/data/user/0/');
    var baseName = path.split('/').pop();
    var found = null;
    // 1) 先查缓存：命中且仍打开则直接复用，避免全堆扫描
    var cached = _wcdbInstCache[path];
    if (cached) {
        try {
            if (_wcdbIsOpen(cached)) return cached;
        } catch (_) {}
        try { delete _wcdbInstCache[path]; } catch (_) { _wcdbInstCache[path] = null; }
    }
    // 始终遍历全部候选类：不同库分属不同 WCDB 版本（EnMicroMsg=1.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;
                            _wcdbInstCache[path] = instance;  // 缓存供后续复用
                        }
                    } catch (_) {}
                },
                onComplete: function () {}
            });
        } catch (e) {
            log('warn', 'db', 'Java.choose 失败 ' + cls + ': ' + e);
        }
    }
    if (!found) log('warn', 'db', '未找到已打开的 WCDB 实例(已试' + classes.length + '类): ' + baseName);
    return found;
}

// 诊断：枚举所有 WCDB 实例路径（真机排查 8.0.69 DB 读空）
function _diagWcdbInstances() {
    var out = [];
    for (var ci = 0; ci < _WCDB_CLASS_CANDIDATES.length; ci++) {
        var cls = _WCDB_CLASS_CANDIDATES[ci];
        try { Java.use(cls); } catch (_) { out.push({cls: cls, exists: false}); continue; }
        var paths = [];
        try {
            Java.choose(cls, {
                onMatch: function (inst) {
                    try { paths.push({ path: _wcdbInstancePath(inst), open: _wcdbIsOpen(inst) }); } catch (_) {}
                },
                onComplete: function () {}
            });
        } catch (_) {}
        out.push({ cls: cls, exists: true, instances: paths });
    }
    return out;
}

function _execSnsSQL(sql) {
    return _execSQL(sql, _getSnsDbPath());
}

// ============================================================
// § 4  rpc.exports — Python 端通过 FridaManager.call_rpc 调用
// ============================================================

rpc.exports = {

    // ==================== 系统 ====================

    ping: function () {
        return 'pong from wechat_hook_v3.0 — 96 actions / 26 modules';
    },

    getProcessInfo: function () {
        return {
            pid: Process.id,
            arch: Process.arch,
            platform: Process.platform,
            modules: Process.enumerateModules().length,
            wechat_version: _wechatVersion,
            hooked: _hooked,
        };
    },

    getHookStatus: function () {
        return _hooked;
    },

    // 诊断：枚举真机 WCDB 实例路径（排查 8.0.69 DB 读空）
    diagWcdb: function () {
        try {
            return Java.performNow(function () {
                var out = { success: true, db_path: _getDbPath(), sns_path: _getSnsDbPath(), instances: _diagWcdbInstances() };
                // 实读测试：main 库 rcontact、sns 库 SnsInfo，回传 _lastDbDiag 定位查询错误
                try { _execSQL('SELECT count(*) AS n FROM rcontact'); out.main_query = JSON.parse(JSON.stringify(_lastDbDiag)); } catch (e1) { out.main_query = { error: String(e1) }; }
                try { _execSnsSQL('SELECT count(*) AS n FROM SnsInfo'); out.sns_query = JSON.parse(JSON.stringify(_lastDbDiag)); } catch (e2) { out.sns_query = { error: String(e2) }; }
                return out;
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    // 诊断：运行时反射枚举 8.0.69 朋友圈(SNS)发布/读取相关类与方法
    // 用于在混淆版定位 post_moments(sns.model.ay 等价) / get_moments(m3.onGYNetEnd 等价) 真实类名。
    diagSns: function (params) {
        // 安全诊断：**禁用** Java.enumerateLoadedClassesSync（微信 10万+ 类，桥接枚举会阻塞
        // agent 事件循环导致 WS 掉线 — 2026-05-30 实测）。改为「试探调用方提供的候选类名」：
        //   params.candidates = ["com.tencent.mm.plugin.sns.model.xx", ...]（来自离线 apk/smali 分析）
        // 对每个候选 Java.use + 反射方法名（最多 8 个，快速、不枚举），定位发/读朋友圈真实类。
        var cands = (params && params.candidates) || [];
        if (!cands || cands.length === 0) {
            return {
                success: false,
                error: 'no_candidates',
                hint: '运行时全量枚举已禁用（会致 WS 掉线）。请先离线对 8.0.69 apk 跑 jadx/baksmali 取候选类名，再传 params.candidates=[...] 试探。',
            };
        }
        var lim = Math.min(cands.length, 8);
        try {
            return Java.performNow(function () {
                var out = [];
                for (var i = 0; i < lim; i++) {
                    var cn = cands[i];
                    var info = { cls: cn, exists: false, methods: [], ctors: 0, hasCommit: false };
                    try {
                        var k = Java.use(cn);
                        info.exists = true;
                        var ms = k.class.getDeclaredMethods();
                        var names = {};
                        for (var m = 0; m < ms.length; m++) {
                            var mn = ms[m].getName();
                            names[mn] = 1;
                            if (mn.toLowerCase().indexOf('commit') >= 0) info.hasCommit = true;
                        }
                        info.methods = Object.keys(names).slice(0, 30);
                        info.ctors = k.class.getDeclaredConstructors().length;
                    } catch (e0) { info.err = String(e0); }
                    out.push(info);
                }
                return { success: true, tested: lim, classes: out };
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    getWechatVersion: function () {
        if (_wechatVersion) return { success: true, version: _wechatVersion };
        try {
            return Java.performNow(function () {
                var ctx = getCtx();
                var pm = ctx.getPackageManager();
                var info = pm.getPackageInfo(jStr('com.tencent.mm'), 0);
                _wechatVersion = safeStr(info.versionName.value);
                _resolveVersionCompat(_wechatVersion);
                return { success: true, version: _wechatVersion };
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    getVersionCompat: function () {
        return {
            wechat_version: _wechatVersion,
            matched: _versionCompat !== _DEFAULT_COMPAT,
            supported_versions: Object.keys(_VERSION_TABLE),
            active_compat: _versionCompat ? Object.keys(_versionCompat) : [],
        };
    },

    // ==================== H17 消息发送 ====================

    sendMessage: function (params) {
        var toId = (params && params.to_id) || '';
        var content = (params && params.content) || '';
        var msgType = (params && params.msg_type) || 'text';

        if (!toId || !content) {
            return { success: false, error: '缺少 to_id 或 content' };
        }

        log('info', 'rpc', 'sendMessage', { to_id: toId, content_len: content.length, msg_type: msgType });

        try {
            return Java.performNow(function () {
                return _sendMessageInternal(toId, content, msgType);
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    // ==================== H15 消息获取 ====================

    getMessages: function (params) {
        var limit = (params && params.limit) || 3000;
        var offset = (params && params.offset) || 0;
        var conversationId = (params && params.conversation_id) || '';

        var dbRes = null;
        try {
            dbRes = Java.performNow(function () {
                return _getMessagesFromDB(conversationId, limit, offset);
            });
        } catch (e) {
            dbRes = { success: false, error: String(e), messages: [] };
        }
        // DB 读为空时 fallback 到 hook 实时缓冲（insert hook 已拦截新消息）
        if (!dbRes || !dbRes.messages || dbRes.messages.length === 0) {
            offset = parseInt(offset, 10) || 0;
            if (offset < 0) offset = 0;
            var filteredBuffer = _messageBuffer.filter(function (m) {
                return !conversationId || m.from_id === conversationId;
            });
            var start = Math.max(filteredBuffer.length - offset - limit, 0);
            var end = offset > 0 ? filteredBuffer.length - offset : filteredBuffer.length;
            var buf = filteredBuffer.slice(start, end);
            if (buf.length > 0) {
                return {
                    success: true,
                    messages: buf.map(function (m) {
                        return { id: m.msg_svr_id, type: m.msg_type, from_id: m.from_id,
                                 content: m.content, timestamp: m.timestamp, is_send: m.is_send };
                    }),
                    count: buf.length,
                    returned_count: buf.length,
                    requested_limit: limit,
                    offset: offset,
                    total_count: filteredBuffer.length,
                    has_more: filteredBuffer.length > offset + buf.length,
                    source: 'hook_buffer',
                    db_diag: _lastDbDiag,
                };
            }
            if (dbRes) dbRes.db_diag = _lastDbDiag;
        }
        return dbRes;
    },

    getRecentMessages: function (params) {
        var limit = (params && params.limit) || 3000;
        var msgs = _messageBuffer.slice(-limit);
        return { success: true, messages: msgs, count: msgs.length };
    },

    searchMessages: function (params) {
        var keyword = (params && params.keyword) || '';
        var limit = (params && params.limit) || 50;
        if (!keyword) return { success: false, error: '缺少 keyword' };
        try {
            return Java.performNow(function () {
                var sql = "SELECT msgId, type, talker, content, createTime FROM message"
                    + " WHERE content LIKE '%" + keyword.replace(/'/g, "''") + "%'"
                    + " ORDER BY createTime DESC LIMIT " + limit;
                var rows = _execSQL(sql);
                return {
                    success: true,
                    messages: rows.map(function (r) {
                        return { id: r.msgId, type: r.type, from_id: r.talker, content: r.content, timestamp: r.createTime };
                    }),
                    count: rows.length,
                };
            });
        } catch (e) {
            return { success: false, error: String(e), messages: [] };
        }
    },

    // ==================== H16 联系人 ====================

    getContacts: function (params) {
        var limit = (params && params.limit) || 10000;
        var offset = (params && params.offset) || 0;
        try {
            return Java.performNow(function () { return _getContactsFromDB(limit, offset); });
        } catch (e) {
            return { success: false, error: String(e), contacts: [] };
        }
    },

    getContactInfo: function (params) {
        var wxid = (params && params.wxid) || '';
        if (!wxid) return { success: false, error: '缺少 wxid' };
        try {
            return Java.performNow(function () { return _getContactInfoFromDB(wxid); });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    searchContacts: function (params) {
        var keyword = (params && params.keyword) || '';
        var limit = (params && params.limit) || 50;
        if (!keyword) return { success: false, error: '缺少 keyword' };
        try {
            return Java.performNow(function () {
                var kw = keyword.replace(/'/g, "''");
                var sql = "SELECT username, nickname, conRemark, type FROM rcontact"
                    + " WHERE (nickname LIKE '%" + kw + "%' OR conRemark LIKE '%" + kw + "%' OR username LIKE '%" + kw + "%')"
                    + " AND type NOT IN (0, 33) AND verifyFlag=0"
                    + " ORDER BY nickname LIMIT " + limit;
                var rows = _execSQL(sql);
                return {
                    success: true,
                    contacts: rows.map(function (r) {
                        return { wxid: r.username, nickname: r.nickname, remark: r.conRemark, type: r.type };
                    }),
                    count: rows.length,
                };
            });
        } catch (e) {
            return { success: false, error: String(e), contacts: [] };
        }
    },

    // ==================== H18/H19 好友请求 ====================

    getFriendRequests: function (params) {
        var limit = (params && params.limit) || 50;
        var cached = _friendRequestBuffer.slice(-limit);
        if (cached.length > 0) return { success: true, requests: cached, count: cached.length, source: 'buffer' };
        try {
            return Java.performNow(function () { return _getFriendRequestsFromDB(limit); });
        } catch (e) {
            return { success: false, error: String(e), requests: [] };
        }
    },

    addFriend: function (params) {
        var userId = (params && params.user_id) || '';
        var message = (params && params.message) || '你好，我想加你为好友';
        var sourceType = (params && params.source_type) || 3; // 3=搜索

        if (!userId) return { success: false, error: '缺少 user_id' };
        log('info', 'rpc', 'addFriend', { user_id: userId, source_type: sourceType });

        try {
            return Java.performNow(function () {
                return _addFriendInternal(userId, message, sourceType);
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    acceptFriend: function (params) {
        var encryptUserName = (params && params.encrypt_username) || '';
        var ticket = (params && params.ticket) || '';

        if (!encryptUserName && !ticket) return { success: false, error: '缺少 encrypt_username 或 ticket' };
        log('info', 'rpc', 'acceptFriend', { encrypt_username: encryptUserName });

        try {
            return Java.performNow(function () {
                return _acceptFriendInternal(encryptUserName, ticket);
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    deleteFriend: function (params) {
        var wxid = (params && params.wxid) || '';
        if (!wxid) return { success: false, error: '缺少 wxid' };
        try {
            return Java.performNow(function () {
                return _deleteFriendInternal(wxid);
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    setFriendRemark: function (params) {
        var wxid = (params && params.wxid) || '';
        var remark = (params && params.remark) || '';
        if (!wxid) return { success: false, error: '缺少 wxid' };
        try {
            return Java.performNow(function () {
                return _setRemarkInternal(wxid, remark);
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    // ==================== H22 群管理 ====================

    getGroups: function (params) {
        var limit = (params && params.limit) || 100;
        var offset = (params && params.offset) || 0;
        try {
            return Java.performNow(function () { return _getGroupsFromDB(limit, offset); });
        } catch (e) {
            return { success: false, error: String(e), groups: [] };
        }
    },

    getGroupMembers: function (params) {
        var groupId = (params && params.group_id) || '';
        if (!groupId) return { success: false, error: '缺少 group_id' };
        try {
            return Java.performNow(function () { return _getGroupMembersFromDB(groupId); });
        } catch (e) {
            return { success: false, error: String(e), members: [] };
        }
    },

    getGroupInfo: function (params) {
        var groupId = (params && params.group_id) || '';
        if (!groupId) return { success: false, error: '缺少 group_id' };
        try {
            return Java.performNow(function () { return _getGroupInfoFromDB(groupId); });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    sendGroupMessage: function (params) {
        var groupId = (params && params.group_id) || '';
        var content = (params && params.content) || '';
        if (!groupId || !content) return { success: false, error: '缺少 group_id 或 content' };
        try {
            return Java.performNow(function () {
                return _sendMessageInternal(groupId, content, 'text');
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    createGroup: function (params) {
        var memberIds = (params && params.member_ids) || [];
        var topic = (params && params.topic) || '';
        if (memberIds.length < 2) return { success: false, error: '建群至少需要2个成员' };
        try {
            return Java.performNow(function () {
                return _createGroupInternal(memberIds, topic);
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    inviteToGroup: function (params) {
        var groupId = (params && params.group_id) || '';
        var memberIds = (params && params.member_ids) || [];
        if (!groupId || memberIds.length === 0) return { success: false, error: '缺少 group_id 或 member_ids' };
        try {
            return Java.performNow(function () {
                return _inviteToGroupInternal(groupId, memberIds);
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    removeFromGroup: function (params) {
        var groupId = (params && params.group_id) || '';
        var memberIds = (params && params.member_ids) || [];
        if (!groupId || memberIds.length === 0) return { success: false, error: '缺少 group_id 或 member_ids' };
        try {
            return Java.performNow(function () {
                return _removeFromGroupInternal(groupId, memberIds);
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    setGroupAnnouncement: function (params) {
        var groupId = (params && params.group_id) || '';
        var announcement = (params && params.announcement) || '';
        if (!groupId) return { success: false, error: '缺少 group_id' };
        try {
            return Java.performNow(function () {
                return _setGroupAnnouncementInternal(groupId, announcement);
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    setGroupName: function (params) {
        var groupId = (params && params.group_id) || '';
        var name = (params && params.name) || '';
        if (!groupId || !name) return { success: false, error: '缺少 group_id 或 name' };
        try {
            return Java.performNow(function () {
                return _setGroupNameInternal(groupId, name);
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    quitGroup: function (params) {
        var groupId = (params && params.group_id) || '';
        if (!groupId) return { success: false, error: '缺少 group_id' };
        try {
            return Java.performNow(function () {
                return _quitGroupInternal(groupId);
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    // ==================== H20/H21 朋友圈 ====================

    postMoments: function (params) {
        var content = (params && params.content) || '';
        var imageUrls = (params && params.image_urls) || [];
        if (!content && imageUrls.length === 0) return { success: false, error: '朋友圈内容不能为空' };
        var autoSend = !(params && params.auto_send === false);
        try {
            return Java.performNow(function () {
                return _postMomentsInternal(content, imageUrls, autoSend);
            });
        } catch (e) {
            return { success: false, error: '发朋友圈失败: ' + String(e) };
        }
    },

    getMoments: function (params) {
        var limit = (params && params.limit) || 20;
        var wxid = (params && params.wxid) || '';
        try {
            return Java.performNow(function () { return _getMomentsFromDB(wxid, limit); });
        } catch (e) {
            return { success: false, error: String(e), moments: [] };
        }
    },

    likeMoments: function (params) {
        var snsId = (params && params.sns_id) || '';
        if (!snsId) return { success: false, error: '缺少 sns_id' };
        try {
            return Java.performNow(function () {
                return _likeMomentsInternal(snsId);
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    commentMoments: function (params) {
        var snsId = (params && params.sns_id) || '';
        var comment = (params && params.comment) || '';
        if (!snsId || !comment) return { success: false, error: '缺少 sns_id 或 comment' };
        try {
            return Java.performNow(function () {
                return _commentMomentsInternal(snsId, comment);
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    deleteMoments: function (params) {
        var snsId = (params && params.sns_id) || '';
        if (!snsId) return { success: false, error: '缺少 sns_id' };
        return _intentAction('com.workphone.DELETE_MOMENTS', { sns_id: snsId }, 'moments_deleted', { sns_id: snsId });
    },

    // ==================== H23 账号管理 ====================

    getProfile: function () {
        try {
            return Java.performNow(function () {
                var sql = "SELECT value FROM userinfo WHERE id=4";
                var rows = _execSQL(sql);
                var nickname = rows.length > 0 ? rows[0].value : '';
                var sql2 = "SELECT value FROM userinfo WHERE id=2";
                var rows2 = _execSQL(sql2);
                var wxid = rows2.length > 0 ? rows2[0].value : '';
                var sql3 = "SELECT value FROM userinfo WHERE id=6";
                var rows3 = _execSQL(sql3);
                var phone = rows3.length > 0 ? rows3[0].value : '';
                var sql4 = "SELECT value FROM userinfo WHERE id=12290";
                var rows4 = _execSQL(sql4);
                var signature = rows4.length > 0 ? rows4[0].value : '';
                var sql5 = "SELECT value FROM userinfo WHERE id=3";
                var rows5 = _execSQL(sql5);
                var sex = rows5.length > 0 ? rows5[0].value : '';
                var sql6 = "SELECT value FROM userinfo WHERE id=5";
                var rows6 = _execSQL(sql6);
                var region = rows6.length > 0 ? rows6[0].value : '';
                return {
                    success: true,
                    profile: { nickname: nickname, wxid: wxid, phone: phone, signature: signature, sex: sex, region: region, wechat_version: _wechatVersion },
                };
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    checkAccountStatus: function () {
        try {
            return Java.performNow(function () {
                var ctx = getCtx();
                var pm = ctx.getPackageManager();
                var info = pm.getPackageInfo(jStr('com.tencent.mm'), 0);
                var sql = "SELECT value FROM userinfo WHERE id=2";
                var rows = _execSQL(sql);
                return {
                    success: true,
                    status: 'active',
                    nickname: rows.length > 0 ? rows[0].value : '',
                    version: safeStr(info.versionName.value),
                    pid: Process.id,
                };
            });
        } catch (e) {
            return { success: false, error: String(e), status: 'unknown' };
        }
    },

    setNickname: function (params) {
        var nickname = (params && params.nickname) || '';
        if (!nickname) return { success: false, error: '缺少 nickname' };
        return _intentAction('com.workphone.SET_NICKNAME', { nickname: nickname }, 'nickname_changed', { nickname: nickname });
    },

    setSignature: function (params) {
        var signature = (params && params.signature) || '';
        return _intentAction('com.workphone.SET_SIGNATURE', { signature: signature }, 'signature_changed', { signature: signature });
    },

    setAvatar: function (params) {
        var imagePath = (params && params.image_path) || '';
        if (!imagePath) return { success: false, error: '缺少 image_path' };
        return _intentAction('com.workphone.SET_AVATAR', { image_path: imagePath }, 'avatar_changed', { image_path: imagePath });
    },

    setSex: function (params) {
        var sex = (params && params.sex) || '1';
        return _intentAction('com.workphone.SET_SEX', { sex: sex }, 'sex_changed', { sex: sex });
    },

    setRegion: function (params) {
        var region = (params && params.region) || '';
        if (!region) return { success: false, error: '缺少 region' };
        return _intentAction('com.workphone.SET_REGION', { region: region }, 'region_changed', { region: region });
    },

    setWhatUp: function (params) {
        var status = (params && params.status) || '';
        return _intentAction('com.workphone.SET_WHATSUP', { status: status }, 'status_changed', { status: status });
    },

    // ==================== H24 账号安全 ====================

    unblockSelf: function () {
        return _intentAction('com.workphone.UNBLOCK_SELF', {}, 'unblock_started', {});
    },

    changePassword: function (params) {
        var oldPwd = (params && params.old_password) || '';
        var newPwd = (params && params.new_password) || '';
        if (!oldPwd || !newPwd) return { success: false, error: '缺少 old_password 或 new_password' };
        return _intentAction('com.workphone.CHANGE_PASSWORD', { old_password: oldPwd, new_password: newPwd }, 'password_changed', {});
    },

    bindPhone: function (params) {
        var phone = (params && params.phone) || '';
        if (!phone) return { success: false, error: '缺少 phone' };
        return _intentAction('com.workphone.BIND_PHONE', { phone: phone }, 'phone_bind_started', { phone: phone });
    },

    unbindPhone: function () {
        return _intentAction('com.workphone.UNBIND_PHONE', {}, 'phone_unbind_started', {});
    },

    getLoginDevices: function () {
        return _intentAction('com.workphone.GET_LOGIN_DEVICES', {}, null, {});
    },

    removeLoginDevice: function (params) {
        var deviceId = (params && params.device_id) || '';
        if (!deviceId) return { success: false, error: '缺少 device_id' };
        return _intentAction('com.workphone.REMOVE_LOGIN_DEVICE', { device_id: deviceId }, 'login_device_removed', { device_id: deviceId });
    },

    enableFingerprint: function (params) {
        var enable = params && params.enable !== undefined ? params.enable : true;
        return _intentAction('com.workphone.TOGGLE_FINGERPRINT', { enable: String(enable) }, 'fingerprint_toggled', { enable: enable });
    },

    setAccountProtection: function (params) {
        var enable = params && params.enable !== undefined ? params.enable : true;
        return _intentAction('com.workphone.SET_ACCOUNT_PROTECTION', { enable: String(enable) }, 'account_protection_set', { enable: enable });
    },

    // ==================== H25 支付 ====================

    sendRedPacket: function (params) {
        var toId = (params && params.to_id) || '';
        var amount = (params && params.amount) || '';
        var message = (params && params.message) || '恭喜发财';
        if (!toId || !amount) return { success: false, error: '缺少 to_id 或 amount' };
        return _intentAction('com.workphone.SEND_RED_PACKET', { to_id: toId, amount: amount, message: message }, 'red_packet_sent', { to_id: toId, amount: amount });
    },

    receiveRedPacket: function (params) {
        var msgSvrId = (params && params.msg_svr_id) || '';
        if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id' };
        return _intentAction('com.workphone.RECEIVE_RED_PACKET', { msg_svr_id: msgSvrId }, 'red_packet_received', { msg_svr_id: msgSvrId });
    },

    sendTransfer: function (params) {
        var toId = (params && params.to_id) || '';
        var amount = (params && params.amount) || '';
        var desc = (params && params.description) || '';
        if (!toId || !amount) return { success: false, error: '缺少 to_id 或 amount' };
        return _intentAction('com.workphone.SEND_TRANSFER', { to_id: toId, amount: amount, description: desc }, 'transfer_sent', { to_id: toId, amount: amount });
    },

    receiveTransfer: function (params) {
        var msgSvrId = (params && params.msg_svr_id) || '';
        if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id' };
        return _intentAction('com.workphone.RECEIVE_TRANSFER', { msg_svr_id: msgSvrId }, 'transfer_received', { msg_svr_id: msgSvrId });
    },

    getWalletBalance: function () {
        return _intentAction('com.workphone.GET_WALLET_BALANCE', {}, null, {});
    },

    getTransactionHistory: function (params) {
        var limit = (params && params.limit) || 20;
        return _intentAction('com.workphone.GET_TRANSACTIONS', { limit: String(limit) }, null, {});
    },

    showPaymentCode: function () {
        return _intentAction('com.workphone.SHOW_PAYMENT_CODE', {}, null, {});
    },

    getSafetyCenter: function () {
        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 (params) {
        var limit = (params && params.limit) || 5000;
        var offset = (params && params.offset) || 0;
        try {
            return Java.performNow(function () {
                return _getLabelsFromDB(limit, offset);
            });
        } catch (e) {
            return { success: false, error: String(e), labels: [] };
        }
    },

    createLabel: function (params) {
        var name = (params && params.name) || '';
        if (!name) return { success: false, error: '缺少 name' };
        try {
            return Java.performNow(function () {
                var nextId = _nextLabelId();
                var esc = _escapeSql(name);
                var idStr = String(nextId);
                var wr = _execSQLWrite(
                    "INSERT INTO contactlabel (labelID, labelName) VALUES ('" + idStr + "', '" + esc + "')"
                );
                if (!wr.success) {
                    wr = _execSQLWrite(
                        "INSERT INTO contactlabelinfo (labelId, labelName) VALUES ('" + idStr + "', '" + esc + "')"
                    );
                }
                if (!wr.success) return { success: false, error: wr.error, method: 'wcdb_insert' };
                _tryNotifyLabelChanged();
                return { success: true, label_id: idStr, name: name, method: 'wcdb_insert' };
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    deleteLabel: function (params) {
        var labelId = (params && (params.label_id || params.labelId)) || '';
        if (!labelId) return { success: false, error: '缺少 label_id' };
        try {
            return Java.performNow(function () {
                var escId = _escapeSql(labelId);
                var wr = _execSQLWrite("DELETE FROM contactlabel WHERE labelID='" + escId + "'");
                if (!wr.success) {
                    wr = _execSQLWrite("DELETE FROM contactlabelinfo WHERE labelId='" + escId + "'");
                }
                if (!wr.success) return { success: false, error: wr.error, method: 'wcdb_delete' };
                _execSQLWrite(
                    "UPDATE rcontact SET contactLabelIds=REPLACE(REPLACE(REPLACE(contactLabelIds,'" + escId + ",',''),'," + escId + "',''),'" + escId + "','') WHERE contactLabelIds LIKE '%" + escId + "%'"
                );
                _execSQLWrite(
                    "UPDATE rcontact SET labelidlist=REPLACE(REPLACE(REPLACE(labelidlist,'" + escId + ",',''),'," + escId + "',''),'" + escId + "','') WHERE labelidlist LIKE '%" + escId + "%'"
                );
                _tryNotifyLabelChanged();
                return { success: true, label_id: String(labelId), method: 'wcdb_delete' };
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    setContactLabel: function (params) {
        var wxid = (params && params.wxid) || '';
        var labelIds = _normalizeLabelIds((params && params.label_ids) || (params && params.labelIds) || []);
        if (!wxid) return { success: false, error: '缺少 wxid' };
        try {
            return Java.performNow(function () {
                var escWxid = _escapeSql(wxid);
                var joined = labelIds.join(',');
                var escJoined = _escapeSql(joined);
                var wr = _execSQLWrite(
                    "UPDATE rcontact SET contactLabelIds='" + escJoined + "' WHERE username='" + escWxid + "'"
                );
                if (!wr.success) {
                    wr = _execSQLWrite(
                        "UPDATE rcontact SET labelidlist='" + escJoined + "' WHERE username='" + escWxid + "'"
                    );
                }
                if (!wr.success) return { success: false, error: wr.error, method: 'wcdb_update' };
                _tryNotifyLabelChanged();
                return { success: true, wxid: wxid, label_ids: labelIds, method: 'wcdb_update' };
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    getContactsByLabel: function (params) {
        var labelId = (params && params.label_id) || '';
        if (!labelId) return { success: false, error: '缺少 label_id' };
        try {
            return Java.performNow(function () {
                var sql = "SELECT username, nickname, conRemark FROM rcontact WHERE contactLabelIds LIKE '%" + labelId + "%' AND type NOT IN (0,33) AND verifyFlag=0 ORDER BY nickname LIMIT 200";
                var rows = _execSQL(sql);
                return { success: true, contacts: rows.map(function (r) { return { wxid: r.username, nickname: r.nickname, remark: r.conRemark }; }), count: rows.length };
            });
        } catch (e) {
            return { success: false, error: String(e), contacts: [] };
        }
    },

    // ==================== H29 收藏管理 ====================

    getFavorites: function (params) {
        var limit = (params && params.limit) || 50;
        try {
            return Java.performNow(function () {
                var names = ['Favorite.db', 'favorite.db', 'EnFavorite.db'];
                var favPath = null;
                for (var i = 0; i < names.length; i++) {
                    favPath = _findDbPath(names[i]);
                    if (favPath) break;
                }
                if (!favPath) {
                    var sql2 = "SELECT localId, type, sourceUserName, updateTime FROM FavItemInfo ORDER BY updateTime DESC LIMIT " + limit;
                    var rows2 = _execSQL(sql2);
                    if (rows2.length > 0) return { success: true, favorites: rows2, count: rows2.length, source: 'main_db' };
                    return { success: true, favorites: [], count: 0, note: 'Favorite 表不在独立数据库中，主库也未找到' };
                }
                var sql = "SELECT localId, type, sourceUserName, updateTime FROM FavItemInfo ORDER BY updateTime DESC LIMIT " + limit;
                var rows = _execSQL(sql, favPath);
                return { success: true, favorites: rows, count: rows.length };
            });
        } catch (e) {
            return { success: false, error: String(e), favorites: [] };
        }
    },

    addFavorite: function (params) {
        var msgSvrId = (params && params.msg_svr_id) || '';
        var type = (params && params.type) || 'message';
        var content = (params && params.content) || (params && params.content_desc) || '';
        if (!msgSvrId && content) {
            return _intentAction('com.workphone.ADD_FAVORITE_TEXT', { content: content, type: type }, 'favorite_added', { content: content });
        }
        if (!msgSvrId) return { success: false, error: '缺少 msg_svr_id 或 content' };
        return _intentAction('com.workphone.ADD_FAVORITE', { msg_svr_id: msgSvrId, type: type }, 'favorite_added', { msg_svr_id: msgSvrId });
    },

    deleteFavorite: function (params) {
        var localId = (params && params.local_id) || '';
        if (!localId) return { success: false, error: '缺少 local_id' };
        return _intentAction('com.workphone.DELETE_FAVORITE', { local_id: localId }, 'favorite_deleted', { local_id: localId });
    },

    // ==================== H30 设置 ====================

    setPrivacy: function (params) {
        var setting = (params && params.setting) || '';
        var value = params && params.value !== undefined ? params.value : true;
        if (!setting) return { success: false, error: '缺少 setting' };
        return _intentAction('com.workphone.SET_PRIVACY', { setting: setting, value: String(value) }, 'privacy_set', { setting: setting, value: value });
    },

    setNotification: function (params) {
        var type = (params && params.type) || 'all';
        var enable = params && params.enable !== undefined ? params.enable : true;
        return _intentAction('com.workphone.SET_NOTIFICATION', { type: type, enable: String(enable) }, 'notification_set', { type: type, enable: enable });
    },

    clearChatHistory: function (params) {
        var wxid = (params && params.wxid) || '';
        if (!wxid) return { success: false, error: '缺少 wxid' };
        return _intentAction('com.workphone.CLEAR_CHAT', { wxid: wxid }, 'chat_cleared', { wxid: wxid });
    },

    setChatBackground: function (params) {
        var wxid = (params && params.wxid) || '';
        var imagePath = (params && params.image_path) || '';
        return _intentAction('com.workphone.SET_CHAT_BG', { wxid: wxid, image_path: imagePath }, 'chat_bg_set', { wxid: wxid });
    },

    setDoNotDisturb: function (params) {
        var wxid = (params && params.wxid) || '';
        var enable = params && params.enable !== undefined ? params.enable : true;
        if (!wxid) {
            return _intentAction('com.workphone.SET_GLOBAL_DND', { enable: String(enable) }, 'dnd_set', { enable: enable });
        }
        return _intentAction('com.workphone.SET_DND', { wxid: wxid, enable: String(enable) }, 'dnd_set', { wxid: wxid, enable: enable });
    },

    pinChat: function (params) {
        var wxid = (params && params.wxid) || '';
        var pin = params && params.pin !== undefined ? params.pin : true;
        if (!wxid) return { success: false, error: '缺少 wxid' };
        return _intentAction('com.workphone.PIN_CHAT', { wxid: wxid, pin: String(pin) }, 'chat_pinned', { wxid: wxid, pin: pin });
    },

    // ==================== H31 搜索 ====================

    globalSearch: function (params) {
        var keyword = (params && params.keyword) || '';
        var limit = (params && params.limit) || 30;
        if (!keyword) return { success: false, error: '缺少 keyword' };
        try {
            return Java.performNow(function () {
                var kw = keyword.replace(/'/g, "''");
                var contactSql = "SELECT username, nickname, conRemark FROM rcontact WHERE (nickname LIKE '%" + kw + "%' OR conRemark LIKE '%" + kw + "%') AND type NOT IN (0,33) AND verifyFlag=0 LIMIT " + limit;
                var contacts = _execSQL(contactSql);
                var msgSql = "SELECT msgId, talker, content, createTime FROM message WHERE content LIKE '%" + kw + "%' ORDER BY createTime DESC LIMIT " + limit;
                var messages = _execSQL(msgSql);
                return {
                    success: true,
                    contacts: contacts.map(function (r) { return { wxid: r.username, nickname: r.nickname, remark: r.conRemark }; }),
                    messages: messages.map(function (r) { return { id: r.msgId, from_id: r.talker, content: r.content, timestamp: r.createTime }; }),
                };
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    // ==================== H32 小程序 ====================

    openMiniProgram: function (params) {
        var appId = (params && params.app_id) || (params && params.name) || '';
        var path = (params && params.path) || '';
        if (!appId) return { success: false, error: '缺少 app_id' };
        return _intentAction('com.workphone.OPEN_MINI_PROGRAM', { app_id: appId, path: path }, 'mini_program_opened', { app_id: appId });
    },

    getRecentMiniPrograms: function () {
        return _intentAction('com.workphone.GET_RECENT_MINI_PROGRAMS', {}, null, {});
    },

    shareMiniProgram: function (params) {
        var appId = (params && params.app_id) || '';
        var toId = (params && params.to_id) || '';
        var title = (params && params.title) || '';
        if (!appId || !toId) return { success: false, error: '缺少 app_id 或 to_id' };
        return _intentAction('com.workphone.SHARE_MINI_PROGRAM', { app_id: appId, to_id: toId, title: title }, 'mini_program_shared', { app_id: appId, to_id: toId });
    },

    // ==================== H33 文件传输 ====================

    sendImage: function (params) {
        var toId = (params && params.to_id) || '';
        var imagePath = (params && params.image_path) || '';
        if (!toId || !imagePath) return { success: false, error: '缺少 to_id 或 image_path' };
        try {
            return Java.performNow(function () {
                return _shareMediaToWechat(imagePath, 'image/*', toId, 'image');
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    sendVideo: function (params) {
        var toId = (params && params.to_id) || '';
        var videoPath = (params && params.video_path) || '';
        if (!toId || !videoPath) return { success: false, error: '缺少 to_id 或 video_path' };
        try {
            return Java.performNow(function () {
                return _shareMediaToWechat(videoPath, 'video/*', toId, 'video');
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    sendFile: function (params) {
        var toId = (params && params.to_id) || '';
        var filePath = (params && params.file_path) || '';
        if (!toId || !filePath) return { success: false, error: '缺少 to_id 或 file_path' };
        try {
            return Java.performNow(function () {
                return _shareMediaToWechat(filePath, '*/*', toId, 'file');
            });
        } catch (e) {
            return { success: false, error: String(e) };
        }
    },

    sendVoice: function (params) {
        var toId = (params && params.to_id) || (params && params.user_id) || '';
        var voicePath = (params && params.voice_path) || '';
        var duration = (params && params.duration) || 0;
        if (!toId) return { success: false, error: '缺少 to_id' };
        if (!voicePath && duration) {
            return _intentAction('com.workphone.SEND_VOICE_RECORD', { to_id: toId, duration: String(duration) }, 'voice_sent', { to_id: toId, duration: duration });
        }
        if (!voicePath) return { success: false, error: '缺少 voice_path' };
        return _intentAction('com.workphone.SEND_VOICE', { to_id: toId, voice_path: voicePath, duration: String(duration) }, 'voice_sent', { to_id: toId });
    },

    sendLocation: function (params) {
        var toId = (params && params.to_id) || '';
        var lat = (params && params.latitude) || '';
        var lng = (params && params.longitude) || '';
        var label = (params && params.label) || '';
        if (!toId || !lat || !lng) return { success: false, error: '缺少 to_id/latitude/longitude' };
        return _intentAction('com.workphone.SEND_LOCATION', { to_id: toId, latitude: lat, longitude: lng, label: label }, 'location_sent', { to_id: toId });
    },

    sendCard: function (params) {
        var toId = (params && params.to_id) || '';
        var cardWxid = (params && params.card_wxid) || '';
        if (!toId || !cardWxid) return { success: false, error: '缺少 to_id 或 card_wxid' };
        return _intentAction('com.workphone.SEND_CARD', { to_id: toId, card_wxid: cardWxid }, 'card_sent', { to_id: toId, card_wxid: cardWxid });
    },

    sendLink: function (params) {
        var toId = (params && params.to_id) || '';
        var url = (params && params.url) || '';
        var title = (params && params.title) || '';
        var desc = (params && params.description) || '';
        if (!toId || !url) return { success: false, error: '缺少 to_id 或 url' };
        var _linkText = (title ? title + '\n' : '') + (desc ? desc + '\n' : '') + url;
        return _shareTextToWechat(_linkText, toId);
    },

    // ==================== H34 消息转发 ====================

    forwardMessage: function (params) {
        var msgSvrId = (params && params.msg_svr_id) || '';
        var toId = (params && params.to_id) || '';
        if (!msgSvrId || !toId) return { success: false, error: '缺少 msg_svr_id 或 to_id' };
        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;
    var sendMethod = 'failed';
    var intentFailure = null;

    // 方案 A: 直接调用微信内部消息发送类（按版本兼容表自动选择）
    var candidateClasses = getCompat('send_message');

    for (var i = 0; i < candidateClasses.length && !sent; i++) {
        try {
            var c = candidateClasses[i];
            var MsgCls = Java.use(c.cls);
            if (MsgCls) {
                var m = c.sig ? MsgCls[c.method].overload.apply(MsgCls[c.method], c.sig) : MsgCls[c.method];
                if (c.sig && c.sig.length === 3) {
                    m.call(MsgCls, jStr(toId), jStr(content), jStr(msgType === 'text' ? '' : msgType));
                } else {
                    m.call(MsgCls, jStr(toId), jStr(content));
                }
                sent = true;
                sendMethod = 'internal_' + c.cls.split('.').pop();
                log('info', 'send', '消息通过内部方法发送: ' + c.cls);
                emitEvent('message_sent', { to_id: toId, content: content, msg_type: msgType, method: sendMethod });
            }
        } catch (_) { continue; }
    }

    // 方案 B: Intent 广播降级；无真实接收端时不得把广播计为发送成功。
    if (!sent) {
        var intentResult = _intentAction(
            'com.workphone.SEND_MESSAGE',
            { to_id: toId, content: content, msg_type: msgType },
            'message_sent',
            { to_id: toId, content: content, msg_type: msgType, method: 'intent_broadcast' }
        );
        if (intentResult && intentResult.success) {
            sent = true;
            sendMethod = 'intent_broadcast';
            log('info', 'send', '消息通过 Intent 广播发送');
        } else {
            intentFailure = intentResult || { success: false, error: 'intent_dispatch_failed' };
            log('warn', 'send', 'Intent 发送未验证: ' + safeStr(intentFailure.error));
        }
    }

    var messageId = sent ? generateId('msg') : null;
    if (sent) {
        var talker = (toId === '文件传输助手' || toId === 'File Transfer') ? 'filehelper' : toId;
        _messageBuffer.push({
            msg_svr_id: messageId,
            msg_type: msgType || 'text',
            from_id: talker,
            content: content,
            timestamp: new Date().toISOString(),
            is_send: true,
        });
        if (_messageBuffer.length > CONFIG.MESSAGE_BUFFER_MAX) {
            _messageBuffer = _messageBuffer.slice(-Math.floor(CONFIG.MESSAGE_BUFFER_MAX * 0.6));
        }
    }

    var result = {
        success: sent,
        message_id: messageId,
        method: sendMethod,
        verified: sent,
        to_id: toId,
    };
    if (!sent && intentFailure) {
        result.error = intentFailure.error || 'send_failed';
        result.action = intentFailure.action || 'com.workphone.SEND_MESSAGE';
        result.hint = intentFailure.hint || '';
    }
    return result;
}

// ============================================================
// § 6  内部实现 — 数据库读取 (H15/H16)
// ============================================================

function _getMessagesFromDB(conversationId, limit, offset) {
    limit = parseInt(limit, 10) || 3000;
    if (limit < 1) limit = 3000;
    if (limit > 5000) limit = 5000;
    offset = parseInt(offset, 10) || 0;
    if (offset < 0) offset = 0;
    // DB talker 用 wxid；显示名「文件传输助手」→ filehelper
    if (conversationId === '文件传输助手' || conversationId === 'File Transfer') {
        conversationId = 'filehelper';
    }
    var where = conversationId
        ? " WHERE talker='" + conversationId.replace(/'/g, "''") + "'"
        : '';
    var sql = 'SELECT msgId, type, talker, content, createTime, isSend FROM message'
        + where + ' ORDER BY createTime DESC LIMIT ' + limit + ' OFFSET ' + offset;
    var rows = _execSQL(sql);
    var totalCount = null;
    try {
        var cntRows = _execSQL('SELECT count(*) AS n FROM message' + where);
        totalCount = cntRows && cntRows[0] ? parseInt(cntRows[0].n, 10) : null;
    } catch (e) { /* ignore count failure */ }
    return {
        success: true,
        messages: rows.map(function (r) {
            return {
                id: r.msgId, type: r.type, from_id: r.talker,
                content: r.content, timestamp: r.createTime,
                is_send: r.isSend === '1',
            };
        }),
        count: rows.length,
        returned_count: rows.length,
        total_count: totalCount,
        requested_limit: limit,
        offset: offset,
        has_more: totalCount !== null ? totalCount > offset + rows.length : rows.length >= limit,
    };
}

function _getContactsFromDB(limit, offset) {
    limit = parseInt(limit, 10) || 10000;
    if (limit < 1) limit = 10000;
    if (limit > 20000) limit = 20000;
    offset = parseInt(offset, 10) || 0;
    if (offset < 0) offset = 0;
    var labelMap = {};
    try {
        var labelRows = _execSQL("SELECT labelId, labelName FROM contactlabelinfo");
        labelRows.forEach(function (r) {
            labelMap[r.labelId] = r.labelName;
        });
    } catch (e) { /* 部分版本无标签表 */ }

    var contactLabelMap = {};
    try {
        // 8.0.69 列名为 contactLabelIds（旧版 labelidlist）
        var clRows = _execSQL(
            "SELECT username, contactLabelIds FROM rcontact WHERE contactLabelIds IS NOT NULL AND contactLabelIds != ''"
        );
        clRows.forEach(function (r) {
            if (!r.contactLabelIds) return;
            var ids = String(r.contactLabelIds).split(",").filter(function (id) { return id; });
            contactLabelMap[r.username] = ids.map(function (id) {
                return labelMap[id] || id;
            }).filter(function (n) { return n; });
        });
    } catch (e) { /* ignore */ }

    // 8.0.69 兼容：先探测 rcontact 实际列，只 SELECT 存在的列（避免缺列如 signature/country 致整查询返 0）
    var _wantCols = ["username", "nickname", "conRemark", "type", "alias", "imgFlag", "sex", "country", "province", "city", "signature"];
    var _availCols = {};
    try {
        var _ti = _execSQL("PRAGMA table_info(rcontact)");
        (_ti || []).forEach(function (c) { if (c && c.name) _availCols[c.name] = true; });
    } catch (ept) { /* PRAGMA 不支持时退回全列 */ }
    var _useCols = Object.keys(_availCols).length > 0
        ? _wantCols.filter(function (c) { return _availCols[c]; })
        : _wantCols;
    if (_useCols.indexOf("username") < 0) _useCols.unshift("username");
    var _cols = "SELECT " + _useCols.join(", ") + " FROM rcontact ";
    var _excludeSys = "username NOT LIKE '%@chatroom' AND username NOT LIKE 'gh_%' "
        + "AND username NOT IN ('filehelper','fmessage','medianote','floatbottle','newsapp','weixin','qmessage','tmessage') ";
    var page = " LIMIT " + limit + " OFFSET " + offset;
    var sql = _cols + "WHERE type NOT IN (0, 33) AND verifyFlag=0 AND " + _excludeSys
        + "ORDER BY conRemark, nickname" + page;
    var rows = _execSQL(sql);
    var _diag = { strict: (rows || []).length, use_cols: _useCols, avail_cols: Object.keys(_availCols), requested_limit: limit, offset: offset };
    try {
        var rawCnt = _execSQL("SELECT count(*) AS n FROM rcontact");
        _diag.rcontact_total = rawCnt && rawCnt[0] ? parseInt(rawCnt[0].n, 10) : null;
    } catch (e0a) { _diag.rcontact_total_err = String(e0a); }
    try {
        var filteredCnt = _execSQL("SELECT count(*) AS n FROM rcontact WHERE type NOT IN (0, 33) AND verifyFlag=0 AND " + _excludeSys);
        _diag.filtered_total = filteredCnt && filteredCnt[0] ? parseInt(filteredCnt[0].n, 10) : null;
    } catch (e0b) { _diag.filtered_total_err = String(e0b); }
    // 8.0.69 兼容回退①：严格过滤(type/verifyFlag)为空时，放宽为仅排除系统/群/公众号
    if (!rows || rows.length === 0) {
        var sqlRelax = _cols + "WHERE verifyFlag=0 AND " + _excludeSys
            + "ORDER BY conRemark, nickname" + page;
        var relaxed = _execSQL(sqlRelax);
        _diag.relax_verifyflag = (relaxed || []).length;
        if (relaxed && relaxed.length > 0) {
            log('info', 'contacts', '严格过滤空，回退宽过滤命中 ' + relaxed.length + ' 条');
            rows = relaxed;
        }
    }
    // 8.0.69 兼容回退②：连 verifyFlag=0 也空时，丢弃 verifyFlag，仅排除系统/群/公众号（部分版本好友 verifyFlag!=0）
    if (!rows || rows.length === 0) {
        var sqlNoVerify = _cols + "WHERE " + _excludeSys + "ORDER BY conRemark, nickname" + page;
        var noverify = _execSQL(sqlNoVerify);
        _diag.relax_noverify = (noverify || []).length;
        if (noverify && noverify.length > 0) {
            log('info', 'contacts', 'verifyFlag 过滤空，丢弃 verifyFlag 命中 ' + noverify.length + ' 条');
            rows = noverify;
        }
    }
    // 诊断：rcontact 原始总行数 + 字段探测（定位 8.0.69 读空根因，写入返回的 _diag）
    if (!rows || rows.length === 0) {
        try { var rawCnt = _execSQL("SELECT count(*) AS n FROM rcontact"); _diag.rcontact_total = rawCnt && rawCnt[0] ? rawCnt[0].n : null; } catch (e0) { _diag.rcontact_total_err = String(e0); }
        try { var vf = _execSQL("SELECT verifyFlag, count(*) AS n FROM rcontact GROUP BY verifyFlag"); _diag.verifyflag_dist = vf; } catch (e1) { _diag.verifyflag_err = String(e1); }
        try { var tp = _execSQL("SELECT type, count(*) AS n FROM rcontact GROUP BY type LIMIT 20"); _diag.type_dist = tp; } catch (e2) { _diag.type_err = String(e2); }
    }
    var sexMap = { "0": "unknown", "1": "male", "2": "female" };
    var contacts = rows.map(function (r) {
        var nick = r.nickname || "";
        var remark = r.conRemark || "";
        var alias = r.alias || "";
        var wxid = r.username || "";
        var display = nick || remark || alias || wxid;
        return {
            wxid: wxid,
            user_id: wxid,
            nickname: nick,
            display_name: display,
            remark: remark,
            alias: alias,
            wechat_id: alias,
            type: r.type,
            sex: sexMap[String(r.sex)] || "unknown",
            country: r.country || "",
            province: r.province || "",
            city: r.city || "",
            signature: r.signature || "",
            tags: contactLabelMap[wxid] || [],
            has_avatar: (r.imgFlag || 0) > 0,
        };
    });
    _contactCache = contacts;
    return {
        success: true,
        contacts: contacts,
        count: contacts.length,
        returned_count: contacts.length,
        total_count: _diag.filtered_total,
        raw_total_count: _diag.rcontact_total,
        requested_limit: limit,
        offset: offset,
        has_more: _diag.filtered_total !== null && _diag.filtered_total !== undefined
            ? _diag.filtered_total > offset + contacts.length
            : contacts.length >= limit,
        _diag: _diag
    };
}

function _getContactInfoFromDB(wxid) {
    var sql = "SELECT username, nickname, conRemark, type, alias FROM rcontact"
        + " WHERE username='" + wxid.replace(/'/g, "''") + "'";
    var rows = _execSQL(sql);
    if (rows.length > 0) return { success: true, contact: rows[0] };
    return { success: false, error: '未找到联系人: ' + wxid };
}

// ============================================================
// § 7  内部实现 — 好友管理 (H18/H19)
// ============================================================

function _getFriendRequestsFromDB(limit) {
    var sql = "SELECT msgId, type, talker, content, createTime FROM message"
        + " WHERE type = 10002"
        + " ORDER BY createTime DESC LIMIT " + limit;
    var rows = _execSQL(sql);
    var requests = rows.map(function (r) {
        var info = _parseFriendRequestXml(r.content);
        return {
            msg_id: r.msgId,
            from_id: info.fromusername || r.talker,
            from_nickname: info.fromnickname || '',
            content: info.content || '',
            encrypt_username: info.encryptusername || '',
            ticket: info.ticket || '',
            source: info.sourceType || '',
            timestamp: r.createTime,
        };
    });
    return { success: true, requests: requests, count: requests.length };
}

function _parseFriendRequestXml(xml) {
    var info = {};
    if (!xml) return info;
    var fields = ['fromusername', 'fromnickname', 'content', 'encryptusername', 'ticket', 'sourceType'];
    for (var i = 0; i < fields.length; i++) {
        var re = new RegExp(fields[i] + '="([^"]*)"');
        var m = xml.match(re);
        if (m) info[fields[i]] = m[1];
    }
    return info;
}

function _addFriendInternal(userId, message, sourceType) {
    return _intentAction(
        'com.workphone.ADD_FRIEND',
        { user_id: userId, message: message, source_type: sourceType },
        'friend_request_sent',
        { user_id: userId, message: message, source_type: sourceType }
    );
}

function _acceptFriendInternal(encryptUserName, ticket) {
    return _intentAction(
        'com.workphone.ACCEPT_FRIEND',
        { encrypt_username: encryptUserName, ticket: ticket },
        'friend_accepted',
        { encrypt_username: encryptUserName }
    );
}

function _deleteFriendInternal(wxid) {
    return _intentAction(
        'com.workphone.DELETE_FRIEND',
        { wxid: wxid },
        'friend_deleted',
        { wxid: wxid }
    );
}

function _setRemarkInternal(wxid, remark) {
    return _intentAction('com.workphone.SET_REMARK', { wxid: wxid, remark: remark });
}

// ============================================================
// § 8  内部实现 — 群管理 (H22)
// ============================================================

function _getGroupsFromDB(limit, offset) {
    limit = parseInt(limit, 10) || 100;
    if (limit < 1) limit = 100;
    if (limit > 5000) limit = 5000;
    offset = parseInt(offset, 10) || 0;
    if (offset < 0) offset = 0;
    var sql = "SELECT username, nickname FROM rcontact"
        + " WHERE username LIKE '%@chatroom'"
        + " ORDER BY nickname LIMIT " + limit + " OFFSET " + offset;
    var rows = _execSQL(sql);
    var totalCount = null;
    try {
        var cntRows = _execSQL("SELECT count(*) AS n FROM rcontact WHERE username LIKE '%@chatroom'");
        totalCount = cntRows && cntRows[0] ? parseInt(cntRows[0].n, 10) : null;
    } catch (e) { /* ignore count failure */ }
    return {
        success: true,
        groups: rows.map(function (r) {
            return { group_id: r.username, name: r.nickname };
        }),
        count: rows.length,
        returned_count: rows.length,
        total_count: totalCount,
        requested_limit: limit,
        offset: offset,
        has_more: totalCount !== null ? totalCount > offset + rows.length : rows.length >= limit,
    };
}

function _getLabelsFromDB(limit, offset) {
    limit = parseInt(limit, 10) || 5000;
    if (limit < 1) limit = 5000;
    if (limit > 20000) limit = 20000;
    offset = parseInt(offset, 10) || 0;
    if (offset < 0) offset = 0;

    var tables = [
        { table: "contactlabel", id: "labelID", name: "labelName" },
        { table: "contactlabelinfo", id: "labelId", name: "labelName" }
    ];
    var rows = [];
    var totalCount = null;
    var sourceTable = "";
    for (var i = 0; i < tables.length; i++) {
        var t = tables[i];
        try {
            rows = _execSQL(
                "SELECT " + t.id + " AS labelID, " + t.name + " AS labelName FROM " + t.table
                + " ORDER BY CAST(" + t.id + " AS INTEGER), " + t.id
                + " LIMIT " + limit + " OFFSET " + offset
            );
            var cntRows = _execSQL("SELECT count(*) AS n FROM " + t.table);
            totalCount = cntRows && cntRows[0] ? parseInt(cntRows[0].n, 10) : null;
            sourceTable = t.table;
            break;
        } catch (e) {
            rows = [];
            totalCount = null;
        }
    }

    return {
        success: true,
        labels: rows.map(function (r) { return { id: r.labelID || r.labelId, name: r.labelName || "" }; }),
        count: rows.length,
        returned_count: rows.length,
        total_count: totalCount,
        requested_limit: limit,
        offset: offset,
        has_more: totalCount !== null ? totalCount > offset + rows.length : rows.length >= limit,
        source_table: sourceTable,
    };
}

function _getGroupMembersFromDB(groupId) {
    var sql = "SELECT memberlist, displayname FROM chatroom"
        + " WHERE chatroomname='" + groupId.replace(/'/g, "''") + "'";
    var rows = _execSQL(sql);
    if (rows.length === 0) return { success: false, error: '群不存在: ' + groupId, members: [] };

    var memberList = (rows[0].memberlist || '').split(';').filter(function (m) { return m; });
    var displayNames = (rows[0].displayname || '').split(';');

    var members = memberList.map(function (wxid, idx) {
        return {
            wxid: wxid,
            display_name: (displayNames[idx] || '').trim(),
        };
    });

    return { success: true, group_id: groupId, members: members, count: members.length };
}

function _getGroupInfoFromDB(groupId) {
    var sql = "SELECT chatroomname, memberlist, displayname, roomowner, chatroomnick FROM chatroom"
        + " WHERE chatroomname='" + groupId.replace(/'/g, "''") + "'";
    var rows = _execSQL(sql);
    if (rows.length === 0) return { success: false, error: '群不存在: ' + groupId };

    var r = rows[0];
    var memberList = (r.memberlist || '').split(';').filter(function (m) { return m; });
    return {
        success: true,
        group: {
            group_id: r.chatroomname,
            name: r.chatroomnick || '',
            owner: r.roomowner || '',
            member_count: memberList.length,
            members: memberList,
        },
    };
}

function _createGroupInternal(memberIds, topic) {
    return _intentAction(
        'com.workphone.CREATE_GROUP',
        { member_ids: memberIds.join(','), topic: topic || '' },
        'group_created',
        { member_ids: memberIds, topic: topic }
    );
}

function _inviteToGroupInternal(groupId, memberIds) {
    return _intentAction(
        'com.workphone.INVITE_GROUP',
        { group_id: groupId, member_ids: memberIds.join(',') },
        'group_invite',
        { group_id: groupId, member_ids: memberIds }
    );
}

function _removeFromGroupInternal(groupId, memberIds) {
    return _intentAction(
        'com.workphone.REMOVE_GROUP_MEMBER',
        { group_id: groupId, member_ids: memberIds.join(',') },
        'group_member_removed',
        { group_id: groupId, member_ids: memberIds }
    );
}

function _setGroupAnnouncementInternal(groupId, announcement) {
    return _intentAction(
        'com.workphone.SET_GROUP_ANNOUNCEMENT',
        { group_id: groupId, announcement: announcement }
    );
}

function _setGroupNameInternal(groupId, name) {
    return _intentAction('com.workphone.SET_GROUP_NAME', { group_id: groupId, name: name });
}

function _quitGroupInternal(groupId) {
    return _intentAction(
        'com.workphone.QUIT_GROUP',
        { group_id: groupId },
        'group_quit',
        { group_id: groupId }
    );
}

// ============================================================
// § 9  内部实现 — 朋友圈 (H20/H21)
// ============================================================

// 取当前栈顶 Activity（ActivityThread.mActivities 遍历 paused=false）
function _getTopActivity() {
    var top = null;
    var lastAny = null;       // 兜底：最后一个可取到的 activity（即便 paused）
    var preferred = null;     // 优先：分享/选聊天/搜索类 activity（按类名匹配）
    try {
        var ActivityThread = Java.use('android.app.ActivityThread');
        var at = ActivityThread.currentActivityThread();
        var mActivities = at.mActivities.value;  // ArrayMap<IBinder, ActivityClientRecord>
        var vals = mActivities.values().toArray();
        for (var i = 0; i < vals.length; i++) {
            var rec = Java.cast(vals[i], Java.use('android.app.ActivityThread$ActivityClientRecord'));
            try {
                var act = rec.activity.value;
                if (act) {
                    lastAny = act;
                    var cn = '';
                    try { cn = act.getClass().getName(); } catch (_) {}
                    // 选聊天/分享/搜索界面优先（MIUI 下分享瞬间主 activity 可能短暂 paused）
                    if (cn && (cn.indexOf('ShareImgUI') >= 0 || cn.indexOf('SelectConversationUI') >= 0 ||
                               cn.indexOf('ShareToTimeLineUI') >= 0 || cn.indexOf('Search') >= 0 ||
                               cn.indexOf('SnsUploadUI') >= 0)) {
                        preferred = act;
                    }
                }
                if (!rec.paused.value) { top = act; break; }
            } catch (_) {}
        }
    } catch (e) {
        log('warn', 'sns', '_getTopActivity 失败: ' + e);
    }
    // 优先级：非paused栈顶 > 分享/选聊天类 > 任意最后一个（best-effort，修「无栈顶 Activity」）
    return top || preferred || lastAny;
}

// 递归遍历 view 树，找文本匹配且可点击的 View 并 performClick
function _clickViewByText(root, texts) {
    if (!root) return false;
    try {
        var TextView = Java.use('android.widget.TextView');
        var ViewGroup = Java.use('android.view.ViewGroup');
        // 当前节点是 TextView 且文本命中
        try {
            var asTv = Java.cast(root, TextView);
            var t = asTv.getText();
            var s = t ? t.toString() : '';
            for (var k = 0; k < texts.length; k++) {
                if (s.indexOf(texts[k]) >= 0) {
                    var clickTarget = root;
                    // 向上找可点击祖先（按钮文字常包在可点击容器内）
                    var p = root;
                    for (var up = 0; up < 4 && p; up++) {
                        if (p.isClickable()) { clickTarget = p; break; }
                        p = p.getParent();
                    }
                    if (clickTarget && clickTarget.performClick) {
                        clickTarget.performClick();
                        log('info', 'sns', '点击发表按钮: "' + s + '"');
                        return true;
                    }
                }
            }
        } catch (_) {}
        // 递归子节点
        try {
            var vg = Java.cast(root, ViewGroup);
            var n = vg.getChildCount();
            for (var c = 0; c < n; c++) {
                if (_clickViewByText(vg.getChildAt(c), texts)) return true;
            }
        } catch (_) {}
    } catch (_) {}
    return false;
}

// 遍历找搜索框 EditText 并用 Frida setText 输入（不经 IME/INJECT_EVENTS，直接触发 TextWatcher 过滤）。
// 用于微信"选择一个聊天"(ShareImgUI)：目标联系人常不在最近聊天可见列表，需搜索过滤后才渲染。
function _findEditTextAndSetText(root, text) {
    if (!root) return false;
    try {
        var EditText = Java.use('android.widget.EditText');
        var ViewGroup = Java.use('android.view.ViewGroup');
        try {
            var et = Java.cast(root, EditText);
            try { et.requestFocus(); } catch (_) {}
            et.setText.overload('java.lang.CharSequence').call(et, text);
            log('info', 'send', '搜索框输入: ' + text);
            return true;
        } catch (_) {}
        try {
            var vg = Java.cast(root, ViewGroup);
            var n = vg.getChildCount();
            for (var c = 0; c < n; c++) {
                if (_findEditTextAndSetText(vg.getChildAt(c), text)) return true;
            }
        } catch (_) {}
    } catch (_) {}
    return false;
}

// 朋友圈发布界面打开后，自动点「发表」（best-effort，失败由 need_confirm_send 兜底）
function _autoClickPublish(delayMs) {
    try {
        var Handler = Java.use('android.os.Handler');
        var Looper = Java.use('android.os.Looper');
        var handler = Handler.$new(Looper.getMainLooper());
        var Runnable = Java.use('java.lang.Runnable');
        var R = Java.registerClass({
            name: 'com.workphone.PublishClicker' + Date.now(),
            implements: [Runnable],
            methods: {
                run: function () {
                    try {
                        var act = _getTopActivity();
                        if (!act) { log('warn', 'sns', '自动发表：无栈顶 Activity'); return; }
                        var decor = act.getWindow().getDecorView();
                        var ok = _clickViewByText(decor, ['发表', '完成', 'Post', 'Send']);
                        emitEvent('moments_publish_click', { clicked: ok });
                        log('info', 'sns', '自动发表点击结果: ' + ok);
                    } catch (e) {
                        log('warn', 'sns', '自动发表执行失败: ' + e);
                    }
                }
            }
        });
        handler.postDelayed(R.$new(), delayMs || 2000);
        return true;
    } catch (e) {
        log('warn', 'sns', '_autoClickPublish 调度失败: ' + e);
        return false;
    }
}

// 分享后 u2 自动选联系人 + 发送（best-effort）：两段延迟点击。
// 第一段点联系人文本（昵称/备注/"文件传输助手"），第二段点"发送"确认。
// wxid → 微信"选择一个聊天"列表显示名候选解析。
// 真机实测：to_id=filehelper 在列表里显示为"文件传输助手"，按 wxid 文本点不中 → 必须解析显示名。
// 顺序：DB 备注(conRemark) > 特殊账号映射 > DB 昵称(nickname) > 原 wxid 兜底。
function _resolveContactDisplayNames(toId) {
    var cands = [];
    var special = {
        'filehelper': ['文件传输助手', 'File Transfer'],
        'fmessage': ['服务通知'],
        'medianote': ['语音记事本'],
        'weixin': ['微信团队', 'WeChat Team'],
        'floatbottle': ['漂流瓶'],
        'newsapp': ['腾讯新闻'],
    };
    // DB 查备注/昵称（备注优先，最贴近列表显示）
    try {
        Java.performNow(function () {
            var esc = String(toId).replace(/'/g, "''");
            var rows = _execSQL("SELECT nickname, conRemark FROM rcontact WHERE username='" + esc + "' LIMIT 1");
            if (rows && rows.length > 0) {
                if (rows[0].conRemark) cands.push(rows[0].conRemark);
                if (rows[0].nickname && rows[0].nickname !== rows[0].conRemark) cands.push(rows[0].nickname);
            }
        });
    } catch (e) { log('warn', 'send', '解析显示名 DB 查询失败: ' + e); }
    if (special[toId]) cands = cands.concat(special[toId]);
    cands.push(toId); // 微信号/wxid 兜底
    var seen = {}, out = [];
    for (var i = 0; i < cands.length; i++) { var v = cands[i]; if (v && !seen[v]) { seen[v] = 1; out.push(v); } }
    return out;
}

function _autoSelectAndSend(toId, delayMs) {
    try {
        var nameCandidates = _resolveContactDisplayNames(toId);
        log('info', 'send', '选联系人候选显示名: ' + JSON.stringify(nameCandidates));
        var Handler = Java.use('android.os.Handler');
        var Looper = Java.use('android.os.Looper');
        var handler = Handler.$new(Looper.getMainLooper());
        var Runnable = Java.use('java.lang.Runnable');
        var d = delayMs || 2500;
        // 第-1段：点"搜索"伪框进真搜索界面（ShareImgUI 顶部搜索是 TextView 伪框，点击才出 EditText）
        var RC = Java.registerClass({
            name: 'com.workphone.ShareSearchOpener' + Date.now(),
            implements: [Runnable],
            methods: {
                run: function () {
                    try {
                        var act = _getTopActivity();
                        if (!act) return;
                        var decor = act.getWindow().getDecorView();
                        var ok = _clickViewByText(decor, ['搜索', 'Search']);
                        log('info', 'send', '点搜索伪框 = ' + ok);
                    } catch (e) { log('warn', 'send', '点搜索框失败: ' + e); }
                }
            }
        });
        handler.postDelayed(RC.$new(), d);
        // 第〇段：搜索框输入显示名过滤（最近聊天列表懒加载，目标常不在可见项）
        var RS = Java.registerClass({
            name: 'com.workphone.ShareSearcher' + Date.now(),
            implements: [Runnable],
            methods: {
                run: function () {
                    try {
                        var act = _getTopActivity();
                        if (!act) return;
                        var decor = act.getWindow().getDecorView();
                        var kw = nameCandidates[0];
                        var typed = _findEditTextAndSetText(decor, kw);
                        emitEvent('share_search_input', { keyword: kw, typed: typed });
                        log('info', 'send', '分享搜索输入 "' + kw + '" = ' + typed);
                    } catch (e) { log('warn', 'send', '搜索输入失败: ' + e); }
                }
            }
        });
        handler.postDelayed(RS.$new(), d + 1200);
        var R1 = Java.registerClass({
            name: 'com.workphone.ContactSelector' + Date.now(),
            implements: [Runnable],
            methods: {
                run: function () {
                    try {
                        var act = _getTopActivity();
                        if (!act) { log('warn', 'send', '选联系人：无栈顶 Activity'); return; }
                        var decor = act.getWindow().getDecorView();
                        var ok = _clickViewByText(decor, nameCandidates);
                        emitEvent('share_contact_click', { to_id: toId, candidates: nameCandidates, clicked: ok });
                        log('info', 'send', '分享选联系人点击 ' + JSON.stringify(nameCandidates) + ' = ' + ok);
                    } catch (e) { log('warn', 'send', '选联系人失败: ' + e); }
                }
            }
        });
        handler.postDelayed(R1.$new(), d + 3100);
        var R2 = Java.registerClass({
            name: 'com.workphone.ShareSendClicker' + Date.now(),
            implements: [Runnable],
            methods: {
                run: function () {
                    try {
                        var act = _getTopActivity();
                        if (!act) return;
                        var decor = act.getWindow().getDecorView();
                        var ok = _clickViewByText(decor, ['发送', '發送', 'Send', '发送(1)', '發送(1)']);
                        emitEvent('share_send_click', { clicked: ok });
                        log('info', 'send', '分享发送点击结果: ' + ok);
                    } catch (e) { log('warn', 'send', '发送点击失败: ' + e); }
                }
            }
        });
        handler.postDelayed(R2.$new(), d + 4700);
        return true;
    } catch (e) {
        log('warn', 'send', '_autoSelectAndSend 调度失败: ' + e);
        return false;
    }
}

// 真发布：启动微信朋友圈发布 Activity（SnsUploadUI）并预填内容
// 替代旧的 com.workphone.POST_MOMENTS 广播（无接收端=占位）。
// 纯文字朋友圈：Ksnsupload_type=9（社区已知），文本 extra 多 key 兜底。
// 媒体分享真实现（替代 no_receiver 占位）：ACTION_SEND 系统分享拉起微信"选择联系人"界面。
// 微信进程内 startActivity 给自己：关 StrictMode 允许 file:// uri，避免 FileUriExposedException。
// 阶段一：真拉起微信分享界面（可观测、非占位）；阶段二 u2 自动选 to_id + 发送（_autoSelectAndSend）。
function _shareMediaToWechat(filePath, mimeType, toId, kind) {
    try {
        // 关 VmPolicy 允许 file:// uri 跨组件传递
        try {
            var StrictMode = Java.use('android.os.StrictMode');
            var VmBuilder = Java.use('android.os.StrictMode$VmPolicy$Builder');
            StrictMode.setVmPolicy(VmBuilder.$new().build());
        } catch (_) {}

        var Intent = Java.use('android.content.Intent');
        var Uri = Java.use('android.net.Uri');
        var File = Java.use('java.io.File');
        var Parcelable = Java.use('android.os.Parcelable');
        var ctx = getCtx();

        var f = File.$new(filePath);
        var exists = false;
        try { exists = f.exists(); } catch (_) {}
        if (!exists) {
            return { success: false, error: '文件不存在: ' + filePath, kind: kind };
        }

        var uri = Uri.fromFile(f);
        function buildIntent(cls) {
            var intent = Intent.$new();
            intent.setAction('android.intent.action.SEND');
            intent.setType(mimeType);
            intent.putExtra('android.intent.extra.STREAM', Java.cast(uri, Parcelable));
            intent.setPackage('com.tencent.mm');
            // 直达微信"选择一个聊天"页（ShareImgUI），跳过 MIUI 系统分享面板那一层
            if (cls) intent.setClassName('com.tencent.mm', cls);
            intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK
            intent.addFlags(0x00000001); // FLAG_GRANT_READ_URI_PERMISSION
            return intent;
        }

        var method = 'action_send';
        var started = false;
        var lastErr = '';
        try {
            ctx.startActivity(buildIntent('com.tencent.mm.ui.tools.ShareImgUI'));
            started = true;
            method = 'action_send_direct';
        } catch (e1) {
            lastErr = String(e1);
        }
        if (!started) {
            ctx.startActivity(buildIntent(null));
            started = true;
            method = 'action_send_systempanel';
        }

        log('info', 'send', 'ACTION_SEND 分享' + kind + ' 拉起微信(' + method + '): ' + filePath + (lastErr ? ' fallback=' + lastErr : ''));
        emitEvent('media_share_ui_opened', { kind: kind, file: filePath, to_id: toId, mime: mimeType, method: method });

        // 阶段二：u2 自动选联系人 + 发送（best-effort；失败由 need_manual_select 兜底）
        var scheduled = _autoSelectAndSend(toId, 2500);

        return {
            success: true,
            method: method,
            kind: kind,
            file: filePath,
            to_id: toId,
            auto_select_scheduled: scheduled,
            need_manual_select: !scheduled,
            note: scheduled
                ? '已拉起微信分享界面，2.5s 后自动选联系人发送'
                : '已拉起微信分享界面，待选联系人发送（u2 自动化未就绪）',
        };
    } catch (e) {
        return { success: false, error: '分享' + kind + '失败: ' + String(e), kind: kind };
    }
}

// 文本/链接分享真实现（替代 SEND_LINK no_receiver 占位）：ACTION_SEND text/plain 拉起微信"选择联系人"。
// 与 _shareMediaToWechat 同款 intent，仅把 STREAM 换成 EXTRA_TEXT；链接以"标题+换行+URL"纯文本投递。
// 待真机验证：设备复连后第一批 A 类验证目标。
function _shareTextToWechat(text, toId) {
    try {
        var Intent = Java.use('android.content.Intent');
        var ctx = getCtx();
        function buildIntent(cls) {
            var intent = Intent.$new();
            intent.setAction('android.intent.action.SEND');
            intent.setType('text/plain');
            intent.putExtra('android.intent.extra.TEXT', text);
            intent.setPackage('com.tencent.mm');
            // 直达微信"选择一个聊天"页（ShareImgUI），跳过 MIUI 系统分享面板那一层
            if (cls) intent.setClassName('com.tencent.mm', cls);
            intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK
            return intent;
        }

        var method = 'action_send_text';
        var started = false;
        var lastErr = '';
        // 真机实测发现：仅 setPackage 时 MIUI 仍弹系统分享面板（多一层"发送给朋友/收藏"）。
        // 优先 setClassName 直达 ShareImgUI（微信"选择联系人转发"页），失败再回退系统面板。
        try {
            ctx.startActivity(buildIntent('com.tencent.mm.ui.tools.ShareImgUI'));
            started = true;
            method = 'action_send_text_direct';
        } catch (e1) {
            lastErr = String(e1);
        }
        if (!started) {
            ctx.startActivity(buildIntent(null));
            started = true;
            method = 'action_send_text_systempanel';
        }

        log('info', 'send', 'ACTION_SEND 文本/链接 拉起微信(' + method + '): len=' + text.length + (lastErr ? ' fallback=' + lastErr : ''));
        emitEvent('text_share_ui_opened', { to_id: toId, text_len: text.length, method: method });

        var scheduled = _autoSelectAndSend(toId, 2500);
        return {
            success: true,
            method: method,
            to_id: toId,
            auto_select_scheduled: scheduled,
            need_manual_select: !scheduled,
            note: scheduled
                ? '已拉起微信分享界面，2.5s 后自动选联系人发送'
                : '已拉起微信分享界面，待选联系人发送（u2 自动化未就绪）',
        };
    } catch (e) {
        return { success: false, error: '文本/链接分享失败: ' + String(e) };
    }
}

function _postMomentsInternal(content, imageUrls, autoSend) {
    imageUrls = imageUrls || [];
    if (autoSend === undefined) autoSend = true;
    var candidates = getCompat('sns_upload');  // [{cls:'...SnsUploadUI'}, ...]
    var lastErr = '';
    for (var i = 0; i < candidates.length; i++) {
        var clsName = candidates[i].cls;
        try {
            var Intent = Java.use('android.content.Intent');
            var ComponentName = Java.use('android.content.ComponentName');
            var ctx = getCtx();
            var SnsCls = Java.use(clsName);  // 校验类存在
            var intent = Intent.$new();
            intent.setComponent(ComponentName.$new('com.tencent.mm', clsName));
            intent.addFlags(0x10000000);  // FLAG_ACTIVITY_NEW_TASK
            // 纯文字：type=9；图文：type=1（含 imglist）
            var isText = imageUrls.length === 0;
            intent.putExtra('Ksnsupload_type', isText ? 9 : 1);
            // 文本 extra 多 key 兜底（不同版本 key 名不同）
            intent.putExtra('Ksnsupload_text', content);
            intent.putExtra('sns_text', content);
            intent.putExtra('Ksns_upload_text', content);
            if (!isText) {
                intent.putExtra('Ksnsupload_imglist', imageUrls.join(','));
            }
            ctx.startActivity(intent);
            log('info', 'sns', '启动 SnsUploadUI(' + clsName + ') content_len=' + content.length + ' images=' + imageUrls.length);
            emitEvent('moments_post_ui_opened', { content: content, image_count: imageUrls.length, cls: clsName });
            // 打开发布界面+预填后，best-effort 自动点「发表」（失败由 need_confirm_send 兜底）
            var scheduled = autoSend ? _autoClickPublish(2200) : false;
            return {
                success: true,
                method: 'sns_upload_ui',
                cls: clsName,
                auto_click_scheduled: scheduled,
                need_confirm_send: !scheduled,
                note: scheduled ? '已打开朋友圈发布界面并预填，2.2s 后自动点发表' : '已打开发布界面并预填，待点发表',
                sns_id: generateId('sns'),
            };
        } catch (e) {
            lastErr = String(e);
        }
    }
    return { success: false, error: '发朋友圈失败(所有 SnsUploadUI 候选): ' + lastErr };
}

function _getMomentsFromDB(wxid, limit) {
    var snsPath = _getSnsDbPath();
    if (!snsPath) {
        return { success: false, error: 'SnsMicroMsg.db 未找到', moments: [] };
    }

    var where = wxid
        ? " WHERE userName='" + wxid.replace(/'/g, "''") + "'"
        : '';
    // 8.0.69 兼容：探测 SnsInfo 实际列，只 SELECT 存在的列（避免 mediaCount/likeCount/commentCount 缺列致整查询返 0）
    var _wantSns = ["snsId", "userName", "createTime", "content", "mediaCount", "likeCount", "commentCount", "type", "sourceType"];
    var _availSns = {};
    var _snsDiag = {};
    try {
        var _sti = _execSnsSQL("PRAGMA table_info(SnsInfo)");
        (_sti || []).forEach(function (c) { if (c && c.name) _availSns[c.name] = true; });
        _snsDiag.avail_cols = Object.keys(_availSns);
    } catch (eps) { _snsDiag.pragma_err = String(eps); }
    var _useSns = Object.keys(_availSns).length > 0
        ? _wantSns.filter(function (c) { return _availSns[c]; })
        : _wantSns;
    if (_useSns.indexOf("snsId") < 0) _useSns.unshift("snsId");
    _snsDiag.use_cols = _useSns;
    var sql = "SELECT " + _useSns.join(", ") + " FROM SnsInfo"
        + where + " ORDER BY createTime DESC LIMIT " + limit;

    var rows = _execSnsSQL(sql);
    _snsDiag.rows = (rows || []).length;
    if (!rows || rows.length === 0) {
        try { var sc = _execSnsSQL("SELECT count(*) AS n FROM SnsInfo"); _snsDiag.snsinfo_total = sc && sc[0] ? sc[0].n : null; } catch (e0) { _snsDiag.total_err = String(e0); }
        var hasSnsRows = safeInt(_snsDiag.snsinfo_total, 0) > 0;
        var emptyMoments = _emptyReadFailure('朋友圈', _lastDbDiag, hasSnsRows);
        if (emptyMoments) {
            emptyMoments.moments = [];
            emptyMoments._diag = _snsDiag;
            return emptyMoments;
        }
    }
    return {
        success: true,
        moments: (rows || []).map(function (r) {
            var parsed = _parseSnsContent(r.content);
            return {
                sns_id: r.snsId,
                user_id: r.userName,
                text: parsed.text,
                media_count: safeInt(r.mediaCount, 0),
                like_count: safeInt(r.likeCount, 0),
                comment_count: safeInt(r.commentCount, 0),
                timestamp: r.createTime,
            };
        }),
        count: (rows || []).length,
        _diag: _snsDiag,
    };
}

function _parseSnsContent(raw) {
    if (!raw) return { text: '' };
    try {
        var idx = raw.indexOf('<contentDesc>');
        var endIdx = raw.indexOf('</contentDesc>');
        if (idx >= 0 && endIdx > idx) {
            return { text: raw.substring(idx + 13, endIdx) };
        }
    } catch (_) {}
    return { text: safeStr(raw).substring(0, 200) };
}

function _likeMomentsInternal(snsId) {
    return _intentAction(
        'com.workphone.LIKE_MOMENTS',
        { sns_id: snsId },
        'moments_liked',
        { sns_id: snsId }
    );
}

function _commentMomentsInternal(snsId, comment) {
    return _intentAction(
        'com.workphone.COMMENT_MOMENTS',
        { sns_id: snsId, comment: comment },
        'moments_commented',
        { sns_id: snsId, comment: comment }
    );
}

// ============================================================
// § 10  通用 Intent 调度器（所有非 DB 操作统一走此）
// ============================================================

function _intentAction(intentAction, extras, eventType, eventPayload) {
    try {
        return Java.performNow(function () {
            var Intent = Java.use('android.content.Intent');
            var ctx = getCtx();
            var intent = Intent.$new(intentAction);
            var keys = Object.keys(extras || {});
            for (var i = 0; i < keys.length; i++) {
                intent.putExtra(keys[i], String(extras[keys[i]]));
            }
            // 真机铁律 #3/#14：先校验是否存在接收端，无接收端不得返回 success:true（禁止假成功）
            var receivers = 0;
            try {
                var pm = ctx.getPackageManager();
                var list = pm.queryBroadcastReceivers(intent, 0);
                receivers = list ? list.size() : 0;
            } catch (qe) {
                receivers = -1; // 查询失败：未知，按未验证处理
            }
            if (receivers <= 0) {
                var receiverError = receivers === 0 ? 'no_receiver_registered' : 'receiver_query_failed';
                log('warn', 'intent', intentAction + ' 接收端不可验证 → 不广播（避免假成功，需真实 RPC 或装接收端 App）');
                return {
                    success: false,
                    method: 'intent_broadcast',
                    verified: false,
                    error: receiverError,
                    receivers: receivers,
                    action: intentAction,
                    hint: '该动作经 Android 广播分发，当前设备未注册接收端；需实现真实 Frida RPC 或部署接收端 App',
                };
            }
            ctx.sendBroadcast(intent);
            if (eventType) {
                emitEvent(eventType, eventPayload || {});
            }
            log('info', 'intent', intentAction + ' → ' + keys.join(',') + ' (receivers=' + receivers + ')');
            return {
                success: true,
                method: 'intent_broadcast',
                verified: receivers > 0,
                receivers: receivers,
                action: intentAction,
            };
        });
    } catch (e) {
        return { success: false, error: String(e), action: intentAction };
    }
}

// ============================================================
// § 11  Hook 初始化
// ============================================================

Java.perform(function () {
    log('info', 'init', 'wechat_hook_v3.1 正在初始化（110 actions / 24 modules / 版本自适应）...');

    // 获取微信版本 + 初始化版本兼容
    try {
        var ctx = getCtx();
        var pm = ctx.getPackageManager();
        var info = pm.getPackageInfo(jStr('com.tencent.mm'), 0);
        _wechatVersion = safeStr(info.versionName.value);
        log('info', 'init', '微信版本: ' + _wechatVersion);
        _resolveVersionCompat(_wechatVersion);
    } catch (_) {
        _versionCompat = _DEFAULT_COMPAT;
        log('warn', 'init', '无法获取微信版本，使用 default 全候选');
    }

    // 查找数据库路径
    _dbPath = _findDbPath(CONFIG.DB_NAME);
    _snsDbPath = _findDbPath(CONFIG.SNS_DB_NAME);
    log('info', 'init', 'EnMicroMsg.db: ' + (_dbPath || '未找到'));
    log('info', 'init', 'SnsMicroMsg.db: ' + (_snsDbPath || '未找到'));

    // ---- Hook 1: SQLite insert 拦截新消息 (H15) ----
    if (CONFIG.HOOK_SQLITE) {
        try {
            var SQLiteDatabase = Java.use('android.database.sqlite.SQLiteDatabase');
            SQLiteDatabase.insert.overload(
                'java.lang.String', 'java.lang.String', 'android.content.ContentValues'
            ).implementation = function (table, nullColumnHack, values) {
                var result = this.insert(table, nullColumnHack, values);
                try {
                    var tbl = safeStr(table);
                    if (tbl === 'message') {
                        var talker = safeStr(values.getAsString(jStr('talker')));
                        var content = safeStr(values.getAsString(jStr('content')));
                        var msgType = safeStr(values.getAsString(jStr('type')));
                        var msgSvrId = safeStr(values.getAsString(jStr('msgSvrId')));
                        var isSend = safeStr(values.getAsString(jStr('isSend')));

                        if (talker && content) {
                            var msg = {
                                from_id: talker, content: content, msg_type: msgType,
                                msg_svr_id: msgSvrId, is_send: isSend === '1',
                                timestamp: new Date().toISOString(),
                            };
                            _messageBuffer.push(msg);
                            if (_messageBuffer.length > CONFIG.MESSAGE_BUFFER_MAX) {
                                _messageBuffer = _messageBuffer.slice(-Math.floor(CONFIG.MESSAGE_BUFFER_MAX * 0.6));
                            }
                            emitEvent('message_received', msg);
                        }

                        // H18: 好友请求 type=10002
                        if (msgType === '10002') {
                            var reqInfo = _parseFriendRequestXml(content);
                            var req = {
                                msg_id: msgSvrId, from_id: reqInfo.fromusername || talker,
                                from_nickname: reqInfo.fromnickname || '',
                                encrypt_username: reqInfo.encryptusername || '',
                                ticket: reqInfo.ticket || '', content: reqInfo.content || '',
                                timestamp: new Date().toISOString(),
                            };
                            _friendRequestBuffer.push(req);
                            if (_friendRequestBuffer.length > CONFIG.FRIEND_REQUEST_BUFFER_MAX) {
                                _friendRequestBuffer = _friendRequestBuffer.slice(-60);
                            }
                            emitEvent('friend_request_received', req);
                            log('info', 'hook', '检测到好友请求: ' + req.from_id);
                        }
                    }
                } catch (_) {}
                return result;
            };
            _hooked.sqlite_insert = true;
            log('info', 'hook', 'SQLite insert 消息拦截已启用');
        } catch (e) {
            log('warn', 'hook', 'SQLite insert Hook 失败: ' + e);
        }
    }

    // ---- Hook 2: SQLite update 拦截消息撤回 ----
    if (CONFIG.HOOK_SQLITE) {
        try {
            var SQLiteDB2 = Java.use('android.database.sqlite.SQLiteDatabase');
            SQLiteDB2.update.overload(
                'java.lang.String', 'android.content.ContentValues', 'java.lang.String', '[Ljava.lang.String;'
            ).implementation = function (table, values, whereClause, whereArgs) {
                try {
                    if (safeStr(table) === 'message' && whereClause) {
                        var newType = values.getAsInteger(jStr('type'));
                        if (newType && newType.intValue() === 10000) {
                            var content = safeStr(values.getAsString(jStr('content')));
                            if (content.indexOf('撤回') !== -1 || content.indexOf('recalled') !== -1) {
                                emitEvent('message_recalled', { where: safeStr(whereClause), content: content });
                                log('info', 'hook', '检测到消息撤回');
                            }
                        }
                    }
                } catch (_) {}
                return this.update(table, values, whereClause, whereArgs);
            };
            _hooked.sqlite_update = true;
            log('info', 'hook', 'SQLite update 撤回拦截已启用');
        } catch (e) {
            log('warn', 'hook', 'SQLite update Hook 失败: ' + e);
        }
    }

    // ---- Hook 3: 网络请求拦截 ----
    if (CONFIG.HOOK_NETWORK) {
        try {
            var URL = Java.use('java.net.URL');
            URL.openConnection.overload().implementation = function () {
                var conn = this.openConnection();
                var url = safeStr(this.toString());
                if (url.indexOf('weixin.qq.com') !== -1 || url.indexOf('wechat') !== -1 || url.indexOf('wx.qq.com') !== -1) {
                    emitEvent('network_request', { url: url, method: 'GET' });
                }
                return conn;
            };
            _hooked.network = true;
            log('info', 'hook', '网络请求拦截已启用');
        } catch (e) {
            log('warn', 'hook', '网络 Hook 失败: ' + e);
        }
    }

    // ---- Hook 4: 好友请求 XML 解析拦截 (H18 加强) ----
    if (CONFIG.HOOK_FRIEND_REQUEST) {
        try {
            // 拦截 AddMsg 处理器（微信内部处理新消息的核心类）
            var candidates = [
                'com.tencent.mm.plugin.messenger.foundation.a.b',
                'com.tencent.mm.plugin.messenger.foundation.a.c',
            ];
            for (var ci = 0; ci < candidates.length; ci++) {
                try {
                    var Cls = Java.use(candidates[ci]);
                    if (Cls) {
                        _hooked.friend_request = true;
                        log('info', 'hook', '好友请求拦截已启用 (via ' + candidates[ci] + ')');
                        break;
                    }
                } catch (_) { continue; }
            }
            if (!_hooked.friend_request) {
                log('info', 'hook', '好友请求拦截通过 SQLite 拦截实现（type=10002）');
                _hooked.friend_request = true;
            }
        } catch (e) {
            log('warn', 'hook', '好友请求 Hook 失败: ' + e);
        }
    }

    // ---- Hook 5: 朋友圈上传拦截 (H20 可选) ----
    if (CONFIG.HOOK_MOMENTS) {
        try {
            var snsCandidates = [
                'com.tencent.mm.plugin.sns.ui.SnsUploadUI',
                'com.tencent.mm.plugin.sns.ui.SnsTimeLineUI',
            ];
            for (var si = 0; si < snsCandidates.length; si++) {
                try {
                    var SnsCls = Java.use(snsCandidates[si]);
                    if (SnsCls) {
                        _hooked.sns_upload = true;
                        log('info', 'hook', '朋友圈监控已启用 (via ' + snsCandidates[si] + ')');
                        break;
                    }
                } catch (_) { continue; }
            }
        } catch (e) {
            log('warn', 'hook', '朋友圈 Hook 失败: ' + e);
        }
    }

    log('info', 'init', 'wechat_hook_v3.1 初始化完成 — 110 actions / 全功能就绪', _hooked);
    emitEvent('hook_initialized', {
        version: '3.0.0',
        wechat_version: _wechatVersion,
        hooked: _hooked,
        db_path: _dbPath || '',
        sns_db_path: _snsDbPath || '',
    });
});
