Merge pull request #49 from lich0821/pyq

Add PengYouQuan
This commit is contained in:
Changhua 2023-07-17 00:02:45 +08:00 committed by GitHub
commit 6bc052b4fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 425 additions and 122 deletions

View File

@ -182,6 +182,12 @@ Rust 客户端。
</details> </details>
## 版本更新 ## 版本更新
### v39.0.2 (2023.07.16)
* 修复朋友圈消息 `is_group` 为 `True` 问题
<details><summary>点击查看更多</summary>
客户端越来越多了,版本号开始混乱,所以重新定义了版本号:`w.x.y.z`。 客户端越来越多了,版本号开始混乱,所以重新定义了版本号:`w.x.y.z`。
其中: 其中:
@ -190,7 +196,8 @@ Rust 客户端。
* `y` 是 `WeChatFerry` 的版本,从 0 开始 * `y` 是 `WeChatFerry` 的版本,从 0 开始
* `z` 是各客户端的版本,从 0 开始 * `z` 是各客户端的版本,从 0 开始
<details><summary>点击查看</summary> ### v39.0.1 (2023.07.16)
* 获取朋友圈消息
### v39.0.0 (2023.07.14) ### v39.0.0 (2023.07.14)
升级到 `3.9.2.23`。 升级到 `3.9.2.23`。

View File

@ -38,14 +38,16 @@ typedef vector<DbRow_t> DbRows_t;
typedef struct { typedef struct {
bool is_self; bool is_self;
bool is_group; bool is_group;
int32_t type; uint32_t type;
string id; uint32_t ts;
string xml; uint64_t id;
string sender; string sender;
string roomid; string roomid;
string content; string content;
string sign;
string thumb; string thumb;
string extra; string extra;
string xml;
} WxMsg_t; } WxMsg_t;
typedef struct { typedef struct {

View File

@ -22,6 +22,7 @@ enum Functions {
FUNC_EXEC_DB_QUERY = 0x50; FUNC_EXEC_DB_QUERY = 0x50;
FUNC_ACCEPT_FRIEND = 0x51; FUNC_ACCEPT_FRIEND = 0x51;
FUNC_RECV_TRANSFER = 0x52; FUNC_RECV_TRANSFER = 0x52;
FUNC_REFRESH_PYQ = 0x53;
FUNC_DECRYPT_IMAGE = 0x60; FUNC_DECRYPT_IMAGE = 0x60;
FUNC_ADD_ROOM_MEMBERS = 0x70; FUNC_ADD_ROOM_MEMBERS = 0x70;
FUNC_DEL_ROOM_MEMBERS = 0x71; FUNC_DEL_ROOM_MEMBERS = 0x71;
@ -42,6 +43,8 @@ message Request
XmlMsg xml = 9; XmlMsg xml = 9;
DecPath dec = 10; DecPath dec = 10;
Transfer tf = 11; Transfer tf = 11;
uint64 ui64 = 12; // 64
bool flag = 13;
} }
} }
@ -68,14 +71,16 @@ message WxMsg
{ {
bool is_self = 1; // bool is_self = 1; //
bool is_group = 2; // bool is_group = 2; //
int32 type = 3; // uint64 id = 3; // id
string id = 4; // id uint32 type = 4; //
string xml = 5; // xml uint32 ts = 5; //
string sender = 6; // string roomid = 6; // id
string roomid = 7; // id string content = 7; //
string content = 8; // string sender = 8; //
string thumb = 9; // string sign = 9; // Sign
string extra = 10; // string thumb = 10; //
string extra = 11; //
string xml = 12; // xml
} }
message TextMsg message TextMsg

View File

@ -233,6 +233,7 @@ $(SolutionDir)rpc\tool\protoc --nanopb_out=. wcf.proto</Command>
<ClInclude Include="contact_mgmt.h" /> <ClInclude Include="contact_mgmt.h" />
<ClInclude Include="load_calls.h" /> <ClInclude Include="load_calls.h" />
<ClInclude Include="log.h" /> <ClInclude Include="log.h" />
<ClInclude Include="pyq.h" />
<ClInclude Include="receive_msg.h" /> <ClInclude Include="receive_msg.h" />
<ClInclude Include="receive_transfer.h" /> <ClInclude Include="receive_transfer.h" />
<ClInclude Include="resource.h" /> <ClInclude Include="resource.h" />
@ -257,6 +258,7 @@ $(SolutionDir)rpc\tool\protoc --nanopb_out=. wcf.proto</Command>
<ClCompile Include="contact_mgmt.cpp" /> <ClCompile Include="contact_mgmt.cpp" />
<ClCompile Include="load_calls.cpp" /> <ClCompile Include="load_calls.cpp" />
<ClCompile Include="log.cpp" /> <ClCompile Include="log.cpp" />
<ClCompile Include="pyq.cpp" />
<ClCompile Include="receive_msg.cpp" /> <ClCompile Include="receive_msg.cpp" />
<ClCompile Include="receive_transfer.cpp" /> <ClCompile Include="receive_transfer.cpp" />
<ClCompile Include="rpc_server.cpp" /> <ClCompile Include="rpc_server.cpp" />

View File

@ -90,6 +90,9 @@
<ClInclude Include="sqlite3.h"> <ClInclude Include="sqlite3.h">
<Filter>头文件</Filter> <Filter>头文件</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="pyq.h">
<Filter>头文件</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClCompile Include="dllmain.cpp"> <ClCompile Include="dllmain.cpp">
@ -149,6 +152,9 @@
<ClCompile Include="receive_transfer.cpp"> <ClCompile Include="receive_transfer.cpp">
<Filter>源文件</Filter> <Filter>源文件</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="pyq.cpp">
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="spy.def"> <None Include="spy.def">

View File

@ -9,8 +9,8 @@ WxCalls_t wxCalls = {
{ 0x2FFD484, 0x2FFD590, 0x2FFD500, 0x30238CC }, // User Info: wxid, nickname, mobile, home { 0x2FFD484, 0x2FFD590, 0x2FFD500, 0x30238CC }, // User Info: wxid, nickname, mobile, home
{ 0x768140, 0xCE6C80, 0x756960 }, // Send Message { 0x768140, 0xCE6C80, 0x756960 }, // Send Message
/* Receive Message: /* Receive Message:
Hook, call, type, self, id, msgXml, roomId, wxId, content, thumb, extra */ Hook, call, msgId, type, isSelf, ts, roomId, content, wxid, sign, thumb, extra, msgXml */
{ 0xD19A0B, 0x756960, 0x38, 0x3C, 0x194, 0x1FC, 0x48, 0x180, 0x70, 0x1A8, 0x1BC }, { 0xD19A0B, 0x756960, 0x30, 0x38, 0x3C, 0x44, 0x48, 0x70, 0x180, 0x194, 0x1A8, 0x1BC, 0x1FC },
{ 0x768140, 0XF59E40, 0XCE6640, 0x756960 }, // Send Image Message { 0x768140, 0XF59E40, 0XCE6640, 0x756960 }, // Send Image Message
{ 0x76AE20, 0xF59E40, 0xB6D1F0, 0x756960 }, // Send File Message { 0x76AE20, 0xF59E40, 0xB6D1F0, 0x756960 }, // Send File Message
{ 0xB8A70, 0x3ED5E0, 0x107F00, 0x3ED7B0, 0x2386FE4 }, // Send xml Message { 0xB8A70, 0x3ED5E0, 0x107F00, 0x3ED7B0, 0x2386FE4 }, // Send xml Message
@ -24,7 +24,10 @@ WxCalls_t wxCalls = {
{ 0xA17D50, 0xF59E40, 0xA18BD0, 0xA17E70 }, // Accept New Friend application { 0xA17D50, 0xF59E40, 0xA18BD0, 0xA17E70 }, // Accept New Friend application
{ 0x78CF20, 0xF59E40, 0xBD1DC0 }, // Add chatroom members { 0x78CF20, 0xF59E40, 0xBD1DC0 }, // Add chatroom members
{ 0x78CF20, 0xF59E40, 0xBD22A0 }, // Delete chatroom members { 0x78CF20, 0xF59E40, 0xBD22A0 }, // Delete chatroom members
{ 0x7B2E60, 0x15E2C20, 0x79C250 } // Receive transfer { 0x7B2E60, 0x15E2C20, 0x79C250 }, // Receive transfer
/* Receive PYQ
hook, call, call1, call2, call3, start, end, ts, wxid, content, xml, step*/
{ 0x14F9E15, 0x14FA0A0, 0xC39680, 0x14E2140, 0x14E21E0, 0x20, 0x24, 0x2C, 0x18, 0x3C, 0x384, 0xB48 }
}; };
int LoadCalls(const wchar_t *version, WxCalls_t *calls) int LoadCalls(const wchar_t *version, WxCalls_t *calls)

