feat: 3.9.8.25

This commit is contained in:
ttttupup 2023-12-20 17:56:37 +08:00
parent 514fd1dff4
commit f95c36c5d5
83 changed files with 608 additions and 6713 deletions

203
README.md
View File

@ -1,5 +1,5 @@
# wxhelper
wechat hook 。PC端微信逆向学习。支持3.8.0.413.8.1.26, 3.9.0.28, 3.9.2.23,3.9.2.26版本。
wechat hook 。PC端微信逆向学习。
#### 免责声明:
本仓库发布的内容,仅用于学习研究,请勿用于非法用途和商业用途!如因此产生任何法律纠纷,均与作者无关!
@ -18,26 +18,23 @@ dll在注入成功时创建了一个默认端口为19088的http服务端
|-------------------------- |----------------- -------------- --------
```
#### 使用说明:
支持的版本3.8.0.41、3.8.1.26、3.9.0.28、3.9.2.23、3.9.2.26 、3.9.5.81。
源码和主要实现在相应的分支内。
src:主要的dll代码
tool简单的注入工具一个是控制台一个是图形界面。
python: tcpserver.py: 简单的服务器用以接收消息内容。decrpty.py: 微信数据库解密工具。 http_server.pyhttp server端。
source: 简单的命令行远程注入源码。
其他目录:热心作者提供的一些客户端。
#### 快速开始:
#### 0.首先安装对应的版本的微信分支名称即代表的是微信对应的版本。dll的前缀都会带有微信版本。
#### 1.使用注入工具注入wxhelper.dll,注入成功后即可通过postman直接调用对应的接口。
#### 2.可以使用python/clent.py进行简单测试。
1.使用注入工具将wxhelper.dll注入到WeChat中。
2.执行
```
curl --location --request POST '127.0.0.1:19088/api/checkLogin'
```
即可检查登录状态。
如果已经登录则执行获取登录用户信息
```
curl --location --request POST '127.0.0.1:19088/api/userInfo'
```
3.其他更多接口参考相关文档。
##### 特别注意:
##### 1.hook相关的接口都需要先调用对应的hook接口server端才会收到相应消息。
##### 2.注意个别接口在一些版本没有实现,功能预览里没有的功能就是没有实现。
##### 3.如果注入不成功,请先检查注入工具,或者使用其他注入工具。
##### 4.相关功能只在win11环境下进行简单测试其他环境无法保证。
#### 参与项目
@ -54,7 +51,7 @@ source: 简单的命令行远程注入源码。
#### 编译环境
Visual Studio 2022(x86)
Visual Studio 2022
Visual Studio code
@ -63,8 +60,6 @@ cmake
vcpkg
#### 编译构建
先准备好编译环境。
#### <font color= "#dd0000"> 以下是x86环境构建3.9.5.81是x64环境具体参考对应分支。</font>
```
cd wxhelper
mkdir build
@ -72,173 +67,11 @@ cd build
cmake -DCMAKE_C_COMPILER=cl.exe \
-DCMAKE_CXX_COMPILER=cl.exe \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_INSTALL_PREFIX=C:/other/codeSource/windows/wxhelper/out/install/x86-debug \
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=C:/vcpkg/scripts/buildsystems/vcpkg.cmake \
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=${vcpkg install dir}/scripts/buildsystems/vcpkg.cmake \
-SC:/wxhelper \
-BC:/wxhelper/build/x86-debug\
-BC:/wxhelper/build/x64-debug\
-G Ninja
cmake --build ..
```
以下是在vscode中操作vs中的操作类似。
1.安装vcpkgcmakevscode
2.安装相应的库如果安装的版本不同则根据vcpkg安装成功后提示的find_package修改CMakeLists.txt内容即可。或者自己编译。
```
vcpkg install mongoose
vcpkg install nlohmann-json
```
3.vscode 配置CMakePresets.json,主要设置CMAKE_C_COMPILER 和CMAKE_CXX_COMPILER 为cl.exe.参考如下
```
{
"name": "x86-release",
"displayName": "x86-release",
"description": "Sets Ninja generator, build and install directory",
"generator": "Ninja",
"binaryDir": "${sourceDir}/out/build/${presetName}",
"architecture":{
"value": "x86",
"strategy": "external"
},
"cacheVariables": {
"CMAKE_C_COMPILER": "cl.exe",
"CMAKE_CXX_COMPILER": "cl.exe",
"CMAKE_BUILD_TYPE": "Release",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/out/install/${presetName}",
"CMAKE_TOOLCHAIN_FILE": {
"value": "C:/soft/vcpkg/scripts/buildsystems/vcpkg.cmake",
"type": "FILEPATH"
}
},
"environment": {
}
}
```
4.执行cmake build vscode中右键configure all projects,在Terminal中点击Run Task如没有先配置build任务然后运行即可
5.命令行注入工具,注入命令
``` javascript
//-i 注入程序名 -p 注入dll路径
// -u 卸载程序名 -d 卸载dll名称
// -m pid 关闭微信互斥体,多开微信
// -P port 指定http端口需要使用 specify-port 分支的生成的dll
// -I 注入程序的pid
//注入
ConsoleInject.exe -i demo.exe -p E:\testInject.dll
//卸载
ConsoleInject.exe -u demo.exe -d testInject.dll
//多开
ConsoleInject.exe -m 1222
// 注入并指定http端口
ConsoleInject.exe -i demo.exe -p E:\testInject.dll -P 18888
// 注入指定pid并关闭多开限制
ConsoleInject.exe -I 15048 -p E:\testInject.dll -m 15048
```
6.如果想改变端口可以在微信目录下创建config.ini配置文件,修改端口即可。不创建则默认端口19088。
``` shell
[config]
port=19099
```
#### 更新说明
2022-12-26 增加3.8.1.26版本支持。
2022-12-29 新增提取文字功能。
2023-01-02 退出微信登录。
2023-01-31 新增修改群昵称仅支持3.8.1.26)。
2023-02-01 新增拍一拍仅支持3.8.1.26)。
2023-02-04 新增群消息置顶和取消置顶。
2023-02-06 新增确认收款。
2023-02-08 新增朋友圈消息。
2023-02-09 新增3.9.0.28版本基础功能。
2023-02-13 新增群昵称和微信名称。
2023-02-17 : 新增通过wxid添加好友,搜索查找微信。
2023-03-02 新增发送@消息
2023-03-04 新增消息附件下载
2023-03-21 新增hook语音
2023-03-30 新增获取语音文件(推荐使用这个非hook接口)
2023-04-08 : 3.9.2.23版本功能更新
2023-06-05 3.9.2.26版本更新
2023-07-07 3.9.5.81版本更新
#### 功能预览:
0.检查是否登录
1.获取登录微信信息
2.发送文本
3.发送@文本
5.发送图片
6.发送文件
9.hook消息
10.取消hook消息
11.hook图片
12.取消hook图片
13.hook语音
14.取消hook语音
17.删除好友
19.通过手机或qq查找微信
20.通过wxid添加好友
23.通过好友申请
25.获取群成员
26.获取群成员昵称
27.删除群成员
28.增加群成员
31.修改群昵称
32.获取数据库句柄
34.查询数据库
35.hook日志
36.取消hook日志
40.转发消息
44.退出登录
45.确认收款
46.联系人列表
47.获取群详情
48.获取解密图片
49.图片提取文字ocr
50.拍一拍
51.群消息置顶消息
52.群消息取消置顶
53.朋友圈首页
54.朋友圈下一页
55.获取联系人或者群名称
56.获取消息附件(图片,视频,文件)
57.获取语音文件(silk3格式)
58.登录二维码
59.邀请入群
60.获取群/群成员详情
61.撤回消息
62.发送公众号消息
63.转发公众号消息
64.发送小程序
65.退款
66.下载头像(勿用,没什么用)
#### 感谢
https://github.com/ljc545w/ComWeChatRobot
https://github.com/NationalSecurityAgency/ghidra
https://github.com/x64dbg/x64dbg
#### 讨论组
https://t.me/+LmvAauweyUpjYzJl

View File

@ -3,7 +3,7 @@
#include "base64.h"
#include "spdlog/spdlog.h"
#include "utils.h"
namespace offset = wxhelper::V3_9_7_29::offset;
namespace offset = wxhelper::V3_9_8_25::offset;
namespace wxhelper {
void FreeResult(std::vector<std::vector<common::SqlResult>> &data) {

View File

@ -32,6 +32,8 @@ void GlobalManager::initialize(HMODULE module) {
http_server->AddHttpApiUrl("/api/userInfo", GetSelfInfo);
http_server->AddHttpApiUrl("/api/getDBInfo", GetDBInfo);
http_server->AddHttpApiUrl("/api/execSql", ExecSql);
http_server->AddHttpApiUrl("/api/lockWeChat", LockWeChat);
http_server->AddHttpApiUrl("/api/unlockWeChat", UnLockWeChat);
http_server->Start();
base::ThreadPool::GetInstance().Create(2, 8);

View File

@ -135,6 +135,8 @@ std::string GetSelfInfo(mg_http_message* hm) {
{"dataSavePath", self_info.data_save_path},
{"currentDataPath", self_info.current_data_path},
{"dbKey", self_info.db_key},
{"publicKey", self_info.public_key},
{"privateKey", self_info.private_key},
};
ret_data["data"] = j_info;
}
@ -190,4 +192,18 @@ std::string ExecSql(mg_http_message* hm) {
return ret_data.dump();
}
std::string LockWeChat(struct mg_http_message* hm) {
INT64 success = WechatService::GetInstance().LockWeChat();
nlohmann::json ret_data = {
{"code", success}, {"data", {}}, {"msg", "success"}};
return ret_data.dump();
}
std::string UnLockWeChat(struct mg_http_message* hm) {
INT64 success = WechatService::GetInstance().UnLockWeChat();
nlohmann::json ret_data = {
{"code", success}, {"data", {}}, {"msg", "success"}};
return ret_data.dump();
}
} // namespace wxhelper

View File

@ -12,6 +12,8 @@ std::string CheckLogin(struct mg_http_message *hm);
std::string GetSelfInfo(struct mg_http_message *hm);
std::string GetDBInfo(struct mg_http_message *hm);
std::string ExecSql(struct mg_http_message *hm);
std::string LockWeChat(struct mg_http_message *hm);
std::string UnLockWeChat(struct mg_http_message *hm);
} // namespace wxhelper
#endif

View File

@ -131,6 +131,8 @@ struct SelfInfoInner {
std::string signature;
std::string current_data_path;
std::string db_key;
std::string public_key;
std::string private_key;
};
struct ContactInner {
@ -205,7 +207,7 @@ struct ContactProfileInner {
};
} // namespace common
namespace V3_9_5_81 {
namespace V3_9_8_25 {
namespace function {
typedef UINT64 (*__GetAccountService)();
@ -284,6 +286,11 @@ typedef UINT64 (*__SendPatMsg)(UINT64, UINT64);
typedef UINT64 (*__GetOCRManager)();
typedef UINT64 (*__DoOCRTask)(UINT64, UINT64, UINT64, UINT64, UINT64);
typedef UINT64 (*__GetLockWechatMgr)();
typedef UINT64 (*__RequestLockWechat)(UINT64);
typedef UINT64 (*__RequestUnLockWechat)(UINT64);
} // namespace function
namespace prototype {
@ -343,51 +350,48 @@ struct WeChatStr {
} // namespace prototype
namespace offset {
const UINT64 kGetAccountServiceMgr = 0x8c1230;
const UINT64 kGetAccountServiceMgr = 0x94e510;
const UINT64 kSyncMsg = 0xc39680;
const UINT64 kSyncMsgNext = 0xc39680;
const UINT64 kGetCurrentDataPath = 0xf5d130;
const UINT64 kGetAppDataSavePath = 0x12d7040;
const UINT64 kGetSendMessageMgr = 0x8c00e0;
const UINT64 kSendTextMsg = 0xfcd8d0;
const UINT64 kFreeChatMsg = 0x8aaa00;
const UINT64 kGetCurrentDataPath = 0x101a920;
const UINT64 kGetAppDataSavePath = 0x13a5b90;
const UINT64 kGetSendMessageMgr = 0x94cd10;
const UINT64 kSendTextMsg = 0x1091F70;
const UINT64 kFreeChatMsg = 0x94e590;
const UINT64 kDoAddMsg = 0x1010d80;
const UINT64 kDoAddMsg = 0x10d9450;
const UINT64 kSendImageMsg = 0xfc3d30;
const UINT64 kChatMsgInstanceCounter = 0x8c7fd0;
const UINT64 kSendFileMsg = 0xdd27f0;
const UINT64 kGetAppMsgMgr = 0x8c33f0;
const UINT64 kGetContactMgr = 0x8ae3d0;
const UINT64 kGetContactList = 0xeab270;
const UINT64 kGetContactMgr = 0x93a570;
const UINT64 kGetContactList = 0xf6cb70;
const UINT64 k_sqlite3_exec = 0x252e340;
const UINT64 k_sqlite3_prepare = 0x2535eb0;
const UINT64 k_sqlite3_open = 0x256d6b0;
const UINT64 k_sqlite3_backup_init = 0x24e8450;
const UINT64 k_sqlite3_errcode = 0x256bfb0;
const UINT64 k_sqlite3_close = 0x256a110;
const UINT64 k_sqlite3_step = 0x24f2350;
const UINT64 k_sqlite3_column_count = 0x24f2b70;
const UINT64 k_sqlite3_column_name = 0x24f3570;
const UINT64 k_sqlite3_column_type = 0x24f33c0;
const UINT64 k_sqlite3_column_blob = 0x24f2ba0;
const UINT64 k_sqlite3_column_bytes = 0x24f2c90;
const UINT64 k_sqlite3_finalize = 0x24f1400;
const UINT64 k_sqlite3_exec = 0x26e4f20;
const UINT64 k_sqlite3_prepare = 0x26ecaa0;
const UINT64 k_sqlite3_open = 0x27242a0;
const UINT64 k_sqlite3_step = 0x26a8f30;
const UINT64 k_sqlite3_column_count = 0x26a9750;
const UINT64 k_sqlite3_column_name = 0x26aa150;
const UINT64 k_sqlite3_column_type = 0x26a9fa0;
const UINT64 k_sqlite3_column_blob = 0x26a9780;
const UINT64 k_sqlite3_column_bytes = 0x26a9870;
const UINT64 k_sqlite3_finalize = 0x26a7fe0;
const UINT64 kGPInstance = 0x3a6f908;
const UINT64 kGPInstance = 0x3d8b4f8;
const UINT64 kMicroMsgDB = 0xb8;
const UINT64 kChatMsgDB = 0x2c8;
const UINT64 kMiscDB = 0x5f0;
const UINT64 kEmotionDB = 0x838;
const UINT64 kMediaDB = 0xef8;
const UINT64 kBizchatMsgDB = 0x1a70;
const UINT64 kFunctionMsgDB = 0x1b48;
const UINT64 kEmotionDB = 0x888;
const UINT64 kMediaDB = 0xF48;
const UINT64 kBizchatMsgDB = 0x1AC0;
const UINT64 kFunctionMsgDB = 0x1b98;
const UINT64 kDBName = 0x28;
const UINT64 kStorageStart = 0x0;
const UINT64 kStorageEnd = 0x0;
const UINT64 kMultiDBMgr = 0x3acfb68;
const UINT64 kPublicMsgMgr = 0x3acc268;
const UINT64 kFavoriteStorageMgr = 0x3acf0d0;
const UINT64 kMultiDBMgr = 0x3e00910;
const UINT64 kPublicMsgMgr = 0x3dfe098;
const UINT64 kFavoriteStorageMgr = 0x3e01478;
const UINT64 kChatRoomMgr = 0x8e9d30;
const UINT64 kGetChatRoomDetailInfo = 0xe73590;
@ -444,99 +448,13 @@ const UINT64 kSendPatMsg = 0x195f340;
const UINT64 kGetOCRManager = 0x999780;
const UINT64 kDoOCRTask = 0x190b2a0;
} // namespace offset
} // namespace V3_9_5_81
namespace V3_9_7_29 {
namespace prototype {
struct WeChatString {
wchar_t *ptr;
DWORD length;
DWORD max_length;
INT64 c_ptr = 0;
DWORD c_len = 0;
WeChatString() { WeChatString(NULL); }
WeChatString(const std::wstring &s) {
ptr = (wchar_t *)(s.c_str());
length = static_cast<DWORD>(s.length());
max_length = static_cast<DWORD>(s.length());
}
WeChatString(const wchar_t *pStr) { WeChatString((wchar_t *)pStr); }
WeChatString(int tmp) {
ptr = NULL;
length = 0x0;
max_length = 0x0;
}
WeChatString(wchar_t *pStr) {
ptr = pStr;
length = static_cast<DWORD>(wcslen(pStr));
max_length = static_cast<DWORD>(wcslen(pStr));
}
void set_value(const wchar_t *pStr) {
ptr = (wchar_t *)pStr;
length = static_cast<DWORD>(wcslen(pStr));
max_length = static_cast<DWORD>(wcslen(pStr) * 2);
}
};
} // namespace prototype
namespace offset {
const UINT64 k_sqlite3_exec = 0x2654db0;
const UINT64 k_sqlite3_prepare = 0x265c920;
const UINT64 k_sqlite3_open = 0x2694120;
const UINT64 k_sqlite3_backup_init = 0x260eec0;
const UINT64 k_sqlite3_errcode = 0x2692a20;
const UINT64 k_sqlite3_close = 0x2690b80;
const UINT64 k_sqlite3_step = 0x2618dc0;
const UINT64 k_sqlite3_column_count = 0x26195e0;
const UINT64 k_sqlite3_column_name = 0x2619fe0;
const UINT64 k_sqlite3_column_type = 0x2619e30;
const UINT64 k_sqlite3_column_blob = 0x2619610;
const UINT64 k_sqlite3_column_bytes = 0x2619700;
const UINT64 k_sqlite3_finalize = 0x2617e70;
const UINT64 kGPInstance = 0x3c19fe8;
const UINT64 kMicroMsgDB = 0xb8;
const UINT64 kChatMsgDB = 0x2c8;
const UINT64 kMiscDB = 0x5f0;
const UINT64 kEmotionDB = 0x888;
const UINT64 kMediaDB = 0xf48;
const UINT64 kBizchatMsgDB = 0x1ac0;
const UINT64 kFunctionMsgDB = 0x1b98;
const UINT64 kDBName = 0x28;
const UINT64 kStorageStart = 0x0;
const UINT64 kStorageEnd = 0x0;
const UINT64 kMultiDBMgr = 0x3c8ef40;
const UINT64 kPublicMsgMgr = 0x3c8c6c8;
const UINT64 kFavoriteStorageMgr = 0x3c8fac0;
const UINT64 kGetSendMessageMgr = 0x8fe740;
const UINT64 kFreeChatMsg = 0x8fffc0;
const UINT64 kSendTextMsg = 0x1024370;
const UINT64 kDoAddMsg = 0x106b810;
const UINT64 kGetContactMgr = 0x8ebfb0;
const UINT64 kGetContactList = 0xeff050;
const UINT64 kGetAccountServiceMgr = 0x8fff40;
const UINT64 kGetAppDataSavePath = 0x1336c60;
const UINT64 kGetCurrentDataPath = 0xfacb50;
const UINT64 kGetLockWechatMgr = 0xa727b0;
const UINT64 kRequestLockWechat = 0xa2cc70;
const UINT64 kRequestUnLockWechat = 0xa2cf10;
} // namespace offset
namespace function {
typedef UINT64 (*__GetSendMessageMgr)();
typedef UINT64 (*__SendTextMsg)(UINT64, UINT64, UINT64, UINT64, UINT64, UINT64,
UINT64, UINT64);
typedef UINT64 (*__FreeChatMsg)(UINT64);
typedef UINT64 (*__GetContactMgr)();
typedef UINT64 (*__GetContactList)(UINT64, UINT64);
typedef UINT64(*__GetAccountService)();
typedef UINT64 (*__GetDataSavePath)(UINT64);
typedef UINT64 (*__GetCurrentDataPath)(UINT64);
} // namespace function
} // namespace V3_9_7_29
} // namespace V3_9_8_15
} // namespace wxhelper
#endif

View File

@ -12,7 +12,7 @@
#include "spdlog/spdlog.h"
#include "thread_pool.h"
#include "wxutils.h"
namespace offset = wxhelper::V3_9_7_29::offset;
namespace offset = wxhelper::V3_9_8_25::offset;
namespace common = wxhelper::common;
namespace hook {

View File

@ -1,9 +1,9 @@
#include "wechat_service.h"
#include "wxutils.h"
#include "utils.h"
namespace offset = wxhelper::V3_9_7_29::offset;
namespace prototype = wxhelper::V3_9_7_29::prototype;
namespace func = wxhelper::V3_9_7_29::function;
namespace offset = wxhelper::V3_9_8_25::offset;
namespace prototype = wxhelper::V3_9_8_25::prototype;
namespace func = wxhelper::V3_9_8_25::function;
namespace wxhelper {
WechatService::~WechatService() {}
@ -142,6 +142,22 @@ INT64 WechatService::GetSelfInfo(common::SelfInfoInner& out) {
*(INT64 *)(service_addr + 0x450 + 0x10));
}
if (*(INT64 *)(service_addr + 0x7B8) == 0 ||
*(INT64 *)(service_addr + 0x7B8 + 0x10) == 0) {
out.public_key = std::string();
} else {
out.public_key = std::string(*(char **)(service_addr + 0x7B8),
*(INT64 *)(service_addr + 0x7B8 + 0x10));
}
if (*(INT64 *)(service_addr + 0x7D8) == 0 ||
*(INT64 *)(service_addr + 0x7D8 + 0x10) == 0) {
out.private_key = std::string();
} else {
out.private_key = std::string(*(char **)(service_addr + 0x7D8),
*(INT64 *)(service_addr + 0x7D8 + 0x10));
}
if (*(INT64 *)(service_addr + 0x6E0) == 0 ||
*(INT64 *)(service_addr + 0x6E8) == 0) {
out.db_key = std::string();
@ -375,6 +391,30 @@ INT64 WechatService::DoOCRTask(const std::wstring& img_path,
return INT64();
}
INT64 WechatService::LockWeChat() {
INT64 success = -1;
UINT64 lock_mgr_addr = base_addr_ + offset::kGetLockWechatMgr;
UINT64 request_lock_addr = base_addr_ + offset::kRequestLockWechat;
func::__GetLockWechatMgr GetLockMgr = (func::__GetLockWechatMgr)lock_mgr_addr;
func::__RequestLockWechat request_lock =
(func::__RequestLockWechat)request_lock_addr;
UINT64 mgr = GetLockMgr();
success = request_lock(mgr);
return success;
}
INT64 WechatService::UnLockWeChat() {
INT64 success = -1;
UINT64 lock_mgr_addr = base_addr_ + offset::kGetLockWechatMgr;
UINT64 request_unlock_addr = base_addr_ + offset::kRequestUnLockWechat;
func::__GetLockWechatMgr GetLockMgr = (func::__GetLockWechatMgr)lock_mgr_addr;
func::__RequestUnLockWechat request_unlock =
(func::__RequestUnLockWechat)request_unlock_addr;
UINT64 mgr = GetLockMgr();
success = request_unlock(mgr);
return success;
}
void WechatService::SetBaseAddr(UINT64 addr) { base_addr_ = addr; }
void WechatService::SetJsApiAddr(UINT64 addr) { js_api_addr_ = addr; }

View File

@ -70,6 +70,8 @@ class WechatService : public base::Singleton<WechatService> {
const std::string& index_page);
INT64 SendPatMsg(const std::wstring& room_id, const std::wstring& wxid);
INT64 DoOCRTask(const std::wstring& img_path, std::string& result);
INT64 LockWeChat();
INT64 UnLockWeChat();
void SetBaseAddr(UINT64 addr);
void SetJsApiAddr(UINT64 addr);

View File

@ -132,7 +132,7 @@ data 接口返回的数据
"dataSavePath": "C:\\wechatDir\\WeChat Files\\",
"dbKey": "965715e30e474da09250cb5aa047e3940ffa1c8f767c4263b132bb512933db49",
"headImage": "https://wx.qlogo.cn/mmhead/ver_1/MiblV0loY0GILewQ4u2121",
"mobile": "13949175447",
"mobile": "13913913913",
"name": "xxx",
"province": "Henan",
"signature": "xxx",

View File

@ -116,7 +116,7 @@ HiddenDll=0 //1隐藏 0不隐藏
"dataSavePath": "C:\\wechatDir\\WeChat Files\\",
"dbKey": "965715e30e474da09250cb5aa047e3940ffa1c8f767c4263b132bb512933db49",
"headImage": "https://wx.qlogo.cn/mmhead/ver_1/MiblV0loY0GILewQ4u2121",
"mobile": "13949175447",
"mobile": "13913913913",
"name": "xxx",
"province": "Henan",
"signature": "xxx",

481
doc/3.9.8.25.md Normal file
View File

@ -0,0 +1,481 @@
#### 编译构建
环境:
cl.exe目录= c:/cl.exe
ml64.exe目录 =c:/ml64.exe
vcpkg目录 = c:/vcpkg
wxhelper目录 = c:/wxhelper
```
vcpkg install detours:x64-windows
vcpkg install nlohmann-json:x64-windows
cd wxhelper
mkdir build
cd build
cmake -DCMAKE_C_COMPILER=cl.exe \
-DCMAKE_CXX_COMPILER=cl.exe \
-DCMAKE_ASM_MASM_COMPILER=ml64.exe \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_INSTALL_PREFIX=C:/wxhelper/install/x64-debug \
-DCMAKE_TOOLCHAIN_FILE:FILEPATH=C:/vcpkg/scripts/buildsystems/vcpkg.cmake \
-SC:c:/wxhelper \
-BC:c:/wxhelper/build/x64-debug\
-G Ninja
cmake --build ..
```
如果有错误按错误提示修正即可。
## 3.9.5.81版本http接口文档文档仅供参考。
### 简单说明:
所有接口只支持post方法。
全部使用json格式。
格式: http://host:port/api/xxxx
host: 绑定的host
port: 监听的端口
xxxx: 对应的功能路径
返回结构的json格式
``` javascript
{
"code": 1,
"data": {},
"msg": "success"
}
```
code 错误码
msg 成功/错误信息
data 接口返回的数据
#### 0.检查微信登录**
###### 接口功能
> 检查微信是否登录
###### 接口地址
> [/api/checkLogin](/api/checkLogin)
###### HTTP请求方式
> POST JSON
###### 请求参数
|参数|必选|类型|说明|
|---|---|---|---|
###### 返回字段
|返回字段|字段类型|说明 |
|---|---|---|
|code|int|返回状态,1 成功, 0失败|
|result|string|成功提示|
|data|string|响应内容|
###### 接口示例
入参:
``` javascript
```
响应:
``` javascript
{
"code": 1,
"msg": "success",
"data":null
}
```
#### 1.获取登录用户信息**
###### 接口功能
> 获取登录用户信息
###### 接口地址
> [/api/userInfo](/api/userInfo)
###### HTTP请求方式
> POST JSON
###### 请求参数
|参数|必选|类型|说明|
|---|---|---|---|
###### 返回字段
|返回字段|字段类型|说明 |
|---|---|---|
|code|int|返回状态,1 成功, 0失败|
|result|string|成功提示|
|data|object|响应内容|
|&#8194;&#8194;account|string|账号|
|&#8194;&#8194;headImage|string|头像|
|&#8194;&#8194;city|string|城市|
|&#8194;&#8194;country|string|国家|
|&#8194;&#8194;currentDataPath|string|当前数据目录,登录的账号目录|
|&#8194;&#8194;dataSavePath|string|微信保存目录|
|&#8194;&#8194;mobile|string|手机|
|&#8194;&#8194;name|string|昵称|
|&#8194;&#8194;province|string|省|
|&#8194;&#8194;wxid|string|wxid|
|&#8194;&#8194;signature|string|个人签名|
|&#8194;&#8194;dbKey|string|数据库的SQLCipher的加密key可以使用该key配合decrypt.py解密数据库
###### 接口示例
入参:
``` javascript
```
响应:
``` javascript
{
"code": 1,
"data": {
"account": "xxx",
"city": "Zhengzhou",
"country": "CN",
"currentDataPath": "C:\\WeChat Files\\wxid_xxx\\",
"dataSavePath": "C:\\wechatDir\\WeChat Files\\",
"dbKey": "965715e30e474da09250cb5aa047e3940ffa1c8f767c4263b132bb512933db49",
"headImage": "https://wx.qlogo.cn/mmhead/ver_1/MiblV0loY0GILewQ4u2121",
"mobile": "13912341234",
"name": "xxx",
"province": "Henan",
"signature": "xxx",
"wxid": "wxid_22222",
"privateKey":"-----BEGIN RSA PRIVATE KEY-----\nMIICXAIBAAKBgQCsC8wKKfylbnl0QpcowI4XDoCMlDptEeVq1aY0w9nR62llfjVL\nKIDbHMf9+tCxv5MWBuxrZgldzLkSQ/M5XwL5HQrO+XTj9Sx/ -END RSA PRIVATE KEY-----\n",
"publicKey":"-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQAB\n-----END PUBLIC KEY-----\n"
},
"msg": "success"
}
```
#### 2.发送文本消息**
###### 接口功能
> 发送文本消息
###### 接口地址
> [/api/sendTextMsg](/api/sendTextMsg)
###### HTTP请求方式
> POST JSON
###### 请求参数
|参数|必选|类型|说明|
|---|---|---|---|
|wxid |true |string| 接收人wxid |
|msg|true |string|消息文本内容|
###### 返回字段
|返回字段|字段类型|说明 |
|---|---|---|
|code|int|返回状态,不为0成功, 0失败|
|msg|string|成功提示|
|data|object|null|
###### 接口示例
入参:
``` javascript
{
"wxid": "filehelper",
"msg": "1112222"
}
```
响应:
``` javascript
{"code":345686720,"msg":"success","data":null}
```
#### 3.hook消息**
###### 接口功能
> hook接收文本消息图片消息群消息.该接口将hook的消息通过tcp回传给本地的端口。
enableHttp=1时使用urltimeout参数配置服务端的接收地址。请求为postContent-Type 为json。
enableHttp=0时使用ipport的tcp服务回传消息。
###### 接口地址
> [/api/hookSyncMsg](/api/hookSyncMsg)
###### HTTP请求方式
> POST JSON
###### 请求参数
|参数|必选|类型|说明|
|---|---|---|---|
|port |true |string| 本地服务端端口,用来接收消息内容 |
|ip |true |string| 服务端ip地址用来接收消息内容可以是任意ip,即tcp客户端连接的服务端的ip|
|url |true |string| http的请求地址enableHttp=1时不能为空 |
|timeout |true |string| 超时时间单位ms|
|enableHttp |true |bool| true/false true.启用http false.不启用http|
###### 返回字段
|返回字段|字段类型|说明 |
|---|---|---|
|code|int|返回状态,0成功, 非0失败|
|data|object|null|
|msg|string|成功提示|
###### 接口示例
入参:
``` javascript
{
"port": "19099",
"ip":"127.0.0.1",
"url":"http://localhost:8080",
"timeout":"3000",
"enableHttp":false
}
```
响应:
``` javascript
{"code":0,"msg":"success","data":null}
```
#### 4.取消hook消息**
###### 接口功能
> 取消hook消息
###### 接口地址
> [/api/unhookSyncMsg](/api/unhookSyncMsg)
###### HTTP请求方式
> POST JSON
###### 请求参数
|参数|必选|类型|说明|
|---|---|---|---|
###### 返回字段
|返回字段|字段类型|说明 |
|---|---|---|
|code|int|返回状态,0成功, 非0失败|
|data|object|null|
|msg|string|成功提示|
###### 接口示例
入参:
``` javascript
```
响应:
``` javascript
{"code":0,"msg":"success","data":null}
```
#### 5.好友列表**
###### 接口功能
> 好友列表
###### 接口地址
> [/api/getContactList](/api/getContactList)
###### HTTP请求方式
> POST JSON
###### 请求参数
|参数|必选|类型|说明|
|---|---|---|---|
###### 返回字段
|返回字段|字段类型|说明 |
|---|---|---|
|code|int|返回状态,0成功, 非0失败|
|data|object|好友信息|
|&#8194;&#8194;customAccount|string|自定义账号|
|&#8194;&#8194;encryptName|string|昵称|
|&#8194;&#8194;nickname|string|昵称|
|&#8194;&#8194;pinyin|string|简拼|
|&#8194;&#8194;pinyinAll|string|全拼|
|&#8194;&#8194;reserved1|number|未知|
|&#8194;&#8194;reserved2|number|未知|
|&#8194;&#8194;type|number|未知|
|&#8194;&#8194;verifyFlag|number|未知|
|&#8194;&#8194;wxid|string|wxid|
|msg|string|成功提示|
###### 接口示例
入参:
``` javascript
```
响应:
``` javascript
{
"code": 1,
"data": [
{
"customAccount": "",
"encryptName": "v3_020b3826fd03010000000000e04128fddf4d90000000501ea9a3dba12f95f6b60a0536a1adb6b40fc4086288f46c0b89e6c4eb8062bb1661b4b6fbab708dc4f89d543d7ade135b2be74c14b9cfe3accef377b9@stranger",
"nickname": "文件传输助手",
"pinyin": "WJCSZS",
"pinyinAll": "wenjianchuanshuzhushou",
"reserved1": 1,
"reserved2": 1,
"type": 3,
"verifyFlag": 0,
"wxid": "filehelper"
}
].
"msg": "success"
```
#### 6.获取数据库信息**
###### 接口功能
> 获取数据库信息和句柄
###### 接口地址
> [/api/getDBInfo](/api/getDBInfo)
###### HTTP请求方式
> POST JSON
###### 请求参数
|参数|必选|类型|说明|
|---|---|---|---|
###### 返回字段
|返回字段|字段类型|说明 |
|---|---|---|
|code|int|返回状态,0成功, 非0失败|
|msg|string|返回信息|
|data|array|好友信息|
|&#8194;&#8194;databaseName|string|数据库名称|
|&#8194;&#8194;handle|number|句柄|
|&#8194;&#8194;tables|array|表信息|
|&#8194;&#8194;&#8194;&#8194;name|string|表名|
|&#8194;&#8194;&#8194;&#8194;rootpage|string|rootpage|
|&#8194;&#8194;&#8194;&#8194;sql|string|ddl语句|
|&#8194;&#8194;&#8194;&#8194;tableName|string|表名|
###### 接口示例
入参:
``` javascript
```
响应:
``` javascript
{
"code": 1,
"data": [
{
"databaseName": "MicroMsg.db",
"handle": 1755003930784,
"tables": [
{
"name": "Contact",
"rootpage": "2",
"sql": "CREATE TABLE Contact(UserName TEXT PRIMARY KEY ,Alias TEXT,EncryptUserName TEXT,DelFlag INTEGER DEFAULT 0,Type INTEGER DEFAULT 0,VerifyFlag INTEGER DEFAULT 0,Reserved1 INTEGER DEFAULT 0,Reserved2 INTEGER DEFAULT 0,Reserved3 TEXT,Reserved4 TEXT,Remark TEXT,NickName TEXT,LabelIDList TEXT,DomainList TEXT,ChatRoomType int,PYInitial TEXT,QuanPin TEXT,RemarkPYInitial TEXT,RemarkQuanPin TEXT,BigHeadImgUrl TEXT,SmallHeadImgUrl TEXT,HeadImgMd5 TEXT,ChatRoomNotify INTEGER DEFAULT 0,Reserved5 INTEGER DEFAULT 0,Reserved6 TEXT,Reserved7 TEXT,ExtraBuf BLOB,Reserved8 INTEGER DEFAULT 0,Reserved9 INTEGER DEFAULT 0,Reserved10 TEXT,Reserved11 TEXT)",
"tableName": "Contact"
}
]
}
],
"msg":"success"
}
```
#### 7.查询数据库**
###### 接口功能
> 查询数据库
###### 接口地址
> [/api/execSql](/api/execSql)
###### HTTP请求方式
> POST JSON
###### 请求参数
|参数|必选|类型|说明|
|---|---|---|---|
|dbHandle |true |number| |
|sql |true |string| 执行的sql |
###### 返回字段
|返回字段|字段类型|说明 |
|---|---|---|
|code|int|返回状态,0成功, 非0失败|
|msg|string|返回信息|
|data|array|sqlite返回的结果|
###### 接口示例
入参:
``` javascript
{
"dbHandle":2006119800400,
"sql":"select * from MSG where localId =301;"
}
```
响应:
``` javascript
{
"code": 1,
"data": [
[
"localId",
"TalkerId",
"MsgSvrID",
"Type",
"SubType",
"IsSender",
"CreateTime",
"Sequence",
"StatusEx",
"FlagEx",
"Status",
"MsgServerSeq",
"MsgSequence",
"StrTalker",
"StrContent",
"DisplayContent",
"Reserved0",
"Reserved1",
"Reserved2",
"Reserved3",
"Reserved4",
"Reserved5",
"Reserved6",
"CompressContent",
"BytesExtra",
"BytesTrans"
],
[
"301",
"1",
"8824834301214701891",
"1",
"0",
"0",
"1685401473",
"1685401473000",
"0",
"0",
"2",
"1",
"795781866",
"wxid_123",
"testtest",
"",
"0",
"2",
"",
"",
"",
"",
"",
"",
"CgQIEBAAGo0BCAcSiAE8bXNnc291cmNlPJPHNpZ25hdHVyZT52MV9wd12bTZyRzwvc2lnbmF0dXJPgoJPHRtcF9ub2RlPgoJCTxwsaXNoZXItaWQ+Jmx0OyFbQ0RBVEFbXV0mZ3Q7PC9wdWJsaXNoZXItaWQ+Cgk8L3RtcF9ub2RlPgo8L21zZ3NvdXJjZT4KGiQIAhIgNDE1MDA0NjRhZTRmMjk2NjhjMzY2ZjFkOTdmMjAwNDg=",
""
]
],
"msg": "success"
}
```

View File

@ -1,11 +0,0 @@
package main
import (
"go_client/tcpserver"
"log"
)
func main() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
tcpserver.Listen(19099)
}

View File

@ -1,44 +0,0 @@
package tcpserver
import (
"bufio"
"log"
"net"
"strconv"
)
func Listen(port int) {
p := strconv.Itoa(port)
adress := "127.0.0.1:" + p
ln, err := net.Listen("tcp", adress)
if err != nil {
log.Fatal(err)
}
defer ln.Close()
log.Println("tcp server started")
for {
conn, err := ln.Accept()
if err != nil {
log.Println(err)
continue
}
go handle(conn)
}
}
func handle(conn net.Conn) {
defer func() {
if err := recover(); err != nil {
log.Println("发生了未处理的异常", err)
}
}()
defer conn.Close()
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
line := scanner.Bytes()
log.Println("收到消息:", string(line))
}
if err := scanner.Err(); err != nil {
log.Println("错误:", err)
}
}

View File

@ -1,33 +0,0 @@
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/

View File

@ -1,21 +0,0 @@
环境为jdk17
执行之后会在当前项目所处磁盘根路径生成一个exec文件夹,然后会把src/main/resources/exec下的文件放在那避免因为路径问题出错
java_client/src/main/resources/exec/c.exe 为注入器,只不过把名字改短了,更新的话换成最新版,改个名字就行, wxhelper.dll同理
项目启动之后,会生成一个tcp服务端,用来接受hook信息,然后把接收的信息放在队列中,之后用一个线程去循环处理消息.
具体实现可以看
```com.example.wxhk.tcp.vertx```包下的三个文件
com.example.wxhk.tcp.vertx.VertxTcp 这个是tcp服务端,接受信息
com.example.wxhk.tcp.vertx.InitWeChat 微信环境初始化
com.example.wxhk.tcp.vertx.ArrHandle 循环消息处理
com.example.wxhk.server.WxSmgServer 为消息处理接口,实现其中的方法即可
![image](https://github.com/sglmsn/wxhelper/assets/36943585/59d49401-a492-46a9-8ed9-dab7fb1822b4)
启动项目需要去修改配置文件的微信路径

View File

@ -1,184 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>wxhk</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>wxhk</name>
<description>wxhk</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-thymeleaf</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-web</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.92.Final</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.11.0</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>4.4.2</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<version>4.4.2</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web-client</artifactId>
<version>4.4.2</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-mysql-client</artifactId>
<version>4.4.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.dromara.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>6.0.0.M3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<!-- 排除文件配置 -->
<!-- <excludes> -->
<!-- <exclude>*.**</exclude> -->
<!-- <exclude>*/**.xml</exclude> -->
<!-- </excludes> -->
<!-- 包含文件配置,现在只打包 com 文件夹 -->
<includes>
<include>
**/com/example/wxhk/**
</include>
</includes>
<archive>
<manifest>
<!-- 配置加入依赖包 -->
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<useUniqueVersions>false</useUniqueVersions>
<!-- Spring Boot 启动类(自行修改) -->
<mainClass>com.example.wxhk.WxhkApplication</mainClass>
</manifest>
<manifestEntries>
<!-- 外部资源路径加入 manifest.mf 的 Class-Path -->
<Class-Path>resources/</Class-Path>
</manifestEntries>
</archive>
<!-- jar 输出目录 -->
<outputDirectory>${project.build.directory}/pack/</outputDirectory>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<!-- 复制依赖 -->
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- 依赖包 输出目录 -->
<outputDirectory>${project.build.directory}/pack/lib</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<!-- 复制资源 -->
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<!-- 资源文件 输出目录 -->
<outputDirectory>${project.build.directory}/pack/resources</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,22 +0,0 @@
package com.example.wxhk;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WxhkApplication {
public static final Vertx vertx;
static {
vertx = Vertx.vertx(new VertxOptions().setWorkerPoolSize(5).setEventLoopPoolSize(5));
}
//ConsoleInject.exe -i WeChat.exe -p D:\wxhelper.dll
//ConsoleApplication.exe -I 4568 -p C:\wxhelper.dll -m 17484 -P 1888
public static void main(String[] args) {
SpringApplication.run(WxhkApplication.class, args);
}
}

View File

@ -1,37 +0,0 @@
package com.example.wxhk.constant;
/**
* 接受到的微信消息类型
*
* @author wt
* @date 2023/05/26
*/
public enum WxMsgType {
/**
*
*/
私聊信息(1),
好友请求(37),
收到名片(42),
表情(47),
转账和收款(49),
收到转账之后或者文件助手等信息(51),
入群(10000),
/**
* 扫码触发,会触发2次, 有一次有编号,一次没有,还有登陆之后也有,很多情况都会调用这个
*/
扫码触发(10002),
;
Integer type;
WxMsgType(Integer type) {
this.type = type;
}
public Integer getType() {
return type;
}
}

View File

@ -1,14 +0,0 @@
package com.example.wxhk.controller;
import org.dromara.hutool.log.Log;
public class WxMsgController {
protected static final Log log = Log.get();
void init() {
}
}

View File

@ -1,11 +0,0 @@
package com.example.wxhk.infe;
/**
* http 响应
* @author wt
* @date 2023/06/01
*/
public interface Resp extends java.io.Serializable{
}

View File

@ -1,17 +0,0 @@
package com.example.wxhk.infe;
import io.vertx.core.json.JsonObject;
/**
* http接口请求的基础接口
*
* @author wt
* @date 2023/06/01
*/
public interface SendMsg<T> extends java.io.Serializable{
default JsonObject toJson(){
return JsonObject.mapFrom(this);
}
}

View File

@ -1,49 +0,0 @@
package com.example.wxhk.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
/**
* 私聊
*
* @author wt
* @date 2023/05/26
*/
@Data
@Accessors(chain = true)
@JsonIgnoreProperties(ignoreUnknown = true)
public class PrivateChatMsg implements Serializable {
String path;
/**
* 内容
*/
private String content;
/**
* 当是群聊的时候 为群id,否则为微信id
*/
private String fromGroup;
/**
* 微信id
*/
private String fromUser;
private Integer isSendMsg;
/**
* 1通过手机发送
*/
private Integer isSendByPhone;
private Long msgId;
private Integer pid;
private String sign;
private String signature;
private String time;
private Integer timestamp;
/**
* 类型
*/
private Integer type;
}

View File

@ -1,21 +0,0 @@
package com.example.wxhk.model.dto;
import java.math.BigDecimal;
/**
* 支付信息
*
* @author wt
* @param receiverUsername 付款人
* @param decimal 收款金额
* @param remark 备注
* @param transcationid
* @param transferid
* @date 2023/06/06
*/
public record PayoutInformation(String receiverUsername, BigDecimal decimal, String remark,String transcationid,String transferid) implements java.io.Serializable {
public PayoutInformation(String receiverUsername, BigDecimal decimal, String remark) {
this(receiverUsername, decimal, remark, null, null);
}
}

View File

@ -1,20 +0,0 @@
package com.example.wxhk.model.request;
import com.example.wxhk.infe.SendMsg;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 添加wxid 好友
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class AddFriends implements SendMsg<AddFriends> {
String wxid;
/**
* 验证信息
*/
String msg;
}

View File

@ -1,27 +0,0 @@
package com.example.wxhk.model.request;
import com.example.wxhk.infe.SendMsg;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 确认收款
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class ConfirmThePayment implements SendMsg<ConfirmThePayment> {
/**
* 转账人微信id从hook的消息中获取
*/
String wxid;
/**
* 从hook的消息中获取对应的字段内容
*/
String transcationId;
/**
* 从hook的消息中获取对应的字段内容
*/
String transferId;
}

View File

@ -1,19 +0,0 @@
package com.example.wxhk.model.request;
import com.example.wxhk.infe.SendMsg;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 通过手机或者qq查找微信
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class FindWeChat implements SendMsg<FindWeChat> {
/**
* 通过 手机或qq查询信息
*/
String keyword;
}

View File

@ -1,23 +0,0 @@
package com.example.wxhk.model.request;
import com.example.wxhk.infe.SendMsg;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 转发消息
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class ForwardMessages implements SendMsg<ForwardMessages> {
/**
* 消息接收人wxid
*/
String wxid;
/**
* 消息id
*/
String msgid;
}

View File

@ -1,16 +0,0 @@
package com.example.wxhk.model.request;
import com.example.wxhk.infe.SendMsg;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 获取群成员
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class GetGroupMembers implements SendMsg<GetGroupMembers> {
String chatRoomId;
}

View File

@ -1,23 +0,0 @@
package com.example.wxhk.model.request;
import com.example.wxhk.infe.SendMsg;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 获取群成员昵称
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class GetsTheNicknameOfAGroupMember implements SendMsg<GetsTheNicknameOfAGroupMember> {
/**
* 聊天室id
*/
String chatRoomId;
/**
* 成员id
*/
String memberId;
}

View File

@ -1,23 +0,0 @@
package com.example.wxhk.model.request;
import com.example.wxhk.infe.SendMsg;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 增加群成员
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class IncreaseGroupMembership implements SendMsg<IncreaseGroupMembership> {
/**
* 聊天室id
*/
String chatRoomId;
/**
* 成员id,分割
*/
String memberIds;
}

View File

@ -1,18 +0,0 @@
package com.example.wxhk.model.request;
import com.example.wxhk.infe.SendMsg;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 开启hook
*
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class OpenHook implements SendMsg<OpenHook> {
String port;
String ip;
}

View File

@ -1,25 +0,0 @@
package com.example.wxhk.model.request;
import com.example.wxhk.infe.SendMsg;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 发送at文本
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class SendAtText implements SendMsg<SendAtText> {
/**
* 聊天室id,群聊用
*/
String chatRoomId;
/**
* 群聊的时候用at多个用逗号隔开,@所有人则是<b>notify@all</b>
*/
String wxids;
String msg;
}

View File

@ -1,22 +0,0 @@
package com.example.wxhk.model.request;
import com.example.wxhk.infe.SendMsg;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 发送文件
*
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class SendFile implements SendMsg<SendFile> {
String wxid;
/**
* 发送文件路径
* "filePath": "C:/Users/123.txt"
*/
String filePath;
}

View File

@ -1,22 +0,0 @@
package com.example.wxhk.model.request;
import com.example.wxhk.infe.SendMsg;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 发送图片
*
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class SendImg implements SendMsg<SendImg> {
String wxid;
/**
* 发送图片接口
* "imagePath": "C:/Users/123.png"
*/
String imagePath;
}

View File

@ -1,52 +0,0 @@
package com.example.wxhk.model.request;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* http请求参数
*
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class SendMsg {
/**
* wxid
*/
String wxid;
/**
* 消息内容
*/
String msg;
/**
* 聊天室id,群聊用
*/
String chatRoomId;
/**
* 成员id
*/
String memberId;
/**
* 群聊的时候用at多个用逗号隔开,@所有人则是<b>notify@all</b>
*/
String wxids;
/**
* 发送图片接口
* "imagePath": "C:/Users/123.png"
*/
String imagePath;
/**
* 发送文件路径
* "filePath": "C:/Users/123.txt"
*/
String filePath;
/**
* 通过 手机或qq查询信息
*/
String keyword;
}

View File

@ -1,18 +0,0 @@
package com.example.wxhk.model.request;
import com.example.wxhk.infe.SendMsg;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 发送文本
*
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class SendText implements SendMsg<SendText> {
String wxid;
String msg;
}

View File

@ -1,27 +0,0 @@
package com.example.wxhk.model.request;
import com.example.wxhk.infe.SendMsg;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 通过好友请求
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
public class ThroughFriends implements SendMsg<ThroughFriends> {
/**
* 添加好友消息内容里的encryptusername
*/
String v3;
/**
* 添加好友消息内容里的ticket
*/
String v4;
/**
* 好友权限,0是无限制,1是不让他看我,2是不看他,3是1+2
*/
String permission;
}

View File

@ -1,50 +0,0 @@
package com.example.wxhk.model.response;
import com.example.wxhk.infe.Resp;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.util.List;
/**
* 联系人列表
* @author wt
* @date 2023/06/01
*/
@Data
@Accessors(chain = true)
@JsonIgnoreProperties(ignoreUnknown = true)
public class ContactList implements Resp {
/**
* code : 1
* data : [{"customAccount":"","delFlag":0,"type":1,"userName":"朋友推荐消息","verifyFlag":0,"wxid":"fmessage"},{"customAccount":"tencent_cloud","delFlag":0,"type":3,"userName":"腾讯云助手","verifyFlag":24,"wxid":"gh_a73e2407e0f8"},{"customAccount":"","delFlag":0,"type":1,"userName":"语音记事本","verifyFlag":0,"wxid":"medianote"},{"customAccount":"","delFlag":0,"type":1,"userName":"漂流瓶","verifyFlag":0,"wxid":"floatbottle"},{"customAccount":"jys-wt","delFlag":0,"type":8651011,"userName":"时光似水戏流年","verifyFlag":0,"wxid":"wxid_gf1fogt5a0pq22"},{"customAccount":"wxzhifu","delFlag":0,"type":3,"userName":"微信支付","verifyFlag":24,"wxid":"gh_3dfda90e39d6"},{"customAccount":"dhkzfr","delFlag":0,"type":3,"userName":"阿芙(代发)","verifyFlag":0,"wxid":"wxid_kh16lri40gzj22"},{"customAccount":"","delFlag":0,"type":3,"userName":"文件传输助手","verifyFlag":0,"wxid":"filehelper"},{"customAccount":"","delFlag":0,"type":3,"userName":"fff","verifyFlag":0,"wxid":"24964676359@chatroom"},{"customAccount":"","delFlag":0,"type":2,"userName":"最美阿芙","verifyFlag":0,"wxid":"23793178249@chatroom"},{"customAccount":"afu943344","delFlag":0,"type":2,"userName":"A-阿芙4号-LOL永劫云顶出租-代发","verifyFlag":0,"wxid":"wxid_1gxthknqbmwv22"},{"customAccount":"","delFlag":0,"type":3,"userName":"微信收款助手","verifyFlag":24,"wxid":"gh_f0a92aa7146c"},{"customAccount":"","delFlag":0,"type":0,"userName":"","verifyFlag":0,"wxid":"25984984710827869@openim"}]
* result : OK
*/
private Integer code;
private String result;
private List<DataBean> data;
@Data
@Accessors(chain = true)
public static class DataBean implements Serializable {
/**
* customAccount :
* delFlag : 0
* type : 1
* userName : 朋友推荐消息
* verifyFlag : 0
* wxid : fmessage
*/
private String customAccount;
private Integer delFlag;
private Integer type;
private String userName;
private Integer verifyFlag;
private String wxid;
}
}

View File

@ -1,37 +0,0 @@
package com.example.wxhk.model.response;
import com.example.wxhk.infe.Resp;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
@Data
@Accessors(chain = true)
@JsonIgnoreProperties(ignoreUnknown = true)
public class GroupMembers implements Resp {
/**
* code : 1
* data : {"admin":"wxid_gf1fogt5a0pq22","chatRoomId":"24964676359@chatroom","members":"wxid_gf1fogt5a0pq22^Gwxid_4yr8erik0uho22"}
* result : OK
*/
private Integer code;
private DataBean data;
private String result;
@Data
public static class DataBean implements Serializable {
/**
* admin : wxid_gf1fogt5a0pq22
* chatRoomId : 24964676359@chatroom
* members : wxid_gf1fogt5a0pq22^Gwxid_4yr8erik0uho22
*/
private String admin;
private String chatRoomId;
private String members;
}
}

View File

@ -1,250 +0,0 @@
package com.example.wxhk.msg;
import com.example.wxhk.constant.WxMsgType;
import com.example.wxhk.model.PrivateChatMsg;
import com.example.wxhk.model.dto.PayoutInformation;
import com.example.wxhk.server.WxSmgServer;
import com.example.wxhk.tcp.vertx.InitWeChat;
import jakarta.annotation.PostConstruct;
import org.dromara.hutool.core.util.XmlUtil;
import org.dromara.hutool.log.Log;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import java.math.BigDecimal;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
@Component
public class WxMsgHandle {
public static final ConcurrentHashMap<Integer, Handle> map = new ConcurrentHashMap<>(32);
protected static final Log log = Log.get();
/**
* 文件传输助手
*/
public static final String FILEHELPER = "filehelper";
/**
* 收款码缓存 因为有2段信息,一段是交易id,里面可以解析出来源方,二段解析出金额
*/
public static ConcurrentHashMap<String, String> collection_code_caching = new ConcurrentHashMap<>();
public static WxSmgServer wxSmgServer;
/**
*
*/
public static final ReentrantReadWriteLock LOOK = new ReentrantReadWriteLock();
@Autowired
public void setWxSmgServer(WxSmgServer wxSmgServer) {
WxMsgHandle.wxSmgServer = wxSmgServer;
}
@PostConstruct
public void init() {
add(chatMsg -> {
wxSmgServer.私聊(chatMsg);
return null;
}, WxMsgType.私聊信息);
add(chatMsg -> {
if (FILEHELPER.equals(chatMsg.getFromUser())) {
wxSmgServer.文件助手(chatMsg);
}
return 1;
}, WxMsgType.收到转账之后或者文件助手等信息);
add(chatMsg -> {
wxSmgServer.收到名片(chatMsg);
return 1;
}, WxMsgType.收到名片);
add(chatMsg -> {
wxSmgServer.收到好友请求(chatMsg);
return 1;
}, WxMsgType.好友请求);// 好友请求
add(chatMsg -> {
boolean f = 解析扫码支付第二段(chatMsg);
if (f) {
f = 解析收款信息1段(chatMsg);
if (f) {
解析收款信息2段(chatMsg);
}
}
return null;
}, WxMsgType.转账和收款);
add(chatMsg -> {
boolean f = 解析扫码支付第一段(chatMsg);
return null;
}, WxMsgType.扫码触发);
}
/**
* 解析扫码支付第一段,得到交易id和微信id
*
* @param chatMsg
* @return boolean 返回true 则继续解析,否则解析成功,不需要解析了
*/
public static boolean 解析扫码支付第一段(PrivateChatMsg chatMsg) {
try {
Document document = XmlUtil.parseXml(chatMsg.getContent());
Element documentElement = document.getDocumentElement();
String localName = documentElement.getLocalName();
if ("sysmsg".equals(localName)) {
String type = documentElement.getAttribute("type");
if ("paymsg".equals(type)) {
NodeList outtradeno = documentElement.getElementsByTagName("outtradeno");
if (outtradeno.getLength() > 0) {
String textContent = outtradeno.item(0).getTextContent();
String textContent1 = documentElement.getElementsByTagName("username").item(0).getTextContent();
collection_code_caching.put(textContent, textContent1);
return false;
}
}
}
} catch (Exception e) {
log.error(e);
}
return true;
}
/**
* 解析扫码支付第二段
*
* @param chatMsg 聊天味精
* @return boolean true 继续解析, false则解析成功,不需要再解析了
*/
public static boolean 解析扫码支付第二段(PrivateChatMsg chatMsg) {
try {
Document document = XmlUtil.parseXml(chatMsg.getContent());
Element documentElement = document.getDocumentElement();
String localName = documentElement.getLocalName();
if ("msg".equals(localName)) {
NodeList outtradeno = documentElement.getElementsByTagName("weapp_path");
if (outtradeno.getLength() > 1) {
String textContent = outtradeno.item(1).getTextContent();
Set<Map.Entry<String, String>> entries = collection_code_caching.entrySet();
Iterator<Map.Entry<String, String>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> next = iterator.next();
if (textContent.contains(next.getKey())) {
// 得到了交易信息
NodeList word = documentElement.getElementsByTagName("word");
String monery = word.item(1).getTextContent();
String remark = word.item(3).getTextContent();
if (monery.startsWith("")) {
String substring = monery.substring(1);
BigDecimal decimal = new BigDecimal(substring);
log.info("扫码收款:{},付款人:{},付款备注:{}", decimal.stripTrailingZeros().toPlainString(), next.getValue(), remark);
wxSmgServer.扫码收款(new PayoutInformation(next.getValue(),decimal,remark));
iterator.remove();
return false;
}
}
}
}
}
} catch (Exception e) {
log.error(e);
}
return true;
}
public static boolean 解析收款信息2段(PrivateChatMsg chatMsg) {
try {
Document document = XmlUtil.parseXml(chatMsg.getContent());
Element documentElement = document.getDocumentElement();
String localName = documentElement.getLocalName();
if ("msg".equals(localName)) {
if (documentElement.getElementsByTagName("transcationid").getLength() > 0) {
String remark = documentElement.getElementsByTagName("pay_memo").item(0).getTextContent();
String monery = documentElement.getElementsByTagName("feedesc").item(0).getTextContent();
String receiver_username = documentElement.getElementsByTagName("receiver_username").item(0).getTextContent();
// 如果是机器人发出的,则跳过解析
if (InitWeChat.WXID_MAP.contains(receiver_username) ) {
return false;
}
if (monery.startsWith("")) {
String substring = monery.substring(1);
BigDecimal decimal = new BigDecimal(substring);
log.info("收款:{},付款人:{},付款备注:{}", decimal.stripTrailingZeros().toPlainString(), chatMsg.getFromUser(), remark);
wxSmgServer.收款之后(new PayoutInformation(chatMsg.getFromUser(), decimal, remark));
return false;
};
}
}
} catch (Exception e) {
log.error(e);
}
return true;
}
/**
* 解析收款信息1段
* <b>会自动进行收款</b>
*
* @param chatMsg
* @return boolean true则 继续解析,false则不需要解析了
*/
public static boolean 解析收款信息1段(PrivateChatMsg chatMsg) {
try {
String content = chatMsg.getContent();
Document document = XmlUtil.parseXml(content);
NodeList paysubtype1 = document.getElementsByTagName("paysubtype");
if (paysubtype1.getLength() == 0) {
return true;
}
Node paysubtype = paysubtype1.item(0);
if ("1".equals(paysubtype.getTextContent().trim())) {
// 手机发出去的
String textContent = document.getElementsByTagName("receiver_username").item(0).getTextContent();
if (!InitWeChat.WXID_MAP.contains(textContent)) {
// 如果不是机器人收款,则认为不需要解析了,大概率是机器人自己发出去的
return false;
}
String remark = document.getElementsByTagName("pay_memo").item(0).getTextContent();
String monery = document.getElementsByTagName("feedesc").item(0).getTextContent();
String receiver_username = document.getElementsByTagName("receiver_username").item(0).getTextContent();
if (monery.startsWith("")) {
String substring = monery.substring(1);
BigDecimal decimal = new BigDecimal(substring);
Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0);
Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0);
wxSmgServer.接到收款(new PayoutInformation(chatMsg.getFromUser(), decimal, remark, transcationid.getTextContent(), transferid.getTextContent()));
return false;
}
}
} catch (Exception e) {
log.error(e);
}
return true;
}
public static void exec(PrivateChatMsg chatMsg) {
Handle handle = map.get(chatMsg.getType());
if (handle != null) {
handle.handle(chatMsg);
}
}
public void add(Handle handle, WxMsgType... type) {
for (WxMsgType integer : type) {
map.put(integer.getType(), handle);
}
}
public interface Handle {
Object handle(PrivateChatMsg chatMsg);
}
}

View File

@ -1,30 +0,0 @@
package com.example.wxhk.server;
import com.example.wxhk.model.PrivateChatMsg;
import com.example.wxhk.model.dto.PayoutInformation;
/**
* 微信消息处理提取
* @author wt
* @date 2023/06/06
*/
public interface WxSmgServer {
/**
* 接到收款
*
* @param payoutInformation 支付信息
*/
void 接到收款(PayoutInformation payoutInformation);
void 收款之后(PayoutInformation pay);
void 私聊(PrivateChatMsg chatMsg);
void 文件助手(PrivateChatMsg chatMsg);
void 收到名片(PrivateChatMsg chatMsg);
void 收到好友请求(PrivateChatMsg chatMsg);
void 扫码收款(PayoutInformation payoutInformation);
}

View File

@ -1,64 +0,0 @@
package com.example.wxhk.server.impl;
import com.example.wxhk.model.PrivateChatMsg;
import com.example.wxhk.model.dto.PayoutInformation;
import com.example.wxhk.model.request.ConfirmThePayment;
import com.example.wxhk.util.HttpSendUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.util.XmlUtil;
import org.dromara.hutool.log.Log;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.util.Objects;
@Service
public class WxSmgServerImpl implements com.example.wxhk.server.WxSmgServer {
protected static final Log log=Log.get();
public static final String FILEHELPER = "filehelper";
@Override
public void 接到收款(PayoutInformation payoutInformation) {
HttpSendUtil.确认收款(new ConfirmThePayment().setWxid(payoutInformation.receiverUsername()).setTranscationId(payoutInformation.transcationid()).setTransferId(payoutInformation.transferid()));
}
@Override
public void 收款之后(PayoutInformation pay) {
HttpSendUtil.发送文本(pay.receiverUsername(), StrUtil.format("收到款项:{},备注:{}", pay.decimal().stripTrailingZeros().toPlainString(), pay.remark()));
}
@Override
public void 私聊(PrivateChatMsg chatMsg) {
if (Objects.equals(chatMsg.getIsSendMsg(), 1) && Objects.equals(chatMsg.getIsSendByPhone(), 1)) {
log.info("手机端对:{}发出:{}", chatMsg.getFromUser(), chatMsg.getContent());
}
}
@Override
public void 文件助手(PrivateChatMsg chatMsg) {
}
@Override
public void 收到名片(PrivateChatMsg chatMsg) {
if (FILEHELPER.equals(chatMsg.getFromUser())) {
Document document = XmlUtil.parseXml(chatMsg.getContent());
Element documentElement = document.getDocumentElement();
String username = documentElement.getAttribute("username");
if (StrUtil.isNotBlank(username)) {
HttpSendUtil.发送文本(username);
}
}
}
@Override
public void 收到好友请求(PrivateChatMsg chatMsg) {
HttpSendUtil.通过好友请求(chatMsg);
}
@Override
public void 扫码收款(PayoutInformation payoutInformation) {
HttpSendUtil.发送文本(payoutInformation.receiverUsername(), StrUtil.format("扫码收款:{},备注:{}", payoutInformation.decimal().stripTrailingZeros().toPlainString(), payoutInformation.remark()));
}
}

View File

@ -1,90 +0,0 @@
package com.example.wxhk.tcp.vertx;
import com.example.wxhk.model.PrivateChatMsg;
import com.example.wxhk.msg.WxMsgHandle;
import com.example.wxhk.util.HttpSendUtil;
import io.vertx.core.json.JsonObject;
import jakarta.annotation.PostConstruct;
import org.dromara.hutool.core.thread.NamedThreadFactory;
import org.dromara.hutool.log.Log;
import org.springframework.stereotype.Component;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 消息处理
*
* @author wt
* @date 2023/05/31
*/
@Component
public class ArrHandle {
/**
* 线程处理消息队列,但是必须保证核心数大于2,其中必定要有一个线程可以单独处理交易队列信息
*/
public static final ThreadPoolExecutor sub = new ThreadPoolExecutor(4, 10, 30, TimeUnit.MINUTES, new LinkedBlockingQueue<>(), new NamedThreadFactory("sub", false));
public static final ThreadLocal<PrivateChatMsg> chatMsgThreadLocal = new InheritableThreadLocal<>();
protected static final Log log = Log.get();
/**
* 得到当前正在处理的消息
*
* @return {@link PrivateChatMsg}
*/
public static PrivateChatMsg getPriMsg() {
return chatMsgThreadLocal.get();
}
@PostConstruct
public void exec() {
for (int i = 0; i < sub.getCorePoolSize()-1; i++) {
sub.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
JsonObject take = VertxTcp.LINKED_BLOCKING_QUEUE.take();
log.info("{}", take.encode());
PrivateChatMsg privateChatMsg = take.mapTo(PrivateChatMsg.class);
chatMsgThreadLocal.set(privateChatMsg);
if ("weixin".equals(privateChatMsg.getFromUser())) {
String s = HttpSendUtil.获取当前登陆微信id();
InitWeChat.WXID_MAP.add(s);
continue;
}
WxMsgHandle.exec(privateChatMsg);
} catch (Exception e) {
log.error(e);
}finally {
chatMsgThreadLocal.remove();
}
}
log.error("退出线程了");
});
}
sub.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
JsonObject take = VertxTcp.LINKED_BLOCKING_QUEUE_MON.take();
log.info("{}", take.encode());
PrivateChatMsg privateChatMsg = take.mapTo(PrivateChatMsg.class);
chatMsgThreadLocal.set(privateChatMsg);
if ("weixin".equals(privateChatMsg.getFromUser())) {
String s = HttpSendUtil.获取当前登陆微信id();
InitWeChat.WXID_MAP.add(s);
continue;
}
WxMsgHandle.exec(privateChatMsg);
} catch (Exception e) {
log.error(e);
}finally {
chatMsgThreadLocal.remove();
}
}
log.error("退出线程了");
});
}
}

View File

@ -1,158 +0,0 @@
package com.example.wxhk.tcp.vertx;
import com.example.wxhk.util.HttpAsyncUtil;
import com.example.wxhk.util.HttpSyncUtil;
import io.vertx.core.impl.ConcurrentHashSet;
import io.vertx.core.json.JsonObject;
import org.dromara.hutool.core.io.file.FileUtil;
import org.dromara.hutool.core.net.NetUtil;
import org.dromara.hutool.core.text.StrUtil;
import org.dromara.hutool.core.thread.ThreadUtil;
import org.dromara.hutool.log.Log;
import org.dromara.hutool.setting.Setting;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
/**
* 微信注入环境初始化和相关方法
*
* @author wt
* @date 2023/05/16
*/
@Order(-1)
@Component
public class InitWeChat implements CommandLineRunner {
public final static Log log = Log.get();
public static final ConcurrentHashSet<String> WXID_MAP = new ConcurrentHashSet<>();
public static String wxPath;
public static Integer wxPort;
public static Integer vertxPort;
/**
* wxhelper.dll 所在路径
*/
public static File DLL_PATH;
public static void 注入dll(String wxPid) throws IOException {
String format = StrUtil.format("cmd /C c.exe -I {} -p {}\\wxhelper.dll -m {}", wxPid, DLL_PATH.getAbsolutePath(), wxPid);
Process exec = Runtime.getRuntime().exec(format, null, DLL_PATH);
log.info("注入结果:{}", new String(exec.getInputStream().readAllBytes(), "gbk"));
}
@NotNull
private static File 环境初始化() {
File target = new File(new File("").getAbsolutePath().split("\\\\")[0] + "\\exec\\");
try {
File wxPathFile = new File(wxPath);
File config = new File(wxPathFile.getParentFile(), "config.ini");
Setting setting = new Setting(config.getAbsolutePath());
setting.getGroupedMap().put("config", "port", String.valueOf(wxPort));
setting.store();
ClassPathResource classPathResource = new ClassPathResource("exec");
File file = classPathResource.getFile();
target.mkdir();
for (File listFile : file.listFiles()) {
FileUtil.copy(listFile, target, true);
}
} catch (Exception e) {
log.error(e, "环境初始化失败,请检查");
}
return target;
}
/**
* 返回最后一个微信的pid
*
* @return {@link String}
* @throws IOException ioexception
*/
public static String createWx() throws IOException {
Runtime.getRuntime().exec("cmd /C \"" + wxPath + "\"");
return getWxPid();
}
@NotNull
private static String getWxPid() throws IOException {
String line = null;
try {
Process exec = Runtime.getRuntime().exec("cmd /C tasklist /FI \"IMAGENAME eq WeChat.exe\" ");
byte[] bytes = exec.getInputStream().readAllBytes();
line = new String(bytes, "gbk");
String[] split = line.split("\n");
if (!line.contains("WeChat.exe")) {
return createWx();
}
String[] split1 = split[split.length - 1].replaceAll("\\s{2,}", " ").split(" ");
return split1[1];
} catch (IOException e) {
log.error("获取端口错误:{}", line);
throw e;
}
}
public static Integer getWxPort() {
return wxPort;
}
@Value("${wx.port}")
public void setWxPort(Integer wxPort) {
InitWeChat.wxPort = wxPort;
}
public static String getWxPath() {
return wxPath;
}
@Value("${wx.path}")
public void setWxPath(String wxPath) {
InitWeChat.wxPath = wxPath;
}
public static Integer getVertxPort() {
return vertxPort;
}
@Value("${vertx.port}")
public void setVertxPort(Integer vertxPort) {
InitWeChat.vertxPort = vertxPort;
}
@Override
public void run(String... args) throws Exception {
//tasklist /FI "IMAGENAME eq WeChat.exe" /m
boolean usableLocalPort = NetUtil.isUsableLocalPort(wxPort);
if (usableLocalPort) {
DLL_PATH = 环境初始化();
String wxPid = getWxPid();
注入dll(wxPid);
}
ThreadUtil.execute(() -> {
while (!Thread.currentThread().isInterrupted()) {
JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.检查微信登陆, new JsonObject());
if (exec.getInteger("code").equals(1)) {
JsonObject dl = HttpSyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject());
JsonObject jsonObject = dl.getJsonObject("data");
String wx = jsonObject.getString("wxid");
WXID_MAP.add(wx);
if (log.isDebugEnabled()) {
log.debug("检测到微信登陆:{}", wx);
}
break;
}
ThreadUtil.safeSleep(500);
}
});
// FIXME: 2023/6/2 程序结束后关闭hook会偶尔出现微信闪退情况,暂时禁用
// Runtime.getRuntime().addShutdownHook(new Thread(HttpSendUtil::关闭hook));
//netstat -aon|findstr "端口号"
// c.exe -I 4568 -p D:\exec\wxhelper.dll -m 4568
}
}

View File

@ -1,91 +0,0 @@
package com.example.wxhk.tcp.vertx;
import com.example.wxhk.WxhkApplication;
import com.example.wxhk.constant.WxMsgType;
import com.example.wxhk.util.HttpAsyncUtil;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Future;
import io.vertx.core.Promise;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.NetServer;
import io.vertx.core.net.NetServerOptions;
import io.vertx.core.parsetools.JsonParser;
import org.dromara.hutool.log.Log;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.Objects;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 接受微信hook信息
*
* @author wt
* @date 2023/05/26
*/
@Component
@Order()
public class VertxTcp extends AbstractVerticle implements CommandLineRunner {
public final static LinkedBlockingQueue<JsonObject> LINKED_BLOCKING_QUEUE = new LinkedBlockingQueue<>();
/**
* 这个只保留交易相关的类型
*/
public final static LinkedBlockingQueue<JsonObject> LINKED_BLOCKING_QUEUE_MON = new LinkedBlockingQueue<>();
protected static final Log log = Log.get();
NetServer netServer;
@Override
public void start(Promise<Void> startPromise) throws Exception {
netServer = vertx.createNetServer(new NetServerOptions()
.setPort(InitWeChat.getVertxPort())
.setIdleTimeout(0)
.setLogActivity(false)
);
netServer.connectHandler(socket -> {
JsonParser parser = JsonParser.newParser();
parser.objectValueMode();
parser.handler(event -> {
switch (event.type()) {
case START_OBJECT -> {
}
case END_OBJECT -> {
}
case START_ARRAY -> {
}
case END_ARRAY -> {
}
case VALUE -> {
JsonObject entries = event.objectValue();
if(Objects.equals(entries.getInteger("type"), WxMsgType.扫码触发.getType()) ||
Objects.equals(entries.getInteger("type"), WxMsgType.转账和收款.getType())){
LINKED_BLOCKING_QUEUE_MON.add(entries);
}else{
LINKED_BLOCKING_QUEUE.add(entries);
}
}
}
});
socket.handler(parser);
});
Future<NetServer> listen = netServer.listen();
listen.onComplete(event -> {
boolean succeeded = event.succeeded();
if (succeeded) {
HttpAsyncUtil.exec(HttpAsyncUtil.Type.开启hook, new JsonObject().put("port", InitWeChat.getVertxPort().toString()).put("ip", "127.0.0.1"));
startPromise.complete();
} else {
startPromise.fail(event.cause());
}
});
}
@Override
public void run(String... args) throws Exception {
WxhkApplication.vertx.deployVerticle(this, new DeploymentOptions().setWorkerPoolSize(6));
}
}

View File

@ -1,77 +0,0 @@
package com.example.wxhk.util;
import com.example.wxhk.WxhkApplication;
import com.example.wxhk.tcp.vertx.InitWeChat;
import io.vertx.core.AsyncResult;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpResponse;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import org.dromara.hutool.log.Log;
/**
* http异步请求
*
* @author wt
* @date 2023/05/25
*/
public class HttpAsyncUtil {
public static final WebClient client = WebClient.create(WxhkApplication.vertx, new WebClientOptions().setDefaultHost("localhost").setDefaultPort(InitWeChat.wxPort)
.setConnectTimeout(10000).setMaxPoolSize(10).setPoolEventLoopSize(10));
protected static final Log log = Log.get();
public static Future<HttpResponse<Buffer>> exec(Type type, JsonObject object) {
return client.post(InitWeChat.wxPort, "localhost", "/api/?type=" + type.getType())
.sendJsonObject(object)
.onSuccess(event ->
{
if (log.isDebugEnabled()) {
log.debug("type:{},{}", type.getType(), event.bodyAsJsonObject());
}
}
);
}
public static Future<HttpResponse<Buffer>> exec(Type type, JsonObject object, Handler<AsyncResult<HttpResponse<Buffer>>> handler) {
return client.post(InitWeChat.wxPort, "localhost", "/api/?type=" + type.getType())
.sendJsonObject(object)
.onComplete(handler)
;
}
public enum Type {
检查微信登陆("0"),
获取登录信息("1"),
发送文本("2"),
发送at文本("3"),
发送图片("5"),
发送文件("6"),
开启hook("9"),
关闭hook("10"),
添加好友("20"),
通过好友申请("23"),
获取群成员("25"),
获取群成员昵称("26"),
删除群成员("27"),
确认收款("45"),
联系人列表("46"),
查询微信信息("55"),
;
String type;
Type(String type) {
this.type = type;
}
public String getType() {
return type;
}
}
}

View File

@ -1,151 +0,0 @@
package com.example.wxhk.util;
import com.example.wxhk.model.PrivateChatMsg;
import com.example.wxhk.model.request.*;
import com.example.wxhk.model.response.ContactList;
import com.example.wxhk.model.response.GroupMembers;
import com.example.wxhk.tcp.vertx.ArrHandle;
import com.example.wxhk.tcp.vertx.InitWeChat;
import io.vertx.core.json.JsonObject;
import org.dromara.hutool.core.util.XmlUtil;
import org.dromara.hutool.log.Log;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
/**
* 常见方法
*
* @author wt
* @date 2023/05/29
*/
public class HttpSendUtil {
protected static final Log log = Log.get();
public static JsonObject 通过好友请求(PrivateChatMsg msg) {
Document document = XmlUtil.parseXml(msg.getContent());
String encryptusername = document.getDocumentElement().getAttribute("encryptusername");
String ticket = document.getDocumentElement().getAttribute("ticket");
return HttpSyncUtil.exec(HttpAsyncUtil.Type.通过好友申请, new JsonObject().put("v3", encryptusername).put("v4", ticket).put("permission", "0"));
}
public static JsonObject 确认收款(PrivateChatMsg msg) {
try {
String content = msg.getContent();
Document document = XmlUtil.parseXml(content);
Node paysubtype = document.getElementsByTagName("paysubtype").item(0);
if ("1".equals(paysubtype.getTextContent().trim())) {
// 手机发出去的
String textContent = document.getElementsByTagName("receiver_username").item(0).getTextContent();
if (!InitWeChat.WXID_MAP.contains(textContent)) {
return new JsonObject().put("spick", true);
}
Node transcationid = document.getDocumentElement().getElementsByTagName("transcationid").item(0);
Node transferid = document.getDocumentElement().getElementsByTagName("transferid").item(0);
return HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, new JsonObject().put("wxid", msg.getFromUser())
.put("transcationId", transcationid.getTextContent())
.put("transferId", transferid.getTextContent()));
}
// 如果是确认接受收款,则跳过
return new JsonObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static JsonObject 发送文本(String wxid, String msg) {
return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送文本, JsonObject.mapFrom(new SendMsg().setMsg(msg).setWxid(wxid)));
}
public static JsonObject 发送文本(String msg) {
return 发送文本(ArrHandle.getPriMsg().getFromUser(), msg);
}
public static JsonObject 发送at文本(String chatRoomId, String wxids, String msg) {
return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送at文本, JsonObject.mapFrom(new SendMsg().setMsg(msg).setWxids(wxids).setChatRoomId(chatRoomId)));
}
public static JsonObject 发送at文本(String wxids, String msg) {
return 发送at文本(ArrHandle.getPriMsg().getFromGroup(), wxids, msg);
}
public static JsonObject 发送图片(String wxid, String msg) {
return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送图片, JsonObject.mapFrom(new SendMsg().setImagePath(msg).setWxid(wxid)));
}
public static JsonObject 发送图片(String msg) {
return 发送图片(ArrHandle.getPriMsg().getFromUser(), msg);
}
public static JsonObject 发送文件(String wxid, String msg) {
return HttpSyncUtil.exec(HttpAsyncUtil.Type.发送文件, JsonObject.mapFrom(new SendMsg().setFilePath(msg).setWxid(wxid)));
}
public static JsonObject 发送文件(String msg) {
return 发送文件(ArrHandle.getPriMsg().getFromUser(), msg);
}
public static JsonObject 添加好友(AddFriends p) {
return HttpSyncUtil.exec(HttpAsyncUtil.Type.添加好友, p.toJson());
}
public static String 获取当前登陆微信id() {
JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject());
return exec.getJsonObject("data").getString("wxid");
}
public static ContactList 联系人列表(){
JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.联系人列表, new JsonObject());
return exec.mapTo(ContactList.class);
}
public static JsonObject 开启hook(OpenHook hook){
JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.开启hook,hook.toJson());
return exec;
}
public static JsonObject 关闭hook(){
JsonObject exec = HttpSyncUtil.exec(HttpAsyncUtil.Type.关闭hook,new JsonObject());
return exec;
}
public static GroupMembers 获取群成员(GetGroupMembers p){
return HttpSyncUtil.exec(HttpAsyncUtil.Type.获取群成员, p.toJson()).mapTo(GroupMembers.class);
}
public static JsonObject 确认收款(ConfirmThePayment payment){
return HttpSyncUtil.exec(HttpAsyncUtil.Type.确认收款, payment.toJson());
}
@Deprecated
public static com.example.wxhk.infe.SendMsg of(HttpAsyncUtil.Type type) {
switch (type) {
case 检查微信登陆 -> {
}
case 获取登录信息 -> {
}
case 发送文本 -> {
return new SendText();
}
case 发送at文本 -> {
return new SendAtText();
}
case 发送图片 -> {
return new SendImg();
}
case 发送文件 -> {
return new SendFile();
}
}
return new SendText();
}
}

View File

@ -1,36 +0,0 @@
package com.example.wxhk.util;
import com.example.wxhk.tcp.vertx.InitWeChat;
import io.vertx.core.json.JsonObject;
import org.dromara.hutool.http.client.ClientConfig;
import org.dromara.hutool.http.client.Request;
import org.dromara.hutool.http.client.engine.ClientEngine;
import org.dromara.hutool.http.client.engine.ClientEngineFactory;
import org.dromara.hutool.http.meta.Method;
import org.dromara.hutool.log.Log;
/**
* http同步请求
*
* @author wt
* @date 2023/05/25
*/
public class HttpSyncUtil {
protected static final Log log = Log.get();
static final ClientEngine engine;
static {
ClientConfig clientConfig = ClientConfig.of()
.setTimeout(30 * 1000);
engine = ClientEngineFactory.createEngine(clientConfig);
}
public static JsonObject exec(HttpAsyncUtil.Type type, JsonObject obj) {
String post = engine.send(Request.of("http://localhost:" + InitWeChat.wxPort + "/api/?type=" + type.getType()).method(Method.POST).body(obj.encode())).bodyStr();
if (log.isDebugEnabled()) {
log.debug("type:{},{}", type.getType(), post);
}
return new JsonObject(post);
}
}

View File

@ -1,4 +0,0 @@
wx.path=D:\\Program Files (x86)\\Tencent\\WeChat\\[3.9.2.23]\\WeChat.exe
wx.port=19088
spring.profiles.active=local
vertx.port=8080

View File

@ -1,171 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--日志格式应用spring boot默认的格式也可以自己更改-->
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<!--定义日志存放的位置,默认存放在项目启动的相对路径的目录-->
<springProperty scope="context" name="LOG_PATH" source="log.path" defaultValue="log"/>
<property name="withLineNumber_debug"
value="%clr(%d{HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) [%t] %replace(%caller{1}){'\t|Caller.{1}0|\r\n', ''} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}"/>
<property name="file_pattern"
value="%d{MM-dd HH:mm:ss.SSS} %-5level [${PID:- } %thread] %logger{50}#%method,%line : %msg%n"/>
<!-- ****************************************************************************************** -->
<!-- ****************************** 本地开发只在控制台打印日志 ************************************ -->
<!-- ****************************************************************************************** -->
<springProfile name="local">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${withLineNumber_debug}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<!-- 日志记录器,日期滚动记录 -->
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>log_error.log</file>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>100</maxHistory>
</rollingPolicy>
<!-- 追加方式记录日志 -->
<append>true</append>
<!-- 日志文件的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${file_pattern}</pattern>
<charset>utf-8</charset>
</encoder>
<!-- 此日志文件只记录error级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--默认所有的包以info-->
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
<!--各个服务的包在本地执行的时候打开debug模式-->
<logger name="com.example.wxhk" level="debug" additivity="false">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE_ERROR"/>
</logger>
<logger name="org.springframework" level="info" additivity="false">
<appender-ref ref="STDOUT"/>
</logger>
</springProfile>
<!-- ********************************************************************************************** -->
<!-- **** 放到服务器上不管在什么环境都只在文件记录日志控制台catalina.out打印logback捕获不到的日志 **** -->
<!-- ********************************************************************************************** -->
<springProfile name="!local">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<!-- 日志记录器,日期滚动记录 -->
<appender name="FILE_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${LOG_PATH}/log_error.log</file>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/error/%d{yyyy-MM}/log_error-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>100</maxHistory>
</rollingPolicy>
<!-- 追加方式记录日志 -->
<append>true</append>
<!-- 日志文件的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${file_pattern}</pattern>
<charset>utf-8</charset>
</encoder>
<!-- 此日志文件只记录error级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>error</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 日志记录器,日期滚动记录 -->
<appender name="FILE_ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${LOG_PATH}/log_total.log</file>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/total/%d{yyyy-MM}/log_total-%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>100</maxHistory>
</rollingPolicy>
<!-- 追加方式记录日志 -->
<append>true</append>
<!-- 日志文件的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${file_pattern}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<!-- 业务错误 -->
<appender name="business_log" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文件的路径及文件名 -->
<file>${LOG_PATH}/log_business.log</file>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/business/%d{yyyy-MM}/log_business-%d{yyyy-MM-dd}.%i.log.gz
</fileNamePattern>
<maxFileSize>50MB</maxFileSize>
<maxHistory>100</maxHistory>
</rollingPolicy>
<!-- 追加方式记录日志 -->
<append>true</append>
<!-- 日志文件的格式 -->
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<pattern>${file_pattern}</pattern>
<charset>utf-8</charset>
</encoder>
</appender>
<logger name="com.example.wxhk" level="info" additivity="false">
<appender-ref ref="business_log"/>
<appender-ref ref="FILE_ERROR"/>
</logger>
<logger name="p6spy" level="info" additivity="false">
<appender-ref ref="business_log"/>
</logger>
<logger name="org.springframework" level="warn"/>
<!--记录到文件时记录两类一类是error日志一个是所有日志-->
<root level="info">
<appender-ref ref="FILE_ERROR"/>
<appender-ref ref="FILE_ALL"/>
</root>
</springProfile>
</configuration>

View File

@ -1,10 +0,0 @@
logMessageFormat=com.p6spy.engine.spy.appender.CustomLineFormat
# 使用Slf4J记录sql
appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准单位秒
outagedetectioninterval=2
#日期格式
dateformat=HH:mm:ss
customLogMessageFormat=%(executionTime)|%(category)|connection%(connectionId)|%(sqlSingleLine)

View File

@ -1,13 +0,0 @@
package com.example.wxhk;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class WxhkApplicationTests {
@Test
void contextLoads() {
}
}

View File

@ -1,48 +0,0 @@
package com.example.wxhk.tcp;
import com.example.wxhk.util.HttpAsyncUtil;
import io.vertx.core.Future;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.client.HttpResponse;
import org.dromara.hutool.core.lang.Console;
import org.dromara.hutool.core.thread.ThreadUtil;
import org.dromara.hutool.log.Log;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class HttpAsyncUtilTest {
protected static final Log log = Log.get();
@Test
void exec() {
Future<HttpResponse<Buffer>> exec = HttpAsyncUtil.exec(HttpAsyncUtil.Type.联系人列表, new JsonObject());
exec.onSuccess(event -> {
Console.log(event.bodyAsJsonObject());
});
}
@Test
void exec1() {
for(int i=0;i<10000;i++){
int finalI = i;
HttpAsyncUtil.exec(HttpAsyncUtil.Type.获取登录信息, new JsonObject(), event -> {
if (event.succeeded()) {
log.info("i:{},{}", finalI,event.result().bodyAsJsonObject());
}else{
event.cause().printStackTrace();
}
});
log.info("发出请求:{}",i);
}
ThreadUtil.sync(this);
}
@Test
void exec2() {
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,45 +0,0 @@
package com.example.wxhk.util;
import com.example.wxhk.model.request.GetGroupMembers;
import com.example.wxhk.model.response.ContactList;
import com.example.wxhk.model.response.GroupMembers;
import org.dromara.hutool.core.lang.Console;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
@SpringBootTest
class HttpSendUtilTest {
@Test
void 获取当前登陆微信id() {
String s = HttpSendUtil.获取当前登陆微信id();
}
@Test
void 联系人列表() {
ContactList contactList = HttpSendUtil.联系人列表();
List<ContactList.DataBean> data = contactList.getData();
for (ContactList.DataBean datum : data) {
Console.log(datum.getWxid(),datum.getUserName());
}
Console.log(contactList);
}
@Test
void 开启hook() {
}
@Test
void 关闭ook() {
HttpSendUtil.关闭hook();
}
@Test
void 获取群成员() {
GroupMembers 获取群成员 = HttpSendUtil.获取群成员(new GetGroupMembers().setChatRoomId("24964676359@chatroom"));
Console.log(获取群成员);
}
}

View File

@ -1,493 +0,0 @@
import requests
import json
def checkLogin():
url = "127.0.0.1:19088/api/checkLogin"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def userInfo():
url = "127.0.0.1:19088/api/userInfo"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def sendTextMsg():
url = "127.0.0.1:19088/api/sendTextMsg"
payload = json.dumps({
"wxid": "filehelper",
"msg": "12www"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def sendImagesMsg():
url = "127.0.0.1:19088/api/sendImagesMsg"
print("modify imagePath")
raise RuntimeError("modify imagePath then deleted me")
payload = json.dumps({
"wxid": "filehelper",
"imagePath": "C:\\pic.png"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def sendFileMsg():
url = "127.0.0.1:19088/api/sendFileMsg"
print("modify filePath")
raise RuntimeError("modify filePath then deleted me")
payload = json.dumps({
"wxid": "filehelper",
"filePath": "C:\\test.zip"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def hookSyncMsg():
url = "127.0.0.1:19088/api/hookSyncMsg"
print("modify ip port url ")
raise RuntimeError("modify ip port url then deleted me")
payload = json.dumps({
"port": "19099",
"ip": "127.0.0.1",
"url": "http://localhost:8080",
"timeout": "3000",
"enableHttp": "0"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def unhookSyncMsg():
url = "127.0.0.1:19088/api/unhookSyncMsg"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def getContactList():
url = "127.0.0.1:19088/api/getContactList"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def getDBInfo():
url = "127.0.0.1:19088/api/getDBInfo"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def execSql():
url = "127.0.0.1:19088/api/execSql"
print("modify dbHandle ")
raise RuntimeError("modify dbHandle then deleted me")
payload = json.dumps({
"dbHandle": 1713425147584,
"sql": "select * from MSG where localId =100;"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def getChatRoomDetailInfo():
url = "127.0.0.1:19088/api/getChatRoomDetailInfo"
print("modify chatRoomId ")
raise RuntimeError("modify chatRoomId then deleted me")
payload = json.dumps({
"chatRoomId": "123333@chatroom"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def addMemberToChatRoom():
url = "127.0.0.1:19088/api/addMemberToChatRoom"
print("modify chatRoomId memberIds ")
raise RuntimeError("modify chatRoomId memberIds then deleted me")
payload = json.dumps({
"chatRoomId": "123@chatroom",
"memberIds": "wxid_123"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def delMemberFromChatRoom():
url = "127.0.0.1:19088/api/delMemberFromChatRoom"
print("modify chatRoomId memberIds ")
raise RuntimeError("modify chatRoomId memberIds then deleted me")
payload = json.dumps({
"chatRoomId": "21363231004@chatroom",
"memberIds": "wxid_123"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def modifyNickname():
url = "127.0.0.1:19088/api/modifyNickname"
print("modify chatRoomId wxid nickName")
raise RuntimeError("modify chatRoomId wxid nickName then deleted me")
payload = json.dumps({
"chatRoomId": "123@chatroom",
"wxid": "wxid_123",
"nickName": "test"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def getMemberFromChatRoom():
print("modify chatRoomId ")
raise RuntimeError("modify chatRoomId then deleted me")
url = "127.0.0.1:19088/api/getMemberFromChatRoom"
payload = json.dumps({
"chatRoomId": "123@chatroom"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def topMsg():
print("modify msgId ")
raise RuntimeError("modify msgId then deleted me")
url = "127.0.0.1:19088/api/topMsg"
payload = json.dumps({
"msgId": 1222222
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def removeTopMsg():
print("modify msgId chatRoomId ")
raise RuntimeError("modify msgId chatRoomId then deleted me")
url = "127.0.0.1:19088/api/removeTopMsg"
payload = json.dumps({
"chatRoomId": "123@chatroom",
"msgId": 123
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def InviteMemberToChatRoom():
print("modify memberIds chatRoomId ")
raise RuntimeError("modify memberIds chatRoomId then deleted me")
url = "127.0.0.1:19088/api/InviteMemberToChatRoom"
payload = json.dumps({
"chatRoomId": "123@chatroom",
"memberIds": "wxid_123"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def hookLog():
url = "127.0.0.1:19088/api/hookLog"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def unhookLog():
url = "127.0.0.1:19088/api/unhookLog"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def createChatRoom():
print("modify memberIds ")
raise RuntimeError("modify memberIds then deleted me")
url = "127.0.0.1:19088/api/createChatRoom"
payload = json.dumps({
"memberIds": "wxid_8yn4k908tdqp22,wxid_oyb662qhop4422"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def quitChatRoom():
print("modify chatRoomId ")
raise RuntimeError("modify chatRoomId then deleted me")
url = "127.0.0.1:19088/api/quitChatRoom"
payload = json.dumps({
"chatRoomId": "123@chatroom"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def forwardMsg():
print("modify msgId ")
raise RuntimeError("modify msgId then deleted me")
url = "127.0.0.1:19088/api/forwardMsg"
payload = json.dumps({
"wxid": "filehelper",
"msgId": "12331"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def getSNSFirstPage():
url = "127.0.0.1:19088/api/getSNSFirstPage"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def getSNSNextPage():
print("modify snsId ")
raise RuntimeError("modify snsId then deleted me")
url = "127.0.0.1:19088/api/getSNSNextPage"
payload = json.dumps({
"snsId": ""
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def addFavFromMsg():
print("modify msgId ")
raise RuntimeError("modify msgId then deleted me")
url = "127.0.0.1:19088/api/addFavFromMsg"
payload = json.dumps({
"msgId": "1222222"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def addFavFromImage():
print("modify wxid imagePath ")
raise RuntimeError("modify wxid imagePath then deleted me")
url = "127.0.0.1:19088/api/addFavFromImage"
payload = json.dumps({
"wxid": "",
"imagePath": ""
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def getContactProfile():
print("modify wxid ")
raise RuntimeError("modify wxid then deleted me")
url = "127.0.0.1:19088/api/getContactProfile"
payload = json.dumps({
"wxid": ""
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def sendAtText():
print("modify wxids chatRoomId")
raise RuntimeError("modify wxids chatRoomId then deleted me")
url = "127.0.0.1:19088/api/sendAtText"
payload = json.dumps({
"wxids": "notify@all",
"chatRoomId": "123@chatroom",
"msg": "你好啊"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def forwardPublicMsg():
print("modify param ")
raise RuntimeError("modify param then deleted me")
url = "127.0.0.1:19088/api/forwardPublicMsg"
payload = json.dumps({
"appName": "",
"userName": "",
"title": "",
"url": "",
"thumbUrl": "",
"digest": "",
"wxid": "filehelper"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def forwardPublicMsgByMsgId():
print("modify param ")
raise RuntimeError("modify param then deleted me")
url = "127.0.0.1:19088/api/forwardPublicMsgByMsgId"
payload = json.dumps({
"msgId": 123,
"wxid": "filehelper"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def downloadAttach():
print("modify param ")
raise RuntimeError("modify param then deleted me")
url = "127.0.0.1:19088/api/downloadAttach"
payload = json.dumps({
"msgId": 123
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def decodeImage():
print("modify param ")
raise RuntimeError("modify param then deleted me")
url = "127.0.0.1:19088/api/decodeImage"
payload = json.dumps({
"filePath": "C:\\66664816980131.dat",
"storeDir": "C:\\test"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def getVoiceByMsgId():
print("modify param ")
raise RuntimeError("modify param then deleted me")
url = "127.0.0.1:19088/api/getVoiceByMsgId"
payload = json.dumps({
"msgId": 7880439644200,
"storeDir": "c:\\test"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
if __name__ == '__main__':
checkLogin()
# userInfo()

View File

@ -1,595 +0,0 @@
import requests
import json
def check_login():
"""
0.检查是否登录
:return:
"""
url = "127.0.0.1:19088/api/?type=0"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def user_info():
"""
登录用户信息
:return:
"""
url = "127.0.0.1:19088/api/?type=8"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def send_text():
"""
发送文本
:return:
"""
url = "127.0.0.1:19088/api/?type=2"
payload = json.dumps({
"wxid": "filehelper",
"msg": "123"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def send_at():
"""
发送@消息
:return:
"""
url = "127.0.0.1:19088/api/?type=3"
payload = json.dumps({
"chatRoomId": "12333@chatroom",
"wxids": "notify@all",
"msg": "12333"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def send_img():
"""
发送图片
:return:
"""
url = "127.0.0.1:19088/api/?type=5"
payload = json.dumps({
"wxid": "filehelper",
"imagePath": "C:/123.png"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def send_file():
"""
发送文件
:return:
"""
url = "127.0.0.1:19088/api/?type=6"
payload = json.dumps({
"wxid": "filehelper",
"filePath": "C:/test.txt"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def hook_msg():
"""
hook 消息
:return:
"""
url = "127.0.0.1:19088/api/?type=9"
payload = json.dumps({
"port": "19099",
"ip": "127.0.0.1"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def unhook_msg():
"""
取消消息hook
:return:
"""
url = "127.0.0.1:19088/api/?type=10"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def hook_img():
"""
hook 图片
:return:
"""
url = "127.0.0.1:19088/api/?type=11"
payload = json.dumps({
"imgDir": "C:\\img"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def unhook_img():
"""
取消hook 图片
:return:
"""
url = "127.0.0.1:19088/api/?type=12"
payload = json.dumps({
"imgDir": "C:\\img"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def hook_voice():
"""
hook 语音
:return:
"""
url = "127.0.0.1:19088/api/?type=56"
payload = json.dumps({
"msgId": 322456091115784000
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def unhook_voice():
"""
取消hook 语音
:return:
"""
url = "127.0.0.1:19088/api/?type=14"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def del_friend():
"""
删除好友
:return:
"""
url = "127.0.0.1:19088/api/?type=17"
payload = json.dumps({
"wxid": "wxid_1124423322"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def search_friend():
"""
网络搜素用户
:return:
"""
url = "127.0.0.1:19088/api/?type=19"
payload = json.dumps({
"keyword": "13812345678"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def add_friend():
"""
添加好友
:return:
"""
url = "127.0.0.1:19088/api/?type=20"
payload = json.dumps({
"wxid": "wxid_o11222334422"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def fetch_chat_room_members():
"""
群成员
:return:
"""
url = "127.0.0.1:19088/api/?type=25"
payload = json.dumps({
"chatRoomId": "2112222004@chatroom"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def get_member_nickname():
"""
群成员昵称
:return:
"""
url = "127.0.0.1:19088/api/?type=26"
payload = json.dumps({
"chatRoomId": "322333384@chatroom",
"memberId": "wxid_4m1112222u22"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def del_member():
"""
删除群成员
:return:
"""
url = "127.0.0.1:19088/api/?type=27"
payload = json.dumps({
"chatRoomId": "31122263384@chatroom",
"memberIds": "wxid_12223334422"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def add_member():
"""
增加群成员
:return:
"""
url = "127.0.0.1:19088/api/?type=28"
payload = json.dumps({
"chatRoomId": "1111163384@chatroom",
"memberIds": "wxid_o12222222"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def modify_room_name():
"""
修改群昵称
:return:
"""
url = "127.0.0.1:19088/api/?type=31"
payload = json.dumps({
"chatRoomId": "222285428@chatroom",
"wxid": "wxid_222222512",
"nickName": "qqq"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def get_db_handlers():
"""
获取sqlite3的操作句柄
:return:
"""
url = "127.0.0.1:19088/api/?type=32"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def query_db_by_sql():
"""
查询数据库
:return:
"""
url = "127.0.0.1:19088/api/?type=34"
payload = json.dumps({
"dbHandle": 116201928,
"sql": "select localId from MSG where MsgSvrID= 7533111101686156"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def hook_log():
"""
hook 日志
:return:
"""
url = "127.0.0.1:19088/api/?type=36"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def unhook_log():
"""
取消hook日志
:return:
"""
url = "127.0.0.1:19088/api/?type=37"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def forward():
"""
转发消息
:return:
"""
url = "127.0.0.1:19088/api/?type=40"
payload = json.dumps({
"wxid": "filehelper",
"msgid": "705117679011122708"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def logout():
"""
退出登录
:return:
"""
url = "127.0.0.1:19088/api/?type=44"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def confirm_receipt():
"""
确认收款
:return:
"""
url = "127.0.0.1:19088/api/?type=45"
payload = json.dumps({
"wxid": "wxid_1111112622",
"transcationId": "10000500012312222212243388865912",
"transferId": "100005000120212222173123036"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def contact_list():
"""
好友列表
:return:
"""
url = "127.0.0.1:19088/api/?type=46"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def room_detail():
"""
群详情
:return:
"""
url = "127.0.0.1:19088/api/?type=47"
payload = json.dumps({
"chatRoomId": "199134446111@chatroom"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def ocr():
"""
ocr提取文字
:return:
"""
url = "127.0.0.1:19088/api/?type=49"
payload = json.dumps({
"imagePath": "C:\\WeChat Files\\b23e84997144dd12f21554b0.dat"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def pat():
"""
拍一拍
:return:
"""
url = "127.0.0.1:19088/api/?type=50"
payload = json.dumps({
"chatRoomId": "211111121004@chatroom",
"wxid": "wxid_111111111422"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def top_msg():
"""
消息置顶
:return:
"""
url = "127.0.0.1:19088/api/?type=51"
payload = json.dumps({
"wxid": "wxid_o11114422",
"msgid": 3728307145189195000
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def close_top_msg():
"""
取消置顶
:return:
"""
url = "127.0.0.1:19088/api/?type=52"
payload = json.dumps({
"chatRoomId": "213222231004@chatroom",
"msgid": 3728307145189195000
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def sns_first():
"""
朋友圈首页
:return:
"""
url = "127.0.0.1:19088/api/?type=53"
payload = {}
headers = {}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def sns_next():
"""
朋友圈下一页
:return:
"""
url = "127.0.0.1:19088/api/?type=54"
payload = json.dumps({
"snsId": "14091988153735844377"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def query_nickname():
"""
查询联系人或群名称
:return:
"""
url = "127.0.0.1:19088/api/?type=55"
payload = json.dumps({
"id": "wxid_1112p4422"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def download_msg_attach():
"""
下载消息附件
:return:
"""
url = "127.0.0.1:19088/api/?type=56"
payload = json.dumps({
"msgId": 6080100336053626000
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
def get_member_info():
"""
获取群/群成员信息
:return:
"""
url = "127.0.0.1:19088/api/?type=57"
payload = json.dumps({
"wxid": "wxid_tx8k6tu21112"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
if __name__ == '__main__':
check_login()
user_info()
send_text()

View File

@ -1,51 +0,0 @@
import ctypes
import hashlib
import hmac
# pip install pycryptodome
from Crypto.Cipher import AES
def decrypt(password, input_file, out_file):
password = bytes.fromhex(password.replace(' ', ''))
with open(input_file, 'rb') as (f):
blist = f.read()
print(len(blist))
salt = blist[:16]
key = hashlib.pbkdf2_hmac('sha1', password, salt, DEFAULT_ITER, KEY_SIZE)
first = blist[16:DEFAULT_PAGESIZE]
mac_salt = bytes([x ^ 58 for x in salt])
mac_key = hashlib.pbkdf2_hmac('sha1', key, mac_salt, 2, KEY_SIZE)
hash_mac = hmac.new(mac_key, digestmod='sha1')
hash_mac.update(first[:-32])
hash_mac.update(bytes(ctypes.c_int(1)))
if hash_mac.digest() == first[-32:-12]:
print('decrypt success')
else:
print('password error')
return
blist = [blist[i:i + DEFAULT_PAGESIZE] for i in range(DEFAULT_PAGESIZE, len(blist), DEFAULT_PAGESIZE)]
with open(out_file, 'wb') as (f):
f.write(SQLITE_FILE_HEADER)
t = AES.new(key, AES.MODE_CBC, first[-48:-32])
f.write(t.decrypt(first[:-48]))
f.write(first[-48:])
for i in blist:
t = AES.new(key, AES.MODE_CBC, i[-48:-32])
f.write(t.decrypt(i[:-48]))
f.write(i[-48:])
def main():
password = '565735E30E474DA09250CB5AA047E3940FFA1C6F767C4263B13ABB512933DA49'
input_file = 'C:/var/Applet.db'
out_file = 'c:/var/out/Applet.db'
decrypt(password, input_file, out_file)
if __name__ == '__main__':
SQLITE_FILE_HEADER = bytes('SQLite format 3', encoding='ASCII') + bytes(1)
KEY_SIZE = 32
DEFAULT_PAGESIZE = 4096
DEFAULT_ITER = 64000
main()

View File

@ -1,26 +0,0 @@
from fastapi import FastAPI, Request
app = FastAPI()
# pip install fastapi
# run command :uvicorn test:app --reload
# 127.0.0.1:8000/api
@app.post("/api")
def create_item(request: Request):
print("recv msg")
return {"code": 0, "msg": "success"}
@app.middleware("http")
async def TestCustomMiddleware(request: Request, call_next):
the_headers = request.headers
the_body = await request.json()
print(the_headers)
print(the_body)
response = await call_next(request)
return response

View File

@ -1,10 +0,0 @@
### 常用的一些工具
client.py : 快速测试dll的http接口。
decrpt.py : 微信数据库解密工具。password 为dll个人信息里返回的dbkey。
http_server.py : 一个简单的http server用来接收hook的消息。
tcpserver.py: 一个简单的tcp server用来接收hook的消息。

View File

@ -1,57 +0,0 @@
import json
import threading
import socketserver
class ReceiveMsgSocketServer(socketserver.BaseRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def handle(self):
conn = self.request
while True:
try:
ptr_data = b""
while True:
data = conn.recv(1024)
ptr_data += data
if len(data) == 0 or data[-1] == 0xA:
break
msg = json.loads(ptr_data)
ReceiveMsgSocketServer.msg_callback(msg)
except OSError:
break
except json.JSONDecodeError:
pass
conn.sendall("200 OK".encode())
conn.close()
@staticmethod
def msg_callback(msg):
print(msg)
def start_socket_server(port: int = 19099,
request_handler=ReceiveMsgSocketServer,
main_thread: bool = True) -> int or None:
ip_port = ("127.0.0.1", port)
try:
s = socketserver.ThreadingTCPServer(ip_port, request_handler)
if main_thread:
s.serve_forever()
else:
socket_server = threading.Thread(target=s.serve_forever)
socket_server.setDaemon(True)
socket_server.start()
return socket_server.ident
except KeyboardInterrupt:
pass
except Exception as e:
print(e)
return None
if __name__ == '__main__':
start_socket_server()

View File

@ -1,20 +0,0 @@
cmake_minimum_required(VERSION 3.0.0)
project(injector VERSION 1.0.0)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D '_UNICODE' /D 'UNICODE'")
file(GLOB INJECT_CPP_FILES ${PROJECT_SOURCE_DIR}/*.cc ${PROJECT_SOURCE_DIR}/*.cpp)
add_executable (injector ${INJECT_CPP_FILES})
SET_TARGET_PROPERTIES(injector PROPERTIES LINKER_LANGUAGE C
ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin
LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin
RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin
OUTPUT_NAME "injector"
PREFIX "")

View File

@ -1,659 +0,0 @@
#ifndef __GETOPT_H__
/**
* DISCLAIMER
* This file is part of the mingw-w64 runtime package.
*
* The mingw-w64 runtime package and its code is distributed in the hope that it
* will be useful but WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESSED OR
* IMPLIED ARE HEREBY DISCLAIMED. This includes but is not limited to
* warranties of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
/*
* Copyright (c) 2002 Todd C. Miller <Todd.Miller@courtesan.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Sponsored in part by the Defense Advanced Research Projects
* Agency (DARPA) and Air Force Research Laboratory, Air Force
* Materiel Command, USAF, under agreement number F39502-99-1-0512.
*/
/*-
* Copyright (c) 2000 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Dieter Baron and Thomas Klausner.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#pragma warning(disable:4996);
#define __GETOPT_H__
/* All the headers include this file. */
#include <crtdefs.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <windows.h>
#ifdef __cplusplus
extern "C" {
#endif
#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */
#ifdef REPLACE_GETOPT
int opterr = 1; /* if error message should be printed */
int optind = 1; /* index into parent argv vector */
int optopt = '?'; /* character checked for validity */
#undef optreset /* see getopt.h */
#define optreset __mingw_optreset
int optreset; /* reset getopt */
char* optarg; /* argument associated with option */
#endif
//extern int optind; /* index of first non-option in argv */
//extern int optopt; /* single option character, as parsed */
//extern int opterr; /* flag to enable built-in diagnostics... */
// /* (user may set to zero, to suppress) */
//
//extern char *optarg; /* pointer to argument of current option */
#define PRINT_ERROR ((opterr) && (*options != ':'))
#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */
#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */
#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */
/* return values */
#define BADCH (int)'?'
#define BADARG ((*options == ':') ? (int)':' : (int)'?')
#define INORDER (int)1
#ifndef __CYGWIN__
#define __progname __argv[0]
#else
extern char __declspec(dllimport)* __progname;
#endif
#ifdef __CYGWIN__
static char EMSG[] = "";
#else
#define EMSG ""
#endif
static int getopt_internal(int, char* const*, const char*,
const struct option*, int*, int);
static int parse_long_options(char* const*, const char*,
const struct option*, int*, int);
static int gcd(int, int);
static void permute_args(int, int, int, char* const*);
static char* place = EMSG; /* option letter processing */
/* XXX: set optreset to 1 rather than these two */
static int nonopt_start = -1; /* first non option argument (for permute) */
static int nonopt_end = -1; /* first option after non options (for permute) */
/* Error messages */
static const char recargchar[] = "option requires an argument -- %c";
static const char recargstring[] = "option requires an argument -- %s";
static const char ambig[] = "ambiguous option -- %.*s";
static const char noarg[] = "option doesn't take an argument -- %.*s";
static const char illoptchar[] = "unknown option -- %c";
static const char illoptstring[] = "unknown option -- %s";
static void
_vwarnx(const char* fmt, va_list ap)
{
(void)fprintf(stderr, "%s: ", __progname);
if (fmt != NULL)
(void)vfprintf(stderr, fmt, ap);
(void)fprintf(stderr, "\n");
}
static void
warnx(const char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
_vwarnx(fmt, ap);
va_end(ap);
}
/*
* Compute the greatest common divisor of a and b.
*/
static int
gcd(int a, int b)
{
int c;
c = a % b;
while (c != 0) {
a = b;
b = c;
c = a % b;
}
return (b);
}
/*
* Exchange the block from nonopt_start to nonopt_end with the block
* from nonopt_end to opt_end (keeping the same order of arguments
* in each block).
*/
static void
permute_args(int panonopt_start, int panonopt_end, int opt_end,
char* const* nargv)
{
int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos;
char* swap;
/*
* compute lengths of blocks and number and size of cycles
*/
nnonopts = panonopt_end - panonopt_start;
nopts = opt_end - panonopt_end;
ncycle = gcd(nnonopts, nopts);
cyclelen = (opt_end - panonopt_start) / ncycle;
for (i = 0; i < ncycle; i++) {
cstart = panonopt_end + i;
pos = cstart;
for (j = 0; j < cyclelen; j++) {
if (pos >= panonopt_end)
pos -= nnonopts;
else
pos += nopts;
swap = nargv[pos];
/* LINTED const cast */
((char**)nargv)[pos] = nargv[cstart];
/* LINTED const cast */
((char**)nargv)[cstart] = swap;
}
}
}
#ifdef REPLACE_GETOPT
/*
* getopt --
* Parse argc/argv argument vector.
*
* [eventually this will replace the BSD getopt]
*/
int
getopt(int nargc, char* const* nargv, const char* options)
{
/*
* We don't pass FLAG_PERMUTE to getopt_internal() since
* the BSD getopt(3) (unlike GNU) has never done this.
*
* Furthermore, since many privileged programs call getopt()
* before dropping privileges it makes sense to keep things
* as simple (and bug-free) as possible.
*/
return (getopt_internal(nargc, nargv, options, NULL, NULL, 0));
}
#endif /* REPLACE_GETOPT */
//extern int getopt(int nargc, char * const *nargv, const char *options);
#ifdef _BSD_SOURCE
/*
* BSD adds the non-standard `optreset' feature, for reinitialisation
* of `getopt' parsing. We support this feature, for applications which
* proclaim their BSD heritage, before including this header; however,
* to maintain portability, developers are advised to avoid it.
*/
# define optreset __mingw_optreset
extern int optreset;
#endif
#ifdef __cplusplus
}
#endif
/*
* POSIX requires the `getopt' API to be specified in `unistd.h';
* thus, `unistd.h' includes this header. However, we do not want
* to expose the `getopt_long' or `getopt_long_only' APIs, when
* included in this manner. Thus, close the standard __GETOPT_H__
* declarations block, and open an additional __GETOPT_LONG_H__
* specific block, only when *not* __UNISTD_H_SOURCED__, in which
* to declare the extended API.
*/
#endif /* !defined(__GETOPT_H__) */
#if !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__)
#define __GETOPT_LONG_H__
#ifdef __cplusplus
extern "C" {
#endif
struct option /* specification for a long form option... */
{
const char* name; /* option name, without leading hyphens */
int has_arg; /* does it take an argument? */
int* flag; /* where to save its status, or NULL */
int val; /* its associated status value */
};
enum /* permitted values for its `has_arg' field... */
{
no_argument = 0, /* option never takes an argument */
required_argument, /* option always requires an argument */
optional_argument /* option may take an argument */
};
/*
* parse_long_options --
* Parse long options in argc/argv argument vector.
* Returns -1 if short_too is set and the option does not match long_options.
*/
static int
parse_long_options(char* const* nargv, const char* options,
const struct option* long_options, int* idx, int short_too)
{
char* current_argv, * has_equal;
size_t current_argv_len;
int i, ambiguous, match;
#define IDENTICAL_INTERPRETATION(_x, _y) \
(long_options[(_x)].has_arg == long_options[(_y)].has_arg && \
long_options[(_x)].flag == long_options[(_y)].flag && \
long_options[(_x)].val == long_options[(_y)].val)
current_argv = place;
match = -1;
ambiguous = 0;
optind++;
if ((has_equal = strchr(current_argv, '=')) != NULL) {
/* argument found (--option=arg) */
current_argv_len = has_equal - current_argv;
has_equal++;
}
else
current_argv_len = strlen(current_argv);
for (i = 0; long_options[i].name; i++) {
/* find matching long option */
if (strncmp(current_argv, long_options[i].name,
current_argv_len))
continue;
if (strlen(long_options[i].name) == current_argv_len) {
/* exact match */
match = i;
ambiguous = 0;
break;
}
/*
* If this is a known short option, don't allow
* a partial match of a single character.
*/
if (short_too && current_argv_len == 1)
continue;
if (match == -1) /* partial match */
match = i;
else if (!IDENTICAL_INTERPRETATION(i, match))
ambiguous = 1;
}
if (ambiguous) {
/* ambiguous abbreviation */
if (PRINT_ERROR)
warnx(ambig, (int)current_argv_len,
current_argv);
optopt = 0;
return (BADCH);
}
if (match != -1) { /* option found */
if (long_options[match].has_arg == no_argument
&& has_equal) {
if (PRINT_ERROR)
warnx(noarg, (int)current_argv_len,
current_argv);
/*
* XXX: GNU sets optopt to val regardless of flag
*/
if (long_options[match].flag == NULL)
optopt = long_options[match].val;
else
optopt = 0;
return (BADARG);
}
if (long_options[match].has_arg == required_argument ||
long_options[match].has_arg == optional_argument) {
if (has_equal)
optarg = has_equal;
else if (long_options[match].has_arg ==
required_argument) {
/*
* optional argument doesn't use next nargv
*/
optarg = nargv[optind++];
}
}
if ((long_options[match].has_arg == required_argument)
&& (optarg == NULL)) {
/*
* Missing argument; leading ':' indicates no error
* should be generated.
*/
if (PRINT_ERROR)
warnx(recargstring,
current_argv);
/*
* XXX: GNU sets optopt to val regardless of flag
*/
if (long_options[match].flag == NULL)
optopt = long_options[match].val;
else
optopt = 0;
--optind;
return (BADARG);
}
}
else { /* unknown option */
if (short_too) {
--optind;
return (-1);
}
if (PRINT_ERROR)
warnx(illoptstring, current_argv);
optopt = 0;
return (BADCH);
}
if (idx)
*idx = match;
if (long_options[match].flag) {
*long_options[match].flag = long_options[match].val;
return (0);
}
else
return (long_options[match].val);
#undef IDENTICAL_INTERPRETATION
}
/*
* getopt_internal --
* Parse argc/argv argument vector. Called by user level routines.
*/
static int
getopt_internal(int nargc, char* const* nargv, const char* options,
const struct option* long_options, int* idx, int flags)
{
char* oli; /* option letter list index */
int optchar, short_too;
static int posixly_correct = -1;
if (options == NULL)
return (-1);
/*
* XXX Some GNU programs (like cvs) set optind to 0 instead of
* XXX using optreset. Work around this braindamage.
*/
if (optind == 0)
optind = optreset = 1;
/*
* Disable GNU extensions if POSIXLY_CORRECT is set or options
* string begins with a '+'.
*
* CV, 2009-12-14: Check POSIXLY_CORRECT anew if optind == 0 or
* optreset != 0 for GNU compatibility.
*/
if (posixly_correct == -1 || optreset != 0)
posixly_correct = (getenv("POSIXLY_CORRECT") != NULL);
if (*options == '-')
flags |= FLAG_ALLARGS;
else if (posixly_correct || *options == '+')
flags &= ~FLAG_PERMUTE;
if (*options == '+' || *options == '-')
options++;
optarg = NULL;
if (optreset)
nonopt_start = nonopt_end = -1;
start:
if (optreset || !*place) { /* update scanning pointer */
optreset = 0;
if (optind >= nargc) { /* end of argument vector */
place = EMSG;
if (nonopt_end != -1) {
/* do permutation, if we have to */
permute_args(nonopt_start, nonopt_end,
optind, nargv);
optind -= nonopt_end - nonopt_start;
}
else if (nonopt_start != -1) {
/*
* If we skipped non-options, set optind
* to the first of them.
*/
optind = nonopt_start;
}
nonopt_start = nonopt_end = -1;
return (-1);
}
if (*(place = nargv[optind]) != '-' ||
(place[1] == '\0' && strchr(options, '-') == NULL)) {
place = EMSG; /* found non-option */
if (flags & FLAG_ALLARGS) {
/*
* GNU extension:
* return non-option as argument to option 1
*/
optarg = nargv[optind++];
return (INORDER);
}
if (!(flags & FLAG_PERMUTE)) {
/*
* If no permutation wanted, stop parsing
* at first non-option.
*/
return (-1);
}
/* do permutation */
if (nonopt_start == -1)
nonopt_start = optind;
else if (nonopt_end != -1) {
permute_args(nonopt_start, nonopt_end,
optind, nargv);
nonopt_start = optind -
(nonopt_end - nonopt_start);
nonopt_end = -1;
}
optind++;
/* process next argument */
goto start;
}
if (nonopt_start != -1 && nonopt_end == -1)
nonopt_end = optind;
/*
* If we have "-" do nothing, if "--" we are done.
*/
if (place[1] != '\0' && *++place == '-' && place[1] == '\0') {
optind++;
place = EMSG;
/*
* We found an option (--), so if we skipped
* non-options, we have to permute.
*/
if (nonopt_end != -1) {
permute_args(nonopt_start, nonopt_end,
optind, nargv);
optind -= nonopt_end - nonopt_start;
}
nonopt_start = nonopt_end = -1;
return (-1);
}
}
/*
* Check long options if:
* 1) we were passed some
* 2) the arg is not just "-"
* 3) either the arg starts with -- we are getopt_long_only()
*/
if (long_options != NULL && place != nargv[optind] &&
(*place == '-' || (flags & FLAG_LONGONLY))) {
short_too = 0;
if (*place == '-')
place++; /* --foo long option */
else if (*place != ':' && strchr(options, *place) != NULL)
short_too = 1; /* could be short option too */
optchar = parse_long_options(nargv, options, long_options,
idx, short_too);
if (optchar != -1) {
place = EMSG;
return (optchar);
}
}
if ((optchar = (int)*place++) == (int)':' ||
(optchar == (int)'-' && *place != '\0') ||
(oli = (char*)strchr(options, optchar)) == NULL) {
/*
* If the user specified "-" and '-' isn't listed in
* options, return -1 (non-option) as per POSIX.
* Otherwise, it is an unknown option character (or ':').
*/
if (optchar == (int)'-' && *place == '\0')
return (-1);
if (!*place)
++optind;
if (PRINT_ERROR)
warnx(illoptchar, optchar);
optopt = optchar;
return (BADCH);
}
if (long_options != NULL && optchar == 'W' && oli[1] == ';') {
/* -W long-option */
if (*place) /* no space */
/* NOTHING */;
else if (++optind >= nargc) { /* no arg */
place = EMSG;
if (PRINT_ERROR)
warnx(recargchar, optchar);
optopt = optchar;
return (BADARG);
}
else /* white space */
place = nargv[optind];
optchar = parse_long_options(nargv, options, long_options,
idx, 0);
place = EMSG;
return (optchar);
}
if (*++oli != ':') { /* doesn't take argument */
if (!*place)
++optind;
}
else { /* takes (optional) argument */
optarg = NULL;
if (*place) /* no white space */
optarg = place;
else if (oli[1] != ':') { /* arg not optional */
if (++optind >= nargc) { /* no arg */
place = EMSG;
if (PRINT_ERROR)
warnx(recargchar, optchar);
optopt = optchar;
return (BADARG);
}
else
optarg = nargv[optind];
}
place = EMSG;
++optind;
}
/* dump back option letter */
return (optchar);
}
/*
* getopt_long --
* Parse argc/argv argument vector.
*/
int
getopt_long(int nargc, char* const* nargv, const char* options,
const struct option* long_options, int* idx)
{
return (getopt_internal(nargc, nargv, options, long_options, idx,
FLAG_PERMUTE));
}
/*
* getopt_long_only --
* Parse argc/argv argument vector.
*/
int
getopt_long_only(int nargc, char* const* nargv, const char* options,
const struct option* long_options, int* idx)
{
return (getopt_internal(nargc, nargv, options, long_options, idx,
FLAG_PERMUTE | FLAG_LONGONLY));
}
//extern int getopt_long(int nargc, char * const *nargv, const char *options,
// const struct option *long_options, int *idx);
//extern int getopt_long_only(int nargc, char * const *nargv, const char *options,
// const struct option *long_options, int *idx);
/*
* Previous MinGW implementation had...
*/
#ifndef HAVE_DECL_GETOPT
/*
* ...for the long form API only; keep this for compatibility.
*/
# define HAVE_DECL_GETOPT 1
#endif
#ifdef __cplusplus
}
#endif
#endif /* !defined(__UNISTD_H_SOURCED__) && !defined(__GETOPT_LONG_H__) */

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

View File

@ -1,14 +0,0 @@
## 可以使用对应分支下的注入工具,或者自己编译一下 source目录下的注入程序。
1.ConsoleApplication.exe
编译好的x64版本的注入器
命令行注入工具,注入命令
``` javascript
//-i 注入程序名 -p 注入dll路径
// -u 卸载程序名 -d 卸载dll名称
//注入
ConsoleInject.exe -i demo.exe -p E:\wxhelper.dll
//卸载
ConsoleInject.exe -u demo.exe -d wxhelper.dll
```

View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<annotationProcessing>
<profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<module name="weChatHook-java" />
</profile>
</annotationProcessing>
</component>
</project>

View File

@ -1,36 +0,0 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="JavaDoc" enabled="true" level="WARNING" enabled_by_default="true">
<option name="TOP_LEVEL_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="INNER_CLASS_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="METHOD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="@return@param@throws or @exception" />
</value>
</option>
<option name="FIELD_OPTIONS">
<value>
<option name="ACCESS_JAVADOC_REQUIRED_FOR" value="none" />
<option name="REQUIRED_TAGS" value="" />
</value>
</option>
<option name="IGNORE_DEPRECATED" value="false" />
<option name="IGNORE_JAVADOC_PERIOD" value="true" />
<option name="IGNORE_DUPLICATED_THROWS" value="false" />
<option name="IGNORE_POINT_TO_ITSELF" value="false" />
<option name="myAdditionalJavadocTags" value="date" />
</inspection_tool>
</profile>
</component>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RemoteRepositoriesConfiguration">
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Central Repository" />
<option name="url" value="https://repo.maven.apache.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="central" />
<option name="name" value="Maven Central repository" />
<option name="url" value="https://repo1.maven.org/maven2" />
</remote-repository>
<remote-repository>
<option name="id" value="jboss.community" />
<option name="name" value="JBoss Community repository" />
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
</remote-repository>
</component>
</project>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">
<option name="originalFiles">
<list>
<option value="$PROJECT_DIR$/pom.xml" />
</list>
</option>
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

View File

@ -1,29 +0,0 @@
#maven打包
```aidl
进入weChatHook-java项目根目录执行如下命令进行打包
mvn package
打包完成后在target目录下找到
weChatHook-java-1.0-jar-with-dependencies.jar
文件就可以直接启动了
```
#启动命令
####命令参数说明
###port:监听的端口 默认端口19077
###hookApi消息转发的接口 为空不转发
```aidl
java -jar .\weChatHook-java-1.0-jar-with-dependencies.jar --port=9999 --hookApi=http://localhost:29099/api/demo/msg
```
#java接收hook消息示例
```aidl
@RequestMapping("/api/demo")
public class DemoController {
@PostMapping("/msg")
public void getMsg(String msg){
JSONObject jsonObject = JSON.parseObject(msg);
jsonObject.forEach((k,v)->{
System.out.println(k+":"+v);
});
}
```

View File

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>weChatHook-java</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- jsoup 依赖-->
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.14.3</version>
</dependency>
<!-- fastjson 阿里依赖-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.83</version>
</dependency>
<!-- lombok 依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<!-- netty 依赖-->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.51.Final</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>com.example.service.WeChatHookNettyServer</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,619 +0,0 @@
package com.example.client;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.SneakyThrows;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import java.util.HashMap;
import java.util.Map;
/**
* @PACKAGE_NAME: com.example.client
* @NAME: WeChatHookClient
* @AUTHOR: wxs
* @DATE: 2023/5/31 15:08
* @PROJECT_NAME: WeChatHook-java
**/
public class WeChatHookClient {
private static final String apiPath = "http://127.0.0.1:19088/api/";
public static void main(String[] args) {
System.out.println(check_login());
}
/**
* 检查是否登录
*
* @return
*/
public static JSONObject check_login() {
String url = apiPath + "?type=0";
JSONObject response = post(url, null);
return response;
}
/**
* 登录用户信息
*
* @return
*/
public static JSONObject user_info() {
String url = apiPath + "?type=8";
JSONObject response = post(url, null);
System.out.println(response);
return response;
}
/**
* 发送文本
*
* @param wxid
* @param msg
* @return
*/
public static JSONObject send_text(String wxid, String msg) {
String url = apiPath + "?type=2";
Map<String, Object> map = new HashMap<>();
map.put("wxid", wxid);
map.put("msg", msg);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 发送@消息
*
* @param chatRoomId
* @param wxids notify@all
* @param msg
* @return
*/
public static JSONObject send_at(String chatRoomId, String wxids, String msg) {
String url = apiPath + "?type=3";
Map<String, Object> map = new HashMap<>();
map.put("chatRoomId", chatRoomId);
map.put("wxids", wxids);
map.put("msg", msg);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 发送图片
*
* @param wxid
* @param imagePath C:/Users/ww/Downloads/素材图片 (4).jpg
* @return
*/
public static JSONObject send_img(String wxid, String imagePath) {
String url = apiPath + "?type=5";
Map<String, Object> map = new HashMap<>();
map.put("wxid", wxid);
map.put("imagePath", imagePath);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 发送文件
*
* @param wxid
* @param filePath C:/test.txt
* @return
*/
public static JSONObject send_file(String wxid, String filePath) {
String url = apiPath + "?type=6";
Map<String, Object> map = new HashMap<>();
map.put("wxid", wxid);
map.put("filePath", filePath);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* hook 消息
*
* @param ip
* @param port
* @return
*/
public static JSONObject hook_msg(String ip, String port) {
String url = apiPath + "?type=9";
Map<String, Object> map = new HashMap<>();
map.put("ip", ip);
map.put("port", port);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 取消消息hook
*
* @return
*/
public static JSONObject unhook_msg() {
String url = apiPath + "?type=10";
JSONObject response = post(url, null);
System.out.println(response);
return response;
}
/**
* hook 图片
*
* @return
*/
public static JSONObject hook_img(String imgDir) {
String url = apiPath + "?type=11";
Map<String, Object> map = new HashMap<>();
map.put("imgDir", imgDir);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 取消hook 图片
*
* @param imgDir C:\img
* @return
*/
public static JSONObject unhook_img(String imgDir) {
String url = apiPath + "?type=12";
Map<String, Object> map = new HashMap<>();
map.put("imgDir", imgDir);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* hook 语音
*
* @return
*/
public static JSONObject hook_voice(Long msgId) {
String url = apiPath + "?type=56";
Map<String, Object> map = new HashMap<>();
map.put("msgId", msgId);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 取消hook 语音
*
* @return
*/
public static JSONObject unhook_voice() {
String url = apiPath + "?type=14";
JSONObject response = post(url, null);
System.out.println(response);
return response;
}
/**
* 删除好友
*
* @param wxid
* @return
*/
public static JSONObject del_friend(String wxid) {
String url = apiPath + "?type=17";
Map<String, Object> map = new HashMap<>();
map.put("wxid", wxid);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 网络搜素用户
*
* @return
*/
public static JSONObject search_friend(String keyword) {
String url = apiPath + "?type=19";
Map<String, Object> map = new HashMap<>();
map.put("keyword", keyword);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 添加好友
*
* @param wxid
* @return
*/
public static JSONObject add_friend(String wxid) {
String url = apiPath + "?type=20";
Map<String, Object> map = new HashMap<>();
map.put("wxid", wxid);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 群成员
*
* @param chatRoomId
* @return
*/
public static JSONObject fetch_chat_room_members(String chatRoomId) {
String url = apiPath + "?type=25";
Map<String, Object> map = new HashMap<>();
map.put("chatRoomId", chatRoomId);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 群成员昵称
*
* @param chatRoomId
* @param memberId
* @return
*/
public static JSONObject get_member_nickname(String chatRoomId, String memberId) {
String url = apiPath + "?type=26";
Map<String, Object> map = new HashMap<>();
map.put("chatRoomId", chatRoomId);
map.put("memberId", memberId);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 删除群成员
*
* @param chatRoomId
* @param memberIds
* @return
*/
public static JSONObject del_member(String chatRoomId, String memberIds) {
String url = apiPath + "?type=27";
Map<String, Object> map = new HashMap<>();
map.put("chatRoomId", chatRoomId);
map.put("memberIds", memberIds);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 增加群成员
*
* @param chatRoomId
* @param memberIds
* @return
*/
public static JSONObject add_member(String chatRoomId, String memberIds) {
String url = apiPath + "?type=28";
Map<String, Object> map = new HashMap<>();
map.put("chatRoomId", chatRoomId);
map.put("memberIds", memberIds);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 修改群昵称
*
* @param chatRoomId
* @param wxid
* @param nickName
* @return
*/
public static JSONObject modify_room_name(String chatRoomId, String wxid, String nickName) {
String url = apiPath + "?type=31";
Map<String, Object> map = new HashMap<>();
map.put("chatRoomId", chatRoomId);
map.put("wxid", wxid);
map.put("nickName", nickName);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 获取sqlite3的操作句柄
*
* @return
*/
public static JSONObject get_db_handlers() {
String url = apiPath + "?type=32";
JSONObject response = post(url, null);
System.out.println(response);
return response;
}
/**
* 查询数据库
*
* @param dbHandle
* @param sql
* @return
*/
public static JSONObject query_db_by_sql(String dbHandle, String sql) {
String url = apiPath + "?type=34";
Map<String, Object> map = new HashMap<>();
map.put("dbHandle", dbHandle);
map.put("sql", sql);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* hook 日志
*
* @return
*/
public static JSONObject hook_log() {
String url = apiPath + "?type=36";
JSONObject response = post(url, null);
System.out.println(response);
return response;
}
/**
* 取消hook日志
*
* @return
*/
public static JSONObject unhook_log() {
String url = apiPath + "?type=37";
JSONObject response = post(url, null);
System.out.println(response);
return response;
}
/**
* 转发消息
*
* @param wxid
* @param msgid
* @return
*/
public static JSONObject forward(String wxid, String msgid) {
String url = apiPath + "?type=40";
Map<String, Object> map = new HashMap<>();
map.put("wxid", wxid);
map.put("msgid", msgid);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 退出登录
*
* @return
*/
public static JSONObject logout() {
String url = apiPath + "?type=44";
JSONObject response = post(url, null);
System.out.println(response);
return response;
}
/**
* 确认收款
*
* @param wxid
* @param transcationId
* @param transferId
* @return
*/
public static JSONObject confirm_receipt(String wxid, String transcationId, String transferId) {
String url = apiPath + "?type=45";
Map<String, Object> map = new HashMap<>();
map.put("wxid", wxid);
map.put("transcationId", transcationId);
map.put("transferId", transferId);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 好友列表
*
* @return
*/
public static JSONObject contact_list() {
String url = apiPath + "?type=46";
JSONObject response = post(url, null);
System.out.println(response);
return response;
}
/**
* 群详情
*
* @param chatRoomId
* @return
*/
public static JSONObject room_detail(String chatRoomId) {
String url = apiPath + "?type=47";
Map<String, Object> map = new HashMap<>();
map.put("chatRoomId", chatRoomId);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* ocr提取文字
*
* @param imagePath
* @return
*/
public static JSONObject ocr(String imagePath) {
String url = apiPath + "?type=49";
Map<String, Object> map = new HashMap<>();
map.put("imagePath", imagePath);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 拍一拍
*
* @param chatRoomId
* @param wxid
* @return
*/
public static JSONObject pat(String chatRoomId, String wxid) {
String url = apiPath + "?type=50";
Map<String, Object> map = new HashMap<>();
map.put("chatRoomId", chatRoomId);
map.put("wxid", wxid);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 消息置顶
*
* @param chatRoomId
* @param msgid
* @return
*/
public static JSONObject top_msg(String chatRoomId, Long msgid) {
String url = apiPath + "?type=51";
Map<String, Object> map = new HashMap<>();
map.put("chatRoomId", chatRoomId);
map.put("msgid", msgid);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 取消置顶
*
* @param chatRoomId
* @param msgid
* @return
*/
public static JSONObject close_top_msg(String chatRoomId, Long msgid) {
String url = apiPath + "?type=52";
Map<String, Object> map = new HashMap<>();
map.put("chatRoomId", chatRoomId);
map.put("msgid", msgid);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 朋友圈首页
*
* @return
*/
public static JSONObject sns_first() {
String url = apiPath + "?type=53";
JSONObject response = post(url, null);
System.out.println(response);
return response;
}
/**
* 朋友圈下一页
*
* @param snsId
* @return
*/
public static JSONObject sns_next(String snsId) {
String url = apiPath + "?type=54";
Map<String, Object> map = new HashMap<>();
map.put("snsId", snsId);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 查询联系人或群名称
*
* @param wxid 微信id
* @return
*/
public static JSONObject query_nickname(String wxid) {
String url = apiPath + "?type=55";
Map<String, Object> map = new HashMap<>();
map.put("id", wxid);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 下载消息附件
*
* @param msgId
* @return
*/
public static JSONObject download_msg_attach(Long msgId) {
String url = apiPath + "?type=56";
Map<String, Object> map = new HashMap<>();
map.put("msgId", msgId);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
/**
* 获取群/群成员信息
*
* @param wxid
* @return
*/
public static JSONObject get_member_info(String wxid) {
String url = apiPath + "?type=57";
Map<String, String> map = new HashMap<>();
map.put("wxid", wxid);
JSONObject response = post(url, JSON.toJSONString(map));
System.out.println(response);
return response;
}
@SneakyThrows
public static JSONObject post(String url, String json) {
String body = Jsoup.connect(url)
.method(Connection.Method.POST)
.header("Content-Type", "application/json")
.requestBody(json)
.execute().body();
return JSON.parseObject(body);
}
@SneakyThrows
public static JSONObject hook(String url, String json) {
String body = Jsoup.connect(url)
.data("msg",json)
.method(Connection.Method.POST)
.timeout(1000)
.execute().body();
return JSON.parseObject(body);
}
}

View File

@ -1,272 +0,0 @@
package com.example.service;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.example.client.WeChatHookClient;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.StringUtil;
import java.io.*;
import java.net.InetSocketAddress;
import java.util.*;
/**
* @PACKAGE_NAME: com.example.service
* @NAME: WeChatHookNettyServer
* @AUTHOR: wxs
* @DATE: 2023/5/31 15:07
* @PROJECT_NAME: WeChatHook-java
**/
public class WeChatHookNettyServer {
/**
* 直接启动main方法
*
* @param args
*/
public static void main(String[] args) {
Integer serverPort = 19077;
String hookApi = null;
for (String arg : args) {
System.out.println(arg);
if (arg.startsWith("--port")) {
serverPort = Integer.valueOf(arg.split("=")[1]);
}
if (arg.startsWith("--hookApi")) {
hookApi = arg.split("=")[1];
}
}
//1注入
inject();
//2开启hook
try {
JSONObject result = WeChatHookClient.hook_msg("127.0.0.1", serverPort.toString());
} catch (Exception e) {
System.out.println("hook 失败,请检查微信是否登录");
return;
}
//3启动服务
start(serverPort, hookApi);
}
/**
* 执行注入命令
*/
public static void inject() {
//
File consoleInjectTemp = null;
File wxhelperTemp = null;
try {
consoleInjectTemp = createTempFile("ConsoleInject", ".exe");
wxhelperTemp = createTempFile("wxhelper", ".dll");
String ConsoleInject = consoleInjectTemp.getAbsolutePath();
String wxhelper = wxhelperTemp.getAbsolutePath();
//ConsoleInject.exe -i WeChat.exe -p C:\Users\DELL\Desktop\injector\wxhelper.dll
String command = ConsoleInject + " -i WeChat.exe -p " + wxhelper;
//重试3次
int retryCount = 3;
do {
retryCount--;
try {
//检查登录状态
JSONObject jsonObject = WeChatHookClient.check_login();
//如果已登录不需要注入
if (jsonObject.getInteger("code").equals(1)) {
return;
}
} catch (Exception e) {
System.out.println(e.getMessage() + "请确认微信已登录");
}
//执行注入命令
excuteShell(command);
} while (retryCount >= 0);
} catch (Exception e) {
e.printStackTrace();
return;
} finally {
////如果不为空删除临时文件
if (Objects.nonNull(consoleInjectTemp)) {
consoleInjectTemp.delete();
}
//如果不为空删除临时文件
if (Objects.nonNull(wxhelperTemp)) {
wxhelperTemp.delete();
}
}
}
/**
* 创建临时文件
*
* @param fileName
* @param suffix
* @return
*/
private static File createTempFile(String fileName, String suffix) {
InputStream inputStream = WeChatHookNettyServer.class.getResourceAsStream("/" + fileName + suffix);
FileOutputStream outputStream = null;
try {
File tempFile = File.createTempFile(fileName, suffix);
outputStream = new FileOutputStream(tempFile);
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
return tempFile;
} catch (IOException e) {
e.printStackTrace();
} finally {
if (Objects.nonNull(outputStream)) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* 启动服务
*
* @param port
*/
public static void start(Integer port, String hookApi) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024 * 100, Delimiters.lineDelimiter()));
ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));
ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
ch.pipeline().addLast(new ReceiveMsgHandler(hookApi));
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
Channel channel = serverBootstrap.bind().sync().channel();
System.out.println("服务启动成功 端口号 " + port);
channel.closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private static class ReceiveMsgHandler extends SimpleChannelInboundHandler<String> {
private String hookApi;
public ReceiveMsgHandler(String hookApi) {
this.hookApi = hookApi;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) {
JSONObject jsonObject = JSON.parseObject(msg);
jsonObject.forEach((k, v) -> {
System.out.println(k + " = " + v);
});
String fromGroup = jsonObject.getString("fromGroup");
String fromUser = jsonObject.getString("fromUser");
String from;
if (fromGroup.equals(fromUser)) {
JSONObject fromGroupJson = WeChatHookClient.query_nickname(jsonObject.getString("fromGroup"));
String groupNname = fromGroupJson.getString("name");
from = "消息来自:" + groupNname;
jsonObject.put("fromUserName", groupNname);
} else {
JSONObject fromGroupJson = WeChatHookClient.query_nickname(jsonObject.getString("fromGroup"));
String groupNname = fromGroupJson.getString("name");
JSONObject fromUserJson = WeChatHookClient.query_nickname(jsonObject.getString("fromUser"));
String fromUserName = fromUserJson.getString("name");
jsonObject.put("fromUserName", fromUserName);
from = "消息来自:" + groupNname + "->" + fromUserName;
}
System.out.println("----------" + from + "----------");
//消息转发
if (StringUtil.isNullOrEmpty(hookApi)) {
return;
}
//检查api接口是否是通的
//转发消息
try {
WeChatHookClient.hook(hookApi, msg);
} catch (Exception e) {
//请检查hookApi服务是否正常
System.err.println("--》消息转发失败请检查hookApi服务是否正常");
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
/**
* 执行shell 命令
*
* @param command
*/
public static void excuteShell(String command) {
try {
Process process = Runtime.getRuntime().exec(command);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
int exitCode = process.waitFor();
System.out.println("Exit Code: " + exitCode);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}