mirror of
https://github.com/yincongcyincong/wechat_chatter.git
synced 2026-07-15 10:26:52 +08:00
add audio
This commit is contained in:
+36
-14
@@ -9,7 +9,8 @@ var buf2RespAddr = baseAddr.add(0x37173B0)
|
||||
function setReceiver() {
|
||||
|
||||
// 3. 开始拦截
|
||||
Interceptor.attach(buf2RespAddr, {
|
||||
Interceptor.attach(buf2RespAddr,
|
||||
{
|
||||
onEnter: function (args) {
|
||||
const currentPtr = this.context.x1;
|
||||
let start = 0x1e;
|
||||
@@ -52,23 +53,13 @@ function setReceiver() {
|
||||
var msgType = "private"
|
||||
var groupId = ""
|
||||
var senderUser = sender
|
||||
var messages = [];
|
||||
var senderNickname = ""
|
||||
var messages = getMessages(content, sender, mediaContent);
|
||||
|
||||
if (sender.includes("@chatroom")) {
|
||||
msgType = "group"
|
||||
groupId = sender
|
||||
|
||||
let splitIndex = content.indexOf(':')
|
||||
let pureContent = content.substring(splitIndex + 1).trim();
|
||||
const parts = pureContent.split('\u2005');
|
||||
for (let part of parts) {
|
||||
part = part.trim();
|
||||
if (!part.startsWith("@")) {
|
||||
messages.push({type: "text", data: {text: part}});
|
||||
}
|
||||
}
|
||||
|
||||
const sendUserStart = content.indexOf('wxid_')
|
||||
senderUser = content.substring(sendUserStart, splitIndex).trim();
|
||||
|
||||
@@ -102,7 +93,6 @@ function setReceiver() {
|
||||
if (!senderNickname) {
|
||||
senderNickname = sender
|
||||
}
|
||||
messages.push({type: "text", data: {text: content}});
|
||||
}
|
||||
|
||||
send({
|
||||
@@ -119,7 +109,6 @@ function setReceiver() {
|
||||
sender: {user_id: senderUser, nickname: senderNickname},
|
||||
msgsource: xml,
|
||||
raw_message: content,
|
||||
// media: mediaContent,
|
||||
show_content:userContent
|
||||
})
|
||||
},
|
||||
@@ -130,6 +119,39 @@ function setReceiver() {
|
||||
// 使用 setImmediate 确保在模块加载后执行
|
||||
setImmediate(setReceiver)
|
||||
|
||||
function getMessages(content, sender, mediaContent) {
|
||||
var messages = [];
|
||||
if (sender.includes("@chatroom")) {
|
||||
let splitIndex = content.indexOf(':')
|
||||
let pureContent = content.substring(splitIndex + 1).trim();
|
||||
const parts = pureContent.split('\u2005');
|
||||
for (let part of parts) {
|
||||
part = part.trim();
|
||||
if (part.startsWith("<?xml version=\"1.0\"?><msg><img")) {
|
||||
messages.push({type: "image", data: {text: part}});
|
||||
} else if (part.startsWith("<msg><voicemsg")) {
|
||||
messages.push({type: "record", data: {text: part}});
|
||||
} else {
|
||||
messages.push({type: "text", data: {text: part}});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (content.startsWith("<?xml version=\"1.0\"?><msg><img")) {
|
||||
messages.push({type: "image", data: {text: content}});
|
||||
} else if (content.startsWith("<msg><voicemsg")) {
|
||||
const audioStart = mediaContent.indexOf(35);
|
||||
if (audioStart !== -1) {
|
||||
mediaContent = mediaContent.subarray(audioStart);
|
||||
}
|
||||
messages.push({type: "record", data: {text: content, media: mediaContent}});
|
||||
} else {
|
||||
messages.push({type: "text", data: {text: content}});
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
|
||||
function getProtobufRawBytes(pBuffer, scanSize) {
|
||||
const tags = [0x12, 0x1A, 0x2A, 0x42, 0x52, 0x5A];
|
||||
|
||||
@@ -1,419 +0,0 @@
|
||||
// pure_js_keyboard_injector.js
|
||||
console.log("Pure JS Keyboard Injector - Starting...");
|
||||
|
||||
// 常量定义
|
||||
const NSEventTypeKeyDown = 10;
|
||||
const NSEventTypeKeyUp = 11;
|
||||
const kVK_Return = 36; // 回车键
|
||||
|
||||
// 1. Hook QNSView的handleKeyEvent方法
|
||||
if (ObjC.available) {
|
||||
const QNSView = ObjC.classes.QNSView;
|
||||
|
||||
if (QNSView) {
|
||||
console.log("✓ Found QNSView class");
|
||||
|
||||
// Hook方法
|
||||
const handleKeyEventMethod = QNSView['- handleKeyEvent:eventType:'];
|
||||
if (handleKeyEventMethod) {
|
||||
Interceptor.attach(handleKeyEventMethod.implementation, {
|
||||
onEnter: function(args) {
|
||||
console.log("\n[QNSView Hook]");
|
||||
const event = new ObjC.Object(args[2]);
|
||||
const eventType = args[3];
|
||||
console.log(`Event Type (a4): ${eventType}`);
|
||||
console.log(`KeyCode: ${event.keyCode()}`);
|
||||
console.log(`Characters: ${event.characters()}`);
|
||||
}
|
||||
});
|
||||
console.log("✓ QNSView handleKeyEvent hooked");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 发送回车键的函数
|
||||
function sendEnterKey() {
|
||||
try {
|
||||
console.log("\n=== 发送回车键 ===");
|
||||
|
||||
const NSApplication = ObjC.classes.NSApplication;
|
||||
const NSEvent = ObjC.classes.NSEvent;
|
||||
const app = NSApplication.sharedApplication();
|
||||
const keyWindow = app.keyWindow();
|
||||
|
||||
if (!keyWindow) {
|
||||
console.log("❌ 没有找到活动窗口");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`窗口: ${keyWindow}`);
|
||||
|
||||
// 查找QNSView
|
||||
function findQNSView(view) {
|
||||
if (view.$className === 'QNSView') {
|
||||
return view;
|
||||
}
|
||||
|
||||
try {
|
||||
const subviews = view.subviews();
|
||||
const count = subviews.count();
|
||||
for (let i = 0; i < count; i++) {
|
||||
const subview = subviews.objectAtIndex_(i);
|
||||
const found = findQNSView(subview);
|
||||
if (found) return found;
|
||||
}
|
||||
} catch (e) {
|
||||
// 忽略错误
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const contentView = keyWindow.contentView();
|
||||
const qnsView = findQNSView(contentView);
|
||||
|
||||
if (!qnsView) {
|
||||
console.log("❌ 没有找到QNSView,使用备用方法");
|
||||
sendEnterKeyAlternative();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✓ 找到QNSView: ${qnsView}`);
|
||||
|
||||
// 创建回车键按下事件
|
||||
const keyDownEvent = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
NSEventTypeKeyDown, // type = 10 (按下)
|
||||
{ x: 100, y: 100 }, // 位置
|
||||
0, // 修饰键
|
||||
Date.now() / 1000, // 时间戳(秒)
|
||||
keyWindow.windowNumber(), // 窗口编号
|
||||
NULL, // 上下文
|
||||
'\r', // 字符(回车)
|
||||
'\r', // 忽略修饰键的字符
|
||||
0, // 是否重复
|
||||
kVK_Return // 键码36=回车
|
||||
);
|
||||
|
||||
// 创建回车键释放事件
|
||||
const keyUpEvent = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
NSEventTypeKeyUp, // type = 11 (释放)
|
||||
{ x: 100, y: 100 }, // 位置
|
||||
0, // 修饰键
|
||||
(Date.now() / 1000) + 0.05, // 稍后的时间
|
||||
keyWindow.windowNumber(), // 窗口编号
|
||||
NULL, // 上下文
|
||||
'\r', // 字符
|
||||
'\r', // 忽略修饰键的字符
|
||||
0, // 是否重复
|
||||
kVK_Return // 键码
|
||||
);
|
||||
|
||||
// 发送按键按下(根据逆向分析,a4=6可能是按键按下)
|
||||
console.log("发送回车键按下...");
|
||||
qnsView.handleKeyEvent_eventType_(keyDownEvent, 6);
|
||||
|
||||
// 延迟发送按键释放
|
||||
setTimeout(() => {
|
||||
console.log("发送回车键释放...");
|
||||
qnsView.handleKeyEvent_eventType_(keyUpEvent, 7); // 猜测7是按键释放
|
||||
}, 50);
|
||||
|
||||
console.log("✓ 回车键发送完成");
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 发送回车键失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 备用方法:使用CGEvent
|
||||
function sendEnterKeyAlternative() {
|
||||
try {
|
||||
console.log("尝试使用CGEvent发送回车键...");
|
||||
|
||||
const CGEventCreateKeyboardEvent = Module.findExportByName('CoreGraphics', 'CGEventCreateKeyboardEvent');
|
||||
const CGEventPost = Module.findExportByName('CoreGraphics', 'CGEventPost');
|
||||
|
||||
if (CGEventCreateKeyboardEvent && CGEventPost) {
|
||||
const kCGHIDEventTap = 0;
|
||||
|
||||
// 发送回车键按下
|
||||
const keyDown = new NativeFunction(CGEventCreateKeyboardEvent, 'pointer', ['pointer', 'uint64', 'bool'])(
|
||||
NULL,
|
||||
kVK_Return,
|
||||
true
|
||||
);
|
||||
new NativeFunction(CGEventPost, 'void', ['uint32', 'pointer'])(kCGHIDEventTap, keyDown);
|
||||
|
||||
// 延迟发送释放
|
||||
setTimeout(() => {
|
||||
const keyUp = new NativeFunction(CGEventCreateKeyboardEvent, 'pointer', ['pointer', 'uint64', 'bool'])(
|
||||
NULL,
|
||||
kVK_Return,
|
||||
false
|
||||
);
|
||||
new NativeFunction(CGEventPost, 'void', ['uint32', 'pointer'])(kCGHIDEventTap, keyUp);
|
||||
console.log("✓ CGEvent 回车键发送完成");
|
||||
}, 50);
|
||||
} else {
|
||||
console.log("❌ CGEvent API 不可用");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ CGEvent方法失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 发送文本"123"的函数
|
||||
function sendText123() {
|
||||
try {
|
||||
console.log("\n=== 发送文本 '123' ===");
|
||||
|
||||
const NSApplication = ObjC.classes.NSApplication;
|
||||
const NSEvent = ObjC.classes.NSEvent;
|
||||
const app = NSApplication.sharedApplication();
|
||||
const keyWindow = app.keyWindow();
|
||||
|
||||
if (!keyWindow) {
|
||||
console.log("❌ 没有找到活动窗口");
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找QNSView
|
||||
function findQNSView(view) {
|
||||
if (view.$className === 'QNSView') return view;
|
||||
try {
|
||||
const subviews = view.subviews();
|
||||
const count = subviews.count();
|
||||
for (let i = 0; i < count; i++) {
|
||||
const found = findQNSView(subviews.objectAtIndex_(i));
|
||||
if (found) return found;
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
const qnsView = findQNSView(keyWindow.contentView());
|
||||
|
||||
if (!qnsView) {
|
||||
console.log("❌ 没有找到QNSView");
|
||||
return;
|
||||
}
|
||||
|
||||
// 要发送的字符和对应的键码
|
||||
const textToSend = [
|
||||
{ char: '1', keyCode: 18 },
|
||||
{ char: '2', keyCode: 19 },
|
||||
{ char: '3', keyCode: 20 }
|
||||
];
|
||||
|
||||
// 逐个发送字符
|
||||
textToSend.forEach((item, index) => {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
console.log(`发送字符: ${item.char}`);
|
||||
|
||||
// 创建按键按下事件
|
||||
const keyDownEvent = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
NSEventTypeKeyDown,
|
||||
{ x: 100, y: 100 },
|
||||
0,
|
||||
Date.now() / 1000,
|
||||
keyWindow.windowNumber(),
|
||||
NULL,
|
||||
item.char,
|
||||
item.char,
|
||||
0,
|
||||
item.keyCode
|
||||
);
|
||||
|
||||
// 创建按键释放事件
|
||||
const keyUpEvent = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
NSEventTypeKeyUp,
|
||||
{ x: 100, y: 100 },
|
||||
0,
|
||||
(Date.now() / 1000) + 0.03,
|
||||
keyWindow.windowNumber(),
|
||||
NULL,
|
||||
item.char,
|
||||
item.char,
|
||||
0,
|
||||
item.keyCode
|
||||
);
|
||||
|
||||
// 发送按键按下
|
||||
qnsView.handleKeyEvent_eventType_(keyDownEvent, 6);
|
||||
|
||||
// 延迟发送按键释放
|
||||
setTimeout(() => {
|
||||
qnsView.handleKeyEvent_eventType_(keyUpEvent, 7);
|
||||
}, 30);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`发送字符 ${item.char} 失败: ${error}`);
|
||||
}
|
||||
}, index * 100); // 每个字符间隔100ms
|
||||
});
|
||||
|
||||
console.log("✓ 文本'123'发送中...");
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 发送文本失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 组合函数:先发送123,然后回车
|
||||
function send123AndEnter() {
|
||||
console.log("\n=== 开始发送: 123 + 回车 ===");
|
||||
|
||||
// 先发送123
|
||||
sendText123();
|
||||
|
||||
// 延迟500ms后发送回车
|
||||
setTimeout(() => {
|
||||
console.log("\n=== 发送回车键 ===");
|
||||
sendEnterKey();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// 6. 通用按键注入函数
|
||||
function injectKey(keyCode, eventType, characters = '') {
|
||||
try {
|
||||
const NSApplication = ObjC.classes.NSApplication;
|
||||
const NSEvent = ObjC.classes.NSEvent;
|
||||
const app = NSApplication.sharedApplication();
|
||||
const keyWindow = app.keyWindow();
|
||||
|
||||
if (!keyWindow) {
|
||||
console.log("❌ 没有活动窗口");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找QNSView
|
||||
function findQNSView(view) {
|
||||
if (view.$className === 'QNSView') return view;
|
||||
try {
|
||||
const subviews = view.subviews();
|
||||
for (let i = 0; i < subviews.count(); i++) {
|
||||
const found = findQNSView(subviews.objectAtIndex_(i));
|
||||
if (found) return found;
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
const qnsView = findQNSView(keyWindow.contentView());
|
||||
|
||||
if (qnsView) {
|
||||
const event = NSEvent.keyEventWithType_location_modifierFlags_timestamp_windowNumber_context_characters_charactersIgnoringModifiers_isARepeat_keyCode_(
|
||||
eventType, // 10=按下, 11=释放
|
||||
{ x: 100, y: 100 },
|
||||
0,
|
||||
Date.now() / 1000,
|
||||
keyWindow.windowNumber(),
|
||||
NULL,
|
||||
characters,
|
||||
characters,
|
||||
0,
|
||||
keyCode
|
||||
);
|
||||
|
||||
// 根据逆向分析,a4参数:6可能是按下,7可能是释放
|
||||
const a4Param = eventType === NSEventTypeKeyDown ? 6 : 7;
|
||||
qnsView.handleKeyEvent_eventType_(event, a4Param);
|
||||
|
||||
console.log(`✓ 发送按键: keyCode=${keyCode}, type=${eventType}, a4=${a4Param}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ 注入按键失败: ${error}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 直接调用Qt事件发送(基于逆向分析)
|
||||
function sendQtKeyEvent(keyCode, text, modifiers = 0) {
|
||||
try {
|
||||
console.log(`\n=== 直接发送Qt键盘事件: ${text} ===`);
|
||||
|
||||
// 尝试找到QNSView并调用底层函数
|
||||
const NSApplication = ObjC.classes.NSApplication;
|
||||
const app = NSApplication.sharedApplication();
|
||||
const keyWindow = app.keyWindow();
|
||||
|
||||
if (!keyWindow) return;
|
||||
|
||||
// 查找QNSView
|
||||
function findQNSView(view) {
|
||||
if (view.$className === 'QNSView') return view;
|
||||
try {
|
||||
const subviews = view.subviews();
|
||||
for (let i = 0; i < subviews.count(); i++) {
|
||||
const found = findQNSView(subviews.objectAtIndex_(i));
|
||||
if (found) return found;
|
||||
}
|
||||
} catch (e) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
const qnsView = findQNSView(keyWindow.contentView());
|
||||
|
||||
if (qnsView && qnsView.handleKeyEvent) {
|
||||
// 创建模拟的事件对象
|
||||
const fakeEvent = {
|
||||
keyCode: function() { return keyCode; },
|
||||
characters: function() { return text; },
|
||||
charactersIgnoringModifiers: function() { return text; },
|
||||
timestamp: function() { return Date.now() / 1000; },
|
||||
modifierFlags: function() { return modifiers; },
|
||||
isARepeat: function() { return 0; }
|
||||
};
|
||||
|
||||
// 包装成ObjC对象
|
||||
const eventWrapper = new ObjC.Object(fakeEvent);
|
||||
|
||||
// 发送事件
|
||||
qnsView.handleKeyEvent_eventType_(eventWrapper, 6);
|
||||
|
||||
console.log(`✓ Qt事件发送: ${text} (keyCode: ${keyCode})`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Qt事件发送失败: ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 8. 导出函数到全局
|
||||
global.sendEnterKey = sendEnterKey;
|
||||
global.sendText123 = sendText123;
|
||||
global.send123AndEnter = send123AndEnter;
|
||||
global.injectKey = injectKey;
|
||||
global.sendQtKeyEvent = sendQtKeyEvent;
|
||||
|
||||
// 9. 创建交互式菜单
|
||||
function showMenu() {
|
||||
console.log("\n" + "=".repeat(50));
|
||||
console.log("🎹 键盘注入器 - 纯JS版本");
|
||||
console.log("=".repeat(50));
|
||||
console.log("可用命令:");
|
||||
console.log("1. sendEnterKey() - 发送回车键");
|
||||
console.log("2. sendText123() - 发送文本 '123'");
|
||||
console.log("3. send123AndEnter() - 发送 '123' 然后回车");
|
||||
console.log("4. injectKey(36, 10) - 发送回车键按下");
|
||||
console.log("5. injectKey(36, 11) - 发送回车键释放");
|
||||
console.log("6. sendQtKeyEvent(18, '1') - 直接发送Qt事件");
|
||||
console.log("=".repeat(50));
|
||||
console.log("示例: 发送 '123' 然后回车:");
|
||||
console.log(" send123AndEnter()");
|
||||
console.log("=".repeat(50));
|
||||
}
|
||||
|
||||
// 10. 自动执行(可选)
|
||||
// 取消下面行的注释可以自动发送
|
||||
// setTimeout(send123AndEnter, 1000);
|
||||
|
||||
// 显示菜单
|
||||
showMenu();
|
||||
|
||||
console.log("\n✅ 键盘注入器加载完成!");
|
||||
console.log("📝 输入命令开始注入键盘事件...");
|
||||
+10
-1
@@ -5,6 +5,15 @@ go 1.25.0
|
||||
require (
|
||||
github.com/frida/frida-go v1.0.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/wdvxdr1123/go-silk v0.0.0-20220304095002-f67345df09ea
|
||||
)
|
||||
|
||||
require github.com/google/uuid v1.6.0 // indirect
|
||||
require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
golang.org/x/sys v0.0.0-20201126233918-771906719818 // indirect
|
||||
modernc.org/libc v1.8.1 // indirect
|
||||
modernc.org/mathutil v1.2.2 // indirect
|
||||
modernc.org/memory v1.0.4 // indirect
|
||||
)
|
||||
|
||||
@@ -1,6 +1,32 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/frida/frida-go v1.0.0 h1:Xlq1CB8QSAC6zbOFdjCX0oK8RjQGtdl2yATbUQKROwo=
|
||||
github.com/frida/frida-go v1.0.0/go.mod h1:O8Dg1YBGfQsBEL1a8x3GURw/JllJrcuvg78ga2OgdM4=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/wdvxdr1123/go-silk v0.0.0-20220304095002-f67345df09ea h1:sl1pYm1kHtIndckTY8YDt+QFt77vI0JnKHP0U8rZtKc=
|
||||
github.com/wdvxdr1123/go-silk v0.0.0-20220304095002-f67345df09ea/go.mod h1:ecFKZPX81BaB70I6ruUgEwYcDOtuNgJGnjdK+MIl5ko=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201126233918-771906719818 h1:f1CIuDlJhwANEC2MM87MBEVMr3jl5bifgsfj90XAF9c=
|
||||
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/libc v1.8.1 h1:y9oPIhwcaFXxX7kMp6Qb2ZLKzr0mDkikWN3CV5GS63o=
|
||||
modernc.org/libc v1.8.1/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
|
||||
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY=
|
||||
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||
modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM=
|
||||
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
|
||||
|
||||
+13
-14
@@ -10,6 +10,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
@@ -73,11 +74,11 @@ func sendHandler(w http.ResponseWriter, r *http.Request) {
|
||||
func SendHttpReq(msg map[string]interface{}) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("panic: %v\n", r)
|
||||
log.Printf("panic: %v, %v\n", r, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(time.Duration(config.SendInterval) * time.Second)
|
||||
time.Sleep(time.Duration(config.SendInterval) * time.Millisecond)
|
||||
// 这里处理你的 X1 数据
|
||||
jsonData, err := json.Marshal(msg["payload"])
|
||||
if err != nil {
|
||||
@@ -86,18 +87,16 @@ func SendHttpReq(msg map[string]interface{}) {
|
||||
}
|
||||
|
||||
fmt.Printf("发送数据: %s\n", string(jsonData))
|
||||
if myWechatId == "" {
|
||||
m := new(WechatMessage)
|
||||
err = json.Unmarshal(jsonData, m)
|
||||
if err != nil {
|
||||
log.Printf("解析消息失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
myWechatId = m.SelfID
|
||||
|
||||
if m.GroupId != "" {
|
||||
userID2NicknameMap.Store(m.GroupId+"_"+m.UserID, m.Sender.Nickname)
|
||||
}
|
||||
m := new(WechatMessage)
|
||||
err = json.Unmarshal(jsonData, m)
|
||||
if err != nil {
|
||||
log.Printf("解析消息失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
myWechatId = m.SelfID
|
||||
|
||||
if m.GroupId != "" {
|
||||
userID2NicknameMap.Store(m.GroupId+"_"+m.UserID, m.Sender.Nickname)
|
||||
}
|
||||
|
||||
// 4. 创建 POST 请求
|
||||
|
||||
+15
-8
@@ -33,10 +33,14 @@ var (
|
||||
)
|
||||
|
||||
type WechatMessage struct {
|
||||
GroupId string `json:"group_id"`
|
||||
SelfID string `json:"self_id"`
|
||||
UserID string `json:"user_id"`
|
||||
Sender *Sender `json:"sender"`
|
||||
GroupId string `json:"group_id"`
|
||||
SelfID string `json:"self_id"`
|
||||
UserID string `json:"user_id"`
|
||||
Sender *Sender `json:"sender"`
|
||||
Time int64 `json:"time"`
|
||||
PostType string `json:"post_type"`
|
||||
MessageId string `json:"message_id"`
|
||||
Message []*Message `json:"message"`
|
||||
}
|
||||
|
||||
type Sender struct {
|
||||
@@ -65,10 +69,12 @@ type Message struct {
|
||||
}
|
||||
|
||||
type SendRequestData struct {
|
||||
Id string `json:"id"`
|
||||
Text string `json:"text"`
|
||||
File string `json:"file"`
|
||||
QQ string `json:"qq"`
|
||||
Id string `json:"id"`
|
||||
Text string `json:"text"`
|
||||
File string `json:"file"`
|
||||
URL string `json:"url"`
|
||||
QQ string `json:"qq"`
|
||||
Media []byte `json:"media"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -139,6 +145,7 @@ func initFlag() {
|
||||
fmt.Println("ImagePath", config.ImagePath)
|
||||
fmt.Println("WechatConf", config.WechatConf)
|
||||
|
||||
EnsureDir("./audio")
|
||||
}
|
||||
|
||||
func initFridaGadget() {
|
||||
|
||||
+67
-13
@@ -137,6 +137,13 @@ function getProtobufRawBytes(pBuffer, scanSize) {
|
||||
if (!found) finalResults.push(null); // 未找到该 Tag
|
||||
});
|
||||
|
||||
|
||||
for (; i < uint8Array.length; i++) {
|
||||
if (uint8Array[i] === 0x60 && i + 10 <= uint8Array.length) {
|
||||
finalResults.push(uint8Array.slice(i+1, i+10))
|
||||
}
|
||||
}
|
||||
|
||||
return finalResults;
|
||||
}
|
||||
|
||||
@@ -205,6 +212,29 @@ function getCleanString(uint8Array) {
|
||||
return out;
|
||||
}
|
||||
|
||||
function protobufVarintToNumberString(uint8Array) {
|
||||
let result = BigInt(0);
|
||||
let shift = BigInt(0);
|
||||
|
||||
for (let i = 0; i < uint8Array.length; i++) {
|
||||
const byte = uint8Array[i];
|
||||
|
||||
// 1. 取出低 7 位并累加到结果中
|
||||
// (BigInt(byte & 0x7F) << shift)
|
||||
result += BigInt(byte & 0x7F) << shift;
|
||||
|
||||
// 2. 检查最高位 (MSB)。如果为 0,说明这个数字结束了
|
||||
if ((byte & 0x80) === 0) {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
// 3. 准备处理下一个 7 位
|
||||
shift += BigInt(7);
|
||||
}
|
||||
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
function generateBytes(n) {
|
||||
// 生成随机字符串
|
||||
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
@@ -1105,6 +1135,7 @@ function setReceiver() {
|
||||
const mediaContent = fields[3]
|
||||
const xml = fields[4]
|
||||
const userContent = fields[5]
|
||||
const msgId = protobufVarintToNumberString(fields[6])
|
||||
|
||||
if (sender === "" || receiver === "" || content === "") {
|
||||
console.log("字段缺失,无法解析 sender:" + sender + " receiver:" + receiver + hexdump(currentPtr, {
|
||||
@@ -1119,23 +1150,13 @@ function setReceiver() {
|
||||
var msgType = "private"
|
||||
var groupId = ""
|
||||
var senderUser = sender
|
||||
var messages = [];
|
||||
var senderNickname = ""
|
||||
var messages = getMessages(content, sender, mediaContent);
|
||||
|
||||
if (sender.includes("@chatroom")) {
|
||||
msgType = "group"
|
||||
groupId = sender
|
||||
|
||||
let splitIndex = content.indexOf(':')
|
||||
let pureContent = content.substring(splitIndex + 1).trim();
|
||||
const parts = pureContent.split('\u2005');
|
||||
for (let part of parts) {
|
||||
part = part.trim();
|
||||
if (!part.startsWith("@")) {
|
||||
messages.push({type: "text", data: {text: part}});
|
||||
}
|
||||
}
|
||||
|
||||
const sendUserStart = content.indexOf('wxid_')
|
||||
senderUser = content.substring(sendUserStart, splitIndex).trim();
|
||||
|
||||
@@ -1169,10 +1190,8 @@ function setReceiver() {
|
||||
if (!senderNickname) {
|
||||
senderNickname = sender
|
||||
}
|
||||
messages.push({type: "text", data: {text: content}});
|
||||
}
|
||||
|
||||
const msgId = generateAESKey()
|
||||
send({
|
||||
time: Date.now(),
|
||||
post_type: "message",
|
||||
@@ -1194,6 +1213,41 @@ function setReceiver() {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// 使用 setImmediate 确保在模块加载后执行
|
||||
setImmediate(setReceiver)
|
||||
|
||||
function getMessages(content, sender, mediaContent) {
|
||||
var messages = [];
|
||||
if (sender.includes("@chatroom")) {
|
||||
let splitIndex = content.indexOf(':')
|
||||
let pureContent = content.substring(splitIndex + 1).trim();
|
||||
const parts = pureContent.split('\u2005');
|
||||
for (let part of parts) {
|
||||
part = part.trim();
|
||||
if (part.startsWith("<?xml version=\"1.0\"?><msg><img")) {
|
||||
messages.push({type: "image", data: {text: part}});
|
||||
} else if (part.startsWith("<msg><voicemsg")) {
|
||||
messages.push({type: "record", data: {text: part}});
|
||||
} else {
|
||||
messages.push({type: "text", data: {text: part}});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (content.startsWith("<?xml version=\"1.0\"?><msg><img")) {
|
||||
messages.push({type: "image", data: {text: content}});
|
||||
} else if (content.startsWith("<msg><voicemsg")) {
|
||||
const audioStart = mediaContent.indexOf(2);
|
||||
if (audioStart !== -1) {
|
||||
mediaContent = mediaContent.subarray(audioStart);
|
||||
}
|
||||
messages.push({type: "record", data: {text: content, media: Array.from(mediaContent)}});
|
||||
} else {
|
||||
messages.push({type: "text", data: {text: content}});
|
||||
}
|
||||
}
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
// -------------------------接收消息分区-------------------------
|
||||
@@ -9,9 +9,12 @@ import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/wdvxdr1123/go-silk"
|
||||
)
|
||||
|
||||
func SaveBase64Image(base64Data string) (string, string, error) {
|
||||
@@ -86,3 +89,61 @@ func DetectImageFormat(data []byte) string {
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func EnsureDir(path string) error {
|
||||
_, err := os.Stat(path)
|
||||
if os.IsNotExist(err) {
|
||||
return os.MkdirAll(path, 0755)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func SaveAudioFile(silkBytes []byte) (path string, err error) {
|
||||
mp3Bytes, err := SilkToMp3(silkBytes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
randomNumber := r.Intn(1000)
|
||||
timestamp := time.Now().Unix()
|
||||
fileName := fmt.Sprintf("%d_%d.mp3", randomNumber, timestamp)
|
||||
targetPath := "./audio/" + fileName
|
||||
err = os.WriteFile(targetPath, mp3Bytes, 0644)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return targetPath, nil
|
||||
}
|
||||
|
||||
func SilkToMp3(silkBytes []byte) ([]byte, error) {
|
||||
var pcm, err = silk.DecodeSilkBuffToPcm(silkBytes, 16000)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cmd := exec.Command("ffmpeg",
|
||||
"-f", "s16le",
|
||||
"-ar", "16000",
|
||||
"-ac", "1",
|
||||
"-i", "pipe:0",
|
||||
"-codec:a", "libmp3lame",
|
||||
"-b:a", "192k",
|
||||
"-f", "mp3",
|
||||
"pipe:1",
|
||||
)
|
||||
cmd.Stdin = bytes.NewReader(pcm)
|
||||
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return nil, fmt.Errorf("ffmpeg error: %v, details: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
return out.Bytes(), nil
|
||||
}
|
||||
|
||||
+30
-14
@@ -6,6 +6,7 @@ import (
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -83,11 +84,11 @@ func handleWebSocket(w http.ResponseWriter, r *http.Request) {
|
||||
func SendWebSocketMsg(msg map[string]interface{}) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Printf("panic: %v\n", r)
|
||||
log.Printf("panic: %v, %v\n", r, string(debug.Stack()))
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(time.Duration(config.SendInterval) * time.Second)
|
||||
time.Sleep(time.Duration(config.SendInterval) * time.Millisecond)
|
||||
// 这里处理你的 X1 数据
|
||||
jsonData, err := json.Marshal(msg["payload"])
|
||||
if err != nil {
|
||||
@@ -96,21 +97,36 @@ func SendWebSocketMsg(msg map[string]interface{}) {
|
||||
}
|
||||
|
||||
fmt.Printf("发送数据: %s\n", string(jsonData))
|
||||
if myWechatId == "" {
|
||||
m := new(WechatMessage)
|
||||
err = json.Unmarshal(jsonData, m)
|
||||
if err != nil {
|
||||
log.Printf("解析消息失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
myWechatId = m.SelfID
|
||||
|
||||
if m.GroupId != "" {
|
||||
userID2NicknameMap.Store(m.GroupId+"_"+m.UserID, m.Sender.Nickname)
|
||||
m := new(WechatMessage)
|
||||
err = json.Unmarshal(jsonData, m)
|
||||
if err != nil {
|
||||
log.Printf("解析消息失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
myWechatId = m.SelfID
|
||||
if m.GroupId != "" {
|
||||
userID2NicknameMap.Store(m.GroupId+"_"+m.UserID, m.Sender.Nickname)
|
||||
}
|
||||
|
||||
for _, msg := range m.Message {
|
||||
if msg.Type == "record" {
|
||||
path, err := SaveAudioFile(msg.Data.Media)
|
||||
if err != nil {
|
||||
log.Printf("保存音频失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
msg.Data.URL = path
|
||||
msg.Data.Media = nil
|
||||
}
|
||||
}
|
||||
|
||||
err = conn.WriteMessage(websocket.TextMessage, jsonData)
|
||||
jsonReq, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
log.Printf("JSON 序列化失败: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = conn.WriteMessage(websocket.TextMessage, jsonReq)
|
||||
if err != nil {
|
||||
log.Printf("发送消息失败: %v\n", err)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user