77
WeChatFerry/spy/pyq.cpp Normal file
View File

@ -0,0 +1,77 @@
#include "framework.h"
#include "log.h"
#include "spy_types.h"
#include "util.h"
extern bool gIsListeningPyq;
extern WxCalls_t g_WxCalls;
extern DWORD g_WeChatWinDllAddr;
typedef struct RawVector {
DWORD start;
DWORD finish;
DWORD end;
} RawVector_t;
static int GetFirstPage()
{
int rv = -1;
DWORD pyqCall1 = g_WeChatWinDllAddr + g_WxCalls.pyq.call1;
DWORD pyqCall2 = g_WeChatWinDllAddr + g_WxCalls.pyq.call2;
char buf[0xB44] = { 0 };
__asm {
pushad;
call pyqCall1;
push 0x1;
lea ecx, buf;
push ecx;
mov ecx, eax;
call pyqCall2;
mov rv, eax;
popad;
}
return rv;
}
static int GetNextPage(uint64_t id)
{
int rv = -1;
DWORD pyqCall1 = g_WeChatWinDllAddr + g_WxCalls.pyq.call1;
DWORD pyqCall3 = g_WeChatWinDllAddr + g_WxCalls.pyq.call3;
RawVector_t tmp = { 0 };
__asm {
pushad;
call pyqCall1;
lea ecx, tmp;
push ecx;
mov ebx, dword ptr [id + 0x04];
push ebx;
mov edi, dword ptr [id]
push edi;
mov ecx, eax;
call pyqCall3;
mov rv, eax;
popad;
}
return rv;
}
int RefreshPyq(uint64_t id)
{
if (!gIsListeningPyq) {
LOG_ERROR("没有启动朋友圈消息接收参考enable_receiving_msg");
return -1;
}
if (id == 0) {
return GetFirstPage();
}
return GetNextPage(id);
}

5
WeChatFerry/spy/pyq.h Normal file
View File

@ -0,0 +1,5 @@
#pragma once
#include "stdint.h"
int RefreshPyq(uint64_t id);

View File

