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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,8 +9,8 @@ WxCalls_t wxCalls = {
{ 0x2FFD484, 0x2FFD590, 0x2FFD500, 0x30238CC }, // User Info: wxid, nickname, mobile, home
{ 0x768140, 0xCE6C80, 0x756960 }, // Send Message
/* Receive Message:
Hook, call, type, self, id, msgXml, roomId, wxId, content, thumb, extra */
{ 0xD19A0B, 0x756960, 0x38, 0x3C, 0x194, 0x1FC, 0x48, 0x180, 0x70, 0x1A8, 0x1BC },
Hook, call, msgId, type, isSelf, ts, roomId, content, wxid, sign, thumb, extra, msgXml */
{ 0xD19A0B, 0x756960, 0x30, 0x38, 0x3C, 0x44, 0x48, 0x70, 0x180, 0x194, 0x1A8, 0x1BC, 0x1FC },
{ 0x768140, 0XF59E40, 0XCE6640, 0x756960 }, // Send Image Message
{ 0x76AE20, 0xF59E40, 0xB6D1F0, 0x756960 }, // Send File Message
{ 0xB8A70, 0x3ED5E0, 0x107F00, 0x3ED7B0, 0x2386FE4 }, // Send xml Message
@ -24,7 +24,10 @@ WxCalls_t wxCalls = {
{ 0xA17D50, 0xF59E40, 0xA18BD0, 0xA17E70 }, // Accept New Friend application
{ 0x78CF20, 0xF59E40, 0xBD1DC0 }, // Add 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)

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 "load_calls.h"
#include "log.h"
#include "receive_msg.h"
#include "user_info.h"
#include "util.h"
// Defined in rpc_server.cpp
extern bool gIsListening;
extern bool gIsListening, gIsListeningPyq;
extern mutex gMutex;
extern condition_variable gCV;
extern queue<WxMsg_t> gMsgQueue;
@ -26,9 +27,15 @@ static DWORD recvMsgCallAddr = 0;
static DWORD recvMsgJumpBackAddr = 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()
{
const MsgTypes_t m = {
{ 0x00, "朋友圈消息" },
{ 0x01, "文字" },
{ 0x03, "图片" },
{ 0x22, "语音" },
@ -90,9 +97,12 @@ void DispatchMsg(DWORD reg)
{
WxMsg_t wxMsg;
wxMsg.id = GET_QWORD(reg + g_WxCalls.recvMsg.msgId);
wxMsg.type = GET_DWORD(reg + g_WxCalls.recvMsg.type);
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);
string roomid = GetStringByWstrAddr(reg + g_WxCalls.recvMsg.roomId);
@ -102,7 +112,7 @@ void DispatchMsg(DWORD reg)
if (wxMsg.is_self) {
wxMsg.sender = GetSelfWxid();
} else {
wxMsg.sender = GetStringByStrAddr(reg + g_WxCalls.recvMsg.wxId);
wxMsg.sender = GetStringByStrAddr(reg + g_WxCalls.recvMsg.wxid);
}
} else {
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);
if (!wxMsg.thumb.empty()) {
wxMsg.thumb = GetHomePath() + wxMsg.thumb;
@ -173,3 +181,74 @@ void UnListenMessage()
UnHookAddress(recvMsgHookAddr, recvMsgBackupCode);
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"
void ListenPyq();
void UnListenPyq();
void ListenMessage();
void UnListenMessage();
MsgTypes_t GetMsgTypes();

View File

@ -23,6 +23,7 @@
#include "log.h"
#include "pb_types.h"
#include "pb_util.h"
#include "pyq.h"
#include "receive_msg.h"
#include "receive_transfer.h"
#include "rpc_server.h"
@ -38,7 +39,8 @@
extern int IsLogin(void); // Defined in spy.cpp
bool gIsListening;
bool gIsListening = false;
bool gIsListeningPyq = false;
mutex gMutex;
condition_variable gCV;
queue<WxMsg_t> gMsgQueue;
@ -345,16 +347,18 @@ static void PushMessage()
if (gCV.wait_for(lock, chrono::milliseconds(1000), []() { return !gMsgQueue.empty(); })) {
while (!gMsgQueue.empty()) {
auto wxmsg = gMsgQueue.front();
rsp.msg.wxmsg.id = wxmsg.id;
rsp.msg.wxmsg.is_self = wxmsg.is_self;
rsp.msg.wxmsg.is_group = wxmsg.is_group;
rsp.msg.wxmsg.type = wxmsg.type;
rsp.msg.wxmsg.id = (char *)wxmsg.id.c_str();
rsp.msg.wxmsg.xml = (char *)wxmsg.xml.c_str();
rsp.msg.wxmsg.sender = (char *)wxmsg.sender.c_str();
rsp.msg.wxmsg.ts = wxmsg.ts;
rsp.msg.wxmsg.roomid = (char *)wxmsg.roomid.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.extra = (char *)wxmsg.extra.c_str();
rsp.msg.wxmsg.xml = (char *)wxmsg.xml.c_str();
gMsgQueue.pop();
LOG_DEBUG("Recv msg: {}", wxmsg.content);
pb_ostream_t stream = pb_ostream_from_buffer(buffer, G_BUF_SIZE);
@ -374,7 +378,7 @@ static void PushMessage()
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;
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;
ListenMessage();
if (pyq) {
ListenPyq();
}
HANDLE msgThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)PushMessage, NULL, NULL, NULL);
if (msgThread != 0) {
CloseHandle(msgThread);
@ -405,6 +412,7 @@ bool func_disable_recv_txt(uint8_t *out, size_t *len)
rsp.which_msg = Response_status_tag;
rsp.msg.status = 0;
UnListenPyq();
UnListenMessage(); // 可能需要1秒之后才能退出见 PushMessage
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;
}
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)
{
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
case 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;
}
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);
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: {
LOG_DEBUG("[FUNCTIONS_FUNC_DECRYPT_IMAGE]");
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
FILEVERSION 39,0,0,0
FILEVERSION 39,0,2,0
PRODUCTVERSION 3,9,2,23
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
@ -69,7 +69,7 @@ BEGIN
BEGIN
VALUE "CompanyName", "WeChatFerry"
VALUE "FileDescription", "WeChatFerry"
VALUE "FileVersion", "39.0.0.0"
VALUE "FileVersion", "39.0.2.0"
VALUE "InternalName", "spy.dll"
VALUE "LegalCopyright", "Copyright (C) 2023"
VALUE "OriginalFilename", "spy.dll"

View File

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

View File

@ -9,6 +9,7 @@
#define WECHATINJECTDLL_DEBUG L"spy_debug.dll"
#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_WSTRING(addr) ((WCHAR *)(*(DWORD *)(addr)))
#define GET_STRING_FROM_P(addr) ((CHAR *)(addr))

View File

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

View File

@ -3,18 +3,22 @@
import base64
import logging
from queue import Empty
from threading import Thread
from typing import Any
import requests
from fastapi import Body, FastAPI
from fastapi import Body, Query, FastAPI
from pydantic import BaseModel
from wcferry import Wcf, WxMsg
__version__ = "39.0.0.1"
__version__ = "39.0.2.0"
class Msg(BaseModel):
id: str
id: int
ts: int
sign: str
type: int
xml: 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("/dbs", self.get_dbs, methods=["GET"], summary="获取所有数据库")
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("/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("/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.del_chatroom_members, methods=["DELETE"], summary="删除群成员")
self.add_api_route("/transfer", self.receive_transfer, methods=["POST"], summary="接收转账")
self.add_api_route("/dec-image", self.decrypt_image, methods=["POST"], summary="解密图片")
def _set_cb(self, cb):
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()
self.add_api_route("/chatroom-member", self.del_chatroom_members, methods=["DELETE"], summary="删除群成员")
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 _forward_msg(self, msg, cb):
data = {}
data["id"] = msg.id
data["ts"] = msg.ts
data["sign"] = msg.sign
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:
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:
self.LOG.info(f"没有设置回调,打印消息")
self.wcf.enable_recv_msg(print)
@ -324,6 +344,18 @@ class Http(FastAPI):
ret = self.wcf.receive_transfer(wxid, transferid, transactionid)
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,
src: str = Body("C:\\...", description="加密的图片路径,从图片消息中获取"),
dst: str = Body("C:\\...", description="解密的图片路径")) -> dict:

View File

@ -39,7 +39,7 @@ def process_msg(wcf: Wcf):
def main():
LOG.info("Start demo...")
wcf = Wcf(debug=True)
wcf = Wcf(debug=True) # 默认连接本地服务
sleep(5) # 等微信加载好,以免信息显示异常
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_receiving_msg()
wcf.enable_receiving_msg(pyq=True) # 同时允许接收朋友圈消息
Thread(target=process_msg, name="GetMessage", args=(wcf,), daemon=True).start()
# wcf.disable_recv_msg() # 当需要停止接收消息时调用
@ -71,7 +71,7 @@ def main():
sleep(5)
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;')}")
# 需要真正的 V3、V4 信息
@ -85,6 +85,10 @@ def main():
# ret = wcf.del_chatroom_members("chatroom id", "wxid1,wxid2,wxid3,...")
# LOG.info(f"add_chatroom_members: {ret}")
sleep(5)
wcf.refresh_pyq(0) # 刷新朋友圈第一页
# wcf.refresh_pyq(id) # 从 id 开始刷新朋友圈
# 一直运行
wcf.keep_running()
@ -114,15 +118,20 @@ pip install grpcio-tools pynng
### 重新生成 PB 文件
```sh
# CMD
cd python\wcferry
python -m grpc_tools.protoc --python_out=. --proto_path=..\..\rpc\proto\ wcf.proto
cd clients\python\wcferry
python -m grpc_tools.protoc --python_out=. --proto_path=..\..\..\WeChatFerry\rpc\proto\ wcf.proto
# GitBash
cd python/wcferry
python -m grpc_tools.protoc --python_out=. --proto_path=../../rpc/proto/ wcf.proto
cd clients/python/wcferry
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`。
其中:
@ -131,9 +140,6 @@ python -m grpc_tools.protoc --python_out=. --proto_path=../../rpc/proto/ wcf.pro
* `y` 是 `WeChatFerry` 的版本,从 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
</details>

View File

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

View File

@ -1,7 +1,7 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
__version__ = "39.0.0.1"
__version__ = "39.0.2.0"
import atexit
import base64
@ -358,7 +358,7 @@ class Wcf():
"""
return self.msgQ.get(block, timeout=1)
def enable_receiving_msg(self) -> bool:
def enable_receiving_msg(self, pyq=False) -> bool:
"""允许接收消息,成功后通过 `get_msg` 读取消息"""
def listening_msg():
rsp = wcf_pb2.Response()
@ -379,13 +379,14 @@ class Wcf():
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_ENABLE_RECV_TXT # FUNC_ENABLE_RECV_TXT
req.flag = pyq
rsp = self._send_request(req)
if rsp.status != 0:
return False
self._is_receiving_msg = True
# 阻塞,把控制权交给用户
# self._rpc_get_message(callback)
# self.listening_msg(callback)
# 不阻塞,启动一个新的线程来接收消息
Thread(target=listening_msg, name="GetMessage", daemon=True).start()
@ -526,6 +527,21 @@ class Wcf():
rsp = self._send_request(req)
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:
"""解密图片:

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.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'wcf_pb2', globals())
@ -23,52 +23,52 @@ if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._serialized_options = b'\n\013com.iamteer'
_MSGTYPES_TYPESENTRY._options = None
_MSGTYPES_TYPESENTRY._serialized_options = b'8\001'
_FUNCTIONS._serialized_start=1820
_FUNCTIONS._serialized_end=2314
_FUNCTIONS._serialized_start=1878
_FUNCTIONS._serialized_end=2394
_REQUEST._serialized_start=19
_REQUEST._serialized_end=347
_RESPONSE._serialized_start=350
_RESPONSE._serialized_end=649
_EMPTY._serialized_start=651
_EMPTY._serialized_end=658
_WXMSG._serialized_start=661
_WXMSG._serialized_end=821
_TEXTMSG._serialized_start=823
_TEXTMSG._serialized_end=878
_PATHMSG._serialized_start=880
_PATHMSG._serialized_end=921
_XMLMSG._serialized_start=923
_XMLMSG._serialized_end=994
_MSGTYPES._serialized_start=996
_MSGTYPES._serialized_end=1093
_MSGTYPES_TYPESENTRY._serialized_start=1049
_MSGTYPES_TYPESENTRY._serialized_end=1093
_RPCCONTACT._serialized_start=1096
_RPCCONTACT._serialized_end=1231
_RPCCONTACTS._serialized_start=1233
_RPCCONTACTS._serialized_end=1281
_DBNAMES._serialized_start=1283
_DBNAMES._serialized_end=1307
_DBTABLE._serialized_start=1309
_DBTABLE._serialized_end=1345
_DBTABLES._serialized_start=1347
_DBTABLES._serialized_end=1387
_DBQUERY._serialized_start=1389
_DBQUERY._serialized_end=1423
_DBFIELD._serialized_start=1425
_DBFIELD._serialized_end=1481
_DBROW._serialized_start=1483
_DBROW._serialized_end=1520
_DBROWS._serialized_start=1522
_DBROWS._serialized_end=1556
_VERIFICATION._serialized_start=1558
_VERIFICATION._serialized_end=1611
_ADDMEMBERS._serialized_start=1613
_ADDMEMBERS._serialized_end=1656
_USERINFO._serialized_start=1658
_USERINFO._serialized_end=1726
_DECPATH._serialized_start=1728
_DECPATH._serialized_end=1763
_TRANSFER._serialized_start=1765
_TRANSFER._serialized_end=1817
_REQUEST._serialized_end=379
_RESPONSE._serialized_start=382
_RESPONSE._serialized_end=681
_EMPTY._serialized_start=683
_EMPTY._serialized_end=690
_WXMSG._serialized_start=693
_WXMSG._serialized_end=879
_TEXTMSG._serialized_start=881
_TEXTMSG._serialized_end=936
_PATHMSG._serialized_start=938
_PATHMSG._serialized_end=979
_XMLMSG._serialized_start=981
_XMLMSG._serialized_end=1052
_MSGTYPES._serialized_start=1054
_MSGTYPES._serialized_end=1151
_MSGTYPES_TYPESENTRY._serialized_start=1107
_MSGTYPES_TYPESENTRY._serialized_end=1151
_RPCCONTACT._serialized_start=1154
_RPCCONTACT._serialized_end=1289
_RPCCONTACTS._serialized_start=1291
_RPCCONTACTS._serialized_end=1339
_DBNAMES._serialized_start=1341
_DBNAMES._serialized_end=1365
_DBTABLE._serialized_start=1367
_DBTABLE._serialized_end=1403
_DBTABLES._serialized_start=1405
_DBTABLES._serialized_end=1445
_DBQUERY._serialized_start=1447
_DBQUERY._serialized_end=1481
_DBFIELD._serialized_start=1483
_DBFIELD._serialized_end=1539
_DBROW._serialized_start=1541
_DBROW._serialized_end=1578
_DBROWS._serialized_start=1580
_DBROWS._serialized_end=1614
_VERIFICATION._serialized_start=1616
_VERIFICATION._serialized_end=1669
_ADDMEMBERS._serialized_start=1671
_ADDMEMBERS._serialized_end=1714
_USERINFO._serialized_start=1716
_USERINFO._serialized_end=1784
_DECPATH._serialized_start=1786
_DECPATH._serialized_end=1821
_TRANSFER._serialized_start=1823
_TRANSFER._serialized_end=1875
# @@protoc_insertion_point(module_scope)

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import re
from datetime import datetime
from wcferry import wcf_pb2
@ -24,6 +25,8 @@ class WxMsg():
self._is_group = msg.is_group
self.type = msg.type
self.id = msg.id
self.ts = msg.ts
self.sign = msg.sign
self.xml = msg.xml
self.sender = msg.sender
self.roomid = msg.roomid
@ -33,7 +36,8 @@ class WxMsg():
def __str__(self) -> str:
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 += f"\n{self.thumb}" if self.thumb else ""
s += f"\n{self.extra}" if self.extra else ""

File diff suppressed because one or more lines are too long