From df9557cce8ffa1ab7e99ea5178525760f0e37274 Mon Sep 17 00:00:00 2001 From: Changhua Date: Sat, 7 Oct 2023 15:29:50 +0800 Subject: [PATCH 1/4] Draft pyauto --- clients/{wcfautopy => pyauto}/README.MD | 98 +-- clients/{wcfautopy => pyauto}/demo.py | 30 +- clients/{wcfautopy => pyauto}/setup.py | 15 +- clients/pyauto/wcfauto/__init__.py | 5 + clients/pyauto/wcfauto/auto_res/__init__.py | 7 + .../wcfauto}/auto_res/bot.py | 9 +- .../wcfauto}/auto_res/core.py | 3 +- clients/pyauto/wcfauto/event/__init__.py | 6 + .../wcferry => pyauto/wcfauto}/event/core.py | 2 +- .../wcferry => pyauto/wcfauto}/event/event.py | 2 +- clients/wcfautopy/MANIFEST.in | 2 - clients/wcfautopy/roomdata.proto | 21 - clients/wcfautopy/wcferry/__init__.py | 6 - .../wcfautopy/wcferry/auto_res/__init__.py | 8 - clients/wcfautopy/wcferry/client.py | 694 ------------------ clients/wcfautopy/wcferry/event/__init__.py | 11 - clients/wcfautopy/wcferry/roomdata_pb2.py | 27 - clients/wcfautopy/wcferry/wcf_pb2.py | 74 -- clients/wcfautopy/wcferry/wxmsg.py | 123 ---- 19 files changed, 66 insertions(+), 1077 deletions(-) rename clients/{wcfautopy => pyauto}/README.MD (65%) rename clients/{wcfautopy => pyauto}/demo.py (91%) rename clients/{wcfautopy => pyauto}/setup.py (82%) create mode 100644 clients/pyauto/wcfauto/__init__.py create mode 100644 clients/pyauto/wcfauto/auto_res/__init__.py rename clients/{wcfautopy/wcferry => pyauto/wcfauto}/auto_res/bot.py (98%) rename clients/{wcfautopy/wcferry => pyauto/wcfauto}/auto_res/core.py (99%) create mode 100644 clients/pyauto/wcfauto/event/__init__.py rename clients/{wcfautopy/wcferry => pyauto/wcfauto}/event/core.py (100%) rename clients/{wcfautopy/wcferry => pyauto/wcfauto}/event/event.py (100%) delete mode 100644 clients/wcfautopy/MANIFEST.in delete mode 100644 clients/wcfautopy/roomdata.proto delete mode 100644 clients/wcfautopy/wcferry/__init__.py delete mode 100644 clients/wcfautopy/wcferry/auto_res/__init__.py delete mode 100644 clients/wcfautopy/wcferry/client.py delete mode 100644 clients/wcfautopy/wcferry/event/__init__.py delete mode 100644 clients/wcfautopy/wcferry/roomdata_pb2.py delete mode 100644 clients/wcfautopy/wcferry/wcf_pb2.py delete mode 100644 clients/wcfautopy/wcferry/wxmsg.py diff --git a/clients/wcfautopy/README.MD b/clients/pyauto/README.MD similarity index 65% rename from clients/wcfautopy/README.MD rename to clients/pyauto/README.MD index 1b716a6..1b8f618 100644 --- a/clients/wcfautopy/README.MD +++ b/clients/pyauto/README.MD @@ -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,64 @@ pip install --upgrade wcferry import logging from time import sleep -from wcferry import Wcf, WxMsg, Register + +from wcfauto import Register +from wcferry import 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 +94,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) -* 修复登录账号昵称超长报错问题 - -
点击查看更多 - -版本号:`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) - -
diff --git a/clients/wcfautopy/demo.py b/clients/pyauto/demo.py similarity index 91% rename from clients/wcfautopy/demo.py rename to clients/pyauto/demo.py index 0ace02c..dc50c15 100644 --- a/clients/wcfautopy/demo.py +++ b/clients/pyauto/demo.py @@ -3,62 +3,64 @@ import logging from time import sleep -from wcferry import Wcf, WxMsg, Register + +from wcfauto import Register +from wcferry import 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 开始刷新朋友圈 diff --git a/clients/wcfautopy/setup.py b/clients/pyauto/setup.py similarity index 82% rename from clients/wcfautopy/setup.py rename to clients/pyauto/setup.py index 2f1fa75..ef7fb33 100644 --- a/clients/wcfautopy/setup.py +++ b/clients/pyauto/setup.py @@ -1,19 +1,16 @@ #! /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__, + name="wcfauto", + version=wcfauto.__version__, author="Changhua", author_email="lichanghua0821@gmail.com", description="一个玩微信的工具", @@ -26,9 +23,7 @@ setup( include_package_data=True, install_requires=[ "setuptools", - "grpcio-tools", - "pynng", - "requests", + "wcferry", ], classifiers=[ "Environment :: Win32 (MS Windows)", diff --git a/clients/pyauto/wcfauto/__init__.py b/clients/pyauto/wcfauto/__init__.py new file mode 100644 index 0000000..c28072f --- /dev/null +++ b/clients/pyauto/wcfauto/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from wcfauto.auto_res import Register + +__version__ = "39.0.3.0" diff --git a/clients/pyauto/wcfauto/auto_res/__init__.py b/clients/pyauto/wcfauto/auto_res/__init__.py new file mode 100644 index 0000000..43de01e --- /dev/null +++ b/clients/pyauto/wcfauto/auto_res/__init__.py @@ -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) diff --git a/clients/wcfautopy/wcferry/auto_res/bot.py b/clients/pyauto/wcfauto/auto_res/bot.py similarity index 98% rename from clients/wcfautopy/wcferry/auto_res/bot.py rename to clients/pyauto/wcfauto/auto_res/bot.py index e30aae7..b6cc139 100644 --- a/clients/wcfautopy/wcferry/auto_res/bot.py +++ b/clients/pyauto/wcfauto/auto_res/bot.py @@ -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 wcferry.client import Wcf class Register(Event): diff --git a/clients/wcfautopy/wcferry/auto_res/core.py b/clients/pyauto/wcfauto/auto_res/core.py similarity index 99% rename from clients/wcfautopy/wcferry/auto_res/core.py rename to clients/pyauto/wcfauto/auto_res/core.py index e883964..c85168f 100644 --- a/clients/wcfautopy/wcferry/auto_res/core.py +++ b/clients/pyauto/wcfauto/auto_res/core.py @@ -5,7 +5,8 @@ import functools import queue import traceback from threading import Thread -from typing import Callable, Any +from typing import Any, Callable + from wcferry.client import Wcf from wcferry.wxmsg import WxMsg diff --git a/clients/pyauto/wcfauto/event/__init__.py b/clients/pyauto/wcfauto/event/__init__.py new file mode 100644 index 0000000..463e874 --- /dev/null +++ b/clients/pyauto/wcfauto/event/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +from wcfauto.event.event import Event +from wcfauto.event.core import load_function + +Event = load_function(Event) diff --git a/clients/wcfautopy/wcferry/event/core.py b/clients/pyauto/wcfauto/event/core.py similarity index 100% rename from clients/wcfautopy/wcferry/event/core.py rename to clients/pyauto/wcfauto/event/core.py index 7254525..424e68a 100644 --- a/clients/wcfautopy/wcferry/event/core.py +++ b/clients/pyauto/wcfauto/event/core.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -import traceback import asyncio +import traceback from threading import Thread diff --git a/clients/wcfautopy/wcferry/event/event.py b/clients/pyauto/wcfauto/event/event.py similarity index 100% rename from clients/wcfautopy/wcferry/event/event.py rename to clients/pyauto/wcfauto/event/event.py index fadb03f..4925ba9 100644 --- a/clients/wcfautopy/wcferry/event/event.py +++ b/clients/pyauto/wcfauto/event/event.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -from abc import abstractmethod import asyncio import logging +from abc import abstractmethod class Event(object): diff --git a/clients/wcfautopy/MANIFEST.in b/clients/wcfautopy/MANIFEST.in deleted file mode 100644 index e228aeb..0000000 --- a/clients/wcfautopy/MANIFEST.in +++ /dev/null @@ -1,2 +0,0 @@ -include wcferry/*.dll -include wcferry/*.exe diff --git a/clients/wcfautopy/roomdata.proto b/clients/wcfautopy/roomdata.proto deleted file mode 100644 index 22f307e..0000000 --- a/clients/wcfautopy/roomdata.proto +++ /dev/null @@ -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; -} diff --git a/clients/wcfautopy/wcferry/__init__.py b/clients/wcfautopy/wcferry/__init__.py deleted file mode 100644 index f5d4343..0000000 --- a/clients/wcfautopy/wcferry/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- - -from wcferry.client import Wcf, __version__ -from wcferry.wxmsg import WxMsg -from wcferry.auto_res import Register - diff --git a/clients/wcfautopy/wcferry/auto_res/__init__.py b/clients/wcfautopy/wcferry/auto_res/__init__.py deleted file mode 100644 index 320befe..0000000 --- a/clients/wcfautopy/wcferry/auto_res/__init__.py +++ /dev/null @@ -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) - diff --git a/clients/wcfautopy/wcferry/client.py b/clients/wcfautopy/wcferry/client.py deleted file mode 100644 index 9136f47..0000000 --- a/clients/wcfautopy/wcferry/client.py +++ /dev/null @@ -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): 开始 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: - """解密图片: - - 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 "" diff --git a/clients/wcfautopy/wcferry/event/__init__.py b/clients/wcfautopy/wcferry/event/__init__.py deleted file mode 100644 index 104d964..0000000 --- a/clients/wcfautopy/wcferry/event/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 -*- - -from wcferry.event.event import Event -from wcferry.event.core import load_function - -Event = load_function(Event) - - - - - diff --git a/clients/wcfautopy/wcferry/roomdata_pb2.py b/clients/wcfautopy/wcferry/roomdata_pb2.py deleted file mode 100644 index 23ab48a..0000000 --- a/clients/wcfautopy/wcferry/roomdata_pb2.py +++ /dev/null @@ -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) diff --git a/clients/wcfautopy/wcferry/wcf_pb2.py b/clients/wcfautopy/wcferry/wcf_pb2.py deleted file mode 100644 index ba142c9..0000000 --- a/clients/wcfautopy/wcferry/wcf_pb2.py +++ /dev/null @@ -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) diff --git a/clients/wcfautopy/wcferry/wxmsg.py b/clients/wcfautopy/wcferry/wxmsg.py deleted file mode 100644 index 122ec7c..0000000 --- a/clients/wcfautopy/wcferry/wxmsg.py +++ /dev/null @@ -1,123 +0,0 @@ -# -*- coding: utf-8 -*- - -import re -from datetime import datetime -import time -from wcferry import wcf_pb2 - - -class WxMsg(dict): - """微信消息 - - Attributes: - type (int): 消息类型,可通过 `get_msg_types` 获取 - id (str): 消息 id - xml (str): 消息 xml 部分 - sender (str): 消息发送人 - roomid (str): (仅群消息有)群 id - content (str): 消息内容 - thumb (str): 视频或图片消息的缩略图路径 - 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 - 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 - self._content = msg.content - self._thumb = msg.thumb - self._extra = msg.extra - self.__data = {'isSelf': True if self._is_self else False, - 'isGroup': True if self._is_group else False, - 'isPyq': True if self._type == 0 else False, - 'data': { - 'type': self._type, - 'content': self._content, - 'sender': self._sender, - 'msgid': self._id, - 'roomid': self._roomid if self._roomid else None, - 'xml': self._xml, - 'thumb': self._thumb if self._thumb else None, - 'extra': self._extra if self._extra else None, - 'time': int(time.time() * 1000), - }, 'revokmsgid': None, 'isRevokeMsg': False, } - self.__revokmsg_p() - - def __revokmsg_p(self): - rmsg = self.__data['data']['content'] - rev_type = re.findall('", rmsg) - 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 - self.__data['revokmsgid'] = re.findall('(.*?)', rmsg)[0] - - def __str__(self) -> str: - return repr(self.__data) - - def __repr__(self) -> str: - return repr(self.__data) - - def __getitem__(self, key): - return self.__data[key] - - def __getattr__(self, item): - if item in ['content', 'sender', 'roomid', 'xml', 'thumb', 'extra', 'type']: - return self.__data['data'][item] - if item == 'id': - return self.__data['data']['msgid'] - if item == 'ts': - return self._ts - if item == 'sign': - return self._sign - - def __setitem__(self, key, value): - self.__data[key] = value - - def is_image(self) -> bool: - """是否是图片""" - return self.type == 3 and ('imgdatahash' in self.__data['data']['content']) - - def is_voice(self) -> bool: - """是否是语音""" - return self.type == 34 and ('voicemsg' in self.__data['data']['content']) - - def is_video(self) -> bool: - """是否是视频""" - return self.type == 43 and ('videomsg' in self.__data['data']['content']) - - def is_pyq(self) -> bool: - return self.type == 0 - - def from_self(self) -> bool: - """是否自己发的消息""" - return self._is_self == 1 - - def from_group(self) -> bool: - """是否群聊消息""" - return self._is_group - - def is_at(self, wxid) -> bool: - """是否被 @:群消息,在 @ 名单里,并且不是 @ 所有人""" - if not self.from_group(): - return False # 只有群消息才能 @ - - if not re.findall(f".*({wxid}).*", self.xml): - return False # 不在 @ 清单里 - - if re.findall(r"@(?:所有人|all|All)", self.content): - return False # 排除 @ 所有人 - - return True - - def is_text(self) -> bool: - """是否文本消息""" - return self.type == 1 From 894997684518083719dce6ff9e5255e601e372aa Mon Sep 17 00:00:00 2001 From: Changhua Date: Sat, 7 Oct 2023 15:36:47 +0800 Subject: [PATCH 2/4] Update author --- clients/pyauto/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/pyauto/setup.py b/clients/pyauto/setup.py index ef7fb33..f2dc9a1 100644 --- a/clients/pyauto/setup.py +++ b/clients/pyauto/setup.py @@ -11,8 +11,8 @@ with open("README.md", "r", encoding="utf-8") as fh: setup( name="wcfauto", version=wcfauto.__version__, - author="Changhua", - author_email="lichanghua0821@gmail.com", + author="bujinzhang", + author_email="", description="一个玩微信的工具", long_description=long_description, long_description_content_type="text/markdown", From 13e20e497777de6ab71cd1dea138abfb2ad6c469 Mon Sep 17 00:00:00 2001 From: Changhua Date: Sat, 7 Oct 2023 17:02:43 +0800 Subject: [PATCH 3/4] Impl pyauto --- clients/pyauto/demo.py | 3 +- clients/pyauto/wcfauto/__init__.py | 2 + clients/pyauto/wcfauto/auto_res/bot.py | 2 +- clients/pyauto/wcfauto/auto_res/core.py | 14 ++- clients/pyauto/wcfauto/wcf.py | 141 ++++++++++++++++++++++++ 5 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 clients/pyauto/wcfauto/wcf.py diff --git a/clients/pyauto/demo.py b/clients/pyauto/demo.py index dc50c15..3de2406 100644 --- a/clients/pyauto/demo.py +++ b/clients/pyauto/demo.py @@ -4,8 +4,7 @@ import logging from time import sleep -from wcfauto import Register -from wcferry import Wcf, WxMsg +from wcfauto import Register, Wcf, WxMsg logging.basicConfig(level='DEBUG', format="%(asctime)s %(message)s") LOG = logging.getLogger("Demo") diff --git a/clients/pyauto/wcfauto/__init__.py b/clients/pyauto/wcfauto/__init__.py index c28072f..e54b391 100644 --- a/clients/pyauto/wcfauto/__init__.py +++ b/clients/pyauto/wcfauto/__init__.py @@ -1,5 +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" diff --git a/clients/pyauto/wcfauto/auto_res/bot.py b/clients/pyauto/wcfauto/auto_res/bot.py index b6cc139..d03bef8 100644 --- a/clients/pyauto/wcfauto/auto_res/bot.py +++ b/clients/pyauto/wcfauto/auto_res/bot.py @@ -5,7 +5,7 @@ from abc import abstractmethod from typing import Any, Callable from wcfauto.event import Event -from wcferry.client import Wcf +from wcfauto.wcf import WcfV2 as Wcf class Register(Event): diff --git a/clients/pyauto/wcfauto/auto_res/core.py b/clients/pyauto/wcfauto/auto_res/core.py index c85168f..ce2b666 100644 --- a/clients/pyauto/wcfauto/auto_res/core.py +++ b/clients/pyauto/wcfauto/auto_res/core.py @@ -7,8 +7,8 @@ import traceback from threading import Thread from typing import Any, Callable -from wcferry.client import Wcf -from wcferry.wxmsg import WxMsg +from wcfauto.wcf import WcfV2 as Wcf +from wcfauto.wcf import WxMsgV2 as WxMsg def load_function(cls): @@ -52,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: @@ -79,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: @@ -116,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() diff --git a/clients/pyauto/wcfauto/wcf.py b/clients/pyauto/wcfauto/wcf.py new file mode 100644 index 0000000..846469b --- /dev/null +++ b/clients/pyauto/wcfauto/wcf.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from wcferry import Wcf, WxMsg + + +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 + xml (str): 消息 xml 部分 + sender (str): 消息发送人 + roomid (str): (仅群消息有)群 id + content (str): 消息内容 + thumb (str): 视频或图片消息的缩略图路径 + extra (str): 视频或图片消息的路径 + """ + + 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 + self._sign = msg.sign + self._xml = msg.xml + self._sender = msg.sender + self._roomid = msg.roomid + self._content = msg.content + self._thumb = msg.thumb + self._extra = msg.extra + self.__data = {'isSelf': True if self._is_self else False, + 'isGroup': True if self._is_group else False, + 'isPyq': True if self._type == 0 else False, + 'data': { + 'type': self._type, + 'content': self._content, + 'sender': self._sender, + 'msgid': self._id, + 'roomid': self._roomid if self._roomid else None, + 'xml': self._xml, + 'thumb': self._thumb if self._thumb else None, + 'extra': self._extra if self._extra else None, + 'time': int(time.time() * 1000), + }, 'revokmsgid': None, 'isRevokeMsg': False, } + self.__revokmsg_p() + + def __revokmsg_p(self): + rmsg = self.__data['data']['content'] + rev_type = re.findall('", rmsg) + 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 + self.__data['revokmsgid'] = re.findall('(.*?)', rmsg)[0] + + def __str__(self) -> str: + return repr(self.__data) + + def __repr__(self) -> str: + return repr(self.__data) + + def __getitem__(self, key): + return self.__data[key] + + def __getattr__(self, item): + if item in ['content', 'sender', 'roomid', 'xml', 'thumb', 'extra', 'type']: + return self.__data['data'][item] + if item == 'id': + return self.__data['data']['msgid'] + if item == 'ts': + return self._ts + if item == 'sign': + return self._sign + + def __setitem__(self, key, value): + self.__data[key] = value + + def is_image(self) -> bool: + """是否是图片""" + return self.type == 3 and ('imgdatahash' in self.__data['data']['content']) + + def is_voice(self) -> bool: + """是否是语音""" + return self.type == 34 and ('voicemsg' in self.__data['data']['content']) + + def is_video(self) -> bool: + """是否是视频""" + return self.type == 43 and ('videomsg' in self.__data['data']['content']) + + def is_pyq(self) -> bool: + return self.type == 0 + + def from_self(self) -> bool: + """是否自己发的消息""" + return self._is_self == 1 + + def from_group(self) -> bool: + """是否群聊消息""" + return self._is_group + + def is_at(self, wxid) -> bool: + """是否被 @:群消息,在 @ 名单里,并且不是 @ 所有人""" + if not self.from_group(): + return False # 只有群消息才能 @ + + if not re.findall(f".*({wxid}).*", self.xml): + return False # 不在 @ 清单里 + + if re.findall(r"@(?:所有人|all|All)", self.content): + return False # 排除 @ 所有人 + + return True + + def is_text(self) -> bool: + """是否文本消息""" + return self.type == 1 From 28e701ad2678275ec97b2334fa074672a7ae7f37 Mon Sep 17 00:00:00 2001 From: Changhua Date: Sat, 7 Oct 2023 17:03:01 +0800 Subject: [PATCH 4/4] Update demo --- clients/pyauto/README.MD | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clients/pyauto/README.MD b/clients/pyauto/README.MD index 1b8f618..805dd7c 100644 --- a/clients/pyauto/README.MD +++ b/clients/pyauto/README.MD @@ -19,8 +19,7 @@ pip install --upgrade wcfauto import logging from time import sleep -from wcfauto import Register -from wcferry import Wcf, WxMsg +from wcfauto import Register, Wcf, WxMsg logging.basicConfig(level='DEBUG', format="%(asctime)s %(message)s") LOG = logging.getLogger("Demo")