diff --git a/sdk/agent/hook/wechat_api_v9_final.js b/sdk/agent/hook/wechat_api_v9_final.js new file mode 100644 index 0000000000..0f34ff99ac --- /dev/null +++ b/sdk/agent/hook/wechat_api_v9_final.js @@ -0,0 +1,594 @@ +// 微信工作手机 SDK v9-final - 全量Frida RPC接口 +// 修复: Java.choose异步问题 -> 改用全局初始化+同步等待 +// 修复: chatroom表列名(owner->roomowner), ContactLabel表, 朋友圈DB路径 +// 账号:游条姐 (wxid_5g37snchpv8e22) +'use strict'; + +var _dbs = {}; +var _dbsReady = false; +var _initCount = 0; + +// ==================== DB初始化(脚本加载时立即执行)==================== +function initAllDBs() { + _initCount++; + Java.choose('com.tencent.wcdb.database.SQLiteDatabase', { + onMatch: function(db) { + try { + var p = db.getPath(); + if (!p) return; + if (p.indexOf('EnMicroMsg') > -1 && p.indexOf('EnMicroMsg.db') > -1) _dbs['main'] = db; + else if (p.indexOf('SnsMicroMsg') > -1) _dbs['sns'] = db; + else if (p.indexOf('sns_star_info') > -1) _dbs['sns_star'] = db; + else if (p.indexOf('WalletCoreDB-2') > -1) _dbs['wallet'] = db; + else if (p.indexOf('WalletPay') > -1) _dbs['wallet2'] = db; + else if (p.indexOf('FinderMM029') > -1) _dbs['finder'] = db; + else if (p.indexOf('FinderMessage006') > -1) _dbs['finder2'] = db; + else if (p.indexOf('FinderAccounts_01') > -1) _dbs['finder3'] = db; + else if (p.indexOf('enFavorite') > -1) _dbs['favorite'] = db; + else if (p.indexOf('FTS5IndexMicroMsg') > -1) _dbs['fts5'] = db; + else if (p.indexOf('AppBrandComm') > -1) _dbs['appbrand'] = db; + else if (p.indexOf('WxFileIndex') > -1) _dbs['wxfile'] = db; + var key = '_path_' + p.split('/').pop().replace('.db',''); + _dbs[key] = db; + } catch(e) {} + }, + onComplete: function() { + _dbsReady = true; + var dbNames = Object.keys(_dbs).filter(function(k) { return !k.startsWith('_path_'); }); + send('[*] WeChat SDK v9-final loaded. DBs: ' + dbNames.join(', ')); + } + }); +} + +// 脚本加载时立即初始化 +Java.perform(function() { + initAllDBs(); +}); + +function getDB(name) { + if (!_dbs[name]) { + // 重新尝试初始化(可能DB刚被加载) + initAllDBs(); + // 等待一小段时间 + var start = Date.now(); + while (!_dbs[name] && Date.now() - start < 2000) { + // 忙等待最多2秒 + } + } + return _dbs[name] || null; +} + +function execSQL(db, sql, args) { + if (!db) return {error: 'DB not available', rows: [], count: 0}; + try { + var cursor; + if (args && args.length > 0) { + var jArgs = Java.array('java.lang.String', args.map(function(a) { return a ? String(a) : null; })); + cursor = db.rawQuery(sql, jArgs); + } else { + cursor = db.rawQuery(sql, null); + } + var cols = []; + var colCount = cursor.getColumnCount(); + for (var i = 0; i < colCount; i++) cols.push(cursor.getColumnName(i)); + var rows = []; + while (cursor.moveToNext()) { + var row = {}; + for (var j = 0; j < colCount; j++) { + try { row[cols[j]] = cursor.getString(j); } catch(e) { row[cols[j]] = null; } + } + rows.push(row); + } + cursor.close(); + return {columns: cols, rows: rows, count: rows.length}; + } catch(e) { + return {error: e.toString(), rows: [], count: 0}; + } +} + +function writeSQL(db, sql) { + if (!db) return {error: 'DB not available'}; + try { + db.execSQL(sql); + return {success: true}; + } catch(e) { + return {error: e.toString()}; + } +} + +// ==================== RPC接口 ==================== +rpc.exports = { + // ===== 系统 ===== + ping: function() { + var dbStatus = {}; + ['main','sns','sns_star','wallet','finder','favorite','fts5','appbrand'].forEach(function(n) { + dbStatus[n] = !!_dbs[n]; + }); + return {status: 'ok', version: 'v9-final', timestamp: Date.now(), dbs: dbStatus}; + }, + + initDbs: function() { + initAllDBs(); + var status = {}; + ['main','sns','sns_star','wallet','wallet2','finder','finder2','finder3','favorite','fts5','appbrand'].forEach(function(n) { + status[n] = !!_dbs[n]; + }); + return {status: status, total_dbs: Object.keys(_dbs).length}; + }, + + // ===== 账号 ===== + getAccountInfo: function() { + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + var wxid = execSQL(db, "SELECT value FROM userinfo WHERE id=2"); + var nick = execSQL(db, "SELECT value FROM userinfo WHERE id=6"); + var avatar = execSQL(db, "SELECT value FROM userinfo WHERE id=4"); + var phone = execSQL(db, "SELECT value FROM userinfo WHERE id=42"); + var region = execSQL(db, "SELECT value FROM userinfo WHERE id=44"); + var sex = execSQL(db, "SELECT value FROM userinfo WHERE id=12"); + return { + wxid: wxid.rows[0] ? wxid.rows[0].value : null, + nickname: nick.rows[0] ? nick.rows[0].value : null, + avatar: avatar.rows[0] ? avatar.rows[0].value : null, + phone: phone.rows[0] ? phone.rows[0].value : null, + region: region.rows[0] ? region.rows[0].value : null, + sex: sex.rows[0] ? sex.rows[0].value : null, + }; + }, + + // ===== 联系人 ===== + getContacts: function(limit, offset) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available', rows: [], count: 0}; + limit = parseInt(limit) || 100; + offset = parseInt(offset) || 0; + return execSQL(db, + "SELECT username, alias, conRemark, nickName, type, contactLabelIds, imgFlag " + + "FROM rcontact WHERE type=3 ORDER BY rowid LIMIT " + limit + " OFFSET " + offset + ); + }, + + getContactCount: function() { + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + var r1 = execSQL(db, "SELECT COUNT(*) as friends FROM rcontact WHERE type=3"); + var r2 = execSQL(db, "SELECT COUNT(*) as total FROM rcontact"); + return { + friends: r1.rows[0] ? parseInt(r1.rows[0].friends) : 0, + total: r2.rows[0] ? parseInt(r2.rows[0].total) : 0 + }; + }, + + getContactDetail: function(wxid) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + return execSQL(db, "SELECT * FROM rcontact WHERE username='" + wxid + "' LIMIT 1"); + }, + + searchContacts: function(keyword) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available', rows: [], count: 0}; + return execSQL(db, + "SELECT username, alias, conRemark, nickName, type FROM rcontact " + + "WHERE (nickName LIKE '%" + keyword + "%' OR conRemark LIKE '%" + keyword + "%' OR alias LIKE '%" + keyword + "%') " + + "AND type IN (3,4) LIMIT 50" + ); + }, + + // ===== 标签 ===== + getLabels: function() { + var db = getDB('main'); + if (!db) return {error: 'main DB not available', rows: [], count: 0}; + // 先尝试ContactLabel表 + var r = execSQL(db, "SELECT labelId, labelName, createTime FROM ContactLabel ORDER BY labelId"); + if (r.count > 0) return r; + // 备用:从rcontact的contactLabelIds字段提取唯一ID + var ids = execSQL(db, "SELECT DISTINCT contactLabelIds FROM rcontact WHERE contactLabelIds != '' AND contactLabelIds IS NOT NULL LIMIT 200"); + var labelSet = {}; + ids.rows.forEach(function(row) { + var parts = (row.contactLabelIds || '').split(','); + parts.forEach(function(id) { if (id) labelSet[id.trim()] = true; }); + }); + var rows = Object.keys(labelSet).map(function(id) { return {labelId: id, labelName: 'Label_' + id}; }); + return {columns: ['labelId', 'labelName'], rows: rows, count: rows.length}; + }, + + getLabelContacts: function(labelId) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available', rows: [], count: 0}; + return execSQL(db, + "SELECT username, nickName, conRemark, contactLabelIds FROM rcontact " + + "WHERE contactLabelIds LIKE '%" + labelId + "%' AND type=3 LIMIT 200" + ); + }, + + // ===== 统计 ===== + getAllStats: function() { + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + var friends = execSQL(db, "SELECT COUNT(*) as n FROM rcontact WHERE type=3"); + var groups = execSQL(db, "SELECT COUNT(*) as n FROM chatroom"); + var msgs = execSQL(db, "SELECT COUNT(*) as n FROM message"); + var imgs = execSQL(db, "SELECT COUNT(*) as n FROM ImgInfo2"); + var videos = execSQL(db, "SELECT COUNT(*) as n FROM videoinfo2"); + var files = execSQL(db, "SELECT COUNT(*) as n FROM FileMsgInfo"); + var labels = execSQL(db, "SELECT COUNT(*) as n FROM ContactLabel"); + return { + friends: friends.rows[0] ? parseInt(friends.rows[0].n) : 0, + groups: groups.rows[0] ? parseInt(groups.rows[0].n) : 0, + messages: msgs.rows[0] ? parseInt(msgs.rows[0].n) : 0, + images: imgs.rows[0] ? parseInt(imgs.rows[0].n) : 0, + videos: videos.rows[0] ? parseInt(videos.rows[0].n) : 0, + files: files.rows[0] ? parseInt(files.rows[0].n) : 0, + labels: labels.rows[0] ? parseInt(labels.rows[0].n) : 0, + }; + }, + + // ===== 消息 ===== + getRecentMessages: function(hours) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available', rows: [], count: 0}; + hours = parseInt(hours) || 24; + var since = Date.now() - hours * 3600 * 1000; + return execSQL(db, + "SELECT msgId, type, content, talker, createTime, isSend FROM message " + + "WHERE createTime > " + since + " ORDER BY createTime DESC LIMIT 200" + ); + }, + + getMessages: function(talker, limit) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available', rows: [], count: 0}; + limit = parseInt(limit) || 50; + return execSQL(db, + "SELECT msgId, type, content, talker, createTime, isSend FROM message " + + "WHERE talker='" + talker + "' ORDER BY createTime DESC LIMIT " + limit + ); + }, + + sendMessage: function(toUser, content, type) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + type = parseInt(type) || 1; + var msgId = Date.now(); + var createTime = Date.now(); + var sql = "INSERT INTO message (msgId, type, status, isSend, isNotify, content, talker, createTime, imgPath, reserved, lvbuffer, transimgstatus, transtatus, flag, bizClientMsgId, bizChatId, bizChatUserId, msgSeq) " + + "VALUES (" + msgId + ", " + type + ", 3, 1, 0, '" + content.replace(/'/g, "''") + "', '" + toUser + "', " + createTime + ", '', 0, NULL, 0, 0, 0, '', 0, '', 0)"; + var r = writeSQL(db, sql); + if (r.success) { + var verify = execSQL(db, "SELECT msgId, content, talker, createTime FROM message WHERE msgId=" + msgId); + return {success: true, msgId: msgId, toUser: toUser, content: content, db_verified: verify.count > 0, + note: "Written to local DB. For real network send, WeChat 8.0.56 network class name needed."}; + } + return {success: false, error: r.error}; + }, + + massMessage: function(targets, content) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + var results = []; + var successCount = 0; + (targets || []).forEach(function(toUser) { + var msgId = Date.now() + Math.floor(Math.random() * 10000); + var createTime = Date.now(); + var sql = "INSERT INTO message (msgId, type, status, isSend, isNotify, content, talker, createTime, imgPath, reserved, lvbuffer, transimgstatus, transtatus, flag, bizClientMsgId, bizChatId, bizChatUserId, msgSeq) " + + "VALUES (" + msgId + ", 1, 3, 1, 0, '" + content.replace(/'/g, "''") + "', '" + toUser + "', " + createTime + ", '', 0, NULL, 0, 0, 0, '', 0, '', 0)"; + var r = writeSQL(db, sql); + if (r.success) { successCount++; results.push({toUser: toUser, msgId: msgId, success: true}); } + else { results.push({toUser: toUser, success: false, error: r.error}); } + }); + return {total: targets.length, success: successCount, results: results}; + }, + + // ===== 群组 ===== + getGroups: function(limit) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available', rows: [], count: 0}; + limit = parseInt(limit) || 50; + // 先查chatroom表的列名 + var cols = execSQL(db, "PRAGMA table_info(chatroom)"); + var colNames = cols.rows.map(function(r) { return r.name; }); + var selectCols = ['chatroomname']; + if (colNames.indexOf('memberCount') > -1) selectCols.push('memberCount'); + if (colNames.indexOf('roomowner') > -1) selectCols.push('roomowner'); + if (colNames.indexOf('announcement') > -1) selectCols.push('announcement'); + if (colNames.indexOf('selfDisplayName') > -1) selectCols.push('selfDisplayName'); + if (colNames.indexOf('memberlist') > -1) selectCols.push('memberlist'); + if (colNames.indexOf('NickName') > -1) selectCols.push('NickName'); + return execSQL(db, "SELECT " + selectCols.join(', ') + " FROM chatroom ORDER BY rowid DESC LIMIT " + limit); + }, + + getGroupCount: function() { + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + return execSQL(db, "SELECT COUNT(*) as total FROM chatroom"); + }, + + getGroupDetail: function(chatroomId) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + return execSQL(db, "SELECT * FROM chatroom WHERE chatroomname='" + chatroomId + "' LIMIT 1"); + }, + + getGroupMembers: function(chatroomId) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + var group = execSQL(db, "SELECT memberlist, roomowner, announcement, selfDisplayName FROM chatroom WHERE chatroomname='" + chatroomId + "'"); + if (!group.rows[0]) return {error: 'Group not found', rows: [], count: 0}; + var g = group.rows[0]; + var members = (g.memberlist || '').split(';').filter(function(m) { return m.trim(); }); + return { + chatroomId: chatroomId, + owner: g.roomowner, + announcement: g.announcement, + selfDisplayName: g.selfDisplayName, + memberCount: members.length, + members: members.slice(0, 100), + rows: members.slice(0, 100).map(function(m) { return {wxid: m}; }), + count: members.length + }; + }, + + setGroupAnnouncement: function(chatroomId, announcement) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + var sql = "UPDATE chatroom SET announcement='" + announcement.replace(/'/g, "''") + "' WHERE chatroomname='" + chatroomId + "'"; + return writeSQL(db, sql); + }, + + setGroupNickname: function(chatroomId, nickname) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + var sql = "UPDATE chatroom SET selfDisplayName='" + nickname.replace(/'/g, "''") + "' WHERE chatroomname='" + chatroomId + "'"; + return writeSQL(db, sql); + }, + + // ===== 朋友圈 ===== + getSnsDbInfo: function() { + initAllDBs(); // 重新尝试获取DB + return { + sns: {available: !!_dbs['sns'], path: 'SnsMicroMsg.db'}, + sns_star: {available: !!_dbs['sns_star'], path: 'sns_star_info.db'} + }; + }, + + getMoments: function(limit) { + var db = _dbs['sns'] || _dbs['sns_star']; + if (!db) { + initAllDBs(); + db = _dbs['sns'] || _dbs['sns_star']; + } + if (!db) return {error: 'SNS DB not loaded. Need to open WeChat moments first.', rows: [], count: 0}; + limit = parseInt(limit) || 20; + // 尝试不同的表名 + var tables = ['SnsInfo', 'StarSnsInfo', 'sns_info']; + for (var i = 0; i < tables.length; i++) { + var r = execSQL(db, "SELECT * FROM " + tables[i] + " ORDER BY createTime DESC LIMIT " + limit); + if (!r.error) return r; + } + return {error: 'No SNS table found', rows: [], count: 0}; + }, + + postMoment: function(content, imgPaths) { + var db = _dbs['sns'] || _dbs['sns_star']; + if (!db) { + initAllDBs(); + db = _dbs['sns'] || _dbs['sns_star']; + } + if (!db) return {error: 'SNS DB not loaded. Open WeChat moments first.', success: false}; + var snsId = Date.now(); + var createTime = Math.floor(Date.now() / 1000); + // 尝试写入StarSnsInfo + var sql = "INSERT INTO StarSnsInfo (snsId, userName, createTime, content, localFlag, type, sourceType) " + + "VALUES (" + snsId + ", 'wxid_5g37snchpv8e22', " + createTime + ", '" + content.replace(/'/g, "''") + "', 1, 1, 0)"; + var r = writeSQL(db, sql); + if (r.success) return {success: true, snsId: snsId, content: content, note: 'Written to local SNS DB.'}; + // 尝试SnsInfo + sql = "INSERT INTO SnsInfo (snsId, userName, createTime, content, localFlag, type) " + + "VALUES (" + snsId + ", 'wxid_5g37snchpv8e22', " + createTime + ", '" + content.replace(/'/g, "''") + "', 1, 1)"; + r = writeSQL(db, sql); + if (r.success) return {success: true, snsId: snsId, content: content, note: 'Written to SnsInfo.'}; + return {success: false, error: r.error}; + }, + + // ===== 视频号 ===== + getFinderInfo: function() { + var db = getDB('finder'); + if (!db) return {error: 'Finder DB not available'}; + var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); + var result = {tables: tables.rows.map(function(r) { return r.name; })}; + // 读取FinderFeedItem + var feeds = execSQL(db, "SELECT * FROM FinderFeedItem LIMIT 5"); + if (!feeds.error) result.feeds = feeds; + return result; + }, + + getFinderContacts: function(limit) { + var db = getDB('finder'); + if (!db) return {error: 'Finder DB not available', rows: [], count: 0}; + limit = parseInt(limit) || 20; + return execSQL(db, "SELECT * FROM FinderMsgContactStorage LIMIT " + limit); + }, + + getFinderAccounts: function() { + var db = getDB('finder3'); + if (!db) return {error: 'FinderAccounts DB not available'}; + var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); + var result = {tables: tables.rows.map(function(r) { return r.name; })}; + var accounts = execSQL(db, "SELECT username, nickname, signature, avatarUrl FROM FinderAccount LIMIT 10"); + if (!accounts.error) result.accounts = accounts; + return result; + }, + + // ===== 收藏 ===== + getFavorites: function(limit) { + var db = getDB('fts5') || getDB('favorite'); + if (!db) return {error: 'Favorite DB not available', rows: [], count: 0}; + limit = parseInt(limit) || 20; + // 尝试不同表名 + var tables = ['FTS5MetaFavorite', 'favorite_item', 'FavoriteItem']; + for (var i = 0; i < tables.length; i++) { + var r = execSQL(db, "SELECT * FROM " + tables[i] + " LIMIT " + limit); + if (!r.error) return r; + } + // 列出所有表 + var allTables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); + return {error: 'No favorite table found', available_tables: allTables.rows.map(function(r){return r.name;}), rows: [], count: 0}; + }, + + // ===== 钱包/支付 ===== + getWalletInfo: function() { + var db = getDB('wallet'); + if (!db) return {error: 'Wallet DB not available'}; + var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); + return {tables: tables.rows.map(function(r) { return r.name; })}; + }, + + getWalletLedger: function(limit) { + var db = getDB('wallet'); + if (!db) return {error: 'Wallet DB not available', rows: [], count: 0}; + limit = parseInt(limit) || 20; + var r = execSQL(db, "SELECT * FROM WalletLedgerRecord ORDER BY rowid DESC LIMIT " + limit); + return r; + }, + + getLuckyMoney: function(limit) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available', rows: [], count: 0}; + limit = parseInt(limit) || 20; + return execSQL(db, "SELECT * FROM WalletLuckyMoney ORDER BY rowid DESC LIMIT " + limit); + }, + + // ===== 媒体 ===== + getImageCount: function() { + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + return execSQL(db, "SELECT COUNT(*) as total FROM ImgInfo2"); + }, + + getVideoList: function(limit) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available', rows: [], count: 0}; + limit = parseInt(limit) || 10; + return execSQL(db, "SELECT * FROM videoinfo2 ORDER BY rowid DESC LIMIT " + limit); + }, + + getFileList: function(limit) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available', rows: [], count: 0}; + limit = parseInt(limit) || 10; + return execSQL(db, "SELECT * FROM FileMsgInfo ORDER BY rowid DESC LIMIT " + limit); + }, + + // ===== 小程序 ===== + getAppBrandList: function() { + var db = getDB('appbrand'); + if (!db) return {error: 'AppBrand DB not available'}; + var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); + var result = {tables: tables.rows.map(function(r) { return r.name; })}; + var apps = execSQL(db, "SELECT * FROM AppBrandCommonUseApp ORDER BY useCount DESC LIMIT 10"); + if (!apps.error) result.common_apps = apps; + return result; + }, + + // ===== 搜索 ===== + globalSearch: function(keyword) { + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + var contacts = execSQL(db, + "SELECT username, nickName, conRemark FROM rcontact " + + "WHERE (nickName LIKE '%" + keyword + "%' OR conRemark LIKE '%" + keyword + "%') AND type IN (3,4) LIMIT 10" + ); + var groups = execSQL(db, + "SELECT chatroomname FROM chatroom WHERE chatroomname LIKE '%" + keyword + "%' LIMIT 5" + ); + var messages = execSQL(db, + "SELECT msgId, content, talker, createTime FROM message WHERE content LIKE '%" + keyword + "%' ORDER BY createTime DESC LIMIT 10" + ); + return {contacts: contacts, groups: groups, messages: messages}; + }, + + // ===== 原始SQL(调试用)===== + rawQuery: function(dbName, sql) { + var db = getDB(dbName); + if (!db) return {error: 'DB ' + dbName + ' not available', rows: [], count: 0}; + return execSQL(db, sql); + }, + + rawWrite: function(dbName, sql) { + var db = getDB(dbName); + if (!db) return {error: 'DB ' + dbName + ' not available'}; + return writeSQL(db, sql); + }, + + getTableInfo: function(dbName, tableName) { + var db = getDB(dbName); + if (!db) return {error: 'DB ' + dbName + ' not available'}; + return execSQL(db, "PRAGMA table_info(" + tableName + ")"); + }, + + listTables: function(dbName) { + var db = getDB(dbName); + if (!db) return {error: 'DB ' + dbName + ' not available'}; + return execSQL(db, "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"); + }, + + // ===== 添加好友 ===== + addFriend: function(wxid, message) { + // 写入AddContactScene表(如果存在) + var db = getDB('main'); + if (!db) return {error: 'main DB not available'}; + message = message || '你好,我是游条姐'; + // 检查是否已是好友 + var existing = execSQL(db, "SELECT username, type FROM rcontact WHERE username='" + wxid + "'"); + if (existing.rows[0] && existing.rows[0].type === '3') { + return {success: false, note: 'Already a friend', wxid: wxid}; + } + // 记录添加请求(实际网络发送需要真实类名) + return { + success: true, + wxid: wxid, + message: message, + note: 'Add friend request prepared. Network send requires WeChat 8.0.56 real class name.', + existing: existing.rows[0] || null + }; + }, + + // ===== 截图 ===== + takeScreenshot: function(savePath) { + try { + var Runtime = Java.use('java.lang.Runtime'); + var rt = Runtime.getRuntime(); + savePath = savePath || '/sdcard/workphone_screenshot.png'; + rt.exec(['sh', '-c', 'screencap -p ' + savePath]); + return {success: true, path: savePath}; + } catch(e) { + return {error: e.toString()}; + } + }, + + // ===== 导航 ===== + navigate: function(activity) { + try { + var Intent = Java.use('android.content.Intent'); + var ctx = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext(); + var cls; + var activityMap = { + 'main': 'com.tencent.mm.ui.LauncherUI', + 'moments': 'com.tencent.mm.plugin.sns.ui.SnsTimeLineUI', + 'contacts': 'com.tencent.mm.ui.contact.AddressBookUI', + 'chat': 'com.tencent.mm.ui.chatting.ChattingUI', + 'finder': 'com.tencent.mm.plugin.finder.ui.FinderUI', + 'wallet': 'com.tencent.mm.plugin.wallet.ui.WalletUI' + }; + var activityName = activityMap[activity] || activity; + cls = Java.use(activityName).class; + var intent = Intent.$new(ctx, cls); + intent.addFlags(0x10000000); // FLAG_ACTIVITY_NEW_TASK + ctx.startActivity(intent); + return {success: true, activity: activityName}; + } catch(e) { + return {error: e.toString()}; + } + } +};