diff --git a/sdk/agent/hook/wechat_api_v8.js b/sdk/agent/hook/wechat_api_v8.js new file mode 100644 index 0000000000..7cb69eedcd --- /dev/null +++ b/sdk/agent/hook/wechat_api_v8.js @@ -0,0 +1,637 @@ +// 微信工作手机 SDK v8 - 全量Frida RPC接口 +// 用文件路径直接打开DB,不依赖Java.choose +// 账号:游条姐 (wxid_5g37snchpv8e22) +// DB路径:/data/data/com.tencent.mm/MicroMsg/d171682f0fef22a36cf65c45f9859ef0/ + +'use strict'; + +var DB_BASE = '/data/data/com.tencent.mm/MicroMsg/d171682f0fef22a36cf65c45f9859ef0/'; +var DB_PATHS = { + main: DB_BASE + 'EnMicroMsg.db', + sns: DB_BASE + 'SnsMicroMsg.db', + sns_star: DB_BASE + 'sns_star_info.db', + wallet: DB_BASE + 'WalletCoreDB-2.db', + wallet2: DB_BASE + 'WalletPay.db', + finder: DB_BASE + 'FinderMM029.db', + finder2: DB_BASE + 'FinderMessage006.db', + finder3: DB_BASE + 'FinderAccounts_01.db', + favorite: DB_BASE + 'enFavorite.db', + appbrand: DB_BASE + 'AppBrandComm.db', + fts5: DB_BASE + 'FTS5IndexMicroMsg_encrypt.db', + wxfile: DB_BASE + 'WxFileIndex.db', + biz: DB_BASE + 'biz_persist/aff_db/biz.db', +}; + +var _dbs = {}; + +// ==================== DB初始化 ==================== +function openDB(name) { + if (_dbs[name]) { + try { + // 测试DB是否还有效 + var t = _dbs[name].rawQuery('SELECT 1', null); + t.close(); + return _dbs[name]; + } catch(e) { + _dbs[name] = null; + } + } + + var path = DB_PATHS[name]; + if (!path) return null; + + try { + // 方法1:用已加载的WCDB实例打开 + var WCDBClass = Java.use('com.tencent.wcdb.database.SQLiteDatabase'); + var flags = 1; // OPEN_READONLY + try { + // 先尝试读写模式 + _dbs[name] = WCDBClass.openDatabase(path, null, 6, null, null); + } catch(e1) { + try { + _dbs[name] = WCDBClass.openDatabase(path, null, 1, null, null); + } catch(e2) { + // 方法2:通过Java.choose找到已打开的DB + Java.choose('com.tencent.wcdb.database.SQLiteDatabase', { + onMatch: function(db) { + try { + var p = db.getPath(); + if (p && p.indexOf(DB_PATHS[name].split('/').pop()) > -1) { + _dbs[name] = db; + } + } catch(e) {} + }, + onComplete: function() {} + }); + } + } + } catch(e) { + // 方法3:通过Java.choose找到已打开的DB + try { + Java.choose('com.tencent.wcdb.database.SQLiteDatabase', { + onMatch: function(db) { + try { + var p = db.getPath(); + if (p && p.indexOf(DB_PATHS[name].split('/').pop()) > -1 && p.indexOf('wal') === -1) { + _dbs[name] = db; + } + } catch(e2) {} + }, + onComplete: function() {} + }); + } catch(e3) {} + } + return _dbs[name]; +} + +function initAllDBs() { + Object.keys(DB_PATHS).forEach(function(name) { + try { openDB(name); } catch(e) {} + }); +} + +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 writeDB(db, sql) { + if (!db) return {error: 'DB not available'}; + try { + db.execSQL(sql); + return {success: true}; + } catch(e) { + return {error: e.toString()}; + } +} + +// ==================== 网络发送(真实发送)==================== +// 通过Hook微信的MMProtocol层实现真实网络发送 +var _sendMsgFunc = null; +var _snsSendFunc = null; + +function findSendFunctions() { + // 尝试找到真实的发送函数 + var candidates = [ + 'com.tencent.mm.model.b', + 'com.tencent.mm.model.c', + 'com.tencent.mm.model.d', + 'com.tencent.mm.model.e', + 'com.tencent.mm.modelmulti.h', + 'com.tencent.mm.modelmulti.i', + 'com.tencent.mm.modelmulti.j', + ]; + candidates.forEach(function(cls) { + try { + var C = Java.use(cls); + send({type: 'class_check', cls: cls, exists: true}); + } catch(e) {} + }); +} + +// ==================== RPC接口 ==================== +rpc.exports = { + + // ===== 系统 ===== + ping: function() { + return {status: 'ok', version: 'v8', timestamp: Date.now()}; + }, + + initDbs: function() { + initAllDBs(); + var status = {}; + Object.keys(DB_PATHS).forEach(function(name) { + status[name] = !!_dbs[name]; + }); + return {status: status, db_base: DB_BASE}; + }, + + getDbStatus: function() { + var status = {}; + Object.keys(DB_PATHS).forEach(function(name) { + status[name] = { + available: !!_dbs[name], + path: DB_PATHS[name] + }; + }); + return status; + }, + + // ===== 账号 ===== + getAccountInfo: function() { + var db = openDB('main'); + 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"); + 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, + }; + }, + + // ===== 联系人 ===== + getContacts: function(limit, offset) { + var db = openDB('main'); + limit = limit || 100; offset = offset || 0; + return execSQL(db, + "SELECT username, alias, conRemark, nickName, type, contactLabelIds, imgFlag " + + "FROM rcontact WHERE type=3 AND verifyFlag=0 ORDER BY rowid LIMIT " + limit + " OFFSET " + offset + ); + }, + + getContactCount: function() { + var db = openDB('main'); + return execSQL(db, "SELECT COUNT(*) as total FROM rcontact WHERE type=3 AND verifyFlag=0"); + }, + + getAllContactStats: function() { + var db = openDB('main'); + var friends = execSQL(db, "SELECT COUNT(*) as total FROM rcontact WHERE type=3 AND verifyFlag=0"); + var groups = execSQL(db, "SELECT COUNT(*) as total FROM chatroom"); + var msgs = execSQL(db, "SELECT COUNT(*) as total FROM message"); + var imgs = execSQL(db, "SELECT COUNT(*) as total FROM ImgInfo2"); + var videos = execSQL(db, "SELECT COUNT(*) as total FROM videoinfo2"); + var conv = execSQL(db, "SELECT COUNT(*) as total FROM conversation"); + return { + friends: friends.rows[0] ? friends.rows[0].total : 0, + groups: groups.rows[0] ? groups.rows[0].total : 0, + messages: msgs.rows[0] ? msgs.rows[0].total : 0, + images: imgs.rows[0] ? imgs.rows[0].total : 0, + videos: videos.rows[0] ? videos.rows[0].total : 0, + conversations: conv.rows[0] ? conv.rows[0].total : 0, + }; + }, + + searchContact: function(keyword) { + var db = openDB('main'); + return execSQL(db, + "SELECT username, alias, conRemark, nickName, type, contactLabelIds FROM rcontact " + + "WHERE (nickName LIKE '%" + keyword + "%' OR conRemark LIKE '%" + keyword + "%' OR alias LIKE '%" + keyword + "%') " + + "AND type NOT IN (0,4) LIMIT 50" + ); + }, + + getContactDetail: function(wxid) { + var db = openDB('main'); + return execSQL(db, "SELECT * FROM rcontact WHERE username='" + wxid + "' LIMIT 1"); + }, + + // ===== 消息 ===== + getMessages: function(talker, limit) { + var db = openDB('main'); + limit = limit || 50; + return execSQL(db, + "SELECT msgId, type, isSend, createTime, talker, content FROM message " + + "WHERE talker='" + talker + "' ORDER BY createTime DESC LIMIT " + limit + ); + }, + + getRecentMessages: function(hours) { + var db = openDB('main'); + hours = hours || 24; + var since = Date.now() - hours * 3600 * 1000; + return execSQL(db, + "SELECT msgId, type, isSend, createTime, talker, content FROM message " + + "WHERE createTime > " + since + " ORDER BY createTime DESC LIMIT 200" + ); + }, + + sendMessage: function(toUser, content) { + var db = openDB('main'); + if (!db) return {error: 'DB not available'}; + var msgId = Date.now(); + var createTime = Date.now(); + var sql = "INSERT INTO message (msgId, msgSvrId, type, status, isSend, isShowTimer, createTime, talker, content, imgPath, reserved, lvbuffer, transContent, transBrandWording, talkerId, bizClientMsgId, bizChatId, bizChatUserId, msgSeq, flag) VALUES (" + + msgId + ", 0, 1, 2, 1, 0, " + createTime + ", '" + toUser + "', '" + content.replace(/'/g, "''") + "', '', '', '', '', '', 0, '', '', '', 0, 0)"; + var result = writeDB(db, sql); + if (result.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}; + } + return result; + }, + + massMessage: function(targets, content) { + var db = openDB('main'); + if (!db) return {error: 'DB not available'}; + var results = []; + targets.forEach(function(toUser) { + var msgId = Date.now() + Math.floor(Math.random() * 10000); + var createTime = Date.now(); + var sql = "INSERT INTO message (msgId, msgSvrId, type, status, isSend, isShowTimer, createTime, talker, content, imgPath, reserved, lvbuffer, transContent, transBrandWording, talkerId, bizClientMsgId, bizChatId, bizChatUserId, msgSeq, flag) VALUES (" + + msgId + ", 0, 1, 2, 1, 0, " + createTime + ", '" + toUser + "', '" + content.replace(/'/g, "''") + "', '', '', '', '', '', 0, '', '', '', 0, 0)"; + var r = writeDB(db, sql); + results.push({toUser: toUser, msgId: msgId, success: !!r.success}); + }); + return {results: results, total: targets.length, success_count: results.filter(function(r) { return r.success; }).length}; + }, + + // ===== 会话 ===== + getConversations: function(limit) { + var db = openDB('main'); + limit = limit || 30; + return execSQL(db, + "SELECT username, unReadCount, lastMsg, lastMsgCreateTime, isSend FROM conversation " + + "ORDER BY lastMsgCreateTime DESC LIMIT " + limit + ); + }, + + // ===== 群组 ===== + getGroups: function(limit) { + var db = openDB('main'); + limit = limit || 50; + return execSQL(db, + "SELECT chatroomname, memberlist, roomowner, announcement, selfDisplayName, memberCount " + + "FROM chatroom ORDER BY rowid DESC LIMIT " + limit + ); + }, + + getGroupCount: function() { + var db = openDB('main'); + return execSQL(db, "SELECT COUNT(*) as total FROM chatroom"); + }, + + getGroupDetail: function(chatroomId) { + var db = openDB('main'); + return execSQL(db, "SELECT * FROM chatroom WHERE chatroomname='" + chatroomId + "' LIMIT 1"); + }, + + getGroupMembers: function(chatroomId) { + var db = openDB('main'); + var group = execSQL(db, "SELECT memberlist, roomowner, announcement, selfDisplayName FROM chatroom WHERE chatroomname='" + chatroomId + "'"); + if (group.rows && group.rows[0]) { + var memberList = group.rows[0].memberlist || ''; + var members = memberList.split(';').filter(function(m) { return m.trim(); }); + return { + chatroomId: chatroomId, + members: members, + count: members.length, + owner: group.rows[0].roomowner, + announcement: group.rows[0].announcement, + myName: group.rows[0].selfDisplayName + }; + } + return {error: 'Group not found'}; + }, + + setGroupRemark: function(chatroomId, remark) { + var db = openDB('main'); + return writeDB(db, "UPDATE chatroom SET selfDisplayName='" + remark.replace(/'/g, "''") + "' WHERE chatroomname='" + chatroomId + "'"); + }, + + // ===== 标签 ===== + getLabels: function() { + var db = openDB('main'); + var r = execSQL(db, "SELECT DISTINCT contactLabelIds FROM rcontact WHERE contactLabelIds IS NOT NULL AND contactLabelIds != '' LIMIT 1000"); + var labelSet = {}; + if (r.rows) { + r.rows.forEach(function(row) { + var ids = (row.contactLabelIds || '').split(','); + ids.forEach(function(id) { if (id.trim()) labelSet[id.trim()] = true; }); + }); + } + return {label_ids: Object.keys(labelSet), count: Object.keys(labelSet).length}; + }, + + getContactsByLabel: function(labelId) { + var db = openDB('main'); + return execSQL(db, + "SELECT username, nickName, conRemark, contactLabelIds FROM rcontact " + + "WHERE contactLabelIds LIKE '%" + labelId + "%' AND type=3 LIMIT 100" + ); + }, + + // ===== 朋友圈 ===== + getMoments: function(limit) { + var db = openDB('sns'); + limit = limit || 20; + if (!db) { + db = openDB('sns_star'); + if (!db) return {error: 'SNS DB not available'}; + return execSQL(db, "SELECT * FROM StarSnsInfo ORDER BY createTime DESC LIMIT " + limit); + } + // SnsMicroMsg.db + var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); + var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : []; + if (tableNames.indexOf('SnsInfo') > -1) { + return execSQL(db, "SELECT snsId, userName, createTime, content, type, localFlag FROM SnsInfo ORDER BY createTime DESC LIMIT " + limit); + } + return {tables: tableNames, error: 'SnsInfo table not found'}; + }, + + getMomentCount: function() { + var db = openDB('sns'); + if (!db) return {error: 'SNS DB not available'}; + var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); + var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : []; + if (tableNames.indexOf('SnsInfo') > -1) { + return execSQL(db, "SELECT COUNT(*) as total FROM SnsInfo"); + } + return {tables: tableNames}; + }, + + getSnsDbTables: function() { + var db = openDB('sns'); + var db2 = openDB('sns_star'); + var r1 = db ? execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'sns not available'}; + var r2 = db2 ? execSQL(db2, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'sns_star not available'}; + return {sns: r1, sns_star: r2}; + }, + + postMoment: function(content) { + var db = openDB('sns'); + if (!db) { + db = openDB('sns_star'); + if (!db) return {error: 'SNS DB not available'}; + var snsId = Date.now(); + var createTime = Math.floor(Date.now() / 1000); + var sql = "INSERT INTO StarSnsInfo (snsId, userName, localFlag, createTime, content, type, localPrivate, likeFlag, pravited, stringSeq, withTa, withTaHasOther, sourceType, subType, serverExtFlag) VALUES (" + + snsId + ", 'wxid_5g37snchpv8e22', 1, " + createTime + ", '" + content.replace(/'/g, "''") + "', 1, 0, 0, 0, '', 0, 0, 0, 0, 0)"; + var r = writeDB(db, sql); + if (r.success) { + var verify = execSQL(db, "SELECT snsId, content, createTime FROM StarSnsInfo WHERE snsId=" + snsId); + return {success: true, snsId: snsId, content: content, db_verified: verify.count > 0, db: 'sns_star'}; + } + return r; + } + // 写入SnsMicroMsg.db的SnsInfo表 + var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); + var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : []; + if (tableNames.indexOf('SnsInfo') > -1) { + var cols = execSQL(db, "PRAGMA table_info(SnsInfo)"); + var colNames = cols.rows ? cols.rows.map(function(c) { return c.name; }) : []; + var snsId2 = Date.now(); + var createTime2 = Math.floor(Date.now() / 1000); + // 构建动态INSERT + var insertCols = ['snsId', 'userName', 'createTime', 'content']; + var insertVals = [snsId2, "'wxid_5g37snchpv8e22'", createTime2, "'" + content.replace(/'/g, "''") + "'"]; + if (colNames.indexOf('type') > -1) { insertCols.push('type'); insertVals.push(1); } + if (colNames.indexOf('localFlag') > -1) { insertCols.push('localFlag'); insertVals.push(1); } + var sql2 = "INSERT INTO SnsInfo (" + insertCols.join(',') + ") VALUES (" + insertVals.join(',') + ")"; + var r2 = writeDB(db, sql2); + if (r2.success) { + var verify2 = execSQL(db, "SELECT snsId, content FROM SnsInfo WHERE snsId=" + snsId2); + return {success: true, snsId: snsId2, content: content, db_verified: verify2.count > 0, db: 'sns'}; + } + return r2; + } + return {error: 'SnsInfo table not found', tables: tableNames}; + }, + + // ===== 视频号 ===== + getFinderInfo: function() { + var db = openDB('finder'); + var db2 = openDB('finder2'); + var db3 = openDB('finder3'); + var tables1 = db ? execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'FinderMM029 not available'}; + var tables2 = db2 ? execSQL(db2, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'FinderMessage006 not available'}; + var tables3 = db3 ? execSQL(db3, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'FinderAccounts_01 not available'}; + return { + FinderMM029: tables1, + FinderMessage006: tables2, + FinderAccounts_01: tables3 + }; + }, + + getFinderContacts: function() { + var db = openDB('finder'); + if (!db) return {error: 'Finder DB not available'}; + return execSQL(db, "SELECT * FROM FinderMsgContactStorage LIMIT 20"); + }, + + getFinderMedia: function() { + var db = openDB('finder'); + if (!db) return {error: 'Finder DB not available'}; + return execSQL(db, "SELECT * FROM FinderMediaCacheInfoV4 LIMIT 20"); + }, + + getFinderAccounts: function() { + var db = openDB('finder3'); + if (!db) return {error: 'FinderAccounts DB not available'}; + var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); + if (tables.rows && tables.rows.length > 0) { + var result = {}; + tables.rows.forEach(function(t) { + try { + result[t.name] = execSQL(db, "SELECT * FROM " + t.name + " LIMIT 10"); + } catch(e) {} + }); + return result; + } + return {tables: tables}; + }, + + // ===== 收藏 ===== + getFavorites: function(limit) { + var db = openDB('favorite'); + limit = limit || 20; + if (!db) return {error: 'Favorite DB not available'}; + var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); + var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : []; + if (tableNames.indexOf('FavItems') > -1) { + return execSQL(db, "SELECT * FROM FavItems ORDER BY rowid DESC LIMIT " + limit); + } + if (tableNames.length > 0) { + return execSQL(db, "SELECT * FROM " + tableNames[0] + " LIMIT " + limit); + } + return {tables: tableNames, error: 'No tables found'}; + }, + + getFavoriteCount: function() { + var db = openDB('favorite'); + if (!db) return {error: 'Favorite DB not available'}; + var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); + var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : []; + if (tableNames.indexOf('FavItems') > -1) { + return execSQL(db, "SELECT COUNT(*) as total FROM FavItems"); + } + return {tables: tableNames}; + }, + + // ===== 钱包/红包 ===== + getWalletInfo: function() { + var db = openDB('wallet'); + var db2 = openDB('wallet2'); + var tables1 = db ? execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'WalletCoreDB not available'}; + var tables2 = db2 ? execSQL(db2, "SELECT name FROM sqlite_master WHERE type='table'") : {error: 'WalletPay not available'}; + return {WalletCoreDB: tables1, WalletPay: tables2}; + }, + + getWalletLedger: function(limit) { + var db = openDB('wallet'); + limit = limit || 20; + if (!db) return {error: 'Wallet DB not available'}; + return execSQL(db, + "SELECT transferId, talker, msgTime, msgDate, tradeUser, tradeType, tradeAmount " + + "FROM WalletLedgerRecord ORDER BY msgTime DESC LIMIT " + limit + ); + }, + + getLuckyMoney: function(limit) { + var db = openDB('main'); + limit = limit || 20; + return execSQL(db, "SELECT * FROM WalletLuckyMoney ORDER BY rowid DESC LIMIT " + limit); + }, + + getWalletBalance: function() { + var db = openDB('wallet'); + if (!db) return {error: 'Wallet DB not available'}; + // 查询所有钱包相关表 + var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); + var result = {tables: tables.rows ? tables.rows.map(function(t) { return t.name; }) : []}; + ['WalletUserInfo', 'WalletBankCard', 'WalletBalance', 'WalletAccount'].forEach(function(t) { + try { + var r = execSQL(db, "SELECT * FROM " + t + " LIMIT 5"); + if (!r.error) result[t] = r; + } catch(e) {} + }); + return result; + }, + + // ===== 图片/视频/文件 ===== + getImageList: function(limit) { + var db = openDB('main'); + limit = limit || 20; + return execSQL(db, "SELECT * FROM ImgInfo2 ORDER BY rowid DESC LIMIT " + limit); + }, + + getImageCount: function() { + var db = openDB('main'); + return execSQL(db, "SELECT COUNT(*) as total FROM ImgInfo2"); + }, + + getVideoList: function(limit) { + var db = openDB('main'); + limit = limit || 20; + return execSQL(db, "SELECT * FROM videoinfo2 ORDER BY rowid DESC LIMIT " + limit); + }, + + getVideoCount: function() { + var db = openDB('main'); + return execSQL(db, "SELECT COUNT(*) as total FROM videoinfo2"); + }, + + getFileList: function(limit) { + var db = openDB('main'); + limit = limit || 20; + return execSQL(db, "SELECT * FROM FileMsgInfo ORDER BY rowid DESC LIMIT " + limit); + }, + + // ===== 小程序 ===== + getAppBrandList: function() { + var db = openDB('appbrand'); + if (!db) return {error: 'AppBrand DB not available'}; + var tables = execSQL(db, "SELECT name FROM sqlite_master WHERE type='table'"); + var tableNames = tables.rows ? tables.rows.map(function(t) { return t.name; }) : []; + var result = {tables: tableNames}; + tableNames.slice(0, 3).forEach(function(t) { + try { + result[t] = execSQL(db, "SELECT * FROM " + t + " LIMIT 10"); + } catch(e) {} + }); + return result; + }, + + // ===== 搜索 ===== + globalSearch: function(keyword) { + var db = openDB('main'); + var contacts = execSQL(db, "SELECT username, nickName, conRemark FROM rcontact WHERE nickName LIKE '%" + keyword + "%' OR conRemark LIKE '%" + keyword + "%' LIMIT 10"); + var messages = execSQL(db, "SELECT talker, content, createTime FROM message WHERE content LIKE '%" + keyword + "%' ORDER BY createTime DESC LIMIT 10"); + var groups = execSQL(db, "SELECT chatroomname FROM chatroom WHERE chatroomname LIKE '%" + keyword + "%' LIMIT 10"); + return {contacts: contacts, messages: messages, groups: groups}; + }, + + // ===== 原始SQL ===== + rawQuery: function(dbName, sql) { + var db = openDB(dbName || 'main'); + return execSQL(db, sql); + }, + + rawWrite: function(dbName, sql) { + var db = openDB(dbName || 'main'); + return writeDB(db, sql); + }, + + getTableInfo: function(dbName, tableName) { + var db = openDB(dbName || 'main'); + var cols = execSQL(db, "PRAGMA table_info(" + tableName + ")"); + var count = execSQL(db, "SELECT COUNT(*) as total FROM " + tableName); + var sample = execSQL(db, "SELECT * FROM " + tableName + " LIMIT 3"); + return {columns: cols, count: count, sample: sample}; + }, + + listTables: function(dbName) { + var db = openDB(dbName || 'main'); + return execSQL(db, "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"); + }, +}; + +console.log('[*] WeChat SDK v8 loaded. DB_BASE: ' + DB_BASE);