diff --git a/README.MD b/README.MD index c470ea5..17c6b97 100644 --- a/README.MD +++ b/README.MD @@ -182,6 +182,12 @@ Rust 客户端。 ## 版本更新 + +### v39.0.2 (2023.07.16) +* 修复朋友圈消息 `is_group` 为 `True` 问题 + +
点击查看更多 + 客户端越来越多了,版本号开始混乱,所以重新定义了版本号:`w.x.y.z`。 其中: @@ -190,7 +196,8 @@ Rust 客户端。 * `y` 是 `WeChatFerry` 的版本,从 0 开始 * `z` 是各客户端的版本,从 0 开始 -
点击查看 +### v39.0.1 (2023.07.16) +* 获取朋友圈消息 ### v39.0.0 (2023.07.14) 升级到 `3.9.2.23`。 diff --git a/WeChatFerry/rpc/pb_types.h b/WeChatFerry/rpc/pb_types.h index 447f53a..3e8488c 100644 --- a/WeChatFerry/rpc/pb_types.h +++ b/WeChatFerry/rpc/pb_types.h @@ -38,14 +38,16 @@ typedef vector 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 { diff --git a/WeChatFerry/rpc/proto/wcf.proto b/WeChatFerry/rpc/proto/wcf.proto index caf79f6..5cdc36b 100644 --- a/WeChatFerry/rpc/proto/wcf.proto +++ b/WeChatFerry/rpc/proto/wcf.proto @@ -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 diff --git a/WeChatFerry/spy/Spy.vcxproj b/WeChatFerry/spy/Spy.vcxproj index e28f3f9..0b34661 100644 --- a/WeChatFerry/spy/Spy.vcxproj +++ b/WeChatFerry/spy/Spy.vcxproj @@ -233,6 +233,7 @@ $(SolutionDir)rpc\tool\protoc --nanopb_out=. wcf.proto + @@ -257,6 +258,7 @@ $(SolutionDir)rpc\tool\protoc --nanopb_out=. wcf.proto + diff --git a/WeChatFerry/spy/Spy.vcxproj.filters b/WeChatFerry/spy/Spy.vcxproj.filters index 64edd64..967ac9d 100644 --- a/WeChatFerry/spy/Spy.vcxproj.filters +++ b/WeChatFerry/spy/Spy.vcxproj.filters @@ -90,6 +90,9 @@ 头文件 + + 头文件 + @@ -149,6 +152,9 @@ 源文件 + + 源文件 + diff --git a/WeChatFerry/spy/load_calls.cpp b/WeChatFerry/spy/load_calls.cpp index 9384492..b2551ad 100644 --- a/WeChatFerry/spy/load_calls.cpp +++ b/WeChatFerry/spy/load_calls.cpp @@ -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) diff --git a/WeChatFerry/spy/pyq.cpp b/WeChatFerry/spy/pyq.cpp new file mode 100644 index 0000000..7172318 --- /dev/null +++ b/WeChatFerry/spy/pyq.cpp @@ -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); +} diff --git a/WeChatFerry/spy/pyq.h b/WeChatFerry/spy/pyq.h new file mode 100644 index 0000000..13d7eeb --- /dev/null +++ b/WeChatFerry/spy/pyq.h @@ -0,0 +1,5 @@ +#pragma once + +#include "stdint.h" + +int RefreshPyq(uint64_t id); diff --git a/WeChatFerry/spy/receive_msg.cpp b/WeChatFerry/spy/receive_msg.cpp index e495a63..ca9abbf 100644 --- a/WeChatFerry/spy/receive_msg.cpp +++ b/WeChatFerry/spy/receive_msg.cpp @@ -6,12 +6,13 @@ #include #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 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 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; +} diff --git a/WeChatFerry/spy/receive_msg.h b/WeChatFerry/spy/receive_msg.h index f878c18..ff17170 100644 --- a/WeChatFerry/spy/receive_msg.h +++ b/WeChatFerry/spy/receive_msg.h @@ -2,6 +2,8 @@ #include "pb_types.h" +void ListenPyq(); +void UnListenPyq(); void ListenMessage(); void UnListenMessage(); MsgTypes_t GetMsgTypes(); diff --git a/WeChatFerry/spy/rpc_server.cpp b/WeChatFerry/spy/rpc_server.cpp index 09de304..32a1686 100644 --- a/WeChatFerry/spy/rpc_server.cpp +++ b/WeChatFerry/spy/rpc_server.cpp @@ -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 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); diff --git a/WeChatFerry/spy/spy.aps b/WeChatFerry/spy/spy.aps index e053b40..3c8ea0e 100644 Binary files a/WeChatFerry/spy/spy.aps and b/WeChatFerry/spy/spy.aps differ diff --git a/WeChatFerry/spy/spy.rc b/WeChatFerry/spy/spy.rc index abf36e5..9b3b9ce 100644 --- a/WeChatFerry/spy/spy.rc +++ b/WeChatFerry/spy/spy.rc @@ -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" diff --git a/WeChatFerry/spy/spy_types.h b/WeChatFerry/spy/spy_types.h index b26d546..a279714 100644 --- a/WeChatFerry/spy/spy_types.h +++ b/WeChatFerry/spy/spy_types.h @@ -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 { diff --git a/WeChatFerry/spy/util.h b/WeChatFerry/spy/util.h index 5cf305f..11f03cd 100644 --- a/WeChatFerry/spy/util.h +++ b/WeChatFerry/spy/util.h @@ -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)) diff --git a/clients/http/setup.py b/clients/http/setup.py index 68d0a17..34d57bf 100644 --- a/clients/http/setup.py +++ b/clients/http/setup.py @@ -33,7 +33,7 @@ setup( "setuptools", "fastapi", "uvicorn[standard]", - "wcferry>=39.0.0.0", + "wcferry>=39.0.2.0", ], classifiers=[ "Environment :: Win32 (MS Windows)", diff --git a/clients/http/wcfhttp/core.py b/clients/http/wcfhttp/core.py index ec629dd..ec95b60 100644 --- a/clients/http/wcfhttp/core.py +++ b/clients/http/wcfhttp/core.py @@ -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="开始 id,0 为最新页")) -> dict: + """刷新朋友圈 + + Args: + id (int): 开始 id,0 为最新页 + + 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: diff --git a/clients/python/README.MD b/clients/python/README.MD index de62e3b..9e5b875 100644 --- a/clients/python/README.MD +++ b/clients/python/README.MD @@ -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) +* 获取朋友圈消息 + +
点击查看更多 版本号:`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) + +
diff --git a/clients/python/demo.py b/clients/python/demo.py index fd6d76f..3363fe0 100644 --- a/clients/python/demo.py +++ b/clients/python/demo.py @@ -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() diff --git a/clients/python/wcferry/client.py b/clients/python/wcferry/client.py index 5ef85b4..856c3ea 100644 --- a/clients/python/wcferry/client.py +++ b/clients/python/wcferry/client.py @@ -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): 开始 id,0 为最新页 + + 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: """解密图片: diff --git a/clients/python/wcferry/wcf_pb2.py b/clients/python/wcferry/wcf_pb2.py index 94b36fc..ba142c9 100644 --- a/clients/python/wcferry/wcf_pb2.py +++ b/clients/python/wcferry/wcf_pb2.py @@ -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) diff --git a/clients/python/wcferry/wxmsg.py b/clients/python/wcferry/wxmsg.py index f71ed3f..37d521d 100644 --- a/clients/python/wcferry/wxmsg.py +++ b/clients/python/wcferry/wxmsg.py @@ -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 "" diff --git a/docs/source/_static/openapi-wcfhttp.json b/docs/source/_static/openapi-wcfhttp.json index 861e097..c1ff4d1 100644 --- a/docs/source/_static/openapi-wcfhttp.json +++ b/docs/source/_static/openapi-wcfhttp.json @@ -1 +1 @@ -{"openapi":"3.0.2","info":{"title":"WeChatFerry HTTP 客户端","description":"Github: WeChatFerry
\n\n\n\n\n\n\n\n\n\n\n\n\n
\"碲矿\"\"赞赏\"
后台回复 WeChatFerry 加群交流如果你觉得有用
","version":"0.1.0"},"paths":{"/msg_cb":{"post":{"tags":["示例"],"summary":"接收消息回调样例","description":"示例回调方法,简单打印消息","operationId":"msg_cb_msg_cb_post","requestBody":{"content":{"application/json":{"schema":{"title":"Msg","allOf":[{"$ref":"#/components/schemas/Msg"}],"description":"微信消息"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/login":{"get":{"summary":"获取登录状态","description":"获取登录状态","operationId":"is_login_login_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Is Login Login Get","type":"object"}}}}}}},"/wxid":{"get":{"summary":"获取登录账号 wxid","description":"获取登录账号 wxid","operationId":"get_self_wxid_wxid_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Self Wxid Wxid Get","type":"object"}}}}}}},"/user-info":{"get":{"summary":"获取登录账号个人信息","description":"获取登录账号个人信息","operationId":"get_user_info_user_info_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get User Info User Info Get","type":"object"}}}}}}},"/msg-types":{"get":{"summary":"获取消息类型","description":"获取消息类型","operationId":"get_msg_types_msg_types_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Msg Types Msg Types Get","type":"object"}}}}}}},"/contacts":{"get":{"summary":"获取完整通讯录","description":"获取完整通讯录","operationId":"get_contacts_contacts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Contacts Contacts Get","type":"object"}}}}}}},"/friends":{"get":{"summary":"获取好友列表","description":"获取好友列表","operationId":"get_friends_friends_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Friends Friends Get","type":"object"}}}}}}},"/dbs":{"get":{"summary":"获取所有数据库","description":"获取所有数据库","operationId":"get_dbs_dbs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Dbs Dbs Get","type":"object"}}}}}}},"/{db}/tables":{"get":{"summary":"获取 db 中所有表","description":"获取 db 中所有表\n\nArgs:\n db (str): 数据库名(可通过 `get_dbs` 查询)\n\nReturns:\n List[dict]: `db` 下的所有表名及对应建表语句","operationId":"get_tables__db__tables_get","parameters":[{"required":true,"schema":{"title":"Db","type":"string"},"name":"db","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Tables Db Tables Get","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/text":{"post":{"summary":"发送文本消息","description":"发送文本消息,可参考:https://github.com/lich0821/WeChatRobot/blob/master/robot.py 里 sendTextMsg\n\nArgs:\n msg (str): 要发送的消息,换行使用 `\\\\n`;如果 @ 人的话,需要带上跟 `aters` 里数量相同的 @\n receiver (str): 消息接收人,wxid 或者 roomid\n aters (str): 要 @ 的 wxid,多个用逗号分隔;`@所有人` 只需要 `notify@all`\n\nReturns:\n int: 0 为成功,其他失败","operationId":"send_text_text_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_send_text_text_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Send Text Text Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/image":{"post":{"summary":"发送图片消息","description":"发送图片,非线程安全\n\nArgs:\n path (str): 图片路径,如:`C:/Projs/WeChatRobot/TEQuant.jpeg` 或 `https://raw.githubusercontent.com/lich0821/WeChatRobot/master/TEQuant.jpeg`\n receiver (str): 消息接收人,wxid 或者 roomid\n\nReturns:\n int: 0 为成功,其他失败","operationId":"send_image_image_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_send_image_image_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Send Image Image Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/file":{"post":{"summary":"发送文件消息","description":"发送文件\n\nArgs:\n path (str): 本地文件路径,如:`C:/Projs/WeChatRobot/README.MD`\n receiver (str): 消息接收人,wxid 或者 roomid\n\nReturns:\n int: 0 为成功,其他失败","operationId":"send_file_file_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_send_file_file_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Send File File Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sql":{"post":{"summary":"执行 SQL,如果数据量大注意分页,以免 OOM","description":"执行 SQL,如果数据量大注意分页,以免 OOM\n\nArgs:\n db (str): 要查询的数据库\n sql (str): 要执行的 SQL\n\nReturns:\n List[dict]: 查询结果","operationId":"query_sql_sql_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_query_sql_sql_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Query Sql Sql Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/new-friend":{"post":{"summary":"通过好友申请","description":"通过好友申请\n\nArgs:\n v3 (str): 加密用户名 (好友申请消息里 v3 开头的字符串)\n v4 (str): Ticket (好友申请消息里 v4 开头的字符串)\n scene: 申请方式 (好友申请消息里的 scene)\n\nReturns:\n int: 1 为成功,其他失败","operationId":"accept_new_friend_new_friend_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_accept_new_friend_new_friend_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Accept New Friend New Friend Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/chatroom-member":{"post":{"summary":"添加群成员","description":"添加群成员\n\nArgs:\n roomid (str): 待加群的 id\n wxids (str): 要加到群里的 wxid,多个用逗号分隔\n\nReturns:\n int: 1 为成功,其他失败","operationId":"add_chatroom_members_chatroom_member_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_add_chatroom_members_chatroom_member_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Add Chatroom Members Chatroom Member Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"删除群成员","description":"删除群成员\n\nArgs:\n roomid (str): 群的 id\n wxids (str): 要删除的 wxid,多个用逗号分隔\n\nReturns:\n int: 1 为成功,其他失败","operationId":"del_chatroom_members_chatroom_member_delete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_del_chatroom_members_chatroom_member_delete"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Del Chatroom Members Chatroom Member Delete","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/transfer":{"post":{"summary":"接收转账","description":"接收转账\n\nArgs:\n wxid (str): 转账消息里的发送人 wxid\n transferid (str): 转账消息里的 transferid\n transactionid (str): 转账消息里的 transactionid\n\nReturns:\n int: 1 为成功,其他失败","operationId":"receive_transfer_transfer_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_receive_transfer_transfer_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Receive Transfer Transfer Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dec-image":{"post":{"summary":"解密图片","description":"解密图片:\n\nArgs:\n src (str): 加密的图片路径\n dst (str): 解密的图片路径\n\nReturns:\n bool: 是否成功","operationId":"decrypt_image_dec_image_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_decrypt_image_dec_image_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Decrypt Image Dec Image Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Body_accept_new_friend_new_friend_post":{"title":"Body_accept_new_friend_new_friend_post","type":"object","properties":{"v3":{"title":"V3","type":"string","description":"加密用户名 (好友申请消息里 v3 开头的字符串)","default":"v3"},"v4":{"title":"V4","type":"string","description":"Ticket (好友申请消息里 v4 开头的字符串)","default":"v4"},"scene":{"title":"Scene","type":"integer","description":"申请方式 (好友申请消息里的 scene)","default":30}}},"Body_add_chatroom_members_chatroom_member_post":{"title":"Body_add_chatroom_members_chatroom_member_post","type":"object","properties":{"roomid":{"title":"Roomid","type":"string","description":"待加群的 id","default":"xxxxxxxx@chatroom"},"wxids":{"title":"Wxids","type":"string","description":"要加到群里的 wxid,多个用逗号分隔","default":"wxid_xxxxxxxxxxxxx"}}},"Body_decrypt_image_dec_image_post":{"title":"Body_decrypt_image_dec_image_post","type":"object","properties":{"src":{"title":"Src","type":"string","description":"加密的图片路径,从图片消息中获取","default":"C:\\..."},"dst":{"title":"Dst","type":"string","description":"解密的图片路径","default":"C:\\..."}}},"Body_del_chatroom_members_chatroom_member_delete":{"title":"Body_del_chatroom_members_chatroom_member_delete","type":"object","properties":{"roomid":{"title":"Roomid","type":"string","description":"群的 id","default":"xxxxxxxx@chatroom"},"wxids":{"title":"Wxids","type":"string","description":"要删除的 wxid,多个用逗号分隔","default":"wxid_xxxxxxxxxxxxx"}}},"Body_query_sql_sql_post":{"title":"Body_query_sql_sql_post","type":"object","properties":{"db":{"title":"Db","type":"string","description":"数据库","default":"MicroMsg.db"},"sql":{"title":"Sql","type":"string","description":"SQL 语句","default":"SELECT * FROM Contact LIMIT 1;"}}},"Body_receive_transfer_transfer_post":{"title":"Body_receive_transfer_transfer_post","type":"object","properties":{"wxid":{"title":"Wxid","type":"string","description":"转账消息里的发送人 wxid","default":"wxid_xxxxxxxxxxxxx"},"transferid":{"title":"Transferid","type":"string","description":"转账消息里的 transferid","default":"transferid"},"transactionid":{"title":"Transactionid","type":"string","description":"转账消息里的 transactionid","default":"transactionid"}}},"Body_send_file_file_post":{"title":"Body_send_file_file_post","type":"object","properties":{"path":{"title":"Path","type":"string","description":"本地文件路径,不支持网络路径","default":"C:\\Projs\\WeChatRobot\\TEQuant.jpeg"},"receiver":{"title":"Receiver","type":"string","description":"roomid 或者 wxid","default":"filehelper"}}},"Body_send_image_image_post":{"title":"Body_send_image_image_post","type":"object","properties":{"path":{"title":"Path","type":"string","description":"图片路径","default":"C:\\Projs\\WeChatRobot\\TEQuant.jpeg"},"receiver":{"title":"Receiver","type":"string","description":"消息接收者,roomid 或者 wxid","default":"filehelper"}}},"Body_send_text_text_post":{"title":"Body_send_text_text_post","required":["msg"],"type":"object","properties":{"msg":{"title":"Msg","type":"string","description":"要发送的消息,换行用\\n表示"},"receiver":{"title":"Receiver","type":"string","description":"消息接收者,roomid 或者 wxid","default":"filehelper"},"aters":{"title":"Aters","type":"string","description":"要 @ 的 wxid,多个用逗号分隔;@所有人 用 notify@all","default":""}}},"HTTPValidationError":{"title":"HTTPValidationError","type":"object","properties":{"detail":{"title":"Detail","type":"array","items":{"$ref":"#/components/schemas/ValidationError"}}}},"Msg":{"title":"Msg","required":["id","type","xml","sender","roomid","content","thumb","extra","is_at","is_self","is_group"],"type":"object","properties":{"id":{"title":"Id","type":"string"},"type":{"title":"Type","type":"integer"},"xml":{"title":"Xml","type":"string"},"sender":{"title":"Sender","type":"string"},"roomid":{"title":"Roomid","type":"string"},"content":{"title":"Content","type":"string"},"thumb":{"title":"Thumb","type":"string"},"extra":{"title":"Extra","type":"string"},"is_at":{"title":"Is At","type":"boolean"},"is_self":{"title":"Is Self","type":"boolean"},"is_group":{"title":"Is Group","type":"boolean"}}},"ValidationError":{"title":"ValidationError","required":["loc","msg","type"],"type":"object","properties":{"loc":{"title":"Location","type":"array","items":{"anyOf":[{"type":"string"},{"type":"integer"}]}},"msg":{"title":"Message","type":"string"},"type":{"title":"Error Type","type":"string"}}}}}} \ No newline at end of file +{"openapi":"3.0.2","info":{"title":"WeChatFerry HTTP 客户端","description":"Github: WeChatFerry
\n\n\n\n\n\n\n\n\n\n\n\n\n
\"碲矿\"\"赞赏\"
后台回复 WeChatFerry 加群交流如果你觉得有用
","version":"0.1.0"},"paths":{"/msg_cb":{"post":{"tags":["示例"],"summary":"接收消息回调样例","description":"示例回调方法,简单打印消息","operationId":"msg_cb_msg_cb_post","requestBody":{"content":{"application/json":{"schema":{"title":"Msg","allOf":[{"$ref":"#/components/schemas/Msg"}],"description":"微信消息"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/login":{"get":{"summary":"获取登录状态","description":"获取登录状态","operationId":"is_login_login_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Is Login Login Get","type":"object"}}}}}}},"/wxid":{"get":{"summary":"获取登录账号 wxid","description":"获取登录账号 wxid","operationId":"get_self_wxid_wxid_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Self Wxid Wxid Get","type":"object"}}}}}}},"/user-info":{"get":{"summary":"获取登录账号个人信息","description":"获取登录账号个人信息","operationId":"get_user_info_user_info_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get User Info User Info Get","type":"object"}}}}}}},"/msg-types":{"get":{"summary":"获取消息类型","description":"获取消息类型","operationId":"get_msg_types_msg_types_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Msg Types Msg Types Get","type":"object"}}}}}}},"/contacts":{"get":{"summary":"获取完整通讯录","description":"获取完整通讯录","operationId":"get_contacts_contacts_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Contacts Contacts Get","type":"object"}}}}}}},"/friends":{"get":{"summary":"获取好友列表","description":"获取好友列表","operationId":"get_friends_friends_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Friends Friends Get","type":"object"}}}}}}},"/dbs":{"get":{"summary":"获取所有数据库","description":"获取所有数据库","operationId":"get_dbs_dbs_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Dbs Dbs Get","type":"object"}}}}}}},"/{db}/tables":{"get":{"summary":"获取 db 中所有表","description":"获取 db 中所有表\n\nArgs:\n db (str): 数据库名(可通过 `get_dbs` 查询)\n\nReturns:\n List[dict]: `db` 下的所有表名及对应建表语句","operationId":"get_tables__db__tables_get","parameters":[{"required":true,"schema":{"title":"Db","type":"string"},"name":"db","in":"path"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Get Tables Db Tables Get","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/pyq/":{"get":{"summary":"刷新朋友圈(数据从消息回调中查看)","description":"刷新朋友圈\n\nArgs:\n id (int): 开始 id,0 为最新页\n\nReturns:\n int: 1 为成功,其他失败","operationId":"refresh_pyq_pyq__get","parameters":[{"description":"开始 id,0 为最新页","required":false,"schema":{"title":"Id","type":"integer","description":"开始 id,0 为最新页","default":0},"name":"id","in":"query"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Refresh Pyq Pyq Get","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/text":{"post":{"summary":"发送文本消息","description":"发送文本消息,可参考:https://github.com/lich0821/WeChatRobot/blob/master/robot.py 里 sendTextMsg\n\nArgs:\n msg (str): 要发送的消息,换行使用 `\\\\n`;如果 @ 人的话,需要带上跟 `aters` 里数量相同的 @\n receiver (str): 消息接收人,wxid 或者 roomid\n aters (str): 要 @ 的 wxid,多个用逗号分隔;`@所有人` 只需要 `notify@all`\n\nReturns:\n int: 0 为成功,其他失败","operationId":"send_text_text_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_send_text_text_post"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Send Text Text Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/image":{"post":{"summary":"发送图片消息","description":"发送图片,非线程安全\n\nArgs:\n path (str): 图片路径,如:`C:/Projs/WeChatRobot/TEQuant.jpeg` 或 `https://raw.githubusercontent.com/lich0821/WeChatRobot/master/TEQuant.jpeg`\n receiver (str): 消息接收人,wxid 或者 roomid\n\nReturns:\n int: 0 为成功,其他失败","operationId":"send_image_image_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_send_image_image_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Send Image Image Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/file":{"post":{"summary":"发送文件消息","description":"发送文件\n\nArgs:\n path (str): 本地文件路径,如:`C:/Projs/WeChatRobot/README.MD`\n receiver (str): 消息接收人,wxid 或者 roomid\n\nReturns:\n int: 0 为成功,其他失败","operationId":"send_file_file_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_send_file_file_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Send File File Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/sql":{"post":{"summary":"执行 SQL,如果数据量大注意分页,以免 OOM","description":"执行 SQL,如果数据量大注意分页,以免 OOM\n\nArgs:\n db (str): 要查询的数据库\n sql (str): 要执行的 SQL\n\nReturns:\n List[dict]: 查询结果","operationId":"query_sql_sql_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_query_sql_sql_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Query Sql Sql Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/new-friend":{"post":{"summary":"通过好友申请","description":"通过好友申请\n\nArgs:\n v3 (str): 加密用户名 (好友申请消息里 v3 开头的字符串)\n v4 (str): Ticket (好友申请消息里 v4 开头的字符串)\n scene: 申请方式 (好友申请消息里的 scene)\n\nReturns:\n int: 1 为成功,其他失败","operationId":"accept_new_friend_new_friend_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_accept_new_friend_new_friend_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Accept New Friend New Friend Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/chatroom-member":{"post":{"summary":"添加群成员","description":"添加群成员\n\nArgs:\n roomid (str): 待加群的 id\n wxids (str): 要加到群里的 wxid,多个用逗号分隔\n\nReturns:\n int: 1 为成功,其他失败","operationId":"add_chatroom_members_chatroom_member_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_add_chatroom_members_chatroom_member_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Add Chatroom Members Chatroom Member Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}},"delete":{"summary":"删除群成员","description":"删除群成员\n\nArgs:\n roomid (str): 群的 id\n wxids (str): 要删除的 wxid,多个用逗号分隔\n\nReturns:\n int: 1 为成功,其他失败","operationId":"del_chatroom_members_chatroom_member_delete","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_del_chatroom_members_chatroom_member_delete"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Del Chatroom Members Chatroom Member Delete","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/transfer":{"post":{"summary":"接收转账","description":"接收转账\n\nArgs:\n wxid (str): 转账消息里的发送人 wxid\n transferid (str): 转账消息里的 transferid\n transactionid (str): 转账消息里的 transactionid\n\nReturns:\n int: 1 为成功,其他失败","operationId":"receive_transfer_transfer_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_receive_transfer_transfer_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Receive Transfer Transfer Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/dec-image":{"post":{"summary":"解密图片","description":"解密图片:\n\nArgs:\n src (str): 加密的图片路径\n dst (str): 解密的图片路径\n\nReturns:\n bool: 是否成功","operationId":"decrypt_image_dec_image_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Body_decrypt_image_dec_image_post"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"title":"Response Decrypt Image Dec Image Post","type":"object"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}}},"components":{"schemas":{"Body_accept_new_friend_new_friend_post":{"title":"Body_accept_new_friend_new_friend_post","type":"object","properties":{"v3":{"title":"V3","type":"string","description":"加密用户名 (好友申请消息里 v3 开头的字符串)","default":"v3"},"v4":{"title":"V4","type":"string","description":"Ticket (好友申请消息里 v4 开头的字符串)","default":"v4"},"scene":{"title":"Scene","type":"integer","description":"申请方式 (好友申请消息里的 scene)","default":30}}},"Body_add_chatroom_members_chatroom_member_post":{"title":"Body_add_chatroom_members_chatroom_member_post","type":"object","properties":{"roomid":{"title":"Roomid","type":"string","description":"待加群的 id","default":"xxxxxxxx@chatroom"},"wxids":{"title":"Wxids","type":"string","description":"要加到群里的 wxid,多个用逗号分隔","default":"wxid_xxxxxxxxxxxxx"}}},"Body_decrypt_image_dec_image_post":{"title":"Body_decrypt_image_dec_image_post","type":"object","properties":{"src":{"title":"Src","type":"string","description":"加密的图片路径,从图片消息中获取","default":"C:\\..."},"dst":{"title":"Dst","type":"string","description":"解密的图片路径","default":"C:\\..."}}},"Body_del_chatroom_members_chatroom_member_delete":{"title":"Body_del_chatroom_members_chatroom_member_delete","type":"object","properties":{"roomid":{"title":"Roomid","type":"string","description":"群的 id","default":"xxxxxxxx@chatroom"},"wxids":{"title":"Wxids","type":"string","description":"要删除的 wxid,多个用逗号分隔","default":"wxid_xxxxxxxxxxxxx"}}},"Body_query_sql_sql_post":{"title":"Body_query_sql_sql_post","type":"object","properties":{"db":{"title":"Db","type":"string","description":"数据库","default":"MicroMsg.db"},"sql":{"title":"Sql","type":"string","description":"SQL 语句","default":"SELECT * FROM Contact LIMIT 1;"}}},"Body_receive_transfer_transfer_post":{"title":"Body_receive_transfer_transfer_post","type":"object","properties":{"wxid":{"title":"Wxid","type":"string","description":"转账消息里的发送人 wxid","default":"wxid_xxxxxxxxxxxxx"},"transferid":{"title":"Transferid","type":"string","description":"转账消息里的 transferid","default":"transferid"},"transactionid":{"title":"Transactionid","type":"string","description":"转账消息里的 transactionid","default":"transactionid"}}},"Body_send_file_file_post":{"title":"Body_send_file_file_post","type":"object","properties":{"path":{"title":"Path","type":"string","description":"本地文件路径,不支持网络路径","default":"C:\\Projs\\WeChatRobot\\TEQuant.jpeg"},"receiver":{"title":"Receiver","type":"string","description":"roomid 或者 wxid","default":"filehelper"}}},"Body_send_image_image_post":{"title":"Body_send_image_image_post","type":"object","properties":{"path":{"title":"Path","type":"string","description":"图片路径","default":"C:\\Projs\\WeChatRobot\\TEQuant.jpeg"},"receiver":{"title":"Receiver","type":"string","description":"消息接收者,roomid 或者 wxid","default":"filehelper"}}},"Body_send_text_text_post":{"title":"Body_send_text_text_post","required":["msg"],"type":"object","properties":{"msg":{"title":"Msg","type":"string","description":"要发送的消息,换行用\\n表示"},"receiver":{"title":"Receiver","type":"string","description":"消息接收者,roomid 或者 wxid","default":"filehelper"},"aters":{"title":"Aters","type":"string","description":"要 @ 的 wxid,多个用逗号分隔;@所有人 用 notify@all","default":""}}},"HTTPValidationError":{"title":"HTTPValidationError","type":"object","properties":{"detail":{"title":"Detail","type":"array","items":{"$ref":"#/components/schemas/ValidationError"}}}},"Msg":{"title":"Msg","required":["id","ts","sign","type","xml","sender","roomid","content","thumb","extra","is_at","is_self","is_group"],"type":"object","properties":{"id":{"title":"Id","type":"integer"},"ts":{"title":"Ts","type":"integer"},"sign":{"title":"Sign","type":"string"},"type":{"title":"Type","type":"integer"},"xml":{"title":"Xml","type":"string"},"sender":{"title":"Sender","type":"string"},"roomid":{"title":"Roomid","type":"string"},"content":{"title":"Content","type":"string"},"thumb":{"title":"Thumb","type":"string"},"extra":{"title":"Extra","type":"string"},"is_at":{"title":"Is At","type":"boolean"},"is_self":{"title":"Is Self","type":"boolean"},"is_group":{"title":"Is Group","type":"boolean"}}},"ValidationError":{"title":"ValidationError","required":["loc","msg","type"],"type":"object","properties":{"loc":{"title":"Location","type":"array","items":{"anyOf":[{"type":"string"},{"type":"integer"}]}},"msg":{"title":"Message","type":"string"},"type":{"title":"Error Type","type":"string"}}}}}} \ No newline at end of file