Merge pull request #74 from lich0821/pyauto

pyauto
This commit is contained in:
Changhua 2023-10-07 17:12:58 +08:00 committed by GitHub
commit e759e5fa9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 103 additions and 971 deletions

View File

@ -1,4 +1,4 @@
# WeChatFerry wcfautopy 客户端(基于python客户端进行修改)
# WcfAuto 客户端(基于 python 客户端)
[![PyPi](https://img.shields.io/pypi/v/wcferry.svg)](https://pypi.python.org/pypi/wcferry) [![Downloads](https://static.pepy.tech/badge/wcferry)](https://pypi.python.org/pypi/wcferry) [![Documentation Status](https://readthedocs.org/projects/wechatferry/badge/?version=latest)](https://wechatferry.readthedocs.io/zh/latest/?badge=latest)
|[📖 文档](https://wechatferry.readthedocs.io/)|[📺 视频教程](https://mp.weixin.qq.com/s/APdjGyZ2hllXxyG_sNCfXQ)|[🙋 FAQ](https://mp.weixin.qq.com/s/vAGpn1C9stI8Xzt1hUJhLA)|
@ -8,7 +8,7 @@
## 快速开始
```sh
pip install --upgrade wcferry
pip install --upgrade wcfauto
```
### Demo
@ -18,62 +18,63 @@ pip install --upgrade wcferry
import logging
from time import sleep
from wcferry import Wcf, WxMsg, Register
from wcfauto import Register, Wcf, WxMsg
logging.basicConfig(level='DEBUG', format="%(asctime)s %(message)s")
LOG = logging.getLogger("Demo")
def main():
receiver = Register()
@receiver.message_register(isDivision=True, isGroup=True, isPyq=False)
def process_msg(bot: Wcf, msg: WxMsg):
"""
同步消息函数装饰器
"""
LOG.info(f"收到消息: {msg}")
sleep(5) # 等微信加载好,以免信息显示异常
LOG.info(f"已经登录: {True if bot.is_login() else False}")
LOG.info(f"wxid: {bot.get_self_wxid()}")
# bot.disable_recv_msg() # 当需要停止接收消息时调用
sleep(5)
ret = bot.send_text("Hello world.", "filehelper")
LOG.info(f"send_text: {ret}")
sleep(5)
# 需要确保图片路径正确,建议使用绝对路径(使用双斜杠\\
ret = bot.send_image("https://raw.githubusercontent.com/lich0821/WeChatFerry/master/assets/QR.jpeg", "filehelper")
ret = bot.send_image(
"https://raw.githubusercontent.com/lich0821/WeChatFerry/master/assets/QR.jpeg", "filehelper")
LOG.info(f"send_image: {ret}")
sleep(5)
# 需要确保文件路径正确,建议使用绝对路径(使用双斜杠\\
ret = bot.send_file("https://raw.githubusercontent.com/lich0821/WeChatFerry/master/README.MD", "filehelper")
LOG.info(f"send_file: {ret}")
sleep(5)
LOG.info(f"Message types:\n{bot.get_msg_types()}")
LOG.info(f"Contacts:\n{bot.get_contacts()}")
sleep(5)
LOG.info(f"DBs:\n{bot.get_dbs()}")
LOG.info(f"Tables:\n{bot.get_tables('db')}")
LOG.info(f"Results:\n{bot.query_sql('MicroMsg.db', 'SELECT * FROM Contact LIMIT 1;')}")
# 需要真正的 V3、V4 信息
# bot.accept_new_friend("v3", "v4")
# 添加群成员,填写正确的群 ID 和成员 wxid
# ret = bot.add_chatroom_members("chatroom id", "wxid1,wxid2,wxid3,...")
# LOG.info(f"add_chatroom_members: {ret}")
# 删除群成员,填写正确的群 ID 和成员 wxid
# ret = bot.del_chatroom_members("chatroom id", "wxid1,wxid2,wxid3,...")
# LOG.info(f"add_chatroom_members: {ret}")
sleep(5)
bot.refresh_pyq(0) # 刷新朋友圈第一页
# bot.refresh_pyq(id) # 从 id 开始刷新朋友圈
@ -92,72 +93,8 @@ def main():
if __name__ == "__main__":
main()
```
|![碲矿](https://raw.githubusercontent.com/lich0821/WeChatFerry/master/assets/TEQuant.jpg)|![赞赏](https://raw.githubusercontent.com/lich0821/WeChatFerry/master/assets/QR.jpeg)|
|:-:|:-:|
|后台回复 `WeChatFerry` 加群交流|如果你觉得有用|
## 一起开发
### 配置环境
```sh
# 创建虚拟环境
python -m venv .env
# 激活虚拟环境
source .env/Scripts/activate
# 升级 pip
pip install --upgrade pip
# 安装依赖包
pip install grpcio-tools pynng
```
### 重新生成 PB 文件
```sh
# CMD
cd clients\wcfautopy\wcferry
python -m grpc_tools.protoc --python_out=. --proto_path=..\..\..\WeChatFerry\rpc\proto\ wcf.proto
# GitBash
cd clients/wcfautopy/wcferry
python -m grpc_tools.protoc --python_out=. --proto_path=../../../WeChatFerry/rpc/proto/ wcf.proto
```
## 版本更新
### 39.0.3.0 (2023.09.28)
* 修复登录账号昵称超长报错问题
<details><summary>点击查看更多</summary>
版本号:`w.x.y.z`。
其中:
* `w` 是微信的大版本号,如 `37` (3.7.a.a), `38` (3.8.a.a), `39` (3.9.a.a)
* `x` 是适配的微信的小版本号,从 0 开始
* `y` 是 `WeChatFerry` 的版本,从 0 开始
* `z` 是各客户端的版本,从 0 开始
功能:
* 检查登录状态
* 获取登录账号的 wxid
* 获取消息类型
* 获取所有联系人
* 获取所有好友
* 获取数据库
* 获取某数据库下的表
* 获取用户信息
* 发送文本消息(可 @
* 发送图片wcfautopy 客户端支持网络路径)
* 发送文件wcfautopy 客户端支持网络路径)
* 允许接收消息
* 停止接收消息
* 执行 SQL 查询
* 接受好友申请
* 添加群成员
* 删除群成员
* 解密图片
* 获取朋友圈消息
* 某功能Breaking Change
</details>

View File

@ -3,62 +3,63 @@
import logging
from time import sleep
from wcferry import Wcf, WxMsg, Register
from wcfauto import Register, Wcf, WxMsg
logging.basicConfig(level='DEBUG', format="%(asctime)s %(message)s")
LOG = logging.getLogger("Demo")
def main():
receiver = Register()
@receiver.message_register(isDivision=True, isGroup=True, isPyq=False)
def process_msg(bot: Wcf, msg: WxMsg):
"""
同步消息函数装饰器
"""
LOG.info(f"收到消息: {msg}")
sleep(5) # 等微信加载好,以免信息显示异常
LOG.info(f"已经登录: {True if bot.is_login() else False}")
LOG.info(f"wxid: {bot.get_self_wxid()}")
# bot.disable_recv_msg() # 当需要停止接收消息时调用
sleep(5)
ret = bot.send_text("Hello world.", "filehelper")
LOG.info(f"send_text: {ret}")
sleep(5)
# 需要确保图片路径正确,建议使用绝对路径(使用双斜杠\\
ret = bot.send_image("https://raw.githubusercontent.com/lich0821/WeChatFerry/master/assets/QR.jpeg", "filehelper")
ret = bot.send_image(
"https://raw.githubusercontent.com/lich0821/WeChatFerry/master/assets/QR.jpeg", "filehelper")
LOG.info(f"send_image: {ret}")
sleep(5)
# 需要确保文件路径正确,建议使用绝对路径(使用双斜杠\\
ret = bot.send_file("https://raw.githubusercontent.com/lich0821/WeChatFerry/master/README.MD", "filehelper")
LOG.info(f"send_file: {ret}")
sleep(5)
LOG.info(f"Message types:\n{bot.get_msg_types()}")
LOG.info(f"Contacts:\n{bot.get_contacts()}")
sleep(5)
LOG.info(f"DBs:\n{bot.get_dbs()}")
LOG.info(f"Tables:\n{bot.get_tables('db')}")
LOG.info(f"Results:\n{bot.query_sql('MicroMsg.db', 'SELECT * FROM Contact LIMIT 1;')}")
# 需要真正的 V3、V4 信息
# bot.accept_new_friend("v3", "v4")
# 添加群成员,填写正确的群 ID 和成员 wxid
# ret = bot.add_chatroom_members("chatroom id", "wxid1,wxid2,wxid3,...")
# LOG.info(f"add_chatroom_members: {ret}")
# 删除群成员,填写正确的群 ID 和成员 wxid
# ret = bot.del_chatroom_members("chatroom id", "wxid1,wxid2,wxid3,...")
# LOG.info(f"add_chatroom_members: {ret}")
sleep(5)
bot.refresh_pyq(0) # 刷新朋友圈第一页
# bot.refresh_pyq(id) # 从 id 开始刷新朋友圈

View File

@ -1,21 +1,18 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import print_function
from setuptools import setup, find_packages
import wcferry
import wcfauto
from setuptools import find_packages, setup
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setup(
name="wcferry",
version=wcferry.__version__,
author="Changhua",
author_email="lichanghua0821@gmail.com",
name="wcfauto",
version=wcfauto.__version__,
author="bujinzhang",
author_email="",
description="一个玩微信的工具",
long_description=long_description,
long_description_content_type="text/markdown",
@ -26,9 +23,7 @@ setup(
include_package_data=True,
install_requires=[
"setuptools",
"grpcio-tools",
"pynng",
"requests",
"wcferry",
],
classifiers=[
"Environment :: Win32 (MS Windows)",

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from wcfauto.auto_res import Register
from wcfauto.wcf import WcfV2 as Wcf
from wcfauto.wcf import WxMsgV2 as WxMsg
__version__ = "39.0.3.0"

View File

@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from wcfauto.auto_res.bot import Register
from wcfauto.auto_res.core import load_function
Register = load_function(Register)

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
from typing import Callable, Any
from wcferry.event import Event
from abc import abstractmethod
from wcferry.client import Wcf
import logging
from abc import abstractmethod
from typing import Any, Callable
from wcfauto.event import Event
from wcfauto.wcf import WcfV2 as Wcf
class Register(Event):

View File

@ -5,9 +5,10 @@ import functools
import queue
import traceback
from threading import Thread
from typing import Callable, Any
from wcferry.client import Wcf
from wcferry.wxmsg import WxMsg
from typing import Any, Callable
from wcfauto.wcf import WcfV2 as Wcf
from wcfauto.wcf import WxMsgV2 as WxMsg
def load_function(cls):
@ -51,8 +52,8 @@ def _processing_async_func(self,
async def __async_func(bot: Wcf, message: WxMsg):
try:
# 判断被装饰函数是否为协程函数, 本函数要求是协程函数
if not asyncio.iscoroutinefunction(func): raise ValueError(
f'这里应使用协程函数, 而被装饰函数-> ({func.__name__}) <-是非协程函数')
if not asyncio.iscoroutinefunction(func):
raise ValueError(f'这里应使用协程函数, 而被装饰函数-> ({func.__name__}) <-是非协程函数')
if message.is_pyq() and isPyq:
return await func(bot, message)
if not isDivision:
@ -78,8 +79,9 @@ def _processing_universal_func(self,
def universal_func(bot: Wcf, message: WxMsg):
try:
# 判断被装饰函数是否为协程函数, 本函数要求是协程函数
if asyncio.iscoroutinefunction(func): raise ValueError(
f'这里应使用非协程函数, 而被装饰函数-> ({func.__name__}) <-协程函数')
if asyncio.iscoroutinefunction(func):
raise ValueError(
f'这里应使用非协程函数, 而被装饰函数-> ({func.__name__}) <-协程函数')
if message.is_pyq() and isPyq:
return func(bot, message)
if not isDivision:
@ -115,5 +117,6 @@ def run(self, *args, **kwargs):
self._LOG.debug("开始接受消息")
self._wcf.keep_running()
def stop_receiving(self):
return self._wcf.disable_recv_msg()

View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 -*-
from wcfauto.event.event import Event
from wcfauto.event.core import load_function
Event = load_function(Event)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import traceback
import asyncio
import traceback
from threading import Thread

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
from abc import abstractmethod
import asyncio
import logging
from abc import abstractmethod
class Event(object):

View File

@ -1,14 +1,32 @@
# -*- coding: utf-8 -*-
import re
from datetime import datetime
import time
from wcferry import wcf_pb2
from wcferry import Wcf, WxMsg
class WxMsg(dict):
class WcfV2(Wcf):
def __init__(self, host: str, port: int = 10086, debug: bool = True) -> None:
super().__init__(host, port, debug)
def get_msg(self, block=True) -> WxMsg:
"""从消息队列中获取消息
Args:
block (bool): 是否阻塞默认阻塞
Returns:
WxMsg: 微信消息
Raises:
Empty: 如果阻塞并且超时抛出空异常需要用户自行捕获
"""
return WxMsgV2(self.msgQ.get(block, timeout=1))
class WxMsgV2(WxMsg):
"""微信消息
Attributes:
type (int): 消息类型可通过 `get_msg_types` 获取
id (str): 消息 id
@ -20,10 +38,9 @@ class WxMsg(dict):
extra (str): 视频或图片消息的路径
"""
def __init__(self, msg: wcf_pb2.WxMsg) -> None:
super(WxMsg, self).__init__()
self._is_self = msg.is_self
self._is_group = msg.is_group
def __init__(self, msg: WxMsg) -> None:
# self._is_self = msg._is_self
# self._is_group = msg._is_group
self._type = msg.type
self._id = msg.id
self._ts = msg.ts
@ -54,7 +71,8 @@ class WxMsg(dict):
rmsg = self.__data['data']['content']
rev_type = re.findall('<sysmsg type="(.*?)"\s?', rmsg)
rev_w = re.findall("<replacemsg><!\[CDATA\[(.*?)]]></replacemsg>", rmsg)
if len(rev_type) == 0 or len(rev_w) == 0: return
if len(rev_type) == 0 or len(rev_w) == 0:
return
if rev_type[0] == 'revokemsg' and rev_w[0] == '你撤回了一条消息':
self.__data['data']['content'] = rev_w[0]
self.__data['isRevokeMsg'] = True

View File

@ -1,2 +0,0 @@
include wcferry/*.dll
include wcferry/*.exe

View File

@ -1,21 +0,0 @@
syntax = "proto3";
package com.iamteer.wcf;
message RoomData {
message RoomMember {
string wxid = 1;
string name = 2;
int32 state = 3;
}
repeated RoomMember members = 1;
int32 field_2 = 2;
int32 field_3 = 3;
int32 field_4 = 4;
int32 room_capacity = 5;
int32 field_6 = 6;
int64 field_7 = 7;
int64 field_8 = 8;
}

View File

@ -1,6 +0,0 @@
# -*- coding: utf-8 -*-
from wcferry.client import Wcf, __version__
from wcferry.wxmsg import WxMsg
from wcferry.auto_res import Register

View File

@ -1,8 +0,0 @@
# -*- coding: utf-8 -*-
from wcferry.auto_res.bot import Register
from wcferry.auto_res.core import load_function
Register = load_function(Register)

View File

@ -1,694 +0,0 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
__version__ = "39.0.3.0"
import atexit
import base64
import logging
import mimetypes
import os
import re
import sys
from queue import Queue
from threading import Thread
from time import sleep
from typing import Callable, Dict, List, Optional
import pynng
import requests
from google.protobuf import json_format
from wcferry import wcf_pb2
from wcferry.roomdata_pb2 import RoomData
from wcferry.wxmsg import WxMsg
def _retry():
def decorator(func):
""" Retry the function """
def wrapper(*args, **kwargs):
def logerror(e):
func_name = re.findall(r"func: (.*?)\n", str(args[1]))[-1]
logging.getLogger("WCF").error(f"Call {func_name} failed: {e}")
try:
ret = func(*args, **kwargs)
except pynng.Timeout as _: # 如果超时,重试
try:
ret = func(*args, **kwargs)
except Exception as e:
logerror(e)
ret = wcf_pb2.Response()
except Exception as e: # 其他异常,退出
logerror(e)
sys.exit(-1)
return ret
return wrapper
return decorator
class Wcf():
"""WeChatFerry, 一个玩微信的工具。
Args:
host (str): `wcferry` RPC 服务器地址默认本地启动也可以指定地址连接远程服务
port (int): `wcferry` RPC 服务器端口默认为 10086接收消息会占用 `port+1` 端口
debug (bool): 是否开启调试模式仅本地启动有效
Attributes:
contacts (list): 联系人缓存调用 `get_contacts` 后更新
self_wxid (str): 登录账号 wxid
"""
def __init__(self, host: str = None, port: int = 10086, debug: bool = True) -> None:
self._local_mode = False
self._is_running = False
self._is_receiving_msg = False
self._wcf_root = os.path.abspath(os.path.dirname(__file__))
self._dl_path = f"{self._wcf_root}/.dl"
os.makedirs(self._dl_path, exist_ok=True)
self.LOG = logging.getLogger("WCF")
self.LOG.info(f"wcferry version: {__version__}")
self.port = port
self.host = host
if host is None:
self._local_mode = True
self.host = "127.0.0.1"
cmd = fr'"{self._wcf_root}\wcf.exe" start {self.port} {"debug" if debug else ""}'
if os.system(cmd) != 0:
self.LOG.error("初始化失败!")
os._exit(-1)
self.cmd_url = f"tcp://{self.host}:{self.port}"
# 连接 RPC
self.cmd_socket = pynng.Pair1() # Client --> Server发送消息
self.cmd_socket.send_timeout = 2000 # 发送 2 秒超时
self.cmd_socket.recv_timeout = 2000 # 接收 2 秒超时
try:
self.cmd_socket.dial(self.cmd_url, block=True)
except Exception as e:
self.LOG.error(f"连接失败: {e}")
os._exit(-2)
self.msg_socket = pynng.Pair1() # Server --> Client接收消息
self.msg_socket.send_timeout = 2000 # 发送 2 秒超时
self.msg_socket.recv_timeout = 2000 # 接收 2 秒超时
self.msg_url = self.cmd_url.replace(str(self.port), str(self.port + 1))
atexit.register(self.cleanup) # 退出的时候停止消息接收,防止资源占用
while not self.is_login(): # 等待微信登录成功
sleep(1)
self._is_running = True
self.contacts = []
self.msgQ = Queue()
self._SQL_TYPES = {1: int, 2: float, 3: lambda x: x.decode("utf-8"), 4: bytes, 5: lambda x: None}
self.self_wxid = self.get_self_wxid()
def __del__(self) -> None:
self.cleanup()
def cleanup(self) -> None:
"""关闭连接,回收资源"""
if not self._is_running:
return
self.disable_recv_msg()
self.cmd_socket.close()
if self._local_mode:
cmd = fr'"{self._wcf_root}\wcf.exe" stop'
if os.system(cmd) != 0:
self.LOG.error("退出失败!")
return
self._is_running = False
def keep_running(self):
"""阻塞进程,让 RPC 一直维持连接"""
try:
while True:
sleep(1)
except Exception as e:
self.cleanup()
@_retry()
def _send_request(self, req: wcf_pb2.Request) -> wcf_pb2.Response:
data = req.SerializeToString()
self.cmd_socket.send(data)
rsp = wcf_pb2.Response()
rsp.ParseFromString(self.cmd_socket.recv_msg().bytes)
return rsp
def is_receiving_msg(self) -> bool:
"""是否已启动接收消息功能"""
return self._is_receiving_msg
def is_login(self) -> bool:
"""是否已经登录"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_IS_LOGIN # FUNC_IS_LOGIN
rsp = self._send_request(req)
return rsp.status == 1
def get_self_wxid(self) -> str:
"""获取登录账户的 wxid"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_GET_SELF_WXID # FUNC_GET_SELF_WXID
rsp = self._send_request(req)
return rsp.str
def get_msg_types(self) -> Dict:
"""获取所有消息类型"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_GET_MSG_TYPES # FUNC_GET_MSG_TYPES
rsp = self._send_request(req)
types = json_format.MessageToDict(rsp.types).get("types", {})
types = {int(k): v for k, v in types.items()}
return dict(sorted(dict(types).items()))
def get_contacts(self) -> List[Dict]:
"""获取完整通讯录"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_GET_CONTACTS # FUNC_GET_CONTACTS
rsp = self._send_request(req)
contacts = json_format.MessageToDict(rsp.contacts).get("contacts", [])
self.contacts.clear()
for cnt in contacts:
gender = cnt.get("gender", "")
if gender == 1:
gender = ""
elif gender == 2:
gender = ""
else:
gender = ""
contact = {
"wxid": cnt.get("wxid", ""),
"code": cnt.get("code", ""),
"remark": cnt.get("remark", ""),
"name": cnt.get("name", ""),
"country": cnt.get("country", ""),
"province": cnt.get("province", ""),
"city": cnt.get("city", ""),
"gender": gender}
self.contacts.append(contact)
return self.contacts
def get_dbs(self) -> List[str]:
"""获取所有数据库"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_GET_DB_NAMES # FUNC_GET_DB_NAMES
rsp = self._send_request(req)
dbs = json_format.MessageToDict(rsp.dbs).get("names", [])
return dbs
def get_tables(self, db: str) -> List[Dict]:
"""获取 db 中所有表
Args:
db (str): 数据库名可通过 `get_dbs` 查询
Returns:
List[Dict]: `db` 下的所有表名及对应建表语句
"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_GET_DB_TABLES # FUNC_GET_DB_TABLES
req.str = db
rsp = self._send_request(req)
tables = json_format.MessageToDict(rsp.tables).get("tables", [])
return tables
def get_user_info(self) -> Dict:
"""获取登录账号个人信息"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_GET_USER_INFO # FUNC_GET_USER_INFO
rsp = self._send_request(req)
ui = json_format.MessageToDict(rsp.ui)
return ui
def send_text(self, msg: str, receiver: str, aters: Optional[str] = "") -> int:
"""发送文本消息
Args:
msg (str): 要发送的消息换行使用 `\\\\n` 单杠如果 @ 人的话需要带上跟 `aters` 里数量相同的 @
receiver (str): 消息接收人wxid 或者 roomid
aters (str): @ wxid多个用逗号分隔`@所有人` 只需要 `notify@all`
Returns:
int: 0 为成功其他失败
"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_SEND_TXT # FUNC_SEND_TXT
req.txt.msg = msg
req.txt.receiver = receiver
if aters:
req.txt.aters = aters
rsp = self._send_request(req)
return rsp.status
def _download_file(self, url: str) -> str:
path = None
if not self._local_mode:
self.LOG.error(f"只有本地模式才支持网络路径!")
return path
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36', }
rsp = requests.get(url, headers=headers, stream=True, timeout=60)
rsp.raw.decode_content = True
# 提取文件名
fname = os.path.basename(url)
ct = rsp.headers["content-type"]
ext = mimetypes.guess_extension(ct)
if ext:
if ext not in fname:
fname = fname + ext
else:
fname = fname.split(ext)[0] + ext
# 保存文件,用完后删除
with open(f"{self._dl_path}/{fname}", "wb") as of:
of.write(rsp.content)
path = os.path.normpath(f"{self._dl_path}/{fname}")
except Exception as e:
self.LOG.error(f"网络资源下载失败: {e}")
return path
def _process_path(self, path) -> str:
"""处理路径,如果是网络路径则下载文件
"""
if path.startswith("http"):
path = self._download_file(path)
if not path:
return -102 # 下载失败
elif not os.path.exists(path):
self.LOG.error(f"图片或者文件不存在,请检查路径: {path}")
return -101 # 文件不存在
return path
def send_image(self, path: str, receiver: str) -> int:
"""发送图片,非线程安全
Args:
path (str): 图片路径`C:/Projs/WeChatRobot/TEQuant.jpeg` `https://raw.githubusercontent.com/lich0821/WeChatFerry/master/assets/TEQuant.jpg`
receiver (str): 消息接收人wxid 或者 roomid
Returns:
int: 0 为成功其他失败
"""
path = self._process_path(path)
if isinstance(path, int):
return path
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_SEND_IMG # FUNC_SEND_IMG
req.file.path = path
req.file.receiver = receiver
rsp = self._send_request(req)
return rsp.status
def send_file(self, path: str, receiver: str) -> int:
"""发送文件,非线程安全
Args:
path (str): 本地文件路径`C:/Projs/WeChatRobot/README.MD` `https://raw.githubusercontent.com/lich0821/WeChatFerry/master/README.MD`
receiver (str): 消息接收人wxid 或者 roomid
Returns:
int: 0 为成功其他失败
"""
path = self._process_path(path)
if isinstance(path, int):
return path
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_SEND_FILE # FUNC_SEND_FILE
req.file.path = path
req.file.receiver = receiver
rsp = self._send_request(req)
return rsp.status
def send_xml(self, receiver: str, xml: str, type: int, path: str = None) -> int:
"""发送 XML
Args:
receiver (str): 消息接收人wxid 或者 roomid
xml (str): xml 内容
type (int): xml 类型0x21 为小程序
path (str): 封面图片路径
Returns:
int: 0 为成功其他失败
"""
raise Exception("Not implemented, yet")
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_SEND_XML # FUNC_SEND_XML
req.xml.receiver = receiver
req.xml.content = xml
req.xml.type = type
if path:
req.xml.path = path
rsp = self._send_request(req)
return rsp.status
def send_emotion(self, path: str, receiver: str) -> int:
"""发送表情
Args:
path (str): 本地表情路径`C:/Projs/WeChatRobot/emo.gif`
receiver (str): 消息接收人wxid 或者 roomid
Returns:
int: 0 为成功其他失败
"""
raise Exception("Not implemented, yet")
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_SEND_EMOTION # FUNC_SEND_EMOTION
req.file.path = path
req.file.receiver = receiver
rsp = self._send_request(req)
return rsp.status
def get_msg(self, block=True) -> WxMsg:
"""从消息队列中获取消息
Args:
block (bool): 是否阻塞默认阻塞
Returns:
WxMsg: 微信消息
Raises:
Empty: 如果阻塞并且超时抛出空异常需要用户自行捕获
"""
return self.msgQ.get(block, timeout=1)
def enable_receiving_msg(self, pyq=False) -> bool:
"""允许接收消息,成功后通过 `get_msg` 读取消息"""
def listening_msg():
rsp = wcf_pb2.Response()
self.msg_socket.dial(self.msg_url, block=True)
while self._is_receiving_msg:
try:
rsp.ParseFromString(self.msg_socket.recv_msg().bytes)
except Exception as e:
pass
else:
self.msgQ.put(WxMsg(rsp.wxmsg))
# 退出前关闭通信通道
self.msg_socket.close()
if self._is_receiving_msg:
return True
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.listening_msg(callback)
# 不阻塞,启动一个新的线程来接收消息
Thread(target=listening_msg, name="GetMessage", daemon=True).start()
return True
def enable_recv_msg(self, callback: Callable[[WxMsg], None] = None) -> bool:
"""(不建议使用)设置接收消息回调,消息量大时可能会丢失消息
.. deprecated:: 3.7.0.30.13
"""
def listening_msg():
rsp = wcf_pb2.Response()
self.msg_socket.dial(self.msg_url, block=True)
while self._is_receiving_msg:
try:
rsp.ParseFromString(self.msg_socket.recv_msg().bytes)
except Exception as e:
pass
else:
callback(WxMsg(rsp.wxmsg))
# 退出前关闭通信通道
self.msg_socket.close()
if self._is_receiving_msg:
return True
if callback is None:
return False
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_ENABLE_RECV_TXT # FUNC_ENABLE_RECV_TXT
rsp = self._send_request(req)
if rsp.status != 0:
return False
self._is_receiving_msg = True
# 阻塞,把控制权交给用户
# listening_msg()
# 不阻塞,启动一个新的线程来接收消息
Thread(target=listening_msg, name="GetMessage", daemon=True).start()
return True
def disable_recv_msg(self) -> int:
"""停止接收消息"""
if not self._is_receiving_msg:
return 0
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_DISABLE_RECV_TXT # FUNC_DISABLE_RECV_TXT
rsp = self._send_request(req)
self._is_receiving_msg = False
return rsp.status
def query_sql(self, db: str, sql: str) -> List[Dict]:
"""执行 SQL如果数据量大注意分页以免 OOM
Args:
db (str): 要查询的数据库
sql (str): 要执行的 SQL
Returns:
List[Dict]: 查询结果
"""
result = []
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_EXEC_DB_QUERY # FUNC_EXEC_DB_QUERY
req.query.db = db
req.query.sql = sql
rsp = self._send_request(req)
rows = json_format.MessageToDict(rsp.rows).get("rows", [])
for r in rows:
row = {}
for f in r["fields"]:
c = base64.b64decode(f.get("content", ""))
row[f["column"]] = self._SQL_TYPES[f["type"]](c)
result.append(row)
return result
def accept_new_friend(self, v3: str, v4: str, scene: int = 30) -> int:
"""通过好友申请
Args:
v3 (str): 加密用户名 (好友申请消息里 v3 开头的字符串)
v4 (str): Ticket (好友申请消息里 v4 开头的字符串)
scene: 申请方式 (好友申请消息里的 scene); 为了兼容旧接口默认为扫码添加 (30)
Returns:
int: 1 为成功其他失败
"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_ACCEPT_FRIEND # FUNC_ACCEPT_FRIEND
req.v.v3 = v3
req.v.v4 = v4
req.v.scene = scene
rsp = self._send_request(req)
return rsp.status
def get_friends(self) -> List[Dict]:
"""获取好友列表"""
not_friends = {
"fmessage": "朋友推荐消息",
"medianote": "语音记事本",
"floatbottle": "漂流瓶",
"filehelper": "文件传输助手",
"newsapp": "新闻",
}
friends = []
for cnt in self.get_contacts():
if (cnt["wxid"].endswith("@chatroom") or # 群聊
cnt["wxid"].startswith("gh_") or # 公众号
cnt["wxid"] in not_friends.keys() # 其他杂号
):
continue
friends.append(cnt)
return friends
def receive_transfer(self, wxid: str, transferid: str, transactionid: str) -> int:
"""接收转账
Args:
wxid (str): 转账消息里的发送人 wxid
transferid (str): 转账消息里的 transferid
transactionid (str): 转账消息里的 transactionid
Returns:
int: 1 为成功其他失败
"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_RECV_TRANSFER # FUNC_RECV_TRANSFER
req.tf.wxid = wxid
req.tf.tfid = transferid
req.tf.taid = transactionid
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:
"""解密图片:
Args:
src (str): 加密的图片路径
dst (str): 解密的图片路径
Returns:
bool: 是否成功
"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_DECRYPT_IMAGE # FUNC_DECRYPT_IMAGE
req.dec.src = src
req.dec.dst = dst
rsp = self._send_request(req)
return rsp.status == 1
def add_chatroom_members(self, roomid: str, wxids: str) -> int:
"""添加群成员
Args:
roomid (str): 待加群的 id
wxids (str): 要加到群里的 wxid多个用逗号分隔
Returns:
int: 1 为成功其他失败
"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_ADD_ROOM_MEMBERS # FUNC_ADD_ROOM_MEMBERS
req.m.roomid = roomid
req.m.wxids = wxids
rsp = self._send_request(req)
return rsp.status
def del_chatroom_members(self, roomid: str, wxids: str) -> int:
"""删除群成员
Args:
roomid (str): 群的 id
wxids (str): 要删除成员的 wxid多个用逗号分隔
Returns:
int: 1 为成功其他失败
"""
req = wcf_pb2.Request()
req.func = wcf_pb2.FUNC_DEL_ROOM_MEMBERS # FUNC_DEL_ROOM_MEMBERS
req.m.roomid = roomid
req.m.wxids = wxids.replace(" ", "")
rsp = self._send_request(req)
return rsp.status
def get_chatroom_members(self, roomid: str) -> Dict:
"""获取群成员
Args:
roomid (str): 群的 id
Returns:
Dict: 群成员列表: {wxid1: 昵称1, wxid2: 昵称2, ...}
"""
members = {}
contacts = self.query_sql("MicroMsg.db", "SELECT UserName, NickName FROM Contact;")
contacts = {contact["UserName"]: contact["NickName"]for contact in contacts}
crs = self.query_sql("MicroMsg.db", f"SELECT RoomData FROM ChatRoom WHERE ChatRoomName = '{roomid}';")
if not crs:
return members
bs = crs[0].get("RoomData")
if not bs:
return members
crd = RoomData()
crd.ParseFromString(bs)
if not bs:
return members
for member in crd.members:
members[member.wxid] = member.name if member.name else contacts.get(member.wxid, "")
return members
def get_alias_in_chatroom(self, wxid: str, roomid: str) -> str:
"""获取群名片
Args:
wxid (str): wxid
roomid (str): 群的 id
Returns:
str: 群名片
"""
nickname = self.query_sql("MicroMsg.db", f"SELECT NickName FROM Contact WHERE UserName = '{wxid}';")
if not nickname:
return ""
nickname = nickname[0].get("NickName", "")
crs = self.query_sql("MicroMsg.db", f"SELECT RoomData FROM ChatRoom WHERE ChatRoomName = '{roomid}';")
if not crs:
return ""
bs = crs[0].get("RoomData")
if not bs:
return ""
crd = RoomData()
crd.ParseFromString(bs)
for member in crd.members:
if member.wxid == wxid:
return member.name if member.name else nickname
return ""

View File

@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-
from wcferry.event.event import Event
from wcferry.event.core import load_function
Event = load_function(Event)

View File

@ -1,27 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: roomdata.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0eroomdata.proto\x12\x0f\x63om.iamteer.wcf\"\xf7\x01\n\x08RoomData\x12\x35\n\x07members\x18\x01 \x03(\x0b\x32$.com.iamteer.wcf.RoomData.RoomMember\x12\x0f\n\x07\x66ield_2\x18\x02 \x01(\x05\x12\x0f\n\x07\x66ield_3\x18\x03 \x01(\x05\x12\x0f\n\x07\x66ield_4\x18\x04 \x01(\x05\x12\x15\n\rroom_capacity\x18\x05 \x01(\x05\x12\x0f\n\x07\x66ield_6\x18\x06 \x01(\x05\x12\x0f\n\x07\x66ield_7\x18\x07 \x01(\x03\x12\x0f\n\x07\x66ield_8\x18\x08 \x01(\x03\x1a\x37\n\nRoomMember\x12\x0c\n\x04wxid\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\r\n\x05state\x18\x03 \x01(\x05\x62\x06proto3')
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'roomdata_pb2', globals())
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
_ROOMDATA._serialized_start=36
_ROOMDATA._serialized_end=283
_ROOMDATA_ROOMMEMBER._serialized_start=228
_ROOMDATA_ROOMMEMBER._serialized_end=283
# @@protoc_insertion_point(module_scope)

View File

@ -1,74 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by the protocol buffer compiler. DO NOT EDIT!
# source: wcf.proto
"""Generated protocol buffer code."""
from google.protobuf.internal import builder as _builder
from google.protobuf import descriptor as _descriptor
from google.protobuf import descriptor_pool as _descriptor_pool
from google.protobuf import symbol_database as _symbol_database
# @@protoc_insertion_point(imports)
_sym_db = _symbol_database.Default()
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())
if _descriptor._USE_C_DESCRIPTORS == False:
DESCRIPTOR._options = None
DESCRIPTOR._serialized_options = b'\n\013com.iamteer'
_MSGTYPES_TYPESENTRY._options = None
_MSGTYPES_TYPESENTRY._serialized_options = b'8\001'
_FUNCTIONS._serialized_start=1878
_FUNCTIONS._serialized_end=2394
_REQUEST._serialized_start=19
_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)