@ -6,12 +6,13 @@
#include <queue> #include <queue>
#include "load_calls.h" #include "load_calls.h"
#include "log.h"
#include "receive_msg.h" #include "receive_msg.h"
#include "user_info.h" #include "user_info.h"
#include "util.h" #include "util.h"
// Defined in rpc_server.cpp // Defined in rpc_server.cpp
extern bool gIsListening; extern bool gIsListening, gIsListeningPyq;
extern mutex gMutex; extern mutex gMutex;
extern condition_variable gCV; extern condition_variable gCV;
extern queue<WxMsg_t> gMsgQueue; extern queue<WxMsg_t> gMsgQueue;
@ -26,9 +27,15 @@ static DWORD recvMsgCallAddr = 0;
static DWORD recvMsgJumpBackAddr = 0; static DWORD recvMsgJumpBackAddr = 0;
static CHAR recvMsgBackupCode[5] = { 0 }; static CHAR recvMsgBackupCode[5] = { 0 };
static DWORD recvPyqHookAddr = 0;
static DWORD recvPyqCallAddr = 0;
static DWORD recvPyqJumpBackAddr = 0;
static CHAR recvPyqBackupCode[5] = { 0 };
MsgTypes_t GetMsgTypes() MsgTypes_t GetMsgTypes()
{ {
const MsgTypes_t m = { const MsgTypes_t m = {
{ 0x00, "朋友圈消息" },
{ 0x01, "文字" }, { 0x01, "文字" },
{ 0x03, "图片" }, { 0x03, "图片" },
{ 0x22, "语音" }, { 0x22, "语音" },
@ -90,9 +97,12 @@ void DispatchMsg(DWORD reg)
{ {
WxMsg_t wxMsg; WxMsg_t wxMsg;
wxMsg.id = GET_QWORD(reg + g_WxCalls.recvMsg.msgId);
wxMsg.type = GET_DWORD(reg + g_WxCalls.recvMsg.type); wxMsg.type = GET_DWORD(reg + g_WxCalls.recvMsg.type);
wxMsg.is_self = GET_DWORD(reg + g_WxCalls.recvMsg.isSelf); wxMsg.is_self = GET_DWORD(reg + g_WxCalls.recvMsg.isSelf);
wxMsg.id = GetStringByStrAddr(reg + g_WxCalls.recvMsg.msgId); wxMsg.ts = GET_DWORD(reg + g_WxCalls.recvMsg.ts);
wxMsg.content = GetStringByWstrAddr(reg + g_WxCalls.recvMsg.content);
wxMsg.sign = GetStringByStrAddr(reg + g_WxCalls.recvMsg.sign);
wxMsg.xml = GetStringByStrAddr(reg + g_WxCalls.recvMsg.msgXml); wxMsg.xml = GetStringByStrAddr(reg + g_WxCalls.recvMsg.msgXml);
string roomid = GetStringByWstrAddr(reg + g_WxCalls.recvMsg.roomId); string roomid = GetStringByWstrAddr(reg + g_WxCalls.recvMsg.roomId);
@ -102,7 +112,7 @@ void DispatchMsg(DWORD reg)
if (wxMsg.is_self) { if (wxMsg.is_self) {
wxMsg.sender = GetSelfWxid(); wxMsg.sender = GetSelfWxid();
} else { } else {
wxMsg.sender = GetStringByStrAddr(reg + g_WxCalls.recvMsg.wxId); wxMsg.sender = GetStringByStrAddr(reg + g_WxCalls.recvMsg.wxid);
} }
} else { } else {
wxMsg.is_group = false; wxMsg.is_group = false;
@ -113,8 +123,6 @@ void DispatchMsg(DWORD reg)
} }
} }
wxMsg.content = GetStringByWstrAddr(reg + g_WxCalls.recvMsg.content);
wxMsg.thumb = GetStringByStrAddr(reg + g_WxCalls.recvMsg.thumb); wxMsg.thumb = GetStringByStrAddr(reg + g_WxCalls.recvMsg.thumb);
if (!wxMsg.thumb.empty()) { if (!wxMsg.thumb.empty()) {
wxMsg.thumb = GetHomePath() + wxMsg.thumb; wxMsg.thumb = GetHomePath() + wxMsg.thumb;
@ -173,3 +181,74 @@ void UnListenMessage()
UnHookAddress(recvMsgHookAddr, recvMsgBackupCode); UnHookAddress(recvMsgHookAddr, recvMsgBackupCode);
gIsListening = false; gIsListening = false;
} }
void DispatchPyq(DWORD reg)
{
DWORD startAddr = *(DWORD *)(reg + g_WxCalls.pyq.start);
DWORD endAddr = *(DWORD *)(reg + g_WxCalls.pyq.end);
if (startAddr == 0) {
return;
}
while (startAddr < endAddr) {
WxMsg_t wxMsg;
wxMsg.type = 0x00; // 朋友圈消息
wxMsg.is_self = false;
wxMsg.is_group = false;
wxMsg.id = GET_QWORD(startAddr);
wxMsg.ts = GET_DWORD(startAddr + g_WxCalls.pyq.ts);
wxMsg.xml = GetStringByWstrAddr(startAddr + g_WxCalls.pyq.xml);
wxMsg.sender = GetStringByWstrAddr(startAddr + g_WxCalls.pyq.wxid);
wxMsg.content = GetStringByWstrAddr(startAddr + g_WxCalls.pyq.content);
{
unique_lock<mutex> lock(gMutex);
gMsgQueue.push(wxMsg); // 推送到队列
}
gCV.notify_all(); // 通知各方消息就绪
startAddr += g_WxCalls.pyq.step;
}
}
__declspec(naked) void RecievePyqFunc()
{
__asm {
pushad
pushfd
push [esp + 0x24]
call DispatchPyq
add esp, 0x4
popfd
popad
call recvPyqCallAddr // 这个为被覆盖的call
jmp recvPyqJumpBackAddr // 跳回被HOOK指令的下一条指令
}
}
void ListenPyq()
{
if (gIsListeningPyq || (g_WeChatWinDllAddr == 0)) {
return;
}
recvPyqHookAddr = g_WeChatWinDllAddr + g_WxCalls.pyq.hook;
recvPyqCallAddr = g_WeChatWinDllAddr + g_WxCalls.pyq.call;
recvPyqJumpBackAddr = recvPyqHookAddr + 5;
HookAddress(recvPyqHookAddr, RecievePyqFunc, recvPyqBackupCode);
gIsListeningPyq = true;
}
void UnListenPyq()
{
if (!gIsListeningPyq) {
return;
}
UnHookAddress(recvPyqHookAddr, recvPyqBackupCode);
gIsListeningPyq = false;
}

View File

@ -2,6 +2,8 @@
#include "pb_types.h" #include "pb_types.h"
void ListenPyq();
void UnListenPyq();
void ListenMessage(); void ListenMessage();
void UnListenMessage(); void UnListenMessage();
MsgTypes_t GetMsgTypes(); MsgTypes_t GetMsgTypes();

View File

@ -23,6 +23,7 @@
#include "log.h" #include "log.h"
#include "pb_types.h" #include "pb_types.h"
#include "pb_util.h" #include "pb_util.h"
#include "pyq.h"
#include "receive_msg.h" #include "receive_msg.h"
#include "receive_transfer.h" #include "receive_transfer.h"
#include "rpc_server.h" #include "rpc_server.h"
@ -38,7 +39,8 @@
extern int IsLogin(void); // Defined in spy.cpp extern int IsLogin(void); // Defined in spy.cpp
bool gIsListening; bool gIsListening = false;
bool gIsListeningPyq = false;
mutex gMutex; mutex gMutex;
condition_variable gCV; condition_variable gCV;
queue<WxMsg_t> gMsgQueue; queue<WxMsg_t> gMsgQueue;
@ -345,16 +347,18 @@ static void PushMessage()
if (gCV.wait_for(lock, chrono::milliseconds(1000), []() { return !gMsgQueue.empty(); })) { if (gCV.wait_for(lock, chrono::milliseconds(1000), []() { return !gMsgQueue.empty(); })) {
while (!gMsgQueue.empty()) { while (!gMsgQueue.empty()) {
auto wxmsg = gMsgQueue.front(); auto wxmsg = gMsgQueue.front();
rsp.msg.wxmsg.id = wxmsg.id;
rsp.msg.wxmsg.is_self = wxmsg.is_self; rsp.msg.wxmsg.is_self = wxmsg.is_self;
rsp.msg.wxmsg.is_group = wxmsg.is_group; rsp.msg.wxmsg.is_group = wxmsg.is_group;
rsp.msg.wxmsg.type = wxmsg.type; rsp.msg.wxmsg.type = wxmsg.type;
rsp.msg.wxmsg.id = (char *)wxmsg.id.c_str(); rsp.msg.wxmsg.ts = wxmsg.ts;
rsp.msg.wxmsg.xml = (char *)wxmsg.xml.c_str();
rsp.msg.wxmsg.sender = (char *)wxmsg.sender.c_str();
rsp.msg.wxmsg.roomid = (char *)wxmsg.roomid.c_str(); rsp.msg.wxmsg.roomid = (char *)wxmsg.roomid.c_str();
rsp.msg.wxmsg.content = (char *)wxmsg.content.c_str(); rsp.msg.wxmsg.content = (char *)wxmsg.content.c_str();
rsp.msg.wxmsg.sender = (char *)wxmsg.sender.c_str();
rsp.msg.wxmsg.sign = (char *)wxmsg.sign.c_str();
rsp.msg.wxmsg.thumb = (char *)wxmsg.thumb.c_str(); rsp.msg.wxmsg.thumb = (char *)wxmsg.thumb.c_str();
rsp.msg.wxmsg.extra = (char *)wxmsg.extra.c_str(); rsp.msg.wxmsg.extra = (char *)wxmsg.extra.c_str();
rsp.msg.wxmsg.xml = (char *)wxmsg.xml.c_str();
gMsgQueue.pop(); gMsgQueue.pop();
LOG_DEBUG("Recv msg: {}", wxmsg.content); LOG_DEBUG("Recv msg: {}", wxmsg.content);
pb_ostream_t stream = pb_ostream_from_buffer(buffer, G_BUF_SIZE); pb_ostream_t stream = pb_ostream_from_buffer(buffer, G_BUF_SIZE);
@ -374,7 +378,7 @@ static void PushMessage()
nng_close(msg_sock); nng_close(msg_sock);
} }
bool func_enable_recv_txt(uint8_t *out, size_t *len) bool func_enable_recv_txt(bool pyq, uint8_t *out, size_t *len)
{ {
Response rsp = Response_init_default; Response rsp = Response_init_default;
rsp.func = Functions_FUNC_ENABLE_RECV_TXT; rsp.func = Functions_FUNC_ENABLE_RECV_TXT;
@ -382,6 +386,9 @@ bool func_enable_recv_txt(uint8_t *out, size_t *len)
rsp.msg.status = -1; rsp.msg.status = -1;
ListenMessage(); ListenMessage();
if (pyq) {
ListenPyq();
}
HANDLE msgThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PushMessage, NULL, NULL, NULL); HANDLE msgThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PushMessage, NULL, NULL, NULL);
if (msgThread != 0) { if (msgThread != 0) {
CloseHandle(msgThread); CloseHandle(msgThread);
@ -405,6 +412,7 @@ bool func_disable_recv_txt(uint8_t *out, size_t *len)
rsp.which_msg = Response_status_tag; rsp.which_msg = Response_status_tag;
rsp.msg.status = 0; rsp.msg.status = 0;
UnListenPyq();
UnListenMessage(); // 可能需要1秒之后才能退出见 PushMessage UnListenMessage(); // 可能需要1秒之后才能退出见 PushMessage
pb_ostream_t stream = pb_ostream_from_buffer(out, *len); pb_ostream_t stream = pb_ostream_from_buffer(out, *len);
@ -486,6 +494,24 @@ bool func_receive_transfer(char *wxid, char *tfid, char *taid, uint8_t *out, siz
return true; return true;
} }
bool func_refresh_pyq(uint64_t id, uint8_t *out, size_t *len)
{
Response rsp = Response_init_default;
rsp.func = Functions_FUNC_REFRESH_PYQ;
rsp.which_msg = Response_status_tag;
rsp.msg.status = RefreshPyq(id);
pb_ostream_t stream = pb_ostream_from_buffer(out, *len);
if (!pb_encode(&stream, Response_fields, &rsp)) {
LOG_ERROR("Encoding failed: {}", PB_GET_ERROR(&stream));
return false;
}
*len = stream.bytes_written;
return true;
}
bool func_decrypt_image(char *src, char *dst, uint8_t *out, size_t *len) bool func_decrypt_image(char *src, char *dst, uint8_t *out, size_t *len)
{ {
Response rsp = Response_init_default; Response rsp = Response_init_default;
@ -629,7 +655,8 @@ static bool dispatcher(uint8_t *in, size_t in_len, uint8_t *out, size_t *out_len
#endif #endif
case Functions_FUNC_ENABLE_RECV_TXT: { case Functions_FUNC_ENABLE_RECV_TXT: {
LOG_DEBUG("[Functions_FUNC_ENABLE_RECV_TXT]"); LOG_DEBUG("[Functions_FUNC_ENABLE_RECV_TXT]");
ret = func_enable_recv_txt(out, out_len); LOG_BUFFER(in, in_len);
ret = func_enable_recv_txt(req.msg.flag, out, out_len);
break; break;
} }
case Functions_FUNC_DISABLE_RECV_TXT: { case Functions_FUNC_DISABLE_RECV_TXT: {
@ -652,6 +679,11 @@ static bool dispatcher(uint8_t *in, size_t in_len, uint8_t *out, size_t *out_len
ret = func_receive_transfer(req.msg.tf.wxid, req.msg.tf.tfid, req.msg.tf.taid, out, out_len); ret = func_receive_transfer(req.msg.tf.wxid, req.msg.tf.tfid, req.msg.tf.taid, out, out_len);
break; break;
} }
case Functions_FUNC_REFRESH_PYQ: {
LOG_DEBUG("[Functions_FUNC_REFRESH_PYQ]");
ret = func_refresh_pyq(req.msg.ui64, out, out_len);
break;
}
case Functions_FUNC_DECRYPT_IMAGE: { case Functions_FUNC_DECRYPT_IMAGE: {
LOG_DEBUG("[FUNCTIONS_FUNC_DECRYPT_IMAGE]"); LOG_DEBUG("[FUNCTIONS_FUNC_DECRYPT_IMAGE]");
ret = func_decrypt_image(req.msg.dec.src, req.msg.dec.dst, out, out_len); ret = func_decrypt_image(req.msg.dec.src, req.msg.dec.dst, out, out_len);

Binary file not shown.

View File

@ -51,7 +51,7 @@ END
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 39,0,0,0 FILEVERSION 39,0,2,0
PRODUCTVERSION 3,9,2,23 PRODUCTVERSION 3,9,2,23
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
@ -69,7 +69,7 @@ BEGIN
BEGIN BEGIN
VALUE "CompanyName", "WeChatFerry" VALUE "CompanyName", "WeChatFerry"
VALUE "FileDescription", "WeChatFerry" VALUE "FileDescription", "WeChatFerry"
VALUE "FileVersion", "39.0.0.0" VALUE "FileVersion", "39.0.2.0"
VALUE "InternalName", "spy.dll" VALUE "InternalName", "spy.dll"
VALUE "LegalCopyright", "Copyright (C) 2023" VALUE "LegalCopyright", "Copyright (C) 2023"
VALUE "OriginalFilename", "spy.dll" VALUE "OriginalFilename", "spy.dll"

View File

@ -12,15 +12,17 @@ typedef struct UserInfoCall {
typedef struct RecvMsg { typedef struct RecvMsg {
DWORD hook; // Hook地址 DWORD hook; // Hook地址
DWORD call; // Call地址 DWORD call; // Call地址
DWORD msgId; // 消息ID地址
DWORD type; // 消息类型地址 DWORD type; // 消息类型地址
DWORD isSelf; // 是否自己发送标志地址 DWORD isSelf; // 是否自己发送标志地址
DWORD msgId; // 消息ID地址 DWORD ts; // TimeStamp
DWORD msgXml; // 消息xml内容地址
DWORD roomId; // 群聊时为群ID私聊时为微信ID DWORD roomId; // 群聊时为群ID私聊时为微信ID
DWORD wxId; // 私聊时为空群聊时为发送者微信ID
DWORD content; // 消息内容地址 DWORD content; // 消息内容地址
DWORD wxid; // 私聊时为空群聊时为发送者微信ID
DWORD sign; // Sign
DWORD thumb; // 缩略图 DWORD thumb; // 缩略图
DWORD extra; // 附加数据 DWORD extra; // 附加数据
DWORD msgXml; // 消息xml内容地址
} RecvMsg_t; } RecvMsg_t;
typedef struct SendText { typedef struct SendText {
@ -85,6 +87,21 @@ typedef struct TF {
DWORD call3; DWORD call3;
} TF_t; } TF_t;
typedef struct Pyq {
DWORD hook;
DWORD call;
DWORD call1;
DWORD call2;
DWORD call3;
DWORD start;
DWORD end;
DWORD ts;
DWORD wxid;
DWORD content;
DWORD xml;
DWORD step;
} Pyq_t;
typedef struct WxCalls { typedef struct WxCalls {
DWORD login; // 登录状态 DWORD login; // 登录状态
UserInfoCall_t ui; // 用户信息 UserInfoCall_t ui; // 用户信息
@ -100,6 +117,7 @@ typedef struct WxCalls {
RoomMember_t arm; // 添加群成员 RoomMember_t arm; // 添加群成员
RoomMember_t drm; // 删除群成员 RoomMember_t drm; // 删除群成员
TF_t tf; // 接收转账 TF_t tf; // 接收转账
Pyq_t pyq; // 接收朋友圈消息
} WxCalls_t; } WxCalls_t;
typedef struct WxString { typedef struct WxString {

View File

@ -9,6 +9,7 @@
#define WECHATINJECTDLL_DEBUG L"spy_debug.dll" #define WECHATINJECTDLL_DEBUG L"spy_debug.dll"
#define GET_DWORD(addr) ((DWORD) * (DWORD *)(addr)) #define GET_DWORD(addr) ((DWORD) * (DWORD *)(addr))
#define GET_QWORD(addr) ((uint64_t) * (uint64_t *)(addr))
#define GET_STRING(addr) ((CHAR *)(*(DWORD *)(addr))) #define GET_STRING(addr) ((CHAR *)(*(DWORD *)(addr)))
#define GET_WSTRING(addr) ((WCHAR *)(*(DWORD *)(addr))) #define GET_WSTRING(addr) ((WCHAR *)(*(DWORD *)(addr)))
#define GET_STRING_FROM_P(addr) ((CHAR *)(addr)) #define GET_STRING_FROM_P(addr) ((CHAR *)(addr))

View File

@ -33,7 +33,7 @@ setup(
"setuptools", "setuptools",
"fastapi", "fastapi",
"uvicorn[standard]", "uvicorn[standard]",
"wcferry>=39.0.0.0", "wcferry>=39.0.2.0",
], ],
classifiers=[ classifiers=[
"Environment :: Win32 (MS Windows)", "Environment :: Win32 (MS Windows)",

View File

@ -3,18 +3,22 @@
import base64 import base64
import logging import logging
from queue import Empty
from threading import Thread
from typing import Any from typing import Any
import requests import requests
from fastapi import Body, FastAPI from fastapi import Body, Query, FastAPI
from pydantic import BaseModel from pydantic import BaseModel
from wcferry import Wcf, WxMsg from wcferry import Wcf, WxMsg
__version__ = "39.0.0.1" __version__ = "39.0.2.0"
class Msg(BaseModel): class Msg(BaseModel):
id: str id: int
ts: int
sign: str
type: int type: int
xml: str xml: str
sender: str sender: str
@ -46,6 +50,7 @@ class Http(FastAPI):
self.add_api_route("/friends", self.get_friends, methods=["GET"], summary="获取好友列表") self.add_api_route("/friends", self.get_friends, methods=["GET"], summary="获取好友列表")
self.add_api_route("/dbs", self.get_dbs, methods=["GET"], summary="获取所有数据库") self.add_api_route("/dbs", self.get_dbs, methods=["GET"], summary="获取所有数据库")
self.add_api_route("/{db}/tables", self.get_tables, methods=["GET"], summary="获取 db 中所有表") self.add_api_route("/{db}/tables", self.get_tables, methods=["GET"], summary="获取 db 中所有表")
self.add_api_route("/pyq/", self.refresh_pyq, methods=["GET"], summary="刷新朋友圈(数据从消息回调中查看)")
self.add_api_route("/text", self.send_text, methods=["POST"], summary="发送文本消息") self.add_api_route("/text", self.send_text, methods=["POST"], summary="发送文本消息")
self.add_api_route("/image", self.send_image, methods=["POST"], summary="发送图片消息") self.add_api_route("/image", self.send_image, methods=["POST"], summary="发送图片消息")
@ -55,35 +60,50 @@ class Http(FastAPI):
self.add_api_route("/sql", self.query_sql, methods=["POST"], summary="执行 SQL如果数据量大注意分页以免 OOM") self.add_api_route("/sql", self.query_sql, methods=["POST"], summary="执行 SQL如果数据量大注意分页以免 OOM")
self.add_api_route("/new-friend", self.accept_new_friend, methods=["POST"], summary="通过好友申请") self.add_api_route("/new-friend", self.accept_new_friend, methods=["POST"], summary="通过好友申请")
self.add_api_route("/chatroom-member", self.add_chatroom_members, methods=["POST"], summary="添加群成员") self.add_api_route("/chatroom-member", self.add_chatroom_members, methods=["POST"], summary="添加群成员")
self.add_api_route("/chatroom-member", self.del_chatroom_members, methods=["DELETE"], summary="删除群成员")
self.add_api_route("/transfer", self.receive_transfer, methods=["POST"], summary="接收转账") self.add_api_route("/transfer", self.receive_transfer, methods=["POST"], summary="接收转账")
self.add_api_route("/dec-image", self.decrypt_image, methods=["POST"], summary="解密图片") self.add_api_route("/dec-image", self.decrypt_image, methods=["POST"], summary="解密图片")
def _set_cb(self, cb): self.add_api_route("/chatroom-member", self.del_chatroom_members, methods=["DELETE"], summary="删除群成员")
def callback(msg: WxMsg):
data = {}
data["id"] = msg.id
data["type"] = msg.type
data["xml"] = msg.xml
data["sender"] = msg.sender
data["roomid"] = msg.roomid
data["content"] = msg.content
data["thumb"] = msg.thumb
data["extra"] = msg.extra
data["is_at"] = msg.is_at(self.wcf.self_wxid)
data["is_self"] = msg.from_self()
data["is_group"] = msg.from_group()
try: def _forward_msg(self, msg, cb):
rsp = requests.post(url=cb, json=data) data = {}
if rsp.status_code != 200: data["id"] = msg.id
self.LOG.error(f"消息转发失败HTTP 状态码为: {rsp.status_code}") data["ts"] = msg.ts
except Exception as e: data["sign"] = msg.sign
self.LOG.error(f"消息转发异常: {e}") data["type"] = msg.type
data["xml"] = msg.xml
data["sender"] = msg.sender
data["roomid"] = msg.roomid
data["content"] = msg.content
data["thumb"] = msg.thumb
data["extra"] = msg.extra
data["is_at"] = msg.is_at(self.wcf.self_wxid)
data["is_self"] = msg.from_self()
data["is_group"] = msg.from_group()
try:
rsp = requests.post(url=cb, json=data)
if rsp.status_code != 200:
self.LOG.error(f"消息转发失败HTTP 状态码为: {rsp.status_code}")
except Exception as e:
self.LOG.error(f"消息转发异常: {e}")
def _set_cb(self, cb):
def callback(wcf: Wcf):
while wcf.is_receiving_msg():
try:
msg = wcf.get_msg()
self.LOG.info(msg)
self._forward_msg(msg, cb)
except Empty:
continue # Empty message
except Exception as e:
self.LOG.error(f"Receiving message error: {e}")
if cb: if cb:
self.LOG.info(f"消息回调: {cb}") self.LOG.info(f"消息回调: {cb}")
self.wcf.enable_recv_msg(callback=callback) self.wcf.enable_receiving_msg(pyq=True) # 同时允许接收朋友圈消息
Thread(target=callback, name="GetMessage", args=(self.wcf,), daemon=True).start()
else: else:
self.LOG.info(f"没有设置回调,打印消息") self.LOG.info(f"没有设置回调,打印消息")
self.wcf.enable_recv_msg(print) self.wcf.enable_recv_msg(print)
@ -324,6 +344,18 @@ class Http(FastAPI):
ret = self.wcf.receive_transfer(wxid, transferid, transactionid) ret = self.wcf.receive_transfer(wxid, transferid, transactionid)
return {"status": ret, "message": "成功"if ret == 1 else "失败"} return {"status": ret, "message": "成功"if ret == 1 else "失败"}
def refresh_pyq(self, id: int = Query(0, description="开始 id0 为最新页")) -> dict:
"""刷新朋友圈
Args:
id (int): 开始 id0 为最新页
Returns:
int: 1 为成功其他失败
"""
ret = self.wcf.refresh_pyq(id)
return {"status": ret, "message": "成功"if ret == 1 else "失败"}
def decrypt_image(self, def decrypt_image(self,
src: str = Body("C:\\...", description="加密的图片路径,从图片消息中获取"), src: str = Body("C:\\...", description="加密的图片路径,从图片消息中获取"),
dst: str = Body("C:\\...", description="解密的图片路径")) -> dict: dst: str = Body("C:\\...", description="解密的图片路径")) -> dict:

View File

@ -39,7 +39,7 @@ def process_msg(wcf: Wcf):
def main(): def main():
LOG.info("Start demo...") LOG.info("Start demo...")
wcf = Wcf(debug=True) wcf = Wcf(debug=True) # 默认连接本地服务
sleep(5) # 等微信加载好,以免信息显示异常 sleep(5) # 等微信加载好,以免信息显示异常
LOG.info(f"已经登录: {True if wcf.is_login() else False}") LOG.info(f"已经登录: {True if wcf.is_login() else False}")
@ -49,7 +49,7 @@ def main():
# wcf.enable_recv_msg(LOG.info) # deprecated # wcf.enable_recv_msg(LOG.info) # deprecated
# 允许接收消息 # 允许接收消息
wcf.enable_receiving_msg() wcf.enable_receiving_msg(pyq=True) # 同时允许接收朋友圈消息
Thread(target=process_msg, name="GetMessage", args=(wcf,), daemon=True).start() Thread(target=process_msg, name="GetMessage", args=(wcf,), daemon=True).start()
# wcf.disable_recv_msg() # 当需要停止接收消息时调用 # wcf.disable_recv_msg() # 当需要停止接收消息时调用
@ -71,7 +71,7 @@ def main():
sleep(5) sleep(5)
LOG.info(f"DBs:\n{wcf.get_dbs()}") LOG.info(f"DBs:\n{wcf.get_dbs()}")
LOG.info(f"Tables:\n{wcf.get_tables('MicroMsg.db')}") LOG.info(f"Tables:\n{wcf.get_tables('db')}")
LOG.info(f"Results:\n{wcf.query_sql('MicroMsg.db', 'SELECT * FROM Contact LIMIT 1;')}") LOG.info(f"Results:\n{wcf.query_sql('MicroMsg.db', 'SELECT * FROM Contact LIMIT 1;')}")
# 需要真正的 V3、V4 信息 # 需要真正的 V3、V4 信息
@ -85,6 +85,10 @@ def main():
# ret = wcf.del_chatroom_members("chatroom id", "wxid1,wxid2,wxid3,...") # ret = wcf.del_chatroom_members("chatroom id", "wxid1,wxid2,wxid3,...")
# LOG.info(f"add_chatroom_members: {ret}") # LOG.info(f"add_chatroom_members: {ret}")
sleep(5)
wcf.refresh_pyq(0) # 刷新朋友圈第一页
# wcf.refresh_pyq(id) # 从 id 开始刷新朋友圈
# 一直运行 # 一直运行
wcf.keep_running() wcf.keep_running()
@ -114,15 +118,20 @@ pip install grpcio-tools pynng
### 重新生成 PB 文件 ### 重新生成 PB 文件
```sh ```sh
# CMD # CMD
cd python\wcferry cd clients\python\wcferry
python -m grpc_tools.protoc --python_out=. --proto_path=..\..\rpc\proto\ wcf.proto python -m grpc_tools.protoc --python_out=. --proto_path=..\..\..\WeChatFerry\rpc\proto\ wcf.proto
# GitBash # GitBash
cd python/wcferry cd clients/python/wcferry
python -m grpc_tools.protoc --python_out=. --proto_path=../../rpc/proto/ wcf.proto python -m grpc_tools.protoc --python_out=. --proto_path=../../../WeChatFerry/rpc/proto/ wcf.proto
``` ```
## 版本更新 ## 版本更新
### 39.0.2.0 (2023.07.16)
* 获取朋友圈消息
<details><summary>点击查看更多</summary>
版本号:`w.x.y.z`。 版本号:`w.x.y.z`。
其中: 其中:
@ -131,9 +140,6 @@ python -m grpc_tools.protoc --python_out=. --proto_path=../../rpc/proto/ wcf.pro
* `y` 是 `WeChatFerry` 的版本,从 0 开始 * `y` 是 `WeChatFerry` 的版本,从 0 开始
* `z` 是各客户端的版本,从 0 开始 * `z` 是各客户端的版本,从 0 开始
### 39.0.0.1 (2023.07.15)
修复不能 @ 问题。
功能: 功能:
* 检查登录状态 * 检查登录状态
@ -154,4 +160,7 @@ python -m grpc_tools.protoc --python_out=. --proto_path=../../rpc/proto/ wcf.pro
* 添加群成员 * 添加群成员
* 删除群成员 * 删除群成员
* 解密图片 * 解密图片
* 获取朋友圈消息
* 某功能Breaking Change * 某功能Breaking Change
</details>

View File

@ -25,7 +25,6 @@ def process_msg(wcf: Wcf):
def main(): def main():
LOG.info("Start demo...") LOG.info("Start demo...")
wcf = Wcf(debug=True) # 默认连接本地服务 wcf = Wcf(debug=True) # 默认连接本地服务
# wcf = Wcf("tcp://127.0.0.1:10086") # 连接远端服务
sleep(5) # 等微信加载好,以免信息显示异常 sleep(5) # 等微信加载好,以免信息显示异常
LOG.info(f"已经登录: {True if wcf.is_login() else False}") LOG.info(f"已经登录: {True if wcf.is_login() else False}")
@ -35,7 +34,7 @@ def main():
# wcf.enable_recv_msg(LOG.info) # deprecated # wcf.enable_recv_msg(LOG.info) # deprecated
# 允许接收消息 # 允许接收消息
wcf.enable_receiving_msg() wcf.enable_receiving_msg(pyq=True) # 同时允许接收朋友圈消息
Thread(target=process_msg, name="GetMessage", args=(wcf,), daemon=True).start() Thread(target=process_msg, name="GetMessage", args=(wcf,), daemon=True).start()
# wcf.disable_recv_msg() # 当需要停止接收消息时调用 # wcf.disable_recv_msg() # 当需要停止接收消息时调用
@ -71,6 +70,10 @@ def main():
# ret = wcf.del_chatroom_members("chatroom id", "wxid1,wxid2,wxid3,...") # ret = wcf.del_chatroom_members("chatroom id", "wxid1,wxid2,wxid3,...")
# LOG.info(f"add_chatroom_members: {ret}") # LOG.info(f"add_chatroom_members: {ret}")
sleep(5)
wcf.refresh_pyq(0) # 刷新朋友圈第一页
# wcf.refresh_pyq(id) # 从 id 开始刷新朋友圈
# 一直运行 # 一直运行
wcf.keep_running() wcf.keep_running()

View File

@ -1,7 +1,7 @@
#! /usr/bin/env python3 #! /usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
__version__ = "39.0.0.1" __version__ = "39.0.2.0"
import atexit import atexit
import base64 import base64
@ -358,7 +358,7 @@ class Wcf():
""" """
return self.msgQ.get(block, timeout=1) return self.msgQ.get(block, timeout=1)
def enable_receiving_msg(self) -> bool: def enable_receiving_msg(self, pyq=False) -> bool:
"""允许接收消息,成功后通过 `get_msg` 读取消息""" """允许接收消息,成功后通过 `get_msg` 读取消息"""
def listening_msg(): def listening_msg():
rsp = wcf_pb2.Response() rsp = wcf_pb2.Response()
@ -379,13 +379,14 @@ class Wcf():
req = wcf_pb2.Request() req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_ENABLE_RECV_TXT # FUNC_ENABLE_RECV_TXT req.func = wcf_pb2.FUNC_ENABLE_RECV_TXT # FUNC_ENABLE_RECV_TXT
req.flag = pyq
rsp = self._send_request(req) rsp = self._send_request(req)
if rsp.status != 0: if rsp.status != 0:
return False return False
self._is_receiving_msg = True self._is_receiving_msg = True
# 阻塞,把控制权交给用户 # 阻塞,把控制权交给用户
# self._rpc_get_message(callback) # self.listening_msg(callback)
# 不阻塞,启动一个新的线程来接收消息 # 不阻塞,启动一个新的线程来接收消息
Thread(target=listening_msg, name="GetMessage", daemon=True).start() Thread(target=listening_msg, name="GetMessage", daemon=True).start()
@ -526,6 +527,21 @@ class Wcf():
rsp = self._send_request(req) rsp = self._send_request(req)
return rsp.status return rsp.status
def refresh_pyq(self, id: int = 0) -> int:
"""刷新朋友圈
Args:
id (int): 开始 id0 为最新页
Returns:
int: 1 为成功其他失败
"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_REFRESH_PYQ # FUNC_REFRESH_PYQ
req.ui64 = id
rsp = self._send_request(req)
return rsp.status
def decrypt_image(self, src: str, dst: str) -> bool: def decrypt_image(self, src: str, dst: str) -> bool:
"""解密图片: """解密图片:

View File

@ -13,7 +13,7 @@ _sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\twcf.proto\x12\x03wcf\"\xc8\x02\n\x07Request\x12\x1c\n\x04\x66unc\x18\x01 \x01(\x0e\x32\x0e.wcf.Functions\x12\x1b\n\x05\x65mpty\x18\x02 \x01(\x0b\x32\n.wcf.EmptyH\x00\x12\r\n\x03str\x18\x03 \x01(\tH\x00\x12\x1b\n\x03txt\x18\x04 \x01(\x0b\x32\x0c.wcf.TextMsgH\x00\x12\x1c\n\x04\x66ile\x18\x05 \x01(\x0b\x32\x0c.wcf.PathMsgH\x00\x12\x1d\n\x05query\x18\x06 \x01(\x0b\x32\x0c.wcf.DbQueryH\x00\x12\x1e\n\x01v\x18\x07 \x01(\x0b\x32\x11.wcf.VerificationH\x00\x12\x1c\n\x01m\x18\x08 \x01(\x0b\x32\x0f.wcf.AddMembersH\x00\x12\x1a\n\x03xml\x18\t \x01(\x0b\x32\x0b.wcf.XmlMsgH\x00\x12\x1b\n\x03\x64\x65\x63\x18\n \x01(\x0b\x32\x0c.wcf.DecPathH\x00\x12\x1b\n\x02tf\x18\x0b \x01(\x0b\x32\r.wcf.TransferH\x00\x42\x05\n\x03msg\"\xab\x02\n\x08Response\x12\x1c\n\x04\x66unc\x18\x01 \x01(\x0e\x32\x0e.wcf.Functions\x12\x10\n\x06status\x18\x02 \x01(\x05H\x00\x12\r\n\x03str\x18\x03 \x01(\tH\x00\x12\x1b\n\x05wxmsg\x18\x04 \x01(\x0b\x32\n.wcf.WxMsgH\x00\x12\x1e\n\x05types\x18\x05 \x01(\x0b\x32\r.wcf.MsgTypesH\x00\x12$\n\x08\x63ontacts\x18\x06 \x01(\x0b\x32\x10.wcf.RpcContactsH\x00\x12\x1b\n\x03\x64\x62s\x18\x07 \x01(\x0b\x32\x0c.wcf.DbNamesH\x00\x12\x1f\n\x06tables\x18\x08 \x01(\x0b\x32\r.wcf.DbTablesH\x00\x12\x1b\n\x04rows\x18\t \x01(\x0b\x32\x0b.wcf.DbRowsH\x00\x12\x1b\n\x02ui\x18\n \x01(\x0b\x32\r.wcf.UserInfoH\x00\x42\x05\n\x03msg\"\x07\n\x05\x45mpty\"\xa0\x01\n\x05WxMsg\x12\x0f\n\x07is_self\x18\x01 \x01(\x08\x12\x10\n\x08is_group\x18\x02 \x01(\x08\x12\x0c\n\x04type\x18\x03 \x01(\x05\x12\n\n\x02id\x18\x04 \x01(\t\x12\x0b\n\x03xml\x18\x05 \x01(\t\x12\x0e\n\x06sender\x18\x06 \x01(\t\x12\x0e\n\x06roomid\x18\x07 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x08 \x01(\t\x12\r\n\x05thumb\x18\t \x01(\t\x12\r\n\x05\x65xtra\x18\n \x01(\t\"7\n\x07TextMsg\x12\x0b\n\x03msg\x18\x01 \x01(\t\x12\x10\n\x08receiver\x18\x02 \x01(\t\x12\r\n\x05\x61ters\x18\x03 \x01(\t\")\n\x07PathMsg\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x10\n\x08receiver\x18\x02 \x01(\t\"G\n\x06XmlMsg\x12\x10\n\x08receiver\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\x12\x0c\n\x04path\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\x05\"a\n\x08MsgTypes\x12\'\n\x05types\x18\x01 \x03(\x0b\x32\x18.wcf.MsgTypes.TypesEntry\x1a,\n\nTypesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x87\x01\n\nRpcContact\x12\x0c\n\x04wxid\x18\x01 \x01(\t\x12\x0c\n\x04\x63ode\x18\x02 \x01(\t\x12\x0e\n\x06remark\x18\x03 \x01(\t\x12\x0c\n\x04name\x18\x04 \x01(\t\x12\x0f\n\x07\x63ountry\x18\x05 \x01(\t\x12\x10\n\x08province\x18\x06 \x01(\t\x12\x0c\n\x04\x63ity\x18\x07 \x01(\t\x12\x0e\n\x06gender\x18\x08 \x01(\x05\"0\n\x0bRpcContacts\x12!\n\x08\x63ontacts\x18\x01 \x03(\x0b\x32\x0f.wcf.RpcContact\"\x18\n\x07\x44\x62Names\x12\r\n\x05names\x18\x01 \x03(\t\"$\n\x07\x44\x62Table\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0b\n\x03sql\x18\x02 \x01(\t\"(\n\x08\x44\x62Tables\x12\x1c\n\x06tables\x18\x01 \x03(\x0b\x32\x0c.wcf.DbTable\"\"\n\x07\x44\x62Query\x12\n\n\x02\x64\x62\x18\x01 \x01(\t\x12\x0b\n\x03sql\x18\x02 \x01(\t\"8\n\x07\x44\x62\x46ield\x12\x0c\n\x04type\x18\x01 \x01(\x05\x12\x0e\n\x06\x63olumn\x18\x02 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\x0c\"%\n\x05\x44\x62Row\x12\x1c\n\x06\x66ields\x18\x01 \x03(\x0b\x32\x0c.wcf.DbField\"\"\n\x06\x44\x62Rows\x12\x18\n\x04rows\x18\x01 \x03(\x0b\x32\n.wcf.DbRow\"5\n\x0cVerification\x12\n\n\x02v3\x18\x01 \x01(\t\x12\n\n\x02v4\x18\x02 \x01(\t\x12\r\n\x05scene\x18\x03 \x01(\x05\"+\n\nAddMembers\x12\x0e\n\x06roomid\x18\x01 \x01(\t\x12\r\n\x05wxids\x18\x02 \x01(\t\"D\n\x08UserInfo\x12\x0c\n\x04wxid\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06mobile\x18\x03 \x01(\t\x12\x0c\n\x04home\x18\x04 \x01(\t\"#\n\x07\x44\x65\x63Path\x12\x0b\n\x03src\x18\x01 \x01(\t\x12\x0b\n\x03\x64st\x18\x02 \x01(\t\"4\n\x08Transfer\x12\x0c\n\x04wxid\x18\x01 \x01(\t\x12\x0c\n\x04tfid\x18\x02 \x01(\t\x12\x0c\n\x04taid\x18\x03 \x01(\t*\xee\x03\n\tFunctions\x12\x11\n\rFUNC_RESERVED\x10\x00\x12\x11\n\rFUNC_IS_LOGIN\x10\x01\x12\x16\n\x12\x46UNC_GET_SELF_WXID\x10\x10\x12\x16\n\x12\x46UNC_GET_MSG_TYPES\x10\x11\x12\x15\n\x11\x46UNC_GET_CONTACTS\x10\x12\x12\x15\n\x11\x46UNC_GET_DB_NAMES\x10\x13\x12\x16\n\x12\x46UNC_GET_DB_TABLES\x10\x14\x12\x16\n\x12\x46UNC_GET_USER_INFO\x10\x15\x12\x11\n\rFUNC_SEND_TXT\x10 \x12\x11\n\rFUNC_SEND_IMG\x10!\x12\x12\n\x0e\x46UNC_SEND_FILE\x10\"\x12\x11\n\rFUNC_SEND_XML\x10#\x12\x15\n\x11\x46UNC_SEND_EMOTION\x10$\x12\x18\n\x14\x46UNC_ENABLE_RECV_TXT\x10\x30\x12\x19\n\x15\x46UNC_DISABLE_RECV_TXT\x10@\x12\x16\n\x12\x46UNC_EXEC_DB_QUERY\x10P\x12\x16\n\x12\x46UNC_ACCEPT_FRIEND\x10Q\x12\x16\n\x12\x46UNC_RECV_TRANSFER\x10R\x12\x16\n\x12\x46UNC_DECRYPT_IMAGE\x10`\x12\x19\n\x15\x46UNC_ADD_ROOM_MEMBERS\x10p\x12\x19\n\x15\x46UNC_DEL_ROOM_MEMBERS\x10qB\r\n\x0b\x63om.iamteerb\x06proto3') DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\twcf.proto\x12\x03wcf\"\xe8\x02\n\x07Request\x12\x1c\n\x04\x66unc\x18\x01 \x01(\x0e\x32\x0e.wcf.Functions\x12\x1b\n\x05\x65mpty\x18\x02 \x01(\x0b\x32\n.wcf.EmptyH\x00\x12\r\n\x03str\x18\x03 \x01(\tH\x00\x12\x1b\n\x03txt\x18\x04 \x01(\x0b\x32\x0c.wcf.TextMsgH\x00\x12\x1c\n\x04\x66ile\x18\x05 \x01(\x0b\x32\x0c.wcf.PathMsgH\x00\x12\x1d\n\x05query\x18\x06 \x01(\x0b\x32\x0c.wcf.DbQueryH\x00\x12\x1e\n\x01v\x18\x07 \x01(\x0b\x32\x11.wcf.VerificationH\x00\x12\x1c\n\x01m\x18\x08 \x01(\x0b\x32\x0f.wcf.AddMembersH\x00\x12\x1a\n\x03xml\x18\t \x01(\x0b\x32\x0b.wcf.XmlMsgH\x00\x12\x1b\n\x03\x64\x65\x63\x18\n \x01(\x0b\x32\x0c.wcf.DecPathH\x00\x12\x1b\n\x02tf\x18\x0b \x01(\x0b\x32\r.wcf.TransferH\x00\x12\x0e\n\x04ui64\x18\x0c \x01(\x04H\x00\x12\x0e\n\x04\x66lag\x18\r \x01(\x08H\x00\x42\x05\n\x03msg\"\xab\x02\n\x08Response\x12\x1c\n\x04\x66unc\x18\x01 \x01(\x0e\x32\x0e.wcf.Functions\x12\x10\n\x06status\x18\x02 \x01(\x05H\x00\x12\r\n\x03str\x18\x03 \x01(\tH\x00\x12\x1b\n\x05wxmsg\x18\x04 \x01(\x0b\x32\n.wcf.WxMsgH\x00\x12\x1e\n\x05types\x18\x05 \x01(\x0b\x32\r.wcf.MsgTypesH\x00\x12$\n\x08\x63ontacts\x18\x06 \x01(\x0b\x32\x10.wcf.RpcContactsH\x00\x12\x1b\n\x03\x64\x62s\x18\x07 \x01(\x0b\x32\x0c.wcf.DbNamesH\x00\x12\x1f\n\x06tables\x18\x08 \x01(\x0b\x32\r.wcf.DbTablesH\x00\x12\x1b\n\x04rows\x18\t \x01(\x0b\x32\x0b.wcf.DbRowsH\x00\x12\x1b\n\x02ui\x18\n \x01(\x0b\x32\r.wcf.UserInfoH\x00\x42\x05\n\x03msg\"\x07\n\x05\x45mpty\"\xba\x01\n\x05WxMsg\x12\x0f\n\x07is_self\x18\x01 \x01(\x08\x12\x10\n\x08is_group\x18\x02 \x01(\x08\x12\n\n\x02id\x18\x03 \x01(\x04\x12\x0c\n\x04type\x18\x04 \x01(\r\x12\n\n\x02ts\x18\x05 \x01(\r\x12\x0e\n\x06roomid\x18\x06 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x07 \x01(\t\x12\x0e\n\x06sender\x18\x08 \x01(\t\x12\x0c\n\x04sign\x18\t \x01(\t\x12\r\n\x05thumb\x18\n \x01(\t\x12\r\n\x05\x65xtra\x18\x0b \x01(\t\x12\x0b\n\x03xml\x18\x0c \x01(\t\"7\n\x07TextMsg\x12\x0b\n\x03msg\x18\x01 \x01(\t\x12\x10\n\x08receiver\x18\x02 \x01(\t\x12\r\n\x05\x61ters\x18\x03 \x01(\t\")\n\x07PathMsg\x12\x0c\n\x04path\x18\x01 \x01(\t\x12\x10\n\x08receiver\x18\x02 \x01(\t\"G\n\x06XmlMsg\x12\x10\n\x08receiver\x18\x01 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x02 \x01(\t\x12\x0c\n\x04path\x18\x03 \x01(\t\x12\x0c\n\x04type\x18\x04 \x01(\x05\"a\n\x08MsgTypes\x12\'\n\x05types\x18\x01 \x03(\x0b\x32\x18.wcf.MsgTypes.TypesEntry\x1a,\n\nTypesEntry\x12\x0b\n\x03key\x18\x01 \x01(\x05\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\x87\x01\n\nRpcContact\x12\x0c\n\x04wxid\x18\x01 \x01(\t\x12\x0c\n\x04\x63ode\x18\x02 \x01(\t\x12\x0e\n\x06remark\x18\x03 \x01(\t\x12\x0c\n\x04name\x18\x04 \x01(\t\x12\x0f\n\x07\x63ountry\x18\x05 \x01(\t\x12\x10\n\x08province\x18\x06 \x01(\t\x12\x0c\n\x04\x63ity\x18\x07 \x01(\t\x12\x0e\n\x06gender\x18\x08 \x01(\x05\"0\n\x0bRpcContacts\x12!\n\x08\x63ontacts\x18\x01 \x03(\x0b\x32\x0f.wcf.RpcContact\"\x18\n\x07\x44\x62Names\x12\r\n\x05names\x18\x01 \x03(\t\"$\n\x07\x44\x62Table\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0b\n\x03sql\x18\x02 \x01(\t\"(\n\x08\x44\x62Tables\x12\x1c\n\x06tables\x18\x01 \x03(\x0b\x32\x0c.wcf.DbTable\"\"\n\x07\x44\x62Query\x12\n\n\x02\x64\x62\x18\x01 \x01(\t\x12\x0b\n\x03sql\x18\x02 \x01(\t\"8\n\x07\x44\x62\x46ield\x12\x0c\n\x04type\x18\x01 \x01(\x05\x12\x0e\n\x06\x63olumn\x18\x02 \x01(\t\x12\x0f\n\x07\x63ontent\x18\x03 \x01(\x0c\"%\n\x05\x44\x62Row\x12\x1c\n\x06\x66ields\x18\x01 \x03(\x0b\x32\x0c.wcf.DbField\"\"\n\x06\x44\x62Rows\x12\x18\n\x04rows\x18\x01 \x03(\x0b\x32\n.wcf.DbRow\"5\n\x0cVerification\x12\n\n\x02v3\x18\x01 \x01(\t\x12\n\n\x02v4\x18\x02 \x01(\t\x12\r\n\x05scene\x18\x03 \x01(\x05\"+\n\nAddMembers\x12\x0e\n\x06roomid\x18\x01 \x01(\t\x12\r\n\x05wxids\x18\x02 \x01(\t\"D\n\x08UserInfo\x12\x0c\n\x04wxid\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0e\n\x06mobile\x18\x03 \x01(\t\x12\x0c\n\x04home\x18\x04 \x01(\t\"#\n\x07\x44\x65\x63Path\x12\x0b\n\x03src\x18\x01 \x01(\t\x12\x0b\n\x03\x64st\x18\x02 \x01(\t\"4\n\x08Transfer\x12\x0c\n\x04wxid\x18\x01 \x01(\t\x12\x0c\n\x04tfid\x18\x02 \x01(\t\x12\x0c\n\x04taid\x18\x03 \x01(\t*\x84\x04\n\tFunctions\x12\x11\n\rFUNC_RESERVED\x10\x00\x12\x11\n\rFUNC_IS_LOGIN\x10\x01\x12\x16\n\x12\x46UNC_GET_SELF_WXID\x10\x10\x12\x16\n\x12\x46UNC_GET_MSG_TYPES\x10\x11\x12\x15\n\x11\x46UNC_GET_CONTACTS\x10\x12\x12\x15\n\x11\x46UNC_GET_DB_NAMES\x10\x13\x12\x16\n\x12\x46UNC_GET_DB_TABLES\x10\x14\x12\x16\n\x12\x46UNC_GET_USER_INFO\x10\x15\x12\x11\n\rFUNC_SEND_TXT\x10 \x12\x11\n\rFUNC_SEND_IMG\x10!\x12\x12\n\x0e\x46UNC_SEND_FILE\x10\"\x12\x11\n\rFUNC_SEND_XML\x10#\x12\x15\n\x11\x46UNC_SEND_EMOTION\x10$\x12\x18\n\x14\x46UNC_ENABLE_RECV_TXT\x10\x30\x12\x19\n\x15\x46UNC_DISABLE_RECV_TXT\x10@\x12\x16\n\x12\x46UNC_EXEC_DB_QUERY\x10P\x12\x16\n\x12\x46UNC_ACCEPT_FRIEND\x10Q\x12\x16\n\x12\x46UNC_RECV_TRANSFER\x10R\x12\x14\n\x10\x46UNC_REFRESH_PYQ\x10S\x12\x16\n\x12\x46UNC_DECRYPT_IMAGE\x10`\x12\x19\n\x15\x46UNC_ADD_ROOM_MEMBERS\x10p\x12\x19\n\x15\x46UNC_DEL_ROOM_MEMBERS\x10qB\r\n\x0b\x63om.iamteerb\x06proto3')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals()) _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'wcf_pb2', globals()) _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'wcf_pb2', globals())
@ -23,52 +23,52 @@ if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._serialized_options = b'\n\013com.iamteer' DESCRIPTOR._serialized_options = b'\n\013com.iamteer'
_MSGTYPES_TYPESENTRY._options = None _MSGTYPES_TYPESENTRY._options = None
_MSGTYPES_TYPESENTRY._serialized_options = b'8\001' _MSGTYPES_TYPESENTRY._serialized_options = b'8\001'
_FUNCTIONS._serialized_start=1820 _FUNCTIONS._serialized_start=1878
_FUNCTIONS._serialized_end=2314 _FUNCTIONS._serialized_end=2394
_REQUEST._serialized_start=19 _REQUEST._serialized_start=19
_REQUEST._serialized_end=347 _REQUEST._serialized_end=379
_RESPONSE._serialized_start=350 _RESPONSE._serialized_start=382
_RESPONSE._serialized_end=649 _RESPONSE._serialized_end=681
_EMPTY._serialized_start=651 _EMPTY._serialized_start=683
_EMPTY._serialized_end=658 _EMPTY._serialized_end=690
_WXMSG._serialized_start=661 _WXMSG._serialized_start=693
_WXMSG._serialized_end=821 _WXMSG._serialized_end=879
_TEXTMSG._serialized_start=823 _TEXTMSG._serialized_start=881
_TEXTMSG._serialized_end=878 _TEXTMSG._serialized_end=936
_PATHMSG._serialized_start=880 _PATHMSG._serialized_start=938
_PATHMSG._serialized_end=921 _PATHMSG._serialized_end=979
_XMLMSG._serialized_start=923 _XMLMSG._serialized_start=981
_XMLMSG._serialized_end=994 _XMLMSG._serialized_end=1052
_MSGTYPES._serialized_start=996 _MSGTYPES._serialized_start=1054
_MSGTYPES._serialized_end=1093 _MSGTYPES._serialized_end=1151
_MSGTYPES_TYPESENTRY._serialized_start=1049 _MSGTYPES_TYPESENTRY._serialized_start=1107
_MSGTYPES_TYPESENTRY._serialized_end=1093 _MSGTYPES_TYPESENTRY._serialized_end=1151
_RPCCONTACT._serialized_start=1096 _RPCCONTACT._serialized_start=1154
_RPCCONTACT._serialized_end=1231 _RPCCONTACT._serialized_end=1289
_RPCCONTACTS._serialized_start=1233 _RPCCONTACTS._serialized_start=1291
_RPCCONTACTS._serialized_end=1281 _RPCCONTACTS._serialized_end=1339
_DBNAMES._serialized_start=1283 _DBNAMES._serialized_start=1341
_DBNAMES._serialized_end=1307 _DBNAMES._serialized_end=1365
_DBTABLE._serialized_start=1309 _DBTABLE._serialized_start=1367
_DBTABLE._serialized_end=1345 _DBTABLE._serialized_end=1403
_DBTABLES._serialized_start=1347 _DBTABLES._serialized_start=1405
_DBTABLES._serialized_end=1387 _DBTABLES._serialized_end=1445
_DBQUERY._serialized_start=1389 _DBQUERY._serialized_start=1447
_DBQUERY._serialized_end=1423 _DBQUERY._serialized_end=1481
_DBFIELD._serialized_start=1425 _DBFIELD._serialized_start=1483
_DBFIELD._serialized_end=1481 _DBFIELD._serialized_end=1539
_DBROW._serialized_start=1483 _DBROW._serialized_start=1541
_DBROW._serialized_end=1520 _DBROW._serialized_end=1578
_DBROWS._serialized_start=1522 _DBROWS._serialized_start=1580
_DBROWS._serialized_end=1556 _DBROWS._serialized_end=1614
_VERIFICATION._serialized_start=1558 _VERIFICATION._serialized_start=1616
_VERIFICATION._serialized_end=1611 _VERIFICATION._serialized_end=1669
_ADDMEMBERS._serialized_start=1613 _ADDMEMBERS._serialized_start=1671
_ADDMEMBERS._serialized_end=1656 _ADDMEMBERS._serialized_end=1714
_USERINFO._serialized_start=1658 _USERINFO._serialized_start=1716
_USERINFO._serialized_end=1726 _USERINFO._serialized_end=1784
_DECPATH._serialized_start=1728 _DECPATH._serialized_start=1786
_DECPATH._serialized_end=1763 _DECPATH._serialized_end=1821
_TRANSFER._serialized_start=1765 _TRANSFER._serialized_start=1823
_TRANSFER._serialized_end=1817 _TRANSFER._serialized_end=1875
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re import re
from datetime import datetime
from wcferry import wcf_pb2 from wcferry import wcf_pb2
@ -24,6 +25,8 @@ class WxMsg():
self._is_group = msg.is_group self._is_group = msg.is_group
self.type = msg.type self.type = msg.type
self.id = msg.id self.id = msg.id
self.ts = msg.ts
self.sign = msg.sign
self.xml = msg.xml self.xml = msg.xml
self.sender = msg.sender self.sender = msg.sender
self.roomid = msg.roomid self.roomid = msg.roomid
@ -33,7 +36,8 @@ class WxMsg():
def __str__(self) -> str: def __str__(self) -> str:
s = f"{'自己发的:' if self._is_self else ''}" s = f"{'自己发的:' if self._is_self else ''}"
s += f"{self.sender}[{self.roomid}]:{self.id}:{self.type}:{self.xml.replace(chr(10), '').replace(chr(9),'')}\n" s += f"{self.sender}[{self.roomid}]|{self.id}|{datetime.fromtimestamp(self.ts)}|{self.type}|{self.sign}"
s += f"\n{self.xml.replace(chr(10), '').replace(chr(9),'')}\n"
s += self.content s += self.content
s += f"\n{self.thumb}" if self.thumb else "" s += f"\n{self.thumb}" if self.thumb else ""
s += f"\n{self.extra}" if self.extra else "" s += f"\n{self.extra}" if self.extra else ""

File diff suppressed because one or more lines are too long