Files
workphone-sdk/sdk/agent/hook/wechat_full_control_v5.js

760 lines
32 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 工作手机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&&params.limit)||200;
var offset = (params&&params.offset)||0;
var keyword = params&&params.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&&params.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&&params.wxid;
var remark = params&&params.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&&params.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&&params.to_id;
var content = params&&params.content;
var msgType = (params&&params.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&&params.targets;
var content = params&&params.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&&params.talker;
var limit = (params&&params.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&&params.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&&params.limit)||20;
var offset = (params&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.group_id;
var notice = params&&params.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&&params.group_id;
var name = params&&params.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&&params.group_id;
var nick = params&&params.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&&params.group_id;
var memberId = params&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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&&params.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 };
}
};