760 lines
32 KiB
JavaScript
760 lines
32 KiB
JavaScript
/**
|
||
* 工作手机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 };
|
||
}
|
||
};
|