diff --git a/clients/pyauto/README.MD b/clients/pyauto/README.MD index 805dd7c..cf129c3 100644 --- a/clients/pyauto/README.MD +++ b/clients/pyauto/README.MD @@ -18,7 +18,6 @@ pip install --upgrade wcfauto import logging from time import sleep - from wcfauto import Register, Wcf, WxMsg logging.basicConfig(level='DEBUG', format="%(asctime)s %(message)s") @@ -85,6 +84,34 @@ def main(): 异步消息函数装饰器 """ print(msg) + + @receiver.group_changed_register(allow_other_receive=False) + async def group_changed(bot: Wcf, msg: WxMsg): + """ + 群组信息变化函数装饰器 + """ + print(msg) + + @receiver.revoke_message_register(allow_other_receive=False) + async def group_changed(bot: Wcf, msg: WxMsg): + """ + 撤回消息函数装饰器 + """ + print(msg) + + def judge(msg: WxMsg): + """ + 消息判断函数 + """ + return False + + + @receiver.custom_message_register(register_name='custom', msg_judge_func=judge, allow_other_receive=False) + async def group_changed(bot: Wcf, msg: WxMsg): + """ + 自定义消息接收函数装饰器 + """ + print(msg) # 开始接受消息 receiver.run() @@ -93,6 +120,7 @@ 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)| diff --git a/clients/pyauto/demo.py b/clients/pyauto/demo.py index 3de2406..2470cbf 100644 --- a/clients/pyauto/demo.py +++ b/clients/pyauto/demo.py @@ -3,7 +3,6 @@ import logging from time import sleep - from wcfauto import Register, Wcf, WxMsg logging.basicConfig(level='DEBUG', format="%(asctime)s %(message)s") @@ -71,6 +70,34 @@ def main(): """ print(msg) + @receiver.group_changed_register(allow_other_receive=False) + async def group_changed(bot: Wcf, msg: WxMsg): + """ + 群组信息变化函数装饰器 + """ + print(msg) + + @receiver.revoke_message_register(allow_other_receive=False) + async def group_changed(bot: Wcf, msg: WxMsg): + """ + 撤回消息函数装饰器 + """ + print(msg) + + def judge(msg: WxMsg): + """ + 消息判断函数 + """ + return False + + + @receiver.custom_message_register(register_name='custom', msg_judge_func=judge, allow_other_receive=False) + async def group_changed(bot: Wcf, msg: WxMsg): + """ + 自定义消息接收函数装饰器 + """ + print(msg) + # 开始接受消息 receiver.run() diff --git a/clients/pyauto/wcfauto/__init__.py b/clients/pyauto/wcfauto/__init__.py index e54b391..75593bc 100644 --- a/clients/pyauto/wcfauto/__init__.py +++ b/clients/pyauto/wcfauto/__init__.py @@ -5,3 +5,4 @@ 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/__init__.py b/clients/pyauto/wcfauto/auto_res/__init__.py index 43de01e..0ebd8b6 100644 --- a/clients/pyauto/wcfauto/auto_res/__init__.py +++ b/clients/pyauto/wcfauto/auto_res/__init__.py @@ -5,3 +5,4 @@ from wcfauto.auto_res.core import load_function Register = load_function(Register) + diff --git a/clients/pyauto/wcfauto/auto_res/bot.py b/clients/pyauto/wcfauto/auto_res/bot.py index d03bef8..ee8d9d0 100644 --- a/clients/pyauto/wcfauto/auto_res/bot.py +++ b/clients/pyauto/wcfauto/auto_res/bot.py @@ -1,15 +1,19 @@ # -*- coding: utf-8 -*- -import logging -from abc import abstractmethod -from typing import Any, Callable - +from typing import Callable, Any from wcfauto.event import Event -from wcfauto.wcf import WcfV2 as Wcf +from abc import abstractmethod +from wcfauto.wcf import WcfV2 as Wcf, WxMsgV2 as WxMsg +import logging class Register(Event): - def __init__(self, debug=True, **kwargs): + """ + 消息注册器 + """ + def __init__(self, + debug=True, + **kwargs): super(Register, self).__init__() logging.basicConfig(level='DEBUG', format="%(asctime)s %(message)s") self._LOG = logging.getLogger("Demo") @@ -18,8 +22,10 @@ class Register(Event): self._wcf = Wcf(debug=debug, **kwargs) @abstractmethod - def _process_msg(self, wcf: Wcf): + def _process_msg(self, + wcf: Wcf): """ + 外部不可访问接口 有消息的时候,通知分发器分发消息 :param wcf: Wcf :return: None @@ -28,11 +34,17 @@ class Register(Event): @abstractmethod def _register(self, - func: Callable[[Any], Any]): + kind: str, + register_name: str, + allow_other_receive: bool, + judge_msg: Callable[[Any], Any]): """ + 外部不可访问接口 消息处理工厂, 所有消息处理函数都会汇总在这里提交给事件分发器 - :param func: 被装饰的待处理消息函数 - :return: func + :param kind: 用于区分被装饰函数属于异步或者同步函数, 主要用于消息分发函数中做区分主要两大类函数 + :param register_name: 函数类名 (用于一个装饰器用于装饰多个函数时, 将这些函数统一取名一个类名) + :param allow_other_receive 是否允许其他消息函数(指函数类名不同且必须同为同一个 kind的消息函数(即同为异步或者同步))接收消息 + :param judge_msg 用来判断消息是否是本函数要求的消息 """ raise NotImplementedError @@ -40,14 +52,21 @@ class Register(Event): def _processing_async_func(self, isGroup: bool, isDivision: bool, - isPyq: bool): + isPyq: bool, + register_name: str, + allow_other_receive: bool, + judge_msg: Callable[[Any], bool]): """ - 异步函数消息处理函数, 用来接受非协程函数 + 外部不可访问接口 + 异步函数消息处理函数, 用来接受协程函数, 此函数为异步装饰器函数基函数 参数: :param isGroup 对消息进行限制, 当为True时, 只接受群消息, 当为False时, 只接受私聊消息, 注意! 仅当isDivision为 True时, isGroup参数生效 :param isPyq 是否接受朋友圈消息 :param isDivision 是否对消息分组 + :param register_name 函数类名 (用于一个装饰器用于装饰多个函数时, 将这些函数统一取名一个类名) + :param allow_other_receive 是否允许其他消息函数(指函数类名不同且必须同为同一个 kind的消息函数(即同为异步或者同步))接收消息 + :param judge_msg 用来判断消息是否是本函数要求的消息 """ raise NotImplementedError @@ -55,14 +74,21 @@ class Register(Event): def _processing_universal_func(self, isGroup: bool, isDivision: bool, - isPyq: bool): + isPyq: bool, + register_name: str, + allow_other_receive: bool, + judge_msg: Callable[[Any], bool]): """ - 同步函数消息处理函数, 用来接受非协程函数 + 外部不可访问接口 + 同步函数消息处理函数, 用来接受非协程函数, 此函数为同步装饰器函数基函数 参数: :param isGroup 对消息进行限制, 当为True时, 只接受群消息, 当为False时, 只接受私聊消息, 注意! 仅当isDivision为 True时, isGroup参数生效 :param isPyq 是否接受朋友圈消息 :param isDivision 是否对消息分组 + :param register_name 函数类名 (用于一个装饰器用于装饰多个函数时, 将这些函数统一取名一个类名) + :param allow_other_receive 是否允许其他消息函数(指函数类名不同且必须同为同一个 kind的消息函数(即同为异步或者同步))接收消息 + :param judge_msg 用来判断消息是否是本函数要求的消息 """ raise NotImplementedError @@ -73,7 +99,7 @@ class Register(Event): isPyq: bool = False): """ 外部可访问接口 - 消息处理函数注册器, 用来接受同步函数 + [所有消息] 处理函数注册器, 用来接受同步函数 参数: :param isGroup 对消息进行限制, 当为True时, 只接受群消息, 当为False时, 只接受私聊消息, 注意! 仅当isDivision为 True时, isGroup参数生效 @@ -89,7 +115,7 @@ class Register(Event): isPyq: bool = False): """ 外部可访问接口 - 消息处理函数注册器, 用来接受异步函数 + [所有消息] 处理函数注册器, 用来接受异步函数 参数: :param isGroup 对消息进行限制, 当为True时, 只接受群消息, 当为False时, 只接受私聊消息, 注意! 仅当isDivision为 True时, isGroup参数生效 @@ -99,7 +125,68 @@ class Register(Event): raise NotImplementedError @abstractmethod - def run(self, *args, **kwargs): + def revoke_message_register(self, + isGroup: bool = False, + isDivision: bool = False, + isPyq: bool = False, + allow_other_receive: bool = True): + """ + 外部可访问接口 + [撤回消息] 处理函数注册器, 用来接受异步函数 + 参数: + :param isGroup 对消息进行限制, 当为True时, 只接受群消息, 当为False时, 只接受私聊消息, + 注意! 仅当isDivision为 True时, isGroup参数生效 + :param isPyq 是否接受朋友圈消息 + :param isDivision 是否对消息分组 + :param allow_other_receive 是否允许符合本函数要求的消息(撤回消息)被其他消息函数(指函数类名不同且 + 必须同为同一个 kind的消息函数(即同为异步或者同步))接受 + """ + raise NotImplementedError + + def group_changed_register(self, + isGroup: bool = False, + isDivision: bool = False, + isPyq: bool = False, + allow_other_receive: bool = True): + """ + 外部可访问接口 + [群成员变动消息]处理函数注册器, 用来接受异步函数 + 参数: + :param isGroup 对消息进行限制, 当为True时, 只接受群消息, 当为False时, 只接受私聊消息, + 注意! 仅当isDivision为 True时, isGroup参数生效 + :param isPyq 是否接受朋友圈消息 + :param isDivision 是否对消息分组 + :param allow_other_receive 是否允许符合本函数要求的消息(群成员消息)被其他消息函数(指函数类名不同且 + 必须同为同一个 kind的消息函数(即同为异步或者同步))接受 + """ + raise NotImplementedError + + def custom_message_register(self, + register_name: str, + msg_judge_func: Callable[[WxMsg], bool], + allow_other_receive: bool, + isGroup: bool = False, + isDivision: bool = False, + isPyq: bool = False): + """ + 外部可访问接口 + [自定义消息]处理函数注册器, 用来接受异步函数 + 参数: + :param isGroup 对消息进行限制, 当为True时, 只接受群消息, 当为False时, 只接受私聊消息, + 注意! 仅当isDivision为 True时, isGroup参数生效 + :param isPyq 是否接受朋友圈消息 + :param isDivision 是否对消息分组 + :param register_name 注册函数类名, 注意不可为空, 注意函数类名决定最后消息分发结果, 因此每个新函数的类名应是不同的 + :param msg_judge_func 判断消息是否为符合自定义消息的函数 (函数直接受一个参数 WcfMsg作为参数, 返回值为 bool) + 在函数内编写需要符合自定义要求的函数, 但函数返回值必须为 bool类型, 符合要求返回 True, + 不符合要求返回 False + :param allow_other_receive 是否允许符合本函数要求的消息(自定义符合消息)被其他消息函数(指函数类名不同且 + 必须同为同一个 kind的消息函数(即同为异步或者同步))接受 + """ + raise NotImplementedError + + @abstractmethod + def run(self, pyq=True): """ 启动程序, 开始接受消息 """ diff --git a/clients/pyauto/wcfauto/auto_res/core.py b/clients/pyauto/wcfauto/auto_res/core.py index ce2b666..63eb71a 100644 --- a/clients/pyauto/wcfauto/auto_res/core.py +++ b/clients/pyauto/wcfauto/auto_res/core.py @@ -5,8 +5,7 @@ import functools import queue import traceback from threading import Thread -from typing import Any, Callable - +from typing import Callable, Any from wcfauto.wcf import WcfV2 as Wcf from wcfauto.wcf import WxMsgV2 as WxMsg @@ -18,6 +17,9 @@ def load_function(cls): cls._processing_universal_func = _processing_universal_func cls.message_register = message_register cls.async_message_register = async_message_register + cls.revoke_message_register = revoke_message_register + cls.group_changed_register = group_changed_register + cls.custom_message_register = custom_message_register cls.run = run cls.stop_receiving = stop_receiving return cls @@ -35,20 +37,33 @@ def _process_msg(self, wcf: Wcf): def _register(self, - func: Callable[[Any], Any]): - self._add_callback(func, self._wcf) - # 此处必须返回被装饰函数原函数, 否则丢失被装饰函数信息 - return func + kind: str, + register_name: str, + allow_other_receive: bool, + judge_msg: Callable[[Any], Any]): + def __register(func: Callable[[Any], Any]): + self._add_callback(func, + self._wcf, + kind=kind, + register_name=register_name, + allow_other_rec=allow_other_receive, + judge_msg=judge_msg) + # 此处必须返回被装饰函数原函数, 否则丢失被装饰函数信息 + return func + + return __register def _processing_async_func(self, isGroup: bool, isDivision: bool, - isPyq: bool,): + isPyq: bool, + register_name: str, + allow_other_receive: bool, + judge_msg: Callable[[Any], bool]): def _async_func(func): - @functools.wraps(func) - @self._register + @self._register('async', register_name, allow_other_receive, judge_msg) async def __async_func(bot: Wcf, message: WxMsg): try: # 判断被装饰函数是否为协程函数, 本函数要求是协程函数 @@ -71,17 +86,19 @@ def _processing_async_func(self, def _processing_universal_func(self, isGroup: bool, isDivision: bool, - isPyq: bool, ): + isPyq: bool, + register_name: str, + allow_other_receive: bool, + judge_msg: Callable[[Any], bool]): def _universal_func(func): @functools.wraps(func) - @self._register + @self._register('universal', register_name, allow_other_receive, judge_msg) def universal_func(bot: Wcf, message: WxMsg): try: # 判断被装饰函数是否为协程函数, 本函数要求是协程函数 if asyncio.iscoroutinefunction(func): - raise ValueError( - f'这里应使用非协程函数, 而被装饰函数-> ({func.__name__}) <-协程函数') + raise ValueError(f'这里应使用非协程函数, 而被装饰函数-> ({func.__name__}) <-协程函数') if message.is_pyq() and isPyq: return func(bot, message) if not isDivision: @@ -92,7 +109,6 @@ def _processing_universal_func(self, return func(bot, message) except: traceback.print_exc() - return None return universal_func return _universal_func @@ -101,18 +117,80 @@ def message_register(self, isGroup: bool = False, isDivision: bool = False, isPyq: bool = False): - return self._processing_universal_func(isGroup, isDivision, isPyq) + return self._processing_universal_func(isGroup, + isDivision, + isPyq, + register_name='common', + allow_other_receive=True, + judge_msg=lambda x: True) + + +def revoke_message_register(self, + isGroup: bool = False, + isDivision: bool = False, + isPyq: bool = False, + allow_other_receive: bool = True): + def judge_msg(msg): + if msg['isRevokeMsg'] and msg['revokmsgid'] is not None: + return True + return False + + return self._processing_async_func(isGroup, + isDivision, + isPyq, + register_name='revokeMessage', + allow_other_receive=allow_other_receive, + judge_msg=judge_msg) + + +def group_changed_register(self, + isGroup: bool = False, + isDivision: bool = False, + isPyq: bool = False, + allow_other_receive: bool = True): + def judge_msg(msg): + if msg['isGroup'] and msg['data']['type'] == 10000: + if '加入了群聊' in msg['data']['content'] or '出了群聊' in msg['data']['content']: + return True + return False + + return self._processing_async_func(isGroup, + isDivision, + isPyq, + register_name='groupChanged', + allow_other_receive=allow_other_receive, + judge_msg=judge_msg) + + +def custom_message_register(self, + register_name: str, + msg_judge_func: Callable[[WxMsg], bool], + allow_other_receive: bool, + isGroup: bool = False, + isDivision: bool = False, + isPyq: bool = False): + return self._processing_async_func(isGroup, + isDivision, + isPyq, + register_name=register_name, + allow_other_receive=allow_other_receive, + judge_msg=msg_judge_func) def async_message_register(self, isGroup: bool = False, isDivision: bool = False, isPyq: bool = False): - return self._processing_async_func(isGroup, isDivision, isPyq) + return self._processing_async_func(isGroup, + isDivision, + isPyq, + register_name='common', + allow_other_receive=True, + judge_msg=lambda x: True) -def run(self, *args, **kwargs): - self._wcf.enable_receiving_msg(*args, pyq=True, **kwargs) +def run(self, pyq=True): + self._wcf.enable_receiving_msg(pyq=pyq) Thread(target=self._process_msg, name="GetMessage", args=(self._wcf,), daemon=True).start() self._LOG.debug("开始接受消息") self._wcf.keep_running() diff --git a/clients/pyauto/wcfauto/event/__init__.py b/clients/pyauto/wcfauto/event/__init__.py index 463e874..ef6920e 100644 --- a/clients/pyauto/wcfauto/event/__init__.py +++ b/clients/pyauto/wcfauto/event/__init__.py @@ -4,3 +4,8 @@ from wcfauto.event.event import Event from wcfauto.event.core import load_function Event = load_function(Event) + + + + + diff --git a/clients/pyauto/wcfauto/event/core.py b/clients/pyauto/wcfauto/event/core.py index 424e68a..8a2f975 100644 --- a/clients/pyauto/wcfauto/event/core.py +++ b/clients/pyauto/wcfauto/event/core.py @@ -1,7 +1,10 @@ # -*- coding: utf-8 -*- -import asyncio import traceback +from typing import Callable, Any +from wcfauto.wcf import WcfV2 as Wcf +from wcfauto.wcf import WxMsgV2 as WxMsg +import asyncio from threading import Thread @@ -11,17 +14,32 @@ def load_function(cls): return cls -def _add_callback(self, func, bot): +def _add_callback(self, + func: Callable[[Any], Any], + bot: Wcf, + kind: str, + register_name: str, + allow_other_rec: bool, + judge_msg: Callable[[WxMsg], bool]): """ 消息处理函数加载器 - :param func: 消息处理函数 - :param args: 消息处理函数参数 - :param kwargs: 消息处理函数参数 + :param func: 装饰器装饰的函数 + :param bot: Wcf类 + :param kind: 装饰器所处类别, 分为异步和同步 + :param register_name: 装饰器所处类别下的函数类名(主要区分不同装饰器的作用, 以及为其中的 allow_other_rec参数做准备) + :param allow_other_rec: 是否允许消息分发到其他不同类名装饰器(该参数只对同一个类 kind中的不同函数类装饰器有效 + 同步和异步装饰器属于互不干扰类型, 每个大类 kind中的参数只对该大类中的函数类装饰器有效) + :param judge_msg: 判断是否为该装饰器所处理的消息的函数 """ if func in self._message_callback_func_list: return self._message_callback_func_list.append(func) - self._message_callback_func[func] = bot - + self._cbFunc[func] = { + 'bot': bot, + 'kind': kind, + 'func_kind': register_name, + 'allow_other_rec': allow_other_rec, + 'judge_msg': judge_msg, + } def _run_func(self): @@ -31,29 +49,84 @@ def _run_func(self): try: async_func = [] universal_func = [] - for ele in self._message_callback_func: + # 将装饰器中的函数分为异步和同步 + for ele in self._cbFunc: if asyncio.iscoroutinefunction(ele): async_func.append(ele) else: universal_func.append(ele) + # 支持在这里自定义函数过滤消息, 以及使得否分发给所有函数 + def filter_message(universal_func_list, async_func_list, msg): + """ + 根据每个装饰器的过滤要求过滤消息, 以及对分发的函数的限制 + """ + # 对分类后的函数, 分别进行消息函数 函数类分类 + _join_thread_func = [] + _join_loop_func = [] + # 对函数类分类结果判断是否分类过, 若分过类直接跳过, 拒绝再次分类, 否则进行一遍筛选 + if not self._inCache: + # 对传入的过滤函数按照 kind进行分组 + for f_ele in self._cbFunc: + if self._kind_dict[self._cbFunc[f_ele]['kind']].get(self._cbFunc[f_ele]['func_kind'], None) is None: + self._kind_dict[self._cbFunc[f_ele]['kind']][self._cbFunc[f_ele]['func_kind']] = { + 'kind': self._cbFunc[f_ele]['kind'], + 'func_kind': self._cbFunc[f_ele]['func_kind'], + 'allow_other_rec': self._cbFunc[f_ele]['allow_other_rec'], + 'judge_msg': self._cbFunc[f_ele]['judge_msg'], + } + self._kind_dict[self._cbFunc[f_ele]['kind']][self._cbFunc[f_ele]['func_kind']]['fun'] = [] + self._kind_dict[self._cbFunc[f_ele]['kind']][self._cbFunc[f_ele]['func_kind']]['fun'].append(f_ele) + if f_ele not in (li := self._kind_dict[self._cbFunc[f_ele]['kind']][self._cbFunc[f_ele]['func_kind']]['fun']): + li.append(f_ele) + + self._inCache = True + + # 判断任意两个大类 kind 中是否存在全函数类 allow_other_rec 都为 True 的情况, 若出现, 则不进行该大类的函数的消息限制分发, 消息分发给所有函数类 + if len(lis := ([al_ele['allow_other_rec'] for al_ele in self._kind_dict['async'].values()])) == 1 and lis[0]: + self._loop_flag = True + _join_loop_func = async_func_list + + if len(lis := ([al_ele['allow_other_rec'] for al_ele in self._kind_dict['universal'].values()])) == 1 and lis[0]: + self._thread_flag = True + _join_thread_func = universal_func_list + + # 进行仔细过滤, 对有消息分发限制的函数进行识别, 只把消息分发给符合限制的函数 + for f_ele in self._kind_dict.values(): + for k_ele in f_ele.values(): + if k_ele['kind'] == 'async' and not self._loop_flag: + if k_ele['judge_msg'](msg): + if not k_ele['allow_other_rec']: + _join_loop_func = self._kind_dict[k_ele['kind']][k_ele['func_kind']]['fun'] + break + else: + _join_loop_func.extend(self._kind_dict[k_ele['kind']][k_ele['func_kind']]['fun']) + elif k_ele['kind'] == 'universal' and not self._thread_flag: + if k_ele['judge_msg'](msg) and not k_ele['allow_other_rec']: + if not k_ele['allow_other_rec']: + _join_thread_func = self._kind_dict[k_ele['kind']][k_ele['func_kind']]['fun'] + break + else: + _join_thread_func.extend(self._kind_dict[k_ele['kind']][k_ele['func_kind']]['fun']) + return _join_thread_func, _join_loop_func + + universal_func, async_func = filter_message(universal_func, async_func, self._message) + # 同步函数运行器 def run_universal_func(): for fn in universal_func: - fn(self._message_callback_func[fn], self._message) + fn(self._cbFunc[fn]['bot'], self._message) if len(universal_func) != 0: Thread(target=run_universal_func).start() if len(async_func) == 0: return # 异步函数运行器 async def _run_callback(): - tasks = [asyncio.create_task(func(self._message_callback_func[func], self._message)) - for func in async_func] - await asyncio.wait(tasks) + _tasks = [asyncio.create_task( + a_ele( + self._cbFunc[a_ele]['bot'], self._message)) for a_ele in async_func] + return await asyncio.wait(_tasks) self._loop.run_until_complete(_run_callback()) except: traceback.print_exc() - - - diff --git a/clients/pyauto/wcfauto/event/event.py b/clients/pyauto/wcfauto/event/event.py index 4925ba9..39e0980 100644 --- a/clients/pyauto/wcfauto/event/event.py +++ b/clients/pyauto/wcfauto/event/event.py @@ -1,14 +1,21 @@ # -*- coding: utf-8 -*- - -import asyncio -import logging from abc import abstractmethod +import logging +from typing import Callable, Any +from wcfauto.wcf import WcfV2 as Wcf +from wcfauto.wcf import WxMsgV2 as WxMsg +import asyncio class Event(object): - _message_callback_func = {} + _cbFunc = {} + _loop_flag = False + _thread_flag = False + _inCache = False _message_callback_func_list = [] _loop = asyncio.get_event_loop() + _filter_cache = {} + _kind_dict = {'async': {}, 'universal': {}} def __init__(self): super(Event, self).__init__() @@ -16,18 +23,29 @@ class Event(object): self._logger: logging = logging.getLogger() @abstractmethod - def _add_callback(self, func, *args, **kwargs): + def _add_callback(self, + func: Callable[[Any], Any], + bot: Wcf, + kind: str, + register_name: str, + allow_other_rec: bool, + judge_msg: Callable[[WxMsg], bool]): """ 消息处理函数加载器 - :param func: 消息处理函数 - :param args: 消息处理函数参数 - :param kwargs: 消息处理函数参数 + :param func: 装饰器装饰的函数 + :param bot: Wcf类 + :param kind: 装饰器所处类别, 分为异步和同步 + :param register_name: 装饰器所处类别下的函数类名(主要区分不同装饰器的作用, 以及为其中的 allow_other_rec参数做准备) + :param allow_other_rec: 是否允许消息分发到其他不同类名装饰器(该参数只对同一个类 kind中的不同函数类装饰器有效 + 同步和异步装饰器属于互不干扰类型, 每个大类 kind中的参数只对该大类中的函数类装饰器有效) + :param judge_msg: 判断是否为该装饰器所处理的消息的函数 """ raise NotImplementedError @abstractmethod def _run_func(self): """ - 消息分发器, 将消息发送给所有消息处理函数 + 消息分发器, 将消息发送给可接受消息的消息处理函数 """ raise NotImplementedError + diff --git a/clients/pyauto/wcfauto/msg_list.py b/clients/pyauto/wcfauto/msg_list.py new file mode 100644 index 0000000..dc9257f --- /dev/null +++ b/clients/pyauto/wcfauto/msg_list.py @@ -0,0 +1,51 @@ +# coding: utf-8 +# @Author: 小杨大帅哥 +import queue +import time +from threading import Thread + +class messageList(list): + def __init__(self, *args, **kwargs): + super(messageList, self).__init__(*args, **kwargs) + self.__isRunning = True + self.__th = None + self.__time_step = 2*60 + self.__msg_queen = queue.Queue() + self.start() + + def append(self, item) -> None: + self.__isRunning = True + if item['data'].get('msgid', None) is None: + return + super(messageList, self).append({str(item['data']['msgid']): item}) + self.__msg_queen.put({'data': item, 'submit_time': time.time()}) + + def stop(self): + self.__isRunning = False + + def find_msg(self, msgid): + msgid = str(msgid) + for msg_ele in self: + if str(msgid) == str(list(msg_ele.keys())[0]): + return msg_ele[msgid] + return None + + def start(self): + def _start(): + while True: + if self.__isRunning: + try: + new_data = self.__msg_queen.get() + now = time.time() + if now - new_data['submit_time'] >= self.__time_step: + self.remove({str(new_data['data']['data']['msgid']): new_data['data']}) + continue + time.sleep(self.__time_step - (now - new_data['submit_time'])) + self.remove({str(new_data['data']['data']['msgid']): new_data['data']}) + except (queue.Empty, KeyboardInterrupt): + pass + self.__th = Thread(target=_start, name='run', daemon=True) + self.__th.start() + + +msg_list = messageList() diff --git a/clients/pyauto/wcfauto/wcf.py b/clients/pyauto/wcfauto/wcf.py index 846469b..5ec1dc5 100644 --- a/clients/pyauto/wcfauto/wcf.py +++ b/clients/pyauto/wcfauto/wcf.py @@ -2,12 +2,12 @@ import re import time - +from wcfauto.msg_list import msg_list from wcferry import Wcf, WxMsg class WcfV2(Wcf): - def __init__(self, host: str, port: int = 10086, debug: bool = True) -> None: + def __init__(self, host: str = None, port: int = 10086, debug: bool = True) -> None: super().__init__(host, port, debug) def get_msg(self, block=True) -> WxMsg: @@ -27,8 +27,9 @@ class WcfV2(Wcf): class WxMsgV2(WxMsg): """微信消息 + Attributes: - type (int): 消息类型,可通过 `get_msg_types` 获取 + type (int): 消息类型,可通过 `wcf类的 get_msg_types` 获取 id (str): 消息 id xml (str): 消息 xml 部分 sender (str): 消息发送人 @@ -39,8 +40,6 @@ class WxMsgV2(WxMsg): """ 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 @@ -49,8 +48,8 @@ class WxMsgV2(WxMsg): self._sender = msg.sender self._roomid = msg.roomid self._content = msg.content - self._thumb = msg.thumb - self._extra = msg.extra + self._thumb = msg.thumb.replace("\\", "/") + self._extra = msg.extra.replace("\\", "/") 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, @@ -64,20 +63,54 @@ class WxMsgV2(WxMsg): '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, } + }, 'revokmsgid': None, 'isRevokeMsg': False} self.__revokmsg_p() + self.__initial() + msg_list.append(self) def __revokmsg_p(self): rmsg = self.__data['data']['content'] - rev_type = re.findall('", rmsg) + rev_type = re.findall(r'", rmsg) if len(rev_type) == 0 or len(rev_w) == 0: return - if rev_type[0] == 'revokemsg' and rev_w[0] == '你撤回了一条消息': + if rev_type[0] == 'revokemsg' and '撤回了一条消息' in rev_w[0]: self.__data['data']['content'] = rev_w[0] self.__data['isRevokeMsg'] = True self.__data['revokmsgid'] = re.findall('(.*?)', rmsg)[0] + def __initial(self): + try: + if self.__data['data']['type'] == 51: + op_id = re.findall(r"", self.__data['data']['content'])[0] + name = re.findall(r'(.*?)', self.__data['data']['content'])[0].strip() + if name == 'lastMessage' and str(op_id) == '2': + username = re.findall(r'(.*?)', self.__data['data']['content'])[0] + self.__data['data']['xml'] = self.__data['data']['content'] + self.__data['data']['content'] = f'其他设备进入 [{username}] 聊天界面' + return + if name == 'HandOffMaster' and str(op_id) == '11': + opcode = re.findall(r'opcode="(\d+)"', self.__data['data']['content'])[0] + self.__data['data']['xml'] = self.__data['data']['content'] + title = '' or re.findall(r'<!\[CDATA\[(.*?)]]>', self.__data['data']['content'])[0] + type_id = re.findall(r' str: return repr(self.__data) @@ -139,3 +172,14 @@ class WxMsgV2(WxMsg): def is_text(self) -> bool: """是否文本消息""" return self.type == 1 + + def get_revoke_msg(self) -> "WxMsg" or None: + """ + 获取撤回的消息 + 注意仅有撤回的消息在调用函数会返回消息内容, 自行修改 msg内容或者不是撤回的消息在调用该函数后会返回 None + 未撤回的消息调用该函数会返回 None + :return: 返回被撤回的信息, 信息的类仍然是 WxMsg + """ + if self.__data['isRevokeMsg'] and (self.__data['revokmsgid'] is not None): + return msg_list.find_msg(self.__data['revokmsgid']) + return None