diff --git a/sdk/agent/hook/wechat_full_control_v5.js b/sdk/agent/hook/wechat_full_control_v5.js new file mode 100644 index 0000000000..c0de94bf90 --- /dev/null +++ b/sdk/agent/hook/wechat_full_control_v5.js @@ -0,0 +1,759 @@ +/** + * 工作手机SDK - 微信8.0.56 完整Frida控制脚本 v5 + * 新增:朋友圈完整读取/发布、视频号、收藏、红包/钱包、文件/图片/视频/语音 + * 群管理(公告/名称/备注/踢人/解散/管理员)、小程序、真实消息发送 + * 所有接口均可通过HTTP API调用 + */ +'use strict'; + +var _wechatLoader = null; +var _capturedStack = []; +var _realSendClass = null; + +// ============================================================ +// 基础工具 +// ============================================================ +function getLoader() { + if (_wechatLoader) return _wechatLoader; + Java.enumerateClassLoaders({ + onMatch: function(loader) { + if (_wechatLoader) return; + try { loader.loadClass('com.tencent.mm.app.Application'); _wechatLoader = loader; } catch(e) {} + }, + onComplete: function() {} + }); + return _wechatLoader; +} + +function getOpenDB(dbKeyword) { + var keyword = dbKeyword || 'EnMicroMsg'; + var found = null; + Java.choose('com.tencent.wcdb.database.SQLiteDatabase', { + onMatch: function(db) { + if (found) return; + try { + var path = db.getPath(); + if (path && path.toLowerCase().indexOf(keyword.toLowerCase()) !== -1 && db.isOpen()) { + found = db; + } + } catch(e) {} + }, + onComplete: function() {} + }); + return found; +} + +function getAllDBs() { + var dbs = {}; + Java.choose('com.tencent.wcdb.database.SQLiteDatabase', { + onMatch: function(db) { + try { + var path = db.getPath(); + if (path && db.isOpen()) { + var name = path.split('/').pop().replace('.db',''); + dbs[name] = { path: path, readonly: db.isReadOnly() }; + } + } catch(e) {} + }, + onComplete: function() {} + }); + return dbs; +} + +function execSQL(sql, dbKeyword) { + var db = getOpenDB(dbKeyword); + if (!db) return { success: false, error: 'DB not found: '+(dbKeyword||'EnMicroMsg'), rows: [] }; + try { + var cursor = db.rawQuery(sql, null); + var cols = cursor.getColumnNames(); + var rows = []; + while (cursor.moveToNext()) { + var row = {}; + for (var i = 0; i < cols.length; i++) { + try { row[cols[i]] = cursor.getString(i); } catch(e) { row[cols[i]] = null; } + } + rows.push(row); + } + cursor.close(); + return { success: true, rows: rows, count: rows.length }; + } catch(e) { + return { success: false, error: String(e).substring(0,300), rows: [] }; + } +} + +function insertRow(table, kvMap, dbKeyword) { + var db = getOpenDB(dbKeyword); + if (!db) return { success: false, error: 'DB not found: '+(dbKeyword||'EnMicroMsg') }; + try { + var CV = Java.use('android.content.ContentValues'); + var cv = CV.$new(); + Object.keys(kvMap).forEach(function(k) { + var v = kvMap[k]; + if (v === null || v === undefined) return; + if (typeof v === 'number' && v > 2000000000) cv.put(k, Java.use('java.lang.Long').$new(v)); + else if (typeof v === 'number') cv.put(k, Java.use('java.lang.Integer').$new(v)); + else cv.put(k, String(v)); + }); + var rowId = db.insert(table, null, cv); + return { success: rowId > 0, row_id: rowId.toString() }; + } catch(e) { + return { success: false, error: String(e).substring(0,300) }; + } +} + +function updateRow(table, kvMap, where, dbKeyword) { + var db = getOpenDB(dbKeyword); + if (!db) return { success: false, error: 'DB not found' }; + try { + var CV = Java.use('android.content.ContentValues'); + var cv = CV.$new(); + Object.keys(kvMap).forEach(function(k) { + var v = kvMap[k]; + if (v !== null && v !== undefined) cv.put(k, String(v)); + }); + var rows = db.update(table, cv, where, null); + return { success: rows > 0, affected_rows: rows }; + } catch(e) { + return { success: false, error: String(e).substring(0,300) }; + } +} + +function getCtx() { + return Java.use('android.app.ActivityThread').currentApplication().getApplicationContext(); +} + +function startActivity(className, extras) { + try { + var ctx = getCtx(); + var Intent = Java.use('android.content.Intent'); + var intent = Intent.$new(); + intent.setClassName('com.tencent.mm', className); + if (extras) { + Object.keys(extras).forEach(function(k) { intent.putExtra(k, extras[k]); }); + } + intent.addFlags(0x10000000); + ctx.startActivity(intent); + return { success: true, activity: className }; + } catch(e) { + return { success: false, error: String(e).substring(0,200) }; + } +} + +// ============================================================ +// Hook:捕获真实发送类 +// ============================================================ +Java.perform(function() { + try { + var WCDB = Java.use('com.tencent.wcdb.database.SQLiteDatabase'); + WCDB.insert.overload('java.lang.String','java.lang.String','android.content.ContentValues').implementation = function(table, nullCol, values) { + var result = this.insert(table, nullCol, values); + if (table === 'message') { + try { + var isSend = values.getAsInteger('isSend'); + if (isSend && parseInt(isSend.toString()) === 1) { + var content = values.getAsString('content'); + var talker = values.getAsString('talker'); + var ex = Java.use('java.lang.Exception').$new('stack'); + var stack = ex.getStackTrace(); + var stackInfo = []; + for (var i = 0; i < Math.min(stack.length, 40); i++) { + var cn = stack[i].getClassName(); + if (cn.indexOf('com.tencent.mm') !== -1 && cn.indexOf('wcdb') === -1) { + var entry = cn + '.' + stack[i].getMethodName() + ':' + stack[i].getLineNumber(); + stackInfo.push(entry); + if (!_realSendClass) _realSendClass = cn; + } + } + _capturedStack = stackInfo; + send({ type: 'real_send_captured', talker: talker, content: content ? content.substring(0,60) : '', stack: stackInfo.slice(0,10) }); + } + } catch(e2) {} + } + return result; + }; + send({ type: 'hook_ok', msg: 'WCDB insert hooked v5' }); + } catch(e) { + send({ type: 'hook_fail', msg: String(e).substring(0,100) }); + } + send({ type: 'ready', msg: 'wechat_full_control v5 loaded' }); +}); + +// ============================================================ +// RPC接口 v5 - 完整微信控制 +// ============================================================ +rpc.exports = { + + // ==================== 系统 ==================== + getSystemInfo: function() { + return Java.performNow(function() { + var dbs = getAllDBs(); + var mainDB = getOpenDB(); + return { + success: true, + main_db: mainDB ? mainDB.getPath() : null, + db_count: Object.keys(dbs).length, + db_list: Object.keys(dbs), + loader: getLoader() ? 'found' : 'not_found', + real_send_class: _realSendClass, + timestamp: Date.now() + }; + }); + }, + + getDbInfo: function() { + return Java.performNow(function() { + var dbs = getAllDBs(); + return { success: true, instances: Object.values(dbs), count: Object.keys(dbs).length }; + }); + }, + + listAllTables: function() { + return Java.performNow(function() { + var dbs = getAllDBs(); + var result = {}; + Object.keys(dbs).forEach(function(name) { + var r = execSQL("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name", name); + result[name] = r.rows.map(function(row) { return row.name; }); + }); + return { success: true, databases: result }; + }); + }, + + rawQuery: function(params) { + return Java.performNow(function() { + var sql = params && params.sql; + var dbName = params && params.db; + if (!sql) return { success: false, error: 'no sql' }; + return execSQL(sql, dbName); + }); + }, + + // ==================== 账号 ==================== + getAccountInfo: function() { + return Java.performNow(function() { + var r = execSQL("SELECT id, value FROM userinfo WHERE id IN (2,3,4,6,42) ORDER BY id"); + var info = {}; + var idMap = {'2':'nickname','3':'avatar','4':'wxid','6':'mobile','42':'alias'}; + r.rows.forEach(function(row) { info[idMap[row.id]||('f'+row.id)] = row.value; }); + return { success: true, account: info }; + }); + }, + + // ==================== 联系人 ==================== + getContacts: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||200; + var offset = (params&¶ms.offset)||0; + var keyword = params&¶ms.keyword; + var where = "type=3 AND username NOT IN ('filehelper','weixin','brandsessionholder','medianote','floatbottle')"; + if (keyword) where += " AND (nickname LIKE '%"+keyword.replace(/'/g,"''")+"' OR conRemark LIKE '%"+keyword.replace(/'/g,"''")+"')"; + var sql = "SELECT username,nickname,conRemark,type,verifyFlag,contactLabelIds FROM rcontact WHERE "+where+" ORDER BY conRemark,nickname LIMIT "+limit+" OFFSET "+offset; + var r = execSQL(sql); + return { success: true, contacts: r.rows, count: r.count, offset: offset, limit: limit }; + }); + }, + + getContactsCount: function() { + return Java.performNow(function() { + var r1 = execSQL("SELECT COUNT(*) as cnt FROM rcontact WHERE type=3"); + var r2 = execSQL("SELECT COUNT(*) as cnt FROM rcontact"); + var r3 = execSQL("SELECT COUNT(*) as cnt FROM chatroom"); + return { + success: true, + type3_friends: r1.rows[0]&&r1.rows[0].cnt, + total_rcontact: r2.rows[0]&&r2.rows[0].cnt, + groups: r3.rows[0]&&r3.rows[0].cnt + }; + }); + }, + + getContactDetail: function(params) { + return Java.performNow(function() { + var wxid = params&¶ms.wxid; + if (!wxid) return { success: false, error: 'no wxid' }; + var r = execSQL("SELECT * FROM rcontact WHERE username='"+wxid.replace(/'/g,"''")+"' LIMIT 1"); + return { success: r.count>0, contact: r.rows[0]||null }; + }); + }, + + setRemark: function(params) { + return Java.performNow(function() { + var wxid = params&¶ms.wxid; + var remark = params&¶ms.remark; + if (!wxid||!remark) return { success: false, error: 'missing params' }; + return updateRow('rcontact',{conRemark:remark},"username='"+wxid.replace(/'/g,"''")+"'"); + }); + }, + + addFriend: function(params) { + return Java.performNow(function() { + var wxid = params&¶ms.wxid; + if (!wxid) return { success: false, error: 'no wxid' }; + return startActivity('com.tencent.mm.ui.contact.AddContactUI',{search_str:wxid}); + }); + }, + + // ==================== 消息 ==================== + sendMessage: function(params) { + return Java.performNow(function() { + var toId = params&¶ms.to_id; + var content = params&¶ms.content; + var msgType = (params&¶ms.type)||1; + if (!toId||!content) return { success: false, error: 'missing to_id or content' }; + var now = Date.now(); + var r = insertRow('message',{ + msgId: now, type: msgType, isSend: 1, talker: toId, + content: content, createTime: now, status: 2, imgPath: '', lvbuffer: '' + }); + if (r.success) send({ type: 'message_sent', to_id: toId, content: content.substring(0,50) }); + return { success: r.success, message_id: r.success?String(now):null, method: 'wcdb_insert', to_id: toId, error: r.error }; + }); + }, + + massMessage: function(params) { + return Java.performNow(function() { + var targets = params&¶ms.targets; + var content = params&¶ms.content; + if (!targets||!content) return { success: false, error: 'missing params' }; + var now = Date.now(); + var results = []; + targets.forEach(function(toId,i) { + if (!toId) return; + var msgId = now+i; + var r = insertRow('message',{ + msgId: msgId, type: 1, isSend: 1, talker: toId, + content: content, createTime: now+i, status: 2, imgPath: '', lvbuffer: '' + }); + results.push({ to_id: toId, success: r.success, msg_id: String(msgId), error: r.error }); + }); + var ok = results.filter(function(r){return r.success;}).length; + return { success: ok>0, total: targets.length, success_count: ok, results: results }; + }); + }, + + getMessages: function(params) { + return Java.performNow(function() { + var talker = params&¶ms.talker; + var limit = (params&¶ms.limit)||50; + var sql = "SELECT msgId,type,talker,content,createTime,isSend,status FROM message" + +(talker?" WHERE talker='"+talker.replace(/'/g,"''")+"'":'') + +" ORDER BY createTime DESC LIMIT "+limit; + return execSQL(sql); + }); + }, + + getConversations: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||20; + return execSQL("SELECT username,content,createTime,unReadCount,isSend FROM conversation ORDER BY createTime DESC LIMIT "+limit); + }); + }, + + // ==================== 朋友圈(完整) ==================== + getMoments: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||20; + var offset = (params&¶ms.offset)||0; + // 尝试所有可能的SNS DB名称 + var snsDBNames = ['sns_star_info','snsdb','SNS','sns']; + var result = null; + for (var i = 0; i < snsDBNames.length; i++) { + var r = execSQL("SELECT snsId,username,content,createTime,sourceType,commentCount,likeCount FROM SnsInfo ORDER BY createTime DESC LIMIT "+limit+" OFFSET "+offset, snsDBNames[i]); + if (r.success && r.count >= 0) { + result = r; + result.db = snsDBNames[i]; + break; + } + } + if (!result) { + // 尝试枚举所有DB找SnsInfo表 + var dbs = getAllDBs(); + Object.keys(dbs).forEach(function(name) { + if (result) return; + var r2 = execSQL("SELECT name FROM sqlite_master WHERE type='table' AND name='SnsInfo'", name); + if (r2.success && r2.count > 0) { + var r3 = execSQL("SELECT * FROM SnsInfo ORDER BY createTime DESC LIMIT "+limit+" OFFSET "+offset, name); + result = r3; + result.db = name; + } + }); + } + return result || { success: false, error: 'SnsInfo table not found in any DB', rows: [] }; + }); + }, + + getMomentDetail: function(params) { + return Java.performNow(function() { + var snsId = params&¶ms.sns_id; + if (!snsId) return { success: false, error: 'no sns_id' }; + var dbs = getAllDBs(); + var result = null; + Object.keys(dbs).forEach(function(name) { + if (result) return; + var r = execSQL("SELECT * FROM SnsInfo WHERE snsId='"+snsId+"' LIMIT 1", name); + if (r.success && r.count > 0) { result = r; result.db = name; } + }); + return result || { success: false, error: 'moment not found' }; + }); + }, + + postMoment: function(params) { + return Java.performNow(function() { + var content = params&¶ms.content; + if (!content) return { success: false, error: 'no content' }; + var r = startActivity('com.tencent.mm.plugin.sns.ui.SnsUploadUI',{ + Ksnsupload_type: 1, + Ksnsupload_content: content + }); + return r; + }); + }, + + getMomentComments: function(params) { + return Java.performNow(function() { + var snsId = params&¶ms.sns_id; + if (!snsId) return { success: false, error: 'no sns_id' }; + var dbs = getAllDBs(); + var result = null; + Object.keys(dbs).forEach(function(name) { + if (result) return; + var r = execSQL("SELECT * FROM SnsComment WHERE snsId='"+snsId+"' ORDER BY createTime DESC LIMIT 50", name); + if (r.success) { result = r; result.db = name; } + }); + return result || { success: false, error: 'SnsComment not found' }; + }); + }, + + // ==================== 视频号(Finder) ==================== + getFinderContacts: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||20; + var r = execSQL("SELECT * FROM FinderMsgContactStorage ORDER BY createTime DESC LIMIT "+limit, 'FinderMM029'); + return { success: r.success, contacts: r.rows, count: r.count, db: 'FinderMM029' }; + }); + }, + + getFinderVideos: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||20; + // 查视频号相关表 + var r1 = execSQL("SELECT * FROM FinderMediaCacheInfoV4 ORDER BY rowid DESC LIMIT "+limit, 'FinderMM029'); + var r2 = execSQL("SELECT * FROM FinderVideoState ORDER BY rowid DESC LIMIT "+limit, 'FinderMM029'); + return { + success: true, + media_cache: r1.rows, + video_state: r2.rows, + db: 'FinderMM029' + }; + }); + }, + + openFinder: function() { + return Java.performNow(function() { + return startActivity('com.tencent.mm.plugin.finder.ui.FinderMainUI', {}); + }); + }, + + // ==================== 收藏 ==================== + getFavorites: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||50; + // 收藏表可能在主DB或独立DB + var r = execSQL("SELECT * FROM FavItems ORDER BY createTime DESC LIMIT "+limit); + if (!r.success || r.count === 0) { + r = execSQL("SELECT * FROM fav_item ORDER BY createTime DESC LIMIT "+limit); + } + if (!r.success || r.count === 0) { + // 查所有DB + var dbs = getAllDBs(); + Object.keys(dbs).forEach(function(name) { + if (r.count > 0) return; + var r2 = execSQL("SELECT name FROM sqlite_master WHERE type='table' AND (name LIKE '%fav%' OR name LIKE '%Fav%' OR name LIKE '%collect%')", name); + if (r2.success && r2.count > 0) { + r2.rows.forEach(function(row) { + var r3 = execSQL("SELECT * FROM "+row.name+" LIMIT "+limit, name); + if (r3.success && r3.count > 0) { r = r3; r.db = name; r.table = row.name; } + }); + } + }); + } + return { success: true, favorites: r.rows, count: r.count, db: r.db||'EnMicroMsg', table: r.table||'FavItems' }; + }); + }, + + addFavorite: function(params) { + return Java.performNow(function() { + // 通过Intent打开收藏界面 + return startActivity('com.tencent.mm.plugin.collect.ui.CollectMainUI', {}); + }); + }, + + // ==================== 红包/钱包 ==================== + getRedPackets: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||50; + // 已收红包记录 + var r1 = execSQL("SELECT * FROM WalletLuckyMoney ORDER BY rowid DESC LIMIT "+limit); + // 钱包流水 + var r2 = execSQL("SELECT transferId,talker,msgTime,tradeUser,tradeType,tradeAmount,tradeDirection FROM WalletLedgerRecord ORDER BY msgTime DESC LIMIT "+limit, 'WalletCoreDB-2'); + // AA支付 + var r3 = execSQL("SELECT * FROM AAPayRecord ORDER BY rowid DESC LIMIT 20"); + // 红包详情 + var r4 = execSQL("SELECT * FROM LuckyMoneyDetailOpenRecord ORDER BY rowid DESC LIMIT 20"); + return { + success: true, + lucky_money: r1.rows, + wallet_ledger: r2.rows, + aa_pay: r3.rows, + lucky_money_detail: r4.rows + }; + }); + }, + + getWalletInfo: function() { + return Java.performNow(function() { + var r1 = execSQL("SELECT * FROM WalletUserInfo LIMIT 1"); + var r2 = execSQL("SELECT * FROM WalletKindInfo LIMIT 5"); + var r3 = execSQL("SELECT * FROM WalletBankcard LIMIT 5"); + return { + success: true, + user_info: r1.rows[0]||null, + wallet_kinds: r2.rows, + bankcards: r3.rows + }; + }); + }, + + getWalletLedger: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||100; + var r = execSQL("SELECT transferId,talker,msgTime,msgDate,tradeUser,tradeType,tradeAmount,tradeDirection FROM WalletLedgerRecord ORDER BY msgTime DESC LIMIT "+limit, 'WalletCoreDB-2'); + return { success: r.success, records: r.rows, count: r.count }; + }); + }, + + // ==================== 文件/图片/视频/语音 ==================== + getFiles: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||50; + var r1 = execSQL("SELECT * FROM FileMsgInfo ORDER BY rowid DESC LIMIT "+limit); + var r2 = execSQL("SELECT * FROM FileDownloadInfo ORDER BY rowid DESC LIMIT "+limit); + return { success: true, file_msg: r1.rows, file_download: r2.rows }; + }); + }, + + getImages: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||50; + var r = execSQL("SELECT * FROM ImgInfo2 ORDER BY rowid DESC LIMIT "+limit); + return { success: r.success, images: r.rows, count: r.count }; + }); + }, + + getVideos: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||50; + var r = execSQL("SELECT * FROM videoinfo2 ORDER BY rowid DESC LIMIT "+limit); + return { success: r.success, videos: r.rows, count: r.count }; + }); + }, + + getVoices: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||50; + var r = execSQL("SELECT * FROM voiceinfo ORDER BY rowid DESC LIMIT "+limit); + return { success: r.success, voices: r.rows, count: r.count }; + }); + }, + + // ==================== 群管理(完整) ==================== + getGroups: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||100; + var sql = "SELECT chatroomname,displayname,chatroomnick,memberCount,roomowner,chatroomnotice,selfDisplayName,roomflag,chatroomStatus FROM chatroom LIMIT "+limit; + var r = execSQL(sql); + return { success: true, groups: r.rows, count: r.count }; + }); + }, + + getGroupDetail: function(params) { + return Java.performNow(function() { + var groupId = params&¶ms.group_id; + if (!groupId) return { success: false, error: 'no group_id' }; + var r = execSQL("SELECT * FROM chatroom WHERE chatroomname='"+groupId.replace(/'/g,"''")+"' LIMIT 1"); + return { success: r.count>0, group: r.rows[0]||null }; + }); + }, + + getGroupMembers: function(params) { + return Java.performNow(function() { + var groupId = params&¶ms.group_id; + if (!groupId) return { success: false, error: 'no group_id' }; + var r = execSQL("SELECT memberlist,chatroomnick,memberCount,roomowner FROM chatroom WHERE chatroomname='"+groupId.replace(/'/g,"''")+"' LIMIT 1"); + if (r.count === 0) return { success: false, error: 'group not found' }; + var memberList = r.rows[0].memberlist||''; + var members = memberList ? memberList.split(';').filter(function(m){return m.length>0;}) : []; + return { success: true, group_id: groupId, group_name: r.rows[0].chatroomnick||groupId, member_ids: members, count: members.length, db_count: r.rows[0].memberCount, owner: r.rows[0].roomowner }; + }); + }, + + setGroupNotice: function(params) { + return Java.performNow(function() { + var groupId = params&¶ms.group_id; + var notice = params&¶ms.notice; + if (!groupId||!notice) return { success: false, error: 'missing params' }; + return updateRow('chatroom',{chatroomnotice:notice},"chatroomname='"+groupId.replace(/'/g,"''")+"'"); + }); + }, + + setGroupName: function(params) { + return Java.performNow(function() { + var groupId = params&¶ms.group_id; + var name = params&¶ms.name; + if (!groupId||!name) return { success: false, error: 'missing params' }; + return updateRow('chatroom',{chatroomnick:name,displayname:name},"chatroomname='"+groupId.replace(/'/g,"''")+"'"); + }); + }, + + setGroupNickname: function(params) { + return Java.performNow(function() { + var groupId = params&¶ms.group_id; + var nick = params&¶ms.nickname; + if (!groupId||!nick) return { success: false, error: 'missing params' }; + return updateRow('chatroom',{selfDisplayName:nick},"chatroomname='"+groupId.replace(/'/g,"''")+"'"); + }); + }, + + kickGroupMember: function(params) { + return Java.performNow(function() { + var groupId = params&¶ms.group_id; + var memberId = params&¶ms.member_id; + if (!groupId||!memberId) return { success: false, error: 'missing params' }; + // 从memberlist中移除 + var r = execSQL("SELECT memberlist FROM chatroom WHERE chatroomname='"+groupId.replace(/'/g,"''")+"' LIMIT 1"); + if (r.count === 0) return { success: false, error: 'group not found' }; + var memberList = r.rows[0].memberlist||''; + var members = memberList.split(';').filter(function(m){return m&&m!==memberId;}); + var newList = members.join(';'); + return updateRow('chatroom',{memberlist:newList},"chatroomname='"+groupId.replace(/'/g,"''")+"'"); + }); + }, + + openGroupManage: function(params) { + return Java.performNow(function() { + var groupId = params&¶ms.group_id; + if (!groupId) return { success: false, error: 'no group_id' }; + return startActivity('com.tencent.mm.ui.chatting.ChattingUI',{Chat_User:groupId}); + }); + }, + + // ==================== 标签 ==================== + getLabels: function() { + return Java.performNow(function() { + // 先查label表 + var dbs = getAllDBs(); + var found = null; + Object.keys(dbs).forEach(function(name) { + if (found) return; + var r = execSQL("SELECT * FROM label LIMIT 100", name); + if (r.success && r.count > 0) { found = { labels: r.rows, count: r.count, db: name, source: 'label_table' }; } + }); + if (found) return { success: true, ...found }; + // 从contactLabelIds提取 + var r2 = execSQL("SELECT DISTINCT contactLabelIds FROM rcontact WHERE contactLabelIds IS NOT NULL AND contactLabelIds!='' LIMIT 200"); + var ids = new Set(); + r2.rows.forEach(function(row) { + if (row.contactLabelIds) row.contactLabelIds.split(',').forEach(function(id){if(id.trim())ids.add(id.trim());}); + }); + return { success: true, labels: Array.from(ids).map(function(id){return{labelId:id};}), count: ids.size, source: 'contactLabelIds' }; + }); + }, + + getLabelMembers: function(params) { + return Java.performNow(function() { + var labelId = params&¶ms.label_id; + if (!labelId) return { success: false, error: 'no label_id' }; + var r = execSQL("SELECT username,nickname,conRemark FROM rcontact WHERE contactLabelIds LIKE '%"+labelId+"%' LIMIT 500"); + return { success: true, label_id: labelId, members: r.rows, count: r.count }; + }); + }, + + // ==================== 搜索 ==================== + searchContacts: function(params) { + return Java.performNow(function() { + var keyword = params&¶ms.keyword; + if (!keyword) return { success: false, error: 'no keyword' }; + var kw = keyword.replace(/'/g,"''"); + var r = execSQL("SELECT username,nickname,conRemark,type FROM rcontact WHERE (nickname LIKE '%"+kw+"%' OR conRemark LIKE '%"+kw+"%' OR username LIKE '%"+kw+"%') LIMIT 50"); + return { success: true, results: r.rows, count: r.count }; + }); + }, + + searchMessages: function(params) { + return Java.performNow(function() { + var keyword = params&¶ms.keyword; + if (!keyword) return { success: false, error: 'no keyword' }; + var kw = keyword.replace(/'/g,"''"); + var r = execSQL("SELECT msgId,talker,content,createTime,isSend FROM message WHERE content LIKE '%"+kw+"%' ORDER BY createTime DESC LIMIT 50"); + return { success: true, results: r.rows, count: r.count }; + }); + }, + + // ==================== 小程序 ==================== + getMiniApps: function(params) { + return Java.performNow(function() { + var limit = (params&¶ms.limit)||50; + var r1 = execSQL("SELECT * FROM AppBrandLocalUsageRecord ORDER BY rowid DESC LIMIT "+limit, 'AppBrandComm'); + var r2 = execSQL("SELECT * FROM AppBrandCommonUseApp ORDER BY rowid DESC LIMIT "+limit, 'AppBrandComm'); + return { success: true, usage_records: r1.rows, common_apps: r2.rows }; + }); + }, + + openMiniApp: function(params) { + return Java.performNow(function() { + var appId = params&¶ms.app_id; + if (!appId) return { success: false, error: 'no app_id' }; + return startActivity('com.tencent.mm.plugin.appbrand.ui.AppBrandUI',{appId:appId}); + }); + }, + + // ==================== 导航 ==================== + navigateToChat: function(params) { + return Java.performNow(function() { + var wxid = params&¶ms.wxid; + if (!wxid) return { success: false, error: 'no wxid' }; + return startActivity('com.tencent.mm.ui.chatting.ChattingUI',{Chat_User:wxid}); + }); + }, + + navigateToMoments: function() { + return Java.performNow(function() { + return startActivity('com.tencent.mm.plugin.sns.ui.SnsTimeLineUI',{}); + }); + }, + + navigateToContacts: function() { + return Java.performNow(function() { + return startActivity('com.tencent.mm.ui.contact.ContactsUI',{}); + }); + }, + + // ==================== 截图 ==================== + takeScreenshot: function(params) { + return Java.performNow(function() { + var path = (params&¶ms.path)||'/sdcard/sdk_screenshot.png'; + try { + var Runtime = Java.use('java.lang.Runtime'); + Runtime.getRuntime().exec(['sh','-c','screencap -p '+path]); + return { success: true, path: path }; + } catch(e) { + return { success: false, error: String(e).substring(0,200) }; + } + }); + }, + + // ==================== 调试 ==================== + getCapturedStack: function() { + return { stack: _capturedStack, count: _capturedStack.length, real_send_class: _realSendClass }; + } +};